From 38862c8ef99b9fea638b590de4a75a8da2d94101 Mon Sep 17 00:00:00 2001 From: Amorilia Date: Sun, 30 Mar 2008 11:33:06 +0000 Subject: [PATCH] setting eol-style properties to native --- NvTriStrip/NvTriStrip.h | 290 +-- NvTriStrip/NvTriStripObjects.h | 486 ++-- NvTriStrip/VertexCache.cpp | 174 +- NvTriStrip/VertexCache.h | 54 +- NvTriStrip/qtwrapper.cpp | 154 +- NvTriStrip/qtwrapper.h | 28 +- basemodel.cpp | 1522 +++++------ basemodel.h | 678 ++--- fsengine/bsa.cpp | 1032 ++++---- fsengine/bsa.h | 234 +- fsengine/fsengine.cpp | 982 ++++---- fsengine/fsengine.h | 202 +- fsengine/fsmanager.cpp | 428 ++-- fsengine/fsmanager.h | 186 +- gl/glcontrolable.h | 150 +- gl/glcontroller.cpp | 1354 +++++----- gl/glcontroller.h | 294 +-- gl/glmarker.cpp | 92 +- gl/glmarker.h | 92 +- gl/glmesh.cpp | 1618 ++++++------ gl/glmesh.h | 200 +- gl/glnode.cpp | 3330 ++++++++++++------------ gl/glnode.h | 362 +-- gl/glparticles.cpp | 976 +++---- gl/glparticles.h | 140 +- gl/glproperty.cpp | 1598 ++++++------ gl/glproperty.h | 704 +++--- gl/glscene.cpp | 696 ++--- gl/glscene.h | 238 +- gl/gltex.cpp | 916 +++---- gl/gltex.h | 200 +- gl/gltexloaders.cpp | 1780 ++++++------- gl/gltexloaders.h | 122 +- gl/gltools.cpp | 1218 ++++----- gl/gltools.h | 414 +-- gl/marker/constraints.h | 564 ++--- gl/marker/furniture.h | 2142 ++++++++-------- gl/renderer.cpp | 1634 ++++++------ gl/renderer.h | 410 +-- glview.cpp | 2524 +++++++++---------- glview.h | 436 ++-- importex/3ds.cpp | 1492 +++++------ importex/3ds.h | 1186 ++++----- importex/importex.cpp | 186 +- importex/obj.cpp | 1828 +++++++------- kfmmodel.cpp | 658 ++--- kfmmodel.h | 250 +- kfmxml.cpp | 526 ++-- message.cpp | 112 +- message.h | 50 +- nifdelegate.cpp | 590 ++--- nifitem.h | 556 ++-- nifmodel.cpp | 4336 ++++++++++++++++---------------- nifmodel.h | 746 +++--- nifproxy.cpp | 1072 ++++---- nifproxy.h | 188 +- nifskope.cpp | 2296 ++++++++--------- nifskope.h | 528 ++-- niftypes.cpp | 848 +++---- niftypes.h | 2130 ++++++++-------- nifvalue.cpp | 2358 ++++++++--------- nifvalue.h | 956 +++---- nifxml.cpp | 952 +++---- options.cpp | 1644 ++++++------ options.h | 348 +-- spellbook.cpp | 542 ++-- spellbook.h | 254 +- spells/animation.cpp | 536 ++-- spells/blocks.cpp | 1568 ++++++------ spells/blocks.h | 34 +- spells/color.cpp | 60 +- spells/flags.cpp | 824 +++--- spells/havok.cpp | 660 ++--- spells/headerstring.cpp | 276 +- spells/light.cpp | 188 +- spells/material.cpp | 250 +- spells/mesh.cpp | 1198 ++++----- spells/mesh.h | 34 +- spells/misc.cpp | 272 +- spells/morphctrl.cpp | 222 +- spells/normals.cpp | 408 +-- spells/optimize.cpp | 870 +++---- spells/sanitize.cpp | 626 ++--- spells/skeleton.cpp | 2012 +++++++-------- spells/skeleton.h | 64 +- spells/stringpalette.cpp | 370 +-- spells/strippify.cpp | 818 +++--- spells/tangentspace.cpp | 456 ++-- spells/tangentspace.h | 34 +- spells/texture.cpp | 1120 ++++----- spells/transform.cpp | 738 +++--- spells/transform.h | 32 +- widgets/colorwheel.cpp | 752 +++--- widgets/colorwheel.h | 188 +- widgets/copyfnam.cpp | 166 +- widgets/copyfnam.h | 128 +- widgets/fileselect.cpp | 540 ++-- widgets/fileselect.h | 274 +- widgets/floatedit.cpp | 342 +-- widgets/floatedit.h | 188 +- widgets/floatslider.cpp | 844 +++---- widgets/floatslider.h | 244 +- widgets/groupbox.cpp | 166 +- widgets/groupbox.h | 114 +- widgets/nifeditors.cpp | 798 +++--- widgets/nifeditors.h | 380 +-- widgets/nifview.cpp | 396 +-- widgets/nifview.h | 160 +- widgets/refrbrowser.cpp | 238 +- widgets/refrbrowser.h | 120 +- widgets/uvedit.cpp | 2062 +++++++-------- widgets/uvedit.h | 302 +-- widgets/valuedit.cpp | 1732 ++++++------- widgets/valueedit.h | 422 ++-- widgets/xmlcheck.cpp | 972 +++---- widgets/xmlcheck.h | 226 +- 116 files changed, 41355 insertions(+), 41355 deletions(-) diff --git a/NvTriStrip/NvTriStrip.h b/NvTriStrip/NvTriStrip.h index 19a86ee68..203c80ed9 100644 --- a/NvTriStrip/NvTriStrip.h +++ b/NvTriStrip/NvTriStrip.h @@ -1,145 +1,145 @@ -#ifndef NVTRISTRIP_H -#define NVTRISTRIP_H - -#ifndef NULL -#define NULL 0 -#endif - -//#pragma comment(lib, "nvtristrip") - -//////////////////////////////////////////////////////////////////////////////////////// -// Public interface for stripifier -//////////////////////////////////////////////////////////////////////////////////////// - -//GeForce1 and 2 cache size -#define CACHESIZE_GEFORCE1_2 16 - -//GeForce3 cache size -#define CACHESIZE_GEFORCE3 24 - -enum PrimType -{ - PT_LIST, - PT_STRIP, - PT_FAN -}; - -struct PrimitiveGroup -{ - PrimType type; - unsigned int numIndices; - unsigned short* indices; - -//////////////////////////////////////////////////////////////////////////////////////// - - PrimitiveGroup() : type(PT_STRIP), numIndices(0), indices(NULL) {} - ~PrimitiveGroup() - { - if(indices) - delete[] indices; - indices = NULL; - } -}; - - -//////////////////////////////////////////////////////////////////////////////////////// -// EnableRestart() -// -// For GPUs that support primitive restart, this sets a value as the restart index -// -// Restart is meaningless if strips are not being stitched together, so enabling restart -// makes NvTriStrip forcing stitching. So, you'll get back one strip. -// -// Default value: disabled -// -void EnableRestart(const unsigned int restartVal); - -//////////////////////////////////////////////////////////////////////////////////////// -// DisableRestart() -// -// For GPUs that support primitive restart, this disables using primitive restart -// -void DisableRestart(); - - -//////////////////////////////////////////////////////////////////////////////////////// -// SetCacheSize() -// -// Sets the cache size which the stripfier uses to optimize the data. -// Controls the length of the generated individual strips. -// This is the "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 -// You may want to play around with this number to tweak performance. -// -// Default value: 16 -// -void SetCacheSize(const unsigned int cacheSize); - - -//////////////////////////////////////////////////////////////////////////////////////// -// SetStitchStrips() -// -// bool to indicate whether to stitch together strips into one huge strip or not. -// If set to true, you'll get back one huge strip stitched together using degenerate -// triangles. -// If set to false, you'll get back a large number of separate strips. -// -// Default value: true -// -void SetStitchStrips(const bool bStitchStrips); - - -//////////////////////////////////////////////////////////////////////////////////////// -// SetMinStripSize() -// -// Sets the minimum acceptable size for a strip, in triangles. -// All strips generated which are shorter than this will be thrown into one big, separate list. -// -// Default value: 0 -// -void SetMinStripSize(const unsigned int minSize); - - -//////////////////////////////////////////////////////////////////////////////////////// -// SetListsOnly() -// -// If set to true, will return an optimized list, with no strips at all. -// -// Default value: false -// -void SetListsOnly(const bool bListsOnly); - - -//////////////////////////////////////////////////////////////////////////////////////// -// GenerateStrips() -// -// in_indices: input index list, the indices you would use to render -// in_numIndices: number of entries in in_indices -// primGroups: array of optimized/stripified PrimitiveGroups -// numGroups: number of groups returned -// -// Be sure to call delete[] on the returned primGroups to avoid leaking mem -// -bool GenerateStrips(const unsigned short* in_indices, const unsigned int in_numIndices, - PrimitiveGroup** primGroups, unsigned short* numGroups, bool validateEnabled = false); - - -//////////////////////////////////////////////////////////////////////////////////////// -// RemapIndices() -// -// Function to remap your indices to improve spatial locality in your vertex buffer. -// -// in_primGroups: array of PrimitiveGroups you want remapped -// numGroups: number of entries in in_primGroups -// numVerts: number of vertices in your vertex buffer, also can be thought of as the range -// of acceptable values for indices in your primitive groups. -// remappedGroups: array of remapped PrimitiveGroups -// -// Note that, according to the remapping handed back to you, you must reorder your -// vertex buffer. -// -// Credit goes to the MS Xbox crew for the idea for this interface. -// -void RemapIndices(const PrimitiveGroup* in_primGroups, const unsigned short numGroups, - const unsigned short numVerts, PrimitiveGroup** remappedGroups); - -#endif +#ifndef NVTRISTRIP_H +#define NVTRISTRIP_H + +#ifndef NULL +#define NULL 0 +#endif + +//#pragma comment(lib, "nvtristrip") + +//////////////////////////////////////////////////////////////////////////////////////// +// Public interface for stripifier +//////////////////////////////////////////////////////////////////////////////////////// + +//GeForce1 and 2 cache size +#define CACHESIZE_GEFORCE1_2 16 + +//GeForce3 cache size +#define CACHESIZE_GEFORCE3 24 + +enum PrimType +{ + PT_LIST, + PT_STRIP, + PT_FAN +}; + +struct PrimitiveGroup +{ + PrimType type; + unsigned int numIndices; + unsigned short* indices; + +//////////////////////////////////////////////////////////////////////////////////////// + + PrimitiveGroup() : type(PT_STRIP), numIndices(0), indices(NULL) {} + ~PrimitiveGroup() + { + if(indices) + delete[] indices; + indices = NULL; + } +}; + + +//////////////////////////////////////////////////////////////////////////////////////// +// EnableRestart() +// +// For GPUs that support primitive restart, this sets a value as the restart index +// +// Restart is meaningless if strips are not being stitched together, so enabling restart +// makes NvTriStrip forcing stitching. So, you'll get back one strip. +// +// Default value: disabled +// +void EnableRestart(const unsigned int restartVal); + +//////////////////////////////////////////////////////////////////////////////////////// +// DisableRestart() +// +// For GPUs that support primitive restart, this disables using primitive restart +// +void DisableRestart(); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetCacheSize() +// +// Sets the cache size which the stripfier uses to optimize the data. +// Controls the length of the generated individual strips. +// This is the "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 +// You may want to play around with this number to tweak performance. +// +// Default value: 16 +// +void SetCacheSize(const unsigned int cacheSize); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetStitchStrips() +// +// bool to indicate whether to stitch together strips into one huge strip or not. +// If set to true, you'll get back one huge strip stitched together using degenerate +// triangles. +// If set to false, you'll get back a large number of separate strips. +// +// Default value: true +// +void SetStitchStrips(const bool bStitchStrips); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetMinStripSize() +// +// Sets the minimum acceptable size for a strip, in triangles. +// All strips generated which are shorter than this will be thrown into one big, separate list. +// +// Default value: 0 +// +void SetMinStripSize(const unsigned int minSize); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetListsOnly() +// +// If set to true, will return an optimized list, with no strips at all. +// +// Default value: false +// +void SetListsOnly(const bool bListsOnly); + + +//////////////////////////////////////////////////////////////////////////////////////// +// GenerateStrips() +// +// in_indices: input index list, the indices you would use to render +// in_numIndices: number of entries in in_indices +// primGroups: array of optimized/stripified PrimitiveGroups +// numGroups: number of groups returned +// +// Be sure to call delete[] on the returned primGroups to avoid leaking mem +// +bool GenerateStrips(const unsigned short* in_indices, const unsigned int in_numIndices, + PrimitiveGroup** primGroups, unsigned short* numGroups, bool validateEnabled = false); + + +//////////////////////////////////////////////////////////////////////////////////////// +// RemapIndices() +// +// Function to remap your indices to improve spatial locality in your vertex buffer. +// +// in_primGroups: array of PrimitiveGroups you want remapped +// numGroups: number of entries in in_primGroups +// numVerts: number of vertices in your vertex buffer, also can be thought of as the range +// of acceptable values for indices in your primitive groups. +// remappedGroups: array of remapped PrimitiveGroups +// +// Note that, according to the remapping handed back to you, you must reorder your +// vertex buffer. +// +// Credit goes to the MS Xbox crew for the idea for this interface. +// +void RemapIndices(const PrimitiveGroup* in_primGroups, const unsigned short numGroups, + const unsigned short numVerts, PrimitiveGroup** remappedGroups); + +#endif diff --git a/NvTriStrip/NvTriStripObjects.h b/NvTriStrip/NvTriStripObjects.h index 3b0e66df3..b4cc1e6a3 100644 --- a/NvTriStrip/NvTriStripObjects.h +++ b/NvTriStrip/NvTriStripObjects.h @@ -1,243 +1,243 @@ - -#ifndef NV_TRISTRIP_OBJECTS_H -#define NV_TRISTRIP_OBJECTS_H - -#include -#include -#include -#include "VertexCache.h" - -///////////////////////////////////////////////////////////////////////////////// -// -// Types defined for stripification -// -///////////////////////////////////////////////////////////////////////////////// - -struct MyVertex { - float x, y, z; - float nx, ny, nz; -}; - -typedef MyVertex MyVector; - -struct MyFace { - int v1, v2, v3; - float nx, ny, nz; -}; - - -class NvFaceInfo { -public: - - // vertex indices - NvFaceInfo(int v0, int v1, int v2, bool bIsFake = false){ - m_v0 = v0; m_v1 = v1; m_v2 = v2; - m_stripId = -1; - m_testStripId = -1; - m_experimentId = -1; - m_bIsFake = bIsFake; - } - - // data members are left public - int m_v0, m_v1, m_v2; - int m_stripId; // real strip Id - int m_testStripId; // strip Id in an experiment - int m_experimentId; // in what experiment was it given an experiment Id? - bool m_bIsFake; //if true, will be deleted when the strip it's in is deleted -}; - -// nice and dumb edge class that points knows its -// indices, the two faces, and the next edge using -// the lesser of the indices -class NvEdgeInfo { -public: - - // constructor puts 1 ref on us - NvEdgeInfo (int v0, int v1){ - m_v0 = v0; - m_v1 = v1; - m_face0 = NULL; - m_face1 = NULL; - m_nextV0 = NULL; - m_nextV1 = NULL; - - // we will appear in 2 lists. this is a good - // way to make sure we delete it the second time - // we hit it in the edge infos - m_refCount = 2; - - } - - // ref and unref - void Unref () { if (--m_refCount == 0) delete this; } - - // data members are left public - unsigned int m_refCount; - NvFaceInfo *m_face0, *m_face1; - int m_v0, m_v1; - NvEdgeInfo *m_nextV0, *m_nextV1; -}; - - -// This class is a quick summary of parameters used -// to begin a triangle strip. Some operations may -// want to create lists of such items, so they were -// pulled out into a class -class NvStripStartInfo { -public: - NvStripStartInfo(NvFaceInfo *startFace, NvEdgeInfo *startEdge, bool toV1){ - m_startFace = startFace; - m_startEdge = startEdge; - m_toV1 = toV1; - } - NvFaceInfo *m_startFace; - NvEdgeInfo *m_startEdge; - bool m_toV1; -}; - - -typedef std::vector NvFaceInfoVec; -typedef std::list NvFaceInfoList; -typedef std::list NvStripList; -typedef std::vector NvEdgeInfoVec; - -typedef std::vector WordVec; -typedef std::vector IntVec; -typedef std::vector MyVertexVec; -typedef std::vector MyFaceVec; - -template -inline void SWAP(T& first, T& second) -{ - T temp = first; - first = second; - second = temp; -} - -// This is a summary of a strip that has been built -class NvStripInfo { -public: - - // A little information about the creation of the triangle strips - NvStripInfo(const NvStripStartInfo &startInfo, int stripId, int experimentId = -1) : - m_startInfo(startInfo) - { - m_stripId = stripId; - m_experimentId = experimentId; - visited = false; - m_numDegenerates = 0; - } - - // This is an experiment if the experiment id is >= 0 - inline bool IsExperiment () const { return m_experimentId >= 0; } - - inline bool IsInStrip (const NvFaceInfo *faceInfo) const - { - if(faceInfo == NULL) - return false; - - return (m_experimentId >= 0 ? faceInfo->m_testStripId == m_stripId : faceInfo->m_stripId == m_stripId); - } - - bool SharesEdge(const NvFaceInfo* faceInfo, NvEdgeInfoVec &edgeInfos); - - // take the given forward and backward strips and combine them together - void Combine(const NvFaceInfoVec &forward, const NvFaceInfoVec &backward); - - //returns true if the face is "unique", i.e. has a vertex which doesn't exist in the faceVec - bool Unique(NvFaceInfoVec& faceVec, NvFaceInfo* face); - - // mark the triangle as taken by this strip - bool IsMarked (NvFaceInfo *faceInfo); - void MarkTriangle(NvFaceInfo *faceInfo); - - // build the strip - void Build(NvEdgeInfoVec &edgeInfos, NvFaceInfoVec &faceInfos); - - // public data members - NvStripStartInfo m_startInfo; - NvFaceInfoVec m_faces; - int m_stripId; - int m_experimentId; - - bool visited; - - int m_numDegenerates; -}; - -typedef std::vector NvStripInfoVec; - - -//The actual stripifier -class NvStripifier { -public: - - // Constructor - NvStripifier(); - ~NvStripifier(); - - //the target vertex cache size, the structure to place the strips in, and the input indices - void Stripify(const WordVec &in_indices, const int in_cacheSize, const int in_minStripLength, - const unsigned short maxIndex, NvStripInfoVec &allStrips, NvFaceInfoVec &allFaces); - void CreateStrips(const NvStripInfoVec& allStrips, IntVec& stripIndices, const bool bStitchStrips, unsigned int& numSeparateStrips, const bool bRestart, const unsigned int restartVal); - - static int GetUniqueVertexInB(NvFaceInfo *faceA, NvFaceInfo *faceB); - //static int GetSharedVertex(NvFaceInfo *faceA, NvFaceInfo *faceB); - static void GetSharedVertices(NvFaceInfo *faceA, NvFaceInfo *faceB, int* vertex0, int* vertex1); - - static bool IsDegenerate(const NvFaceInfo* face); - static bool IsDegenerate(const unsigned short v0, const unsigned short v1, const unsigned short v2); - -protected: - - WordVec indices; - int cacheSize; - int minStripLength; - float meshJump; - bool bFirstTimeResetPoint; - - ///////////////////////////////////////////////////////////////////////////////// - // - // Big mess of functions called during stripification - // - ///////////////////////////////////////////////////////////////////////////////// - - //******************** - bool IsMoneyFace(const NvFaceInfo& face); - bool FaceContainsIndex(const NvFaceInfo& face, const unsigned int index); - - bool IsCW(NvFaceInfo *faceInfo, int v0, int v1); - bool NextIsCW(const int numIndices); - - static int GetNextIndex(const WordVec &indices, NvFaceInfo *face); - static NvEdgeInfo *FindEdgeInfo(NvEdgeInfoVec &edgeInfos, int v0, int v1); - static NvFaceInfo *FindOtherFace(NvEdgeInfoVec &edgeInfos, int v0, int v1, NvFaceInfo *faceInfo); - NvFaceInfo *FindGoodResetPoint(NvFaceInfoVec &faceInfos, NvEdgeInfoVec &edgeInfos); - - void FindAllStrips(NvStripInfoVec &allStrips, NvFaceInfoVec &allFaceInfos, NvEdgeInfoVec &allEdgeInfos, int numSamples); - void SplitUpStripsAndOptimize(NvStripInfoVec &allStrips, NvStripInfoVec &outStrips, NvEdgeInfoVec& edgeInfos, NvFaceInfoVec& outFaceList); - void RemoveSmallStrips(NvStripInfoVec& allStrips, NvStripInfoVec& allBigStrips, NvFaceInfoVec& faceList); - - bool FindTraversal(NvFaceInfoVec &faceInfos, NvEdgeInfoVec &edgeInfos, NvStripInfo *strip, NvStripStartInfo &startInfo); - int CountRemainingTris(std::list::iterator iter, std::list::iterator end); - - void CommitStrips(NvStripInfoVec &allStrips, const NvStripInfoVec &strips); - - float AvgStripSize(const NvStripInfoVec &strips); - int FindStartPoint(NvFaceInfoVec &faceInfos, NvEdgeInfoVec &edgeInfos); - - void UpdateCacheStrip(VertexCache* vcache, NvStripInfo* strip); - void UpdateCacheFace(VertexCache* vcache, NvFaceInfo* face); - float CalcNumHitsStrip(VertexCache* vcache, NvStripInfo* strip); - int CalcNumHitsFace(VertexCache* vcache, NvFaceInfo* face); - int NumNeighbors(NvFaceInfo* face, NvEdgeInfoVec& edgeInfoVec); - - void BuildStripifyInfo(NvFaceInfoVec &faceInfos, NvEdgeInfoVec &edgeInfos, const unsigned short maxIndex); - bool AlreadyExists(NvFaceInfo* faceInfo, NvFaceInfoVec& faceInfos); - - // let our strip info classes and the other classes get - // to these protected stripificaton methods if they want - friend class NvStripInfo; -}; - -#endif + +#ifndef NV_TRISTRIP_OBJECTS_H +#define NV_TRISTRIP_OBJECTS_H + +#include +#include +#include +#include "VertexCache.h" + +///////////////////////////////////////////////////////////////////////////////// +// +// Types defined for stripification +// +///////////////////////////////////////////////////////////////////////////////// + +struct MyVertex { + float x, y, z; + float nx, ny, nz; +}; + +typedef MyVertex MyVector; + +struct MyFace { + int v1, v2, v3; + float nx, ny, nz; +}; + + +class NvFaceInfo { +public: + + // vertex indices + NvFaceInfo(int v0, int v1, int v2, bool bIsFake = false){ + m_v0 = v0; m_v1 = v1; m_v2 = v2; + m_stripId = -1; + m_testStripId = -1; + m_experimentId = -1; + m_bIsFake = bIsFake; + } + + // data members are left public + int m_v0, m_v1, m_v2; + int m_stripId; // real strip Id + int m_testStripId; // strip Id in an experiment + int m_experimentId; // in what experiment was it given an experiment Id? + bool m_bIsFake; //if true, will be deleted when the strip it's in is deleted +}; + +// nice and dumb edge class that points knows its +// indices, the two faces, and the next edge using +// the lesser of the indices +class NvEdgeInfo { +public: + + // constructor puts 1 ref on us + NvEdgeInfo (int v0, int v1){ + m_v0 = v0; + m_v1 = v1; + m_face0 = NULL; + m_face1 = NULL; + m_nextV0 = NULL; + m_nextV1 = NULL; + + // we will appear in 2 lists. this is a good + // way to make sure we delete it the second time + // we hit it in the edge infos + m_refCount = 2; + + } + + // ref and unref + void Unref () { if (--m_refCount == 0) delete this; } + + // data members are left public + unsigned int m_refCount; + NvFaceInfo *m_face0, *m_face1; + int m_v0, m_v1; + NvEdgeInfo *m_nextV0, *m_nextV1; +}; + + +// This class is a quick summary of parameters used +// to begin a triangle strip. Some operations may +// want to create lists of such items, so they were +// pulled out into a class +class NvStripStartInfo { +public: + NvStripStartInfo(NvFaceInfo *startFace, NvEdgeInfo *startEdge, bool toV1){ + m_startFace = startFace; + m_startEdge = startEdge; + m_toV1 = toV1; + } + NvFaceInfo *m_startFace; + NvEdgeInfo *m_startEdge; + bool m_toV1; +}; + + +typedef std::vector NvFaceInfoVec; +typedef std::list NvFaceInfoList; +typedef std::list NvStripList; +typedef std::vector NvEdgeInfoVec; + +typedef std::vector WordVec; +typedef std::vector IntVec; +typedef std::vector MyVertexVec; +typedef std::vector MyFaceVec; + +template +inline void SWAP(T& first, T& second) +{ + T temp = first; + first = second; + second = temp; +} + +// This is a summary of a strip that has been built +class NvStripInfo { +public: + + // A little information about the creation of the triangle strips + NvStripInfo(const NvStripStartInfo &startInfo, int stripId, int experimentId = -1) : + m_startInfo(startInfo) + { + m_stripId = stripId; + m_experimentId = experimentId; + visited = false; + m_numDegenerates = 0; + } + + // This is an experiment if the experiment id is >= 0 + inline bool IsExperiment () const { return m_experimentId >= 0; } + + inline bool IsInStrip (const NvFaceInfo *faceInfo) const + { + if(faceInfo == NULL) + return false; + + return (m_experimentId >= 0 ? faceInfo->m_testStripId == m_stripId : faceInfo->m_stripId == m_stripId); + } + + bool SharesEdge(const NvFaceInfo* faceInfo, NvEdgeInfoVec &edgeInfos); + + // take the given forward and backward strips and combine them together + void Combine(const NvFaceInfoVec &forward, const NvFaceInfoVec &backward); + + //returns true if the face is "unique", i.e. has a vertex which doesn't exist in the faceVec + bool Unique(NvFaceInfoVec& faceVec, NvFaceInfo* face); + + // mark the triangle as taken by this strip + bool IsMarked (NvFaceInfo *faceInfo); + void MarkTriangle(NvFaceInfo *faceInfo); + + // build the strip + void Build(NvEdgeInfoVec &edgeInfos, NvFaceInfoVec &faceInfos); + + // public data members + NvStripStartInfo m_startInfo; + NvFaceInfoVec m_faces; + int m_stripId; + int m_experimentId; + + bool visited; + + int m_numDegenerates; +}; + +typedef std::vector NvStripInfoVec; + + +//The actual stripifier +class NvStripifier { +public: + + // Constructor + NvStripifier(); + ~NvStripifier(); + + //the target vertex cache size, the structure to place the strips in, and the input indices + void Stripify(const WordVec &in_indices, const int in_cacheSize, const int in_minStripLength, + const unsigned short maxIndex, NvStripInfoVec &allStrips, NvFaceInfoVec &allFaces); + void CreateStrips(const NvStripInfoVec& allStrips, IntVec& stripIndices, const bool bStitchStrips, unsigned int& numSeparateStrips, const bool bRestart, const unsigned int restartVal); + + static int GetUniqueVertexInB(NvFaceInfo *faceA, NvFaceInfo *faceB); + //static int GetSharedVertex(NvFaceInfo *faceA, NvFaceInfo *faceB); + static void GetSharedVertices(NvFaceInfo *faceA, NvFaceInfo *faceB, int* vertex0, int* vertex1); + + static bool IsDegenerate(const NvFaceInfo* face); + static bool IsDegenerate(const unsigned short v0, const unsigned short v1, const unsigned short v2); + +protected: + + WordVec indices; + int cacheSize; + int minStripLength; + float meshJump; + bool bFirstTimeResetPoint; + + ///////////////////////////////////////////////////////////////////////////////// + // + // Big mess of functions called during stripification + // + ///////////////////////////////////////////////////////////////////////////////// + + //******************** + bool IsMoneyFace(const NvFaceInfo& face); + bool FaceContainsIndex(const NvFaceInfo& face, const unsigned int index); + + bool IsCW(NvFaceInfo *faceInfo, int v0, int v1); + bool NextIsCW(const int numIndices); + + static int GetNextIndex(const WordVec &indices, NvFaceInfo *face); + static NvEdgeInfo *FindEdgeInfo(NvEdgeInfoVec &edgeInfos, int v0, int v1); + static NvFaceInfo *FindOtherFace(NvEdgeInfoVec &edgeInfos, int v0, int v1, NvFaceInfo *faceInfo); + NvFaceInfo *FindGoodResetPoint(NvFaceInfoVec &faceInfos, NvEdgeInfoVec &edgeInfos); + + void FindAllStrips(NvStripInfoVec &allStrips, NvFaceInfoVec &allFaceInfos, NvEdgeInfoVec &allEdgeInfos, int numSamples); + void SplitUpStripsAndOptimize(NvStripInfoVec &allStrips, NvStripInfoVec &outStrips, NvEdgeInfoVec& edgeInfos, NvFaceInfoVec& outFaceList); + void RemoveSmallStrips(NvStripInfoVec& allStrips, NvStripInfoVec& allBigStrips, NvFaceInfoVec& faceList); + + bool FindTraversal(NvFaceInfoVec &faceInfos, NvEdgeInfoVec &edgeInfos, NvStripInfo *strip, NvStripStartInfo &startInfo); + int CountRemainingTris(std::list::iterator iter, std::list::iterator end); + + void CommitStrips(NvStripInfoVec &allStrips, const NvStripInfoVec &strips); + + float AvgStripSize(const NvStripInfoVec &strips); + int FindStartPoint(NvFaceInfoVec &faceInfos, NvEdgeInfoVec &edgeInfos); + + void UpdateCacheStrip(VertexCache* vcache, NvStripInfo* strip); + void UpdateCacheFace(VertexCache* vcache, NvFaceInfo* face); + float CalcNumHitsStrip(VertexCache* vcache, NvStripInfo* strip); + int CalcNumHitsFace(VertexCache* vcache, NvFaceInfo* face); + int NumNeighbors(NvFaceInfo* face, NvEdgeInfoVec& edgeInfoVec); + + void BuildStripifyInfo(NvFaceInfoVec &faceInfos, NvEdgeInfoVec &edgeInfos, const unsigned short maxIndex); + bool AlreadyExists(NvFaceInfo* faceInfo, NvFaceInfoVec& faceInfos); + + // let our strip info classes and the other classes get + // to these protected stripificaton methods if they want + friend class NvStripInfo; +}; + +#endif diff --git a/NvTriStrip/VertexCache.cpp b/NvTriStrip/VertexCache.cpp index a89548d05..a65f87fe9 100644 --- a/NvTriStrip/VertexCache.cpp +++ b/NvTriStrip/VertexCache.cpp @@ -1,88 +1,88 @@ - - -#include "VertexCache.h" - -VertexCache::VertexCache() -{ - VertexCache(16); -} - - -VertexCache::VertexCache(int size) -{ - numEntries = size; - - entries = new int[numEntries]; - - for(int i = 0; i < numEntries; i++) - entries[i] = -1; -} - - -VertexCache::~VertexCache() -{ - delete[] entries; -} - - -int VertexCache::At(int index) -{ - return entries[index]; -} - - -void VertexCache::Set(int index, int value) -{ - entries[index] = value; -} - - -void VertexCache::Clear() -{ - for(int i = 0; i < numEntries; i++) - entries[i] = -1; -} - -void VertexCache::Copy(VertexCache* inVcache) -{ - for(int i = 0; i < numEntries; i++) - { - inVcache->Set(i, entries[i]); - } -} - -bool VertexCache::InCache(int entry) -{ - bool returnVal = false; - - for(int i = 0; i < numEntries; i++) - { - if(entries[i] == entry) - { - returnVal = true; - break; - } - } - - return returnVal; -} - - -int VertexCache::AddEntry(int entry) -{ - int removed; - - removed = entries[numEntries - 1]; - - //push everything right one - for(int i = numEntries - 2; i >= 0; i--) - { - entries[i + 1] = entries[i]; - } - - entries[0] = entry; - - return removed; -} - + + +#include "VertexCache.h" + +VertexCache::VertexCache() +{ + VertexCache(16); +} + + +VertexCache::VertexCache(int size) +{ + numEntries = size; + + entries = new int[numEntries]; + + for(int i = 0; i < numEntries; i++) + entries[i] = -1; +} + + +VertexCache::~VertexCache() +{ + delete[] entries; +} + + +int VertexCache::At(int index) +{ + return entries[index]; +} + + +void VertexCache::Set(int index, int value) +{ + entries[index] = value; +} + + +void VertexCache::Clear() +{ + for(int i = 0; i < numEntries; i++) + entries[i] = -1; +} + +void VertexCache::Copy(VertexCache* inVcache) +{ + for(int i = 0; i < numEntries; i++) + { + inVcache->Set(i, entries[i]); + } +} + +bool VertexCache::InCache(int entry) +{ + bool returnVal = false; + + for(int i = 0; i < numEntries; i++) + { + if(entries[i] == entry) + { + returnVal = true; + break; + } + } + + return returnVal; +} + + +int VertexCache::AddEntry(int entry) +{ + int removed; + + removed = entries[numEntries - 1]; + + //push everything right one + for(int i = numEntries - 2; i >= 0; i--) + { + entries[i + 1] = entries[i]; + } + + entries[0] = entry; + + return removed; +} + \ No newline at end of file diff --git a/NvTriStrip/VertexCache.h b/NvTriStrip/VertexCache.h index 0d3066b18..5a1ea4caf 100644 --- a/NvTriStrip/VertexCache.h +++ b/NvTriStrip/VertexCache.h @@ -1,27 +1,27 @@ - -#ifndef VERTEX_CACHE_H - -#define VERTEX_CACHE_H - -class VertexCache -{ - -public: - VertexCache(int size); - VertexCache(); - ~VertexCache(); - bool InCache(int entry); - int AddEntry(int entry); - void Clear(); - void Copy(VertexCache* inVcache) ; - int At(int index); - void Set(int index, int value); - -private: - - int *entries; - int numEntries; - -}; - -#endif + +#ifndef VERTEX_CACHE_H + +#define VERTEX_CACHE_H + +class VertexCache +{ + +public: + VertexCache(int size); + VertexCache(); + ~VertexCache(); + bool InCache(int entry); + int AddEntry(int entry); + void Clear(); + void Copy(VertexCache* inVcache) ; + int At(int index); + void Set(int index, int value); + +private: + + int *entries; + int numEntries; + +}; + +#endif diff --git a/NvTriStrip/qtwrapper.cpp b/NvTriStrip/qtwrapper.cpp index d1b9e9e6a..4cc913d17 100644 --- a/NvTriStrip/qtwrapper.cpp +++ b/NvTriStrip/qtwrapper.cpp @@ -1,77 +1,77 @@ -#include "qtwrapper.h" - -#include "NvTriStrip.h" - -QList< QVector > stripify( QVector triangles, bool stitch ) -{ - unsigned short * data = (unsigned short *) malloc( triangles.count() * 3 * sizeof( unsigned short ) ); - for ( int t = 0; t < triangles.count(); t++ ) - { - data[ t * 3 + 0 ] = triangles[t][0]; - data[ t * 3 + 1 ] = triangles[t][1]; - data[ t * 3 + 2 ] = triangles[t][2]; - } - - PrimitiveGroup * groups = 0; - unsigned short numGroups = 0; - - SetStitchStrips( stitch ); - //SetCacheSize( 64 ); - GenerateStrips( data, triangles.count()*3, &groups, &numGroups ); - free( data ); - - QList< QVector > strips; - - if ( !groups ) - return strips; - - for ( int g = 0; g < numGroups; g++ ) - { - if ( groups[g].type == PT_STRIP ) - { - QVector< quint16 > strip( groups[g].numIndices, 0 ); - for ( quint32 s = 0; s < groups[g].numIndices; s++ ) - { - strip[s] = groups[g].indices[s]; - } - strips.append( strip ); - } - } - - delete [] groups; - - return strips; -} - -QVector triangulate( QVector strip ) -{ - QVector tris; - quint16 a, b = strip.value( 0 ), c = strip.value( 1 ); - bool flip = false; - for ( int s = 2; s < strip.count(); s++ ) - { - a = b; - b = c; - c = strip.value( s ); - if ( a != b && b != c && c != a ) - { - if ( ! flip ) - tris.append( Triangle( a, b, c ) ); - else - tris.append( Triangle( a, c, b ) ); - } - flip = ! flip; - } - return tris; -} - -QVector triangulate( QList< QVector > strips ) -{ - QVector tris; - foreach( QVector strip, strips ) - { - tris += triangulate( strip ); - } - return tris; -} - +#include "qtwrapper.h" + +#include "NvTriStrip.h" + +QList< QVector > stripify( QVector triangles, bool stitch ) +{ + unsigned short * data = (unsigned short *) malloc( triangles.count() * 3 * sizeof( unsigned short ) ); + for ( int t = 0; t < triangles.count(); t++ ) + { + data[ t * 3 + 0 ] = triangles[t][0]; + data[ t * 3 + 1 ] = triangles[t][1]; + data[ t * 3 + 2 ] = triangles[t][2]; + } + + PrimitiveGroup * groups = 0; + unsigned short numGroups = 0; + + SetStitchStrips( stitch ); + //SetCacheSize( 64 ); + GenerateStrips( data, triangles.count()*3, &groups, &numGroups ); + free( data ); + + QList< QVector > strips; + + if ( !groups ) + return strips; + + for ( int g = 0; g < numGroups; g++ ) + { + if ( groups[g].type == PT_STRIP ) + { + QVector< quint16 > strip( groups[g].numIndices, 0 ); + for ( quint32 s = 0; s < groups[g].numIndices; s++ ) + { + strip[s] = groups[g].indices[s]; + } + strips.append( strip ); + } + } + + delete [] groups; + + return strips; +} + +QVector triangulate( QVector strip ) +{ + QVector tris; + quint16 a, b = strip.value( 0 ), c = strip.value( 1 ); + bool flip = false; + for ( int s = 2; s < strip.count(); s++ ) + { + a = b; + b = c; + c = strip.value( s ); + if ( a != b && b != c && c != a ) + { + if ( ! flip ) + tris.append( Triangle( a, b, c ) ); + else + tris.append( Triangle( a, c, b ) ); + } + flip = ! flip; + } + return tris; +} + +QVector triangulate( QList< QVector > strips ) +{ + QVector tris; + foreach( QVector strip, strips ) + { + tris += triangulate( strip ); + } + return tris; +} + diff --git a/NvTriStrip/qtwrapper.h b/NvTriStrip/qtwrapper.h index 0dbfe7fb1..2e6469ce8 100644 --- a/NvTriStrip/qtwrapper.h +++ b/NvTriStrip/qtwrapper.h @@ -1,14 +1,14 @@ -#ifndef NVTRISTRIP_WRAPPER_H -#define NVTRISTRIP_WRAPPER_H - -#include "../niftypes.h" - -#include -#include - -QList< QVector > stripify( QVector triangles, bool stitch = true ); - -QVector triangulate( QVector strips ); -QVector triangulate( QList< QVector > strips ); - -#endif +#ifndef NVTRISTRIP_WRAPPER_H +#define NVTRISTRIP_WRAPPER_H + +#include "../niftypes.h" + +#include +#include + +QList< QVector > stripify( QVector triangles, bool stitch = true ); + +QVector triangulate( QVector strips ); +QVector triangulate( QList< QVector > strips ); + +#endif diff --git a/basemodel.cpp b/basemodel.cpp index 13fc0f1db..d3d66b7be 100644 --- a/basemodel.cpp +++ b/basemodel.cpp @@ -1,761 +1,761 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "basemodel.h" -#include "niftypes.h" - -#include -#include -#include -#include - -BaseModel::BaseModel( QObject * parent ) : QAbstractItemModel( parent ) -{ - msgMode = EmitMessages; - root = new NifItem( 0 ); -} - -BaseModel::~BaseModel() -{ - delete root; -} - -void BaseModel::msg( const Message & m ) const -{ - switch ( msgMode ) - { - case EmitMessages: - emit sigMessage( m ); - return; - case CollectMessages: - default: - messages.append( m ); - return; - } -} - - -/* - * array functions - */ - -bool BaseModel::isArray( const QModelIndex & index ) const -{ - return ! itemArr1( index ).isEmpty(); -} - -int BaseModel::getArraySize( NifItem * array ) const -{ - NifItem * parent = array->parent(); - if ( ! parent || parent == root ) - return -1; - - if ( array->arr1().isEmpty() ) - return 0; - - bool ok; - int d1 = array->arr1().toInt( &ok ); - if ( ! ok ) - { - QString left, right; - QString arr1 = array->arr1(); - - static const char * const exp[] = { " | ", " & ", " / ", " + ", " - " }; - static const int num_exp = 5; - - int c; - for ( c = 0; c < num_exp; c++ ) - { - int p = arr1.indexOf( exp[c] ); - if ( p > 0 ) - { - left = arr1.left( p ).trimmed(); - right = arr1.right( arr1.length() - p - strlen( exp[c] ) ).trimmed(); - break; - } - } - - if ( c >= num_exp ) - { - left = arr1.trimmed(); - c = 0; - } - - int r = 0; // d1 is left - - bool ok; - - if ( ! left.isEmpty() ) - { - d1 = left.toInt( &ok ); - if ( ! ok ) - { - NifItem * dim1 = parent; - - while ( left == "ARG" ) - { - if ( ! dim1->parent() ) return 0; - left = dim1->arg(); - dim1 = dim1->parent(); - } - - dim1 = getItem( dim1, left ); - if ( ! dim1 ) - { - msg( Message() << tr("failed to get array size for array") << array->name() ); - return 0; - } - - if ( dim1->childCount() == 0 ) - d1 = dim1->value().toCount(); - else - { - NifItem * item = dim1->child( array->row() ); - if ( item ) - d1 = item->value().toCount(); - else { - msg( Message() << tr("failed to get array size for array ") << array->name() ); - return 0; - }; - } - } - } - - if ( ! right.isEmpty() ) - { - r = right.toInt( &ok ); - if ( ! ok ) - { - msg( Message() << tr("failed to get array size for array ") << array->name() ); - return 0; - } - } - - switch ( c ) - { - case 0: d1 |= r; break; - case 1: d1 &= r; break; - case 2: d1 /= r; break; - case 3: d1 += r; break; - case 4: d1 -= r; break; - } - } - - if ( d1 < 0 ) - { - msg( Message() << tr("invalid array size for array") << array->name() ); - d1 = -1; - } - return d1; -} - -bool BaseModel::updateArray( const QModelIndex & array ) -{ - NifItem * item = static_cast( array.internalPointer() ); - if ( ! ( array.isValid() && item && array.model() == this ) ) - return false; - return updateArrayItem( item, false ); -} - -bool BaseModel::updateArray( const QModelIndex & parent, const QString & name ) -{ - return updateArray( getIndex( parent, name ) ); -} - -/* - * item value functions - */ - -QString BaseModel::itemName( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); - return item->name(); -} - -QString BaseModel::itemType( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); - return item->type(); -} - -QString BaseModel::itemTmplt( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); - return item->temp(); -} - - -NifValue BaseModel::getValue( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return NifValue(); - return item->value(); -} - -// where is -// NifValue BaseModel::getValue( const QModelIndex & parent, const QString & name ) const -// ? - -QString BaseModel::itemArg( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); - return item->arg(); -} - -QString BaseModel::itemArr1( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); - return item->arr1(); -} - -QString BaseModel::itemArr2( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); - return item->arr2(); -} - -QString BaseModel::itemCond( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); - return item->cond(); -} - -quint32 BaseModel::itemVer1( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return 0; - return item->ver1(); -} - -quint32 BaseModel::itemVer2( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return 0; - return item->ver2(); -} - -QString BaseModel::itemText( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); - return item->text(); -} - - -bool BaseModel::setValue( const QModelIndex & index, const NifValue & val ) -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return false; - return setItemValue( item, val ); -} - -bool BaseModel::setValue( const QModelIndex & parent, const QString & name, const NifValue & val ) -{ - NifItem * parentItem = static_cast( parent.internalPointer() ); - if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) - return false; - - NifItem * item = getItem( parentItem, name ); - if ( item ) - return setItemValue( item, val ); - else - return false; -} - - -/* - * QAbstractModel interface - */ - -QModelIndex BaseModel::index( int row, int column, const QModelIndex & parent ) const -{ - NifItem * parentItem; - - if ( ! ( parent.isValid() && parent.model() == this ) ) - parentItem = root; - else - parentItem = static_cast( parent.internalPointer() ); - - NifItem * childItem = ( parentItem ? parentItem->child( row ) : 0 ); - if ( childItem ) - return createIndex( row, column, childItem ); - else - return QModelIndex(); -} - -QModelIndex BaseModel::parent( const QModelIndex & child ) const -{ - if ( ! ( child.isValid() && child.model() == this ) ) - return QModelIndex(); - - NifItem *childItem = static_cast( child.internalPointer() ); - if ( ! childItem ) return QModelIndex(); - NifItem *parentItem = childItem->parent(); - - if ( parentItem == root || ! parentItem ) - return QModelIndex(); - - return createIndex( parentItem->row(), 0, parentItem ); -} - -int BaseModel::rowCount( const QModelIndex & parent ) const -{ - NifItem * parentItem; - - if ( ! ( parent.isValid() && parent.model() == this ) ) - parentItem = root; - else - parentItem = static_cast( parent.internalPointer() ); - - return ( parentItem ? parentItem->childCount() : 0 ); -} - -QVariant BaseModel::data( const QModelIndex & index, int role ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) - return QVariant(); - - int column = index.column(); - - switch ( role ) - { - case Qt::DisplayRole: - { - switch ( column ) - { - case NameCol: return item->name(); - case TypeCol: return item->type(); - case ValueCol: return item->value().toString(); - case ArgCol: return item->arg(); - case Arr1Col: return item->arr1(); - case Arr2Col: return item->arr2(); - case CondCol: return item->cond(); - case Ver1Col: return ver2str( item->ver1() ); - case Ver2Col: return ver2str( item->ver2() ); - default: return QVariant(); - } - } - case Qt::EditRole: - { - switch ( column ) - { - case NameCol: return item->name(); - case TypeCol: return item->type(); - case ValueCol: return item->value().toVariant(); - case ArgCol: return item->arg(); - case Arr1Col: return item->arr1(); - case Arr2Col: return item->arr2(); - case CondCol: return item->cond(); - case Ver1Col: return ver2str( item->ver1() ); - case Ver2Col: return ver2str( item->ver2() ); - default: return QVariant(); - } - } - case Qt::ToolTipRole: - { - QString tip; - switch ( column ) - { - case ValueCol: - { - switch ( item->value().type() ) - { - case NifValue::tWord: - case NifValue::tShort: - { - quint16 s = item->value().toCount(); - return QString( "dec: %1
hex: 0x%2" ).arg( s ).arg( s, 4, 16, QChar( '0' ) ); - } - case NifValue::tBool: - case NifValue::tInt: - case NifValue::tUInt: - { - quint32 i = item->value().toCount(); - return QString( "dec: %1
hex: 0x%2" ).arg( i ).arg( i, 8, 16, QChar( '0' ) ); - } - case NifValue::tFloat: - { - float f = item->value().toFloat(); - quint32 i = item->value().toCount(); - return QString( "float: %1
data: 0x%2" ).arg( f ).arg( i, 8, 16, QChar( '0' ) ); - } - case NifValue::tFlags: - { - quint16 f = item->value().toCount(); - return QString( "dec: %1
hex: 0x%2
bin: 0b%3" ).arg( f ).arg( f, 4, 16, QChar( '0' ) ).arg( f, 16, 2, QChar( '0' ) ); - } - case NifValue::tVector3: - return item->value().get().toHtml(); - case NifValue::tMatrix: - return item->value().get().toHtml(); - case NifValue::tQuat: - case NifValue::tQuatXYZW: - return item->value().get().toHtml(); - case NifValue::tColor3: - { - Color3 c = item->value().get(); - return QString( "R %1
G %2
B %3" ).arg( c[0] ).arg( c[1] ).arg( c[2] ); - } - case NifValue::tColor4: - { - Color4 c = item->value().get(); - return QString( "R %1
G %2
B %3
A %4" ).arg( c[0] ).arg( c[1] ).arg( c[2] ).arg( c[3] ); - } - default: - break; - } - } break; - default: - break; - } - } return QVariant(); - case Qt::BackgroundColorRole: - { - if ( column == ValueCol && item->value().isColor() ) - { - return item->value().toColor(); - } - } return QVariant(); - default: - return QVariant(); - } -} - -bool BaseModel::setData( const QModelIndex & index, const QVariant & value, int role ) -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && role == Qt::EditRole && index.model() == this && item ) ) - return false; - - switch ( index.column() ) - { - case BaseModel::NameCol: - item->setName( value.toString() ); - break; - case BaseModel::TypeCol: - item->setType( value.toString() ); - break; - case BaseModel::ValueCol: - item->value().fromVariant( value ); - break; - case BaseModel::ArgCol: - item->setArg( value.toString() ); - break; - case BaseModel::Arr1Col: - item->setArr1( value.toString() ); - break; - case BaseModel::Arr2Col: - item->setArr2( value.toString() ); - break; - case BaseModel::CondCol: - item->setCond( value.toString() ); - break; - case BaseModel::Ver1Col: - item->setVer1( str2ver( value.toString() ) ); - break; - case BaseModel::Ver2Col: - item->setVer2( str2ver( value.toString() ) ); - break; - default: - return false; - } - - emit dataChanged( index, index ); - - return true; -} - -QVariant BaseModel::headerData( int section, Qt::Orientation orientation, int role ) const -{ - if ( role != Qt::DisplayRole ) - return QVariant(); - switch ( role ) - { - case Qt::DisplayRole: - switch ( section ) - { - case NameCol: return tr("Name"); - case TypeCol: return tr("Type"); - case ValueCol: return tr("Value"); - case ArgCol: return tr("Argument"); - case Arr1Col: return tr("Array1"); - case Arr2Col: return tr("Array2"); - case CondCol: return tr("Condition"); - case Ver1Col: return tr("since"); - case Ver2Col: return tr("until"); - default: return QVariant(); - } - default: - return QVariant(); - } -} - -Qt::ItemFlags BaseModel::flags( const QModelIndex & index ) const -{ - if ( !index.isValid() ) return Qt::ItemIsEnabled; - Qt::ItemFlags flags = Qt::ItemIsSelectable; - if ( evalCondition( index, true ) ) - flags |= Qt::ItemIsEnabled; - switch( index.column() ) - { - case TypeCol: - return flags; - case ValueCol: - if ( itemArr1( index ).isEmpty() ) - return flags | Qt::ItemIsEditable; - else - return flags; - default: - return flags | Qt::ItemIsEditable; - } -} - -/* - * load and save - */ - -bool BaseModel::loadFromFile( const QString & filename ) -{ - QFile f( filename ); - bool x = f.open( QIODevice::ReadOnly ) && load( f ); - folder = filename.left( qMax( filename.lastIndexOf( "\\" ), filename.lastIndexOf( "/" ) ) ); - return x; -} - -bool BaseModel::saveToFile( const QString & filename ) const -{ - QFile f( filename ); - return f.open( QIODevice::WriteOnly ) && save( f ); -} - -/* - * searching - */ - -NifItem * BaseModel::getItem( NifItem * item, const QString & name ) const -{ - if ( ! item || item == root ) return 0; - - int slash = name.indexOf( "/" ); - if ( slash > 0 ) - { - QString left = name.left( slash ); - QString right = name.right( name.length() - slash - 1 ); - - if ( left == ".." ) - return getItem( item->parent(), right ); - else - return getItem( getItem( item, left ), right ); - } - - for ( int c = 0; c < item->childCount(); c++ ) - { - NifItem * child = item->child( c ); - - if ( child->name() == name && evalCondition( child ) ) - return child; - } - - return 0; -} - -NifItem * BaseModel::getItemX( NifItem * item, const QString & name ) const -{ - if ( ! item || ! item->parent() ) return 0; - - NifItem * parent = item->parent(); - for ( int c = item->row() - 1; c >= 0; c-- ) - { - NifItem * child = parent->child( c ); - - if ( child && child->name() == name && evalCondition( child ) ) - return child; - } - - return getItemX( parent, name ); -} - -QModelIndex BaseModel::getIndex( const QModelIndex & parent, const QString & name ) const -{ - NifItem * parentItem = static_cast( parent.internalPointer() ); - if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) - return QModelIndex(); - - NifItem * item = getItem( parentItem, name ); - if ( item ) - return createIndex( item->row(), 0, item ); - else - return QModelIndex(); -} - -/* - * conditions and version - */ - - -bool BaseModel::evalCondition( NifItem * item, bool chkParents ) const -{ - if ( ! evalVersion( item, chkParents ) ) - return false; - - if ( item == root ) - return true; - - if ( chkParents && item->parent() ) - if ( ! evalCondition( item->parent(), true ) ) - return false; - - QString cond = item->cond(); - - if ( cond.isEmpty() ) - return true; - - if ( cond.contains( "&&" ) ) - { - foreach ( QString c, cond.split( "&&" ) ) - { - c = c.trimmed(); - if ( c.startsWith( "(" ) && c.endsWith( ")" ) ) - c = c.mid( 1, c.length() - 2 ).trimmed(); - if ( ! evalConditionHelper( item, c ) ) - return false; - } - return true; - } - - return evalConditionHelper( item, cond ); -} - -bool BaseModel::evalConditionHelper( NifItem * item, const QString & cond ) const -{ - QString left, right; - - static const char * const exp[] = { "!=", "==", ">=", "<=", ">", "<", "&", "+", "-" }; - static const int num_exp = 9; - - int c; - for ( c = 0; c < num_exp; c++ ) - { - int p = cond.indexOf( exp[c] ); - if ( p > 0 ) - { - left = cond.left( p ).trimmed(); - right = cond.right( cond.length() - p - strlen( exp[c] ) ).trimmed(); - break; - } - } - - if ( c >= num_exp ) - { - left = cond.trimmed(); - c = 0; - } - - int l = 0; - int r = 0; - - bool ok; - - if ( ! left.isEmpty() ) - { - l = left.toInt( &ok ); - if ( ! ok ) - { - NifItem * i = item; - - while ( left == "ARG" ) - { - if ( ! i->parent() ) return false; - i = i->parent(); - left = i->arg(); - } - - i = getItem( i->parent(), left ); - if ( i ) - l = i->value().toCount(); - else - l = 0; - } - } - - if ( ! right.isEmpty() ) - { - r = right.toInt( &ok ); - if ( ! ok ) - { - NifItem * i = getItem( item->parent(), right ); - if ( i ) - r = i->value().toCount(); - else - r = 0; - } - } - - switch ( c ) - { - case 0: return l != r; - case 1: return l == r; - case 2: return l >= r; - case 3: return l <= r; - case 4: return l > r; - case 5: return l < r; - case 6: return l & r; - case 7: return l + r; - case 8: return l - r; - default: return false; - } -} - -bool BaseModel::evalVersion( const QModelIndex & index, bool chkParents ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( index.isValid() && index.model() == this && item ) - return evalVersion( item, chkParents ); - return false; -} - -bool BaseModel::evalCondition( const QModelIndex & index, bool chkParents ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( index.isValid() && index.model() == this && item ) - return evalCondition( item, chkParents ); - return false; -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "basemodel.h" +#include "niftypes.h" + +#include +#include +#include +#include + +BaseModel::BaseModel( QObject * parent ) : QAbstractItemModel( parent ) +{ + msgMode = EmitMessages; + root = new NifItem( 0 ); +} + +BaseModel::~BaseModel() +{ + delete root; +} + +void BaseModel::msg( const Message & m ) const +{ + switch ( msgMode ) + { + case EmitMessages: + emit sigMessage( m ); + return; + case CollectMessages: + default: + messages.append( m ); + return; + } +} + + +/* + * array functions + */ + +bool BaseModel::isArray( const QModelIndex & index ) const +{ + return ! itemArr1( index ).isEmpty(); +} + +int BaseModel::getArraySize( NifItem * array ) const +{ + NifItem * parent = array->parent(); + if ( ! parent || parent == root ) + return -1; + + if ( array->arr1().isEmpty() ) + return 0; + + bool ok; + int d1 = array->arr1().toInt( &ok ); + if ( ! ok ) + { + QString left, right; + QString arr1 = array->arr1(); + + static const char * const exp[] = { " | ", " & ", " / ", " + ", " - " }; + static const int num_exp = 5; + + int c; + for ( c = 0; c < num_exp; c++ ) + { + int p = arr1.indexOf( exp[c] ); + if ( p > 0 ) + { + left = arr1.left( p ).trimmed(); + right = arr1.right( arr1.length() - p - strlen( exp[c] ) ).trimmed(); + break; + } + } + + if ( c >= num_exp ) + { + left = arr1.trimmed(); + c = 0; + } + + int r = 0; // d1 is left + + bool ok; + + if ( ! left.isEmpty() ) + { + d1 = left.toInt( &ok ); + if ( ! ok ) + { + NifItem * dim1 = parent; + + while ( left == "ARG" ) + { + if ( ! dim1->parent() ) return 0; + left = dim1->arg(); + dim1 = dim1->parent(); + } + + dim1 = getItem( dim1, left ); + if ( ! dim1 ) + { + msg( Message() << tr("failed to get array size for array") << array->name() ); + return 0; + } + + if ( dim1->childCount() == 0 ) + d1 = dim1->value().toCount(); + else + { + NifItem * item = dim1->child( array->row() ); + if ( item ) + d1 = item->value().toCount(); + else { + msg( Message() << tr("failed to get array size for array ") << array->name() ); + return 0; + }; + } + } + } + + if ( ! right.isEmpty() ) + { + r = right.toInt( &ok ); + if ( ! ok ) + { + msg( Message() << tr("failed to get array size for array ") << array->name() ); + return 0; + } + } + + switch ( c ) + { + case 0: d1 |= r; break; + case 1: d1 &= r; break; + case 2: d1 /= r; break; + case 3: d1 += r; break; + case 4: d1 -= r; break; + } + } + + if ( d1 < 0 ) + { + msg( Message() << tr("invalid array size for array") << array->name() ); + d1 = -1; + } + return d1; +} + +bool BaseModel::updateArray( const QModelIndex & array ) +{ + NifItem * item = static_cast( array.internalPointer() ); + if ( ! ( array.isValid() && item && array.model() == this ) ) + return false; + return updateArrayItem( item, false ); +} + +bool BaseModel::updateArray( const QModelIndex & parent, const QString & name ) +{ + return updateArray( getIndex( parent, name ) ); +} + +/* + * item value functions + */ + +QString BaseModel::itemName( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); + return item->name(); +} + +QString BaseModel::itemType( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); + return item->type(); +} + +QString BaseModel::itemTmplt( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); + return item->temp(); +} + + +NifValue BaseModel::getValue( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return NifValue(); + return item->value(); +} + +// where is +// NifValue BaseModel::getValue( const QModelIndex & parent, const QString & name ) const +// ? + +QString BaseModel::itemArg( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); + return item->arg(); +} + +QString BaseModel::itemArr1( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); + return item->arr1(); +} + +QString BaseModel::itemArr2( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); + return item->arr2(); +} + +QString BaseModel::itemCond( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); + return item->cond(); +} + +quint32 BaseModel::itemVer1( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return 0; + return item->ver1(); +} + +quint32 BaseModel::itemVer2( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return 0; + return item->ver2(); +} + +QString BaseModel::itemText( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return QString(); + return item->text(); +} + + +bool BaseModel::setValue( const QModelIndex & index, const NifValue & val ) +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return false; + return setItemValue( item, val ); +} + +bool BaseModel::setValue( const QModelIndex & parent, const QString & name, const NifValue & val ) +{ + NifItem * parentItem = static_cast( parent.internalPointer() ); + if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) + return false; + + NifItem * item = getItem( parentItem, name ); + if ( item ) + return setItemValue( item, val ); + else + return false; +} + + +/* + * QAbstractModel interface + */ + +QModelIndex BaseModel::index( int row, int column, const QModelIndex & parent ) const +{ + NifItem * parentItem; + + if ( ! ( parent.isValid() && parent.model() == this ) ) + parentItem = root; + else + parentItem = static_cast( parent.internalPointer() ); + + NifItem * childItem = ( parentItem ? parentItem->child( row ) : 0 ); + if ( childItem ) + return createIndex( row, column, childItem ); + else + return QModelIndex(); +} + +QModelIndex BaseModel::parent( const QModelIndex & child ) const +{ + if ( ! ( child.isValid() && child.model() == this ) ) + return QModelIndex(); + + NifItem *childItem = static_cast( child.internalPointer() ); + if ( ! childItem ) return QModelIndex(); + NifItem *parentItem = childItem->parent(); + + if ( parentItem == root || ! parentItem ) + return QModelIndex(); + + return createIndex( parentItem->row(), 0, parentItem ); +} + +int BaseModel::rowCount( const QModelIndex & parent ) const +{ + NifItem * parentItem; + + if ( ! ( parent.isValid() && parent.model() == this ) ) + parentItem = root; + else + parentItem = static_cast( parent.internalPointer() ); + + return ( parentItem ? parentItem->childCount() : 0 ); +} + +QVariant BaseModel::data( const QModelIndex & index, int role ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) + return QVariant(); + + int column = index.column(); + + switch ( role ) + { + case Qt::DisplayRole: + { + switch ( column ) + { + case NameCol: return item->name(); + case TypeCol: return item->type(); + case ValueCol: return item->value().toString(); + case ArgCol: return item->arg(); + case Arr1Col: return item->arr1(); + case Arr2Col: return item->arr2(); + case CondCol: return item->cond(); + case Ver1Col: return ver2str( item->ver1() ); + case Ver2Col: return ver2str( item->ver2() ); + default: return QVariant(); + } + } + case Qt::EditRole: + { + switch ( column ) + { + case NameCol: return item->name(); + case TypeCol: return item->type(); + case ValueCol: return item->value().toVariant(); + case ArgCol: return item->arg(); + case Arr1Col: return item->arr1(); + case Arr2Col: return item->arr2(); + case CondCol: return item->cond(); + case Ver1Col: return ver2str( item->ver1() ); + case Ver2Col: return ver2str( item->ver2() ); + default: return QVariant(); + } + } + case Qt::ToolTipRole: + { + QString tip; + switch ( column ) + { + case ValueCol: + { + switch ( item->value().type() ) + { + case NifValue::tWord: + case NifValue::tShort: + { + quint16 s = item->value().toCount(); + return QString( "dec: %1
hex: 0x%2" ).arg( s ).arg( s, 4, 16, QChar( '0' ) ); + } + case NifValue::tBool: + case NifValue::tInt: + case NifValue::tUInt: + { + quint32 i = item->value().toCount(); + return QString( "dec: %1
hex: 0x%2" ).arg( i ).arg( i, 8, 16, QChar( '0' ) ); + } + case NifValue::tFloat: + { + float f = item->value().toFloat(); + quint32 i = item->value().toCount(); + return QString( "float: %1
data: 0x%2" ).arg( f ).arg( i, 8, 16, QChar( '0' ) ); + } + case NifValue::tFlags: + { + quint16 f = item->value().toCount(); + return QString( "dec: %1
hex: 0x%2
bin: 0b%3" ).arg( f ).arg( f, 4, 16, QChar( '0' ) ).arg( f, 16, 2, QChar( '0' ) ); + } + case NifValue::tVector3: + return item->value().get().toHtml(); + case NifValue::tMatrix: + return item->value().get().toHtml(); + case NifValue::tQuat: + case NifValue::tQuatXYZW: + return item->value().get().toHtml(); + case NifValue::tColor3: + { + Color3 c = item->value().get(); + return QString( "R %1
G %2
B %3" ).arg( c[0] ).arg( c[1] ).arg( c[2] ); + } + case NifValue::tColor4: + { + Color4 c = item->value().get(); + return QString( "R %1
G %2
B %3
A %4" ).arg( c[0] ).arg( c[1] ).arg( c[2] ).arg( c[3] ); + } + default: + break; + } + } break; + default: + break; + } + } return QVariant(); + case Qt::BackgroundColorRole: + { + if ( column == ValueCol && item->value().isColor() ) + { + return item->value().toColor(); + } + } return QVariant(); + default: + return QVariant(); + } +} + +bool BaseModel::setData( const QModelIndex & index, const QVariant & value, int role ) +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && role == Qt::EditRole && index.model() == this && item ) ) + return false; + + switch ( index.column() ) + { + case BaseModel::NameCol: + item->setName( value.toString() ); + break; + case BaseModel::TypeCol: + item->setType( value.toString() ); + break; + case BaseModel::ValueCol: + item->value().fromVariant( value ); + break; + case BaseModel::ArgCol: + item->setArg( value.toString() ); + break; + case BaseModel::Arr1Col: + item->setArr1( value.toString() ); + break; + case BaseModel::Arr2Col: + item->setArr2( value.toString() ); + break; + case BaseModel::CondCol: + item->setCond( value.toString() ); + break; + case BaseModel::Ver1Col: + item->setVer1( str2ver( value.toString() ) ); + break; + case BaseModel::Ver2Col: + item->setVer2( str2ver( value.toString() ) ); + break; + default: + return false; + } + + emit dataChanged( index, index ); + + return true; +} + +QVariant BaseModel::headerData( int section, Qt::Orientation orientation, int role ) const +{ + if ( role != Qt::DisplayRole ) + return QVariant(); + switch ( role ) + { + case Qt::DisplayRole: + switch ( section ) + { + case NameCol: return tr("Name"); + case TypeCol: return tr("Type"); + case ValueCol: return tr("Value"); + case ArgCol: return tr("Argument"); + case Arr1Col: return tr("Array1"); + case Arr2Col: return tr("Array2"); + case CondCol: return tr("Condition"); + case Ver1Col: return tr("since"); + case Ver2Col: return tr("until"); + default: return QVariant(); + } + default: + return QVariant(); + } +} + +Qt::ItemFlags BaseModel::flags( const QModelIndex & index ) const +{ + if ( !index.isValid() ) return Qt::ItemIsEnabled; + Qt::ItemFlags flags = Qt::ItemIsSelectable; + if ( evalCondition( index, true ) ) + flags |= Qt::ItemIsEnabled; + switch( index.column() ) + { + case TypeCol: + return flags; + case ValueCol: + if ( itemArr1( index ).isEmpty() ) + return flags | Qt::ItemIsEditable; + else + return flags; + default: + return flags | Qt::ItemIsEditable; + } +} + +/* + * load and save + */ + +bool BaseModel::loadFromFile( const QString & filename ) +{ + QFile f( filename ); + bool x = f.open( QIODevice::ReadOnly ) && load( f ); + folder = filename.left( qMax( filename.lastIndexOf( "\\" ), filename.lastIndexOf( "/" ) ) ); + return x; +} + +bool BaseModel::saveToFile( const QString & filename ) const +{ + QFile f( filename ); + return f.open( QIODevice::WriteOnly ) && save( f ); +} + +/* + * searching + */ + +NifItem * BaseModel::getItem( NifItem * item, const QString & name ) const +{ + if ( ! item || item == root ) return 0; + + int slash = name.indexOf( "/" ); + if ( slash > 0 ) + { + QString left = name.left( slash ); + QString right = name.right( name.length() - slash - 1 ); + + if ( left == ".." ) + return getItem( item->parent(), right ); + else + return getItem( getItem( item, left ), right ); + } + + for ( int c = 0; c < item->childCount(); c++ ) + { + NifItem * child = item->child( c ); + + if ( child->name() == name && evalCondition( child ) ) + return child; + } + + return 0; +} + +NifItem * BaseModel::getItemX( NifItem * item, const QString & name ) const +{ + if ( ! item || ! item->parent() ) return 0; + + NifItem * parent = item->parent(); + for ( int c = item->row() - 1; c >= 0; c-- ) + { + NifItem * child = parent->child( c ); + + if ( child && child->name() == name && evalCondition( child ) ) + return child; + } + + return getItemX( parent, name ); +} + +QModelIndex BaseModel::getIndex( const QModelIndex & parent, const QString & name ) const +{ + NifItem * parentItem = static_cast( parent.internalPointer() ); + if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) + return QModelIndex(); + + NifItem * item = getItem( parentItem, name ); + if ( item ) + return createIndex( item->row(), 0, item ); + else + return QModelIndex(); +} + +/* + * conditions and version + */ + + +bool BaseModel::evalCondition( NifItem * item, bool chkParents ) const +{ + if ( ! evalVersion( item, chkParents ) ) + return false; + + if ( item == root ) + return true; + + if ( chkParents && item->parent() ) + if ( ! evalCondition( item->parent(), true ) ) + return false; + + QString cond = item->cond(); + + if ( cond.isEmpty() ) + return true; + + if ( cond.contains( "&&" ) ) + { + foreach ( QString c, cond.split( "&&" ) ) + { + c = c.trimmed(); + if ( c.startsWith( "(" ) && c.endsWith( ")" ) ) + c = c.mid( 1, c.length() - 2 ).trimmed(); + if ( ! evalConditionHelper( item, c ) ) + return false; + } + return true; + } + + return evalConditionHelper( item, cond ); +} + +bool BaseModel::evalConditionHelper( NifItem * item, const QString & cond ) const +{ + QString left, right; + + static const char * const exp[] = { "!=", "==", ">=", "<=", ">", "<", "&", "+", "-" }; + static const int num_exp = 9; + + int c; + for ( c = 0; c < num_exp; c++ ) + { + int p = cond.indexOf( exp[c] ); + if ( p > 0 ) + { + left = cond.left( p ).trimmed(); + right = cond.right( cond.length() - p - strlen( exp[c] ) ).trimmed(); + break; + } + } + + if ( c >= num_exp ) + { + left = cond.trimmed(); + c = 0; + } + + int l = 0; + int r = 0; + + bool ok; + + if ( ! left.isEmpty() ) + { + l = left.toInt( &ok ); + if ( ! ok ) + { + NifItem * i = item; + + while ( left == "ARG" ) + { + if ( ! i->parent() ) return false; + i = i->parent(); + left = i->arg(); + } + + i = getItem( i->parent(), left ); + if ( i ) + l = i->value().toCount(); + else + l = 0; + } + } + + if ( ! right.isEmpty() ) + { + r = right.toInt( &ok ); + if ( ! ok ) + { + NifItem * i = getItem( item->parent(), right ); + if ( i ) + r = i->value().toCount(); + else + r = 0; + } + } + + switch ( c ) + { + case 0: return l != r; + case 1: return l == r; + case 2: return l >= r; + case 3: return l <= r; + case 4: return l > r; + case 5: return l < r; + case 6: return l & r; + case 7: return l + r; + case 8: return l - r; + default: return false; + } +} + +bool BaseModel::evalVersion( const QModelIndex & index, bool chkParents ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( index.isValid() && index.model() == this && item ) + return evalVersion( item, chkParents ); + return false; +} + +bool BaseModel::evalCondition( const QModelIndex & index, bool chkParents ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( index.isValid() && index.model() == this && item ) + return evalCondition( item, chkParents ); + return false; +} + diff --git a/basemodel.h b/basemodel.h index b06c2ca7b..abaf11a3e 100644 --- a/basemodel.h +++ b/basemodel.h @@ -1,339 +1,339 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef BaseModel_H -#define BaseModel_H - -#include - -#include - -class QAbstractItemDelegate; - -#include "niftypes.h" -#include "nifitem.h" - -#include "message.h" - -//! Base class for nif and kfm models, which store files in memory. -/*! - * This class serves as an abstract base class for NifModel and KfmModel - * classes. - */ -class BaseModel : public QAbstractItemModel -{ -Q_OBJECT -public: - BaseModel( QObject * parent = 0 ); - ~BaseModel(); - - //! Clear model data. - virtual void clear() = 0; - - //! Load from file. - bool loadFromFile( const QString & filename ); - //! Save to file. - bool saveToFile( const QString & filename ) const; - - //! Generic load from QIODevice. - virtual bool load( QIODevice & device ) = 0; - //! Generic save to QIODevice. - virtual bool save( QIODevice & device ) const = 0; - - //! If the model was loaded from a file then getFolder returns the folder. - /*! - * This function is used to resolve external resources. - * \return The folder of the last file that was loaded with loadFromFile. - */ - QString getFolder() const { return folder; } - - //! Return true if the index pointed to is an array. - /*! - * \param array The index to check. - * \return true if the index is an array. - */ - bool isArray( const QModelIndex & iArray ) const; - //! Update the size of an array (append or remove items). - /*! - * \param array The index of the array whose size to update. - * \return true if the update succeeded, false otherwise. - */ - bool updateArray( const QModelIndex & iArray ); - bool updateArray( const QModelIndex & parent, const QString & name ); - //! Get an model index array as a QVector. - /*! - * \param array The index of the array to get. - * \return The array as QVector. - */ - template QVector getArray( const QModelIndex & iArray ) const; - template QVector getArray( const QModelIndex & iArray, const QString & name ) const; - //! Write a QVector to a model index array. - template void setArray( const QModelIndex & iArray, const QVector & array ); - template void setArray( const QModelIndex & iArray, const QString & name, const QVector & array ); - - //! Get an item. - template T get( const QModelIndex & index ) const; - template T get( const QModelIndex & parent, const QString & name ) const; - //! Set an item. - template bool set( const QModelIndex & index, const T & d ); - template bool set( const QModelIndex & parent, const QString & name, const T & v ); - - //! Get an item as a NifValue. - NifValue getValue( const QModelIndex & index ) const; - NifValue getValue( const QModelIndex & parent, const QString & name ) const; - - //! Set an item from a NifValue. - bool setValue( const QModelIndex & index, const NifValue & v ); - bool setValue( const QModelIndex & parent, const QString & name, const NifValue & v ); - - // get item attributes - //! Get the item name. - QString itemName( const QModelIndex & index ) const; - //! Get the item type string. - QString itemType( const QModelIndex & index ) const; - //! Get the item argument string. - QString itemArg( const QModelIndex & index ) const; - //! Get the item arr1 string. - QString itemArr1( const QModelIndex & index ) const; - //! Get the item arr2 string. - QString itemArr2( const QModelIndex & index ) const; - //! Get the item condition string. - QString itemCond( const QModelIndex & index ) const; - //! Get the item first version. - quint32 itemVer1( const QModelIndex & index ) const; - //! Get the item last version. - quint32 itemVer2( const QModelIndex & index ) const; - //! Get the item documentation. - QString itemText( const QModelIndex & index ) const; - //! Get the item template string. - QString itemTmplt( const QModelIndex & index ) const; - - // find a branch by name - QModelIndex getIndex( const QModelIndex & parent, const QString & name ) const; - - // evaluate condition and version - bool evalCondition( const QModelIndex & idx, bool chkParents = false ) const; - // evaluate version - bool evalVersion( const QModelIndex & idx, bool chkParents = false ) const; - - virtual QString getVersion() const = 0; - virtual quint32 getVersionNumber() const = 0; - - - // QAbstractModel interface - - QModelIndex index( int row, int column, const QModelIndex & parent = QModelIndex() ) const; - QModelIndex parent( const QModelIndex & index ) const; - - int rowCount( const QModelIndex & parent = QModelIndex() ) const; - int columnCount( const QModelIndex & parent = QModelIndex() ) const { return 9; } - - QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; - bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ); - - QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; - - Qt::ItemFlags flags( const QModelIndex & index ) const; - - - - // column numbers - enum { - NameCol = 0, - TypeCol = 1, - ValueCol = 2, - ArgCol = 3, - Arr1Col = 4, - Arr2Col = 5, - CondCol = 6, - Ver1Col = 7, - Ver2Col = 8 - }; - - enum MsgMode - { - EmitMessages, CollectMessages - }; - - void setMessageMode( MsgMode m ) { msgMode = m; } - QList getMessages() const { QList lst = messages; messages.clear(); return lst; } - -signals: - void sigMessage( const Message & msg ) const; - void sigProgress( int c, int m ) const; - -protected: - virtual bool updateArrayItem( NifItem * array, bool fast ) = 0; - int getArraySize( NifItem * array ) const; - - virtual NifItem * getItem( NifItem * parent, const QString & name ) const; - NifItem * getItemX( NifItem * item, const QString & name ) const; // find upwards - - template T get( NifItem * parent, const QString & name ) const; - template T get( NifItem * item ) const; - - template bool set( NifItem * parent, const QString & name, const T & d ); - template bool set( NifItem * item, const T & d ); - virtual bool setItemValue( NifItem * item, const NifValue & v ) = 0; - bool setItemValue( NifItem * parent, const QString & name, const NifValue & v ); - - virtual bool evalVersion( NifItem * item, bool chkParents = false ) const = 0; - bool evalCondition( NifItem * item, bool chkParents = false ) const; - bool evalConditionHelper( NifItem * item, const QString & cond ) const; - - virtual QString ver2str( quint32 ) const = 0; - virtual quint32 str2ver( QString ) const = 0; - - virtual bool setHeaderString( const QString & ) = 0; - - // root item - NifItem * root; - - QString folder; - - MsgMode msgMode; - mutable QList messages; - - void msg( const Message & m ) const; - - friend class NifIStream; - friend class NifOStream; -}; // class BaseModel - - -template inline T BaseModel::get( NifItem * parent, const QString & name ) const -{ - NifItem * item = getItem( parent, name ); - if ( item ) - return item->value().get(); - else - return T(); -} - -template inline T BaseModel::get( const QModelIndex & parent, const QString & name ) const -{ - NifItem * parentItem = static_cast( parent.internalPointer() ); - if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) - return T(); - - NifItem * item = getItem( parentItem, name ); - if ( item ) - return item->value().get(); - else - return T(); -} - -template inline bool BaseModel::set( NifItem * parent, const QString & name, const T & d ) -{ - NifItem * item = getItem( parent, name ); - if ( item ) - return set( item, d ); - else - return false; -} - -template inline bool BaseModel::set( const QModelIndex & parent, const QString & name, const T & d ) -{ - NifItem * parentItem = static_cast( parent.internalPointer() ); - if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) - return false; - - NifItem * item = getItem( parentItem, name ); - if ( item ) - return set( item, d ); - else - return false; -} - -template inline T BaseModel::get( NifItem * item ) const -{ - return item->value().get(); -} - -template inline T BaseModel::get( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) - return T(); - - return item->value().get(); -} - -template inline bool BaseModel::set( NifItem * item, const T & d ) -{ - if ( item->value().set( d ) ) - { - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - return true; - } - else - return false; -} - -template inline bool BaseModel::set( const QModelIndex & index, const T & d ) -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return false; - return set( item, d ); -} - -template inline QVector BaseModel::getArray( const QModelIndex & iArray ) const -{ - NifItem * item = static_cast( iArray.internalPointer() ); - if ( isArray( iArray ) && item && iArray.model() == this ) - return item->getArray(); - return QVector(); -} - -template inline QVector BaseModel::getArray( const QModelIndex & iParent, const QString & name ) const -{ - return getArray( getIndex( iParent, name ) ); -} - -template inline void BaseModel::setArray( const QModelIndex & iArray, const QVector & array ) -{ - NifItem * item = static_cast( iArray.internalPointer() ); - if ( isArray( iArray ) && item && iArray.model() == this ) - { - item->setArray( array ); - int x = item->childCount() - 1; - if ( x >= 0 ) - emit dataChanged( createIndex( 0, ValueCol, item->child( 0 ) ), createIndex( x, ValueCol, item->child( x ) ) ); - } -} - -template inline void BaseModel::setArray( const QModelIndex & iParent, const QString & name, const QVector & array ) -{ - setArray( getIndex( iParent, name ), array ); -} - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef BaseModel_H +#define BaseModel_H + +#include + +#include + +class QAbstractItemDelegate; + +#include "niftypes.h" +#include "nifitem.h" + +#include "message.h" + +//! Base class for nif and kfm models, which store files in memory. +/*! + * This class serves as an abstract base class for NifModel and KfmModel + * classes. + */ +class BaseModel : public QAbstractItemModel +{ +Q_OBJECT +public: + BaseModel( QObject * parent = 0 ); + ~BaseModel(); + + //! Clear model data. + virtual void clear() = 0; + + //! Load from file. + bool loadFromFile( const QString & filename ); + //! Save to file. + bool saveToFile( const QString & filename ) const; + + //! Generic load from QIODevice. + virtual bool load( QIODevice & device ) = 0; + //! Generic save to QIODevice. + virtual bool save( QIODevice & device ) const = 0; + + //! If the model was loaded from a file then getFolder returns the folder. + /*! + * This function is used to resolve external resources. + * \return The folder of the last file that was loaded with loadFromFile. + */ + QString getFolder() const { return folder; } + + //! Return true if the index pointed to is an array. + /*! + * \param array The index to check. + * \return true if the index is an array. + */ + bool isArray( const QModelIndex & iArray ) const; + //! Update the size of an array (append or remove items). + /*! + * \param array The index of the array whose size to update. + * \return true if the update succeeded, false otherwise. + */ + bool updateArray( const QModelIndex & iArray ); + bool updateArray( const QModelIndex & parent, const QString & name ); + //! Get an model index array as a QVector. + /*! + * \param array The index of the array to get. + * \return The array as QVector. + */ + template QVector getArray( const QModelIndex & iArray ) const; + template QVector getArray( const QModelIndex & iArray, const QString & name ) const; + //! Write a QVector to a model index array. + template void setArray( const QModelIndex & iArray, const QVector & array ); + template void setArray( const QModelIndex & iArray, const QString & name, const QVector & array ); + + //! Get an item. + template T get( const QModelIndex & index ) const; + template T get( const QModelIndex & parent, const QString & name ) const; + //! Set an item. + template bool set( const QModelIndex & index, const T & d ); + template bool set( const QModelIndex & parent, const QString & name, const T & v ); + + //! Get an item as a NifValue. + NifValue getValue( const QModelIndex & index ) const; + NifValue getValue( const QModelIndex & parent, const QString & name ) const; + + //! Set an item from a NifValue. + bool setValue( const QModelIndex & index, const NifValue & v ); + bool setValue( const QModelIndex & parent, const QString & name, const NifValue & v ); + + // get item attributes + //! Get the item name. + QString itemName( const QModelIndex & index ) const; + //! Get the item type string. + QString itemType( const QModelIndex & index ) const; + //! Get the item argument string. + QString itemArg( const QModelIndex & index ) const; + //! Get the item arr1 string. + QString itemArr1( const QModelIndex & index ) const; + //! Get the item arr2 string. + QString itemArr2( const QModelIndex & index ) const; + //! Get the item condition string. + QString itemCond( const QModelIndex & index ) const; + //! Get the item first version. + quint32 itemVer1( const QModelIndex & index ) const; + //! Get the item last version. + quint32 itemVer2( const QModelIndex & index ) const; + //! Get the item documentation. + QString itemText( const QModelIndex & index ) const; + //! Get the item template string. + QString itemTmplt( const QModelIndex & index ) const; + + // find a branch by name + QModelIndex getIndex( const QModelIndex & parent, const QString & name ) const; + + // evaluate condition and version + bool evalCondition( const QModelIndex & idx, bool chkParents = false ) const; + // evaluate version + bool evalVersion( const QModelIndex & idx, bool chkParents = false ) const; + + virtual QString getVersion() const = 0; + virtual quint32 getVersionNumber() const = 0; + + + // QAbstractModel interface + + QModelIndex index( int row, int column, const QModelIndex & parent = QModelIndex() ) const; + QModelIndex parent( const QModelIndex & index ) const; + + int rowCount( const QModelIndex & parent = QModelIndex() ) const; + int columnCount( const QModelIndex & parent = QModelIndex() ) const { return 9; } + + QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; + bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ); + + QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; + + Qt::ItemFlags flags( const QModelIndex & index ) const; + + + + // column numbers + enum { + NameCol = 0, + TypeCol = 1, + ValueCol = 2, + ArgCol = 3, + Arr1Col = 4, + Arr2Col = 5, + CondCol = 6, + Ver1Col = 7, + Ver2Col = 8 + }; + + enum MsgMode + { + EmitMessages, CollectMessages + }; + + void setMessageMode( MsgMode m ) { msgMode = m; } + QList getMessages() const { QList lst = messages; messages.clear(); return lst; } + +signals: + void sigMessage( const Message & msg ) const; + void sigProgress( int c, int m ) const; + +protected: + virtual bool updateArrayItem( NifItem * array, bool fast ) = 0; + int getArraySize( NifItem * array ) const; + + virtual NifItem * getItem( NifItem * parent, const QString & name ) const; + NifItem * getItemX( NifItem * item, const QString & name ) const; // find upwards + + template T get( NifItem * parent, const QString & name ) const; + template T get( NifItem * item ) const; + + template bool set( NifItem * parent, const QString & name, const T & d ); + template bool set( NifItem * item, const T & d ); + virtual bool setItemValue( NifItem * item, const NifValue & v ) = 0; + bool setItemValue( NifItem * parent, const QString & name, const NifValue & v ); + + virtual bool evalVersion( NifItem * item, bool chkParents = false ) const = 0; + bool evalCondition( NifItem * item, bool chkParents = false ) const; + bool evalConditionHelper( NifItem * item, const QString & cond ) const; + + virtual QString ver2str( quint32 ) const = 0; + virtual quint32 str2ver( QString ) const = 0; + + virtual bool setHeaderString( const QString & ) = 0; + + // root item + NifItem * root; + + QString folder; + + MsgMode msgMode; + mutable QList messages; + + void msg( const Message & m ) const; + + friend class NifIStream; + friend class NifOStream; +}; // class BaseModel + + +template inline T BaseModel::get( NifItem * parent, const QString & name ) const +{ + NifItem * item = getItem( parent, name ); + if ( item ) + return item->value().get(); + else + return T(); +} + +template inline T BaseModel::get( const QModelIndex & parent, const QString & name ) const +{ + NifItem * parentItem = static_cast( parent.internalPointer() ); + if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) + return T(); + + NifItem * item = getItem( parentItem, name ); + if ( item ) + return item->value().get(); + else + return T(); +} + +template inline bool BaseModel::set( NifItem * parent, const QString & name, const T & d ) +{ + NifItem * item = getItem( parent, name ); + if ( item ) + return set( item, d ); + else + return false; +} + +template inline bool BaseModel::set( const QModelIndex & parent, const QString & name, const T & d ) +{ + NifItem * parentItem = static_cast( parent.internalPointer() ); + if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) + return false; + + NifItem * item = getItem( parentItem, name ); + if ( item ) + return set( item, d ); + else + return false; +} + +template inline T BaseModel::get( NifItem * item ) const +{ + return item->value().get(); +} + +template inline T BaseModel::get( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) + return T(); + + return item->value().get(); +} + +template inline bool BaseModel::set( NifItem * item, const T & d ) +{ + if ( item->value().set( d ) ) + { + emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); + return true; + } + else + return false; +} + +template inline bool BaseModel::set( const QModelIndex & index, const T & d ) +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return false; + return set( item, d ); +} + +template inline QVector BaseModel::getArray( const QModelIndex & iArray ) const +{ + NifItem * item = static_cast( iArray.internalPointer() ); + if ( isArray( iArray ) && item && iArray.model() == this ) + return item->getArray(); + return QVector(); +} + +template inline QVector BaseModel::getArray( const QModelIndex & iParent, const QString & name ) const +{ + return getArray( getIndex( iParent, name ) ); +} + +template inline void BaseModel::setArray( const QModelIndex & iArray, const QVector & array ) +{ + NifItem * item = static_cast( iArray.internalPointer() ); + if ( isArray( iArray ) && item && iArray.model() == this ) + { + item->setArray( array ); + int x = item->childCount() - 1; + if ( x >= 0 ) + emit dataChanged( createIndex( 0, ValueCol, item->child( 0 ) ), createIndex( x, ValueCol, item->child( x ) ) ); + } +} + +template inline void BaseModel::setArray( const QModelIndex & iParent, const QString & name, const QVector & array ) +{ + setArray( getIndex( iParent, name ), array ); +} + +#endif diff --git a/fsengine/bsa.cpp b/fsengine/bsa.cpp index cb075282b..7731c8d46 100644 --- a/fsengine/bsa.cpp +++ b/fsengine/bsa.cpp @@ -1,516 +1,516 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - - -#include "bsa.h" - -#include -#include -#include - -/* Default header data */ -#define MW_BSAHEADER_FILEID 0x00000100 -#define OB_BSAHEADER_FILEID 0x00415342 /* "BSA\0" */ -#define OB_BSAHEADER_VERSION 0x67 - -/* Archive flags */ -#define OB_BSAARCHIVE_PATHNAMES 1 -#define OB_BSAARCHIVE_FILENAMES 2 -#define OB_BSAARCHIVE_COMPRESSFILES 4 - -/* File flags */ -#define OB_BSAFILE_NIF 0x0001 -#define OB_BSAFILE_DDS 0x0002 -#define OB_BSAFILE_XML 0x0004 -#define OB_BSAFILE_WAV 0x0008 -#define OB_BSAFILE_MP3 0x0010 -#define OB_BSAFILE_TXT 0x0020 -#define OB_BSAFILE_HTML 0x0020 -#define OB_BSAFILE_BAT 0x0020 -#define OB_BSAFILE_SCC 0x0020 -#define OB_BSAFILE_SPT 0x0040 -#define OB_BSAFILE_TEX 0x0080 -#define OB_BSAFILE_FNT 0x0080 -#define OB_BSAFILE_CTL 0x0100 - -/* Bitmasks for the size field in the header */ -#define OB_BSAFILE_SIZEMASK 0x3fffffff -#define OB_BSAFILE_FLAGMASK 0xC0000000 - -/* Record flags */ -#define OB_BSAFILE_FLAG_COMPRESS 0xC0000000 - -struct OBBSAHeader -{ - quint32 FolderRecordOffset; - quint32 ArchiveFlags; - quint32 FolderCount; - quint32 FileCount; - quint32 FolderNameLength; - quint32 FileNameLength; - quint32 FileFlags; - - friend QDebug operator<<( QDebug dbg, const OBBSAHeader & head ) - { - return dbg << "BSAHeader:" - << "\n folder offset" << head.FolderRecordOffset - << "\n archive flags" << head.ArchiveFlags - << "\n folder Count" << head.FolderCount - << "\n file Count" << head.FileCount - << "\n folder name length" << head.FolderNameLength - << "\n file name length" << head.FileNameLength - << "\n file flags" << head.FileFlags; - } - -}; - -struct OBBSAFileInfo -{ - quint64 hash; - quint32 sizeFlags; - quint32 offset; -}; - -struct OBBSAFolderInfo -{ - quint64 hash; - quint32 fileCount; - quint32 offset; -}; - - -struct MWBSAHeader -{ - quint32 HashOffset; - quint32 FileCount; -}; - -struct MWBSAFileSizeOffset -{ - quint32 size; - quint32 offset; -}; - - -quint32 BSA::BSAFile::size() const -{ - return sizeFlags & OB_BSAFILE_SIZEMASK; -} -bool BSA::BSAFile::compressed() const -{ - return sizeFlags & OB_BSAFILE_FLAG_COMPRESS; -} - -static bool BSAReadString( QAbstractFileEngine & bsa, QString & s ) -{ - quint8 len; - if ( bsa.read( (char *) & len, 1 ) != 1 ) - return false; - - QByteArray b( len, 0 ); - if ( bsa.read( b.data(), len ) == len ) - { - s = b; - return true; - } - else - return false; -} - - -BSA::BSA( const QString & filename ) - : FSArchiveFile(), bsa( filename ), status( "initialized" ) -{ - bsaPath = bsa.fileName( QAbstractFileEngine::AbsoluteName ); - bsaBase = bsa.fileName( QAbstractFileEngine::AbsolutePathName ); - bsaName = bsa.fileName( QAbstractFileEngine::BaseName ); - - if ( ! bsa.caseSensitive() ) - { - bsaPath = bsaPath.toLower(); - bsaBase = bsaBase.toLower(); - bsaName = bsaName.toLower(); - } - - qDebug() << "BSA" << bsaName << bsaBase << bsaPath; -} - -BSA::~BSA() -{ - close(); -} - -bool BSA::canOpen( const QString & fn ) -{ - QFSFileEngine f( fn ); - if ( f.open( QIODevice::ReadOnly ) ) - { - quint32 magic, version; - - if ( f.read( (char *) & magic, sizeof( magic ) ) != 4 ) - return false; - - if ( magic == OB_BSAHEADER_FILEID ) - { - if ( f.read( (char *) & version, sizeof( version ) ) != 4 ) - return false; - - return ( version == OB_BSAHEADER_VERSION ); - } - else - return magic == MW_BSAHEADER_FILEID; - } - - return false; -} - -bool BSA::open() -{ - QMutexLocker lock( & bsaMutex ); - - try - { - if ( ! bsa.open( QIODevice::ReadOnly ) ) - throw QString( "file open" ); - - quint32 magic, version; - - bsa.read( (char*) &magic, sizeof( magic ) ); - - if ( magic == OB_BSAHEADER_FILEID ) - { - bsa.read( (char*) &version, sizeof( version ) ); - - if ( version != OB_BSAHEADER_VERSION ) - throw QString( "file version" ); - - OBBSAHeader header; - - if ( bsa.read( (char *) & header, sizeof( header ) ) != sizeof( header ) ) - throw QString( "header size" ); - - //qWarning() << bsaName << header; - - if ( ( header.ArchiveFlags & OB_BSAARCHIVE_PATHNAMES ) == 0 || ( header.ArchiveFlags & OB_BSAARCHIVE_FILENAMES ) == 0 ) - throw QString( "header flags" ); - - compressToggle = header.ArchiveFlags & OB_BSAARCHIVE_COMPRESSFILES; - - if ( ! bsa.seek( header.FolderRecordOffset + header.FolderNameLength + header.FolderCount * ( 1 + sizeof( OBBSAFolderInfo ) ) + header.FileCount * sizeof( OBBSAFileInfo ) ) ) - throw QString( "file name seek" ); - - QByteArray fileNames( header.FileNameLength, 0 ); - if ( bsa.read( fileNames.data(), header.FileNameLength ) != header.FileNameLength ) - throw QString( "file name read" ); - quint32 fileNameIndex = 0; - - // qDebug() << file.pos() - header.FileNameLength << fileNames; - - if ( ! bsa.seek( header.FolderRecordOffset ) ) - throw QString( "folder info seek" ); - - QVector folderInfos( header.FolderCount ); - if ( bsa.read( (char *) folderInfos.data(), header.FolderCount * sizeof( OBBSAFolderInfo ) ) != header.FolderCount * sizeof( OBBSAFolderInfo ) ) - throw QString( "folder info read" ); - - quint32 totalFileCount = 0; - - foreach ( OBBSAFolderInfo folderInfo, folderInfos ) - { - /* - qDebug() << file.pos() << folderInfos[c].offset; - if ( folderInfos[c].offset < header.FileNameLength || ! file.seek( folderInfos[c].offset - header.FileNameLength ) ) - throw QString( "folder content seek" ); - */ - - QString folderName; - if ( ! BSAReadString( bsa, folderName ) || folderName.isEmpty() ) - throw QString( "folder name read" ); - - // qDebug() << folderName; - - BSAFolder * folder = insertFolder( folderName ); - - quint32 fcnt = folderInfo.fileCount; - totalFileCount += fcnt; - QVector fileInfos( fcnt ); - if ( bsa.read( (char *) fileInfos.data(), fcnt * sizeof( OBBSAFileInfo ) ) != fcnt * sizeof( OBBSAFileInfo ) ) - throw QString( "file info read" ); - - foreach ( OBBSAFileInfo fileInfo, fileInfos ) - { - if ( fileNameIndex >= header.FileNameLength ) - throw QString( "file name size" ); - - QString fileName = ( fileNames.data() + fileNameIndex ); - fileNameIndex += fileName.length() + 1; - - insertFile( folder, fileName, fileInfo.sizeFlags, fileInfo.offset ); - } - } - - if ( totalFileCount != header.FileCount ) - throw QString( "file count" ); - } - else if ( magic == MW_BSAHEADER_FILEID ) - { - MWBSAHeader header; - - if ( bsa.read( (char *) & header, sizeof( header ) ) != sizeof( header ) ) - throw QString( "header" ); - - - compressToggle = false; - - quint32 dataOffset = 12 + header.HashOffset + header.FileCount * 8; - - QVector sizeOffset( header.FileCount ); - if ( bsa.read( (char *) sizeOffset.data(), header.FileCount * sizeof( MWBSAFileSizeOffset ) ) != header.FileCount * sizeof( MWBSAFileSizeOffset ) ) - throw QString( "file size/offset" ); - - QVector nameOffset( header.FileCount ); - if ( bsa.read( (char *) nameOffset.data(), header.FileCount * sizeof( quint32 ) ) != header.FileCount * sizeof( quint32 ) ) - throw QString( "file name offset" ); - - QByteArray fileNames; - fileNames.resize( header.HashOffset - 12 * header.FileCount ); - if ( bsa.read( (char *) fileNames.data(), header.HashOffset - 12 * header.FileCount ) != header.HashOffset - 12 * header.FileCount ) - throw QString( "file names" ); - - for ( quint32 c = 0; c < header.FileCount; c++ ) - { - QString fname = fileNames.data() + nameOffset[ c ]; - QString dname; - int x = fname.lastIndexOf( "\\" ); - if ( x > 0 ) - { - dname = fname.left( x ); - fname = fname.remove( 0, x + 1 ); - } - - // qDebug() << "inserting" << dname << fname; - - insertFile( insertFolder( dname ), fname, sizeOffset[ c ].size, dataOffset + sizeOffset[ c ].offset ); - } - } - else - throw QString( "file magic" ); - } - catch ( QString e ) - { - status = e; - return false; - } - - status = "loaded successful"; - - return true; -} - -void BSA::close() -{ - QMutexLocker lock( & bsaMutex ); - - bsa.close(); - qDeleteAll( root.children ); - qDeleteAll( root.files ); - root.children.clear(); - root.files.clear(); - folders.clear(); -} - -qint64 BSA::fileSize( const QString & fn ) const -{ - // note: lazy size count (not accurate for compressed files) - if ( const BSAFile * file = getFile( fn ) ) - { - return file->size(); - } - return 0; -} - -bool BSA::fileContents( const QString & fn, QByteArray & content ) -{ - if ( const BSAFile * file = getFile( fn ) ) - { - QMutexLocker lock( & bsaMutex ); - if ( bsa.seek( file->offset ) ) - { - content.resize( file->size() ); - if ( bsa.read( content.data(), file->size() ) == file->size() ) - { - if ( file->compressed() ^ compressToggle ) - { - quint8 x = content[0]; - content[0] = content[3]; - content[3] = x; - x = content[1]; - content[1] = content[2]; - content[2] = x; - content = qUncompress( content ); - } - return true; - } - } - } - return false; -} - -QStringList BSA::entryList( const QString & fn, QDir::Filters filters ) const -{ - if ( const BSAFolder * folder = getFolder( fn ) ) - { - QStringList entries; - - // todo: filter - - if ( filters.testFlag( QDir::Dirs ) || filters.testFlag( QDir::AllDirs ) ) - { - entries += folder->children.keys(); - } - - if ( filters.testFlag( QDir::Files ) ) - { - entries += folder->files.keys(); - } - - return entries; - } - return QStringList(); -} - -bool BSA::stripBasePath( QString & p ) const -{ - QString base = bsaPath; - - if ( p.startsWith( base ) ) - { - p.remove( 0, base.length() ); - if ( p.startsWith( "/" ) ) - p.remove( 0, 1 ); - return true; - } - return false; -} - -BSA::BSAFolder * BSA::insertFolder( QString name ) -{ - if ( name.isEmpty() ) - return & root; - - name = name.replace( "\\", "/" ).toLower(); - - BSAFolder * folder = folders.value( name ); - if ( ! folder ) - { - // qDebug() << "inserting" << name; - - folder = new BSAFolder; - folders.insert( name, folder ); - - int p = name.lastIndexOf( "/" ); - if ( p >= 0 ) - { - folder->parent = insertFolder( name.left( p ) ); - folder->parent->children.insert( name.right( name.length() - p - 1 ), folder ); - } - else - { - folder->parent = & root; - root.children.insert( name, folder ); - } - } - - return folder; -} - -BSA::BSAFile * BSA::insertFile( BSAFolder * folder, QString name, quint32 sizeFlags, quint32 offset ) -{ - name = name.toLower(); - - BSAFile * file = new BSAFile; - file->sizeFlags = sizeFlags; - file->offset = offset; - - folder->files.insert( name, file ); - return file; -} - -const BSA::BSAFolder * BSA::getFolder( QString fn ) const -{ - if ( fn.isEmpty() ) - return & root; - else - return folders.value( fn ); -} - -const BSA::BSAFile * BSA::getFile( QString fn ) const -{ - QString folderName; - QString fileName = fn; - int p = fn.lastIndexOf( "/" ); - if ( p >= 0 ) - { - folderName = fn.left( p ); - fileName = fn.right( fn.length() - p - 1 ); - } - - if ( const BSAFolder * folder = getFolder( folderName ) ) - return folder->files.value( fileName ); - else - return 0; -} - -bool BSA::hasFile( const QString & fn ) const -{ - return getFile( fn ); -} - -bool BSA::hasFolder( const QString & fn ) const -{ - return getFolder( fn ); -} - -uint BSA::ownerId( const QString &, QAbstractFileEngine::FileOwner type ) const -{ - return bsa.ownerId( type ); -} - -QString BSA::owner( const QString &, QAbstractFileEngine::FileOwner type ) const -{ - return bsa.owner( type ); -} - -QDateTime BSA::fileTime( const QString &, QAbstractFileEngine::FileTime type ) const -{ - return bsa.fileTime( type ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + + +#include "bsa.h" + +#include +#include +#include + +/* Default header data */ +#define MW_BSAHEADER_FILEID 0x00000100 +#define OB_BSAHEADER_FILEID 0x00415342 /* "BSA\0" */ +#define OB_BSAHEADER_VERSION 0x67 + +/* Archive flags */ +#define OB_BSAARCHIVE_PATHNAMES 1 +#define OB_BSAARCHIVE_FILENAMES 2 +#define OB_BSAARCHIVE_COMPRESSFILES 4 + +/* File flags */ +#define OB_BSAFILE_NIF 0x0001 +#define OB_BSAFILE_DDS 0x0002 +#define OB_BSAFILE_XML 0x0004 +#define OB_BSAFILE_WAV 0x0008 +#define OB_BSAFILE_MP3 0x0010 +#define OB_BSAFILE_TXT 0x0020 +#define OB_BSAFILE_HTML 0x0020 +#define OB_BSAFILE_BAT 0x0020 +#define OB_BSAFILE_SCC 0x0020 +#define OB_BSAFILE_SPT 0x0040 +#define OB_BSAFILE_TEX 0x0080 +#define OB_BSAFILE_FNT 0x0080 +#define OB_BSAFILE_CTL 0x0100 + +/* Bitmasks for the size field in the header */ +#define OB_BSAFILE_SIZEMASK 0x3fffffff +#define OB_BSAFILE_FLAGMASK 0xC0000000 + +/* Record flags */ +#define OB_BSAFILE_FLAG_COMPRESS 0xC0000000 + +struct OBBSAHeader +{ + quint32 FolderRecordOffset; + quint32 ArchiveFlags; + quint32 FolderCount; + quint32 FileCount; + quint32 FolderNameLength; + quint32 FileNameLength; + quint32 FileFlags; + + friend QDebug operator<<( QDebug dbg, const OBBSAHeader & head ) + { + return dbg << "BSAHeader:" + << "\n folder offset" << head.FolderRecordOffset + << "\n archive flags" << head.ArchiveFlags + << "\n folder Count" << head.FolderCount + << "\n file Count" << head.FileCount + << "\n folder name length" << head.FolderNameLength + << "\n file name length" << head.FileNameLength + << "\n file flags" << head.FileFlags; + } + +}; + +struct OBBSAFileInfo +{ + quint64 hash; + quint32 sizeFlags; + quint32 offset; +}; + +struct OBBSAFolderInfo +{ + quint64 hash; + quint32 fileCount; + quint32 offset; +}; + + +struct MWBSAHeader +{ + quint32 HashOffset; + quint32 FileCount; +}; + +struct MWBSAFileSizeOffset +{ + quint32 size; + quint32 offset; +}; + + +quint32 BSA::BSAFile::size() const +{ + return sizeFlags & OB_BSAFILE_SIZEMASK; +} +bool BSA::BSAFile::compressed() const +{ + return sizeFlags & OB_BSAFILE_FLAG_COMPRESS; +} + +static bool BSAReadString( QAbstractFileEngine & bsa, QString & s ) +{ + quint8 len; + if ( bsa.read( (char *) & len, 1 ) != 1 ) + return false; + + QByteArray b( len, 0 ); + if ( bsa.read( b.data(), len ) == len ) + { + s = b; + return true; + } + else + return false; +} + + +BSA::BSA( const QString & filename ) + : FSArchiveFile(), bsa( filename ), status( "initialized" ) +{ + bsaPath = bsa.fileName( QAbstractFileEngine::AbsoluteName ); + bsaBase = bsa.fileName( QAbstractFileEngine::AbsolutePathName ); + bsaName = bsa.fileName( QAbstractFileEngine::BaseName ); + + if ( ! bsa.caseSensitive() ) + { + bsaPath = bsaPath.toLower(); + bsaBase = bsaBase.toLower(); + bsaName = bsaName.toLower(); + } + + qDebug() << "BSA" << bsaName << bsaBase << bsaPath; +} + +BSA::~BSA() +{ + close(); +} + +bool BSA::canOpen( const QString & fn ) +{ + QFSFileEngine f( fn ); + if ( f.open( QIODevice::ReadOnly ) ) + { + quint32 magic, version; + + if ( f.read( (char *) & magic, sizeof( magic ) ) != 4 ) + return false; + + if ( magic == OB_BSAHEADER_FILEID ) + { + if ( f.read( (char *) & version, sizeof( version ) ) != 4 ) + return false; + + return ( version == OB_BSAHEADER_VERSION ); + } + else + return magic == MW_BSAHEADER_FILEID; + } + + return false; +} + +bool BSA::open() +{ + QMutexLocker lock( & bsaMutex ); + + try + { + if ( ! bsa.open( QIODevice::ReadOnly ) ) + throw QString( "file open" ); + + quint32 magic, version; + + bsa.read( (char*) &magic, sizeof( magic ) ); + + if ( magic == OB_BSAHEADER_FILEID ) + { + bsa.read( (char*) &version, sizeof( version ) ); + + if ( version != OB_BSAHEADER_VERSION ) + throw QString( "file version" ); + + OBBSAHeader header; + + if ( bsa.read( (char *) & header, sizeof( header ) ) != sizeof( header ) ) + throw QString( "header size" ); + + //qWarning() << bsaName << header; + + if ( ( header.ArchiveFlags & OB_BSAARCHIVE_PATHNAMES ) == 0 || ( header.ArchiveFlags & OB_BSAARCHIVE_FILENAMES ) == 0 ) + throw QString( "header flags" ); + + compressToggle = header.ArchiveFlags & OB_BSAARCHIVE_COMPRESSFILES; + + if ( ! bsa.seek( header.FolderRecordOffset + header.FolderNameLength + header.FolderCount * ( 1 + sizeof( OBBSAFolderInfo ) ) + header.FileCount * sizeof( OBBSAFileInfo ) ) ) + throw QString( "file name seek" ); + + QByteArray fileNames( header.FileNameLength, 0 ); + if ( bsa.read( fileNames.data(), header.FileNameLength ) != header.FileNameLength ) + throw QString( "file name read" ); + quint32 fileNameIndex = 0; + + // qDebug() << file.pos() - header.FileNameLength << fileNames; + + if ( ! bsa.seek( header.FolderRecordOffset ) ) + throw QString( "folder info seek" ); + + QVector folderInfos( header.FolderCount ); + if ( bsa.read( (char *) folderInfos.data(), header.FolderCount * sizeof( OBBSAFolderInfo ) ) != header.FolderCount * sizeof( OBBSAFolderInfo ) ) + throw QString( "folder info read" ); + + quint32 totalFileCount = 0; + + foreach ( OBBSAFolderInfo folderInfo, folderInfos ) + { + /* + qDebug() << file.pos() << folderInfos[c].offset; + if ( folderInfos[c].offset < header.FileNameLength || ! file.seek( folderInfos[c].offset - header.FileNameLength ) ) + throw QString( "folder content seek" ); + */ + + QString folderName; + if ( ! BSAReadString( bsa, folderName ) || folderName.isEmpty() ) + throw QString( "folder name read" ); + + // qDebug() << folderName; + + BSAFolder * folder = insertFolder( folderName ); + + quint32 fcnt = folderInfo.fileCount; + totalFileCount += fcnt; + QVector fileInfos( fcnt ); + if ( bsa.read( (char *) fileInfos.data(), fcnt * sizeof( OBBSAFileInfo ) ) != fcnt * sizeof( OBBSAFileInfo ) ) + throw QString( "file info read" ); + + foreach ( OBBSAFileInfo fileInfo, fileInfos ) + { + if ( fileNameIndex >= header.FileNameLength ) + throw QString( "file name size" ); + + QString fileName = ( fileNames.data() + fileNameIndex ); + fileNameIndex += fileName.length() + 1; + + insertFile( folder, fileName, fileInfo.sizeFlags, fileInfo.offset ); + } + } + + if ( totalFileCount != header.FileCount ) + throw QString( "file count" ); + } + else if ( magic == MW_BSAHEADER_FILEID ) + { + MWBSAHeader header; + + if ( bsa.read( (char *) & header, sizeof( header ) ) != sizeof( header ) ) + throw QString( "header" ); + + + compressToggle = false; + + quint32 dataOffset = 12 + header.HashOffset + header.FileCount * 8; + + QVector sizeOffset( header.FileCount ); + if ( bsa.read( (char *) sizeOffset.data(), header.FileCount * sizeof( MWBSAFileSizeOffset ) ) != header.FileCount * sizeof( MWBSAFileSizeOffset ) ) + throw QString( "file size/offset" ); + + QVector nameOffset( header.FileCount ); + if ( bsa.read( (char *) nameOffset.data(), header.FileCount * sizeof( quint32 ) ) != header.FileCount * sizeof( quint32 ) ) + throw QString( "file name offset" ); + + QByteArray fileNames; + fileNames.resize( header.HashOffset - 12 * header.FileCount ); + if ( bsa.read( (char *) fileNames.data(), header.HashOffset - 12 * header.FileCount ) != header.HashOffset - 12 * header.FileCount ) + throw QString( "file names" ); + + for ( quint32 c = 0; c < header.FileCount; c++ ) + { + QString fname = fileNames.data() + nameOffset[ c ]; + QString dname; + int x = fname.lastIndexOf( "\\" ); + if ( x > 0 ) + { + dname = fname.left( x ); + fname = fname.remove( 0, x + 1 ); + } + + // qDebug() << "inserting" << dname << fname; + + insertFile( insertFolder( dname ), fname, sizeOffset[ c ].size, dataOffset + sizeOffset[ c ].offset ); + } + } + else + throw QString( "file magic" ); + } + catch ( QString e ) + { + status = e; + return false; + } + + status = "loaded successful"; + + return true; +} + +void BSA::close() +{ + QMutexLocker lock( & bsaMutex ); + + bsa.close(); + qDeleteAll( root.children ); + qDeleteAll( root.files ); + root.children.clear(); + root.files.clear(); + folders.clear(); +} + +qint64 BSA::fileSize( const QString & fn ) const +{ + // note: lazy size count (not accurate for compressed files) + if ( const BSAFile * file = getFile( fn ) ) + { + return file->size(); + } + return 0; +} + +bool BSA::fileContents( const QString & fn, QByteArray & content ) +{ + if ( const BSAFile * file = getFile( fn ) ) + { + QMutexLocker lock( & bsaMutex ); + if ( bsa.seek( file->offset ) ) + { + content.resize( file->size() ); + if ( bsa.read( content.data(), file->size() ) == file->size() ) + { + if ( file->compressed() ^ compressToggle ) + { + quint8 x = content[0]; + content[0] = content[3]; + content[3] = x; + x = content[1]; + content[1] = content[2]; + content[2] = x; + content = qUncompress( content ); + } + return true; + } + } + } + return false; +} + +QStringList BSA::entryList( const QString & fn, QDir::Filters filters ) const +{ + if ( const BSAFolder * folder = getFolder( fn ) ) + { + QStringList entries; + + // todo: filter + + if ( filters.testFlag( QDir::Dirs ) || filters.testFlag( QDir::AllDirs ) ) + { + entries += folder->children.keys(); + } + + if ( filters.testFlag( QDir::Files ) ) + { + entries += folder->files.keys(); + } + + return entries; + } + return QStringList(); +} + +bool BSA::stripBasePath( QString & p ) const +{ + QString base = bsaPath; + + if ( p.startsWith( base ) ) + { + p.remove( 0, base.length() ); + if ( p.startsWith( "/" ) ) + p.remove( 0, 1 ); + return true; + } + return false; +} + +BSA::BSAFolder * BSA::insertFolder( QString name ) +{ + if ( name.isEmpty() ) + return & root; + + name = name.replace( "\\", "/" ).toLower(); + + BSAFolder * folder = folders.value( name ); + if ( ! folder ) + { + // qDebug() << "inserting" << name; + + folder = new BSAFolder; + folders.insert( name, folder ); + + int p = name.lastIndexOf( "/" ); + if ( p >= 0 ) + { + folder->parent = insertFolder( name.left( p ) ); + folder->parent->children.insert( name.right( name.length() - p - 1 ), folder ); + } + else + { + folder->parent = & root; + root.children.insert( name, folder ); + } + } + + return folder; +} + +BSA::BSAFile * BSA::insertFile( BSAFolder * folder, QString name, quint32 sizeFlags, quint32 offset ) +{ + name = name.toLower(); + + BSAFile * file = new BSAFile; + file->sizeFlags = sizeFlags; + file->offset = offset; + + folder->files.insert( name, file ); + return file; +} + +const BSA::BSAFolder * BSA::getFolder( QString fn ) const +{ + if ( fn.isEmpty() ) + return & root; + else + return folders.value( fn ); +} + +const BSA::BSAFile * BSA::getFile( QString fn ) const +{ + QString folderName; + QString fileName = fn; + int p = fn.lastIndexOf( "/" ); + if ( p >= 0 ) + { + folderName = fn.left( p ); + fileName = fn.right( fn.length() - p - 1 ); + } + + if ( const BSAFolder * folder = getFolder( folderName ) ) + return folder->files.value( fileName ); + else + return 0; +} + +bool BSA::hasFile( const QString & fn ) const +{ + return getFile( fn ); +} + +bool BSA::hasFolder( const QString & fn ) const +{ + return getFolder( fn ); +} + +uint BSA::ownerId( const QString &, QAbstractFileEngine::FileOwner type ) const +{ + return bsa.ownerId( type ); +} + +QString BSA::owner( const QString &, QAbstractFileEngine::FileOwner type ) const +{ + return bsa.owner( type ); +} + +QDateTime BSA::fileTime( const QString &, QAbstractFileEngine::FileTime type ) const +{ + return bsa.fileTime( type ); +} diff --git a/fsengine/bsa.h b/fsengine/bsa.h index 4714ad889..7520a7be8 100644 --- a/fsengine/bsa.h +++ b/fsengine/bsa.h @@ -1,117 +1,117 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - - -#ifndef BSA_H -#define BSA_H - - -#include "fsengine.h" - - -#include -#include -#include - - -class BSA : public FSArchiveFile -{ -public: - BSA( const QString & filePath ); - ~BSA(); - - bool open(); - void close(); - - QString path() const { return bsaPath; } - QString base() const { return bsaBase; } - QString name() const { return bsaName; } - - bool stripBasePath( QString & ) const; - - bool hasFolder( const QString & ) const; - QStringList entryList( const QString &, QDir::Filters ) const; - - bool hasFile( const QString & ) const; - qint64 fileSize( const QString & ) const; - bool fileContents( const QString &, QByteArray & ); - - uint ownerId( const QString &, QAbstractFileEngine::FileOwner type ) const; - QString owner( const QString &, QAbstractFileEngine::FileOwner type ) const; - QDateTime fileTime( const QString &, QAbstractFileEngine::FileTime type ) const; - - static bool canOpen( const QString & ); - - QString statusText() const { return status; } - -protected: - struct BSAFile - { - quint32 sizeFlags; - quint32 offset; - - quint32 size() const; - bool compressed() const; - }; - - struct BSAFolder - { - BSAFolder() : parent( 0 ) {} - ~BSAFolder() { qDeleteAll( children ); qDeleteAll( files ); } - - BSAFolder * parent; - QHash children; - QHash files; - }; - - BSAFolder * insertFolder( QString name ); - BSAFile * insertFile( BSAFolder * folder, QString name, quint32 sizeFlags, quint32 offset ); - - const BSAFolder * getFolder( QString fn ) const; - const BSAFile * getFile( QString fn ) const; - - QFSFileEngine bsa; - QMutex bsaMutex; - - QString bsaPath; - QString bsaBase; - QString bsaName; - - QHash folders; - BSAFolder root; - - QString status; - - bool compressToggle; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + + +#ifndef BSA_H +#define BSA_H + + +#include "fsengine.h" + + +#include +#include +#include + + +class BSA : public FSArchiveFile +{ +public: + BSA( const QString & filePath ); + ~BSA(); + + bool open(); + void close(); + + QString path() const { return bsaPath; } + QString base() const { return bsaBase; } + QString name() const { return bsaName; } + + bool stripBasePath( QString & ) const; + + bool hasFolder( const QString & ) const; + QStringList entryList( const QString &, QDir::Filters ) const; + + bool hasFile( const QString & ) const; + qint64 fileSize( const QString & ) const; + bool fileContents( const QString &, QByteArray & ); + + uint ownerId( const QString &, QAbstractFileEngine::FileOwner type ) const; + QString owner( const QString &, QAbstractFileEngine::FileOwner type ) const; + QDateTime fileTime( const QString &, QAbstractFileEngine::FileTime type ) const; + + static bool canOpen( const QString & ); + + QString statusText() const { return status; } + +protected: + struct BSAFile + { + quint32 sizeFlags; + quint32 offset; + + quint32 size() const; + bool compressed() const; + }; + + struct BSAFolder + { + BSAFolder() : parent( 0 ) {} + ~BSAFolder() { qDeleteAll( children ); qDeleteAll( files ); } + + BSAFolder * parent; + QHash children; + QHash files; + }; + + BSAFolder * insertFolder( QString name ); + BSAFile * insertFile( BSAFolder * folder, QString name, quint32 sizeFlags, quint32 offset ); + + const BSAFolder * getFolder( QString fn ) const; + const BSAFile * getFile( QString fn ) const; + + QFSFileEngine bsa; + QMutex bsaMutex; + + QString bsaPath; + QString bsaBase; + QString bsaName; + + QHash folders; + BSAFolder root; + + QString status; + + bool compressToggle; +}; + +#endif diff --git a/fsengine/fsengine.cpp b/fsengine/fsengine.cpp index 6f8696b59..5a2ced132 100644 --- a/fsengine/fsengine.cpp +++ b/fsengine/fsengine.cpp @@ -1,491 +1,491 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - - -#include "fsengine.h" -#include "bsa.h" - - -#include -#include -#include -#include -#include -#include -#include - - -static QMap overlayDirs; -static QMutex overlayMutex; - - -class FSOverlayEngine : public QFSFileEngine -{ -public: - FSOverlayEngine( const QString & filename ) - : QFSFileEngine( filename ) - {} - - QStringList entryList( QDir::Filters filters, const QStringList & nameFilters ) const - { - QStringList entries = QFSFileEngine::entryList( filters, nameFilters ); - - if ( filters.testFlag( QDir::Dirs ) || filters.testFlag( QDir::AllDirs ) ) - { - QMutexLocker lock( & overlayMutex ); - - QString dirname = fileName( AbsoluteName ); - if ( ! caseSensitive() ) - dirname = dirname.toLower(); - - if ( overlayDirs.contains( dirname ) ) - { - QStringList list = overlayDirs.value( dirname ); - qDebug() << dirname << "overlay" << list; - - foreach ( QString x, list ) - { - if ( ! entries.contains( x ) ) - { - entries += x; - } - } - } - } - - return entries; - } - - FileFlags fileFlags( FileFlags type = FileInfoAll ) const - { - FileFlags x = QFSFileEngine::fileFlags( type ); - - if ( type.testFlag( DirectoryType ) ) - { - QMutexLocker lock( & overlayMutex ); - - QString base = fileName( AbsolutePathName ); - if ( ! caseSensitive() ) - base = base.toLower(); - - //qDebug() << "base" << base; - - if ( overlayDirs.contains( base ) ) - { - QString name = fileName( BaseName ); - if ( ! caseSensitive() ) - name = name.toLower(); - - //qDebug() << "name" << name; - - if ( overlayDirs[ base ].contains( name ) ) - { - x |= DirectoryType; - qDebug() << "flags |= Dir"; - } - } - } - - return x; - } -}; - -QAbstractFileEngine * FSOverlayHandler::create( const QString & fileName ) const -{ - if ( fileName.startsWith( ":" ) ) - return 0; - else - return new FSOverlayEngine( fileName ); -} - - - -FSArchiveHandler * FSArchiveHandler::openArchive( const QString & fn ) -{ - if ( BSA::canOpen( fn ) ) - { - BSA * bsa = new BSA( fn ); - if ( bsa->open() ) - return new FSArchiveHandler( bsa ); - qDebug() << "fsengine error:" << fn << ":" << bsa->statusText(); - delete bsa; - } - return 0; -} - - -class FSArchiveEngine : public QAbstractFileEngine -{ - FSArchiveFile * archive; - - QString orgFilePath; - QString relFilePath; - - QIODevice::OpenMode fileMode; - QByteArray fileData; - qint64 filePos; - -public: - FSArchiveEngine( FSArchiveFile * a, const QString & org, const QString & rel ) : QAbstractFileEngine() - { - archive = a; - archive->ref.ref(); - - fileMode = QIODevice::NotOpen; - filePos = 0; - - orgFilePath = org; - relFilePath = rel; - - qDebug() << "archive engine" << relFilePath; - } - - ~FSArchiveEngine() - { - if ( ! archive->ref.deref() ) - delete archive; - } - - FileFlags fileFlags( FileFlags type = FileInfoAll ) const - { - FileFlags flags( 0 ); - - if ( ( type.testFlag( FileType ) || type.testFlag( ExistsFlag ) ) && archive->hasFile( relFilePath ) ) - { - flags |= FileType; - flags |= ReadOtherPerm; - flags |= ExistsFlag; - } - if ( ( type.testFlag( DirectoryType ) || type.testFlag( ExistsFlag ) ) && archive->hasFolder( relFilePath ) ) - { - flags |= DirectoryType; - flags |= ReadOtherPerm; - flags |= ExeOtherPerm; - flags |= ExistsFlag; - } - - return flags; - } - - QStringList entryList( QDir::Filters filters, const QStringList & nameFilters ) const - { - QStringList list; - - if ( filters.testFlag( QDir::Dirs ) || filters.testFlag( QDir::AllDirs ) ) - { - // TODO: filter dir names - list = archive->entryList( relFilePath, QDir::Dirs ); - } - - if ( filters.testFlag( QDir::Files ) ) - { - if ( nameFilters.isEmpty() ) - { - list += archive->entryList( relFilePath, QDir::Files ); - } - else - { - QList wildcards; - foreach ( QString pattern, nameFilters ) - wildcards << QRegExp( pattern, Qt::CaseInsensitive, QRegExp::Wildcard ); - - foreach ( QString fn, archive->entryList( relFilePath, QDir::Files ) ) - { - foreach ( QRegExp e, wildcards ) - { - if ( e.exactMatch( fn ) ) - { - list << fn; - break; - } - } - } - } - } - - return list; - } - - void setFileName( const QString & fn ) - { - qDebug() << "bsa engine set file name" << fn; - } - - QString fileName( FileName type ) const - { - switch ( type ) - { - case BaseName: - { - QString bn = orgFilePath; - int x = bn.lastIndexOf( "/" ); - if ( x >= 0 ) - bn.remove( 0, x + 1 ); - qDebug() << "name" << bn; - return bn; - } break; - case PathName: - { - QString pn = orgFilePath; - int x = pn.lastIndexOf( "/" ); - if ( x >= 0 ) - pn = pn.left( x ); - qDebug() << "path" << pn; - return pn; - } break; - case AbsolutePathName: - { - QString pn = archive->path(); - if ( ! relFilePath.isEmpty() ) - pn += "/" + relFilePath; - int x = pn.lastIndexOf( "/" ); - if ( x >= 0 ) - pn = pn.left( x ); - qDebug() << "abspath" << pn; - return pn; - } break; - default: - break; - } - return orgFilePath; - } - - bool isRelativePath() const - { - // todo - return false; - } - - bool open( QIODevice::OpenMode mode ) - { - if ( fileMode != QIODevice::NotOpen || mode != QIODevice::ReadOnly ) - return false; - - if ( archive->fileContents( relFilePath, fileData ) ) - { - fileMode = mode; - filePos = 0; - return true; - } - else - return false; - } - - bool close() - { - if ( fileMode != QIODevice::NotOpen ) - { - fileMode = QIODevice::NotOpen; - filePos = 0; - fileData.clear(); - return true; - } - else - return false; - } - - qint64 size() const - { - if ( fileMode == QIODevice::NotOpen ) - return archive->fileSize( relFilePath ); - else - return fileData.size(); - } - - bool seek( qint64 ofs ) - { - if ( fileMode != QIODevice::NotOpen && ofs >= 0 && ofs < fileData.size() ) - { - filePos = ofs; - return true; - } - return false; - } - - qint64 pos() const - { - return filePos; - } - - qint64 readLine( char * data, qint64 maxlen ) - { - qint64 numread = 0; - while ( numread < maxlen && filePos < fileData.size() ) - { - *data = fileData.at( filePos++ ); - numread++; - if ( *data == '\n' ) - break; - else - data++; - } - return numread; - } - - qint64 read( char * data, qint64 maxlen ) - { - qint64 numread = fileData.size() - filePos; - if ( numread > maxlen ) - numread = maxlen; - memcpy( data, ( fileData.data() + filePos ), numread ); - filePos += numread; - return numread; - } - - qint64 write( const char * data, qint64 len ) - { - return -1; - } - - int handle() const - { - return 0; - } - - bool flush() - { - return true; - } - - uint ownerId( FileOwner type ) const - { - return archive->ownerId( relFilePath, type ); - } - - QString owner( FileOwner type ) const - { - return archive->owner( relFilePath, type ); - } - - QDateTime fileTime( FileTime type ) const - { - return archive->fileTime( relFilePath, type ); - } - - bool copy( const QString & newFile ) - { - return false; - } - - bool isSequential() const - { - return false; - } - - bool caseSensitive() const - { - return false; - } - - bool setPermissions( uint ) { return false; } - bool setSize( qint64 ) { return false; } - bool mkdir( const QString &, bool ) const { return false; } - bool rmdir( const QString &, bool ) const { return false; } - bool rename( const QString & ) { return false; } - bool remove() { return false; } - bool link( const QString & ) { return false; } -}; - - -FSArchiveHandler::FSArchiveHandler( FSArchiveFile * a ) - : QAbstractFileEngineHandler() -{ - archive = a; - archive->ref.ref(); - - QMutexLocker lock( & overlayMutex ); - - overlayDirs[ archive->base() ].append( archive->name() ); -} - -FSArchiveHandler::~FSArchiveHandler() -{ - QMutexLocker lock( & overlayMutex ); - - overlayDirs[ archive->base() ].removeAll( archive->name() ); - - if ( ! archive->ref.deref() ) - delete archive; -} - -QAbstractFileEngine * FSArchiveHandler::create( const QString & filename ) const -{ - QString fn = filename.toLower(); - fn.replace( "\\", "/" ); - return archive->stripBasePath( fn ) ? new FSArchiveEngine( archive, filename, fn ) : 0; -} - - - -#ifdef BSA_TEST - -#include -#include -#include -#include -#include - -int main( int argc, char * argv[] ) -{ - QApplication app( argc, argv ); - - QString fn = "f:\\data\\dlcShiveringisles - meshes.bsa"; //"g:\\nif\\mw\\data files\\bsa.bsa"; //"f:\\data\\Oblivion - Misc.bsa"; - if ( argc > 1 ) - fn = argv[ argc - 1 ]; - - FSOverlayHandler overlayHandler; - - if ( FSArchiveHandler::openArchive( fn ) ) - { - qDebug() << fn << ":status:" << QString( "open" ); - - QDirModel mdl; - mdl.setLazyChildCount( true ); - mdl.setSorting( QDir::DirsFirst | QDir::Name ); - - QTreeView view; - view.resize( 600, 600 ); - view.move( 600, 100 ); - view.setModel( &mdl ); - view.show(); - - QLineEdit line; - line.setCompleter( new QCompleter( new QDirModel( QStringList() << "*.nif", QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, QDir::DirsFirst, & line ), & line ) ); - line.show(); - - return app.exec(); - } - - return 0; -} - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + + +#include "fsengine.h" +#include "bsa.h" + + +#include +#include +#include +#include +#include +#include +#include + + +static QMap overlayDirs; +static QMutex overlayMutex; + + +class FSOverlayEngine : public QFSFileEngine +{ +public: + FSOverlayEngine( const QString & filename ) + : QFSFileEngine( filename ) + {} + + QStringList entryList( QDir::Filters filters, const QStringList & nameFilters ) const + { + QStringList entries = QFSFileEngine::entryList( filters, nameFilters ); + + if ( filters.testFlag( QDir::Dirs ) || filters.testFlag( QDir::AllDirs ) ) + { + QMutexLocker lock( & overlayMutex ); + + QString dirname = fileName( AbsoluteName ); + if ( ! caseSensitive() ) + dirname = dirname.toLower(); + + if ( overlayDirs.contains( dirname ) ) + { + QStringList list = overlayDirs.value( dirname ); + qDebug() << dirname << "overlay" << list; + + foreach ( QString x, list ) + { + if ( ! entries.contains( x ) ) + { + entries += x; + } + } + } + } + + return entries; + } + + FileFlags fileFlags( FileFlags type = FileInfoAll ) const + { + FileFlags x = QFSFileEngine::fileFlags( type ); + + if ( type.testFlag( DirectoryType ) ) + { + QMutexLocker lock( & overlayMutex ); + + QString base = fileName( AbsolutePathName ); + if ( ! caseSensitive() ) + base = base.toLower(); + + //qDebug() << "base" << base; + + if ( overlayDirs.contains( base ) ) + { + QString name = fileName( BaseName ); + if ( ! caseSensitive() ) + name = name.toLower(); + + //qDebug() << "name" << name; + + if ( overlayDirs[ base ].contains( name ) ) + { + x |= DirectoryType; + qDebug() << "flags |= Dir"; + } + } + } + + return x; + } +}; + +QAbstractFileEngine * FSOverlayHandler::create( const QString & fileName ) const +{ + if ( fileName.startsWith( ":" ) ) + return 0; + else + return new FSOverlayEngine( fileName ); +} + + + +FSArchiveHandler * FSArchiveHandler::openArchive( const QString & fn ) +{ + if ( BSA::canOpen( fn ) ) + { + BSA * bsa = new BSA( fn ); + if ( bsa->open() ) + return new FSArchiveHandler( bsa ); + qDebug() << "fsengine error:" << fn << ":" << bsa->statusText(); + delete bsa; + } + return 0; +} + + +class FSArchiveEngine : public QAbstractFileEngine +{ + FSArchiveFile * archive; + + QString orgFilePath; + QString relFilePath; + + QIODevice::OpenMode fileMode; + QByteArray fileData; + qint64 filePos; + +public: + FSArchiveEngine( FSArchiveFile * a, const QString & org, const QString & rel ) : QAbstractFileEngine() + { + archive = a; + archive->ref.ref(); + + fileMode = QIODevice::NotOpen; + filePos = 0; + + orgFilePath = org; + relFilePath = rel; + + qDebug() << "archive engine" << relFilePath; + } + + ~FSArchiveEngine() + { + if ( ! archive->ref.deref() ) + delete archive; + } + + FileFlags fileFlags( FileFlags type = FileInfoAll ) const + { + FileFlags flags( 0 ); + + if ( ( type.testFlag( FileType ) || type.testFlag( ExistsFlag ) ) && archive->hasFile( relFilePath ) ) + { + flags |= FileType; + flags |= ReadOtherPerm; + flags |= ExistsFlag; + } + if ( ( type.testFlag( DirectoryType ) || type.testFlag( ExistsFlag ) ) && archive->hasFolder( relFilePath ) ) + { + flags |= DirectoryType; + flags |= ReadOtherPerm; + flags |= ExeOtherPerm; + flags |= ExistsFlag; + } + + return flags; + } + + QStringList entryList( QDir::Filters filters, const QStringList & nameFilters ) const + { + QStringList list; + + if ( filters.testFlag( QDir::Dirs ) || filters.testFlag( QDir::AllDirs ) ) + { + // TODO: filter dir names + list = archive->entryList( relFilePath, QDir::Dirs ); + } + + if ( filters.testFlag( QDir::Files ) ) + { + if ( nameFilters.isEmpty() ) + { + list += archive->entryList( relFilePath, QDir::Files ); + } + else + { + QList wildcards; + foreach ( QString pattern, nameFilters ) + wildcards << QRegExp( pattern, Qt::CaseInsensitive, QRegExp::Wildcard ); + + foreach ( QString fn, archive->entryList( relFilePath, QDir::Files ) ) + { + foreach ( QRegExp e, wildcards ) + { + if ( e.exactMatch( fn ) ) + { + list << fn; + break; + } + } + } + } + } + + return list; + } + + void setFileName( const QString & fn ) + { + qDebug() << "bsa engine set file name" << fn; + } + + QString fileName( FileName type ) const + { + switch ( type ) + { + case BaseName: + { + QString bn = orgFilePath; + int x = bn.lastIndexOf( "/" ); + if ( x >= 0 ) + bn.remove( 0, x + 1 ); + qDebug() << "name" << bn; + return bn; + } break; + case PathName: + { + QString pn = orgFilePath; + int x = pn.lastIndexOf( "/" ); + if ( x >= 0 ) + pn = pn.left( x ); + qDebug() << "path" << pn; + return pn; + } break; + case AbsolutePathName: + { + QString pn = archive->path(); + if ( ! relFilePath.isEmpty() ) + pn += "/" + relFilePath; + int x = pn.lastIndexOf( "/" ); + if ( x >= 0 ) + pn = pn.left( x ); + qDebug() << "abspath" << pn; + return pn; + } break; + default: + break; + } + return orgFilePath; + } + + bool isRelativePath() const + { + // todo + return false; + } + + bool open( QIODevice::OpenMode mode ) + { + if ( fileMode != QIODevice::NotOpen || mode != QIODevice::ReadOnly ) + return false; + + if ( archive->fileContents( relFilePath, fileData ) ) + { + fileMode = mode; + filePos = 0; + return true; + } + else + return false; + } + + bool close() + { + if ( fileMode != QIODevice::NotOpen ) + { + fileMode = QIODevice::NotOpen; + filePos = 0; + fileData.clear(); + return true; + } + else + return false; + } + + qint64 size() const + { + if ( fileMode == QIODevice::NotOpen ) + return archive->fileSize( relFilePath ); + else + return fileData.size(); + } + + bool seek( qint64 ofs ) + { + if ( fileMode != QIODevice::NotOpen && ofs >= 0 && ofs < fileData.size() ) + { + filePos = ofs; + return true; + } + return false; + } + + qint64 pos() const + { + return filePos; + } + + qint64 readLine( char * data, qint64 maxlen ) + { + qint64 numread = 0; + while ( numread < maxlen && filePos < fileData.size() ) + { + *data = fileData.at( filePos++ ); + numread++; + if ( *data == '\n' ) + break; + else + data++; + } + return numread; + } + + qint64 read( char * data, qint64 maxlen ) + { + qint64 numread = fileData.size() - filePos; + if ( numread > maxlen ) + numread = maxlen; + memcpy( data, ( fileData.data() + filePos ), numread ); + filePos += numread; + return numread; + } + + qint64 write( const char * data, qint64 len ) + { + return -1; + } + + int handle() const + { + return 0; + } + + bool flush() + { + return true; + } + + uint ownerId( FileOwner type ) const + { + return archive->ownerId( relFilePath, type ); + } + + QString owner( FileOwner type ) const + { + return archive->owner( relFilePath, type ); + } + + QDateTime fileTime( FileTime type ) const + { + return archive->fileTime( relFilePath, type ); + } + + bool copy( const QString & newFile ) + { + return false; + } + + bool isSequential() const + { + return false; + } + + bool caseSensitive() const + { + return false; + } + + bool setPermissions( uint ) { return false; } + bool setSize( qint64 ) { return false; } + bool mkdir( const QString &, bool ) const { return false; } + bool rmdir( const QString &, bool ) const { return false; } + bool rename( const QString & ) { return false; } + bool remove() { return false; } + bool link( const QString & ) { return false; } +}; + + +FSArchiveHandler::FSArchiveHandler( FSArchiveFile * a ) + : QAbstractFileEngineHandler() +{ + archive = a; + archive->ref.ref(); + + QMutexLocker lock( & overlayMutex ); + + overlayDirs[ archive->base() ].append( archive->name() ); +} + +FSArchiveHandler::~FSArchiveHandler() +{ + QMutexLocker lock( & overlayMutex ); + + overlayDirs[ archive->base() ].removeAll( archive->name() ); + + if ( ! archive->ref.deref() ) + delete archive; +} + +QAbstractFileEngine * FSArchiveHandler::create( const QString & filename ) const +{ + QString fn = filename.toLower(); + fn.replace( "\\", "/" ); + return archive->stripBasePath( fn ) ? new FSArchiveEngine( archive, filename, fn ) : 0; +} + + + +#ifdef BSA_TEST + +#include +#include +#include +#include +#include + +int main( int argc, char * argv[] ) +{ + QApplication app( argc, argv ); + + QString fn = "f:\\data\\dlcShiveringisles - meshes.bsa"; //"g:\\nif\\mw\\data files\\bsa.bsa"; //"f:\\data\\Oblivion - Misc.bsa"; + if ( argc > 1 ) + fn = argv[ argc - 1 ]; + + FSOverlayHandler overlayHandler; + + if ( FSArchiveHandler::openArchive( fn ) ) + { + qDebug() << fn << ":status:" << QString( "open" ); + + QDirModel mdl; + mdl.setLazyChildCount( true ); + mdl.setSorting( QDir::DirsFirst | QDir::Name ); + + QTreeView view; + view.resize( 600, 600 ); + view.move( 600, 100 ); + view.setModel( &mdl ); + view.show(); + + QLineEdit line; + line.setCompleter( new QCompleter( new QDirModel( QStringList() << "*.nif", QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, QDir::DirsFirst, & line ), & line ) ); + line.show(); + + return app.exec(); + } + + return 0; +} + +#endif diff --git a/fsengine/fsengine.h b/fsengine/fsengine.h index 90107ba12..7fc40a7a5 100644 --- a/fsengine/fsengine.h +++ b/fsengine/fsengine.h @@ -1,101 +1,101 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - - -#ifndef ARCHIVEENGINE_H -#define ARCHIVEENGINE_H - - -#include -#include -#include - - -class FSOverlayHandler : public QAbstractFileEngineHandler -{ -public: - QAbstractFileEngine * create( const QString & fileName ) const; -}; - - -class FSArchiveHandler : public QAbstractFileEngineHandler -{ -public: - static FSArchiveHandler * openArchive( const QString & ); - -public: - FSArchiveHandler( class FSArchiveFile * a ); - ~FSArchiveHandler(); - - QAbstractFileEngine * create( const QString & filename ) const; - -protected: - class FSArchiveFile * archive; -}; - - -class FSArchiveFile -{ -public: - FSArchiveFile() : ref( 0 ) {} - virtual ~FSArchiveFile() {} - - virtual bool open() = 0; - virtual void close() = 0; - - virtual QString base() const = 0; - virtual QString name() const = 0; - virtual QString path() const = 0; - - virtual bool stripBasePath( QString & ) const = 0; - - virtual bool hasFolder( const QString & ) const = 0; - virtual QStringList entryList( const QString &, QDir::Filters ) const = 0; - - virtual bool hasFile( const QString & ) const = 0; - virtual qint64 fileSize( const QString & ) const = 0; - virtual bool fileContents( const QString &, QByteArray & ) = 0; - - virtual uint ownerId( const QString &, QAbstractFileEngine::FileOwner type ) const = 0; - virtual QString owner( const QString &, QAbstractFileEngine::FileOwner type ) const = 0; - virtual QDateTime fileTime( const QString &, QAbstractFileEngine::FileTime type ) const = 0; - -protected: - QAtomic ref; - - friend class FSArchiveHandler; - friend class FSArchiveEngine; -}; - - - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + + +#ifndef ARCHIVEENGINE_H +#define ARCHIVEENGINE_H + + +#include +#include +#include + + +class FSOverlayHandler : public QAbstractFileEngineHandler +{ +public: + QAbstractFileEngine * create( const QString & fileName ) const; +}; + + +class FSArchiveHandler : public QAbstractFileEngineHandler +{ +public: + static FSArchiveHandler * openArchive( const QString & ); + +public: + FSArchiveHandler( class FSArchiveFile * a ); + ~FSArchiveHandler(); + + QAbstractFileEngine * create( const QString & filename ) const; + +protected: + class FSArchiveFile * archive; +}; + + +class FSArchiveFile +{ +public: + FSArchiveFile() : ref( 0 ) {} + virtual ~FSArchiveFile() {} + + virtual bool open() = 0; + virtual void close() = 0; + + virtual QString base() const = 0; + virtual QString name() const = 0; + virtual QString path() const = 0; + + virtual bool stripBasePath( QString & ) const = 0; + + virtual bool hasFolder( const QString & ) const = 0; + virtual QStringList entryList( const QString &, QDir::Filters ) const = 0; + + virtual bool hasFile( const QString & ) const = 0; + virtual qint64 fileSize( const QString & ) const = 0; + virtual bool fileContents( const QString &, QByteArray & ) = 0; + + virtual uint ownerId( const QString &, QAbstractFileEngine::FileOwner type ) const = 0; + virtual QString owner( const QString &, QAbstractFileEngine::FileOwner type ) const = 0; + virtual QDateTime fileTime( const QString &, QAbstractFileEngine::FileTime type ) const = 0; + +protected: + QAtomic ref; + + friend class FSArchiveHandler; + friend class FSArchiveEngine; +}; + + + +#endif diff --git a/fsengine/fsmanager.cpp b/fsengine/fsmanager.cpp index 21e6bddf9..5a9ede49b 100644 --- a/fsengine/fsmanager.cpp +++ b/fsengine/fsmanager.cpp @@ -1,214 +1,214 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - - -#include "fsmanager.h" -#include "fsengine.h" - - -#include -#include -#include -#include -#include -#include -#include -#include - - -FSManager::FSManager( QObject * parent ) - : QObject( parent ), automatic( false ) -{ - overlay = new FSOverlayHandler; - - QSettings cfg; - cfg.beginGroup( "fsengine" ); - - QStringList list = cfg.value( "archives", QStringList() ).toStringList(); - - if ( list.size() == 1 && list.first() == "AUTO" ) - { - automatic = true; - list = autodetectArchives(); - } - - foreach ( QString an, list ) - { - if ( FSArchiveHandler * a = FSArchiveHandler::openArchive( an ) ) - archives.insert( an, a ); - } -} - -FSManager::~FSManager() -{ - qDeleteAll( archives ); - delete overlay; -} - -QStringList FSManager::autodetectArchives() -{ - QStringList list; - -#ifdef Q_OS_WIN32 - { - QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Oblivion", QSettings::NativeFormat ); - QString dataPath = reg.value( "Installed Path" ).toString(); - if ( ! dataPath.isEmpty() ) - { - if ( ! dataPath.endsWith( '/' ) && ! dataPath.endsWith( '\\' ) ) - dataPath += "/"; - dataPath += "Data"; - - QFSFileEngine fs( dataPath ); - foreach ( QString fn, fs.entryList( QDir::Files, QStringList() << "*.bsa" ) ) - { - list << dataPath + "/" + fn; - } - } - } - - { - QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Morrowind", QSettings::NativeFormat ); - QString dataPath = reg.value( "Installed Path" ).toString(); - if ( ! dataPath.isEmpty() ) - { - if ( ! dataPath.endsWith( '/' ) && ! dataPath.endsWith( '\\' ) ) - dataPath += "/"; - dataPath += "Data Files"; - - QFSFileEngine fs( dataPath ); - foreach ( QString fn, fs.entryList( QDir::Files, QStringList() << "*.bsa" ) ) - { - list << dataPath + "/" + fn; - } - } - } -#endif - - return list; -} - -void FSManager::selectArchives() -{ - FSSelector select( this ); - select.exec(); -} - - -FSSelector::FSSelector( FSManager * m ) - : QDialog(), manager( m ) -{ - model = new QStringListModel( this ); - model->setStringList( manager->archives.keys() ); - - view = new QListView( this ); - view->setModel( model ); - view->setEditTriggers( QListView::NoEditTriggers ); - - chkAuto = new QCheckBox( this ); - chkAuto->setText( "Automatic Selection" ); - chkAuto->setChecked( manager->automatic ); - connect( chkAuto, SIGNAL( toggled( bool ) ), this, SLOT( sltAuto( bool ) ) ); - - btAdd = new QPushButton( "Add", this ); - btAdd->setDisabled( manager->automatic ); - connect( btAdd, SIGNAL( clicked() ), this, SLOT( sltAdd() ) ); - - btDel = new QPushButton( "Remove", this ); - btDel->setDisabled( manager->automatic ); - connect( btDel, SIGNAL( clicked() ), this, SLOT( sltDel() ) ); - - QGridLayout * grid = new QGridLayout( this ); - grid->addWidget( chkAuto, 0, 0, 1, 2 ); - grid->addWidget( view, 1, 0, 1, 2 ); - grid->addWidget( btAdd, 2, 0, 1, 1 ); - grid->addWidget( btDel, 2, 1, 1, 1 ); -} - -FSSelector::~FSSelector() -{ - QSettings cfg; - cfg.beginGroup( "fsengine" ); - QStringList list( manager->automatic ? QStringList() << "AUTO" : manager->archives.keys() ); - cfg.setValue( "archives", list ); -} - -void FSSelector::sltAuto( bool x ) -{ - if ( x ) - { - qDeleteAll( manager->archives ); - manager->archives.clear(); - - foreach ( QString an, manager->autodetectArchives() ) - { - if ( FSArchiveHandler * a = FSArchiveHandler::openArchive( an ) ) - { - manager->archives.insert( an, a ); - } - } - - model->setStringList( manager->archives.keys() ); - } - - manager->automatic = x; - - btAdd->setDisabled( x ); - btDel->setDisabled( x ); -} - -void FSSelector::sltAdd() -{ - QStringList list = QFileDialog::getOpenFileNames( this, "Select resource files to add", QString(), "*.bsa" ); - - foreach ( QString an, list ) - { - if ( ! manager->archives.contains( an ) ) - if ( FSArchiveHandler * a = FSArchiveHandler::openArchive( an ) ) - manager->archives.insert( an, a ); - } - - model->setStringList( manager->archives.keys() ); -} - -void FSSelector::sltDel() -{ - QString an = view->currentIndex().data( Qt::DisplayRole ).toString(); - - if ( FSArchiveHandler * a = manager->archives.take( an ) ) - { - delete a; - } - - model->setStringList( manager->archives.keys() ); -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + + +#include "fsmanager.h" +#include "fsengine.h" + + +#include +#include +#include +#include +#include +#include +#include +#include + + +FSManager::FSManager( QObject * parent ) + : QObject( parent ), automatic( false ) +{ + overlay = new FSOverlayHandler; + + QSettings cfg; + cfg.beginGroup( "fsengine" ); + + QStringList list = cfg.value( "archives", QStringList() ).toStringList(); + + if ( list.size() == 1 && list.first() == "AUTO" ) + { + automatic = true; + list = autodetectArchives(); + } + + foreach ( QString an, list ) + { + if ( FSArchiveHandler * a = FSArchiveHandler::openArchive( an ) ) + archives.insert( an, a ); + } +} + +FSManager::~FSManager() +{ + qDeleteAll( archives ); + delete overlay; +} + +QStringList FSManager::autodetectArchives() +{ + QStringList list; + +#ifdef Q_OS_WIN32 + { + QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Oblivion", QSettings::NativeFormat ); + QString dataPath = reg.value( "Installed Path" ).toString(); + if ( ! dataPath.isEmpty() ) + { + if ( ! dataPath.endsWith( '/' ) && ! dataPath.endsWith( '\\' ) ) + dataPath += "/"; + dataPath += "Data"; + + QFSFileEngine fs( dataPath ); + foreach ( QString fn, fs.entryList( QDir::Files, QStringList() << "*.bsa" ) ) + { + list << dataPath + "/" + fn; + } + } + } + + { + QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Morrowind", QSettings::NativeFormat ); + QString dataPath = reg.value( "Installed Path" ).toString(); + if ( ! dataPath.isEmpty() ) + { + if ( ! dataPath.endsWith( '/' ) && ! dataPath.endsWith( '\\' ) ) + dataPath += "/"; + dataPath += "Data Files"; + + QFSFileEngine fs( dataPath ); + foreach ( QString fn, fs.entryList( QDir::Files, QStringList() << "*.bsa" ) ) + { + list << dataPath + "/" + fn; + } + } + } +#endif + + return list; +} + +void FSManager::selectArchives() +{ + FSSelector select( this ); + select.exec(); +} + + +FSSelector::FSSelector( FSManager * m ) + : QDialog(), manager( m ) +{ + model = new QStringListModel( this ); + model->setStringList( manager->archives.keys() ); + + view = new QListView( this ); + view->setModel( model ); + view->setEditTriggers( QListView::NoEditTriggers ); + + chkAuto = new QCheckBox( this ); + chkAuto->setText( "Automatic Selection" ); + chkAuto->setChecked( manager->automatic ); + connect( chkAuto, SIGNAL( toggled( bool ) ), this, SLOT( sltAuto( bool ) ) ); + + btAdd = new QPushButton( "Add", this ); + btAdd->setDisabled( manager->automatic ); + connect( btAdd, SIGNAL( clicked() ), this, SLOT( sltAdd() ) ); + + btDel = new QPushButton( "Remove", this ); + btDel->setDisabled( manager->automatic ); + connect( btDel, SIGNAL( clicked() ), this, SLOT( sltDel() ) ); + + QGridLayout * grid = new QGridLayout( this ); + grid->addWidget( chkAuto, 0, 0, 1, 2 ); + grid->addWidget( view, 1, 0, 1, 2 ); + grid->addWidget( btAdd, 2, 0, 1, 1 ); + grid->addWidget( btDel, 2, 1, 1, 1 ); +} + +FSSelector::~FSSelector() +{ + QSettings cfg; + cfg.beginGroup( "fsengine" ); + QStringList list( manager->automatic ? QStringList() << "AUTO" : manager->archives.keys() ); + cfg.setValue( "archives", list ); +} + +void FSSelector::sltAuto( bool x ) +{ + if ( x ) + { + qDeleteAll( manager->archives ); + manager->archives.clear(); + + foreach ( QString an, manager->autodetectArchives() ) + { + if ( FSArchiveHandler * a = FSArchiveHandler::openArchive( an ) ) + { + manager->archives.insert( an, a ); + } + } + + model->setStringList( manager->archives.keys() ); + } + + manager->automatic = x; + + btAdd->setDisabled( x ); + btDel->setDisabled( x ); +} + +void FSSelector::sltAdd() +{ + QStringList list = QFileDialog::getOpenFileNames( this, "Select resource files to add", QString(), "*.bsa" ); + + foreach ( QString an, list ) + { + if ( ! manager->archives.contains( an ) ) + if ( FSArchiveHandler * a = FSArchiveHandler::openArchive( an ) ) + manager->archives.insert( an, a ); + } + + model->setStringList( manager->archives.keys() ); +} + +void FSSelector::sltDel() +{ + QString an = view->currentIndex().data( Qt::DisplayRole ).toString(); + + if ( FSArchiveHandler * a = manager->archives.take( an ) ) + { + delete a; + } + + model->setStringList( manager->archives.keys() ); +} + diff --git a/fsengine/fsmanager.h b/fsengine/fsmanager.h index 7c90fdaab..934f1d251 100644 --- a/fsengine/fsmanager.h +++ b/fsengine/fsmanager.h @@ -1,93 +1,93 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - - -#ifndef FSMANAGER_H -#define FSMANAGER_H - - -#include -#include -#include - - -class FSOverlayHandler; -class FSArchiveHandler; - - -class FSManager : public QObject -{ - Q_OBJECT -public: - FSManager( QObject * parent ); - ~FSManager(); - -public slots: - void selectArchives(); - -protected: - FSOverlayHandler * overlay; - QMap archives; - bool automatic; - - static QStringList autodetectArchives(); - - friend class FSSelector; -}; - - -class FSSelector : public QDialog -{ - Q_OBJECT -public: - FSSelector( FSManager * m ); - ~FSSelector(); - -protected slots: - void sltAuto( bool ); - void sltAdd(); - void sltDel(); - -protected: - FSManager * manager; - - class QStringListModel * model; - class QListView * view; - - class QCheckBox * chkAuto; - class QPushButton * btAdd; - class QPushButton * btDel; -}; - - -#endif - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + + +#ifndef FSMANAGER_H +#define FSMANAGER_H + + +#include +#include +#include + + +class FSOverlayHandler; +class FSArchiveHandler; + + +class FSManager : public QObject +{ + Q_OBJECT +public: + FSManager( QObject * parent ); + ~FSManager(); + +public slots: + void selectArchives(); + +protected: + FSOverlayHandler * overlay; + QMap archives; + bool automatic; + + static QStringList autodetectArchives(); + + friend class FSSelector; +}; + + +class FSSelector : public QDialog +{ + Q_OBJECT +public: + FSSelector( FSManager * m ); + ~FSSelector(); + +protected slots: + void sltAuto( bool ); + void sltAdd(); + void sltDel(); + +protected: + FSManager * manager; + + class QStringListModel * model; + class QListView * view; + + class QCheckBox * chkAuto; + class QPushButton * btAdd; + class QPushButton * btDel; +}; + + +#endif + diff --git a/gl/glcontrolable.h b/gl/glcontrolable.h index 063626e90..70519bb4d 100644 --- a/gl/glcontrolable.h +++ b/gl/glcontrolable.h @@ -1,75 +1,75 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLCONTROLABLE_H -#define GLCONTROLABLE_H - -#include "nifmodel.h" - -class Controller; - -class Scene; - -class Controllable : public QObject -{ -public: - Controllable( Scene * Scene, const QModelIndex & index ); - virtual ~Controllable(); - - QModelIndex index() const { return iBlock; } - virtual bool isValid() const { return iBlock.isValid(); } - - virtual void clear(); - virtual void update( const NifModel * nif, const QModelIndex & index ); - - virtual void transform(); - - virtual void timeBounds( float & start, float & stop ); - - void setSequence( const QString & seqname ); - Controller * findController( const QString & ctrltype, const QString & var1, const QString & var2 ); - -protected: - virtual void setController( const NifModel * nif, const QModelIndex & iController ) {} - - Scene * scene; - - QPersistentModelIndex iBlock; - - QList controllers; - - QString name; - - friend class ControllerManager; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLCONTROLABLE_H +#define GLCONTROLABLE_H + +#include "nifmodel.h" + +class Controller; + +class Scene; + +class Controllable : public QObject +{ +public: + Controllable( Scene * Scene, const QModelIndex & index ); + virtual ~Controllable(); + + QModelIndex index() const { return iBlock; } + virtual bool isValid() const { return iBlock.isValid(); } + + virtual void clear(); + virtual void update( const NifModel * nif, const QModelIndex & index ); + + virtual void transform(); + + virtual void timeBounds( float & start, float & stop ); + + void setSequence( const QString & seqname ); + Controller * findController( const QString & ctrltype, const QString & var1, const QString & var2 ); + +protected: + virtual void setController( const NifModel * nif, const QModelIndex & iController ) {} + + Scene * scene; + + QPersistentModelIndex iBlock; + + QList controllers; + + QString name; + + friend class ControllerManager; +}; + +#endif diff --git a/gl/glcontroller.cpp b/gl/glcontroller.cpp index ff4624cfb..44dfc79f2 100644 --- a/gl/glcontroller.cpp +++ b/gl/glcontroller.cpp @@ -1,677 +1,677 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "glcontroller.h" -#include "glscene.h" - -/* - * Controllable - */ - -Controllable::Controllable( Scene * s, const QModelIndex & i ) : scene( s ), iBlock( i ) -{ -} - -Controllable::~Controllable() -{ - qDeleteAll( controllers ); -} - -void Controllable::clear() -{ - name = QString(); - - qDeleteAll( controllers ); - controllers.clear(); -} - -Controller * Controllable::findController( const QString & ctrltype, const QString & var1, const QString & var2 ) -{ - Controller * ctrl = 0; - foreach ( Controller * c, controllers ) - { - if ( c->typeId() == ctrltype ) - { - if ( ctrl == 0 ) - { - ctrl = c; - } - else - { - ctrl = 0; - // TODO: eval var1 + var2 offset to determine which controller is targeted - break; - } - } - } - return ctrl; -} - - -void Controllable::update( const NifModel * nif, const QModelIndex & i ) -{ - if ( ! iBlock.isValid() ) - { - clear(); - return; - } - - bool x = false; - - foreach ( Controller * ctrl, controllers ) - { - ctrl->update( nif, i ); - if ( ctrl->index() == i ) - x = true; - } - - if ( iBlock == i || x ) - { - name = nif->get( iBlock, "Name" ); - // sync the list of attached controllers - QList rem( controllers ); - QModelIndex iCtrl = nif->getBlock( nif->getLink( iBlock, "Controller" ) ); - while ( iCtrl.isValid() && nif->inherits( iCtrl, "NiTimeController" ) ) - { - bool add = true; - foreach ( Controller * ctrl, controllers ) - { - if ( ctrl->index() == iCtrl ) - { - add = false; - rem.removeAll( ctrl ); - } - } - if ( add ) - { - setController( nif, iCtrl ); - } - iCtrl = nif->getBlock( nif->getLink( iCtrl, "Next Controller" ) ); - } - foreach ( Controller * ctrl, rem ) - { - controllers.removeAll( ctrl ); - delete ctrl; - } - } -} - -void Controllable::transform() -{ - if ( scene->animate ) - foreach ( Controller * controller, controllers ) - controller->update( scene->time ); -} - -void Controllable::timeBounds( float & tmin, float & tmax ) -{ - if ( controllers.isEmpty() ) - return; - - float mn = controllers.first()->start; - float mx = controllers.first()->stop; - foreach ( Controller * c, controllers ) - { - mn = qMin( mn, c->start ); - mx = qMax( mx, c->stop ); - } - tmin = qMin( tmin, mn ); - tmax = qMax( tmax, mx ); -} - -void Controllable::setSequence( const QString & seqname ) -{ - foreach ( Controller * ctrl, controllers ) - ctrl->setSequence( seqname ); -} - - -/* - * Controller - */ - -Controller::Controller( const QModelIndex & index ) : iBlock( index ) -{ -} - -QString Controller::typeId() const -{ - if ( iBlock.isValid() ) - return iBlock.data( Qt::DisplayRole ).toString(); - return QString(); -} - -void Controller::setSequence( const QString & seqname ) -{ -} - -void Controller::setInterpolator( const QModelIndex & index ) -{ - iInterpolator = index; - - const NifModel * nif = static_cast( index.model() ); - if ( nif ) - iData = nif->getBlock( nif->getLink( iInterpolator, "Data" ) ); -} - -bool Controller::update( const NifModel * nif, const QModelIndex & index ) -{ - if ( iBlock.isValid() && iBlock == index ) - { - start = nif->get( index, "Start Time" ); - stop = nif->get( index, "Stop Time" ); - phase = nif->get( index, "Phase" ); - frequency = nif->get( index, "Frequency" ); - - int flags = nif->get( index, "Flags" ); - active = flags & 0x08; - extrapolation = (Extrapolation) ( ( flags & 0x06 ) >> 1 ); - - QModelIndex idx = nif->getBlock( nif->getLink( iBlock, "Interpolator" ) ); - if ( idx.isValid() ) - { - setInterpolator( idx ); - } - else - { - idx = nif->getBlock( nif->getLink( iBlock, "Data" ) ); - if ( idx.isValid() ) - iData = idx; - } - } - - if ( iInterpolator.isValid() && iInterpolator == index ) - { - iData = nif->getBlock( nif->getLink( iInterpolator, "Data" ) ); - } - - return ( index.isValid() && index == iBlock || index == iInterpolator || index == iData ); -} - -float Controller::ctrlTime( float time ) const -{ - time = frequency * time + phase; - - if ( time >= start && time <= stop ) - return time; - - switch ( extrapolation ) - { - case Cyclic: - { - float delta = stop - start; - if ( delta <= 0 ) - return start; - - float x = ( time - start ) / delta; - float y = ( x - floor( x ) ) * delta; - - return start + y; - } - case Reverse: - { - float delta = stop - start; - if ( delta <= 0 ) - return start; - - float x = ( time - start ) / delta; - float y = ( x - floor( x ) ) * delta; - if ( ( ( (int) fabs( floor( x ) ) ) & 1 ) == 0 ) - return start + y; - else - return stop - y; - } - case Constant: - default: - if ( time < start ) - return start; - if ( time > stop ) - return stop; - return time; - } -} - -bool Controller::timeIndex( float time, const NifModel * nif, const QModelIndex & array, int & i, int & j, float & x ) -{ - int count; - if ( array.isValid() && ( count = nif->rowCount( array ) ) > 0 ) - { - if ( time <= nif->get( array.child( 0, 0 ), "Time" ) ) - { - i = j = 0; - x = 0.0; - return true; - } - if ( time >= nif->get( array.child( count - 1, 0 ), "Time" ) ) - { - i = j = count - 1; - x = 0.0; - return true; - } - - if ( i < 0 || i >= count ) - i = 0; - - float tI = nif->get( array.child( i, 0 ), "Time" ); - if ( time > tI ) - { - j = i + 1; - float tJ; - while ( time >= ( tJ = nif->get( array.child( j, 0 ), "Time" ) ) ) - { - i = j++; - tI = tJ; - } - x = ( time - tI ) / ( tJ - tI ); - return true; - } - else if ( time < tI ) - { - j = i - 1; - float tJ; - while ( time <= ( tJ = nif->get( array.child( j, 0 ), "Time" ) ) ) - { - i = j--; - tI = tJ; - } - x = ( time - tI ) / ( tJ - tI ); - return true; - } - else - { - j = i; - x = 0.0; - return true; - } - } - else - return false; -} - -template bool interpolate( T & value, const QModelIndex & array, float time, int & last ) -{ - const NifModel * nif = static_cast( array.model() ); - if ( nif && array.isValid() ) - { - QModelIndex frames = nif->getIndex( array, "Keys" ); - int next; - float x; - if ( Controller::timeIndex( time, nif, frames, last, next, x ) ) - { - T v1 = nif->get( frames.child( last, 0 ), "Value" ); - T v2 = nif->get( frames.child( next, 0 ), "Value" ); - - switch ( nif->get( array, "Interpolation" ) ) - { - /* - case 2: - { - float t1 = nif->get( frames.child( last, 0 ), "Forward" ); - float t2 = nif->get( frames.child( next, 0 ), "Backward" ); - - float x2 = x * x; - float x3 = x2 * x; - - //x(t) = (2t^3 - 3t^2 + 1)*P1 + (-2t^3 + 3t^2)*P4 + (t^3 - 2t^2 + t)*R1 + (t^3 - t^2)*R4 - value = ( 2 * x3 - 3 * x2 + 1 ) * v1 + ( - 2 * x3 + 3 * x2 ) * v2 + ( x3 - 2 * x2 + x ) * t1 + ( x3 - x2 ) * t2; - } return true; - */ - case 5: - if ( x < 0.5 ) - value = v1; - else - value = v2; - return true; - default: - value = v1 + ( v2 - v1 ) * x; - return true; - } - } - } - - return false; -} - -template <> bool Controller::interpolate( float & value, const QModelIndex & array, float time, int & last ) -{ - return ::interpolate( value, array, time, last ); -} - -template <> bool Controller::interpolate( Vector3 & value, const QModelIndex & array, float time, int & last ) -{ - return ::interpolate( value, array, time, last ); -} - -template <> bool Controller::interpolate( Color4 & value, const QModelIndex & array, float time, int & last ) -{ - return ::interpolate( value, array, time, last ); -} - -template <> bool Controller::interpolate( Color3 & value, const QModelIndex & array, float time, int & last ) -{ - return ::interpolate( value, array, time, last ); -} - -template <> bool Controller::interpolate( bool & value, const QModelIndex & array, float time, int & last ) -{ - int next; - float x; - const NifModel * nif = static_cast( array.model() ); - if ( nif && array.isValid() ) - { - QModelIndex frames = nif->getIndex( array, "Keys" ); - if ( timeIndex( time, nif, frames, last, next, x ) ) - { - value = nif->get( frames.child( last, 0 ), "Value" ); - return true; - } - } - return false; -} - -template <> bool Controller::interpolate( Matrix & value, const QModelIndex & array, float time, int & last ) -{ - int next; - float x; - const NifModel * nif = static_cast( array.model() ); - if ( nif && array.isValid() ) - { - switch ( nif->get( array, "Rotation Type" ) ) - { - case 4: - { - QModelIndex subkeys = nif->getIndex( array, "XYZ Rotations" ); - if ( subkeys.isValid() ) - { - float r[3]; - for ( int s = 0; s < 3 && s < nif->rowCount( subkeys ); s++ ) - { - r[s] = 0; - interpolate( r[s], subkeys.child( s, 0 ), time, last ); - } - value = Matrix::euler( 0, 0, r[2] ) * Matrix::euler( 0, r[1], 0 ) * Matrix::euler( r[0], 0, 0 ); - return true; - } - } break; - default: - { - QModelIndex frames = nif->getIndex( array, "Quaternion Keys" ); - if ( timeIndex( time, nif, frames, last, next, x ) ) - { - Quat v1 = nif->get( frames.child( last, 0 ), "Value" ); - Quat v2 = nif->get( frames.child( next, 0 ), "Value" ); - - float a = acos( Quat::dotproduct( v1, v2 ) ); - - if ( fabs( a ) >= 0.00005 ) - { - float i = 1.0 / sin( a ); - v1 = v1 * sin( ( 1.0 - x ) * a ) * i + v2 * sin( x * a ) * i; - } - value.fromQuat( v1 ); - return true; - } - } break; - } - } - return false; -} - - -/********************************************************************* -Simple b-spline curve algorithm - -Copyright 1994 by Keith Vertanen (vertankd@cda.mrs.umn.edu) - -Released to the public domain (your mileage may vary) - -Found at: Programmers Heaven (www.programmersheaven.com/zone3/cat415/6660.htm) -Modified by: Theo -- reformat and convert doubles to floats -- removed point structure in favor of arbitrary sized float array -**********************************************************************/ - -/*! Used to enable static arrays to be members of vectors */ -template -struct qarray { - qarray(const QModelIndex & array, uint off=0) : array_(array), off_(off) { - nif_ = static_cast( array_.model() ); - } - qarray(const qarray& other, uint off=0) : nif_(other.nif_), array_(other.array_), off_(other.off_+off) {} - - T operator[]( uint index ) const { - return nif_->get( array_.child(index + off_, 0) ); - } - const NifModel * nif_; - const QModelIndex & array_; - uint off_; -}; - -template -struct SplineTraits -{ - // Zero data - static T& Init(T& v) { v = T(); return v; } - - // Number of control points used - static int CountOf() { return (sizeof(T)/sizeof(float)); } - - // Compute point from short array and mult/bias - static T& Compute(T& v, qarray& c, float mult) { - float *vf = (float*)&v; // assume default data is a vector of floats. specialize if necessary. - for (int i=0; i struct SplineTraits -{ - static Quat& Init(Quat& v) { - v = Quat(); v[0] = 0.0f; return v; - } - static int CountOf() { return 4;} - static Quat& Compute(Quat& v, qarray& c, float mult) { - for (int i=0; in) u[j]=n-t+2; // if n-t=-2 then we're screwed, everything goes to 0 - } -} - -template -static void compute_point(int *u, int n, int t, float v, qarray& control, T& output, float mult, float bias) -{ - // initialize the variables that will hold our output - int l = SplineTraits::CountOf(); - SplineTraits::Init(output); - for (int k=0; k<=n; k++) { - qarray qa(control, k*l); - SplineTraits::Compute(output, qa, blend(k,t,u,v)); - } - SplineTraits::Adjust(output, mult, bias); -} - -template -bool bsplineinterpolate( T & value, int degree, float interval, uint nctrl, const QModelIndex & array, uint off, float mult, float bias ) -{ - if (off == USHRT_MAX) - return false; - - qarray subArray(array, off); - int t = degree+1; - int n = nctrl-1; - int l = SplineTraits::CountOf(); - if (interval >= float(nctrl-degree)) - { - SplineTraits::Init(value); - qarray sa(subArray, n*l); - SplineTraits::Compute(value, sa, 1.0f); - SplineTraits::Adjust(value, mult, bias); - } - else - { - int *u = new int[n+t+1]; - compute_intervals(u, n, t); - compute_point(u, n, t, interval, subArray, value, mult, bias); - delete [] u; - } - return true; -} - -Interpolator::Interpolator(Controller *owner) : parent(owner) {} - -bool Interpolator::update( const NifModel * nif, const QModelIndex & index ) -{ - return true; -} -QPersistentModelIndex Interpolator::GetControllerData() -{ - return parent->iData; -} - -TransformInterpolator::TransformInterpolator(Controller *owner) : Interpolator(owner), lTrans( 0 ), lRotate( 0 ), lScale( 0 ) -{} - -bool TransformInterpolator::update( const NifModel * nif, const QModelIndex & index ) -{ - if ( Interpolator::update( nif, index ) ) - { - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ), "NiTransformData" ); - iTranslations = nif->getIndex( iData, "Translations" ); - iRotations = nif->getIndex( iData, "Rotations" ); - if ( ! iRotations.isValid() ) iRotations = iData; - iScales = nif->getIndex( iData, "Scales" ); - return true; - } - return false; -} - -bool TransformInterpolator::updateTransform(Transform& tm, float time) -{ - Controller::interpolate( tm.rotation, iRotations, time, lRotate ); - Controller::interpolate( tm.translation, iTranslations, time, lTrans ); - Controller::interpolate( tm.scale, iScales, time, lScale ); - return true; -} - - -BSplineTransformInterpolator::BSplineTransformInterpolator(Controller *owner) : - TransformInterpolator(owner) , lTransOff( USHRT_MAX ), lRotateOff( USHRT_MAX ), lScaleOff( USHRT_MAX ) - , nCtrl(0), degree(3) -{ -} - -bool BSplineTransformInterpolator::update( const NifModel * nif, const QModelIndex & index ) -{ - if ( Interpolator::update( nif, index ) ) - { - start = nif->get( index, "Start Time"); - stop = nif->get( index, "Stop Time"); - - iSpline = nif->getBlock( nif->getLink( index, "Spline Data" ) ); - iBasis = nif->getBlock( nif->getLink( index, "Basis Data") ); - - if (iSpline.isValid()) - iControl = nif->getIndex( iSpline, "Short Control Points"); - if (iBasis.isValid()) - nCtrl = nif->get( iBasis, "Num Control Pt" ); - - lTrans = nif->getIndex( index, "Translation"); - lRotate = nif->getIndex( index, "Rotation"); - lScale = nif->getIndex( index, "Scale"); - - lTransOff = nif->get( index, "Translate Offset"); - lRotateOff = nif->get( index, "Rotate Offset"); - lScaleOff = nif->get( index, "Scale Offset"); - lTransMult = nif->get( index, "Translate Multiplier"); - lRotateMult = nif->get( index, "Rotation Multiplier"); - lScaleMult = nif->get( index, "Scale Multiplier"); - lTransBias = nif->get( index, "Translate Bias"); - lRotateBias = nif->get( index, "Rotation Bias"); - lScaleBias = nif->get( index, "Scale Bias"); - - return true; - } - return false; -} - -bool BSplineTransformInterpolator::updateTransform(Transform& transform, float time) -{ - float interval = ((time-start)/(stop-start)) * float(nCtrl-degree); - Quat q = transform.rotation.toQuat(); - if (::bsplineinterpolate( q, degree, interval, nCtrl, iControl, lRotateOff, lRotateMult, lRotateBias )) - transform.rotation.fromQuat(q); - ::bsplineinterpolate( transform.translation, degree, interval, nCtrl, iControl, lTransOff, lTransMult, lTransBias ); - ::bsplineinterpolate( transform.scale, degree, interval, nCtrl, iControl, lScaleOff, lScaleMult, lScaleBias ); - return true; -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "glcontroller.h" +#include "glscene.h" + +/* + * Controllable + */ + +Controllable::Controllable( Scene * s, const QModelIndex & i ) : scene( s ), iBlock( i ) +{ +} + +Controllable::~Controllable() +{ + qDeleteAll( controllers ); +} + +void Controllable::clear() +{ + name = QString(); + + qDeleteAll( controllers ); + controllers.clear(); +} + +Controller * Controllable::findController( const QString & ctrltype, const QString & var1, const QString & var2 ) +{ + Controller * ctrl = 0; + foreach ( Controller * c, controllers ) + { + if ( c->typeId() == ctrltype ) + { + if ( ctrl == 0 ) + { + ctrl = c; + } + else + { + ctrl = 0; + // TODO: eval var1 + var2 offset to determine which controller is targeted + break; + } + } + } + return ctrl; +} + + +void Controllable::update( const NifModel * nif, const QModelIndex & i ) +{ + if ( ! iBlock.isValid() ) + { + clear(); + return; + } + + bool x = false; + + foreach ( Controller * ctrl, controllers ) + { + ctrl->update( nif, i ); + if ( ctrl->index() == i ) + x = true; + } + + if ( iBlock == i || x ) + { + name = nif->get( iBlock, "Name" ); + // sync the list of attached controllers + QList rem( controllers ); + QModelIndex iCtrl = nif->getBlock( nif->getLink( iBlock, "Controller" ) ); + while ( iCtrl.isValid() && nif->inherits( iCtrl, "NiTimeController" ) ) + { + bool add = true; + foreach ( Controller * ctrl, controllers ) + { + if ( ctrl->index() == iCtrl ) + { + add = false; + rem.removeAll( ctrl ); + } + } + if ( add ) + { + setController( nif, iCtrl ); + } + iCtrl = nif->getBlock( nif->getLink( iCtrl, "Next Controller" ) ); + } + foreach ( Controller * ctrl, rem ) + { + controllers.removeAll( ctrl ); + delete ctrl; + } + } +} + +void Controllable::transform() +{ + if ( scene->animate ) + foreach ( Controller * controller, controllers ) + controller->update( scene->time ); +} + +void Controllable::timeBounds( float & tmin, float & tmax ) +{ + if ( controllers.isEmpty() ) + return; + + float mn = controllers.first()->start; + float mx = controllers.first()->stop; + foreach ( Controller * c, controllers ) + { + mn = qMin( mn, c->start ); + mx = qMax( mx, c->stop ); + } + tmin = qMin( tmin, mn ); + tmax = qMax( tmax, mx ); +} + +void Controllable::setSequence( const QString & seqname ) +{ + foreach ( Controller * ctrl, controllers ) + ctrl->setSequence( seqname ); +} + + +/* + * Controller + */ + +Controller::Controller( const QModelIndex & index ) : iBlock( index ) +{ +} + +QString Controller::typeId() const +{ + if ( iBlock.isValid() ) + return iBlock.data( Qt::DisplayRole ).toString(); + return QString(); +} + +void Controller::setSequence( const QString & seqname ) +{ +} + +void Controller::setInterpolator( const QModelIndex & index ) +{ + iInterpolator = index; + + const NifModel * nif = static_cast( index.model() ); + if ( nif ) + iData = nif->getBlock( nif->getLink( iInterpolator, "Data" ) ); +} + +bool Controller::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( iBlock.isValid() && iBlock == index ) + { + start = nif->get( index, "Start Time" ); + stop = nif->get( index, "Stop Time" ); + phase = nif->get( index, "Phase" ); + frequency = nif->get( index, "Frequency" ); + + int flags = nif->get( index, "Flags" ); + active = flags & 0x08; + extrapolation = (Extrapolation) ( ( flags & 0x06 ) >> 1 ); + + QModelIndex idx = nif->getBlock( nif->getLink( iBlock, "Interpolator" ) ); + if ( idx.isValid() ) + { + setInterpolator( idx ); + } + else + { + idx = nif->getBlock( nif->getLink( iBlock, "Data" ) ); + if ( idx.isValid() ) + iData = idx; + } + } + + if ( iInterpolator.isValid() && iInterpolator == index ) + { + iData = nif->getBlock( nif->getLink( iInterpolator, "Data" ) ); + } + + return ( index.isValid() && index == iBlock || index == iInterpolator || index == iData ); +} + +float Controller::ctrlTime( float time ) const +{ + time = frequency * time + phase; + + if ( time >= start && time <= stop ) + return time; + + switch ( extrapolation ) + { + case Cyclic: + { + float delta = stop - start; + if ( delta <= 0 ) + return start; + + float x = ( time - start ) / delta; + float y = ( x - floor( x ) ) * delta; + + return start + y; + } + case Reverse: + { + float delta = stop - start; + if ( delta <= 0 ) + return start; + + float x = ( time - start ) / delta; + float y = ( x - floor( x ) ) * delta; + if ( ( ( (int) fabs( floor( x ) ) ) & 1 ) == 0 ) + return start + y; + else + return stop - y; + } + case Constant: + default: + if ( time < start ) + return start; + if ( time > stop ) + return stop; + return time; + } +} + +bool Controller::timeIndex( float time, const NifModel * nif, const QModelIndex & array, int & i, int & j, float & x ) +{ + int count; + if ( array.isValid() && ( count = nif->rowCount( array ) ) > 0 ) + { + if ( time <= nif->get( array.child( 0, 0 ), "Time" ) ) + { + i = j = 0; + x = 0.0; + return true; + } + if ( time >= nif->get( array.child( count - 1, 0 ), "Time" ) ) + { + i = j = count - 1; + x = 0.0; + return true; + } + + if ( i < 0 || i >= count ) + i = 0; + + float tI = nif->get( array.child( i, 0 ), "Time" ); + if ( time > tI ) + { + j = i + 1; + float tJ; + while ( time >= ( tJ = nif->get( array.child( j, 0 ), "Time" ) ) ) + { + i = j++; + tI = tJ; + } + x = ( time - tI ) / ( tJ - tI ); + return true; + } + else if ( time < tI ) + { + j = i - 1; + float tJ; + while ( time <= ( tJ = nif->get( array.child( j, 0 ), "Time" ) ) ) + { + i = j--; + tI = tJ; + } + x = ( time - tI ) / ( tJ - tI ); + return true; + } + else + { + j = i; + x = 0.0; + return true; + } + } + else + return false; +} + +template bool interpolate( T & value, const QModelIndex & array, float time, int & last ) +{ + const NifModel * nif = static_cast( array.model() ); + if ( nif && array.isValid() ) + { + QModelIndex frames = nif->getIndex( array, "Keys" ); + int next; + float x; + if ( Controller::timeIndex( time, nif, frames, last, next, x ) ) + { + T v1 = nif->get( frames.child( last, 0 ), "Value" ); + T v2 = nif->get( frames.child( next, 0 ), "Value" ); + + switch ( nif->get( array, "Interpolation" ) ) + { + /* + case 2: + { + float t1 = nif->get( frames.child( last, 0 ), "Forward" ); + float t2 = nif->get( frames.child( next, 0 ), "Backward" ); + + float x2 = x * x; + float x3 = x2 * x; + + //x(t) = (2t^3 - 3t^2 + 1)*P1 + (-2t^3 + 3t^2)*P4 + (t^3 - 2t^2 + t)*R1 + (t^3 - t^2)*R4 + value = ( 2 * x3 - 3 * x2 + 1 ) * v1 + ( - 2 * x3 + 3 * x2 ) * v2 + ( x3 - 2 * x2 + x ) * t1 + ( x3 - x2 ) * t2; + } return true; + */ + case 5: + if ( x < 0.5 ) + value = v1; + else + value = v2; + return true; + default: + value = v1 + ( v2 - v1 ) * x; + return true; + } + } + } + + return false; +} + +template <> bool Controller::interpolate( float & value, const QModelIndex & array, float time, int & last ) +{ + return ::interpolate( value, array, time, last ); +} + +template <> bool Controller::interpolate( Vector3 & value, const QModelIndex & array, float time, int & last ) +{ + return ::interpolate( value, array, time, last ); +} + +template <> bool Controller::interpolate( Color4 & value, const QModelIndex & array, float time, int & last ) +{ + return ::interpolate( value, array, time, last ); +} + +template <> bool Controller::interpolate( Color3 & value, const QModelIndex & array, float time, int & last ) +{ + return ::interpolate( value, array, time, last ); +} + +template <> bool Controller::interpolate( bool & value, const QModelIndex & array, float time, int & last ) +{ + int next; + float x; + const NifModel * nif = static_cast( array.model() ); + if ( nif && array.isValid() ) + { + QModelIndex frames = nif->getIndex( array, "Keys" ); + if ( timeIndex( time, nif, frames, last, next, x ) ) + { + value = nif->get( frames.child( last, 0 ), "Value" ); + return true; + } + } + return false; +} + +template <> bool Controller::interpolate( Matrix & value, const QModelIndex & array, float time, int & last ) +{ + int next; + float x; + const NifModel * nif = static_cast( array.model() ); + if ( nif && array.isValid() ) + { + switch ( nif->get( array, "Rotation Type" ) ) + { + case 4: + { + QModelIndex subkeys = nif->getIndex( array, "XYZ Rotations" ); + if ( subkeys.isValid() ) + { + float r[3]; + for ( int s = 0; s < 3 && s < nif->rowCount( subkeys ); s++ ) + { + r[s] = 0; + interpolate( r[s], subkeys.child( s, 0 ), time, last ); + } + value = Matrix::euler( 0, 0, r[2] ) * Matrix::euler( 0, r[1], 0 ) * Matrix::euler( r[0], 0, 0 ); + return true; + } + } break; + default: + { + QModelIndex frames = nif->getIndex( array, "Quaternion Keys" ); + if ( timeIndex( time, nif, frames, last, next, x ) ) + { + Quat v1 = nif->get( frames.child( last, 0 ), "Value" ); + Quat v2 = nif->get( frames.child( next, 0 ), "Value" ); + + float a = acos( Quat::dotproduct( v1, v2 ) ); + + if ( fabs( a ) >= 0.00005 ) + { + float i = 1.0 / sin( a ); + v1 = v1 * sin( ( 1.0 - x ) * a ) * i + v2 * sin( x * a ) * i; + } + value.fromQuat( v1 ); + return true; + } + } break; + } + } + return false; +} + + +/********************************************************************* +Simple b-spline curve algorithm + +Copyright 1994 by Keith Vertanen (vertankd@cda.mrs.umn.edu) + +Released to the public domain (your mileage may vary) + +Found at: Programmers Heaven (www.programmersheaven.com/zone3/cat415/6660.htm) +Modified by: Theo +- reformat and convert doubles to floats +- removed point structure in favor of arbitrary sized float array +**********************************************************************/ + +/*! Used to enable static arrays to be members of vectors */ +template +struct qarray { + qarray(const QModelIndex & array, uint off=0) : array_(array), off_(off) { + nif_ = static_cast( array_.model() ); + } + qarray(const qarray& other, uint off=0) : nif_(other.nif_), array_(other.array_), off_(other.off_+off) {} + + T operator[]( uint index ) const { + return nif_->get( array_.child(index + off_, 0) ); + } + const NifModel * nif_; + const QModelIndex & array_; + uint off_; +}; + +template +struct SplineTraits +{ + // Zero data + static T& Init(T& v) { v = T(); return v; } + + // Number of control points used + static int CountOf() { return (sizeof(T)/sizeof(float)); } + + // Compute point from short array and mult/bias + static T& Compute(T& v, qarray& c, float mult) { + float *vf = (float*)&v; // assume default data is a vector of floats. specialize if necessary. + for (int i=0; i struct SplineTraits +{ + static Quat& Init(Quat& v) { + v = Quat(); v[0] = 0.0f; return v; + } + static int CountOf() { return 4;} + static Quat& Compute(Quat& v, qarray& c, float mult) { + for (int i=0; in) u[j]=n-t+2; // if n-t=-2 then we're screwed, everything goes to 0 + } +} + +template +static void compute_point(int *u, int n, int t, float v, qarray& control, T& output, float mult, float bias) +{ + // initialize the variables that will hold our output + int l = SplineTraits::CountOf(); + SplineTraits::Init(output); + for (int k=0; k<=n; k++) { + qarray qa(control, k*l); + SplineTraits::Compute(output, qa, blend(k,t,u,v)); + } + SplineTraits::Adjust(output, mult, bias); +} + +template +bool bsplineinterpolate( T & value, int degree, float interval, uint nctrl, const QModelIndex & array, uint off, float mult, float bias ) +{ + if (off == USHRT_MAX) + return false; + + qarray subArray(array, off); + int t = degree+1; + int n = nctrl-1; + int l = SplineTraits::CountOf(); + if (interval >= float(nctrl-degree)) + { + SplineTraits::Init(value); + qarray sa(subArray, n*l); + SplineTraits::Compute(value, sa, 1.0f); + SplineTraits::Adjust(value, mult, bias); + } + else + { + int *u = new int[n+t+1]; + compute_intervals(u, n, t); + compute_point(u, n, t, interval, subArray, value, mult, bias); + delete [] u; + } + return true; +} + +Interpolator::Interpolator(Controller *owner) : parent(owner) {} + +bool Interpolator::update( const NifModel * nif, const QModelIndex & index ) +{ + return true; +} +QPersistentModelIndex Interpolator::GetControllerData() +{ + return parent->iData; +} + +TransformInterpolator::TransformInterpolator(Controller *owner) : Interpolator(owner), lTrans( 0 ), lRotate( 0 ), lScale( 0 ) +{} + +bool TransformInterpolator::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Interpolator::update( nif, index ) ) + { + QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ), "NiTransformData" ); + iTranslations = nif->getIndex( iData, "Translations" ); + iRotations = nif->getIndex( iData, "Rotations" ); + if ( ! iRotations.isValid() ) iRotations = iData; + iScales = nif->getIndex( iData, "Scales" ); + return true; + } + return false; +} + +bool TransformInterpolator::updateTransform(Transform& tm, float time) +{ + Controller::interpolate( tm.rotation, iRotations, time, lRotate ); + Controller::interpolate( tm.translation, iTranslations, time, lTrans ); + Controller::interpolate( tm.scale, iScales, time, lScale ); + return true; +} + + +BSplineTransformInterpolator::BSplineTransformInterpolator(Controller *owner) : + TransformInterpolator(owner) , lTransOff( USHRT_MAX ), lRotateOff( USHRT_MAX ), lScaleOff( USHRT_MAX ) + , nCtrl(0), degree(3) +{ +} + +bool BSplineTransformInterpolator::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Interpolator::update( nif, index ) ) + { + start = nif->get( index, "Start Time"); + stop = nif->get( index, "Stop Time"); + + iSpline = nif->getBlock( nif->getLink( index, "Spline Data" ) ); + iBasis = nif->getBlock( nif->getLink( index, "Basis Data") ); + + if (iSpline.isValid()) + iControl = nif->getIndex( iSpline, "Short Control Points"); + if (iBasis.isValid()) + nCtrl = nif->get( iBasis, "Num Control Pt" ); + + lTrans = nif->getIndex( index, "Translation"); + lRotate = nif->getIndex( index, "Rotation"); + lScale = nif->getIndex( index, "Scale"); + + lTransOff = nif->get( index, "Translate Offset"); + lRotateOff = nif->get( index, "Rotate Offset"); + lScaleOff = nif->get( index, "Scale Offset"); + lTransMult = nif->get( index, "Translate Multiplier"); + lRotateMult = nif->get( index, "Rotation Multiplier"); + lScaleMult = nif->get( index, "Scale Multiplier"); + lTransBias = nif->get( index, "Translate Bias"); + lRotateBias = nif->get( index, "Rotation Bias"); + lScaleBias = nif->get( index, "Scale Bias"); + + return true; + } + return false; +} + +bool BSplineTransformInterpolator::updateTransform(Transform& transform, float time) +{ + float interval = ((time-start)/(stop-start)) * float(nCtrl-degree); + Quat q = transform.rotation.toQuat(); + if (::bsplineinterpolate( q, degree, interval, nCtrl, iControl, lRotateOff, lRotateMult, lRotateBias )) + transform.rotation.fromQuat(q); + ::bsplineinterpolate( transform.translation, degree, interval, nCtrl, iControl, lTransOff, lTransMult, lTransBias ); + ::bsplineinterpolate( transform.scale, degree, interval, nCtrl, iControl, lScaleOff, lScaleMult, lScaleBias ); + return true; +} diff --git a/gl/glcontroller.h b/gl/glcontroller.h index 7b24b3425..837e88a39 100644 --- a/gl/glcontroller.h +++ b/gl/glcontroller.h @@ -1,147 +1,147 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLCONTROLLER_H -#define GLCONTROLLER_H - -#include "nifmodel.h" - -#include - -class Scene; -class Node; -class Controller; -class Interpolator; - -class Controller -{ -public: - Controller( const QModelIndex & index ); - virtual ~Controller() {} - - float start; - float stop; - float phase; - float frequency; - - enum Extrapolation { - Cyclic = 0, Reverse = 1, Constant = 2 - } extrapolation; - - bool active; - - virtual void setSequence( const QString & seqname ); - - virtual void update( float time ) = 0; - - virtual bool update( const NifModel * nif, const QModelIndex & index ); - - virtual void setInterpolator( const QModelIndex & iInterpolator ); - - QModelIndex index() const { return iBlock; } - - virtual QString typeId() const; - - float ctrlTime( float time ) const; - - template static bool interpolate( T & value, const QModelIndex & data, const QString & arrayid, float time, int & lastindex ); - template static bool interpolate( T & value, const QModelIndex & array, float time, int & lastIndex ); - static bool timeIndex( float time, const NifModel * nif, const QModelIndex & array, int & i, int & j, float & x ); - -protected: - friend class Interpolator; - - QPersistentModelIndex iBlock; - QPersistentModelIndex iInterpolator; - QPersistentModelIndex iData; -}; - -template bool Controller::interpolate( T & value, const QModelIndex & data, const QString & arrayid, float time, int & lastindex ) -{ - const NifModel * nif = static_cast( data.model() ); - if ( nif && data.isValid() ) - { - QModelIndex array = nif->getIndex( data, arrayid ); - return interpolate( value, array, time, lastindex ); - } - else - return false; -} - -class Interpolator : public QObject -{ -public: - Interpolator(Controller *owner); - - virtual bool update( const NifModel * nif, const QModelIndex & index ); - -protected: - QPersistentModelIndex GetControllerData(); - Controller *parent; -}; - -class TransformInterpolator : public Interpolator -{ -public: - TransformInterpolator(Controller *owner); - - virtual bool update( const NifModel * nif, const QModelIndex & index ); - virtual bool updateTransform(Transform& tm, float time); - -protected: - QPersistentModelIndex iTranslations, iRotations, iScales; - int lTrans, lRotate, lScale; -}; - -class BSplineTransformInterpolator : public TransformInterpolator -{ -public: - BSplineTransformInterpolator( Controller *owner ); - - virtual bool update( const NifModel * nif, const QModelIndex & index ); - virtual bool updateTransform(Transform& tm, float time); - -protected: - float start, stop; - QPersistentModelIndex iControl, iSpline, iBasis; - QPersistentModelIndex lTrans, lRotate, lScale; - uint lTransOff, lRotateOff, lScaleOff; - float lTransMult, lRotateMult, lScaleMult; - float lTransBias, lRotateBias, lScaleBias; - uint nCtrl; - int degree; -}; - - -#endif - - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLCONTROLLER_H +#define GLCONTROLLER_H + +#include "nifmodel.h" + +#include + +class Scene; +class Node; +class Controller; +class Interpolator; + +class Controller +{ +public: + Controller( const QModelIndex & index ); + virtual ~Controller() {} + + float start; + float stop; + float phase; + float frequency; + + enum Extrapolation { + Cyclic = 0, Reverse = 1, Constant = 2 + } extrapolation; + + bool active; + + virtual void setSequence( const QString & seqname ); + + virtual void update( float time ) = 0; + + virtual bool update( const NifModel * nif, const QModelIndex & index ); + + virtual void setInterpolator( const QModelIndex & iInterpolator ); + + QModelIndex index() const { return iBlock; } + + virtual QString typeId() const; + + float ctrlTime( float time ) const; + + template static bool interpolate( T & value, const QModelIndex & data, const QString & arrayid, float time, int & lastindex ); + template static bool interpolate( T & value, const QModelIndex & array, float time, int & lastIndex ); + static bool timeIndex( float time, const NifModel * nif, const QModelIndex & array, int & i, int & j, float & x ); + +protected: + friend class Interpolator; + + QPersistentModelIndex iBlock; + QPersistentModelIndex iInterpolator; + QPersistentModelIndex iData; +}; + +template bool Controller::interpolate( T & value, const QModelIndex & data, const QString & arrayid, float time, int & lastindex ) +{ + const NifModel * nif = static_cast( data.model() ); + if ( nif && data.isValid() ) + { + QModelIndex array = nif->getIndex( data, arrayid ); + return interpolate( value, array, time, lastindex ); + } + else + return false; +} + +class Interpolator : public QObject +{ +public: + Interpolator(Controller *owner); + + virtual bool update( const NifModel * nif, const QModelIndex & index ); + +protected: + QPersistentModelIndex GetControllerData(); + Controller *parent; +}; + +class TransformInterpolator : public Interpolator +{ +public: + TransformInterpolator(Controller *owner); + + virtual bool update( const NifModel * nif, const QModelIndex & index ); + virtual bool updateTransform(Transform& tm, float time); + +protected: + QPersistentModelIndex iTranslations, iRotations, iScales; + int lTrans, lRotate, lScale; +}; + +class BSplineTransformInterpolator : public TransformInterpolator +{ +public: + BSplineTransformInterpolator( Controller *owner ); + + virtual bool update( const NifModel * nif, const QModelIndex & index ); + virtual bool updateTransform(Transform& tm, float time); + +protected: + float start, stop; + QPersistentModelIndex iControl, iSpline, iBasis; + QPersistentModelIndex lTrans, lRotate, lScale; + uint lTransOff, lRotateOff, lScaleOff; + float lTransMult, lRotateMult, lScaleMult; + float lTransBias, lRotateBias, lScaleBias; + uint nCtrl; + int degree; +}; + + +#endif + + diff --git a/gl/glmarker.cpp b/gl/glmarker.cpp index d1ecbc763..cb55a42f3 100644 --- a/gl/glmarker.cpp +++ b/gl/glmarker.cpp @@ -1,46 +1,46 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "glmarker.h" - -#include - -void drawMarker( const GLMarker * marker ) -{ - glEnableClientState( GL_VERTEX_ARRAY ); - - glVertexPointer( 3, GL_FLOAT, 0, marker->verts ); - - glDrawElements( GL_TRIANGLES, marker->nf * 3, GL_UNSIGNED_SHORT, marker->faces ); - - glDisableClientState( GL_VERTEX_ARRAY ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "glmarker.h" + +#include + +void drawMarker( const GLMarker * marker ) +{ + glEnableClientState( GL_VERTEX_ARRAY ); + + glVertexPointer( 3, GL_FLOAT, 0, marker->verts ); + + glDrawElements( GL_TRIANGLES, marker->nf * 3, GL_UNSIGNED_SHORT, marker->faces ); + + glDisableClientState( GL_VERTEX_ARRAY ); +} diff --git a/gl/glmarker.h b/gl/glmarker.h index f1e6791be..685790c86 100644 --- a/gl/glmarker.h +++ b/gl/glmarker.h @@ -1,46 +1,46 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLMARKER_H -#define GLMARKER_H - -struct GLMarker -{ - int nv; - int nf; - const float *verts; - const unsigned short *faces; -}; - -void drawMarker( const GLMarker * marker ); - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLMARKER_H +#define GLMARKER_H + +struct GLMarker +{ + int nv; + int nf; + const float *verts; + const unsigned short *faces; +}; + +void drawMarker( const GLMarker * marker ); + +#endif diff --git a/gl/glmesh.cpp b/gl/glmesh.cpp index 5f280a534..a0c215d06 100644 --- a/gl/glmesh.cpp +++ b/gl/glmesh.cpp @@ -1,809 +1,809 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "glmesh.h" -#include "glcontroller.h" -#include "glscene.h" -#include "gltools.h" -#include "options.h" - -#include - -class MorphController : public Controller -{ - struct MorphKey - { - QPersistentModelIndex iFrames; - QVector verts; - int index; - }; - -public: - MorphController( Mesh * mesh, const QModelIndex & index ) - : Controller( index ), target( mesh ) {} - - ~MorphController() - { - qDeleteAll( morph ); - } - - void update( float time ) - { - if ( ! ( target && iData.isValid() && active && morph.count() > 1 ) ) - return; - - time = ctrlTime( time ); - - if ( target->verts.count() != morph[0]->verts.count() ) - return; - - target->verts = morph[0]->verts; - - float x; - - for ( int i = 1; i < morph.count(); i++ ) - { - MorphKey * key = morph[i]; - if ( interpolate( x, key->iFrames, time, key->index ) ) - { - if ( x < 0 ) x = 0; - if ( x > 1 ) x = 1; - - if ( x != 0 && target->verts.count() == key->verts.count() ) - { - for ( int v = 0; v < target->verts.count(); v++ ) - target->verts[v] += key->verts[v] * x; - } - } - } - - target->upBounds = true; - } - - bool update( const NifModel * nif, const QModelIndex & index ) - { - if ( Controller::update( nif, index ) ) - { - qDeleteAll( morph ); - morph.clear(); - - QModelIndex iInterpolators = nif->getIndex( iBlock, "Interpolators" ); - - QModelIndex midx = nif->getIndex( iData, "Morphs" ); - for ( int r = 0; r < nif->rowCount( midx ); r++ ) - { - QModelIndex iKey = midx.child( r, 0 ); - - MorphKey * key = new MorphKey; - key->index = 0; - if ( iInterpolators.isValid() ) - key->iFrames = nif->getIndex( nif->getBlock( nif->getLink( nif->getBlock( nif->getLink( iInterpolators.child( r, 0 ) ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); - else - key->iFrames = iKey; - key->verts = nif->getArray( nif->getIndex( iKey, "Vectors" ) ); - - morph.append( key ); - } - return true; - } - return false; - } - -protected: - QPointer target; - QVector morph; -}; - - -/* - * Mesh - */ - -void Mesh::clear() -{ - Node::clear(); - - iData = iSkin = iSkinData = iSkinPart = iTangentData = QModelIndex(); - - verts.clear(); - norms.clear(); - colors.clear(); - coords.clear(); - tangents.clear(); - binormals.clear(); - triangles.clear(); - tristrips.clear(); - weights.clear(); - partitions.clear(); - sortedTriangles.clear(); - transVerts.clear(); - transNorms.clear(); - transColors.clear(); - transTangents.clear(); - transBinormals.clear(); -} - -void Mesh::update( const NifModel * nif, const QModelIndex & index ) -{ - Node::update( nif, index ); - - if ( ! iBlock.isValid() || ! index.isValid() ) - return; - - upData |= ( iData == index ) || ( iTangentData == index ); - upSkin |= ( iSkin == index ); - upSkin |= ( iSkinData == index ); - upSkin |= ( iSkinPart == index ); - - if ( iBlock == index ) - { - foreach ( int link, nif->getChildLinks( id() ) ) - { - QModelIndex iChild = nif->getBlock( link ); - if ( ! iChild.isValid() ) continue; - QString name = nif->itemName( iChild ); - - if ( name == "NiTriShapeData" || name == "NiTriStripsData" ) - { - if ( ! iData.isValid() ) - { - iData = iChild; - upData = true; - } - else if ( iData != iChild ) - { - qWarning() << "shape block" << id() << "has multiple data blocks"; - } - } - else if ( name == "NiSkinInstance" ) - { - if ( ! iSkin.isValid() ) - { - iSkin = iChild; - upSkin = true; - } - else if ( iSkin != iChild ) - qWarning() << "shape block" << id() << "has multiple skin instances"; - } - } - } - - upBounds |= upData; -} - -void Mesh::setController( const NifModel * nif, const QModelIndex & iController ) -{ - if ( nif->itemName( iController ) == "NiGeomMorpherController" ) - { - Controller * ctrl = new MorphController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } - else - Node::setController( nif, iController ); -} - -bool Mesh::isHidden() const -{ - return ( Node::isHidden() || ( ! Options::drawHidden() && Options::onlyTextured() && ! properties.get< TexturingProperty >() ) ); -} - -bool compareTriangles( const QPair< int, float > & tri1, const QPair< int, float > & tri2 ) -{ - return ( tri1.second < tri2.second ); -} - -void Mesh::transform() -{ - const NifModel * nif = static_cast( iBlock.model() ); - if ( ! nif || ! iBlock.isValid() ) - { - clear(); - return; - } - - if ( upData ) - { - upData = false; - - verts = nif->getArray( iData, "Vertices" ); - norms = nif->getArray( iData, "Normals" ); - colors = nif->getArray( iData, "Vertex Colors" ); - tangents.clear(); - binormals.clear(); - - if ( norms.count() < verts.count() ) norms.clear(); - if ( colors.count() < verts.count() ) colors.clear(); - - coords.clear(); - QModelIndex uvcoord = nif->getIndex( iData, "UV Sets" ); - if ( ! uvcoord.isValid() ) uvcoord = nif->getIndex( iData, "UV Sets 2" ); - if ( uvcoord.isValid() ) - { - for ( int r = 0; r < nif->rowCount( uvcoord ); r++ ) - { - QVector tc = nif->getArray( uvcoord.child( r, 0 ) ); - if ( tc.count() < verts.count() ) tc.clear(); - coords.append( tc ); - } - } - - if ( nif->itemName( iData ) == "NiTriShapeData" ) - { - triangles = nif->getArray( iData, "Triangles" ); - tristrips.clear(); - } - else if ( nif->itemName( iData ) == "NiTriStripsData" ) - { - tristrips.clear(); - QModelIndex points = nif->getIndex( iData, "Points" ); - if ( points.isValid() ) - { - for ( int r = 0; r < nif->rowCount( points ); r++ ) - tristrips.append( nif->getArray( points.child( r, 0 ) ) ); - } - else - qWarning() << nif->itemName( iData ) << "(" << nif->getBlockNumber( iData ) << ") 'points' array not found"; - triangles.clear(); - } - else - { - triangles.clear(); - tristrips.clear(); - } - - QModelIndex iExtraData = nif->getIndex( iBlock, "Extra Data List" ); - if ( iExtraData.isValid() ) - { - for ( int e = 0; e < nif->rowCount( iExtraData ); e++ ) - { - QModelIndex iExtra = nif->getBlock( nif->getLink( iExtraData.child( e, 0 ) ), "NiBinaryExtraData" ); - if ( nif->get( iExtra, "Name" ) == "Tangent space (binormal & tangent vectors)" ) - { - iTangentData = iExtra; - QByteArray data = nif->get( iExtra, "Binary Data" ); - if ( data.count() == verts.count() * 4 * 3 * 2 ) - { - tangents.resize( verts.count() ); - binormals.resize( verts.count() ); - Vector3 * t = (Vector3 *) data.data(); - for ( int c = 0; c < verts.count(); c++ ) - tangents[c] = *t++; - for ( int c = 0; c < verts.count(); c++ ) - binormals[c] = *t++; - } - } - } - } - } - - if ( upSkin ) - { - upSkin = false; - weights.clear(); - partitions.clear(); - - iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), "NiSkinData" ); - - skelRoot = nif->getLink( iSkin, "Skeleton Root" ); - skelTrans = Transform( nif, iSkinData ); - - bones = nif->getLinkArray( iSkin, "Bones" ); - - QModelIndex idxBones = nif->getIndex( iSkinData, "Bone List" ); - if ( idxBones.isValid() ) - { - for ( int b = 0; b < nif->rowCount( idxBones ) && b < bones.count(); b++ ) - { - weights.append( BoneWeights( nif, idxBones.child( b, 0 ), bones[ b ] ) ); - } - } - - iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); - if ( ! iSkinPart.isValid() ) - // nif versions < 10.2.0.0 have skin partition linked in the skin data block - iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); - if ( iSkinPart.isValid() ) - { - QModelIndex idx = nif->getIndex( iSkinPart, "Skin Partition Blocks" ); - for ( int i = 0; i < nif->rowCount( idx ) && idx.isValid(); i++ ) - { - partitions.append( SkinPartition( nif, idx.child( i, 0 ) ) ); - } - } - } - - Node::transform(); -} - -void Mesh::transformShapes() -{ - if ( isHidden() ) - return; - - Node::transformShapes(); - - transformRigid = true; - - if ( weights.count() ) - { - transformRigid = false; - - transVerts.resize( verts.count() ); - transVerts.fill( Vector3() ); - transNorms.resize( norms.count() ); - transNorms.fill( Vector3() ); - transTangents.resize( tangents.count() ); - transTangents.fill( Vector3() ); - transBinormals.resize( binormals.count() ); - transBinormals.fill( Vector3() ); - - Node * root = findParent( skelRoot ); - - if ( partitions.count() ) - { - foreach ( SkinPartition part, partitions ) - { - QVector boneTrans( part.boneMap.count() ); - for ( int t = 0; t < boneTrans.count(); t++ ) - { - Node * bone = root ? root->findChild( bones.value( part.boneMap[t] ) ) : 0; - boneTrans[ t ] = viewTrans() * skelTrans; - if ( bone ) boneTrans[ t ] = boneTrans[ t ] * bone->localTransFrom( skelRoot ) * weights.value( part.boneMap[t] ).trans; - //if ( bone ) boneTrans[ t ] = bone->viewTrans() * weights.value( part.boneMap[t] ).trans; - } - - for ( int v = 0; v < part.vertexMap.count(); v++ ) - { - int vindex = part.vertexMap[ v ]; - if ( transVerts[vindex] == Vector3() ) - { - for ( int w = 0; w < part.numWeightsPerVertex; w++ ) - { - QPair< int, float > weight = part.weights[ v * part.numWeightsPerVertex + w ]; - Transform trans = boneTrans.value( weight.first ); - - if ( verts.count() > vindex ) - transVerts[vindex] += trans * verts[ vindex ] * weight.second; - if ( norms.count() > vindex ) - transNorms[vindex] += trans.rotation * norms[ vindex ] * weight.second; - if ( tangents.count() > vindex ) - transTangents[vindex] += trans.rotation * tangents[ vindex ] * weight.second; - if ( binormals.count() > vindex ) - transBinormals[vindex] += trans.rotation * binormals[ vindex ] * weight.second; - } - } - } - } - } - else - { - int x = 0; - foreach ( BoneWeights bw, weights ) - { - Transform trans = viewTrans() * skelTrans; - Node * bone = root ? root->findChild( bw.bone ) : 0; - if ( bone ) trans = trans * bone->localTransFrom( skelRoot ) * bw.trans; - if ( bone ) weights[x++].tcenter = bone->viewTrans() * bw.center; else x++; - - Matrix natrix = trans.rotation; - foreach ( VertexWeight vw, bw.weights ) - { - if ( transVerts.count() > vw.vertex ) - transVerts[ vw.vertex ] += trans * verts[ vw.vertex ] * vw.weight; - if ( transNorms.count() > vw.vertex ) - transNorms[ vw.vertex ] += natrix * norms[ vw.vertex ] * vw.weight; - if ( transTangents.count() > vw.vertex ) - transTangents[ vw.vertex ] += natrix * tangents[ vw.vertex ] * vw.weight; - if ( transBinormals.count() > vw.vertex ) - transBinormals[ vw.vertex ] += natrix * binormals[ vw.vertex ] * vw.weight; - } - } - } - - for ( int n = 0; n < transNorms.count(); n++ ) - transNorms[n].normalize(); - for ( int t = 0; t < transTangents.count(); t++ ) - transTangents[t].normalize(); - for ( int t = 0; t < transBinormals.count(); t++ ) - transBinormals[t].normalize(); - - bndSphere = BoundSphere( transVerts ); - bndSphere.applyInv( viewTrans() ); - upBounds = false; - } - else - { - transVerts = verts; - transNorms = norms; - transTangents = tangents; - transBinormals = binormals; - } - - AlphaProperty * alphaprop = findProperty(); - - - /* - //Commented this out because this appears from my tests to be an - //incorrect understanding of the flag we previously called "sort." - //Tests have shown that none of the games or official scene viewers - //ever sort triangles, regarless of the path. The triangles are always - //drawn in the order they exist in the triangle array. - - if ( alphaprop && alphaprop->sort() ) - { - - - - QVector< QPair< int, float > > triOrder( triangles.count() ); - if ( transformRigid ) - { - Transform vt = viewTrans(); - Vector3 ref = vt.rotation.inverted() * ( Vector3() - vt.translation ) / vt.scale; - int t = 0; - foreach ( Triangle tri, triangles ) - { - triOrder[t] = QPair( t, 0 - ( ref - verts.value( tri.v1() ) ).squaredLength() - ( ref - verts.value( tri.v2() ) ).squaredLength() - ( ref - verts.value( tri.v1() ) ).squaredLength() ); - t++; - } - } - else - { - int t = 0; - foreach ( Triangle tri, triangles ) - { - QPair< int, float > tp; - tp.first = t; - tp.second = transVerts.value( tri.v1() )[2] + transVerts.value( tri.v2() )[2] + transVerts.value( tri.v3() )[2]; - triOrder[t++] = tp; - } - } - qSort( triOrder.begin(), triOrder.end(), compareTriangles ); - sortedTriangles.resize( triangles.count() ); - for ( int t = 0; t < triangles.count(); t++ ) - sortedTriangles[t] = triangles[ triOrder[t].first ]; - } - else - { - */ - sortedTriangles = triangles; - //} - - MaterialProperty * matprop = findProperty(); - if ( matprop && matprop->alphaValue() != 1.0 ) - { - float a = matprop->alphaValue(); - transColors.resize( colors.count() ); - for ( int c = 0; c < colors.count(); c++ ) - transColors[c] = colors[c].blend( a ); - } - else - transColors = colors; -} - -BoundSphere Mesh::bounds() const -{ - if ( upBounds ) - { - upBounds = false; - bndSphere = BoundSphere( verts ); - } - return worldTrans() * bndSphere; -} - -void Mesh::drawShapes( NodeList * draw2nd ) -{ - if ( isHidden() ) - return; - - glLoadName( nodeId ); - - // draw transparent meshes during second run - - AlphaProperty * aprop = findProperty< AlphaProperty >(); - if ( aprop && aprop->blend() && draw2nd ) - { - draw2nd->add( this ); - return; - } - - // rigid mesh? then pass the transformation on to the gl layer - - if ( transformRigid ) - { - glPushMatrix(); - glMultMatrix( viewTrans() ); - } - - // setup array pointers - - glEnableClientState( GL_VERTEX_ARRAY ); - glVertexPointer( 3, GL_FLOAT, 0, transVerts.data() ); - - if ( transNorms.count() ) - { - glEnableClientState( GL_NORMAL_ARRAY ); - glNormalPointer( GL_FLOAT, 0, transNorms.data() ); - } - - if ( transColors.count() ) - { - glEnableClientState( GL_COLOR_ARRAY ); - glColorPointer( 4, GL_FLOAT, 0, transColors.data() ); - } - else - glColor( Color3( 1.0f, 0.2f, 1.0f ) ); - - - shader = scene->renderer.setupProgram( this, shader ); - - - // render the triangles - - if ( sortedTriangles.count() ) - glDrawElements( GL_TRIANGLES, sortedTriangles.count() * 3, GL_UNSIGNED_SHORT, sortedTriangles.data() ); - - // render the tristrips - - for ( int s = 0; s < tristrips.count(); s++ ) - glDrawElements( GL_TRIANGLE_STRIP, tristrips[s].count(), GL_UNSIGNED_SHORT, tristrips[s].data() ); - - scene->renderer.stopProgram(); - - glDisableClientState( GL_VERTEX_ARRAY ); - glDisableClientState( GL_NORMAL_ARRAY ); - glDisableClientState( GL_COLOR_ARRAY ); - - if ( transformRigid ) - glPopMatrix(); -} - -void Mesh::drawSelection() const -{ - Node::drawSelection(); - - if ( isHidden() ) - return; - - if ( scene->currentBlock != iBlock && scene->currentBlock != iData && scene->currentBlock != iSkinPart - && ( ! iTangentData.isValid() || scene->currentBlock != iTangentData ) ) - return; - - if ( transformRigid ) - { - glPushMatrix(); - glMultMatrix( viewTrans() ); - } - - glDisable( GL_LIGHTING ); - glDisable( GL_COLOR_MATERIAL ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_NORMALIZE ); - glEnable( GL_DEPTH_TEST ); - glDepthMask( GL_FALSE ); - glEnable( GL_BLEND ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - glDisable( GL_ALPHA_TEST ); - - glLineWidth( 1.0 ); - glPointSize( 3.5 ); - - QString n; - int i = -1; - - if ( scene->currentBlock == iBlock || scene->currentIndex == iData ) - { - n = "Faces"; - } - else if ( scene->currentBlock == iData || scene->currentBlock == iSkinPart ) - { - n = scene->currentIndex.data( Qt::DisplayRole ).toString(); - - QModelIndex iParent = scene->currentIndex.parent(); - if ( iParent.isValid() && iParent != iData ) - { - n = iParent.data( Qt::DisplayRole ).toString(); - i = scene->currentIndex.row(); - } - } - else if ( scene->currentBlock == iTangentData ) - { - n = "TSpace"; - } - - if ( n == "Vertices" || n == "Normals" || n == "Vertex Colors" || n == "UV Sets" ) - { - glDepthFunc( GL_LEQUAL ); - glNormalColor(); - glBegin( GL_POINTS ); - for ( int j = 0; j < transVerts.count(); j++ ) - glVertex( transVerts.value( j ) ); - glEnd(); - if ( i >= 0 ) - { - glDepthFunc( GL_ALWAYS ); - glHighlightColor(); - glBegin( GL_POINTS ); - glVertex( transVerts.value( i ) ); - glEnd(); - } - } - if ( n == "Normals" || n == "TSpace" ) - { - glDepthFunc( GL_LEQUAL ); - glNormalColor(); - - float normalScale = bounds().radius / 20; - if ( normalScale < 0.1f ) normalScale = 0.1f; - - glBegin( GL_LINES ); - - for ( int j = 0; j < transVerts.count() && j < transNorms.count(); j++ ) - { - glVertex( transVerts.value( j ) ); - glVertex( transVerts.value( j ) + transNorms.value( j ) * normalScale ); - } - - if ( n == "TSpace" ) - { - for ( int j = 0; j < transVerts.count() && j < transTangents.count() && j < transBinormals.count(); j++ ) - { - glVertex( transVerts.value( j ) ); - glVertex( transVerts.value( j ) + transTangents.value( j ) * normalScale ); - glVertex( transVerts.value( j ) ); - glVertex( transVerts.value( j ) + transBinormals.value( j ) * normalScale ); - } - } - - glEnd(); - - if ( i >= 0 ) - { - glDepthFunc( GL_ALWAYS ); - glHighlightColor(); - glBegin( GL_LINES ); - glVertex( transVerts.value( i ) ); - glVertex( transVerts.value( i ) + transNorms.value( i ) * normalScale ); - glEnd(); - } - } - if ( n == "Faces" || n == "Triangles" ) - { - glDepthFunc( GL_LEQUAL ); - glLineWidth( 1.5f ); - glNormalColor(); - foreach ( Triangle tri, triangles ) - { - glBegin( GL_LINE_STRIP ); - glVertex( transVerts.value( tri.v1() ) ); - glVertex( transVerts.value( tri.v2() ) ); - glVertex( transVerts.value( tri.v3() ) ); - glVertex( transVerts.value( tri.v1() ) ); - glEnd(); - } - if ( i >= 0 ) - { - glDepthFunc( GL_ALWAYS ); - glHighlightColor(); - Triangle tri = triangles.value( i ); - glBegin( GL_LINE_STRIP ); - glVertex( transVerts.value( tri.v1() ) ); - glVertex( transVerts.value( tri.v2() ) ); - glVertex( transVerts.value( tri.v3() ) ); - glVertex( transVerts.value( tri.v1() ) ); - glEnd(); - } - } - if ( n == "Faces" || n == "Strips" ) - { - glDepthFunc( GL_LEQUAL ); - glLineWidth( 1.5f ); - glNormalColor(); - foreach ( QVector strip, tristrips ) - { - quint16 a = strip.value( 0 ); - quint16 b = strip.value( 1 ); - - for ( int v = 2; v < strip.count(); v++ ) - { - quint16 c = strip[v]; - - if ( a != b && b != c && c != a ) - { - glBegin( GL_LINE_STRIP ); - glVertex( transVerts.value( a ) ); - glVertex( transVerts.value( b ) ); - glVertex( transVerts.value( c ) ); - glVertex( transVerts.value( a ) ); - glEnd(); - } - - a = b; - b = c; - } - } - } - if ( n == "Skin Partition Blocks" ) - { - glDepthFunc( GL_LEQUAL ); - for ( int c = 0; c < partitions.count(); c++ ) - { - if ( c == i ) - glHighlightColor(); - else - glNormalColor(); - - QVector vmap = partitions[c].vertexMap; - - foreach ( Triangle tri, partitions[c].triangles ) - { - glBegin( GL_LINE_STRIP ); - glVertex( transVerts.value( vmap.value( tri.v1() ) ) ); - glVertex( transVerts.value( vmap.value( tri.v2() ) ) ); - glVertex( transVerts.value( vmap.value( tri.v3() ) ) ); - glVertex( transVerts.value( vmap.value( tri.v1() ) ) ); - glEnd(); - } - foreach ( QVector strip, partitions[c].tristrips ) - { - quint16 a = vmap.value( strip.value( 0 ) ); - quint16 b = vmap.value( strip.value( 1 ) ); - - for ( int v = 2; v < strip.count(); v++ ) - { - quint16 c = vmap.value( strip[v] ); - - if ( a != b && b != c && c != a ) - { - glBegin( GL_LINE_STRIP ); - glVertex( transVerts.value( a ) ); - glVertex( transVerts.value( b ) ); - glVertex( transVerts.value( c ) ); - glVertex( transVerts.value( a ) ); - glEnd(); - } - - a = b; - b = c; - } - } - } - } - - if ( transformRigid ) - glPopMatrix(); -} - -QString Mesh::textStats() const -{ - return Node::textStats() + QString( "\nshader: %1\n" ).arg( shader ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "glmesh.h" +#include "glcontroller.h" +#include "glscene.h" +#include "gltools.h" +#include "options.h" + +#include + +class MorphController : public Controller +{ + struct MorphKey + { + QPersistentModelIndex iFrames; + QVector verts; + int index; + }; + +public: + MorphController( Mesh * mesh, const QModelIndex & index ) + : Controller( index ), target( mesh ) {} + + ~MorphController() + { + qDeleteAll( morph ); + } + + void update( float time ) + { + if ( ! ( target && iData.isValid() && active && morph.count() > 1 ) ) + return; + + time = ctrlTime( time ); + + if ( target->verts.count() != morph[0]->verts.count() ) + return; + + target->verts = morph[0]->verts; + + float x; + + for ( int i = 1; i < morph.count(); i++ ) + { + MorphKey * key = morph[i]; + if ( interpolate( x, key->iFrames, time, key->index ) ) + { + if ( x < 0 ) x = 0; + if ( x > 1 ) x = 1; + + if ( x != 0 && target->verts.count() == key->verts.count() ) + { + for ( int v = 0; v < target->verts.count(); v++ ) + target->verts[v] += key->verts[v] * x; + } + } + } + + target->upBounds = true; + } + + bool update( const NifModel * nif, const QModelIndex & index ) + { + if ( Controller::update( nif, index ) ) + { + qDeleteAll( morph ); + morph.clear(); + + QModelIndex iInterpolators = nif->getIndex( iBlock, "Interpolators" ); + + QModelIndex midx = nif->getIndex( iData, "Morphs" ); + for ( int r = 0; r < nif->rowCount( midx ); r++ ) + { + QModelIndex iKey = midx.child( r, 0 ); + + MorphKey * key = new MorphKey; + key->index = 0; + if ( iInterpolators.isValid() ) + key->iFrames = nif->getIndex( nif->getBlock( nif->getLink( nif->getBlock( nif->getLink( iInterpolators.child( r, 0 ) ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); + else + key->iFrames = iKey; + key->verts = nif->getArray( nif->getIndex( iKey, "Vectors" ) ); + + morph.append( key ); + } + return true; + } + return false; + } + +protected: + QPointer target; + QVector morph; +}; + + +/* + * Mesh + */ + +void Mesh::clear() +{ + Node::clear(); + + iData = iSkin = iSkinData = iSkinPart = iTangentData = QModelIndex(); + + verts.clear(); + norms.clear(); + colors.clear(); + coords.clear(); + tangents.clear(); + binormals.clear(); + triangles.clear(); + tristrips.clear(); + weights.clear(); + partitions.clear(); + sortedTriangles.clear(); + transVerts.clear(); + transNorms.clear(); + transColors.clear(); + transTangents.clear(); + transBinormals.clear(); +} + +void Mesh::update( const NifModel * nif, const QModelIndex & index ) +{ + Node::update( nif, index ); + + if ( ! iBlock.isValid() || ! index.isValid() ) + return; + + upData |= ( iData == index ) || ( iTangentData == index ); + upSkin |= ( iSkin == index ); + upSkin |= ( iSkinData == index ); + upSkin |= ( iSkinPart == index ); + + if ( iBlock == index ) + { + foreach ( int link, nif->getChildLinks( id() ) ) + { + QModelIndex iChild = nif->getBlock( link ); + if ( ! iChild.isValid() ) continue; + QString name = nif->itemName( iChild ); + + if ( name == "NiTriShapeData" || name == "NiTriStripsData" ) + { + if ( ! iData.isValid() ) + { + iData = iChild; + upData = true; + } + else if ( iData != iChild ) + { + qWarning() << "shape block" << id() << "has multiple data blocks"; + } + } + else if ( name == "NiSkinInstance" ) + { + if ( ! iSkin.isValid() ) + { + iSkin = iChild; + upSkin = true; + } + else if ( iSkin != iChild ) + qWarning() << "shape block" << id() << "has multiple skin instances"; + } + } + } + + upBounds |= upData; +} + +void Mesh::setController( const NifModel * nif, const QModelIndex & iController ) +{ + if ( nif->itemName( iController ) == "NiGeomMorpherController" ) + { + Controller * ctrl = new MorphController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } + else + Node::setController( nif, iController ); +} + +bool Mesh::isHidden() const +{ + return ( Node::isHidden() || ( ! Options::drawHidden() && Options::onlyTextured() && ! properties.get< TexturingProperty >() ) ); +} + +bool compareTriangles( const QPair< int, float > & tri1, const QPair< int, float > & tri2 ) +{ + return ( tri1.second < tri2.second ); +} + +void Mesh::transform() +{ + const NifModel * nif = static_cast( iBlock.model() ); + if ( ! nif || ! iBlock.isValid() ) + { + clear(); + return; + } + + if ( upData ) + { + upData = false; + + verts = nif->getArray( iData, "Vertices" ); + norms = nif->getArray( iData, "Normals" ); + colors = nif->getArray( iData, "Vertex Colors" ); + tangents.clear(); + binormals.clear(); + + if ( norms.count() < verts.count() ) norms.clear(); + if ( colors.count() < verts.count() ) colors.clear(); + + coords.clear(); + QModelIndex uvcoord = nif->getIndex( iData, "UV Sets" ); + if ( ! uvcoord.isValid() ) uvcoord = nif->getIndex( iData, "UV Sets 2" ); + if ( uvcoord.isValid() ) + { + for ( int r = 0; r < nif->rowCount( uvcoord ); r++ ) + { + QVector tc = nif->getArray( uvcoord.child( r, 0 ) ); + if ( tc.count() < verts.count() ) tc.clear(); + coords.append( tc ); + } + } + + if ( nif->itemName( iData ) == "NiTriShapeData" ) + { + triangles = nif->getArray( iData, "Triangles" ); + tristrips.clear(); + } + else if ( nif->itemName( iData ) == "NiTriStripsData" ) + { + tristrips.clear(); + QModelIndex points = nif->getIndex( iData, "Points" ); + if ( points.isValid() ) + { + for ( int r = 0; r < nif->rowCount( points ); r++ ) + tristrips.append( nif->getArray( points.child( r, 0 ) ) ); + } + else + qWarning() << nif->itemName( iData ) << "(" << nif->getBlockNumber( iData ) << ") 'points' array not found"; + triangles.clear(); + } + else + { + triangles.clear(); + tristrips.clear(); + } + + QModelIndex iExtraData = nif->getIndex( iBlock, "Extra Data List" ); + if ( iExtraData.isValid() ) + { + for ( int e = 0; e < nif->rowCount( iExtraData ); e++ ) + { + QModelIndex iExtra = nif->getBlock( nif->getLink( iExtraData.child( e, 0 ) ), "NiBinaryExtraData" ); + if ( nif->get( iExtra, "Name" ) == "Tangent space (binormal & tangent vectors)" ) + { + iTangentData = iExtra; + QByteArray data = nif->get( iExtra, "Binary Data" ); + if ( data.count() == verts.count() * 4 * 3 * 2 ) + { + tangents.resize( verts.count() ); + binormals.resize( verts.count() ); + Vector3 * t = (Vector3 *) data.data(); + for ( int c = 0; c < verts.count(); c++ ) + tangents[c] = *t++; + for ( int c = 0; c < verts.count(); c++ ) + binormals[c] = *t++; + } + } + } + } + } + + if ( upSkin ) + { + upSkin = false; + weights.clear(); + partitions.clear(); + + iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), "NiSkinData" ); + + skelRoot = nif->getLink( iSkin, "Skeleton Root" ); + skelTrans = Transform( nif, iSkinData ); + + bones = nif->getLinkArray( iSkin, "Bones" ); + + QModelIndex idxBones = nif->getIndex( iSkinData, "Bone List" ); + if ( idxBones.isValid() ) + { + for ( int b = 0; b < nif->rowCount( idxBones ) && b < bones.count(); b++ ) + { + weights.append( BoneWeights( nif, idxBones.child( b, 0 ), bones[ b ] ) ); + } + } + + iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); + if ( ! iSkinPart.isValid() ) + // nif versions < 10.2.0.0 have skin partition linked in the skin data block + iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); + if ( iSkinPart.isValid() ) + { + QModelIndex idx = nif->getIndex( iSkinPart, "Skin Partition Blocks" ); + for ( int i = 0; i < nif->rowCount( idx ) && idx.isValid(); i++ ) + { + partitions.append( SkinPartition( nif, idx.child( i, 0 ) ) ); + } + } + } + + Node::transform(); +} + +void Mesh::transformShapes() +{ + if ( isHidden() ) + return; + + Node::transformShapes(); + + transformRigid = true; + + if ( weights.count() ) + { + transformRigid = false; + + transVerts.resize( verts.count() ); + transVerts.fill( Vector3() ); + transNorms.resize( norms.count() ); + transNorms.fill( Vector3() ); + transTangents.resize( tangents.count() ); + transTangents.fill( Vector3() ); + transBinormals.resize( binormals.count() ); + transBinormals.fill( Vector3() ); + + Node * root = findParent( skelRoot ); + + if ( partitions.count() ) + { + foreach ( SkinPartition part, partitions ) + { + QVector boneTrans( part.boneMap.count() ); + for ( int t = 0; t < boneTrans.count(); t++ ) + { + Node * bone = root ? root->findChild( bones.value( part.boneMap[t] ) ) : 0; + boneTrans[ t ] = viewTrans() * skelTrans; + if ( bone ) boneTrans[ t ] = boneTrans[ t ] * bone->localTransFrom( skelRoot ) * weights.value( part.boneMap[t] ).trans; + //if ( bone ) boneTrans[ t ] = bone->viewTrans() * weights.value( part.boneMap[t] ).trans; + } + + for ( int v = 0; v < part.vertexMap.count(); v++ ) + { + int vindex = part.vertexMap[ v ]; + if ( transVerts[vindex] == Vector3() ) + { + for ( int w = 0; w < part.numWeightsPerVertex; w++ ) + { + QPair< int, float > weight = part.weights[ v * part.numWeightsPerVertex + w ]; + Transform trans = boneTrans.value( weight.first ); + + if ( verts.count() > vindex ) + transVerts[vindex] += trans * verts[ vindex ] * weight.second; + if ( norms.count() > vindex ) + transNorms[vindex] += trans.rotation * norms[ vindex ] * weight.second; + if ( tangents.count() > vindex ) + transTangents[vindex] += trans.rotation * tangents[ vindex ] * weight.second; + if ( binormals.count() > vindex ) + transBinormals[vindex] += trans.rotation * binormals[ vindex ] * weight.second; + } + } + } + } + } + else + { + int x = 0; + foreach ( BoneWeights bw, weights ) + { + Transform trans = viewTrans() * skelTrans; + Node * bone = root ? root->findChild( bw.bone ) : 0; + if ( bone ) trans = trans * bone->localTransFrom( skelRoot ) * bw.trans; + if ( bone ) weights[x++].tcenter = bone->viewTrans() * bw.center; else x++; + + Matrix natrix = trans.rotation; + foreach ( VertexWeight vw, bw.weights ) + { + if ( transVerts.count() > vw.vertex ) + transVerts[ vw.vertex ] += trans * verts[ vw.vertex ] * vw.weight; + if ( transNorms.count() > vw.vertex ) + transNorms[ vw.vertex ] += natrix * norms[ vw.vertex ] * vw.weight; + if ( transTangents.count() > vw.vertex ) + transTangents[ vw.vertex ] += natrix * tangents[ vw.vertex ] * vw.weight; + if ( transBinormals.count() > vw.vertex ) + transBinormals[ vw.vertex ] += natrix * binormals[ vw.vertex ] * vw.weight; + } + } + } + + for ( int n = 0; n < transNorms.count(); n++ ) + transNorms[n].normalize(); + for ( int t = 0; t < transTangents.count(); t++ ) + transTangents[t].normalize(); + for ( int t = 0; t < transBinormals.count(); t++ ) + transBinormals[t].normalize(); + + bndSphere = BoundSphere( transVerts ); + bndSphere.applyInv( viewTrans() ); + upBounds = false; + } + else + { + transVerts = verts; + transNorms = norms; + transTangents = tangents; + transBinormals = binormals; + } + + AlphaProperty * alphaprop = findProperty(); + + + /* + //Commented this out because this appears from my tests to be an + //incorrect understanding of the flag we previously called "sort." + //Tests have shown that none of the games or official scene viewers + //ever sort triangles, regarless of the path. The triangles are always + //drawn in the order they exist in the triangle array. + + if ( alphaprop && alphaprop->sort() ) + { + + + + QVector< QPair< int, float > > triOrder( triangles.count() ); + if ( transformRigid ) + { + Transform vt = viewTrans(); + Vector3 ref = vt.rotation.inverted() * ( Vector3() - vt.translation ) / vt.scale; + int t = 0; + foreach ( Triangle tri, triangles ) + { + triOrder[t] = QPair( t, 0 - ( ref - verts.value( tri.v1() ) ).squaredLength() - ( ref - verts.value( tri.v2() ) ).squaredLength() - ( ref - verts.value( tri.v1() ) ).squaredLength() ); + t++; + } + } + else + { + int t = 0; + foreach ( Triangle tri, triangles ) + { + QPair< int, float > tp; + tp.first = t; + tp.second = transVerts.value( tri.v1() )[2] + transVerts.value( tri.v2() )[2] + transVerts.value( tri.v3() )[2]; + triOrder[t++] = tp; + } + } + qSort( triOrder.begin(), triOrder.end(), compareTriangles ); + sortedTriangles.resize( triangles.count() ); + for ( int t = 0; t < triangles.count(); t++ ) + sortedTriangles[t] = triangles[ triOrder[t].first ]; + } + else + { + */ + sortedTriangles = triangles; + //} + + MaterialProperty * matprop = findProperty(); + if ( matprop && matprop->alphaValue() != 1.0 ) + { + float a = matprop->alphaValue(); + transColors.resize( colors.count() ); + for ( int c = 0; c < colors.count(); c++ ) + transColors[c] = colors[c].blend( a ); + } + else + transColors = colors; +} + +BoundSphere Mesh::bounds() const +{ + if ( upBounds ) + { + upBounds = false; + bndSphere = BoundSphere( verts ); + } + return worldTrans() * bndSphere; +} + +void Mesh::drawShapes( NodeList * draw2nd ) +{ + if ( isHidden() ) + return; + + glLoadName( nodeId ); + + // draw transparent meshes during second run + + AlphaProperty * aprop = findProperty< AlphaProperty >(); + if ( aprop && aprop->blend() && draw2nd ) + { + draw2nd->add( this ); + return; + } + + // rigid mesh? then pass the transformation on to the gl layer + + if ( transformRigid ) + { + glPushMatrix(); + glMultMatrix( viewTrans() ); + } + + // setup array pointers + + glEnableClientState( GL_VERTEX_ARRAY ); + glVertexPointer( 3, GL_FLOAT, 0, transVerts.data() ); + + if ( transNorms.count() ) + { + glEnableClientState( GL_NORMAL_ARRAY ); + glNormalPointer( GL_FLOAT, 0, transNorms.data() ); + } + + if ( transColors.count() ) + { + glEnableClientState( GL_COLOR_ARRAY ); + glColorPointer( 4, GL_FLOAT, 0, transColors.data() ); + } + else + glColor( Color3( 1.0f, 0.2f, 1.0f ) ); + + + shader = scene->renderer.setupProgram( this, shader ); + + + // render the triangles + + if ( sortedTriangles.count() ) + glDrawElements( GL_TRIANGLES, sortedTriangles.count() * 3, GL_UNSIGNED_SHORT, sortedTriangles.data() ); + + // render the tristrips + + for ( int s = 0; s < tristrips.count(); s++ ) + glDrawElements( GL_TRIANGLE_STRIP, tristrips[s].count(), GL_UNSIGNED_SHORT, tristrips[s].data() ); + + scene->renderer.stopProgram(); + + glDisableClientState( GL_VERTEX_ARRAY ); + glDisableClientState( GL_NORMAL_ARRAY ); + glDisableClientState( GL_COLOR_ARRAY ); + + if ( transformRigid ) + glPopMatrix(); +} + +void Mesh::drawSelection() const +{ + Node::drawSelection(); + + if ( isHidden() ) + return; + + if ( scene->currentBlock != iBlock && scene->currentBlock != iData && scene->currentBlock != iSkinPart + && ( ! iTangentData.isValid() || scene->currentBlock != iTangentData ) ) + return; + + if ( transformRigid ) + { + glPushMatrix(); + glMultMatrix( viewTrans() ); + } + + glDisable( GL_LIGHTING ); + glDisable( GL_COLOR_MATERIAL ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_NORMALIZE ); + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_FALSE ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_ALPHA_TEST ); + + glLineWidth( 1.0 ); + glPointSize( 3.5 ); + + QString n; + int i = -1; + + if ( scene->currentBlock == iBlock || scene->currentIndex == iData ) + { + n = "Faces"; + } + else if ( scene->currentBlock == iData || scene->currentBlock == iSkinPart ) + { + n = scene->currentIndex.data( Qt::DisplayRole ).toString(); + + QModelIndex iParent = scene->currentIndex.parent(); + if ( iParent.isValid() && iParent != iData ) + { + n = iParent.data( Qt::DisplayRole ).toString(); + i = scene->currentIndex.row(); + } + } + else if ( scene->currentBlock == iTangentData ) + { + n = "TSpace"; + } + + if ( n == "Vertices" || n == "Normals" || n == "Vertex Colors" || n == "UV Sets" ) + { + glDepthFunc( GL_LEQUAL ); + glNormalColor(); + glBegin( GL_POINTS ); + for ( int j = 0; j < transVerts.count(); j++ ) + glVertex( transVerts.value( j ) ); + glEnd(); + if ( i >= 0 ) + { + glDepthFunc( GL_ALWAYS ); + glHighlightColor(); + glBegin( GL_POINTS ); + glVertex( transVerts.value( i ) ); + glEnd(); + } + } + if ( n == "Normals" || n == "TSpace" ) + { + glDepthFunc( GL_LEQUAL ); + glNormalColor(); + + float normalScale = bounds().radius / 20; + if ( normalScale < 0.1f ) normalScale = 0.1f; + + glBegin( GL_LINES ); + + for ( int j = 0; j < transVerts.count() && j < transNorms.count(); j++ ) + { + glVertex( transVerts.value( j ) ); + glVertex( transVerts.value( j ) + transNorms.value( j ) * normalScale ); + } + + if ( n == "TSpace" ) + { + for ( int j = 0; j < transVerts.count() && j < transTangents.count() && j < transBinormals.count(); j++ ) + { + glVertex( transVerts.value( j ) ); + glVertex( transVerts.value( j ) + transTangents.value( j ) * normalScale ); + glVertex( transVerts.value( j ) ); + glVertex( transVerts.value( j ) + transBinormals.value( j ) * normalScale ); + } + } + + glEnd(); + + if ( i >= 0 ) + { + glDepthFunc( GL_ALWAYS ); + glHighlightColor(); + glBegin( GL_LINES ); + glVertex( transVerts.value( i ) ); + glVertex( transVerts.value( i ) + transNorms.value( i ) * normalScale ); + glEnd(); + } + } + if ( n == "Faces" || n == "Triangles" ) + { + glDepthFunc( GL_LEQUAL ); + glLineWidth( 1.5f ); + glNormalColor(); + foreach ( Triangle tri, triangles ) + { + glBegin( GL_LINE_STRIP ); + glVertex( transVerts.value( tri.v1() ) ); + glVertex( transVerts.value( tri.v2() ) ); + glVertex( transVerts.value( tri.v3() ) ); + glVertex( transVerts.value( tri.v1() ) ); + glEnd(); + } + if ( i >= 0 ) + { + glDepthFunc( GL_ALWAYS ); + glHighlightColor(); + Triangle tri = triangles.value( i ); + glBegin( GL_LINE_STRIP ); + glVertex( transVerts.value( tri.v1() ) ); + glVertex( transVerts.value( tri.v2() ) ); + glVertex( transVerts.value( tri.v3() ) ); + glVertex( transVerts.value( tri.v1() ) ); + glEnd(); + } + } + if ( n == "Faces" || n == "Strips" ) + { + glDepthFunc( GL_LEQUAL ); + glLineWidth( 1.5f ); + glNormalColor(); + foreach ( QVector strip, tristrips ) + { + quint16 a = strip.value( 0 ); + quint16 b = strip.value( 1 ); + + for ( int v = 2; v < strip.count(); v++ ) + { + quint16 c = strip[v]; + + if ( a != b && b != c && c != a ) + { + glBegin( GL_LINE_STRIP ); + glVertex( transVerts.value( a ) ); + glVertex( transVerts.value( b ) ); + glVertex( transVerts.value( c ) ); + glVertex( transVerts.value( a ) ); + glEnd(); + } + + a = b; + b = c; + } + } + } + if ( n == "Skin Partition Blocks" ) + { + glDepthFunc( GL_LEQUAL ); + for ( int c = 0; c < partitions.count(); c++ ) + { + if ( c == i ) + glHighlightColor(); + else + glNormalColor(); + + QVector vmap = partitions[c].vertexMap; + + foreach ( Triangle tri, partitions[c].triangles ) + { + glBegin( GL_LINE_STRIP ); + glVertex( transVerts.value( vmap.value( tri.v1() ) ) ); + glVertex( transVerts.value( vmap.value( tri.v2() ) ) ); + glVertex( transVerts.value( vmap.value( tri.v3() ) ) ); + glVertex( transVerts.value( vmap.value( tri.v1() ) ) ); + glEnd(); + } + foreach ( QVector strip, partitions[c].tristrips ) + { + quint16 a = vmap.value( strip.value( 0 ) ); + quint16 b = vmap.value( strip.value( 1 ) ); + + for ( int v = 2; v < strip.count(); v++ ) + { + quint16 c = vmap.value( strip[v] ); + + if ( a != b && b != c && c != a ) + { + glBegin( GL_LINE_STRIP ); + glVertex( transVerts.value( a ) ); + glVertex( transVerts.value( b ) ); + glVertex( transVerts.value( c ) ); + glVertex( transVerts.value( a ) ); + glEnd(); + } + + a = b; + b = c; + } + } + } + } + + if ( transformRigid ) + glPopMatrix(); +} + +QString Mesh::textStats() const +{ + return Node::textStats() + QString( "\nshader: %1\n" ).arg( shader ); +} diff --git a/gl/glmesh.h b/gl/glmesh.h index 14df558c5..b5dfb7e3f 100644 --- a/gl/glmesh.h +++ b/gl/glmesh.h @@ -1,100 +1,100 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLMESH_H -#define GLMESH_H - -#include "glnode.h" -#include "gltools.h" - -class Mesh : public Node -{ -public: - Mesh( Scene * s, const QModelIndex & b ) : Node( s, b ) {} - - void clear(); - void update( const NifModel * nif, const QModelIndex & ); - - void transform(); - void transformShapes(); - - void drawShapes( NodeList * draw2nd = 0 ); - void drawSelection() const; - - bool isHidden() const; - - BoundSphere bounds() const; - - QString textStats() const; - -protected: - void setController( const NifModel * nif, const QModelIndex & controller ); - - QPersistentModelIndex iData, iSkin, iSkinData, iSkinPart, iTangentData; - bool upData, upSkin; - - QVector verts; - QVector norms; - QVector colors; - QVector tangents; - QVector binormals; - - QList< QVector > coords; - - QVector transVerts; - QVector transNorms; - QVector transColors; - QVector transTangents; - QVector transBinormals; - - int skelRoot; - Transform skelTrans; - QVector bones; - QVector weights; - QVector partitions; - - QVector triangles; - QList< QVector > tristrips; - QVector sortedTriangles; - - bool transformRigid; - - mutable BoundSphere bndSphere; - mutable bool upBounds; - - QString shader; - - friend class MorphController; - friend class Renderer; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLMESH_H +#define GLMESH_H + +#include "glnode.h" +#include "gltools.h" + +class Mesh : public Node +{ +public: + Mesh( Scene * s, const QModelIndex & b ) : Node( s, b ) {} + + void clear(); + void update( const NifModel * nif, const QModelIndex & ); + + void transform(); + void transformShapes(); + + void drawShapes( NodeList * draw2nd = 0 ); + void drawSelection() const; + + bool isHidden() const; + + BoundSphere bounds() const; + + QString textStats() const; + +protected: + void setController( const NifModel * nif, const QModelIndex & controller ); + + QPersistentModelIndex iData, iSkin, iSkinData, iSkinPart, iTangentData; + bool upData, upSkin; + + QVector verts; + QVector norms; + QVector colors; + QVector tangents; + QVector binormals; + + QList< QVector > coords; + + QVector transVerts; + QVector transNorms; + QVector transColors; + QVector transTangents; + QVector transBinormals; + + int skelRoot; + Transform skelTrans; + QVector bones; + QVector weights; + QVector partitions; + + QVector triangles; + QList< QVector > tristrips; + QVector sortedTriangles; + + bool transformRigid; + + mutable BoundSphere bndSphere; + mutable bool upBounds; + + QString shader; + + friend class MorphController; + friend class Renderer; +}; + +#endif diff --git a/gl/glnode.cpp b/gl/glnode.cpp index 347ba38c0..055b0d1ae 100644 --- a/gl/glnode.cpp +++ b/gl/glnode.cpp @@ -1,1665 +1,1665 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "glmarker.h" -#include "glnode.h" -#include "glcontroller.h" -#include "glscene.h" -#include "options.h" - -#include "NvTriStrip/qtwrapper.h" - -#include "marker/furniture.h" -#include "marker/constraints.h" - -#ifndef M_PI -#define M_PI 3.1415926535897932385 -#endif - -class TransformController : public Controller -{ -public: - TransformController( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ){} - - void update( float time ) - { - if ( ! ( active && target ) ) - return; - - time = ctrlTime( time ); - - if ( interpolator ) - { - interpolator->updateTransform(target->local, time); - } - } - - void setInterpolator( const QModelIndex & iBlock ) - { - const NifModel * nif = static_cast( iBlock.model() ); - if ( nif && iBlock.isValid() ) - { - if ( interpolator ) - { - delete interpolator; - interpolator = 0; - } - - if ( nif->isNiBlock( iBlock, "NiBSplineCompTransformInterpolator" ) ) - { - iInterpolator = iBlock; - interpolator = new BSplineTransformInterpolator(this); - } - else if ( nif->isNiBlock( iBlock, "NiTransformInterpolator" ) ) - { - iInterpolator = iBlock; - interpolator = new TransformInterpolator(this); - } - - if ( interpolator ) - { - interpolator->update( nif, iInterpolator ); - } - } - } - -protected: - QPointer target; - QPointer interpolator; -}; - -class MultiTargetTransformController : public Controller -{ - typedef QPair< QPointer, QPointer > TransformTarget; - -public: - MultiTargetTransformController( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ) {} - - void update( float time ) - { - if ( ! ( active && target ) ) - return; - - time = ctrlTime( time ); - - foreach ( TransformTarget tt, extraTargets ) - { - if ( tt.first && tt.second ) - { - tt.second->updateTransform( tt.first->local, time ); - } - } - } - - bool update( const NifModel * nif, const QModelIndex & index ) - { - if ( Controller::update( nif, index ) ) - { - if ( target ) - { - Scene * scene = target->scene; - extraTargets.clear(); - - QVector lTargets = nif->getLinkArray( index, "Extra Targets" ); - foreach ( qint32 l, lTargets ) - { - Node * node = scene->getNode( nif, nif->getBlock( l ) ); - if ( node ) - { - extraTargets.append( TransformTarget( node, 0 ) ); - } - } - - } - return true; - } - - foreach ( TransformTarget tt, extraTargets ) - { - // TODO: update the interpolators - } - - return false; - } - - bool setInterpolator( Node * node, const QModelIndex & iInterpolator ) - { - const NifModel * nif = static_cast( iInterpolator.model() ); - if ( ! nif || ! iInterpolator.isValid() ) - return false; - QMutableListIterator it( extraTargets ); - while ( it.hasNext() ) - { - it.next(); - if ( it.value().first == node ) - { - if ( it.value().second ) - { - delete it.value().second; - it.value().second = 0; - } - - if ( nif->isNiBlock( iInterpolator, "NiBSplineCompTransformInterpolator" ) ) - { - it.value().second = new BSplineTransformInterpolator( this ); - } - else if ( nif->isNiBlock( iInterpolator, "NiTransformInterpolator" ) ) - { - it.value().second = new TransformInterpolator( this ); - } - - if ( it.value().second ) - { - it.value().second->update( nif, iInterpolator ); - } - return true; - } - } - return false; - } - -protected: - QPointer target; - QList< TransformTarget > extraTargets; -}; - -class ControllerManager : public Controller -{ -public: - ControllerManager( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ) {} - - void update( float ) {} - - bool update( const NifModel * nif, const QModelIndex & index ) - { - if ( Controller::update( nif, index ) ) - { - if ( target ) - { - Scene * scene = target->scene; - QVector lSequences = nif->getLinkArray( index, "Controller Sequences" ); - foreach ( qint32 l, lSequences ) - { - QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); - if ( iSeq.isValid() ) - { - QString name = nif->get( iSeq, "Name" ); - if ( ! scene->animGroups.contains( name ) ) - { - scene->animGroups.append( name ); - - QMap tags = scene->animTags[ name ]; - - QModelIndex iKeys = nif->getBlock( nif->getLink( iSeq, "Text Keys" ), "NiTextKeyExtraData" ); - QModelIndex iTags = nif->getIndex( iKeys, "Text Keys" ); - for ( int r = 0; r < nif->rowCount( iTags ); r++ ) - { - tags.insert( nif->get( iTags.child( r, 0 ), "Value" ), nif->get( iTags.child( r, 0 ), "Time" ) ); - } - - scene->animTags[ name ] = tags; - } - } - } - } - return true; - } - return false; - } - - void setSequence( const QString & seqname ) - { - const NifModel * nif = static_cast( iBlock.model() ); - if ( target && iBlock.isValid() && nif ) - { - MultiTargetTransformController * multiTargetTransformer = 0; - foreach ( Controller * c, target->controllers ) - { - if ( c->typeId() == "NiMultiTargetTransformController" ) - { - multiTargetTransformer = static_cast( c ); - break; - } - } - - QVector lSequences = nif->getLinkArray( iBlock, "Controller Sequences" ); - foreach ( qint32 l, lSequences ) - { - QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); - if ( iSeq.isValid() && nif->get( iSeq, "Name" ) == seqname ) - { - start = nif->get( iSeq, "Start Time" ); - stop = nif->get( iSeq, "Stop Time" ); - phase = nif->get( iSeq, "Phase" ); - frequency = nif->get( iSeq, "Frequency" ); - - QModelIndex iCtrlBlcks = nif->getIndex( iSeq, "Controlled Blocks" ); - for ( int r = 0; r < nif->rowCount( iCtrlBlcks ); r++ ) - { - QModelIndex iCB = iCtrlBlcks.child( r, 0 ); - - QModelIndex iInterpolator = nif->getBlock( nif->getLink( iCB, "Interpolator" ), "NiInterpolator" ); - - QString nodename = nif->get( iCB, "Node Name" ); - if ( nodename.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Node Name Offset" ); - nodename = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); - } - QString proptype = nif->get( iCB, "Property Type" ); - if ( proptype.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Property Type Offset" ); - proptype = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); - } - QString ctrltype = nif->get( iCB, "Controller Type" ); - if ( ctrltype.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Controller Type Offset" ); - ctrltype = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); - } - QString var1 = nif->get( iCB, "Variable 1" ); - if ( var1.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Variable Offset 1" ); - var1 = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); - } - QString var2 = nif->get( iCB, "Variable 2" ); - if ( var2.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Variable Offset 2" ); - var2 = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); - } - Node * node = target->findChild( nodename ); - if ( ! node ) - continue; - - if ( ctrltype == "NiTransformController" && multiTargetTransformer ) - { - if ( multiTargetTransformer->setInterpolator( node, iInterpolator ) ) - { - multiTargetTransformer->start = start; - multiTargetTransformer->stop = stop; - multiTargetTransformer->phase = phase; - multiTargetTransformer->frequency = frequency; - continue; - } - } - - Controller * ctrl = node->findController( proptype, ctrltype, var1, var2 ); - if ( ctrl ) - { - ctrl->start = start; - ctrl->stop = stop; - ctrl->phase = phase; - ctrl->frequency = frequency; - - ctrl->setInterpolator( iInterpolator ); - } - } - } - } - } - } - -protected: - QPointer target; -}; - -class KeyframeController : public Controller -{ -public: - KeyframeController( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ), lTrans( 0 ), lRotate( 0 ), lScale( 0 ) {} - - void update( float time ) - { - if ( ! ( active && target ) ) - return; - - time = ctrlTime( time ); - - interpolate( target->local.rotation, iRotations, time, lRotate ); - interpolate( target->local.translation, iTranslations, time, lTrans ); - interpolate( target->local.scale, iScales, time, lScale ); - } - - bool update( const NifModel * nif, const QModelIndex & index ) - { - if ( Controller::update( nif, index ) ) - { - iTranslations = nif->getIndex( iData, "Translations" ); - iRotations = nif->getIndex( iData, "Rotations" ); - if ( ! iRotations.isValid() ) - iRotations = iData; - iScales = nif->getIndex( iData, "Scales" ); - return true; - } - return false; - } - -protected: - QPointer target; - - QPersistentModelIndex iTranslations, iRotations, iScales; - - int lTrans, lRotate, lScale; -}; - -class VisibilityController : public Controller -{ -public: - VisibilityController( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ), visLast( 0 ) {} - - void update( float time ) - { - if ( ! ( active && target ) ) - return; - - time = ctrlTime( time ); - - bool isVisible; - if ( interpolate( isVisible, iKeys, time, visLast ) ) - { - target->flags.node.hidden = ! isVisible; - } - } - - bool update( const NifModel * nif, const QModelIndex & index ) - { - if ( Controller::update( nif, index ) ) - { - iKeys = nif->getIndex( iData, "Data" ); - return true; - } - return false; - } - -protected: - QPointer target; - - QPersistentModelIndex iKeys; - - int visLast; -}; - -/* - * Node list - */ - -NodeList::NodeList() -{ -} - -NodeList::NodeList( const NodeList & other ) -{ - operator=( other ); -} - -NodeList::~NodeList() -{ - clear(); -} - -void NodeList::clear() -{ - foreach( Node * n, nodes ) - del( n ); -} - -NodeList & NodeList::operator=( const NodeList & other ) -{ - clear(); - foreach ( Node * n, other.list() ) - add( n ); - return *this; -} - -void NodeList::add( Node * n ) -{ - if ( n && ! nodes.contains( n ) ) - { - ++ n->ref; - nodes.append( n ); - } -} - -void NodeList::del( Node * n ) -{ - if ( nodes.contains( n ) ) - { - int cnt = nodes.removeAll( n ); - - if ( n->ref <= cnt ) - { - delete n; - } - else - n->ref -= cnt; - } -} - -Node * NodeList::get( const QModelIndex & index ) const -{ - foreach ( Node * n, nodes ) - { - if ( n->index().isValid() && n->index() == index ) - return n; - } - return 0; -} - -void NodeList::validate() -{ - QList rem; - foreach ( Node * n, nodes ) - { - if ( ! n->isValid() ) - rem.append( n ); - } - foreach ( Node * n, rem ) - { - del( n ); - } -} - -bool compareNodes( const Node * node1, const Node * node2 ) -{ - // opaque meshes first (sorted from front to rear) - // then alpha enabled meshes (sorted from rear to front) - bool a1 = node1->findProperty(); - bool a2 = node2->findProperty(); - - if ( a1 == a2 ) - if ( a1 ) - return ( node1->center()[2] < node2->center()[2] ); - else - return ( node1->center()[2] > node2->center()[2] ); - else - return a2; -} - -void NodeList::sort() -{ - qStableSort( nodes.begin(), nodes.end(), compareNodes ); -} - - -/* - * Node - */ - - -Node::Node( Scene * s, const QModelIndex & index ) : Controllable( s, index ), parent( 0 ), ref( 0 ) -{ - nodeId = 0; - flags.bits = 0; -} - -void Node::clear() -{ - Controllable::clear(); - - nodeId = 0; - flags.bits = 0; - local = Transform(); - - children.clear(); - properties.clear(); -} - -Controller * Node::findController( const QString & proptype, const QString & ctrltype, const QString & var1, const QString & var2 ) -{ - if ( proptype != "" && ! proptype.isEmpty() ) - { - foreach ( Property * prp, properties.list() ) - { - if ( prp->typeId() == proptype ) - { - return prp->findController( ctrltype, var1, var2 ); - } - } - return 0; - } - - return Controllable::findController( ctrltype, var1, var2 ); -} - -void Node::update( const NifModel * nif, const QModelIndex & index ) -{ - Controllable::update( nif, index ); - - if ( ! iBlock.isValid() ) - { - clear(); - return; - } - - nodeId = nif->getBlockNumber( iBlock ); - - if ( iBlock == index ) - { - flags.bits = nif->get( iBlock, "Flags" ); - local = Transform( nif, iBlock ); - } - - if ( iBlock == index || ! index.isValid() ) - { - PropertyList newProps; - foreach ( qint32 l, nif->getLinkArray( iBlock, "Properties" ) ) - if ( Property * p = scene->getProperty( nif, nif->getBlock( l ) ) ) - newProps.add( p ); - properties = newProps; - - children.clear(); - QModelIndex iChildren = nif->getIndex( iBlock, "Children" ); - QList lChildren = nif->getChildLinks( nif->getBlockNumber( iBlock ) ); - if ( iChildren.isValid() ) - { - for ( int c = 0; c < nif->rowCount( iChildren ); c++ ) - { - qint32 link = nif->getLink( iChildren.child( c, 0 ) ); - if ( lChildren.contains( link ) ) - { - QModelIndex iChild = nif->getBlock( link ); - Node * node = scene->getNode( nif, iChild ); - if ( node ) - { - node->makeParent( this ); - } - } - } - } - } -} - -void Node::makeParent( Node * newParent ) -{ - if ( parent ) - parent->children.del( this ); - parent = newParent; - if ( parent ) - parent->children.add( this ); -} - -void Node::setController( const NifModel * nif, const QModelIndex & iController ) -{ - QString cname = nif->itemName( iController ); - if ( cname == "NiTransformController" ) - { - Controller * ctrl = new TransformController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } - else if ( cname == "NiMultiTargetTransformController" ) - { - Controller * ctrl = new MultiTargetTransformController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } - else if ( cname == "NiControllerManager" ) - { - Controller * ctrl = new ControllerManager( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } - else if ( cname == "NiKeyframeController" ) - { - Controller * ctrl = new KeyframeController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } - else if ( cname == "NiVisController" ) - { - Controller * ctrl = new VisibilityController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } -} - -void Node::activeProperties( PropertyList & list ) const -{ - list.merge( properties ); - if ( parent ) - parent->activeProperties( list ); -} - -const Transform & Node::viewTrans() const -{ - if ( scene->viewTrans.contains( nodeId ) ) - return scene->viewTrans[ nodeId ]; - - Transform t; - if ( parent ) - t = parent->viewTrans() * local; - else - t = scene->view * worldTrans(); - - scene->viewTrans.insert( nodeId, t ); - return scene->viewTrans[ nodeId ]; -} - -const Transform & Node::worldTrans() const -{ - if ( scene->worldTrans.contains( nodeId ) ) - return scene->worldTrans[ nodeId ]; - - Transform t = local; - if ( parent ) - t = parent->worldTrans() * t; - - scene->worldTrans.insert( nodeId, t ); - return scene->worldTrans[ nodeId ]; -} - -Transform Node::localTransFrom( int root ) const -{ - Transform trans; - const Node * node = this; - while ( node && node->nodeId != root ) - { - trans = node->local * trans; - node = node->parent; - } - return trans; -} - -Vector3 Node::center() const -{ - return worldTrans().translation; -} - -Node * Node::findParent( int id ) const -{ - Node * node = parent; - while ( node && node->nodeId != id ) - node = node->parent; - return node; -} - -Node * Node::findChild( int id ) const -{ - foreach ( Node * child, children.list() ) - { - if ( child->nodeId == id ) - return child; - child = child->findChild( id ); - if ( child ) - return child; - } - return 0; -} - -Node * Node::findChild( const QString & name ) const -{ - if ( this->name == name ) - return const_cast( this ); - - foreach ( Node * child, children.list() ) - { - Node * n = child->findChild( name ); - if ( n ) - return n; - } - return 0; -} - -bool Node::isHidden() const -{ - if ( Options::drawHidden() ) - return false; - - if ( flags.node.hidden || ( parent && parent->isHidden() ) ) - return true; - - return ! Options::cullExpression().isEmpty() && name.contains( Options::cullExpression() ); -} - -void Node::transform() -{ - Controllable::transform(); - - // if there's a rigid body attached, then calculate and cache the body's transform - // (need this later in the drawing stage for the constraints) - const NifModel * nif = static_cast( iBlock.model() ); - if ( iBlock.isValid() && nif ) - { - QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Data" ) ); - if ( ! iObject.isValid() ) - iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); - if ( iObject.isValid() ) - { - QModelIndex iBody = nif->getBlock( nif->getLink( iObject, "Body" ) ); - if ( iBody.isValid() ) - { - Transform t; - t.scale = 7; - if ( nif->isNiBlock( iBody, "bhkRigidBodyT" ) ) - { - t.rotation.fromQuat( nif->get( iBody, "Rotation" ) ); - t.translation = nif->get( iBody, "Translation" ) * 7; - } - scene->bhkBodyTrans.insert( nif->getBlockNumber( iBody ), worldTrans() * t ); - } - } - } - - foreach ( Node * node, children.list() ) - node->transform(); -} - -void Node::transformShapes() -{ - foreach ( Node * node, children.list() ) - node->transformShapes(); -} - -void Node::draw() -{ - if ( isHidden() ) - return; - - glLoadName( nodeId ); - - glEnable( GL_DEPTH_TEST ); - glDepthFunc( GL_LEQUAL ); - glDepthMask( GL_TRUE ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_NORMALIZE ); - glDisable( GL_LIGHTING ); - glDisable( GL_COLOR_MATERIAL ); - glEnable( GL_BLEND ); - glDisable( GL_ALPHA_TEST ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - - glNormalColor(); - - glPointSize( 8.5 ); - glLineWidth( 2.5 ); - - Vector3 a = viewTrans().translation; - Vector3 b = a; - if ( parent ) - b = parent->viewTrans().translation; - - glBegin( GL_POINTS ); - glVertex( a ); - glEnd(); - - glBegin( GL_LINES ); - glVertex( a ); - glVertex( b ); - glEnd(); - - foreach ( Node * node, children.list() ) - node->draw(); -} - -void Node::drawSelection() const -{ - if ( scene->currentBlock != iBlock || ! Options::drawNodes() ) - return; - - glLoadName( nodeId ); - - glEnable( GL_DEPTH_TEST ); - glDepthFunc( GL_ALWAYS ); - glDepthMask( GL_TRUE ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_NORMALIZE ); - glDisable( GL_LIGHTING ); - glDisable( GL_COLOR_MATERIAL ); - glEnable( GL_BLEND ); - glDisable( GL_ALPHA_TEST ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - - glHighlightColor(); - - glPointSize( 8.5 ); - glLineWidth( 2.5 ); - - Vector3 a = viewTrans().translation; - Vector3 b = a; - if ( parent ) - b = parent->viewTrans().translation; - - glBegin( GL_POINTS ); - glVertex( a ); - glEnd(); - - glBegin( GL_LINES ); - glVertex( a ); - glVertex( b ); - glEnd(); -} - -void DrawVertexSelection( QVector &verts, int i ) -{ - glPointSize( 3.5 ); - glDepthFunc( GL_LEQUAL ); - glNormalColor(); - glBegin( GL_POINTS ); - for ( int j = 0; j < verts.count(); j++ ) - glVertex( verts.value( j ) ); - glEnd(); - - if ( i >= 0 ) - { - glDepthFunc( GL_ALWAYS ); - glHighlightColor(); - glBegin( GL_POINTS ); - glVertex( verts.value( i ) ); - glEnd(); - } -} - -void DrawTriangleSelection( QVector const &verts, Triangle const &tri ) -{ - glLineWidth( 1.5f ); - glDepthFunc( GL_ALWAYS ); - glHighlightColor(); - glBegin( GL_LINE_STRIP ); - glVertex( verts.value( tri.v1() ) ); - glVertex( verts.value( tri.v2() ) ); - glVertex( verts.value( tri.v3() ) ); - glVertex( verts.value( tri.v1() ) ); - glEnd(); -} - -void DrawTriangleIndex( QVector const &verts, Triangle const &tri, int index) -{ - Vector3 c = ( verts.value( tri.v1() ) + verts.value( tri.v2() ) + verts.value( tri.v3() ) ) / 3.0; - renderText(c, QString("%1").arg(index)); -} - -void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStack & stack, const Scene * scene ) -{ - if ( ! nif || ! iShape.isValid() || stack.contains( iShape ) ) - return; - - stack.push( iShape ); - - //qWarning() << "draw shape" << nif->getBlockNumber( iShape ) << nif->itemName( iShape ); - - QString name = nif->itemName( iShape ); - if ( name == "bhkListShape" ) - { - QModelIndex iShapes = nif->getIndex( iShape, "Sub Shapes" ); - if ( iShapes.isValid() ) - { - for ( int r = 0; r < nif->rowCount( iShapes ); r++ ) - { - drawHvkShape( nif, nif->getBlock( nif->getLink( iShapes.child( r, 0 ) ) ), stack, scene ); - } - } - } - else if ( name == "bhkTransformShape" || name == "bhkConvexTransformShape" ) - { - glPushMatrix(); - Matrix4 tm = nif->get( iShape, "Transform" ); - glMultMatrix( tm ); - drawHvkShape( nif, nif->getBlock( nif->getLink( iShape, "Shape" ) ), stack, scene ); - glPopMatrix(); - } - else if ( name == "bhkSphereShape" ) - { - glLoadName( nif->getBlockNumber( iShape ) ); - drawSphere( Vector3(), nif->get( iShape, "Radius" ) ); - } - else if ( name == "bhkMultiSphereShape" ) - { - glLoadName( nif->getBlockNumber( iShape ) ); - QModelIndex iSpheres = nif->getIndex( iShape, "Spheres" ); - for ( int r = 0; r < nif->rowCount( iSpheres ); r++ ) - { - drawSphere( nif->get( iSpheres.child( r, 0 ), "Center" ), nif->get( iSpheres.child( r, 0 ), "Radius" ) ); - } - } - else if ( name == "bhkBoxShape" ) - { - glLoadName( nif->getBlockNumber( iShape ) ); - Vector3 v = nif->get( iShape, "Dimensions" ); - drawBox( v, - v ); - } - else if ( name == "bhkCapsuleShape" ) - { - glLoadName( nif->getBlockNumber( iShape ) ); - drawCapsule( nif->get( iShape, "First Point" ), nif->get( iShape, "Second Point" ), nif->get( iShape, "Radius" ) ); - } - else if ( name == "bhkNiTriStripsShape" ) - { - glPushMatrix(); - float s = 1.0f / 7.0f; - glScalef( s, s, s ); - - glLoadName( nif->getBlockNumber( iShape ) ); - - QModelIndex iStrips = nif->getIndex( iShape, "Strips Data" ); - for ( int r = 0; r < nif->rowCount( iStrips ); r++ ) - { - QModelIndex iStripData = nif->getBlock( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ); - if ( iStripData.isValid() ) - { - QVector verts = nif->getArray( iStripData, "Vertices" ); - - glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - glBegin( GL_TRIANGLES ); - - QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - { // draw the strips like they appear in the tescs - // (use the unstich strips spell to avoid the spider web effect) - QVector strip = nif->getArray( iPoints.child( r, 0 ) ); - if ( strip.count() >= 3 ) - { - quint16 a = strip[0]; - quint16 b = strip[1]; - - for ( int x = 2; x < strip.size(); x++ ) - { - quint16 c = strip[x]; - glVertex( verts.value( a ) ); - glVertex( verts.value( b ) ); - glVertex( verts.value( c ) ); - a = b; - b = c; - } - } - } - - glEnd(); - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - } - } - glPopMatrix(); - } - else if ( name == "bhkConvexVerticesShape" ) - { - glLoadName( nif->getBlockNumber( iShape ) ); - drawConvexHull( nif->getArray( iShape, "Vertices" ), nif->getArray( iShape, "Normals" ) ); - } - else if ( name == "bhkMoppBvTreeShape" ) - { - drawHvkShape( nif, nif->getBlock( nif->getLink( iShape, "Shape" ) ), stack, scene ); - } - else if ( name == "bhkPackedNiTriStripsShape" ) - { - glLoadName( nif->getBlockNumber( iShape ) ); - - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); - if ( iData.isValid() ) - { - QVector verts = nif->getArray( iData, "Vertices" ); - QModelIndex iTris = nif->getIndex( iData, "Triangles" ); - for ( int t = 0; t < nif->rowCount( iTris ); t++ ) - { - Triangle tri = nif->get( iTris.child( t, 0 ), "Triangle" ); - if ( tri[0] != tri[1] || tri[1] != tri[2] || tri[2] != tri[0] ) - { - glBegin( GL_LINE_STRIP ); - glVertex( verts.value( tri[0] ) ); - glVertex( verts.value( tri[1] ) ); - glVertex( verts.value( tri[2] ) ); - glVertex( verts.value( tri[0] ) ); - glEnd(); - } - } - - // Handle Selection - if (scene->currentBlock == iData ) - { - int i = -1; - QString n = scene->currentIndex.data( Qt::DisplayRole ).toString(); - QModelIndex iParent = scene->currentIndex.parent(); - if ( iParent.isValid() && iParent != iData ) - { - n = iParent.data( Qt::DisplayRole ).toString(); - i = scene->currentIndex.row(); - } - if ( n == "Vertices" || n == "Normals" || n == "Vertex Colors" || n == "UV Sets" ) - DrawVertexSelection(verts, i); - else if ( ( n == "Faces" || n == "Triangles" ) ) - { - if ( i == -1 ) - { - glDepthFunc( GL_ALWAYS ); - glHighlightColor(); - for ( int t = 0; t < nif->rowCount( iTris ); t++ ) - DrawTriangleIndex(verts, nif->get( iTris.child( t, 0 ), "Triangle" ), t); - } - else - { - Triangle tri = nif->get( iTris.child( i, 0 ), "Triangle" ); - DrawTriangleSelection(verts, tri ); - DrawTriangleIndex(verts, tri, i); - } - } - } - } - } - - stack.pop(); -} - -void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, const Scene * scene ) -{ - if ( ! ( nif && iConstraint.isValid() && scene && Options::drawConstraints() ) ) - return; - - QList tBodies; - QModelIndex iBodies = nif->getIndex( iConstraint, "Entities" ); - if( !iBodies.isValid() ) { - return; - } - - for ( int r = 0; r < nif->rowCount( iBodies ); r++ ) - { - qint32 l = nif->getLink( iBodies.child( r, 0 ) ); - if ( ! scene->bhkBodyTrans.contains( l ) ) - return; - else - tBodies.append( scene->bhkBodyTrans.value( l ) ); - } - - if ( tBodies.count() != 2 ) - return; - - glLoadName( nif->getBlockNumber( iConstraint ) ); - - glPushMatrix(); - glLoadMatrix( scene->view ); - - glPushAttrib( GL_ENABLE_BIT ); - glEnable( GL_DEPTH_TEST ); - - QString name = nif->itemName( iConstraint ); - if ( name == "bhkMalleableConstraint" ) - { - if ( nif->getIndex( iConstraint, "Ragdoll" ).isValid() ) - { - name = "bhkRagdollConstraint"; - } - else if ( nif->getIndex( iConstraint, "Limited Hinge" ).isValid() ) - { - name = "bhkLimitedHingeConstraint"; - } - } - - if ( name == "bhkLimitedHingeConstraint" ) - { - QModelIndex iHinge = nif->getIndex( iConstraint, "Limited Hinge" ); - - const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); - const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); - - const Vector3 axleA( nif->get( iHinge, "Axle A" ) ); - const Vector3 axleA1( nif->get( iHinge, "Perp2AxleInA1" ) ); - const Vector3 axleA2( nif->get( iHinge, "Perp2AxleInA2" ) ); - - const Vector3 axleB( nif->get( iHinge, "Axle B" ) ); - const Vector3 axleB2( nif->get( iHinge, "Perp2AxleInB2" ) ); - - const float minAngle = nif->get( iHinge, "Min Angle" ); - const float maxAngle = nif->get( iHinge, "Max Angle" ); - - glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); - glColor( Color3( 0.8f, 0.6f, 0.0f ) ); - glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); - glBegin( GL_LINES ); glVertex( pivotA ); glVertex( pivotA + axleA ); glEnd(); - drawDashLine( pivotA, pivotA + axleA1, 14 ); - drawDashLine( pivotA, pivotA + axleA2, 14 ); - drawCircle( pivotA, axleA, 1.0f ); - drawSolidArc( pivotA, axleA / 5, axleA2, axleA1, minAngle, maxAngle, 1.0f ); - glPopMatrix(); - - glPushMatrix(); - glMultMatrix( tBodies.value( 1 ) ); - glColor( Color3( 0.6f, 0.8f, 0.0f ) ); - glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); - glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axleB ); glEnd(); - drawDashLine( pivotB + axleB2, pivotB, 14 ); - drawDashLine( pivotB + Vector3::crossproduct( axleB2, axleB ), pivotB, 14 ); - drawCircle( pivotB, axleB, 1.01f ); - drawSolidArc( pivotB, axleB / 7, axleB2, Vector3::crossproduct( axleB2, axleB ), minAngle, maxAngle, 1.01f ); - glPopMatrix(); - - glMultMatrix( tBodies.value( 0 ) ); - float angle = Vector3::angle( tBodies.value( 0 ).rotation * axleA2, tBodies.value( 1 ).rotation * axleB2 ); - glColor( Color3( 0.8f, 0.6f, 0.0f ) ); - glBegin( GL_LINES ); - glVertex( pivotA ); - glVertex( pivotA + axleA1 * cosf( angle ) + axleA2 * sinf( angle ) ); - glEnd(); - } - else if ( name == "bhkHingeConstraint" ) - { - QModelIndex iHinge = iConstraint; - - const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); - const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); - - const Vector3 axleA1( nif->get( iHinge, "Perp2AxleInA1" ) ); - const Vector3 axleA2( nif->get( iHinge, "Perp2AxleInA2" ) ); - const Vector3 axleA( Vector3::crossproduct( axleA1, axleA2 ) ); - - const Vector3 axleB( nif->get( iHinge, "Axle B" ) ); - const Vector3 axleB1( axleB[1], axleB[2], axleB[0] ); - const Vector3 axleB2( Vector3::crossproduct( axleB, axleB1 ) ); - - const float minAngle = - PI; - const float maxAngle = + PI; - - glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); - glColor( Color3( 0.8f, 0.6f, 0.0f ) ); - glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); - drawDashLine( pivotA, pivotA + axleA1 ); - drawDashLine( pivotA, pivotA + axleA2 ); - drawSolidArc( pivotA, axleA / 5, axleA2, axleA1, minAngle, maxAngle, 1.0f, 16 ); - glPopMatrix(); - - glMultMatrix( tBodies.value( 1 ) ); - glColor( Color3( 0.6f, 0.8f, 0.0f ) ); - glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); - glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axleB ); glEnd(); - drawSolidArc( pivotB, axleB / 7, axleB2, axleB1, minAngle, maxAngle, 1.01f, 16 ); - } - else if ( name == "bhkStiffSpringConstraint" ) - { - const Vector3 pivotA = tBodies.value( 0 ) * Vector3( nif->get( iConstraint, "Pivot A" ) ); - const Vector3 pivotB = tBodies.value( 1 ) * Vector3( nif->get( iConstraint, "Pivot B" ) ); - const float length = nif->get( iConstraint, "Length" ); - - glColor( Color3( 0.6f, 0.8f, 0.0f ) ); - - drawSpring( pivotA, pivotB, length ); - } - else if ( name == "bhkRagdollConstraint" ) - { - QModelIndex iRagdoll = nif->getIndex( iConstraint, "Ragdoll" ); - - const Vector3 pivotA( nif->get( iRagdoll, "Pivot A" ) ); - const Vector3 pivotB( nif->get( iRagdoll, "Pivot B" ) ); - - const Vector3 planeA( nif->get( iRagdoll, "Plane A" ) ); - const Vector3 planeB( nif->get( iRagdoll, "Plane B" ) ); - - const Vector3 twistA( nif->get( iRagdoll, "Twist A" ) ); - const Vector3 twistB( nif->get( iRagdoll, "Twist B" ) ); - - const float coneAngle( nif->get( iRagdoll, "Cone Min Angle" ) ); - - const float minPlaneAngle( nif->get( iRagdoll, "Plane Min Angle" ) ); - const float maxPlaneAngle( nif->get( iRagdoll, "Plane Max Angle" ) ); - - const float minTwistAngle( nif->get( iRagdoll, "Twist Min Angle" ) ); - const float maxTwistAngle( nif->get( iRagdoll, "Twist Max Angle" ) ); - - glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); - glColor( Color3( 0.8f, 0.6f, 0.0f ) ); - glPopMatrix(); - - glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); - glColor( Color3( 0.8f, 0.6f, 0.0f ) ); - glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); - glBegin( GL_LINES ); glVertex( pivotA ); glVertex( pivotA + twistA ); glEnd(); - drawDashLine( pivotA, pivotA + planeA, 14 ); - drawRagdollCone( pivotA, twistA, planeA, coneAngle, minPlaneAngle, maxPlaneAngle ); - glPopMatrix(); - - glPushMatrix(); - glMultMatrix( tBodies.value( 1 ) ); - glColor( Color3( 0.6f, 0.8f, 0.0f ) ); - glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); - glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + twistB ); glEnd(); - drawDashLine( pivotB + planeB, pivotB, 14 ); - drawRagdollCone( pivotB, twistB, planeB, coneAngle, minPlaneAngle, maxPlaneAngle ); - glPopMatrix(); - } - else if ( name == "bhkPrismaticConstraint" ) - { - const Vector3 pivotA( nif->get( iConstraint, "Pivot A" ) ); - const Vector3 pivotB( nif->get( iConstraint, "Pivot B" ) ); - - const Vector3 planeNormal( nif->get( iConstraint, "Plane" ) ); - const Vector3 slidingAxis( nif->get( iConstraint, "Sliding Axis" ) ); - - const float minDistance = nif->get( iConstraint, "Min Distance" ); - const float maxDistance = nif->get( iConstraint, "Max Distance" ); - - const Vector3 d1 = pivotA + slidingAxis * minDistance; - const Vector3 d2 = pivotA + slidingAxis * maxDistance; - - /* draw Pivot A and Plane */ - glPushMatrix(); - glMultMatrix( tBodies.value( 0 ) ); - glColor( Color3( 0.8f, 0.6f, 0.0f ) ); - glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); - glBegin( GL_LINES ); glVertex( pivotA ); glVertex( pivotA + planeNormal ); glEnd(); - drawDashLine( pivotA, d1, 14 ); - - /* draw rail */ - if ( minDistance < maxDistance ) { - drawRail( d1, d2 ); - } - - /*draw first marker*/ - Transform t; - float angle = atan2f( slidingAxis[1], slidingAxis[0] ); - if ( slidingAxis[0] < 0.0001f && slidingAxis[1] < 0.0001f ) { - angle = PI / 2; - } - - t.translation = d1; - t.rotation.fromEuler( 0.0f, 0.0f, angle ); - glMultMatrix( t ); - - angle = - asinf( slidingAxis[2] / slidingAxis.length() ); - t.translation = Vector3( 0.0f, 0.0f, 0.0f ); - t.rotation.fromEuler( 0.0f, angle, 0.0f ); - glMultMatrix( t ); - - drawMarker( &BumperMarker01 ); - - /*draw second marker*/ - t.translation = Vector3( minDistance < maxDistance ? ( d2 - d1 ).length() : 0.0f, 0.0f, 0.0f ); - t.rotation.fromEuler( 0.0f, 0.0f, PI ); - glMultMatrix( t ); - - drawMarker( &BumperMarker01 ); - glPopMatrix(); - - /* draw Pivot B */ - glPushMatrix(); - glMultMatrix( tBodies.value( 1 ) ); - glColor( Color3( 0.6f, 0.8f, 0.0f ) ); - glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); - glPopMatrix(); - } - - glPopAttrib(); - glPopMatrix(); -} - -void Node::drawHavok() -{ - foreach ( Node * node, children.list() ) - node->drawHavok(); - - const NifModel * nif = static_cast( iBlock.model() ); - if ( ! ( iBlock.isValid() && nif ) ) - return; - - //Check if there's any old style collision bounding box set - if ( nif->get( iBlock, "Has Bounding Box" ) == true ) - { - QModelIndex iBox = nif->getIndex( iBlock, "Bounding Box" ); - - Transform bt; - - bt.translation = nif->get( iBox, "Translation" ); - bt.rotation = nif->get( iBox, "Rotation" ); - bt.scale = 1.0f; - - Vector3 rad = nif->get( iBox, "Radius" ); - - glPushMatrix(); - glLoadMatrix( scene->view ); - glMultMatrix( worldTrans() ); - glMultMatrix( bt ); - - glColor( Color3( 1.0f, 0.0f, 0.0f ) ); - glLineWidth( 1.0f ); - glDisable( GL_LIGHTING ); - drawBox( rad, - rad ); - - glPopMatrix(); - } - - QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Data" ) ); - if ( ! iObject.isValid() ) - iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); - if ( ! iObject.isValid() ) - return; - - QModelIndex iBody = nif->getBlock( nif->getLink( iObject, "Body" ) ); - - glPushMatrix(); - glLoadMatrix( scene->view ); - glMultMatrix( scene->bhkBodyTrans.value( nif->getBlockNumber( iBody ) ) ); - - - //qWarning() << "draw obj" << nif->getBlockNumber( iObject ) << nif->itemName( iObject ); - - glEnable( GL_DEPTH_TEST ); - glDepthMask( GL_TRUE ); - glDepthFunc( GL_LEQUAL ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_NORMALIZE ); - glDisable( GL_LIGHTING ); - glDisable( GL_COLOR_MATERIAL ); - glEnable( GL_BLEND ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - glDisable( GL_ALPHA_TEST ); - glPointSize( 4.5 ); - glLineWidth( 1.0 ); - - static const float colors[8][3] = { - { 0.0f, 1.0f, 0.0f }, - { 1.0f, 0.0f, 0.0f }, - { 1.0f, 0.0f, 1.0f }, - { 1.0f, 1.0f, 1.0f }, - { 0.5f, 0.5f, 1.0f }, - { 1.0f, 0.8f, 0.0f }, - { 1.0f, 0.8f, 0.4f }, - { 0.0f, 1.0f, 1.0f } - }; - - glColor3fv( colors[ nif->get( iBody, "Layer" ) & 7 ] ); - - QStack shapeStack; - drawHvkShape( nif, nif->getBlock( nif->getLink( iBody, "Shape" ) ), shapeStack, scene ); - - glLoadName( nif->getBlockNumber( iBody ) ); - drawAxes( nif->get( iBody, "Center" ), 0.2f ); - - glPopMatrix(); - - foreach ( qint32 l, nif->getLinkArray( iBody, "Constraints" ) ) - { - QModelIndex iConstraint = nif->getBlock( l ); - if ( nif->inherits( iConstraint, "bhkConstraint" ) ) - drawHvkConstraint( nif, iConstraint, scene ); - } -} - -void drawFurnitureMarker( const NifModel *nif, const QModelIndex &iPosition ) -{ - QString name = nif->itemName( iPosition ); - Vector3 offs = nif->get( iPosition, "Offset" ); - quint16 orient = nif->get( iPosition, "Orientation" ); - quint8 ref1 = nif->get( iPosition, "Position Ref 1" ); - quint8 ref2 = nif->get( iPosition, "Position Ref 2" ); - - if ( ref1 != ref2 ) - { - qDebug() << "Position Ref 1 and 2 are not equal!"; - return; - } - - Vector3 flip(1, 1, 1); - const GLMarker *mark; - switch ( ref1 ) - { - case 1: - mark = &FurnitureMarker01; - break; - - case 2: - flip[0] = -1; - mark = &FurnitureMarker01; - break; - - case 3: - mark = &FurnitureMarker03; - break; - - case 4: - mark = &FurnitureMarker04; - break; - - case 11: - mark = &FurnitureMarker11; - break; - - case 12: - flip[0] = -1; - mark = &FurnitureMarker11; - break; - - case 13: - mark = &FurnitureMarker13; - break; - - case 14: - mark = &FurnitureMarker14; - break; - - default: - qDebug() << "Unknown furniture marker " << ref1 << "!"; - return; - } - - float roll = float( orient ) / 6284.0 * 2.0 * (-M_PI); - - glLoadName( ( nif->getBlockNumber( iPosition ) & 0xffff ) | ( ( iPosition.row() & 0xffff ) << 16 ) ); - - glPushMatrix(); - - Transform t; - t.rotation.fromEuler( 0, 0, roll ); - t.translation = offs; - glMultMatrix( t ); - - glScale(flip); - - drawMarker( mark ); - - glPopMatrix(); -} - -void Node::drawFurn() -{ - foreach ( Node * node, children.list() ) - node->drawFurn(); - - const NifModel * nif = static_cast( iBlock.model() ); - if ( ! ( iBlock.isValid() && nif ) ) - return; - - QModelIndex iExtraDataList = nif->getIndex( iBlock, "Extra Data List" ); - - if ( !iExtraDataList.isValid() ) - return; - - glEnable( GL_DEPTH_TEST ); - glDepthMask( GL_FALSE ); - glDepthFunc( GL_LEQUAL ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_NORMALIZE ); - glDisable( GL_LIGHTING ); - glDisable( GL_COLOR_MATERIAL ); - glDisable( GL_CULL_FACE ); - glDisable( GL_BLEND ); - glDisable( GL_ALPHA_TEST ); - glColor4f( 1, 1, 1, 1 ); - - glLineWidth( 1.0 ); - glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - - glPushMatrix(); - - glMultMatrix( viewTrans() ); - - for ( int p = 0; p < nif->rowCount( iExtraDataList ); p++ ) - { - QModelIndex iFurnMark = nif->getBlock( nif->getLink( iExtraDataList.child( p, 0 ) ), "BSFurnitureMarker" ); - if ( ! iFurnMark.isValid() ) - continue; - - QModelIndex iPositions = nif->getIndex( iFurnMark, "Positions" ); - if ( !iPositions.isValid() ) - break; - - for ( int j = 0; j < nif->rowCount( iPositions ); j++ ) - { - QModelIndex iPosition = iPositions.child( j, 0 ); - - if ( scene->currentIndex == iPosition ) - glHighlightColor(); - else - glNormalColor(); - - drawFurnitureMarker( nif, iPosition ); - } - } - - glPopMatrix(); -} - -void Node::drawShapes( NodeList * draw2nd ) -{ - if ( isHidden() ) - return; - - foreach ( Node * node, children.list() ) - { - node->drawShapes( draw2nd ); - } -} - -#define Farg( X ) arg( X, 0, 'f', 5 ) - -QString trans2string( Transform t ) -{ - float xr, yr, zr; - t.rotation.toEuler( xr, yr, zr ); - return QString( "translation X %1, Y %2, Z %3\n" ).Farg( t.translation[0] ).Farg( t.translation[1] ).Farg( t.translation[2] ) - + QString( "rotation Y %1, P %2, R %3 " ).Farg( xr * 180 / PI ).Farg( yr * 180 / PI ).Farg( zr * 180 / PI ) - + QString( "( (%1, %2, %3), " ).Farg( t.rotation( 0, 0 ) ).Farg( t.rotation( 0, 1 ) ).Farg( t.rotation( 0, 2 ) ) - + QString( "(%1, %2, %3), " ).Farg( t.rotation( 1, 0 ) ).Farg( t.rotation( 1, 1 ) ).Farg( t.rotation( 1, 2 ) ) - + QString( "(%1, %2, %3) )\n" ).Farg( t.rotation( 2, 0 ) ).Farg( t.rotation( 2, 1 ) ).Farg( t.rotation( 2, 2 ) ) - + QString( "scale %1\n" ).Farg( t.scale ); -} - -QString Node::textStats() const -{ - return QString( "%1\n\nglobal\n%2\nlocal\n%3\n" ).arg( name ).arg( trans2string( worldTrans() ) ).arg( trans2string( localTrans() ) ); -} - -BoundSphere Node::bounds() const -{ - if ( Options::drawNodes() ) - return BoundSphere( worldTrans().translation, 0 ); - else - return BoundSphere(); -} - - -LODNode::LODNode( Scene * scene, const QModelIndex & iBlock ) - : Node( scene, iBlock ) -{ -} - -void LODNode::clear() -{ - Node::clear(); - ranges.clear(); -} - -void LODNode::update( const NifModel * nif, const QModelIndex & index ) -{ - Node::update( nif, index ); - - if ( ( iBlock.isValid() && index == iBlock ) || ( iData.isValid() && index == iData ) ) - { - ranges.clear(); - iData = nif->getBlock( nif->getLink( iBlock, "LOD Level Data" ), "NiRangeLODData" ); - QModelIndex iLevels; - if ( iData.isValid() ) - { - center = nif->get( iData, "LOD Center" ); - iLevels = nif->getIndex( iData, "LOD Levels" ); - } - else - { - center = nif->get( iBlock, "LOD Center" ); - iLevels = nif->getIndex( iBlock, "LOD Levels" ); - } - if ( iLevels.isValid() ) - for ( int r = 0; r < nif->rowCount( iLevels ); r++ ) - ranges.append( qMakePair( nif->get( iLevels.child( r, 0 ), "Near Extent" ), nif->get( iLevels.child( r, 0 ), "Far Extent" ) ) ); - } -} - -void LODNode::transform() -{ - Node::transform(); - - if ( children.list().isEmpty() ) - return; - - if ( ranges.isEmpty() ) - { - foreach ( Node * child, children.list() ) - child->flags.node.hidden = true; - children.list().first()->flags.node.hidden = false; - return; - } - - float distance = ( viewTrans() * center ).length(); - - int c = 0; - foreach ( Node * child, children.list() ) - { - if ( c < ranges.count() ) - child->flags.node.hidden = ! ( ranges[c].first <= distance && distance < ranges[c].second ); - else - child->flags.node.hidden = true; - c++; - } -} - - -BillboardNode::BillboardNode( Scene * scene, const QModelIndex & iBlock ) - : Node( scene, iBlock ) -{ -} - -const Transform & BillboardNode::viewTrans() const -{ - if ( scene->viewTrans.contains( nodeId ) ) - return scene->viewTrans[ nodeId ]; - - Transform t; - if ( parent ) - t = parent->viewTrans() * local; - else - t = scene->view * worldTrans(); - - t.rotation = Matrix(); - - scene->viewTrans.insert( nodeId, t ); - return scene->viewTrans[ nodeId ]; -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "glmarker.h" +#include "glnode.h" +#include "glcontroller.h" +#include "glscene.h" +#include "options.h" + +#include "NvTriStrip/qtwrapper.h" + +#include "marker/furniture.h" +#include "marker/constraints.h" + +#ifndef M_PI +#define M_PI 3.1415926535897932385 +#endif + +class TransformController : public Controller +{ +public: + TransformController( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ){} + + void update( float time ) + { + if ( ! ( active && target ) ) + return; + + time = ctrlTime( time ); + + if ( interpolator ) + { + interpolator->updateTransform(target->local, time); + } + } + + void setInterpolator( const QModelIndex & iBlock ) + { + const NifModel * nif = static_cast( iBlock.model() ); + if ( nif && iBlock.isValid() ) + { + if ( interpolator ) + { + delete interpolator; + interpolator = 0; + } + + if ( nif->isNiBlock( iBlock, "NiBSplineCompTransformInterpolator" ) ) + { + iInterpolator = iBlock; + interpolator = new BSplineTransformInterpolator(this); + } + else if ( nif->isNiBlock( iBlock, "NiTransformInterpolator" ) ) + { + iInterpolator = iBlock; + interpolator = new TransformInterpolator(this); + } + + if ( interpolator ) + { + interpolator->update( nif, iInterpolator ); + } + } + } + +protected: + QPointer target; + QPointer interpolator; +}; + +class MultiTargetTransformController : public Controller +{ + typedef QPair< QPointer, QPointer > TransformTarget; + +public: + MultiTargetTransformController( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ) {} + + void update( float time ) + { + if ( ! ( active && target ) ) + return; + + time = ctrlTime( time ); + + foreach ( TransformTarget tt, extraTargets ) + { + if ( tt.first && tt.second ) + { + tt.second->updateTransform( tt.first->local, time ); + } + } + } + + bool update( const NifModel * nif, const QModelIndex & index ) + { + if ( Controller::update( nif, index ) ) + { + if ( target ) + { + Scene * scene = target->scene; + extraTargets.clear(); + + QVector lTargets = nif->getLinkArray( index, "Extra Targets" ); + foreach ( qint32 l, lTargets ) + { + Node * node = scene->getNode( nif, nif->getBlock( l ) ); + if ( node ) + { + extraTargets.append( TransformTarget( node, 0 ) ); + } + } + + } + return true; + } + + foreach ( TransformTarget tt, extraTargets ) + { + // TODO: update the interpolators + } + + return false; + } + + bool setInterpolator( Node * node, const QModelIndex & iInterpolator ) + { + const NifModel * nif = static_cast( iInterpolator.model() ); + if ( ! nif || ! iInterpolator.isValid() ) + return false; + QMutableListIterator it( extraTargets ); + while ( it.hasNext() ) + { + it.next(); + if ( it.value().first == node ) + { + if ( it.value().second ) + { + delete it.value().second; + it.value().second = 0; + } + + if ( nif->isNiBlock( iInterpolator, "NiBSplineCompTransformInterpolator" ) ) + { + it.value().second = new BSplineTransformInterpolator( this ); + } + else if ( nif->isNiBlock( iInterpolator, "NiTransformInterpolator" ) ) + { + it.value().second = new TransformInterpolator( this ); + } + + if ( it.value().second ) + { + it.value().second->update( nif, iInterpolator ); + } + return true; + } + } + return false; + } + +protected: + QPointer target; + QList< TransformTarget > extraTargets; +}; + +class ControllerManager : public Controller +{ +public: + ControllerManager( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ) {} + + void update( float ) {} + + bool update( const NifModel * nif, const QModelIndex & index ) + { + if ( Controller::update( nif, index ) ) + { + if ( target ) + { + Scene * scene = target->scene; + QVector lSequences = nif->getLinkArray( index, "Controller Sequences" ); + foreach ( qint32 l, lSequences ) + { + QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); + if ( iSeq.isValid() ) + { + QString name = nif->get( iSeq, "Name" ); + if ( ! scene->animGroups.contains( name ) ) + { + scene->animGroups.append( name ); + + QMap tags = scene->animTags[ name ]; + + QModelIndex iKeys = nif->getBlock( nif->getLink( iSeq, "Text Keys" ), "NiTextKeyExtraData" ); + QModelIndex iTags = nif->getIndex( iKeys, "Text Keys" ); + for ( int r = 0; r < nif->rowCount( iTags ); r++ ) + { + tags.insert( nif->get( iTags.child( r, 0 ), "Value" ), nif->get( iTags.child( r, 0 ), "Time" ) ); + } + + scene->animTags[ name ] = tags; + } + } + } + } + return true; + } + return false; + } + + void setSequence( const QString & seqname ) + { + const NifModel * nif = static_cast( iBlock.model() ); + if ( target && iBlock.isValid() && nif ) + { + MultiTargetTransformController * multiTargetTransformer = 0; + foreach ( Controller * c, target->controllers ) + { + if ( c->typeId() == "NiMultiTargetTransformController" ) + { + multiTargetTransformer = static_cast( c ); + break; + } + } + + QVector lSequences = nif->getLinkArray( iBlock, "Controller Sequences" ); + foreach ( qint32 l, lSequences ) + { + QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); + if ( iSeq.isValid() && nif->get( iSeq, "Name" ) == seqname ) + { + start = nif->get( iSeq, "Start Time" ); + stop = nif->get( iSeq, "Stop Time" ); + phase = nif->get( iSeq, "Phase" ); + frequency = nif->get( iSeq, "Frequency" ); + + QModelIndex iCtrlBlcks = nif->getIndex( iSeq, "Controlled Blocks" ); + for ( int r = 0; r < nif->rowCount( iCtrlBlcks ); r++ ) + { + QModelIndex iCB = iCtrlBlcks.child( r, 0 ); + + QModelIndex iInterpolator = nif->getBlock( nif->getLink( iCB, "Interpolator" ), "NiInterpolator" ); + + QString nodename = nif->get( iCB, "Node Name" ); + if ( nodename.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Node Name Offset" ); + nodename = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); + } + QString proptype = nif->get( iCB, "Property Type" ); + if ( proptype.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Property Type Offset" ); + proptype = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); + } + QString ctrltype = nif->get( iCB, "Controller Type" ); + if ( ctrltype.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Controller Type Offset" ); + ctrltype = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); + } + QString var1 = nif->get( iCB, "Variable 1" ); + if ( var1.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Variable Offset 1" ); + var1 = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); + } + QString var2 = nif->get( iCB, "Variable 2" ); + if ( var2.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Variable Offset 2" ); + var2 = idx.sibling( idx.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); + } + Node * node = target->findChild( nodename ); + if ( ! node ) + continue; + + if ( ctrltype == "NiTransformController" && multiTargetTransformer ) + { + if ( multiTargetTransformer->setInterpolator( node, iInterpolator ) ) + { + multiTargetTransformer->start = start; + multiTargetTransformer->stop = stop; + multiTargetTransformer->phase = phase; + multiTargetTransformer->frequency = frequency; + continue; + } + } + + Controller * ctrl = node->findController( proptype, ctrltype, var1, var2 ); + if ( ctrl ) + { + ctrl->start = start; + ctrl->stop = stop; + ctrl->phase = phase; + ctrl->frequency = frequency; + + ctrl->setInterpolator( iInterpolator ); + } + } + } + } + } + } + +protected: + QPointer target; +}; + +class KeyframeController : public Controller +{ +public: + KeyframeController( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ), lTrans( 0 ), lRotate( 0 ), lScale( 0 ) {} + + void update( float time ) + { + if ( ! ( active && target ) ) + return; + + time = ctrlTime( time ); + + interpolate( target->local.rotation, iRotations, time, lRotate ); + interpolate( target->local.translation, iTranslations, time, lTrans ); + interpolate( target->local.scale, iScales, time, lScale ); + } + + bool update( const NifModel * nif, const QModelIndex & index ) + { + if ( Controller::update( nif, index ) ) + { + iTranslations = nif->getIndex( iData, "Translations" ); + iRotations = nif->getIndex( iData, "Rotations" ); + if ( ! iRotations.isValid() ) + iRotations = iData; + iScales = nif->getIndex( iData, "Scales" ); + return true; + } + return false; + } + +protected: + QPointer target; + + QPersistentModelIndex iTranslations, iRotations, iScales; + + int lTrans, lRotate, lScale; +}; + +class VisibilityController : public Controller +{ +public: + VisibilityController( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ), visLast( 0 ) {} + + void update( float time ) + { + if ( ! ( active && target ) ) + return; + + time = ctrlTime( time ); + + bool isVisible; + if ( interpolate( isVisible, iKeys, time, visLast ) ) + { + target->flags.node.hidden = ! isVisible; + } + } + + bool update( const NifModel * nif, const QModelIndex & index ) + { + if ( Controller::update( nif, index ) ) + { + iKeys = nif->getIndex( iData, "Data" ); + return true; + } + return false; + } + +protected: + QPointer target; + + QPersistentModelIndex iKeys; + + int visLast; +}; + +/* + * Node list + */ + +NodeList::NodeList() +{ +} + +NodeList::NodeList( const NodeList & other ) +{ + operator=( other ); +} + +NodeList::~NodeList() +{ + clear(); +} + +void NodeList::clear() +{ + foreach( Node * n, nodes ) + del( n ); +} + +NodeList & NodeList::operator=( const NodeList & other ) +{ + clear(); + foreach ( Node * n, other.list() ) + add( n ); + return *this; +} + +void NodeList::add( Node * n ) +{ + if ( n && ! nodes.contains( n ) ) + { + ++ n->ref; + nodes.append( n ); + } +} + +void NodeList::del( Node * n ) +{ + if ( nodes.contains( n ) ) + { + int cnt = nodes.removeAll( n ); + + if ( n->ref <= cnt ) + { + delete n; + } + else + n->ref -= cnt; + } +} + +Node * NodeList::get( const QModelIndex & index ) const +{ + foreach ( Node * n, nodes ) + { + if ( n->index().isValid() && n->index() == index ) + return n; + } + return 0; +} + +void NodeList::validate() +{ + QList rem; + foreach ( Node * n, nodes ) + { + if ( ! n->isValid() ) + rem.append( n ); + } + foreach ( Node * n, rem ) + { + del( n ); + } +} + +bool compareNodes( const Node * node1, const Node * node2 ) +{ + // opaque meshes first (sorted from front to rear) + // then alpha enabled meshes (sorted from rear to front) + bool a1 = node1->findProperty(); + bool a2 = node2->findProperty(); + + if ( a1 == a2 ) + if ( a1 ) + return ( node1->center()[2] < node2->center()[2] ); + else + return ( node1->center()[2] > node2->center()[2] ); + else + return a2; +} + +void NodeList::sort() +{ + qStableSort( nodes.begin(), nodes.end(), compareNodes ); +} + + +/* + * Node + */ + + +Node::Node( Scene * s, const QModelIndex & index ) : Controllable( s, index ), parent( 0 ), ref( 0 ) +{ + nodeId = 0; + flags.bits = 0; +} + +void Node::clear() +{ + Controllable::clear(); + + nodeId = 0; + flags.bits = 0; + local = Transform(); + + children.clear(); + properties.clear(); +} + +Controller * Node::findController( const QString & proptype, const QString & ctrltype, const QString & var1, const QString & var2 ) +{ + if ( proptype != "" && ! proptype.isEmpty() ) + { + foreach ( Property * prp, properties.list() ) + { + if ( prp->typeId() == proptype ) + { + return prp->findController( ctrltype, var1, var2 ); + } + } + return 0; + } + + return Controllable::findController( ctrltype, var1, var2 ); +} + +void Node::update( const NifModel * nif, const QModelIndex & index ) +{ + Controllable::update( nif, index ); + + if ( ! iBlock.isValid() ) + { + clear(); + return; + } + + nodeId = nif->getBlockNumber( iBlock ); + + if ( iBlock == index ) + { + flags.bits = nif->get( iBlock, "Flags" ); + local = Transform( nif, iBlock ); + } + + if ( iBlock == index || ! index.isValid() ) + { + PropertyList newProps; + foreach ( qint32 l, nif->getLinkArray( iBlock, "Properties" ) ) + if ( Property * p = scene->getProperty( nif, nif->getBlock( l ) ) ) + newProps.add( p ); + properties = newProps; + + children.clear(); + QModelIndex iChildren = nif->getIndex( iBlock, "Children" ); + QList lChildren = nif->getChildLinks( nif->getBlockNumber( iBlock ) ); + if ( iChildren.isValid() ) + { + for ( int c = 0; c < nif->rowCount( iChildren ); c++ ) + { + qint32 link = nif->getLink( iChildren.child( c, 0 ) ); + if ( lChildren.contains( link ) ) + { + QModelIndex iChild = nif->getBlock( link ); + Node * node = scene->getNode( nif, iChild ); + if ( node ) + { + node->makeParent( this ); + } + } + } + } + } +} + +void Node::makeParent( Node * newParent ) +{ + if ( parent ) + parent->children.del( this ); + parent = newParent; + if ( parent ) + parent->children.add( this ); +} + +void Node::setController( const NifModel * nif, const QModelIndex & iController ) +{ + QString cname = nif->itemName( iController ); + if ( cname == "NiTransformController" ) + { + Controller * ctrl = new TransformController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } + else if ( cname == "NiMultiTargetTransformController" ) + { + Controller * ctrl = new MultiTargetTransformController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } + else if ( cname == "NiControllerManager" ) + { + Controller * ctrl = new ControllerManager( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } + else if ( cname == "NiKeyframeController" ) + { + Controller * ctrl = new KeyframeController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } + else if ( cname == "NiVisController" ) + { + Controller * ctrl = new VisibilityController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } +} + +void Node::activeProperties( PropertyList & list ) const +{ + list.merge( properties ); + if ( parent ) + parent->activeProperties( list ); +} + +const Transform & Node::viewTrans() const +{ + if ( scene->viewTrans.contains( nodeId ) ) + return scene->viewTrans[ nodeId ]; + + Transform t; + if ( parent ) + t = parent->viewTrans() * local; + else + t = scene->view * worldTrans(); + + scene->viewTrans.insert( nodeId, t ); + return scene->viewTrans[ nodeId ]; +} + +const Transform & Node::worldTrans() const +{ + if ( scene->worldTrans.contains( nodeId ) ) + return scene->worldTrans[ nodeId ]; + + Transform t = local; + if ( parent ) + t = parent->worldTrans() * t; + + scene->worldTrans.insert( nodeId, t ); + return scene->worldTrans[ nodeId ]; +} + +Transform Node::localTransFrom( int root ) const +{ + Transform trans; + const Node * node = this; + while ( node && node->nodeId != root ) + { + trans = node->local * trans; + node = node->parent; + } + return trans; +} + +Vector3 Node::center() const +{ + return worldTrans().translation; +} + +Node * Node::findParent( int id ) const +{ + Node * node = parent; + while ( node && node->nodeId != id ) + node = node->parent; + return node; +} + +Node * Node::findChild( int id ) const +{ + foreach ( Node * child, children.list() ) + { + if ( child->nodeId == id ) + return child; + child = child->findChild( id ); + if ( child ) + return child; + } + return 0; +} + +Node * Node::findChild( const QString & name ) const +{ + if ( this->name == name ) + return const_cast( this ); + + foreach ( Node * child, children.list() ) + { + Node * n = child->findChild( name ); + if ( n ) + return n; + } + return 0; +} + +bool Node::isHidden() const +{ + if ( Options::drawHidden() ) + return false; + + if ( flags.node.hidden || ( parent && parent->isHidden() ) ) + return true; + + return ! Options::cullExpression().isEmpty() && name.contains( Options::cullExpression() ); +} + +void Node::transform() +{ + Controllable::transform(); + + // if there's a rigid body attached, then calculate and cache the body's transform + // (need this later in the drawing stage for the constraints) + const NifModel * nif = static_cast( iBlock.model() ); + if ( iBlock.isValid() && nif ) + { + QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Data" ) ); + if ( ! iObject.isValid() ) + iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); + if ( iObject.isValid() ) + { + QModelIndex iBody = nif->getBlock( nif->getLink( iObject, "Body" ) ); + if ( iBody.isValid() ) + { + Transform t; + t.scale = 7; + if ( nif->isNiBlock( iBody, "bhkRigidBodyT" ) ) + { + t.rotation.fromQuat( nif->get( iBody, "Rotation" ) ); + t.translation = nif->get( iBody, "Translation" ) * 7; + } + scene->bhkBodyTrans.insert( nif->getBlockNumber( iBody ), worldTrans() * t ); + } + } + } + + foreach ( Node * node, children.list() ) + node->transform(); +} + +void Node::transformShapes() +{ + foreach ( Node * node, children.list() ) + node->transformShapes(); +} + +void Node::draw() +{ + if ( isHidden() ) + return; + + glLoadName( nodeId ); + + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_LEQUAL ); + glDepthMask( GL_TRUE ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_NORMALIZE ); + glDisable( GL_LIGHTING ); + glDisable( GL_COLOR_MATERIAL ); + glEnable( GL_BLEND ); + glDisable( GL_ALPHA_TEST ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + glNormalColor(); + + glPointSize( 8.5 ); + glLineWidth( 2.5 ); + + Vector3 a = viewTrans().translation; + Vector3 b = a; + if ( parent ) + b = parent->viewTrans().translation; + + glBegin( GL_POINTS ); + glVertex( a ); + glEnd(); + + glBegin( GL_LINES ); + glVertex( a ); + glVertex( b ); + glEnd(); + + foreach ( Node * node, children.list() ) + node->draw(); +} + +void Node::drawSelection() const +{ + if ( scene->currentBlock != iBlock || ! Options::drawNodes() ) + return; + + glLoadName( nodeId ); + + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_ALWAYS ); + glDepthMask( GL_TRUE ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_NORMALIZE ); + glDisable( GL_LIGHTING ); + glDisable( GL_COLOR_MATERIAL ); + glEnable( GL_BLEND ); + glDisable( GL_ALPHA_TEST ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + glHighlightColor(); + + glPointSize( 8.5 ); + glLineWidth( 2.5 ); + + Vector3 a = viewTrans().translation; + Vector3 b = a; + if ( parent ) + b = parent->viewTrans().translation; + + glBegin( GL_POINTS ); + glVertex( a ); + glEnd(); + + glBegin( GL_LINES ); + glVertex( a ); + glVertex( b ); + glEnd(); +} + +void DrawVertexSelection( QVector &verts, int i ) +{ + glPointSize( 3.5 ); + glDepthFunc( GL_LEQUAL ); + glNormalColor(); + glBegin( GL_POINTS ); + for ( int j = 0; j < verts.count(); j++ ) + glVertex( verts.value( j ) ); + glEnd(); + + if ( i >= 0 ) + { + glDepthFunc( GL_ALWAYS ); + glHighlightColor(); + glBegin( GL_POINTS ); + glVertex( verts.value( i ) ); + glEnd(); + } +} + +void DrawTriangleSelection( QVector const &verts, Triangle const &tri ) +{ + glLineWidth( 1.5f ); + glDepthFunc( GL_ALWAYS ); + glHighlightColor(); + glBegin( GL_LINE_STRIP ); + glVertex( verts.value( tri.v1() ) ); + glVertex( verts.value( tri.v2() ) ); + glVertex( verts.value( tri.v3() ) ); + glVertex( verts.value( tri.v1() ) ); + glEnd(); +} + +void DrawTriangleIndex( QVector const &verts, Triangle const &tri, int index) +{ + Vector3 c = ( verts.value( tri.v1() ) + verts.value( tri.v2() ) + verts.value( tri.v3() ) ) / 3.0; + renderText(c, QString("%1").arg(index)); +} + +void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStack & stack, const Scene * scene ) +{ + if ( ! nif || ! iShape.isValid() || stack.contains( iShape ) ) + return; + + stack.push( iShape ); + + //qWarning() << "draw shape" << nif->getBlockNumber( iShape ) << nif->itemName( iShape ); + + QString name = nif->itemName( iShape ); + if ( name == "bhkListShape" ) + { + QModelIndex iShapes = nif->getIndex( iShape, "Sub Shapes" ); + if ( iShapes.isValid() ) + { + for ( int r = 0; r < nif->rowCount( iShapes ); r++ ) + { + drawHvkShape( nif, nif->getBlock( nif->getLink( iShapes.child( r, 0 ) ) ), stack, scene ); + } + } + } + else if ( name == "bhkTransformShape" || name == "bhkConvexTransformShape" ) + { + glPushMatrix(); + Matrix4 tm = nif->get( iShape, "Transform" ); + glMultMatrix( tm ); + drawHvkShape( nif, nif->getBlock( nif->getLink( iShape, "Shape" ) ), stack, scene ); + glPopMatrix(); + } + else if ( name == "bhkSphereShape" ) + { + glLoadName( nif->getBlockNumber( iShape ) ); + drawSphere( Vector3(), nif->get( iShape, "Radius" ) ); + } + else if ( name == "bhkMultiSphereShape" ) + { + glLoadName( nif->getBlockNumber( iShape ) ); + QModelIndex iSpheres = nif->getIndex( iShape, "Spheres" ); + for ( int r = 0; r < nif->rowCount( iSpheres ); r++ ) + { + drawSphere( nif->get( iSpheres.child( r, 0 ), "Center" ), nif->get( iSpheres.child( r, 0 ), "Radius" ) ); + } + } + else if ( name == "bhkBoxShape" ) + { + glLoadName( nif->getBlockNumber( iShape ) ); + Vector3 v = nif->get( iShape, "Dimensions" ); + drawBox( v, - v ); + } + else if ( name == "bhkCapsuleShape" ) + { + glLoadName( nif->getBlockNumber( iShape ) ); + drawCapsule( nif->get( iShape, "First Point" ), nif->get( iShape, "Second Point" ), nif->get( iShape, "Radius" ) ); + } + else if ( name == "bhkNiTriStripsShape" ) + { + glPushMatrix(); + float s = 1.0f / 7.0f; + glScalef( s, s, s ); + + glLoadName( nif->getBlockNumber( iShape ) ); + + QModelIndex iStrips = nif->getIndex( iShape, "Strips Data" ); + for ( int r = 0; r < nif->rowCount( iStrips ); r++ ) + { + QModelIndex iStripData = nif->getBlock( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ); + if ( iStripData.isValid() ) + { + QVector verts = nif->getArray( iStripData, "Vertices" ); + + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + glBegin( GL_TRIANGLES ); + + QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + { // draw the strips like they appear in the tescs + // (use the unstich strips spell to avoid the spider web effect) + QVector strip = nif->getArray( iPoints.child( r, 0 ) ); + if ( strip.count() >= 3 ) + { + quint16 a = strip[0]; + quint16 b = strip[1]; + + for ( int x = 2; x < strip.size(); x++ ) + { + quint16 c = strip[x]; + glVertex( verts.value( a ) ); + glVertex( verts.value( b ) ); + glVertex( verts.value( c ) ); + a = b; + b = c; + } + } + } + + glEnd(); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + glPopMatrix(); + } + else if ( name == "bhkConvexVerticesShape" ) + { + glLoadName( nif->getBlockNumber( iShape ) ); + drawConvexHull( nif->getArray( iShape, "Vertices" ), nif->getArray( iShape, "Normals" ) ); + } + else if ( name == "bhkMoppBvTreeShape" ) + { + drawHvkShape( nif, nif->getBlock( nif->getLink( iShape, "Shape" ) ), stack, scene ); + } + else if ( name == "bhkPackedNiTriStripsShape" ) + { + glLoadName( nif->getBlockNumber( iShape ) ); + + QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + if ( iData.isValid() ) + { + QVector verts = nif->getArray( iData, "Vertices" ); + QModelIndex iTris = nif->getIndex( iData, "Triangles" ); + for ( int t = 0; t < nif->rowCount( iTris ); t++ ) + { + Triangle tri = nif->get( iTris.child( t, 0 ), "Triangle" ); + if ( tri[0] != tri[1] || tri[1] != tri[2] || tri[2] != tri[0] ) + { + glBegin( GL_LINE_STRIP ); + glVertex( verts.value( tri[0] ) ); + glVertex( verts.value( tri[1] ) ); + glVertex( verts.value( tri[2] ) ); + glVertex( verts.value( tri[0] ) ); + glEnd(); + } + } + + // Handle Selection + if (scene->currentBlock == iData ) + { + int i = -1; + QString n = scene->currentIndex.data( Qt::DisplayRole ).toString(); + QModelIndex iParent = scene->currentIndex.parent(); + if ( iParent.isValid() && iParent != iData ) + { + n = iParent.data( Qt::DisplayRole ).toString(); + i = scene->currentIndex.row(); + } + if ( n == "Vertices" || n == "Normals" || n == "Vertex Colors" || n == "UV Sets" ) + DrawVertexSelection(verts, i); + else if ( ( n == "Faces" || n == "Triangles" ) ) + { + if ( i == -1 ) + { + glDepthFunc( GL_ALWAYS ); + glHighlightColor(); + for ( int t = 0; t < nif->rowCount( iTris ); t++ ) + DrawTriangleIndex(verts, nif->get( iTris.child( t, 0 ), "Triangle" ), t); + } + else + { + Triangle tri = nif->get( iTris.child( i, 0 ), "Triangle" ); + DrawTriangleSelection(verts, tri ); + DrawTriangleIndex(verts, tri, i); + } + } + } + } + } + + stack.pop(); +} + +void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, const Scene * scene ) +{ + if ( ! ( nif && iConstraint.isValid() && scene && Options::drawConstraints() ) ) + return; + + QList tBodies; + QModelIndex iBodies = nif->getIndex( iConstraint, "Entities" ); + if( !iBodies.isValid() ) { + return; + } + + for ( int r = 0; r < nif->rowCount( iBodies ); r++ ) + { + qint32 l = nif->getLink( iBodies.child( r, 0 ) ); + if ( ! scene->bhkBodyTrans.contains( l ) ) + return; + else + tBodies.append( scene->bhkBodyTrans.value( l ) ); + } + + if ( tBodies.count() != 2 ) + return; + + glLoadName( nif->getBlockNumber( iConstraint ) ); + + glPushMatrix(); + glLoadMatrix( scene->view ); + + glPushAttrib( GL_ENABLE_BIT ); + glEnable( GL_DEPTH_TEST ); + + QString name = nif->itemName( iConstraint ); + if ( name == "bhkMalleableConstraint" ) + { + if ( nif->getIndex( iConstraint, "Ragdoll" ).isValid() ) + { + name = "bhkRagdollConstraint"; + } + else if ( nif->getIndex( iConstraint, "Limited Hinge" ).isValid() ) + { + name = "bhkLimitedHingeConstraint"; + } + } + + if ( name == "bhkLimitedHingeConstraint" ) + { + QModelIndex iHinge = nif->getIndex( iConstraint, "Limited Hinge" ); + + const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); + const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); + + const Vector3 axleA( nif->get( iHinge, "Axle A" ) ); + const Vector3 axleA1( nif->get( iHinge, "Perp2AxleInA1" ) ); + const Vector3 axleA2( nif->get( iHinge, "Perp2AxleInA2" ) ); + + const Vector3 axleB( nif->get( iHinge, "Axle B" ) ); + const Vector3 axleB2( nif->get( iHinge, "Perp2AxleInB2" ) ); + + const float minAngle = nif->get( iHinge, "Min Angle" ); + const float maxAngle = nif->get( iHinge, "Max Angle" ); + + glPushMatrix(); + glMultMatrix( tBodies.value( 0 ) ); + glColor( Color3( 0.8f, 0.6f, 0.0f ) ); + glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); + glBegin( GL_LINES ); glVertex( pivotA ); glVertex( pivotA + axleA ); glEnd(); + drawDashLine( pivotA, pivotA + axleA1, 14 ); + drawDashLine( pivotA, pivotA + axleA2, 14 ); + drawCircle( pivotA, axleA, 1.0f ); + drawSolidArc( pivotA, axleA / 5, axleA2, axleA1, minAngle, maxAngle, 1.0f ); + glPopMatrix(); + + glPushMatrix(); + glMultMatrix( tBodies.value( 1 ) ); + glColor( Color3( 0.6f, 0.8f, 0.0f ) ); + glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); + glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axleB ); glEnd(); + drawDashLine( pivotB + axleB2, pivotB, 14 ); + drawDashLine( pivotB + Vector3::crossproduct( axleB2, axleB ), pivotB, 14 ); + drawCircle( pivotB, axleB, 1.01f ); + drawSolidArc( pivotB, axleB / 7, axleB2, Vector3::crossproduct( axleB2, axleB ), minAngle, maxAngle, 1.01f ); + glPopMatrix(); + + glMultMatrix( tBodies.value( 0 ) ); + float angle = Vector3::angle( tBodies.value( 0 ).rotation * axleA2, tBodies.value( 1 ).rotation * axleB2 ); + glColor( Color3( 0.8f, 0.6f, 0.0f ) ); + glBegin( GL_LINES ); + glVertex( pivotA ); + glVertex( pivotA + axleA1 * cosf( angle ) + axleA2 * sinf( angle ) ); + glEnd(); + } + else if ( name == "bhkHingeConstraint" ) + { + QModelIndex iHinge = iConstraint; + + const Vector3 pivotA( nif->get( iHinge, "Pivot A" ) ); + const Vector3 pivotB( nif->get( iHinge, "Pivot B" ) ); + + const Vector3 axleA1( nif->get( iHinge, "Perp2AxleInA1" ) ); + const Vector3 axleA2( nif->get( iHinge, "Perp2AxleInA2" ) ); + const Vector3 axleA( Vector3::crossproduct( axleA1, axleA2 ) ); + + const Vector3 axleB( nif->get( iHinge, "Axle B" ) ); + const Vector3 axleB1( axleB[1], axleB[2], axleB[0] ); + const Vector3 axleB2( Vector3::crossproduct( axleB, axleB1 ) ); + + const float minAngle = - PI; + const float maxAngle = + PI; + + glPushMatrix(); + glMultMatrix( tBodies.value( 0 ) ); + glColor( Color3( 0.8f, 0.6f, 0.0f ) ); + glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); + drawDashLine( pivotA, pivotA + axleA1 ); + drawDashLine( pivotA, pivotA + axleA2 ); + drawSolidArc( pivotA, axleA / 5, axleA2, axleA1, minAngle, maxAngle, 1.0f, 16 ); + glPopMatrix(); + + glMultMatrix( tBodies.value( 1 ) ); + glColor( Color3( 0.6f, 0.8f, 0.0f ) ); + glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); + glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + axleB ); glEnd(); + drawSolidArc( pivotB, axleB / 7, axleB2, axleB1, minAngle, maxAngle, 1.01f, 16 ); + } + else if ( name == "bhkStiffSpringConstraint" ) + { + const Vector3 pivotA = tBodies.value( 0 ) * Vector3( nif->get( iConstraint, "Pivot A" ) ); + const Vector3 pivotB = tBodies.value( 1 ) * Vector3( nif->get( iConstraint, "Pivot B" ) ); + const float length = nif->get( iConstraint, "Length" ); + + glColor( Color3( 0.6f, 0.8f, 0.0f ) ); + + drawSpring( pivotA, pivotB, length ); + } + else if ( name == "bhkRagdollConstraint" ) + { + QModelIndex iRagdoll = nif->getIndex( iConstraint, "Ragdoll" ); + + const Vector3 pivotA( nif->get( iRagdoll, "Pivot A" ) ); + const Vector3 pivotB( nif->get( iRagdoll, "Pivot B" ) ); + + const Vector3 planeA( nif->get( iRagdoll, "Plane A" ) ); + const Vector3 planeB( nif->get( iRagdoll, "Plane B" ) ); + + const Vector3 twistA( nif->get( iRagdoll, "Twist A" ) ); + const Vector3 twistB( nif->get( iRagdoll, "Twist B" ) ); + + const float coneAngle( nif->get( iRagdoll, "Cone Min Angle" ) ); + + const float minPlaneAngle( nif->get( iRagdoll, "Plane Min Angle" ) ); + const float maxPlaneAngle( nif->get( iRagdoll, "Plane Max Angle" ) ); + + const float minTwistAngle( nif->get( iRagdoll, "Twist Min Angle" ) ); + const float maxTwistAngle( nif->get( iRagdoll, "Twist Max Angle" ) ); + + glPushMatrix(); + glMultMatrix( tBodies.value( 0 ) ); + glColor( Color3( 0.8f, 0.6f, 0.0f ) ); + glPopMatrix(); + + glPushMatrix(); + glMultMatrix( tBodies.value( 0 ) ); + glColor( Color3( 0.8f, 0.6f, 0.0f ) ); + glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); + glBegin( GL_LINES ); glVertex( pivotA ); glVertex( pivotA + twistA ); glEnd(); + drawDashLine( pivotA, pivotA + planeA, 14 ); + drawRagdollCone( pivotA, twistA, planeA, coneAngle, minPlaneAngle, maxPlaneAngle ); + glPopMatrix(); + + glPushMatrix(); + glMultMatrix( tBodies.value( 1 ) ); + glColor( Color3( 0.6f, 0.8f, 0.0f ) ); + glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); + glBegin( GL_LINES ); glVertex( pivotB ); glVertex( pivotB + twistB ); glEnd(); + drawDashLine( pivotB + planeB, pivotB, 14 ); + drawRagdollCone( pivotB, twistB, planeB, coneAngle, minPlaneAngle, maxPlaneAngle ); + glPopMatrix(); + } + else if ( name == "bhkPrismaticConstraint" ) + { + const Vector3 pivotA( nif->get( iConstraint, "Pivot A" ) ); + const Vector3 pivotB( nif->get( iConstraint, "Pivot B" ) ); + + const Vector3 planeNormal( nif->get( iConstraint, "Plane" ) ); + const Vector3 slidingAxis( nif->get( iConstraint, "Sliding Axis" ) ); + + const float minDistance = nif->get( iConstraint, "Min Distance" ); + const float maxDistance = nif->get( iConstraint, "Max Distance" ); + + const Vector3 d1 = pivotA + slidingAxis * minDistance; + const Vector3 d2 = pivotA + slidingAxis * maxDistance; + + /* draw Pivot A and Plane */ + glPushMatrix(); + glMultMatrix( tBodies.value( 0 ) ); + glColor( Color3( 0.8f, 0.6f, 0.0f ) ); + glBegin( GL_POINTS ); glVertex( pivotA ); glEnd(); + glBegin( GL_LINES ); glVertex( pivotA ); glVertex( pivotA + planeNormal ); glEnd(); + drawDashLine( pivotA, d1, 14 ); + + /* draw rail */ + if ( minDistance < maxDistance ) { + drawRail( d1, d2 ); + } + + /*draw first marker*/ + Transform t; + float angle = atan2f( slidingAxis[1], slidingAxis[0] ); + if ( slidingAxis[0] < 0.0001f && slidingAxis[1] < 0.0001f ) { + angle = PI / 2; + } + + t.translation = d1; + t.rotation.fromEuler( 0.0f, 0.0f, angle ); + glMultMatrix( t ); + + angle = - asinf( slidingAxis[2] / slidingAxis.length() ); + t.translation = Vector3( 0.0f, 0.0f, 0.0f ); + t.rotation.fromEuler( 0.0f, angle, 0.0f ); + glMultMatrix( t ); + + drawMarker( &BumperMarker01 ); + + /*draw second marker*/ + t.translation = Vector3( minDistance < maxDistance ? ( d2 - d1 ).length() : 0.0f, 0.0f, 0.0f ); + t.rotation.fromEuler( 0.0f, 0.0f, PI ); + glMultMatrix( t ); + + drawMarker( &BumperMarker01 ); + glPopMatrix(); + + /* draw Pivot B */ + glPushMatrix(); + glMultMatrix( tBodies.value( 1 ) ); + glColor( Color3( 0.6f, 0.8f, 0.0f ) ); + glBegin( GL_POINTS ); glVertex( pivotB ); glEnd(); + glPopMatrix(); + } + + glPopAttrib(); + glPopMatrix(); +} + +void Node::drawHavok() +{ + foreach ( Node * node, children.list() ) + node->drawHavok(); + + const NifModel * nif = static_cast( iBlock.model() ); + if ( ! ( iBlock.isValid() && nif ) ) + return; + + //Check if there's any old style collision bounding box set + if ( nif->get( iBlock, "Has Bounding Box" ) == true ) + { + QModelIndex iBox = nif->getIndex( iBlock, "Bounding Box" ); + + Transform bt; + + bt.translation = nif->get( iBox, "Translation" ); + bt.rotation = nif->get( iBox, "Rotation" ); + bt.scale = 1.0f; + + Vector3 rad = nif->get( iBox, "Radius" ); + + glPushMatrix(); + glLoadMatrix( scene->view ); + glMultMatrix( worldTrans() ); + glMultMatrix( bt ); + + glColor( Color3( 1.0f, 0.0f, 0.0f ) ); + glLineWidth( 1.0f ); + glDisable( GL_LIGHTING ); + drawBox( rad, - rad ); + + glPopMatrix(); + } + + QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Data" ) ); + if ( ! iObject.isValid() ) + iObject = nif->getBlock( nif->getLink( iBlock, "Collision Object" ) ); + if ( ! iObject.isValid() ) + return; + + QModelIndex iBody = nif->getBlock( nif->getLink( iObject, "Body" ) ); + + glPushMatrix(); + glLoadMatrix( scene->view ); + glMultMatrix( scene->bhkBodyTrans.value( nif->getBlockNumber( iBody ) ) ); + + + //qWarning() << "draw obj" << nif->getBlockNumber( iObject ) << nif->itemName( iObject ); + + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_TRUE ); + glDepthFunc( GL_LEQUAL ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_NORMALIZE ); + glDisable( GL_LIGHTING ); + glDisable( GL_COLOR_MATERIAL ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_ALPHA_TEST ); + glPointSize( 4.5 ); + glLineWidth( 1.0 ); + + static const float colors[8][3] = { + { 0.0f, 1.0f, 0.0f }, + { 1.0f, 0.0f, 0.0f }, + { 1.0f, 0.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 0.5f, 0.5f, 1.0f }, + { 1.0f, 0.8f, 0.0f }, + { 1.0f, 0.8f, 0.4f }, + { 0.0f, 1.0f, 1.0f } + }; + + glColor3fv( colors[ nif->get( iBody, "Layer" ) & 7 ] ); + + QStack shapeStack; + drawHvkShape( nif, nif->getBlock( nif->getLink( iBody, "Shape" ) ), shapeStack, scene ); + + glLoadName( nif->getBlockNumber( iBody ) ); + drawAxes( nif->get( iBody, "Center" ), 0.2f ); + + glPopMatrix(); + + foreach ( qint32 l, nif->getLinkArray( iBody, "Constraints" ) ) + { + QModelIndex iConstraint = nif->getBlock( l ); + if ( nif->inherits( iConstraint, "bhkConstraint" ) ) + drawHvkConstraint( nif, iConstraint, scene ); + } +} + +void drawFurnitureMarker( const NifModel *nif, const QModelIndex &iPosition ) +{ + QString name = nif->itemName( iPosition ); + Vector3 offs = nif->get( iPosition, "Offset" ); + quint16 orient = nif->get( iPosition, "Orientation" ); + quint8 ref1 = nif->get( iPosition, "Position Ref 1" ); + quint8 ref2 = nif->get( iPosition, "Position Ref 2" ); + + if ( ref1 != ref2 ) + { + qDebug() << "Position Ref 1 and 2 are not equal!"; + return; + } + + Vector3 flip(1, 1, 1); + const GLMarker *mark; + switch ( ref1 ) + { + case 1: + mark = &FurnitureMarker01; + break; + + case 2: + flip[0] = -1; + mark = &FurnitureMarker01; + break; + + case 3: + mark = &FurnitureMarker03; + break; + + case 4: + mark = &FurnitureMarker04; + break; + + case 11: + mark = &FurnitureMarker11; + break; + + case 12: + flip[0] = -1; + mark = &FurnitureMarker11; + break; + + case 13: + mark = &FurnitureMarker13; + break; + + case 14: + mark = &FurnitureMarker14; + break; + + default: + qDebug() << "Unknown furniture marker " << ref1 << "!"; + return; + } + + float roll = float( orient ) / 6284.0 * 2.0 * (-M_PI); + + glLoadName( ( nif->getBlockNumber( iPosition ) & 0xffff ) | ( ( iPosition.row() & 0xffff ) << 16 ) ); + + glPushMatrix(); + + Transform t; + t.rotation.fromEuler( 0, 0, roll ); + t.translation = offs; + glMultMatrix( t ); + + glScale(flip); + + drawMarker( mark ); + + glPopMatrix(); +} + +void Node::drawFurn() +{ + foreach ( Node * node, children.list() ) + node->drawFurn(); + + const NifModel * nif = static_cast( iBlock.model() ); + if ( ! ( iBlock.isValid() && nif ) ) + return; + + QModelIndex iExtraDataList = nif->getIndex( iBlock, "Extra Data List" ); + + if ( !iExtraDataList.isValid() ) + return; + + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_FALSE ); + glDepthFunc( GL_LEQUAL ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_NORMALIZE ); + glDisable( GL_LIGHTING ); + glDisable( GL_COLOR_MATERIAL ); + glDisable( GL_CULL_FACE ); + glDisable( GL_BLEND ); + glDisable( GL_ALPHA_TEST ); + glColor4f( 1, 1, 1, 1 ); + + glLineWidth( 1.0 ); + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + glPushMatrix(); + + glMultMatrix( viewTrans() ); + + for ( int p = 0; p < nif->rowCount( iExtraDataList ); p++ ) + { + QModelIndex iFurnMark = nif->getBlock( nif->getLink( iExtraDataList.child( p, 0 ) ), "BSFurnitureMarker" ); + if ( ! iFurnMark.isValid() ) + continue; + + QModelIndex iPositions = nif->getIndex( iFurnMark, "Positions" ); + if ( !iPositions.isValid() ) + break; + + for ( int j = 0; j < nif->rowCount( iPositions ); j++ ) + { + QModelIndex iPosition = iPositions.child( j, 0 ); + + if ( scene->currentIndex == iPosition ) + glHighlightColor(); + else + glNormalColor(); + + drawFurnitureMarker( nif, iPosition ); + } + } + + glPopMatrix(); +} + +void Node::drawShapes( NodeList * draw2nd ) +{ + if ( isHidden() ) + return; + + foreach ( Node * node, children.list() ) + { + node->drawShapes( draw2nd ); + } +} + +#define Farg( X ) arg( X, 0, 'f', 5 ) + +QString trans2string( Transform t ) +{ + float xr, yr, zr; + t.rotation.toEuler( xr, yr, zr ); + return QString( "translation X %1, Y %2, Z %3\n" ).Farg( t.translation[0] ).Farg( t.translation[1] ).Farg( t.translation[2] ) + + QString( "rotation Y %1, P %2, R %3 " ).Farg( xr * 180 / PI ).Farg( yr * 180 / PI ).Farg( zr * 180 / PI ) + + QString( "( (%1, %2, %3), " ).Farg( t.rotation( 0, 0 ) ).Farg( t.rotation( 0, 1 ) ).Farg( t.rotation( 0, 2 ) ) + + QString( "(%1, %2, %3), " ).Farg( t.rotation( 1, 0 ) ).Farg( t.rotation( 1, 1 ) ).Farg( t.rotation( 1, 2 ) ) + + QString( "(%1, %2, %3) )\n" ).Farg( t.rotation( 2, 0 ) ).Farg( t.rotation( 2, 1 ) ).Farg( t.rotation( 2, 2 ) ) + + QString( "scale %1\n" ).Farg( t.scale ); +} + +QString Node::textStats() const +{ + return QString( "%1\n\nglobal\n%2\nlocal\n%3\n" ).arg( name ).arg( trans2string( worldTrans() ) ).arg( trans2string( localTrans() ) ); +} + +BoundSphere Node::bounds() const +{ + if ( Options::drawNodes() ) + return BoundSphere( worldTrans().translation, 0 ); + else + return BoundSphere(); +} + + +LODNode::LODNode( Scene * scene, const QModelIndex & iBlock ) + : Node( scene, iBlock ) +{ +} + +void LODNode::clear() +{ + Node::clear(); + ranges.clear(); +} + +void LODNode::update( const NifModel * nif, const QModelIndex & index ) +{ + Node::update( nif, index ); + + if ( ( iBlock.isValid() && index == iBlock ) || ( iData.isValid() && index == iData ) ) + { + ranges.clear(); + iData = nif->getBlock( nif->getLink( iBlock, "LOD Level Data" ), "NiRangeLODData" ); + QModelIndex iLevels; + if ( iData.isValid() ) + { + center = nif->get( iData, "LOD Center" ); + iLevels = nif->getIndex( iData, "LOD Levels" ); + } + else + { + center = nif->get( iBlock, "LOD Center" ); + iLevels = nif->getIndex( iBlock, "LOD Levels" ); + } + if ( iLevels.isValid() ) + for ( int r = 0; r < nif->rowCount( iLevels ); r++ ) + ranges.append( qMakePair( nif->get( iLevels.child( r, 0 ), "Near Extent" ), nif->get( iLevels.child( r, 0 ), "Far Extent" ) ) ); + } +} + +void LODNode::transform() +{ + Node::transform(); + + if ( children.list().isEmpty() ) + return; + + if ( ranges.isEmpty() ) + { + foreach ( Node * child, children.list() ) + child->flags.node.hidden = true; + children.list().first()->flags.node.hidden = false; + return; + } + + float distance = ( viewTrans() * center ).length(); + + int c = 0; + foreach ( Node * child, children.list() ) + { + if ( c < ranges.count() ) + child->flags.node.hidden = ! ( ranges[c].first <= distance && distance < ranges[c].second ); + else + child->flags.node.hidden = true; + c++; + } +} + + +BillboardNode::BillboardNode( Scene * scene, const QModelIndex & iBlock ) + : Node( scene, iBlock ) +{ +} + +const Transform & BillboardNode::viewTrans() const +{ + if ( scene->viewTrans.contains( nodeId ) ) + return scene->viewTrans[ nodeId ]; + + Transform t; + if ( parent ) + t = parent->viewTrans() * local; + else + t = scene->view * worldTrans(); + + t.rotation = Matrix(); + + scene->viewTrans.insert( nodeId, t ); + return scene->viewTrans[ nodeId ]; +} diff --git a/gl/glnode.h b/gl/glnode.h index 6049eceda..659f6ac45 100644 --- a/gl/glnode.h +++ b/gl/glnode.h @@ -1,181 +1,181 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLNODE_H -#define GLNODE_H - -#include "glcontrolable.h" -#include "glproperty.h" - -class Node; - -class NodeList -{ -public: - NodeList(); - NodeList( const NodeList & other ); - ~NodeList(); - - void add( Node * ); - void del( Node * ); - - Node * get( const QModelIndex & idx ) const; - - void validate(); - - void clear(); - - NodeList & operator=( const NodeList & other ); - - const QList & list() const { return nodes; } - - void sort(); - -protected: - QList nodes; -}; - -class Node : public Controllable -{ - typedef union - { - quint16 bits; - - struct Node - { - bool hidden : 1; - } node; - - } NodeFlags; - -public: - Node( Scene * scene, const QModelIndex & block ); - - virtual void clear(); - virtual void update( const NifModel * nif, const QModelIndex & block ); - - virtual void transform(); - virtual void transformShapes(); - - virtual void draw(); - virtual void drawShapes( NodeList * draw2nd = 0 ); - virtual void drawHavok(); - virtual void drawFurn(); - virtual void drawSelection() const; - - virtual const Transform & viewTrans() const; - virtual const Transform & worldTrans() const; - virtual const Transform & localTrans() const { return local; } - virtual Transform localTransFrom( int parentNode ) const; - virtual Vector3 center() const; - - virtual bool isHidden() const; - bool isVisible() const { return ! isHidden(); } - - int id() const { return nodeId; } - - Node * findParent( int id ) const; - Node * findChild( int id ) const; - Node * findChild( const QString & name ) const; - Node * parentNode() const { return parent; } - void makeParent( Node * parent ); - - virtual class BoundSphere bounds() const; - - template T * findProperty() const; - void activeProperties( PropertyList & list ) const; - - Controller * findController( const QString & proptype, const QString & ctrltype, const QString & var1, const QString & var2 ); - - virtual QString textStats() const; - -protected: - virtual void setController( const NifModel * nif, const QModelIndex & controller ); - - QPointer parent; - - int ref; - - NodeList children; - PropertyList properties; - - int nodeId; - - Transform local; - - NodeFlags flags; - - friend class KeyframeController; - friend class TransformController; - friend class ControllerManager; - friend class MultiTargetTransformController; - friend class VisibilityController; - friend class NodeList; - friend class LODNode; -}; - -template inline T * Node::findProperty() const -{ - T * prop = properties.get(); - if ( prop ) return prop; - if ( parent ) return parent->findProperty(); - return 0; -} - -class LODNode : public Node -{ -public: - LODNode( Scene * scene, const QModelIndex & block ); - - void clear(); - void update( const NifModel * nif, const QModelIndex & block ); - - void transform(); - -protected: - QList< QPair > ranges; - QPersistentModelIndex iData; - - Vector3 center; -}; - -class BillboardNode : public Node -{ -public: - BillboardNode( Scene * scene, const QModelIndex & block ); - - virtual const Transform & viewTrans() const; -}; - -#endif - - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLNODE_H +#define GLNODE_H + +#include "glcontrolable.h" +#include "glproperty.h" + +class Node; + +class NodeList +{ +public: + NodeList(); + NodeList( const NodeList & other ); + ~NodeList(); + + void add( Node * ); + void del( Node * ); + + Node * get( const QModelIndex & idx ) const; + + void validate(); + + void clear(); + + NodeList & operator=( const NodeList & other ); + + const QList & list() const { return nodes; } + + void sort(); + +protected: + QList nodes; +}; + +class Node : public Controllable +{ + typedef union + { + quint16 bits; + + struct Node + { + bool hidden : 1; + } node; + + } NodeFlags; + +public: + Node( Scene * scene, const QModelIndex & block ); + + virtual void clear(); + virtual void update( const NifModel * nif, const QModelIndex & block ); + + virtual void transform(); + virtual void transformShapes(); + + virtual void draw(); + virtual void drawShapes( NodeList * draw2nd = 0 ); + virtual void drawHavok(); + virtual void drawFurn(); + virtual void drawSelection() const; + + virtual const Transform & viewTrans() const; + virtual const Transform & worldTrans() const; + virtual const Transform & localTrans() const { return local; } + virtual Transform localTransFrom( int parentNode ) const; + virtual Vector3 center() const; + + virtual bool isHidden() const; + bool isVisible() const { return ! isHidden(); } + + int id() const { return nodeId; } + + Node * findParent( int id ) const; + Node * findChild( int id ) const; + Node * findChild( const QString & name ) const; + Node * parentNode() const { return parent; } + void makeParent( Node * parent ); + + virtual class BoundSphere bounds() const; + + template T * findProperty() const; + void activeProperties( PropertyList & list ) const; + + Controller * findController( const QString & proptype, const QString & ctrltype, const QString & var1, const QString & var2 ); + + virtual QString textStats() const; + +protected: + virtual void setController( const NifModel * nif, const QModelIndex & controller ); + + QPointer parent; + + int ref; + + NodeList children; + PropertyList properties; + + int nodeId; + + Transform local; + + NodeFlags flags; + + friend class KeyframeController; + friend class TransformController; + friend class ControllerManager; + friend class MultiTargetTransformController; + friend class VisibilityController; + friend class NodeList; + friend class LODNode; +}; + +template inline T * Node::findProperty() const +{ + T * prop = properties.get(); + if ( prop ) return prop; + if ( parent ) return parent->findProperty(); + return 0; +} + +class LODNode : public Node +{ +public: + LODNode( Scene * scene, const QModelIndex & block ); + + void clear(); + void update( const NifModel * nif, const QModelIndex & block ); + + void transform(); + +protected: + QList< QPair > ranges; + QPersistentModelIndex iData; + + Vector3 center; +}; + +class BillboardNode : public Node +{ +public: + BillboardNode( Scene * scene, const QModelIndex & block ); + + virtual const Transform & viewTrans() const; +}; + +#endif + + diff --git a/gl/glparticles.cpp b/gl/glparticles.cpp index adc865219..7dc42ce69 100644 --- a/gl/glparticles.cpp +++ b/gl/glparticles.cpp @@ -1,488 +1,488 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "glcontroller.h" -#include "glparticles.h" -#include "glscene.h" - -#include "math.h" - -float random( float r ) -{ - return r * rand() / RAND_MAX; -} - -Vector3 random( Vector3 v ) -{ - v[0] *= random( 1.0 ); - v[1] *= random( 1.0 ); - v[2] *= random( 1.0 ); - return v; -} - -class ParticleController : public Controller -{ - struct Particle - { - Vector3 position; - Vector3 velocity; - Vector3 unknown; - float lifetime; - float lifespan; - float lasttime; - short y; - short vertex; - - Particle() : lifetime( 0 ), lifespan( 0 ) {} - }; - QVector list; - struct Gravity - { - float force; - int type; - Vector3 position; - Vector3 direction; - }; - QVector grav; - - QPointer target; - - float emitStart, emitStop, emitRate, emitLast, emitAccu, emitMax; - QPointer emitNode; - Vector3 emitRadius; - - float spd, spdRnd; - float ttl, ttlRnd; - - float inc, incRnd; - float dec, decRnd; - - float size; - float grow; - float fade; - - float localtime; - - QList iExtras; - QPersistentModelIndex iColorKeys; - -public: - ParticleController( Particles * particles, const QModelIndex & index ) - : Controller( index ), target( particles ) {} - - bool update( const NifModel * nif, const QModelIndex & index ) - { - if ( ! target ) - return false; - - if ( Controller::update( nif, index ) || ( index.isValid() && iExtras.contains( index ) ) ) - { - emitNode = target->scene->getNode( nif, nif->getBlock( nif->getLink( iBlock, "Emitter" ) ) ); - emitStart = nif->get( iBlock, "Emit Start Time" ); - emitStop = nif->get( iBlock, "Emit Stop Time" ); - emitRate = nif->get( iBlock, "Emit Rate" ); - emitRadius = nif->get( iBlock, "Start Random" ); - emitAccu = 0; - emitLast = emitStart; - - spd = nif->get( iBlock, "Speed" ); - spdRnd = nif->get( iBlock, "Speed Random" ); - - ttl = nif->get( iBlock, "Lifetime" ); - ttlRnd = nif->get( iBlock, "Lifetime Random" ); - - inc = nif->get( iBlock, "Vertical Direction" ); - incRnd = nif->get( iBlock, "Vertical Angle" ); - - dec = nif->get( iBlock, "Horizontal Direction" ); - decRnd = nif->get( iBlock, "Horizontal Angle" ); - - size = nif->get( iBlock, "Size" ); - grow = 0.0; - fade = 0.0; - - list.clear(); - - QModelIndex iParticles = nif->getIndex( iBlock, "Particles" ); - if ( iParticles.isValid() ) - { - emitMax = nif->get( iParticles, "Num Particles" ); - int active = nif->get( iParticles, "Num Valid" ); - iParticles = nif->getIndex( iParticles, "Particles" ); - if ( iParticles.isValid() ) - { - for ( int p = 0; p < active && p < nif->rowCount( iParticles ); p++ ) - { - Particle particle; - particle.velocity = nif->get( iParticles.child( p, 0 ), "Velocity" ); - particle.lifetime = nif->get( iParticles.child( p, 0 ), "Life Time" ); - particle.lifespan = nif->get( iParticles.child( p, 0 ), "Life Span" ); - particle.lasttime = nif->get( iParticles.child( p, 0 ), "Last Time" ); - particle.vertex = nif->get( iParticles.child( p, 0 ), "Vertex ID" ); - } - } - } - - if ( ( nif->get( iBlock, "Emit Flags" ) & 1 ) == 0 ) - { - emitRate = emitMax / ( ttl + ttlRnd / 2 ); - } - - iExtras.clear(); - grav.clear(); - iColorKeys = QModelIndex(); - QModelIndex iExtra = nif->getBlock( nif->getLink( iBlock, "Particle Extra" ) ); - while ( iExtra.isValid() ) - { - iExtras.append( iExtra ); - - QString name = nif->itemName( iExtra ); - if ( name == "NiParticleGrowFade" ) - { - grow = nif->get( iExtra, "Grow" ); - fade = nif->get( iExtra, "Fade" ); - } - else if ( name == "NiParticleColorModifier" ) - { - iColorKeys = nif->getIndex( nif->getBlock( nif->getLink( iExtra, "Color Data" ), "NiColorData" ), "Data" ); - } - else if ( name == "NiGravity" ) - { - Gravity g; - g.force = nif->get( iExtra, "Force" ); - g.type = nif->get( iExtra, "Type" ); - g.position = nif->get( iExtra, "Position" ); - g.direction = nif->get( iExtra, "Direction" ); - grav.append( g ); - } - - iExtra = nif->getBlock( nif->getLink( iExtra, "Next Modifier" ) ); - } - return true; - } - return false; - } - - void update( float time ) - { - if ( ! ( target && active ) ) - return; - - localtime = ctrlTime( time ); - - int n = 0; - while ( n < list.count() ) - { - Particle & p = list[n]; - - float deltaTime = ( localtime > p.lasttime ? localtime - p.lasttime : 0 ); //( stop - start ) - p.lasttime + localtime ); - - p.lifetime += deltaTime; - if ( p.lifetime < p.lifespan && p.vertex < target->verts.count() ) - { - p.position = target->verts[ p.vertex ]; - for ( int i = 0; i < 4; i++ ) - moveParticle( p, deltaTime/4 ); - p.lasttime = localtime; - n++; - } - else - list.remove( n ); - } - - if ( emitNode && emitNode->isVisible() && localtime >= emitStart && localtime <= emitStop ) - { - float emitDelta = ( localtime > emitLast ? localtime - emitLast : 0 ); - emitLast = localtime; - - emitAccu += emitDelta * emitRate; - - int num = int( emitAccu ); - if ( num > 0 ) - { - emitAccu -= num; - while ( num-- > 0 && list.count() < target->verts.count() ) - { - Particle p; - startParticle( p ); - list.append( p ); - } - } - } - - n = 0; - while ( n < list.count() ) - { - Particle & p = list[n]; - p.vertex = n; - target->verts[ n ] = p.position; - if ( n < target->sizes.count() ) - sizeParticle( p, target->sizes[n] ); - if ( n < target->colors.count() ) - colorParticle( p, target->colors[n] ); - n++; - } - - target->active = list.count(); - target->size = size; - } - - void startParticle( Particle & p ) - { - p.position = random( emitRadius * 2 ) - emitRadius; - p.position += target->worldTrans().rotation.inverted() * ( emitNode->worldTrans().translation - target->worldTrans().translation ); - - float i = inc + random( incRnd ); - float d = dec + random( decRnd ); - - p.velocity = Vector3( rand() & 1 ? sin( i ) : - sin( i ), 0, cos( i ) ); - - Matrix m; m.fromEuler( 0, 0, rand() & 1 ? d : -d ); - p.velocity = m * p.velocity; - - p.velocity = p.velocity * ( spd + random( spdRnd ) ); - p.velocity = target->worldTrans().rotation.inverted() * emitNode->worldTrans().rotation * p.velocity; - - p.lifetime = 0; - p.lifespan = ttl + random( ttlRnd ); - p.lasttime = localtime; - } - - void moveParticle( Particle & p, float deltaTime ) - { - foreach ( Gravity g, grav ) - { - switch ( g.type ) - { - case 0: - p.velocity += g.direction * ( g.force * deltaTime ); - break; - case 1: - { - Vector3 dir = ( g.position - p.position ); - dir.normalize(); - p.velocity += dir * ( g.force * deltaTime ); - } break; - } - } - p.position += p.velocity * deltaTime; - } - - void sizeParticle( Particle & p, float & size ) - { - size = 1.0; - - if ( grow > 0 && p.lifetime < grow ) - size *= p.lifetime / grow; - if ( fade > 0 && p.lifespan - p.lifetime < fade ) - size *= ( p.lifespan - p.lifetime ) / fade; - } - - void colorParticle( Particle & p, Color4 & color ) - { - if ( iColorKeys.isValid() ) - { - int i = 0; - interpolate( color, iColorKeys, p.lifetime / p.lifespan, i ); - } - } -}; - -/* - * Particle - */ - -void Particles::clear() -{ - Node::clear(); - - verts.clear(); - colors.clear(); - transVerts.clear(); -} - -void Particles::update( const NifModel * nif, const QModelIndex & index ) -{ - Node::update( nif, index ); - - if ( ! iBlock.isValid() ) - return; - - upData |= ( iData == index ); - - if ( iBlock == index ) - { - foreach ( int link, nif->getChildLinks( id() ) ) - { - QModelIndex iChild = nif->getBlock( link ); - if ( ! iChild.isValid() ) continue; - QString name = nif->itemName( iChild ); - - if ( name == "NiParticlesData" || name == "NiRotatingParticlesData" || name == "NiAutoNormalParticlesData" ) - { - iData = iChild; - upData = true; - } - } - } -} - -void Particles::setController( const NifModel * nif, const QModelIndex & index ) -{ - if ( nif->itemName( index ) == "NiParticleSystemController" || nif->itemName( index ) == "NiBSPArrayController" ) - { - Controller * ctrl = new ParticleController( this, index ); - ctrl->update( nif, index ); - controllers.append( ctrl ); - } - else - Node::setController( nif, index ); -} - -void Particles::transform() -{ - const NifModel * nif = static_cast( iBlock.model() ); - if ( ! nif || ! iBlock.isValid() ) - { - clear(); - return; - } - - if ( upData ) - { - upData = false; - - verts = nif->getArray( nif->getIndex( iData, "Vertices" ) ); - colors = nif->getArray( nif->getIndex( iData, "Vertex Colors" ) ); - sizes = nif->getArray( nif->getIndex( iData, "Sizes" ) ); - - active = nif->get( iData, "Num Valid" ); - size = nif->get( iData, "Active Radius" ); - } - - Node::transform(); -} - -void Particles::transformShapes() -{ - Node::transformShapes(); - - Transform vtrans = viewTrans(); - - transVerts.resize( verts.count() ); - for ( int v = 0; v < verts.count(); v++ ) - transVerts[v] = vtrans * verts[v]; -} - -BoundSphere Particles::bounds() const -{ - BoundSphere sphere( verts ); - sphere.radius += size; - return worldTrans() * sphere | Node::bounds(); -} - -void Particles::drawShapes( NodeList * draw2nd ) -{ - if ( isHidden() ) - return; - - AlphaProperty * aprop = findProperty< AlphaProperty >(); - if ( aprop && aprop->blend() && draw2nd ) - { - draw2nd->add( this ); - return; - } - - glLoadName( nodeId ); - - // setup blending - - glProperty( findProperty< AlphaProperty >() ); - - // setup vertex colors - - glProperty( findProperty< VertexColorProperty >(), ( colors.count() >= transVerts.count() ) ); - - // setup material - - glProperty( findProperty< MaterialProperty >(), findProperty< SpecularProperty >() ); - - // setup texturing - - glProperty( findProperty< TexturingProperty >() ); - - // setup z buffer - - glProperty( findProperty< ZBufferProperty >() ); - - // setup stencil - - glProperty( findProperty< StencilProperty >() ); - - // wireframe ? - - glProperty( findProperty< WireframeProperty >() ); - - // normalize - - glEnable( GL_NORMALIZE ); - glNormal3f( 0.0, 0.0, -1.0 ); - - // render the particles - - static const Vector2 tex[4] = { Vector2( 1.0, 1.0 ), Vector2( 0.0, 1.0 ), Vector2( 1.0, 0.0 ), Vector2( 0.0, 0.0 ) }; - - int p = 0; - foreach ( Vector3 v, transVerts ) - { - if ( p >= active ) - break; - - GLfloat s2 = ( sizes.count() > p ? sizes[ p ] * size : size ); - s2 *= worldTrans().scale; - - glBegin( GL_TRIANGLE_STRIP ); - if ( p < colors.count() ) - glColor( colors[ p ] ); - glTexCoord( tex[0] ); - glVertex( v + Vector3( + s2, + s2, 0 ) ); - glTexCoord( tex[1] ); - glVertex( v + Vector3( - s2, + s2, 0 ) ); - glTexCoord( tex[2] ); - glVertex( v + Vector3( + s2, - s2, 0 ) ); - glTexCoord( tex[3] ); - glVertex( v + Vector3( - s2, - s2, 0 ) ); - glEnd(); - - p++; - } -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "glcontroller.h" +#include "glparticles.h" +#include "glscene.h" + +#include "math.h" + +float random( float r ) +{ + return r * rand() / RAND_MAX; +} + +Vector3 random( Vector3 v ) +{ + v[0] *= random( 1.0 ); + v[1] *= random( 1.0 ); + v[2] *= random( 1.0 ); + return v; +} + +class ParticleController : public Controller +{ + struct Particle + { + Vector3 position; + Vector3 velocity; + Vector3 unknown; + float lifetime; + float lifespan; + float lasttime; + short y; + short vertex; + + Particle() : lifetime( 0 ), lifespan( 0 ) {} + }; + QVector list; + struct Gravity + { + float force; + int type; + Vector3 position; + Vector3 direction; + }; + QVector grav; + + QPointer target; + + float emitStart, emitStop, emitRate, emitLast, emitAccu, emitMax; + QPointer emitNode; + Vector3 emitRadius; + + float spd, spdRnd; + float ttl, ttlRnd; + + float inc, incRnd; + float dec, decRnd; + + float size; + float grow; + float fade; + + float localtime; + + QList iExtras; + QPersistentModelIndex iColorKeys; + +public: + ParticleController( Particles * particles, const QModelIndex & index ) + : Controller( index ), target( particles ) {} + + bool update( const NifModel * nif, const QModelIndex & index ) + { + if ( ! target ) + return false; + + if ( Controller::update( nif, index ) || ( index.isValid() && iExtras.contains( index ) ) ) + { + emitNode = target->scene->getNode( nif, nif->getBlock( nif->getLink( iBlock, "Emitter" ) ) ); + emitStart = nif->get( iBlock, "Emit Start Time" ); + emitStop = nif->get( iBlock, "Emit Stop Time" ); + emitRate = nif->get( iBlock, "Emit Rate" ); + emitRadius = nif->get( iBlock, "Start Random" ); + emitAccu = 0; + emitLast = emitStart; + + spd = nif->get( iBlock, "Speed" ); + spdRnd = nif->get( iBlock, "Speed Random" ); + + ttl = nif->get( iBlock, "Lifetime" ); + ttlRnd = nif->get( iBlock, "Lifetime Random" ); + + inc = nif->get( iBlock, "Vertical Direction" ); + incRnd = nif->get( iBlock, "Vertical Angle" ); + + dec = nif->get( iBlock, "Horizontal Direction" ); + decRnd = nif->get( iBlock, "Horizontal Angle" ); + + size = nif->get( iBlock, "Size" ); + grow = 0.0; + fade = 0.0; + + list.clear(); + + QModelIndex iParticles = nif->getIndex( iBlock, "Particles" ); + if ( iParticles.isValid() ) + { + emitMax = nif->get( iParticles, "Num Particles" ); + int active = nif->get( iParticles, "Num Valid" ); + iParticles = nif->getIndex( iParticles, "Particles" ); + if ( iParticles.isValid() ) + { + for ( int p = 0; p < active && p < nif->rowCount( iParticles ); p++ ) + { + Particle particle; + particle.velocity = nif->get( iParticles.child( p, 0 ), "Velocity" ); + particle.lifetime = nif->get( iParticles.child( p, 0 ), "Life Time" ); + particle.lifespan = nif->get( iParticles.child( p, 0 ), "Life Span" ); + particle.lasttime = nif->get( iParticles.child( p, 0 ), "Last Time" ); + particle.vertex = nif->get( iParticles.child( p, 0 ), "Vertex ID" ); + } + } + } + + if ( ( nif->get( iBlock, "Emit Flags" ) & 1 ) == 0 ) + { + emitRate = emitMax / ( ttl + ttlRnd / 2 ); + } + + iExtras.clear(); + grav.clear(); + iColorKeys = QModelIndex(); + QModelIndex iExtra = nif->getBlock( nif->getLink( iBlock, "Particle Extra" ) ); + while ( iExtra.isValid() ) + { + iExtras.append( iExtra ); + + QString name = nif->itemName( iExtra ); + if ( name == "NiParticleGrowFade" ) + { + grow = nif->get( iExtra, "Grow" ); + fade = nif->get( iExtra, "Fade" ); + } + else if ( name == "NiParticleColorModifier" ) + { + iColorKeys = nif->getIndex( nif->getBlock( nif->getLink( iExtra, "Color Data" ), "NiColorData" ), "Data" ); + } + else if ( name == "NiGravity" ) + { + Gravity g; + g.force = nif->get( iExtra, "Force" ); + g.type = nif->get( iExtra, "Type" ); + g.position = nif->get( iExtra, "Position" ); + g.direction = nif->get( iExtra, "Direction" ); + grav.append( g ); + } + + iExtra = nif->getBlock( nif->getLink( iExtra, "Next Modifier" ) ); + } + return true; + } + return false; + } + + void update( float time ) + { + if ( ! ( target && active ) ) + return; + + localtime = ctrlTime( time ); + + int n = 0; + while ( n < list.count() ) + { + Particle & p = list[n]; + + float deltaTime = ( localtime > p.lasttime ? localtime - p.lasttime : 0 ); //( stop - start ) - p.lasttime + localtime ); + + p.lifetime += deltaTime; + if ( p.lifetime < p.lifespan && p.vertex < target->verts.count() ) + { + p.position = target->verts[ p.vertex ]; + for ( int i = 0; i < 4; i++ ) + moveParticle( p, deltaTime/4 ); + p.lasttime = localtime; + n++; + } + else + list.remove( n ); + } + + if ( emitNode && emitNode->isVisible() && localtime >= emitStart && localtime <= emitStop ) + { + float emitDelta = ( localtime > emitLast ? localtime - emitLast : 0 ); + emitLast = localtime; + + emitAccu += emitDelta * emitRate; + + int num = int( emitAccu ); + if ( num > 0 ) + { + emitAccu -= num; + while ( num-- > 0 && list.count() < target->verts.count() ) + { + Particle p; + startParticle( p ); + list.append( p ); + } + } + } + + n = 0; + while ( n < list.count() ) + { + Particle & p = list[n]; + p.vertex = n; + target->verts[ n ] = p.position; + if ( n < target->sizes.count() ) + sizeParticle( p, target->sizes[n] ); + if ( n < target->colors.count() ) + colorParticle( p, target->colors[n] ); + n++; + } + + target->active = list.count(); + target->size = size; + } + + void startParticle( Particle & p ) + { + p.position = random( emitRadius * 2 ) - emitRadius; + p.position += target->worldTrans().rotation.inverted() * ( emitNode->worldTrans().translation - target->worldTrans().translation ); + + float i = inc + random( incRnd ); + float d = dec + random( decRnd ); + + p.velocity = Vector3( rand() & 1 ? sin( i ) : - sin( i ), 0, cos( i ) ); + + Matrix m; m.fromEuler( 0, 0, rand() & 1 ? d : -d ); + p.velocity = m * p.velocity; + + p.velocity = p.velocity * ( spd + random( spdRnd ) ); + p.velocity = target->worldTrans().rotation.inverted() * emitNode->worldTrans().rotation * p.velocity; + + p.lifetime = 0; + p.lifespan = ttl + random( ttlRnd ); + p.lasttime = localtime; + } + + void moveParticle( Particle & p, float deltaTime ) + { + foreach ( Gravity g, grav ) + { + switch ( g.type ) + { + case 0: + p.velocity += g.direction * ( g.force * deltaTime ); + break; + case 1: + { + Vector3 dir = ( g.position - p.position ); + dir.normalize(); + p.velocity += dir * ( g.force * deltaTime ); + } break; + } + } + p.position += p.velocity * deltaTime; + } + + void sizeParticle( Particle & p, float & size ) + { + size = 1.0; + + if ( grow > 0 && p.lifetime < grow ) + size *= p.lifetime / grow; + if ( fade > 0 && p.lifespan - p.lifetime < fade ) + size *= ( p.lifespan - p.lifetime ) / fade; + } + + void colorParticle( Particle & p, Color4 & color ) + { + if ( iColorKeys.isValid() ) + { + int i = 0; + interpolate( color, iColorKeys, p.lifetime / p.lifespan, i ); + } + } +}; + +/* + * Particle + */ + +void Particles::clear() +{ + Node::clear(); + + verts.clear(); + colors.clear(); + transVerts.clear(); +} + +void Particles::update( const NifModel * nif, const QModelIndex & index ) +{ + Node::update( nif, index ); + + if ( ! iBlock.isValid() ) + return; + + upData |= ( iData == index ); + + if ( iBlock == index ) + { + foreach ( int link, nif->getChildLinks( id() ) ) + { + QModelIndex iChild = nif->getBlock( link ); + if ( ! iChild.isValid() ) continue; + QString name = nif->itemName( iChild ); + + if ( name == "NiParticlesData" || name == "NiRotatingParticlesData" || name == "NiAutoNormalParticlesData" ) + { + iData = iChild; + upData = true; + } + } + } +} + +void Particles::setController( const NifModel * nif, const QModelIndex & index ) +{ + if ( nif->itemName( index ) == "NiParticleSystemController" || nif->itemName( index ) == "NiBSPArrayController" ) + { + Controller * ctrl = new ParticleController( this, index ); + ctrl->update( nif, index ); + controllers.append( ctrl ); + } + else + Node::setController( nif, index ); +} + +void Particles::transform() +{ + const NifModel * nif = static_cast( iBlock.model() ); + if ( ! nif || ! iBlock.isValid() ) + { + clear(); + return; + } + + if ( upData ) + { + upData = false; + + verts = nif->getArray( nif->getIndex( iData, "Vertices" ) ); + colors = nif->getArray( nif->getIndex( iData, "Vertex Colors" ) ); + sizes = nif->getArray( nif->getIndex( iData, "Sizes" ) ); + + active = nif->get( iData, "Num Valid" ); + size = nif->get( iData, "Active Radius" ); + } + + Node::transform(); +} + +void Particles::transformShapes() +{ + Node::transformShapes(); + + Transform vtrans = viewTrans(); + + transVerts.resize( verts.count() ); + for ( int v = 0; v < verts.count(); v++ ) + transVerts[v] = vtrans * verts[v]; +} + +BoundSphere Particles::bounds() const +{ + BoundSphere sphere( verts ); + sphere.radius += size; + return worldTrans() * sphere | Node::bounds(); +} + +void Particles::drawShapes( NodeList * draw2nd ) +{ + if ( isHidden() ) + return; + + AlphaProperty * aprop = findProperty< AlphaProperty >(); + if ( aprop && aprop->blend() && draw2nd ) + { + draw2nd->add( this ); + return; + } + + glLoadName( nodeId ); + + // setup blending + + glProperty( findProperty< AlphaProperty >() ); + + // setup vertex colors + + glProperty( findProperty< VertexColorProperty >(), ( colors.count() >= transVerts.count() ) ); + + // setup material + + glProperty( findProperty< MaterialProperty >(), findProperty< SpecularProperty >() ); + + // setup texturing + + glProperty( findProperty< TexturingProperty >() ); + + // setup z buffer + + glProperty( findProperty< ZBufferProperty >() ); + + // setup stencil + + glProperty( findProperty< StencilProperty >() ); + + // wireframe ? + + glProperty( findProperty< WireframeProperty >() ); + + // normalize + + glEnable( GL_NORMALIZE ); + glNormal3f( 0.0, 0.0, -1.0 ); + + // render the particles + + static const Vector2 tex[4] = { Vector2( 1.0, 1.0 ), Vector2( 0.0, 1.0 ), Vector2( 1.0, 0.0 ), Vector2( 0.0, 0.0 ) }; + + int p = 0; + foreach ( Vector3 v, transVerts ) + { + if ( p >= active ) + break; + + GLfloat s2 = ( sizes.count() > p ? sizes[ p ] * size : size ); + s2 *= worldTrans().scale; + + glBegin( GL_TRIANGLE_STRIP ); + if ( p < colors.count() ) + glColor( colors[ p ] ); + glTexCoord( tex[0] ); + glVertex( v + Vector3( + s2, + s2, 0 ) ); + glTexCoord( tex[1] ); + glVertex( v + Vector3( - s2, + s2, 0 ) ); + glTexCoord( tex[2] ); + glVertex( v + Vector3( + s2, - s2, 0 ) ); + glTexCoord( tex[3] ); + glVertex( v + Vector3( - s2, - s2, 0 ) ); + glEnd(); + + p++; + } +} diff --git a/gl/glparticles.h b/gl/glparticles.h index 7c26a99b0..7b3e8c234 100644 --- a/gl/glparticles.h +++ b/gl/glparticles.h @@ -1,70 +1,70 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLPARTICLES_H -#define GLPARTICLES_H - -#include "glnode.h" - -class Particles : public Node -{ -public: - Particles( Scene * s, const QModelIndex & b ) : Node( s, b ) {} - - void clear(); - void update( const NifModel * nif, const QModelIndex & ); - - void transform(); - void transformShapes(); - - void drawShapes( NodeList * draw2nd = 0 ); - - BoundSphere bounds() const; - -protected: - void setController( const NifModel * nif, const QModelIndex & controller ); - - QPersistentModelIndex iData; - bool upData; - - QVector verts; - QVector colors; - QVector sizes; - QVector transVerts; - - int active; - float size; - - friend class ParticleController; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLPARTICLES_H +#define GLPARTICLES_H + +#include "glnode.h" + +class Particles : public Node +{ +public: + Particles( Scene * s, const QModelIndex & b ) : Node( s, b ) {} + + void clear(); + void update( const NifModel * nif, const QModelIndex & ); + + void transform(); + void transformShapes(); + + void drawShapes( NodeList * draw2nd = 0 ); + + BoundSphere bounds() const; + +protected: + void setController( const NifModel * nif, const QModelIndex & controller ); + + QPersistentModelIndex iData; + bool upData; + + QVector verts; + QVector colors; + QVector sizes; + QVector transVerts; + + int active; + float size; + + friend class ParticleController; +}; + +#endif diff --git a/gl/glproperty.cpp b/gl/glproperty.cpp index 6176ffebb..927c1a644 100644 --- a/gl/glproperty.cpp +++ b/gl/glproperty.cpp @@ -1,799 +1,799 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "glproperty.h" -#include "glcontroller.h" -#include "glscene.h" -#include "options.h" - -Property * Property::create( Scene * scene, const NifModel * nif, const QModelIndex & index ) -{ - Property * property = 0; - - if ( nif->isNiBlock( index, "NiAlphaProperty" ) ) - property = new AlphaProperty( scene, index ); - else if ( nif->isNiBlock( index, "NiZBufferProperty" ) ) - property = new ZBufferProperty( scene, index ); - else if ( nif->isNiBlock( index, "NiTexturingProperty" ) ) - property = new TexturingProperty( scene, index ); - else if ( nif->isNiBlock( index, "NiTextureProperty" ) ) - property = new TextureProperty( scene, index ); - else if ( nif->isNiBlock( index, "NiMaterialProperty" ) ) - property = new MaterialProperty( scene, index ); - else if ( nif->isNiBlock( index, "NiSpecularProperty" ) ) - property = new SpecularProperty( scene, index ); - else if ( nif->isNiBlock( index, "NiWireframeProperty" ) ) - property = new WireframeProperty( scene, index ); - else if ( nif->isNiBlock( index, "NiVertexColorProperty" ) ) - property = new VertexColorProperty( scene, index ); - else if ( nif->isNiBlock( index, "NiStencilProperty" ) ) - property = new StencilProperty( scene, index ); - - if ( property ) - property->update( nif, index ); - - return property; -} - -PropertyList::PropertyList() -{ -} - -PropertyList::PropertyList( const PropertyList & other ) -{ - operator=( other ); -} - -PropertyList::~PropertyList() -{ - clear(); -} - -void PropertyList::clear() -{ - foreach( Property * p, properties ) - if ( --p->ref <= 0 ) - delete p; - properties.clear(); -} - -PropertyList & PropertyList::operator=( const PropertyList & other ) -{ - clear(); - foreach ( Property * p, other.properties ) - add( p ); - return *this; -} - -bool PropertyList::contains( Property * p ) const -{ - if ( ! p ) return false; - QList props = properties.values( p->type() ); - return props.contains( p ); -} - -void PropertyList::add( Property * p ) -{ - if ( p && ! contains( p ) ) - { - ++ p->ref; - properties.insert( p->type(), p ); - } -} - -void PropertyList::del( Property * p ) -{ - if ( ! p ) return; - - QHash::iterator i = properties.find( p->type() ); - while ( i != properties.end() && i.key() == p->type() ) - { - if ( i.value() == p ) - { - i = properties.erase( i ); - if ( --p->ref <= 0 ) - delete p; - } - else - ++i; - } -} - -Property * PropertyList::get( const QModelIndex & index ) const -{ - if ( ! index.isValid() ) return 0; - foreach ( Property * p, properties ) - { - if ( p->index() == index ) - return p; - } - return 0; -} - -void PropertyList::validate() -{ - QList rem; - foreach ( Property * p, properties ) - if ( ! p->isValid() ) - rem.append( p ); - foreach ( Property * p, rem ) - del( p ); -} - -void PropertyList::merge( const PropertyList & other ) -{ - foreach ( Property * p, other.properties ) - if ( ! properties.contains( p->type() ) ) - add( p ); -} - -void AlphaProperty::update( const NifModel * nif, const QModelIndex & block ) -{ - Property::update( nif, block ); - - if ( iBlock.isValid() && iBlock == block ) - { - unsigned short flags = nif->get( iBlock, "Flags" ); - - alphaBlend = flags & 1; - - static const GLenum blendMap[16] = { - GL_ONE, GL_ZERO, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, - GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, - GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA_SATURATE, GL_ONE, - GL_ONE, GL_ONE, GL_ONE, GL_ONE - }; - - alphaSrc = blendMap[ ( flags >> 1 ) & 0x0f ]; - alphaDst = blendMap[ ( flags >> 5 ) & 0x0f ]; - - static const GLenum testMap[8] = { - GL_ALWAYS, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_NEVER - }; - - alphaTest = flags & ( 1 << 9 ); - alphaFunc = testMap[ ( flags >> 10 ) & 0x7 ]; - alphaThreshold = nif->get( iBlock, "Threshold" ) / 255.0; - - alphaSort = ( flags & 0x2000 ) == 0; - } -} - -void glProperty( AlphaProperty * p ) -{ - if ( p && p->alphaBlend && Options::blending() ) - { - glEnable( GL_BLEND ); - glBlendFunc( p->alphaSrc, p->alphaDst ); - } - else - glDisable( GL_BLEND ); - - if ( p && p->alphaTest && Options::blending() ) - { - glEnable( GL_ALPHA_TEST ); - glAlphaFunc( p->alphaFunc, p->alphaThreshold ); - } - else - glDisable( GL_ALPHA_TEST ); -} - -void ZBufferProperty::update( const NifModel * nif, const QModelIndex & block ) -{ - Property::update( nif, block ); - - if ( iBlock.isValid() && iBlock == block ) - { - int flags = nif->get( iBlock, "Flags" ); - depthTest = flags & 1; - depthMask = flags & 2; - if ( nif->checkVersion( 0x10000001, 0 ) ) - { - static const GLenum depthMap[8] = { - GL_ALWAYS, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_NEVER - }; - depthFunc = depthMap[ nif->get( iBlock, "Function" ) & 0x07 ]; - } - else - depthFunc = GL_LEQUAL; - } -} - -void glProperty( ZBufferProperty * p ) -{ - if ( p ) - { - if ( p->depthTest ) - { - glEnable( GL_DEPTH_TEST ); - glDepthFunc( p->depthFunc ); - } - else - glDisable( GL_DEPTH_TEST ); - - glDepthMask( p->depthMask ? GL_TRUE : GL_FALSE ); - } - else - { - glEnable( GL_DEPTH_TEST ); - glDepthFunc( GL_LESS ); - glDepthMask( GL_TRUE ); - glDepthFunc( GL_LEQUAL ); - } -} - -void TexturingProperty::update( const NifModel * nif, const QModelIndex & property ) -{ - Property::update( nif, property ); - - if ( iBlock.isValid() && iBlock == property ) - { - static const char * texnames[8] = { "Base Texture", "Dark Texture", "Detail Texture", "Gloss Texture", "Glow Texture", "Bump Map Texture", "Decal 0 Texture", "Decal Texture 1" }; - for ( int t = 0; t < 8; t++ ) - { - QModelIndex iTex = nif->getIndex( property, texnames[t] ); - if ( iTex.isValid() ) - { - textures[t].iSource = nif->getBlock( nif->getLink( iTex, "Source" ), "NiSourceTexture" ); - textures[t].coordset = nif->get( iTex, "UV Set" ); - switch ( nif->get( iTex, "Filter Mode" ) ) - { - case 0: textures[t].filter = GL_NEAREST; break; - case 1: textures[t].filter = GL_LINEAR; break; - case 2: textures[t].filter = GL_NEAREST_MIPMAP_NEAREST; break; - case 3: textures[t].filter = GL_LINEAR_MIPMAP_NEAREST; break; - case 4: textures[t].filter = GL_NEAREST_MIPMAP_LINEAR; break; - case 5: textures[t].filter = GL_LINEAR_MIPMAP_LINEAR; break; - default: textures[t].filter = GL_LINEAR; break; - } - switch ( nif->get( iTex, "Clamp Mode" ) ) - { - case 0: textures[t].wrapS = GL_CLAMP; textures[t].wrapT = GL_CLAMP; break; - case 1: textures[t].wrapS = GL_CLAMP; textures[t].wrapT = GL_REPEAT; break; - case 2: textures[t].wrapS = GL_REPEAT; textures[t].wrapT = GL_CLAMP; break; - default: textures[t].wrapS = GL_REPEAT; textures[t].wrapT = GL_REPEAT; break; - } - - textures[t].hasTransform = nif->get( iTex, "Has Texture Transform" ); - if ( textures[t].hasTransform ) - { - textures[t].translation = nif->get( iTex, "Translation" ); - textures[t].tiling = nif->get( iTex, "Tiling" ); - textures[t].rotation = nif->get( iTex, "W Rotation" ); - textures[t].center = nif->get( iTex, "Center Offset" ); - } - else - { - textures[t].translation = Vector2(); - textures[t].tiling = Vector2( 1.0, 1.0 ); - textures[t].rotation = 0.0; - textures[t].center = Vector2( 0.5, 0.5 ); - } - } - else - { - textures[t].iSource = QModelIndex(); - } - } - } -} - -QString TexturingProperty::fileName( int id ) const -{ - if ( id >= 0 && id <= 7 ) - { - QModelIndex iSource = textures[ id ].iSource; - const NifModel * nif = qobject_cast( iSource.model() ); - if ( nif && iSource.isValid() ) { - return nif->get( iSource, "File Name" ); - } - } - return QString(); -} - -int TexturingProperty::coordSet( int id ) const -{ - if ( id >= 0 && id <= 7 ) - { - return textures[id].coordset; - } - return -1; -} - - -class TexFlipController : public Controller -{ -public: - TexFlipController( TexturingProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), flipDelta( 0 ), flipSlot( 0 ) {} - - void update( float time ) - { - const NifModel * nif = static_cast( iSources.model() ); - if ( ! ( target && active && iSources.isValid() && nif ) ) - return; - - float r = 0; - - if ( iData.isValid() ) - interpolate( r, iData, "Data", ctrlTime( time ), flipLast ); - else if ( flipDelta > 0 ) - r = ctrlTime( time ) / flipDelta; - - target->textures[flipSlot & 7 ].iSource = nif->getBlock( nif->getLink( iSources.child( (int) r, 0 ) ), "NiSourceTexture" ); - } - - bool update( const NifModel * nif, const QModelIndex & index ) - { - if ( Controller::update( nif, index ) ) - { - flipDelta = nif->get( iBlock, "Delta" ); - flipSlot = nif->get( iBlock, "Texture Slot" ); - - iSources = nif->getIndex( iBlock, "Sources" ); - return true; - } - return false; - } - -protected: - QPointer target; - - float flipDelta; - int flipSlot; - - int flipLast; - - QPersistentModelIndex iSources; -}; - -class TexTransController : public Controller -{ -public: - TexTransController( TexturingProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), texSlot( 0 ), texOP( 0 ) {} - - void update( float time ) - { - if ( ! ( target && active ) ) - return; - - TexturingProperty::TexDesc * tex = & target->textures[ texSlot & 7 ]; - - float val; - if ( interpolate( val, iData, "Data", ctrlTime( time ), lX ) ) - { - switch ( texOP ) - { - case 0: - tex->translation[0] = val; - break; - case 1: - tex->translation[1] = val; - break; - case 2: - tex->rotation = val; - break; - case 3: - tex->tiling[0] = val; - break; - case 4: - tex->tiling[1] = val; - break; - } - } - } - - bool update( const NifModel * nif, const QModelIndex & index ) - { - if ( Controller::update( nif, index ) ) - { - texSlot = nif->get( iBlock, "Texture Slot" ); - texOP = nif->get( iBlock, "Operation" ); - return true; - } - return false; - } - -protected: - QPointer target; - - int texSlot; - int texOP; - - int lX; -}; - -void TexturingProperty::setController( const NifModel * nif, const QModelIndex & iController ) -{ - if ( nif->itemName( iController ) == "NiFlipController" ) - { - Controller * ctrl = new TexFlipController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } - else if ( nif->itemName( iController ) == "NiTextureTransformController" ) - { - Controller * ctrl = new TexTransController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } -} - -int TexturingProperty::getId( const QString & texname ) -{ - static QHash hash; - if ( hash.isEmpty() ) - { - hash.insert( "base", 0 ); - hash.insert( "dark", 1 ); - hash.insert( "detail", 2 ); - hash.insert( "gloss", 3 ); - hash.insert( "glow", 4 ); - hash.insert( "bumpmap", 5 ); - hash.insert( "decal0", 6 ); - hash.insert( "decal1", 7 ); - } - if ( hash.contains( texname ) ) - return hash[ texname ]; - else - return -1; -} - -void glProperty( TexturingProperty * p ) -{ - if ( p && Options::texturing() && p->bind( 0 ) ) - { - glEnable( GL_TEXTURE_2D ); - } - else - { - glDisable( GL_TEXTURE_2D ); - } -} - - -void TextureProperty::update( const NifModel * nif, const QModelIndex & property ) -{ - Property::update( nif, property ); - - if ( iBlock.isValid() && iBlock == property ) - { - iImage = nif->getBlock( nif->getLink( iBlock, "Image" ), "NiImage" ); - } -} - -QString TextureProperty::fileName() const -{ - const NifModel * nif = qobject_cast( iImage.model() ); - if ( nif && iImage.isValid() ) - return nif->get( iImage, "File Name" ); - return QString(); -} - -void glProperty( TextureProperty * p ) -{ - if ( p && Options::texturing() && p->bind() ) - { - glEnable( GL_TEXTURE_2D ); - } - else - { - glDisable( GL_TEXTURE_2D ); - } -} - - -void MaterialProperty::update( const NifModel * nif, const QModelIndex & index ) -{ - Property::update( nif, index ); - - if ( iBlock.isValid() && iBlock == index ) - { - alpha = nif->get( index, "Alpha" ); - if ( alpha < 0.0 ) alpha = 0.0; - if ( alpha > 1.0 ) alpha = 1.0; - - ambient = Color4( nif->get( index, "Ambient Color" ) ); - diffuse = Color4( nif->get( index, "Diffuse Color" ) ); - specular = Color4( nif->get( index, "Specular Color" ) ); - emissive = Color4( nif->get( index, "Emissive Color" ) ); - - shininess = nif->get( index, "Glossiness" ); - } -} - -class AlphaController : public Controller -{ -public: - AlphaController( MaterialProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), lAlpha( 0 ) {} - - void update( float time ) - { - if ( ! ( active && target ) ) - return; - - interpolate( target->alpha, iData, "Data", ctrlTime( time ), lAlpha ); - - if ( target->alpha < 0 ) - target->alpha = 0; - if ( target->alpha > 1 ) - target->alpha = 1; - } - -protected: - QPointer target; - - int lAlpha; -}; - -class MaterialColorController : public Controller -{ -public: - MaterialColorController( MaterialProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), lColor( 0 ), tColor( tAmbient ) {} - - void update( float time ) - { - if ( ! ( active && target ) ) - return; - - Vector3 v3; - interpolate( v3, iData, "Data", ctrlTime( time ), lColor ); - - Color4 color( Color3( v3 ), 1.0 ); - - switch ( tColor ) - { - case tAmbient: - target->ambient = color; - break; - case tDiffuse: - target->diffuse = color; - break; - case tSpecular: - target->specular = color; - break; - case tSelfIllum: - target->emissive = color; - break; - } - } - - bool update( const NifModel * nif, const QModelIndex & index ) - { - if ( Controller::update( nif, index ) ) - { - tColor = nif->get( iBlock, "Target Color" ); - return true; - } - return false; - } - -protected: - QPointer target; - - int lColor; - int tColor; - - enum { - tAmbient = 0, - tDiffuse = 1, - tSpecular = 2, - tSelfIllum = 3 - }; - -}; - -void MaterialProperty::setController( const NifModel * nif, const QModelIndex & iController ) -{ - if ( nif->itemName( iController ) == "NiAlphaController" ) - { - Controller * ctrl = new AlphaController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } - else if ( nif->itemName( iController ) == "NiMaterialColorController" ) - { - Controller * ctrl = new MaterialColorController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } -} - -void glProperty( MaterialProperty * p, SpecularProperty * s ) -{ - if ( p ) - { - glMaterial( GL_FRONT_AND_BACK, GL_AMBIENT, p->ambient.blend( p->alpha ) ); - glMaterial( GL_FRONT_AND_BACK, GL_DIFFUSE, p->diffuse.blend( p->alpha ) ); - glMaterial( GL_FRONT_AND_BACK, GL_EMISSION, p->emissive.blend( p->alpha ) ); - - if ( ! s || s->spec ) - { - glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, p->shininess ); - glMaterial( GL_FRONT_AND_BACK, GL_SPECULAR, p->specular.blend( p->alpha ) ); - } - else - { - glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, 0.0 ); - glMaterial( GL_FRONT_AND_BACK, GL_SPECULAR, Color4( 0.0, 0.0, 0.0, p->alpha ) ); - } - } - else - { - Color4 a( 0.4f, 0.4f, 0.4f, 1.0f ); - Color4 d( 0.8f, 0.8f, 0.8f, 1.0f ); - Color4 s( 1.0f, 1.0f, 1.0f, 1.0f ); - glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, 33.0f ); - glMaterial( GL_FRONT_AND_BACK, GL_AMBIENT, a ); - glMaterial( GL_FRONT_AND_BACK, GL_DIFFUSE, d ); - glMaterial( GL_FRONT_AND_BACK, GL_SPECULAR, s ); - } -} - -void SpecularProperty::update( const NifModel * nif, const QModelIndex & block ) -{ - Property::update( nif, block ); - if ( iBlock.isValid() && iBlock == block ) - { - spec = nif->get( iBlock, "Flags" ) != 0; - } -} - -void WireframeProperty::update( const NifModel * nif, const QModelIndex & block ) -{ - Property::update( nif, block ); - if ( iBlock.isValid() && iBlock == block ) - { - wire = nif->get( iBlock, "Flags" ) != 0; - } -} - -void glProperty( WireframeProperty * p ) -{ - if ( p && p->wire ) - { - glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - glLineWidth( 1.0 ); - } - else - { - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - } -} - -void VertexColorProperty::update( const NifModel * nif, const QModelIndex & block ) -{ - Property::update( nif, block ); - if ( iBlock.isValid() && iBlock == block ) - { - vertexmode = nif->get( iBlock, "Vertex Mode" ); - // 0 : source ignore - // 1 : source emissive - // 2 : source ambient + diffuse - lightmode = nif->get( iBlock, "Lighting Mode" ); - // 0 : emissive - // 1 : emissive + ambient + diffuse - } -} - -void glProperty( VertexColorProperty * p, bool vertexcolors ) -{ - // FIXME - - if ( ! vertexcolors ) - { - glDisable( GL_COLOR_MATERIAL ); - glColor( Color4( 1.0, 1.0, 1.0, 1.0 ) ); - return; - } - - if ( p ) - { - //if ( p->lightmode ) - { - switch ( p->vertexmode ) - { - case 0: - glDisable( GL_COLOR_MATERIAL ); - glColor( Color4( 1.0, 1.0, 1.0, 1.0 ) ); - return; - case 1: - glEnable( GL_COLOR_MATERIAL ); - glColorMaterial( GL_FRONT_AND_BACK, GL_EMISSION ); - return; - case 2: - default: - glEnable( GL_COLOR_MATERIAL ); - glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ); - return; - } - } - //else - //{ - // glDisable( GL_LIGHTING ); - // glDisable( GL_COLOR_MATERIAL ); - //} - } - else - { - glEnable( GL_COLOR_MATERIAL ); - glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ); - } -} - -void StencilProperty::update( const NifModel * nif, const QModelIndex & block ) -{ - Property::update( nif, block ); - if ( iBlock.isValid() && iBlock == block ) - { - //static const GLenum functions[8] = { GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS }; - //static const GLenum operations[8] = { GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_KEEP, GL_KEEP }; - - // ! glFrontFace( GL_CCW ) - switch ( nif->get( iBlock, "Draw Mode" ) ) - { - case 2: - cullEnable = true; - cullMode = GL_FRONT; - break; - case 3: - cullEnable = false; - cullMode = GL_BACK; - break; - case 1: - default: - cullEnable = true; - cullMode = GL_BACK; - break; - } - } -} - -void glProperty( StencilProperty * p ) -{ - if ( p ) - { - if ( p->cullEnable ) - glEnable( GL_CULL_FACE ); - else - glDisable( GL_CULL_FACE ); - glCullFace( p->cullMode ); - } - else - { - glEnable( GL_CULL_FACE ); - glCullFace( GL_BACK ); - } -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "glproperty.h" +#include "glcontroller.h" +#include "glscene.h" +#include "options.h" + +Property * Property::create( Scene * scene, const NifModel * nif, const QModelIndex & index ) +{ + Property * property = 0; + + if ( nif->isNiBlock( index, "NiAlphaProperty" ) ) + property = new AlphaProperty( scene, index ); + else if ( nif->isNiBlock( index, "NiZBufferProperty" ) ) + property = new ZBufferProperty( scene, index ); + else if ( nif->isNiBlock( index, "NiTexturingProperty" ) ) + property = new TexturingProperty( scene, index ); + else if ( nif->isNiBlock( index, "NiTextureProperty" ) ) + property = new TextureProperty( scene, index ); + else if ( nif->isNiBlock( index, "NiMaterialProperty" ) ) + property = new MaterialProperty( scene, index ); + else if ( nif->isNiBlock( index, "NiSpecularProperty" ) ) + property = new SpecularProperty( scene, index ); + else if ( nif->isNiBlock( index, "NiWireframeProperty" ) ) + property = new WireframeProperty( scene, index ); + else if ( nif->isNiBlock( index, "NiVertexColorProperty" ) ) + property = new VertexColorProperty( scene, index ); + else if ( nif->isNiBlock( index, "NiStencilProperty" ) ) + property = new StencilProperty( scene, index ); + + if ( property ) + property->update( nif, index ); + + return property; +} + +PropertyList::PropertyList() +{ +} + +PropertyList::PropertyList( const PropertyList & other ) +{ + operator=( other ); +} + +PropertyList::~PropertyList() +{ + clear(); +} + +void PropertyList::clear() +{ + foreach( Property * p, properties ) + if ( --p->ref <= 0 ) + delete p; + properties.clear(); +} + +PropertyList & PropertyList::operator=( const PropertyList & other ) +{ + clear(); + foreach ( Property * p, other.properties ) + add( p ); + return *this; +} + +bool PropertyList::contains( Property * p ) const +{ + if ( ! p ) return false; + QList props = properties.values( p->type() ); + return props.contains( p ); +} + +void PropertyList::add( Property * p ) +{ + if ( p && ! contains( p ) ) + { + ++ p->ref; + properties.insert( p->type(), p ); + } +} + +void PropertyList::del( Property * p ) +{ + if ( ! p ) return; + + QHash::iterator i = properties.find( p->type() ); + while ( i != properties.end() && i.key() == p->type() ) + { + if ( i.value() == p ) + { + i = properties.erase( i ); + if ( --p->ref <= 0 ) + delete p; + } + else + ++i; + } +} + +Property * PropertyList::get( const QModelIndex & index ) const +{ + if ( ! index.isValid() ) return 0; + foreach ( Property * p, properties ) + { + if ( p->index() == index ) + return p; + } + return 0; +} + +void PropertyList::validate() +{ + QList rem; + foreach ( Property * p, properties ) + if ( ! p->isValid() ) + rem.append( p ); + foreach ( Property * p, rem ) + del( p ); +} + +void PropertyList::merge( const PropertyList & other ) +{ + foreach ( Property * p, other.properties ) + if ( ! properties.contains( p->type() ) ) + add( p ); +} + +void AlphaProperty::update( const NifModel * nif, const QModelIndex & block ) +{ + Property::update( nif, block ); + + if ( iBlock.isValid() && iBlock == block ) + { + unsigned short flags = nif->get( iBlock, "Flags" ); + + alphaBlend = flags & 1; + + static const GLenum blendMap[16] = { + GL_ONE, GL_ZERO, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, + GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA_SATURATE, GL_ONE, + GL_ONE, GL_ONE, GL_ONE, GL_ONE + }; + + alphaSrc = blendMap[ ( flags >> 1 ) & 0x0f ]; + alphaDst = blendMap[ ( flags >> 5 ) & 0x0f ]; + + static const GLenum testMap[8] = { + GL_ALWAYS, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_NEVER + }; + + alphaTest = flags & ( 1 << 9 ); + alphaFunc = testMap[ ( flags >> 10 ) & 0x7 ]; + alphaThreshold = nif->get( iBlock, "Threshold" ) / 255.0; + + alphaSort = ( flags & 0x2000 ) == 0; + } +} + +void glProperty( AlphaProperty * p ) +{ + if ( p && p->alphaBlend && Options::blending() ) + { + glEnable( GL_BLEND ); + glBlendFunc( p->alphaSrc, p->alphaDst ); + } + else + glDisable( GL_BLEND ); + + if ( p && p->alphaTest && Options::blending() ) + { + glEnable( GL_ALPHA_TEST ); + glAlphaFunc( p->alphaFunc, p->alphaThreshold ); + } + else + glDisable( GL_ALPHA_TEST ); +} + +void ZBufferProperty::update( const NifModel * nif, const QModelIndex & block ) +{ + Property::update( nif, block ); + + if ( iBlock.isValid() && iBlock == block ) + { + int flags = nif->get( iBlock, "Flags" ); + depthTest = flags & 1; + depthMask = flags & 2; + if ( nif->checkVersion( 0x10000001, 0 ) ) + { + static const GLenum depthMap[8] = { + GL_ALWAYS, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_NEVER + }; + depthFunc = depthMap[ nif->get( iBlock, "Function" ) & 0x07 ]; + } + else + depthFunc = GL_LEQUAL; + } +} + +void glProperty( ZBufferProperty * p ) +{ + if ( p ) + { + if ( p->depthTest ) + { + glEnable( GL_DEPTH_TEST ); + glDepthFunc( p->depthFunc ); + } + else + glDisable( GL_DEPTH_TEST ); + + glDepthMask( p->depthMask ? GL_TRUE : GL_FALSE ); + } + else + { + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_LESS ); + glDepthMask( GL_TRUE ); + glDepthFunc( GL_LEQUAL ); + } +} + +void TexturingProperty::update( const NifModel * nif, const QModelIndex & property ) +{ + Property::update( nif, property ); + + if ( iBlock.isValid() && iBlock == property ) + { + static const char * texnames[8] = { "Base Texture", "Dark Texture", "Detail Texture", "Gloss Texture", "Glow Texture", "Bump Map Texture", "Decal 0 Texture", "Decal Texture 1" }; + for ( int t = 0; t < 8; t++ ) + { + QModelIndex iTex = nif->getIndex( property, texnames[t] ); + if ( iTex.isValid() ) + { + textures[t].iSource = nif->getBlock( nif->getLink( iTex, "Source" ), "NiSourceTexture" ); + textures[t].coordset = nif->get( iTex, "UV Set" ); + switch ( nif->get( iTex, "Filter Mode" ) ) + { + case 0: textures[t].filter = GL_NEAREST; break; + case 1: textures[t].filter = GL_LINEAR; break; + case 2: textures[t].filter = GL_NEAREST_MIPMAP_NEAREST; break; + case 3: textures[t].filter = GL_LINEAR_MIPMAP_NEAREST; break; + case 4: textures[t].filter = GL_NEAREST_MIPMAP_LINEAR; break; + case 5: textures[t].filter = GL_LINEAR_MIPMAP_LINEAR; break; + default: textures[t].filter = GL_LINEAR; break; + } + switch ( nif->get( iTex, "Clamp Mode" ) ) + { + case 0: textures[t].wrapS = GL_CLAMP; textures[t].wrapT = GL_CLAMP; break; + case 1: textures[t].wrapS = GL_CLAMP; textures[t].wrapT = GL_REPEAT; break; + case 2: textures[t].wrapS = GL_REPEAT; textures[t].wrapT = GL_CLAMP; break; + default: textures[t].wrapS = GL_REPEAT; textures[t].wrapT = GL_REPEAT; break; + } + + textures[t].hasTransform = nif->get( iTex, "Has Texture Transform" ); + if ( textures[t].hasTransform ) + { + textures[t].translation = nif->get( iTex, "Translation" ); + textures[t].tiling = nif->get( iTex, "Tiling" ); + textures[t].rotation = nif->get( iTex, "W Rotation" ); + textures[t].center = nif->get( iTex, "Center Offset" ); + } + else + { + textures[t].translation = Vector2(); + textures[t].tiling = Vector2( 1.0, 1.0 ); + textures[t].rotation = 0.0; + textures[t].center = Vector2( 0.5, 0.5 ); + } + } + else + { + textures[t].iSource = QModelIndex(); + } + } + } +} + +QString TexturingProperty::fileName( int id ) const +{ + if ( id >= 0 && id <= 7 ) + { + QModelIndex iSource = textures[ id ].iSource; + const NifModel * nif = qobject_cast( iSource.model() ); + if ( nif && iSource.isValid() ) { + return nif->get( iSource, "File Name" ); + } + } + return QString(); +} + +int TexturingProperty::coordSet( int id ) const +{ + if ( id >= 0 && id <= 7 ) + { + return textures[id].coordset; + } + return -1; +} + + +class TexFlipController : public Controller +{ +public: + TexFlipController( TexturingProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ), flipDelta( 0 ), flipSlot( 0 ) {} + + void update( float time ) + { + const NifModel * nif = static_cast( iSources.model() ); + if ( ! ( target && active && iSources.isValid() && nif ) ) + return; + + float r = 0; + + if ( iData.isValid() ) + interpolate( r, iData, "Data", ctrlTime( time ), flipLast ); + else if ( flipDelta > 0 ) + r = ctrlTime( time ) / flipDelta; + + target->textures[flipSlot & 7 ].iSource = nif->getBlock( nif->getLink( iSources.child( (int) r, 0 ) ), "NiSourceTexture" ); + } + + bool update( const NifModel * nif, const QModelIndex & index ) + { + if ( Controller::update( nif, index ) ) + { + flipDelta = nif->get( iBlock, "Delta" ); + flipSlot = nif->get( iBlock, "Texture Slot" ); + + iSources = nif->getIndex( iBlock, "Sources" ); + return true; + } + return false; + } + +protected: + QPointer target; + + float flipDelta; + int flipSlot; + + int flipLast; + + QPersistentModelIndex iSources; +}; + +class TexTransController : public Controller +{ +public: + TexTransController( TexturingProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ), texSlot( 0 ), texOP( 0 ) {} + + void update( float time ) + { + if ( ! ( target && active ) ) + return; + + TexturingProperty::TexDesc * tex = & target->textures[ texSlot & 7 ]; + + float val; + if ( interpolate( val, iData, "Data", ctrlTime( time ), lX ) ) + { + switch ( texOP ) + { + case 0: + tex->translation[0] = val; + break; + case 1: + tex->translation[1] = val; + break; + case 2: + tex->rotation = val; + break; + case 3: + tex->tiling[0] = val; + break; + case 4: + tex->tiling[1] = val; + break; + } + } + } + + bool update( const NifModel * nif, const QModelIndex & index ) + { + if ( Controller::update( nif, index ) ) + { + texSlot = nif->get( iBlock, "Texture Slot" ); + texOP = nif->get( iBlock, "Operation" ); + return true; + } + return false; + } + +protected: + QPointer target; + + int texSlot; + int texOP; + + int lX; +}; + +void TexturingProperty::setController( const NifModel * nif, const QModelIndex & iController ) +{ + if ( nif->itemName( iController ) == "NiFlipController" ) + { + Controller * ctrl = new TexFlipController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } + else if ( nif->itemName( iController ) == "NiTextureTransformController" ) + { + Controller * ctrl = new TexTransController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } +} + +int TexturingProperty::getId( const QString & texname ) +{ + static QHash hash; + if ( hash.isEmpty() ) + { + hash.insert( "base", 0 ); + hash.insert( "dark", 1 ); + hash.insert( "detail", 2 ); + hash.insert( "gloss", 3 ); + hash.insert( "glow", 4 ); + hash.insert( "bumpmap", 5 ); + hash.insert( "decal0", 6 ); + hash.insert( "decal1", 7 ); + } + if ( hash.contains( texname ) ) + return hash[ texname ]; + else + return -1; +} + +void glProperty( TexturingProperty * p ) +{ + if ( p && Options::texturing() && p->bind( 0 ) ) + { + glEnable( GL_TEXTURE_2D ); + } + else + { + glDisable( GL_TEXTURE_2D ); + } +} + + +void TextureProperty::update( const NifModel * nif, const QModelIndex & property ) +{ + Property::update( nif, property ); + + if ( iBlock.isValid() && iBlock == property ) + { + iImage = nif->getBlock( nif->getLink( iBlock, "Image" ), "NiImage" ); + } +} + +QString TextureProperty::fileName() const +{ + const NifModel * nif = qobject_cast( iImage.model() ); + if ( nif && iImage.isValid() ) + return nif->get( iImage, "File Name" ); + return QString(); +} + +void glProperty( TextureProperty * p ) +{ + if ( p && Options::texturing() && p->bind() ) + { + glEnable( GL_TEXTURE_2D ); + } + else + { + glDisable( GL_TEXTURE_2D ); + } +} + + +void MaterialProperty::update( const NifModel * nif, const QModelIndex & index ) +{ + Property::update( nif, index ); + + if ( iBlock.isValid() && iBlock == index ) + { + alpha = nif->get( index, "Alpha" ); + if ( alpha < 0.0 ) alpha = 0.0; + if ( alpha > 1.0 ) alpha = 1.0; + + ambient = Color4( nif->get( index, "Ambient Color" ) ); + diffuse = Color4( nif->get( index, "Diffuse Color" ) ); + specular = Color4( nif->get( index, "Specular Color" ) ); + emissive = Color4( nif->get( index, "Emissive Color" ) ); + + shininess = nif->get( index, "Glossiness" ); + } +} + +class AlphaController : public Controller +{ +public: + AlphaController( MaterialProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ), lAlpha( 0 ) {} + + void update( float time ) + { + if ( ! ( active && target ) ) + return; + + interpolate( target->alpha, iData, "Data", ctrlTime( time ), lAlpha ); + + if ( target->alpha < 0 ) + target->alpha = 0; + if ( target->alpha > 1 ) + target->alpha = 1; + } + +protected: + QPointer target; + + int lAlpha; +}; + +class MaterialColorController : public Controller +{ +public: + MaterialColorController( MaterialProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ), lColor( 0 ), tColor( tAmbient ) {} + + void update( float time ) + { + if ( ! ( active && target ) ) + return; + + Vector3 v3; + interpolate( v3, iData, "Data", ctrlTime( time ), lColor ); + + Color4 color( Color3( v3 ), 1.0 ); + + switch ( tColor ) + { + case tAmbient: + target->ambient = color; + break; + case tDiffuse: + target->diffuse = color; + break; + case tSpecular: + target->specular = color; + break; + case tSelfIllum: + target->emissive = color; + break; + } + } + + bool update( const NifModel * nif, const QModelIndex & index ) + { + if ( Controller::update( nif, index ) ) + { + tColor = nif->get( iBlock, "Target Color" ); + return true; + } + return false; + } + +protected: + QPointer target; + + int lColor; + int tColor; + + enum { + tAmbient = 0, + tDiffuse = 1, + tSpecular = 2, + tSelfIllum = 3 + }; + +}; + +void MaterialProperty::setController( const NifModel * nif, const QModelIndex & iController ) +{ + if ( nif->itemName( iController ) == "NiAlphaController" ) + { + Controller * ctrl = new AlphaController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } + else if ( nif->itemName( iController ) == "NiMaterialColorController" ) + { + Controller * ctrl = new MaterialColorController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } +} + +void glProperty( MaterialProperty * p, SpecularProperty * s ) +{ + if ( p ) + { + glMaterial( GL_FRONT_AND_BACK, GL_AMBIENT, p->ambient.blend( p->alpha ) ); + glMaterial( GL_FRONT_AND_BACK, GL_DIFFUSE, p->diffuse.blend( p->alpha ) ); + glMaterial( GL_FRONT_AND_BACK, GL_EMISSION, p->emissive.blend( p->alpha ) ); + + if ( ! s || s->spec ) + { + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, p->shininess ); + glMaterial( GL_FRONT_AND_BACK, GL_SPECULAR, p->specular.blend( p->alpha ) ); + } + else + { + glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, 0.0 ); + glMaterial( GL_FRONT_AND_BACK, GL_SPECULAR, Color4( 0.0, 0.0, 0.0, p->alpha ) ); + } + } + else + { + Color4 a( 0.4f, 0.4f, 0.4f, 1.0f ); + Color4 d( 0.8f, 0.8f, 0.8f, 1.0f ); + Color4 s( 1.0f, 1.0f, 1.0f, 1.0f ); + glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, 33.0f ); + glMaterial( GL_FRONT_AND_BACK, GL_AMBIENT, a ); + glMaterial( GL_FRONT_AND_BACK, GL_DIFFUSE, d ); + glMaterial( GL_FRONT_AND_BACK, GL_SPECULAR, s ); + } +} + +void SpecularProperty::update( const NifModel * nif, const QModelIndex & block ) +{ + Property::update( nif, block ); + if ( iBlock.isValid() && iBlock == block ) + { + spec = nif->get( iBlock, "Flags" ) != 0; + } +} + +void WireframeProperty::update( const NifModel * nif, const QModelIndex & block ) +{ + Property::update( nif, block ); + if ( iBlock.isValid() && iBlock == block ) + { + wire = nif->get( iBlock, "Flags" ) != 0; + } +} + +void glProperty( WireframeProperty * p ) +{ + if ( p && p->wire ) + { + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + glLineWidth( 1.0 ); + } + else + { + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } +} + +void VertexColorProperty::update( const NifModel * nif, const QModelIndex & block ) +{ + Property::update( nif, block ); + if ( iBlock.isValid() && iBlock == block ) + { + vertexmode = nif->get( iBlock, "Vertex Mode" ); + // 0 : source ignore + // 1 : source emissive + // 2 : source ambient + diffuse + lightmode = nif->get( iBlock, "Lighting Mode" ); + // 0 : emissive + // 1 : emissive + ambient + diffuse + } +} + +void glProperty( VertexColorProperty * p, bool vertexcolors ) +{ + // FIXME + + if ( ! vertexcolors ) + { + glDisable( GL_COLOR_MATERIAL ); + glColor( Color4( 1.0, 1.0, 1.0, 1.0 ) ); + return; + } + + if ( p ) + { + //if ( p->lightmode ) + { + switch ( p->vertexmode ) + { + case 0: + glDisable( GL_COLOR_MATERIAL ); + glColor( Color4( 1.0, 1.0, 1.0, 1.0 ) ); + return; + case 1: + glEnable( GL_COLOR_MATERIAL ); + glColorMaterial( GL_FRONT_AND_BACK, GL_EMISSION ); + return; + case 2: + default: + glEnable( GL_COLOR_MATERIAL ); + glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ); + return; + } + } + //else + //{ + // glDisable( GL_LIGHTING ); + // glDisable( GL_COLOR_MATERIAL ); + //} + } + else + { + glEnable( GL_COLOR_MATERIAL ); + glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ); + } +} + +void StencilProperty::update( const NifModel * nif, const QModelIndex & block ) +{ + Property::update( nif, block ); + if ( iBlock.isValid() && iBlock == block ) + { + //static const GLenum functions[8] = { GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS }; + //static const GLenum operations[8] = { GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_KEEP, GL_KEEP }; + + // ! glFrontFace( GL_CCW ) + switch ( nif->get( iBlock, "Draw Mode" ) ) + { + case 2: + cullEnable = true; + cullMode = GL_FRONT; + break; + case 3: + cullEnable = false; + cullMode = GL_BACK; + break; + case 1: + default: + cullEnable = true; + cullMode = GL_BACK; + break; + } + } +} + +void glProperty( StencilProperty * p ) +{ + if ( p ) + { + if ( p->cullEnable ) + glEnable( GL_CULL_FACE ); + else + glDisable( GL_CULL_FACE ); + glCullFace( p->cullMode ); + } + else + { + glEnable( GL_CULL_FACE ); + glCullFace( GL_BACK ); + } +} diff --git a/gl/glproperty.h b/gl/glproperty.h index 7710ddb3b..ef7fc01ae 100644 --- a/gl/glproperty.h +++ b/gl/glproperty.h @@ -1,352 +1,352 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLPROPERTY_H -#define GLPROPERTY_H - -#include - -#include "glcontrolable.h" - -class Property : public Controllable -{ -protected: - Property( Scene * scene, const QModelIndex & index ) : Controllable( scene, index ), ref( 0 ) {} - - int ref; - - friend class PropertyList; - -public: - static Property * create( Scene * scene, const NifModel * nif, const QModelIndex & index ); - - enum Type - { - Alpha, ZBuffer, Material, Texturing, Texture, Specular, Wireframe, VertexColor, Stencil - }; - - virtual Type type() const = 0; - virtual QString typeId() const = 0; - - template static Type _type(); - template T * cast() - { - if ( type() == _type() ) - return static_cast( this ); - else - return 0; - } -}; - -#define REGISTER_PROPERTY( CLASSNAME, TYPENAME ) template <> inline Property::Type Property::_type< CLASSNAME >() { return Property::TYPENAME; } - -class PropertyList -{ -public: - PropertyList(); - PropertyList( const PropertyList & other ); - ~PropertyList(); - - void add( Property * ); - void del( Property * ); - bool contains( Property * ) const; - - Property * get( const QModelIndex & idx ) const; - - template T * get() const; - template bool contains() const; - - void validate(); - - void clear(); - - PropertyList & operator=( const PropertyList & other ); - - QList list() const { return properties.values(); } - - void merge( const PropertyList & list ); - -protected: - QMultiHash properties; -}; - -template inline T * PropertyList::get() const -{ - Property * p = properties.value( Property::_type() ); - if ( p ) - return p->cast(); - else - return 0; -} - -template inline bool PropertyList::contains() const -{ - return properties.contains( Property::_type() ); -} - - -class AlphaProperty : public Property -{ -public: - AlphaProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - - Type type() const { return Alpha; } - QString typeId() const { return "NiAlphaProperty"; } - - void update( const NifModel * nif, const QModelIndex & block ); - - bool blend() const { return alphaBlend; } - bool test() const { return alphaTest; } - bool sort() const { return alphaSort; } - - friend void glProperty( AlphaProperty * ); - -protected: - bool alphaBlend, alphaTest, alphaSort; - GLenum alphaSrc, alphaDst, alphaFunc; - GLfloat alphaThreshold; -}; - -REGISTER_PROPERTY( AlphaProperty, Alpha ) - - -class ZBufferProperty : public Property -{ -public: - ZBufferProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - - Type type() const { return ZBuffer; } - QString typeId() const { return "NiZBufferProperty"; } - - void update( const NifModel * nif, const QModelIndex & block ); - - bool test() const { return depthTest; } - bool mask() const { return depthMask; } - - friend void glProperty( ZBufferProperty * ); - -protected: - bool depthTest; - bool depthMask; - GLenum depthFunc; -}; - -REGISTER_PROPERTY( ZBufferProperty, ZBuffer ) - - -class TexturingProperty : public Property -{ - struct TexDesc - { - QPersistentModelIndex iSource; - GLenum filter; - GLint wrapS, wrapT; - int coordset; - - bool hasTransform; - - Vector2 translation; - Vector2 tiling; - float rotation; - Vector2 center; - }; -public: - TexturingProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - - Type type() const { return Texturing; } - QString typeId() const { return "NiTexturingProperty"; } - - void update( const NifModel * nif, const QModelIndex & block ); - - friend void glProperty( TexturingProperty * ); - - bool bind( int id, const QString & fname = QString() ); - - bool bind( int id, const QList< QVector > & texcoords ); - bool bind( int id, const QList< QVector > & texcoords, int stage ); - - QString fileName( int id ) const; - int coordSet( int id ) const; - - static int getId( const QString & id ); - -protected: - TexDesc textures[8]; - - void setController( const NifModel * nif, const QModelIndex & controller ); - - friend class TexFlipController; - friend class TexTransController; -}; - -REGISTER_PROPERTY( TexturingProperty, Texturing ) - - -class TextureProperty : public Property -{ -public: - TextureProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - - Type type() const { return Texture; } - QString typeId() const { return "NiTextureProperty"; } - - void update( const NifModel * nif, const QModelIndex & block ); - - friend void glProperty( TextureProperty * ); - - bool bind(); - bool bind( const QList< QVector > & texcoords ); - - QString fileName() const; - -protected: - QPersistentModelIndex iImage; -}; - -REGISTER_PROPERTY( TextureProperty, Texture ) - - -class MaterialProperty : public Property -{ -public: - MaterialProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - - Type type() const { return Material; } - QString typeId() const { return "NiMaterialProperty"; } - - void update( const NifModel * nif, const QModelIndex & block ); - - friend void glProperty( class MaterialProperty *, class SpecularProperty * ); - - GLfloat alphaValue() const { return alpha; } - -protected: - Color4 ambient, diffuse, specular, emissive; - GLfloat shininess, alpha; - - void setController( const NifModel * nif, const QModelIndex & controller ); - - friend class AlphaController; - friend class MaterialColorController; -}; - -REGISTER_PROPERTY( MaterialProperty, Material ) - - -class SpecularProperty : public Property -{ -public: - SpecularProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - - Type type() const { return Specular; } - QString typeId() const { return "NiSpecularProperty"; } - - void update( const NifModel * nif, const QModelIndex & index ); - - friend void glProperty( class MaterialProperty *, class SpecularProperty * ); - -protected: - bool spec; -}; - -REGISTER_PROPERTY( SpecularProperty, Specular ) - - -class WireframeProperty : public Property -{ -public: - WireframeProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - - Type type() const { return Wireframe; } - QString typeId() const { return "NiWireframeProperty"; } - - void update( const NifModel * nif, const QModelIndex & index ); - - friend void glProperty( WireframeProperty * ); - -protected: - bool wire; -}; - -REGISTER_PROPERTY( WireframeProperty, Wireframe ) - - -class VertexColorProperty : public Property -{ -public: - VertexColorProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - - Type type() const { return VertexColor; } - QString typeId() const { return "NiVertexColorProperty"; } - - void update( const NifModel * nif, const QModelIndex & index ); - - friend void glProperty( VertexColorProperty *, bool vertexcolors ); - -protected: - int lightmode; - int vertexmode; -}; - -REGISTER_PROPERTY( VertexColorProperty, VertexColor ) - - -class StencilProperty : public Property -{ -public: - StencilProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - - Type type() const { return Stencil; } - QString typeId() const { return "NiStencilProperty"; } - - void update( const NifModel * nif, const QModelIndex & index ); - - friend void glProperty( StencilProperty * ); - -protected: - bool stencil; - - GLenum func; - GLint ref; - GLuint mask; - - GLenum failop; - GLenum zfailop; - GLenum zpassop; - - bool cullEnable; - GLenum cullMode; -}; - -REGISTER_PROPERTY( StencilProperty, Stencil ) - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLPROPERTY_H +#define GLPROPERTY_H + +#include + +#include "glcontrolable.h" + +class Property : public Controllable +{ +protected: + Property( Scene * scene, const QModelIndex & index ) : Controllable( scene, index ), ref( 0 ) {} + + int ref; + + friend class PropertyList; + +public: + static Property * create( Scene * scene, const NifModel * nif, const QModelIndex & index ); + + enum Type + { + Alpha, ZBuffer, Material, Texturing, Texture, Specular, Wireframe, VertexColor, Stencil + }; + + virtual Type type() const = 0; + virtual QString typeId() const = 0; + + template static Type _type(); + template T * cast() + { + if ( type() == _type() ) + return static_cast( this ); + else + return 0; + } +}; + +#define REGISTER_PROPERTY( CLASSNAME, TYPENAME ) template <> inline Property::Type Property::_type< CLASSNAME >() { return Property::TYPENAME; } + +class PropertyList +{ +public: + PropertyList(); + PropertyList( const PropertyList & other ); + ~PropertyList(); + + void add( Property * ); + void del( Property * ); + bool contains( Property * ) const; + + Property * get( const QModelIndex & idx ) const; + + template T * get() const; + template bool contains() const; + + void validate(); + + void clear(); + + PropertyList & operator=( const PropertyList & other ); + + QList list() const { return properties.values(); } + + void merge( const PropertyList & list ); + +protected: + QMultiHash properties; +}; + +template inline T * PropertyList::get() const +{ + Property * p = properties.value( Property::_type() ); + if ( p ) + return p->cast(); + else + return 0; +} + +template inline bool PropertyList::contains() const +{ + return properties.contains( Property::_type() ); +} + + +class AlphaProperty : public Property +{ +public: + AlphaProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + + Type type() const { return Alpha; } + QString typeId() const { return "NiAlphaProperty"; } + + void update( const NifModel * nif, const QModelIndex & block ); + + bool blend() const { return alphaBlend; } + bool test() const { return alphaTest; } + bool sort() const { return alphaSort; } + + friend void glProperty( AlphaProperty * ); + +protected: + bool alphaBlend, alphaTest, alphaSort; + GLenum alphaSrc, alphaDst, alphaFunc; + GLfloat alphaThreshold; +}; + +REGISTER_PROPERTY( AlphaProperty, Alpha ) + + +class ZBufferProperty : public Property +{ +public: + ZBufferProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + + Type type() const { return ZBuffer; } + QString typeId() const { return "NiZBufferProperty"; } + + void update( const NifModel * nif, const QModelIndex & block ); + + bool test() const { return depthTest; } + bool mask() const { return depthMask; } + + friend void glProperty( ZBufferProperty * ); + +protected: + bool depthTest; + bool depthMask; + GLenum depthFunc; +}; + +REGISTER_PROPERTY( ZBufferProperty, ZBuffer ) + + +class TexturingProperty : public Property +{ + struct TexDesc + { + QPersistentModelIndex iSource; + GLenum filter; + GLint wrapS, wrapT; + int coordset; + + bool hasTransform; + + Vector2 translation; + Vector2 tiling; + float rotation; + Vector2 center; + }; +public: + TexturingProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + + Type type() const { return Texturing; } + QString typeId() const { return "NiTexturingProperty"; } + + void update( const NifModel * nif, const QModelIndex & block ); + + friend void glProperty( TexturingProperty * ); + + bool bind( int id, const QString & fname = QString() ); + + bool bind( int id, const QList< QVector > & texcoords ); + bool bind( int id, const QList< QVector > & texcoords, int stage ); + + QString fileName( int id ) const; + int coordSet( int id ) const; + + static int getId( const QString & id ); + +protected: + TexDesc textures[8]; + + void setController( const NifModel * nif, const QModelIndex & controller ); + + friend class TexFlipController; + friend class TexTransController; +}; + +REGISTER_PROPERTY( TexturingProperty, Texturing ) + + +class TextureProperty : public Property +{ +public: + TextureProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + + Type type() const { return Texture; } + QString typeId() const { return "NiTextureProperty"; } + + void update( const NifModel * nif, const QModelIndex & block ); + + friend void glProperty( TextureProperty * ); + + bool bind(); + bool bind( const QList< QVector > & texcoords ); + + QString fileName() const; + +protected: + QPersistentModelIndex iImage; +}; + +REGISTER_PROPERTY( TextureProperty, Texture ) + + +class MaterialProperty : public Property +{ +public: + MaterialProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + + Type type() const { return Material; } + QString typeId() const { return "NiMaterialProperty"; } + + void update( const NifModel * nif, const QModelIndex & block ); + + friend void glProperty( class MaterialProperty *, class SpecularProperty * ); + + GLfloat alphaValue() const { return alpha; } + +protected: + Color4 ambient, diffuse, specular, emissive; + GLfloat shininess, alpha; + + void setController( const NifModel * nif, const QModelIndex & controller ); + + friend class AlphaController; + friend class MaterialColorController; +}; + +REGISTER_PROPERTY( MaterialProperty, Material ) + + +class SpecularProperty : public Property +{ +public: + SpecularProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + + Type type() const { return Specular; } + QString typeId() const { return "NiSpecularProperty"; } + + void update( const NifModel * nif, const QModelIndex & index ); + + friend void glProperty( class MaterialProperty *, class SpecularProperty * ); + +protected: + bool spec; +}; + +REGISTER_PROPERTY( SpecularProperty, Specular ) + + +class WireframeProperty : public Property +{ +public: + WireframeProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + + Type type() const { return Wireframe; } + QString typeId() const { return "NiWireframeProperty"; } + + void update( const NifModel * nif, const QModelIndex & index ); + + friend void glProperty( WireframeProperty * ); + +protected: + bool wire; +}; + +REGISTER_PROPERTY( WireframeProperty, Wireframe ) + + +class VertexColorProperty : public Property +{ +public: + VertexColorProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + + Type type() const { return VertexColor; } + QString typeId() const { return "NiVertexColorProperty"; } + + void update( const NifModel * nif, const QModelIndex & index ); + + friend void glProperty( VertexColorProperty *, bool vertexcolors ); + +protected: + int lightmode; + int vertexmode; +}; + +REGISTER_PROPERTY( VertexColorProperty, VertexColor ) + + +class StencilProperty : public Property +{ +public: + StencilProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} + + Type type() const { return Stencil; } + QString typeId() const { return "NiStencilProperty"; } + + void update( const NifModel * nif, const QModelIndex & index ); + + friend void glProperty( StencilProperty * ); + +protected: + bool stencil; + + GLenum func; + GLint ref; + GLuint mask; + + GLenum failop; + GLenum zfailop; + GLenum zpassop; + + bool cullEnable; + GLenum cullMode; +}; + +REGISTER_PROPERTY( StencilProperty, Stencil ) + +#endif diff --git a/gl/glscene.cpp b/gl/glscene.cpp index 741ff4e0a..a42d5d1d1 100644 --- a/gl/glscene.cpp +++ b/gl/glscene.cpp @@ -1,348 +1,348 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "glscene.h" - -#include "glcontroller.h" -#include "glnode.h" -#include "glmesh.h" -#include "glparticles.h" -#include "gltex.h" -#include "options.h" - - -Scene::Scene( TexCache * texcache ) -{ - currentBlock = currentIndex = QModelIndex(); - animate = true; - - time = 0.0; - sceneBoundsValid = timeBoundsValid = false; - - textures = texcache; -} - -Scene::~Scene() -{ -} - -void Scene::clear( bool flushTextures ) -{ - nodes.clear(); - properties.clear(); - roots.clear(); - - animGroups.clear(); - animTags.clear(); - - //if ( flushTextures ) - textures->flush(); - - sceneBoundsValid = timeBoundsValid = false; -} - -void Scene::update( const NifModel * nif, const QModelIndex & index ) -{ - if ( ! nif ) - return; - - if ( index.isValid() ) - { - QModelIndex block = nif->getBlock( index ); - if ( ! block.isValid() ) - return; - - foreach ( Property * prop, properties.list() ) - prop->update( nif, block ); - - foreach ( Node * node, nodes.list() ) - node->update( nif, block ); - } - else - { - properties.validate(); - nodes.validate(); - - foreach ( Node * n, nodes.list() ) - n->update( nif, QModelIndex() ); - foreach ( Property * p, properties.list() ) - p->update( nif, QModelIndex() ); - - roots.clear(); - foreach ( int link, nif->getRootLinks() ) - { - QModelIndex iBlock = nif->getBlock( link ); - if ( iBlock.isValid() ) - { - Node * node = getNode( nif, iBlock ); - if ( node ) - { - node->makeParent( 0 ); - roots.add( node ); - } - } - } - } - - timeBoundsValid = false; -} - -void Scene::make( NifModel * nif, bool flushTextures ) -{ - clear( flushTextures ); - if ( ! nif ) return; - update( nif, QModelIndex() ); - if ( ! animGroups.contains( animGroup ) ) - { - if ( animGroups.isEmpty() ) - animGroup = QString(); - else - animGroup = animGroups.first(); - } - setSequence( animGroup ); -} - -Node * Scene::getNode( const NifModel * nif, const QModelIndex & iNode ) -{ - if ( ! ( nif && iNode.isValid() ) ) - return 0; - - Node * node = nodes.get( iNode ); - if ( node ) return node; - - if ( nif->inherits( iNode, "NiNode" ) ) - { - if ( nif->itemName( iNode ) == "NiLODNode" ) - node = new LODNode( this, iNode ); - else if ( nif->itemName( iNode ) == "NiBillboardNode" ) - node = new BillboardNode( this, iNode ); - else - node = new Node( this, iNode ); - } - else if ( nif->itemName( iNode ) == "NiTriShape" || nif->itemName( iNode ) == "NiTriStrips" ) - { - node = new Mesh( this, iNode ); - } - //else if ( nif->inherits( iNode, "AParticleNode" ) || nif->inherits( iNode, "AParticleSystem" ) ) - else if ( nif->inherits( iNode, "NiParticles" ) ) // ... where did AParticleSystem go? - { - node = new Particles( this, iNode ); - } - - if ( node ) - { - nodes.add( node ); - node->update( nif, iNode ); - } - - return node; -} - -Property * Scene::getProperty( const NifModel * nif, const QModelIndex & iProperty ) -{ - Property * prop = properties.get( iProperty ); - if ( prop ) - return prop; - prop = Property::create( this, nif, iProperty ); - if ( prop ) - properties.add( prop ); - return prop; -} - -void Scene::setSequence( const QString & seqname ) -{ - animGroup = seqname; - - foreach ( Node * node, nodes.list() ) - node->setSequence( seqname ); - foreach ( Property * prop, properties.list() ) - prop->setSequence( seqname ); - - timeBoundsValid = false; -} - -void Scene::transform( const Transform & trans, float time ) -{ - view = trans; - this->time = time; - - worldTrans.clear(); - viewTrans.clear(); - bhkBodyTrans.clear(); - - foreach ( Property * prop, properties.list() ) - prop->transform(); - - foreach ( Node * node, roots.list() ) - node->transform(); - foreach ( Node * node, roots.list() ) - node->transformShapes(); - - sceneBoundsValid = false; - - // TODO: purge unused textures -} - -void Scene::draw() -{ - drawShapes(); - - if ( Options::drawNodes() ) - drawNodes(); - if ( Options::drawHavok() ) - drawHavok(); - if ( Options::drawFurn() ) - drawFurn(); - - drawSelection(); -} - -void Scene::drawShapes() -{ - if ( Options::blending() ) - { - NodeList draw2nd; - - foreach ( Node * node, roots.list() ) - node->drawShapes( &draw2nd ); - - draw2nd.sort(); - - foreach ( Node * node, draw2nd.list() ) - node->drawShapes(); - } - else - { - foreach ( Node * node, roots.list() ) - node->drawShapes(); - } -} - -void Scene::drawNodes() -{ - foreach ( Node * node, roots.list() ) - node->draw(); -} - -void Scene::drawHavok() -{ - foreach ( Node * node, roots.list() ) - node->drawHavok(); -} - -void Scene::drawFurn() -{ - foreach ( Node * node, roots.list() ) - node->drawFurn(); -} - -void Scene::drawSelection() const -{ - foreach ( Node * node, nodes.list() ) - node->drawSelection(); -} - -BoundSphere Scene::bounds() const -{ - if ( ! sceneBoundsValid ) - { - bndSphere = BoundSphere(); - foreach ( Node * node, nodes.list() ) - { - if ( node->isVisible() ) - bndSphere |= node->bounds(); - } - sceneBoundsValid = true; - } - return bndSphere; -} - -void Scene::updateTimeBounds() const -{ - if ( ! nodes.list().isEmpty() ) - { - tMin = +1000000000; tMax = -1000000000; - foreach ( Node * node, nodes.list() ) - node->timeBounds( tMin, tMax ); - foreach ( Property * prop, properties.list() ) - prop->timeBounds( tMin, tMax ); - } - else - tMin = tMax = 0; - timeBoundsValid = true; -} - -float Scene::timeMin() const -{ - if ( animTags.contains( animGroup ) ) - { - if ( animTags[ animGroup ].contains( "start" ) ) - return animTags[ animGroup ][ "start" ]; - } - if ( ! timeBoundsValid ) - updateTimeBounds(); - return ( tMin > tMax ? 0 : tMin ); -} - -float Scene::timeMax() const -{ - if ( animTags.contains( animGroup ) ) - { - if ( animTags[ animGroup ].contains( "end" ) ) - return animTags[ animGroup ][ "end" ]; - } - - if ( ! timeBoundsValid ) - updateTimeBounds(); - return ( tMin > tMax ? 0 : tMax ); -} - -QString Scene::textStats() -{ - foreach ( Node * node, nodes.list() ) - { - if ( node->index() == currentBlock ) - { - return node->textStats(); - } - } - return QString(); -} - -int Scene::bindTexture( const QString & fname ) -{ - if ( ! Options::texturing() || fname.isEmpty() ) - return 0; - - return textures->bind( fname ); -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "glscene.h" + +#include "glcontroller.h" +#include "glnode.h" +#include "glmesh.h" +#include "glparticles.h" +#include "gltex.h" +#include "options.h" + + +Scene::Scene( TexCache * texcache ) +{ + currentBlock = currentIndex = QModelIndex(); + animate = true; + + time = 0.0; + sceneBoundsValid = timeBoundsValid = false; + + textures = texcache; +} + +Scene::~Scene() +{ +} + +void Scene::clear( bool flushTextures ) +{ + nodes.clear(); + properties.clear(); + roots.clear(); + + animGroups.clear(); + animTags.clear(); + + //if ( flushTextures ) + textures->flush(); + + sceneBoundsValid = timeBoundsValid = false; +} + +void Scene::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( ! nif ) + return; + + if ( index.isValid() ) + { + QModelIndex block = nif->getBlock( index ); + if ( ! block.isValid() ) + return; + + foreach ( Property * prop, properties.list() ) + prop->update( nif, block ); + + foreach ( Node * node, nodes.list() ) + node->update( nif, block ); + } + else + { + properties.validate(); + nodes.validate(); + + foreach ( Node * n, nodes.list() ) + n->update( nif, QModelIndex() ); + foreach ( Property * p, properties.list() ) + p->update( nif, QModelIndex() ); + + roots.clear(); + foreach ( int link, nif->getRootLinks() ) + { + QModelIndex iBlock = nif->getBlock( link ); + if ( iBlock.isValid() ) + { + Node * node = getNode( nif, iBlock ); + if ( node ) + { + node->makeParent( 0 ); + roots.add( node ); + } + } + } + } + + timeBoundsValid = false; +} + +void Scene::make( NifModel * nif, bool flushTextures ) +{ + clear( flushTextures ); + if ( ! nif ) return; + update( nif, QModelIndex() ); + if ( ! animGroups.contains( animGroup ) ) + { + if ( animGroups.isEmpty() ) + animGroup = QString(); + else + animGroup = animGroups.first(); + } + setSequence( animGroup ); +} + +Node * Scene::getNode( const NifModel * nif, const QModelIndex & iNode ) +{ + if ( ! ( nif && iNode.isValid() ) ) + return 0; + + Node * node = nodes.get( iNode ); + if ( node ) return node; + + if ( nif->inherits( iNode, "NiNode" ) ) + { + if ( nif->itemName( iNode ) == "NiLODNode" ) + node = new LODNode( this, iNode ); + else if ( nif->itemName( iNode ) == "NiBillboardNode" ) + node = new BillboardNode( this, iNode ); + else + node = new Node( this, iNode ); + } + else if ( nif->itemName( iNode ) == "NiTriShape" || nif->itemName( iNode ) == "NiTriStrips" ) + { + node = new Mesh( this, iNode ); + } + //else if ( nif->inherits( iNode, "AParticleNode" ) || nif->inherits( iNode, "AParticleSystem" ) ) + else if ( nif->inherits( iNode, "NiParticles" ) ) // ... where did AParticleSystem go? + { + node = new Particles( this, iNode ); + } + + if ( node ) + { + nodes.add( node ); + node->update( nif, iNode ); + } + + return node; +} + +Property * Scene::getProperty( const NifModel * nif, const QModelIndex & iProperty ) +{ + Property * prop = properties.get( iProperty ); + if ( prop ) + return prop; + prop = Property::create( this, nif, iProperty ); + if ( prop ) + properties.add( prop ); + return prop; +} + +void Scene::setSequence( const QString & seqname ) +{ + animGroup = seqname; + + foreach ( Node * node, nodes.list() ) + node->setSequence( seqname ); + foreach ( Property * prop, properties.list() ) + prop->setSequence( seqname ); + + timeBoundsValid = false; +} + +void Scene::transform( const Transform & trans, float time ) +{ + view = trans; + this->time = time; + + worldTrans.clear(); + viewTrans.clear(); + bhkBodyTrans.clear(); + + foreach ( Property * prop, properties.list() ) + prop->transform(); + + foreach ( Node * node, roots.list() ) + node->transform(); + foreach ( Node * node, roots.list() ) + node->transformShapes(); + + sceneBoundsValid = false; + + // TODO: purge unused textures +} + +void Scene::draw() +{ + drawShapes(); + + if ( Options::drawNodes() ) + drawNodes(); + if ( Options::drawHavok() ) + drawHavok(); + if ( Options::drawFurn() ) + drawFurn(); + + drawSelection(); +} + +void Scene::drawShapes() +{ + if ( Options::blending() ) + { + NodeList draw2nd; + + foreach ( Node * node, roots.list() ) + node->drawShapes( &draw2nd ); + + draw2nd.sort(); + + foreach ( Node * node, draw2nd.list() ) + node->drawShapes(); + } + else + { + foreach ( Node * node, roots.list() ) + node->drawShapes(); + } +} + +void Scene::drawNodes() +{ + foreach ( Node * node, roots.list() ) + node->draw(); +} + +void Scene::drawHavok() +{ + foreach ( Node * node, roots.list() ) + node->drawHavok(); +} + +void Scene::drawFurn() +{ + foreach ( Node * node, roots.list() ) + node->drawFurn(); +} + +void Scene::drawSelection() const +{ + foreach ( Node * node, nodes.list() ) + node->drawSelection(); +} + +BoundSphere Scene::bounds() const +{ + if ( ! sceneBoundsValid ) + { + bndSphere = BoundSphere(); + foreach ( Node * node, nodes.list() ) + { + if ( node->isVisible() ) + bndSphere |= node->bounds(); + } + sceneBoundsValid = true; + } + return bndSphere; +} + +void Scene::updateTimeBounds() const +{ + if ( ! nodes.list().isEmpty() ) + { + tMin = +1000000000; tMax = -1000000000; + foreach ( Node * node, nodes.list() ) + node->timeBounds( tMin, tMax ); + foreach ( Property * prop, properties.list() ) + prop->timeBounds( tMin, tMax ); + } + else + tMin = tMax = 0; + timeBoundsValid = true; +} + +float Scene::timeMin() const +{ + if ( animTags.contains( animGroup ) ) + { + if ( animTags[ animGroup ].contains( "start" ) ) + return animTags[ animGroup ][ "start" ]; + } + if ( ! timeBoundsValid ) + updateTimeBounds(); + return ( tMin > tMax ? 0 : tMin ); +} + +float Scene::timeMax() const +{ + if ( animTags.contains( animGroup ) ) + { + if ( animTags[ animGroup ].contains( "end" ) ) + return animTags[ animGroup ][ "end" ]; + } + + if ( ! timeBoundsValid ) + updateTimeBounds(); + return ( tMin > tMax ? 0 : tMax ); +} + +QString Scene::textStats() +{ + foreach ( Node * node, nodes.list() ) + { + if ( node->index() == currentBlock ) + { + return node->textStats(); + } + } + return QString(); +} + +int Scene::bindTexture( const QString & fname ) +{ + if ( ! Options::texturing() || fname.isEmpty() ) + return 0; + + return textures->bind( fname ); +} + diff --git a/gl/glscene.h b/gl/glscene.h index 79128e467..2a7e0a9f7 100644 --- a/gl/glscene.h +++ b/gl/glscene.h @@ -1,119 +1,119 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLSCENE_H -#define GLSCENE_H - -#include -#include - -#include "nifmodel.h" - -#include "glnode.h" -#include "glproperty.h" -#include "gltex.h" -#include "gltools.h" - -#include "renderer.h" - -class Scene -{ -public: - Scene( TexCache * texcache ); - ~Scene(); - - void updateShaders() { renderer.updateShaders(); } - - void clear( bool flushTextures = true ); - void make( NifModel * nif, bool flushTextures = false ); - void make( NifModel * nif, int blockNumber, QStack & nodestack ); - - void update( const NifModel * nif, const QModelIndex & index ); - - void transform( const Transform & trans, float time = 0.0 ); - - void draw(); - void drawShapes(); - void drawNodes(); - void drawHavok(); - void drawFurn(); - void drawSelection() const; - - void setSequence( const QString & seqname ); - - QString textStats(); - - int bindTexture( const QString & fname ); - - Node * getNode( const NifModel * nif, const QModelIndex & iNode ); - Property * getProperty( const NifModel * nif, const QModelIndex & iProperty ); - - Renderer renderer; - - NodeList nodes; - PropertyList properties; - - NodeList roots; - - mutable QHash worldTrans; - mutable QHash viewTrans; - mutable QHash bhkBodyTrans; - - Transform view; - - bool animate; - - float time; - - QString animGroup; - QStringList animGroups; - QMap > animTags; - - TexCache * textures; - - QPersistentModelIndex currentBlock; - QPersistentModelIndex currentIndex; - - BoundSphere bounds() const; - - float timeMin() const; - float timeMax() const; - -protected: - mutable bool sceneBoundsValid, timeBoundsValid; - mutable BoundSphere bndSphere; - mutable float tMin, tMax; - - void updateTimeBounds() const; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLSCENE_H +#define GLSCENE_H + +#include +#include + +#include "nifmodel.h" + +#include "glnode.h" +#include "glproperty.h" +#include "gltex.h" +#include "gltools.h" + +#include "renderer.h" + +class Scene +{ +public: + Scene( TexCache * texcache ); + ~Scene(); + + void updateShaders() { renderer.updateShaders(); } + + void clear( bool flushTextures = true ); + void make( NifModel * nif, bool flushTextures = false ); + void make( NifModel * nif, int blockNumber, QStack & nodestack ); + + void update( const NifModel * nif, const QModelIndex & index ); + + void transform( const Transform & trans, float time = 0.0 ); + + void draw(); + void drawShapes(); + void drawNodes(); + void drawHavok(); + void drawFurn(); + void drawSelection() const; + + void setSequence( const QString & seqname ); + + QString textStats(); + + int bindTexture( const QString & fname ); + + Node * getNode( const NifModel * nif, const QModelIndex & iNode ); + Property * getProperty( const NifModel * nif, const QModelIndex & iProperty ); + + Renderer renderer; + + NodeList nodes; + PropertyList properties; + + NodeList roots; + + mutable QHash worldTrans; + mutable QHash viewTrans; + mutable QHash bhkBodyTrans; + + Transform view; + + bool animate; + + float time; + + QString animGroup; + QStringList animGroups; + QMap > animTags; + + TexCache * textures; + + QPersistentModelIndex currentBlock; + QPersistentModelIndex currentIndex; + + BoundSphere bounds() const; + + float timeMin() const; + float timeMax() const; + +protected: + mutable bool sceneBoundsValid, timeBoundsValid; + mutable BoundSphere bndSphere; + mutable float tMin, tMax; + + void updateTimeBounds() const; +}; + +#endif diff --git a/gl/gltex.cpp b/gl/gltex.cpp index f5f4fd4ea..06c8a1b11 100644 --- a/gl/gltex.cpp +++ b/gl/gltex.cpp @@ -1,458 +1,458 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifdef QT_OPENGL_LIB - -#include - -#include -#include -#include -#include - -#include "glscene.h" -#include "gltex.h" -#include "gltexloaders.h" -#include "options.h" - -#include - -PFNGLACTIVETEXTUREARBPROC _glActiveTextureARB = 0; -PFNGLCLIENTACTIVETEXTUREARBPROC _glClientActiveTextureARB = 0; -PFNGLCOMPRESSEDTEXIMAGE2DPROC _glCompressedTexImage2D = 0; - -int num_texture_units = 0; -float max_anisotropy = 0; - -void initializeTextureUnits( const QGLContext * context ) -{ - QString extensions( (const char *) glGetString(GL_EXTENSIONS) ); - //foreach ( QString e, extensions.split( " " ) ) - // qWarning() << e; - - //if (!extensions.contains("GL_ARB_texture_compression")) - // qWarning() << "texture compression not supported, some textures may not load"; - - // *** check disabled: software decompression is supported *** - //if (!extensions.contains("GL_EXT_texture_compression_s3tc")) - // qWarning() << "S3TC texture compression not supported, some textures may not load"; - - _glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC) context->getProcAddress( "glCompressedTexImage2D" ); - if ( ! _glCompressedTexImage2D ) - _glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC) context->getProcAddress( "glCompressedTexImage2DARB" ); - - if ( ! _glCompressedTexImage2D ) - qWarning( "texture compression not supported" ); - - _glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) context->getProcAddress( "glActiveTextureARB" ); - _glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) context->getProcAddress( "glClientActiveTextureARB" ); - - if ( ! _glActiveTextureARB || ! _glClientActiveTextureARB ) - { - qWarning( "multitexturing not supported" ); - num_texture_units = 1; - } - else - { - glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &num_texture_units ); - if ( num_texture_units < 1 ) - num_texture_units = 1; - //qWarning() << "texture units" << num_texture_units; - } - - if ( extensions.contains( "GL_EXT_texture_filter_anisotropic" ) ) - { - glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, & max_anisotropy ); - //qWarning() << "maximum anisotropy" << max_anisotropy; - } -} - -bool activateTextureUnit( int stage ) -{ - if ( num_texture_units <= 1 ) - return ( stage == 0 ); - - if ( stage < num_texture_units ) - { - _glActiveTextureARB( GL_TEXTURE0_ARB + stage ); - _glClientActiveTextureARB( GL_TEXTURE0_ARB + stage ); - return true; - } - else - return false; -} - -void resetTextureUnits() -{ - if ( num_texture_units <= 1 ) - { - glDisable( GL_TEXTURE_2D ); - return; - } - - for ( int x = num_texture_units-1; x >= 0; x-- ) - { - _glActiveTextureARB( GL_TEXTURE0_ARB + x ); - glDisable( GL_TEXTURE_2D ); - glMatrixMode( GL_TEXTURE ); - glLoadIdentity(); - glMatrixMode( GL_MODELVIEW ); - _glClientActiveTextureARB( GL_TEXTURE0_ARB + x ); - glDisableClientState( GL_TEXTURE_COORD_ARRAY ); - } -} - - -/* - TexCache -*/ - -TexCache::TexCache( QObject * parent ) : QObject( parent ) -{ - watcher = new QFileSystemWatcher( this ); - connect( watcher, SIGNAL( fileChanged( const QString & ) ), this, SLOT( fileChanged( const QString & ) ) ); -} - -TexCache::~TexCache() -{ - //flush(); -} - -QString TexCache::find( const QString & file, const QString & nifdir ) -{ - if ( file.isEmpty() ) - return QString(); - -#ifndef WIN32 - /* convert nif path backslash into forward slash */ - /* also check for both original name and lower case name */ - QString filename_orig = QString(file).replace( "\\", "/" );; - QString filename = file.toLower().replace( "\\", "/" ); -#else - QString filename = file.toLower(); -#endif - - while ( filename.startsWith( "/" ) || filename.startsWith( "\\" ) ) - filename.remove( 0, 1 ); - - QStringList extensions; - extensions << ".tga" << ".dds" << ".bmp"; -#ifndef WIN32 - extensions << ".TGA" << ".DDS" << ".BMP"; -#endif - bool replaceExt = false; - if ( Options::textureAlternatives() ) - { - foreach ( QString ext, extensions ) - { - if ( filename.endsWith( ext ) ) - { - extensions.removeAll( ext ); - extensions.prepend( ext ); - filename = filename.left( filename.length() - ext.length() ); -#ifndef WIN32 - filename_orig = filename_orig.left( filename_orig.length() - ext.length() ); -#endif - replaceExt = true; - break; - } - } - } - - // attempt to find the texture in one of the folders - QDir dir; - foreach ( QString ext, extensions ) - { - if ( replaceExt ) { - filename += ext; -#ifndef WIN32 - filename_orig += ext; -#endif - } - - foreach ( QString folder, Options::textureFolders() ) - { - if( folder.startsWith( "./" ) || folder.startsWith( ".\\" ) ) { - folder = nifdir + "/" + folder; - } - - dir.setPath( folder ); -#ifndef WIN32 - //qWarning() << folder << filename; -#endif - if ( dir.exists( filename ) ) - return dir.filePath( filename ); -#ifndef WIN32 - //qWarning() << folder << filename_orig; - if ( dir.exists( filename_orig ) ) - return dir.filePath( filename_orig ); -#endif - } - - if ( replaceExt ) { - filename = filename.left( filename.length() - ext.length() ); -#ifndef WIN32 - filename_orig = filename_orig.left( filename_orig.length() - ext.length() ); -#endif - } else - break; - } - - if ( replaceExt ) - return filename + extensions.value( 0 ); - else - return filename; -} - -QString TexCache::stripPath( const QString & filepath, const QString & nifFolder ) -{ - QString file = filepath; - file = file.replace( "/", "\\" ).toLower(); - - foreach ( QString base, Options::textureFolders() ) - { - if( base.startsWith( "./" ) || base.startsWith( ".\\" ) ) { - base = nifFolder + "/" + base; - } - base = base.replace( "/", "\\" ).toLower(); - - if ( file.startsWith( base ) ) - { - file.remove( 0, base.length() ); - break; - } - } - - if ( file.startsWith( "/" ) || file.startsWith( "\\" ) ) - file.remove( 0, 1 ); - - return file; -} - -bool TexCache::canLoad( const QString & filePath ) -{ - return texCanLoad( filePath ); -} - -void TexCache::fileChanged( const QString & filepath ) -{ - QMutableHashIterator it( textures ); - while ( it.hasNext() ) - { - it.next(); - - Tex * tx = it.value(); - - if ( tx->filepath == filepath ) - { - if ( QFile::exists( tx->filepath ) ) - { - tx->reload = true; - emit sigRefresh(); - } - else - { - it.remove(); - if ( tx->id ) - glDeleteTextures( 1, &tx->id ); - delete tx; - } - } - } -} - -int TexCache::bind( const QString & fname ) -{ - Tex * tx = textures.value( fname ); - if ( ! tx ) - { - tx = new Tex; - tx->filename = fname; - tx->id = 0; - tx->mipmaps = 0; - tx->reload = false; - - textures.insert( tx->filename, tx ); - } - - if ( tx->filepath.isEmpty() || tx->reload ) - tx->filepath = find( tx->filename, nifFolder ); - - if ( ! tx->id || tx->reload ) - { - if ( QFile::exists( tx->filepath ) && QFileInfo( tx->filepath ).isWritable() ) - watcher->addPath( tx->filepath ); - tx->load(); - } - - glBindTexture( GL_TEXTURE_2D, tx->id ); - - return tx->mipmaps; -} - -void TexCache::Tex::load() -{ - if ( ! id ) - glGenTextures( 1, &id ); - - width = height = mipmaps = 0; - reload = false; - status = QString(); - - glBindTexture( GL_TEXTURE_2D, id ); - - try - { - texLoad( filepath, format, width, height, mipmaps ); - } - catch ( QString e ) - { - status = e; - } -} - -void TexCache::flush() -{ - foreach ( Tex * tx, textures ) - { - if ( tx->id ) - glDeleteTextures( 1, &tx->id ); - } - qDeleteAll( textures ); - textures.clear(); - - if( !watcher->files().empty() ) { - watcher->removePaths( watcher->files() ); - } -} - -bool TexturingProperty::bind( int id, const QString & fname ) -{ - GLuint mipmaps = 0; - if ( id >= 0 && id <= 7 && ( mipmaps = scene->bindTexture( fname.isEmpty() ? fileName( id ) : fname ) ) ) - { - if ( max_anisotropy > 0 ) - { - if ( Options::antialias() ) - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy ); - else - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0 ); - } - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, textures[id].wrapS ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, textures[id].wrapT ); - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - glMatrixMode( GL_TEXTURE ); - glLoadIdentity(); - if ( textures[id].hasTransform ) - { - glTranslatef( - textures[id].center[0], - textures[id].center[1], 0 ); - glRotatef( textures[id].rotation, 0, 0, 1 ); - glTranslatef( textures[id].center[0], textures[id].center[1], 0 ); - glScalef( textures[id].tiling[0], textures[id].tiling[1], 1 ); - glTranslatef( textures[id].translation[0], textures[id].translation[1], 0 ); - } - glMatrixMode( GL_MODELVIEW ); - return true; - } - else - return false; -} - -bool checkSet( int s, const QList< QVector< Vector2 > > & texcoords ) -{ - return s >= 0 && s < texcoords.count() && texcoords[s].count(); -} - -bool TexturingProperty::bind( int id, const QList< QVector< Vector2 > > & texcoords ) -{ - if ( checkSet( textures[id].coordset, texcoords ) && bind( id ) ) - { - glEnable( GL_TEXTURE_2D ); - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 2, GL_FLOAT, 0, texcoords[ textures[id].coordset ].data() ); - return true; - } - else - { - glDisable( GL_TEXTURE_2D ); - return false; - } -} - -bool TexturingProperty::bind( int id, const QList< QVector< Vector2 > > & texcoords, int stage ) -{ - return ( activateTextureUnit( stage ) && bind( id, texcoords ) ); -} - -bool TextureProperty::bind() -{ - if ( GLuint mipmaps = scene->bindTexture( fileName() ) ) - { - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ); - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - glMatrixMode( GL_TEXTURE ); - glLoadIdentity(); - glMatrixMode( GL_MODELVIEW ); - return true; - } - return false; -} - -bool TextureProperty::bind( const QList< QVector< Vector2 > > & texcoords ) -{ - if ( checkSet( 0, texcoords ) && bind() ) - { - glEnable( GL_TEXTURE_2D ); - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 2, GL_FLOAT, 0, texcoords[ 0 ].data() ); - return true; - } - else - { - glDisable( GL_TEXTURE_2D ); - return false; - } -} - -void TexCache::setNifFolder( const QString & folder ) -{ - nifFolder = folder; - flush(); - emit sigRefresh(); -} - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifdef QT_OPENGL_LIB + +#include + +#include +#include +#include +#include + +#include "glscene.h" +#include "gltex.h" +#include "gltexloaders.h" +#include "options.h" + +#include + +PFNGLACTIVETEXTUREARBPROC _glActiveTextureARB = 0; +PFNGLCLIENTACTIVETEXTUREARBPROC _glClientActiveTextureARB = 0; +PFNGLCOMPRESSEDTEXIMAGE2DPROC _glCompressedTexImage2D = 0; + +int num_texture_units = 0; +float max_anisotropy = 0; + +void initializeTextureUnits( const QGLContext * context ) +{ + QString extensions( (const char *) glGetString(GL_EXTENSIONS) ); + //foreach ( QString e, extensions.split( " " ) ) + // qWarning() << e; + + //if (!extensions.contains("GL_ARB_texture_compression")) + // qWarning() << "texture compression not supported, some textures may not load"; + + // *** check disabled: software decompression is supported *** + //if (!extensions.contains("GL_EXT_texture_compression_s3tc")) + // qWarning() << "S3TC texture compression not supported, some textures may not load"; + + _glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC) context->getProcAddress( "glCompressedTexImage2D" ); + if ( ! _glCompressedTexImage2D ) + _glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC) context->getProcAddress( "glCompressedTexImage2DARB" ); + + if ( ! _glCompressedTexImage2D ) + qWarning( "texture compression not supported" ); + + _glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) context->getProcAddress( "glActiveTextureARB" ); + _glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) context->getProcAddress( "glClientActiveTextureARB" ); + + if ( ! _glActiveTextureARB || ! _glClientActiveTextureARB ) + { + qWarning( "multitexturing not supported" ); + num_texture_units = 1; + } + else + { + glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &num_texture_units ); + if ( num_texture_units < 1 ) + num_texture_units = 1; + //qWarning() << "texture units" << num_texture_units; + } + + if ( extensions.contains( "GL_EXT_texture_filter_anisotropic" ) ) + { + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, & max_anisotropy ); + //qWarning() << "maximum anisotropy" << max_anisotropy; + } +} + +bool activateTextureUnit( int stage ) +{ + if ( num_texture_units <= 1 ) + return ( stage == 0 ); + + if ( stage < num_texture_units ) + { + _glActiveTextureARB( GL_TEXTURE0_ARB + stage ); + _glClientActiveTextureARB( GL_TEXTURE0_ARB + stage ); + return true; + } + else + return false; +} + +void resetTextureUnits() +{ + if ( num_texture_units <= 1 ) + { + glDisable( GL_TEXTURE_2D ); + return; + } + + for ( int x = num_texture_units-1; x >= 0; x-- ) + { + _glActiveTextureARB( GL_TEXTURE0_ARB + x ); + glDisable( GL_TEXTURE_2D ); + glMatrixMode( GL_TEXTURE ); + glLoadIdentity(); + glMatrixMode( GL_MODELVIEW ); + _glClientActiveTextureARB( GL_TEXTURE0_ARB + x ); + glDisableClientState( GL_TEXTURE_COORD_ARRAY ); + } +} + + +/* + TexCache +*/ + +TexCache::TexCache( QObject * parent ) : QObject( parent ) +{ + watcher = new QFileSystemWatcher( this ); + connect( watcher, SIGNAL( fileChanged( const QString & ) ), this, SLOT( fileChanged( const QString & ) ) ); +} + +TexCache::~TexCache() +{ + //flush(); +} + +QString TexCache::find( const QString & file, const QString & nifdir ) +{ + if ( file.isEmpty() ) + return QString(); + +#ifndef WIN32 + /* convert nif path backslash into forward slash */ + /* also check for both original name and lower case name */ + QString filename_orig = QString(file).replace( "\\", "/" );; + QString filename = file.toLower().replace( "\\", "/" ); +#else + QString filename = file.toLower(); +#endif + + while ( filename.startsWith( "/" ) || filename.startsWith( "\\" ) ) + filename.remove( 0, 1 ); + + QStringList extensions; + extensions << ".tga" << ".dds" << ".bmp"; +#ifndef WIN32 + extensions << ".TGA" << ".DDS" << ".BMP"; +#endif + bool replaceExt = false; + if ( Options::textureAlternatives() ) + { + foreach ( QString ext, extensions ) + { + if ( filename.endsWith( ext ) ) + { + extensions.removeAll( ext ); + extensions.prepend( ext ); + filename = filename.left( filename.length() - ext.length() ); +#ifndef WIN32 + filename_orig = filename_orig.left( filename_orig.length() - ext.length() ); +#endif + replaceExt = true; + break; + } + } + } + + // attempt to find the texture in one of the folders + QDir dir; + foreach ( QString ext, extensions ) + { + if ( replaceExt ) { + filename += ext; +#ifndef WIN32 + filename_orig += ext; +#endif + } + + foreach ( QString folder, Options::textureFolders() ) + { + if( folder.startsWith( "./" ) || folder.startsWith( ".\\" ) ) { + folder = nifdir + "/" + folder; + } + + dir.setPath( folder ); +#ifndef WIN32 + //qWarning() << folder << filename; +#endif + if ( dir.exists( filename ) ) + return dir.filePath( filename ); +#ifndef WIN32 + //qWarning() << folder << filename_orig; + if ( dir.exists( filename_orig ) ) + return dir.filePath( filename_orig ); +#endif + } + + if ( replaceExt ) { + filename = filename.left( filename.length() - ext.length() ); +#ifndef WIN32 + filename_orig = filename_orig.left( filename_orig.length() - ext.length() ); +#endif + } else + break; + } + + if ( replaceExt ) + return filename + extensions.value( 0 ); + else + return filename; +} + +QString TexCache::stripPath( const QString & filepath, const QString & nifFolder ) +{ + QString file = filepath; + file = file.replace( "/", "\\" ).toLower(); + + foreach ( QString base, Options::textureFolders() ) + { + if( base.startsWith( "./" ) || base.startsWith( ".\\" ) ) { + base = nifFolder + "/" + base; + } + base = base.replace( "/", "\\" ).toLower(); + + if ( file.startsWith( base ) ) + { + file.remove( 0, base.length() ); + break; + } + } + + if ( file.startsWith( "/" ) || file.startsWith( "\\" ) ) + file.remove( 0, 1 ); + + return file; +} + +bool TexCache::canLoad( const QString & filePath ) +{ + return texCanLoad( filePath ); +} + +void TexCache::fileChanged( const QString & filepath ) +{ + QMutableHashIterator it( textures ); + while ( it.hasNext() ) + { + it.next(); + + Tex * tx = it.value(); + + if ( tx->filepath == filepath ) + { + if ( QFile::exists( tx->filepath ) ) + { + tx->reload = true; + emit sigRefresh(); + } + else + { + it.remove(); + if ( tx->id ) + glDeleteTextures( 1, &tx->id ); + delete tx; + } + } + } +} + +int TexCache::bind( const QString & fname ) +{ + Tex * tx = textures.value( fname ); + if ( ! tx ) + { + tx = new Tex; + tx->filename = fname; + tx->id = 0; + tx->mipmaps = 0; + tx->reload = false; + + textures.insert( tx->filename, tx ); + } + + if ( tx->filepath.isEmpty() || tx->reload ) + tx->filepath = find( tx->filename, nifFolder ); + + if ( ! tx->id || tx->reload ) + { + if ( QFile::exists( tx->filepath ) && QFileInfo( tx->filepath ).isWritable() ) + watcher->addPath( tx->filepath ); + tx->load(); + } + + glBindTexture( GL_TEXTURE_2D, tx->id ); + + return tx->mipmaps; +} + +void TexCache::Tex::load() +{ + if ( ! id ) + glGenTextures( 1, &id ); + + width = height = mipmaps = 0; + reload = false; + status = QString(); + + glBindTexture( GL_TEXTURE_2D, id ); + + try + { + texLoad( filepath, format, width, height, mipmaps ); + } + catch ( QString e ) + { + status = e; + } +} + +void TexCache::flush() +{ + foreach ( Tex * tx, textures ) + { + if ( tx->id ) + glDeleteTextures( 1, &tx->id ); + } + qDeleteAll( textures ); + textures.clear(); + + if( !watcher->files().empty() ) { + watcher->removePaths( watcher->files() ); + } +} + +bool TexturingProperty::bind( int id, const QString & fname ) +{ + GLuint mipmaps = 0; + if ( id >= 0 && id <= 7 && ( mipmaps = scene->bindTexture( fname.isEmpty() ? fileName( id ) : fname ) ) ) + { + if ( max_anisotropy > 0 ) + { + if ( Options::antialias() ) + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy ); + else + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0 ); + } + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, textures[id].wrapS ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, textures[id].wrapT ); + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glMatrixMode( GL_TEXTURE ); + glLoadIdentity(); + if ( textures[id].hasTransform ) + { + glTranslatef( - textures[id].center[0], - textures[id].center[1], 0 ); + glRotatef( textures[id].rotation, 0, 0, 1 ); + glTranslatef( textures[id].center[0], textures[id].center[1], 0 ); + glScalef( textures[id].tiling[0], textures[id].tiling[1], 1 ); + glTranslatef( textures[id].translation[0], textures[id].translation[1], 0 ); + } + glMatrixMode( GL_MODELVIEW ); + return true; + } + else + return false; +} + +bool checkSet( int s, const QList< QVector< Vector2 > > & texcoords ) +{ + return s >= 0 && s < texcoords.count() && texcoords[s].count(); +} + +bool TexturingProperty::bind( int id, const QList< QVector< Vector2 > > & texcoords ) +{ + if ( checkSet( textures[id].coordset, texcoords ) && bind( id ) ) + { + glEnable( GL_TEXTURE_2D ); + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 2, GL_FLOAT, 0, texcoords[ textures[id].coordset ].data() ); + return true; + } + else + { + glDisable( GL_TEXTURE_2D ); + return false; + } +} + +bool TexturingProperty::bind( int id, const QList< QVector< Vector2 > > & texcoords, int stage ) +{ + return ( activateTextureUnit( stage ) && bind( id, texcoords ) ); +} + +bool TextureProperty::bind() +{ + if ( GLuint mipmaps = scene->bindTexture( fileName() ) ) + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ); + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glMatrixMode( GL_TEXTURE ); + glLoadIdentity(); + glMatrixMode( GL_MODELVIEW ); + return true; + } + return false; +} + +bool TextureProperty::bind( const QList< QVector< Vector2 > > & texcoords ) +{ + if ( checkSet( 0, texcoords ) && bind() ) + { + glEnable( GL_TEXTURE_2D ); + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 2, GL_FLOAT, 0, texcoords[ 0 ].data() ); + return true; + } + else + { + glDisable( GL_TEXTURE_2D ); + return false; + } +} + +void TexCache::setNifFolder( const QString & folder ) +{ + nifFolder = folder; + flush(); + emit sigRefresh(); +} + +#endif diff --git a/gl/gltex.h b/gl/gltex.h index 80c68a014..b995bcb54 100644 --- a/gl/gltex.h +++ b/gl/gltex.h @@ -1,100 +1,100 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLTEX_H -#define GLTEX_H - -#include - -class QAction; -class QFileSystemWatcher; - -class GroupBox; - -//! A class for handling OpenGL textures. -/*! - * This class stores information on all loaded textures, and watches the texture files. - */ -class TexCache : public QObject -{ - Q_OBJECT - - //! A structure for storing information on a single texture. - struct Tex - { - //! The texture file name. - QString filename; - //! The texture file path. - QString filepath; - GLuint id; - GLuint width, height, mipmaps; - bool reload; - QString format; - QString status; - - void load(); - }; - -public: - TexCache( QObject * parent = 0 ); - ~TexCache(); - - int bind( const QString & fname ); - - static QString find( const QString & file, const QString & nifFolder ); - static QString stripPath( const QString & file, const QString & nifFolder ); - static bool canLoad( const QString & file ); - -signals: - void sigRefresh(); - -public slots: - void flush(); - - void setNifFolder( const QString & ); - -protected slots: - void fileChanged( const QString & filepath ); - -protected: - QHash textures; - QFileSystemWatcher * watcher; - - QString nifFolder; -}; - -void initializeTextureUnits( const QGLContext * ); - -bool activateTextureUnit( int x ); -void resetTextureUnits(); - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLTEX_H +#define GLTEX_H + +#include + +class QAction; +class QFileSystemWatcher; + +class GroupBox; + +//! A class for handling OpenGL textures. +/*! + * This class stores information on all loaded textures, and watches the texture files. + */ +class TexCache : public QObject +{ + Q_OBJECT + + //! A structure for storing information on a single texture. + struct Tex + { + //! The texture file name. + QString filename; + //! The texture file path. + QString filepath; + GLuint id; + GLuint width, height, mipmaps; + bool reload; + QString format; + QString status; + + void load(); + }; + +public: + TexCache( QObject * parent = 0 ); + ~TexCache(); + + int bind( const QString & fname ); + + static QString find( const QString & file, const QString & nifFolder ); + static QString stripPath( const QString & file, const QString & nifFolder ); + static bool canLoad( const QString & file ); + +signals: + void sigRefresh(); + +public slots: + void flush(); + + void setNifFolder( const QString & ); + +protected slots: + void fileChanged( const QString & filepath ); + +protected: + QHash textures; + QFileSystemWatcher * watcher; + + QString nifFolder; +}; + +void initializeTextureUnits( const QGLContext * ); + +bool activateTextureUnit( int x ); +void resetTextureUnits(); + +#endif diff --git a/gl/gltexloaders.cpp b/gl/gltexloaders.cpp index bcb08f41a..1b6c59fd9 100644 --- a/gl/gltexloaders.cpp +++ b/gl/gltexloaders.cpp @@ -1,890 +1,890 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include - -#include -#include "dds/dds_api.h" - -extern PFNGLCOMPRESSEDTEXIMAGE2DPROC _glCompressedTexImage2D; - -//! Check whether a number is a power of two. -bool isPowerOfTwo( unsigned int x ) -{ - while ( ! ( x == 0 || x & 1 ) ) - x = x >> 1; - return ( x == 1 ); -} - -//! Completes mipmap sequence of the current active OpenGL texture. -/*! - * \param m Number of mipmaps that are already in the texture. - * \return Total number of mipmaps. - */ -int generateMipMaps( int m ) -{ - GLint w = 0, h = 0; - - // load the (m-1)'th mipmap as a basis - glGetTexLevelParameteriv( GL_TEXTURE_2D, m-1, GL_TEXTURE_WIDTH, &w ); - glGetTexLevelParameteriv( GL_TEXTURE_2D, m-1, GL_TEXTURE_HEIGHT, &h ); - - //qWarning() << m-1 << w << h; - - quint8 * data = (quint8 *) malloc( w * h * 4 ); - glGetTexImage( GL_TEXTURE_2D, m-1, GL_RGBA, GL_UNSIGNED_BYTE, data ); - - // now generate the mipmaps until width is one or height is one. - while ( w > 1 || h > 1 ) - { - // the buffer overwrites itself to save memory - const quint8 * src = data; - quint8 * dst = data; - - quint32 xo = ( w > 1 ? 1*4 : 0 ); - quint32 yo = ( h > 1 ? w*4 : 0 ); - - w /= 2; - h /= 2; - - if ( w == 0 ) w = 1; - if ( h == 0 ) h = 1; - - //qWarning() << m << w << h; - - for ( int y = 0; y < h; y++ ) - { - for ( int x = 0; x < w; x++ ) - { - for ( int b = 0; b < 4; b++ ) - { - *dst++ = ( *(src+xo) + *(src+yo) + *(src+xo+yo) + *src++ ) / 4; - } - src += xo; - } - src += yo; - } - - glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); - } - - free( data ); - - return m; -} - -bool uncompressRLE( QIODevice & f, int w, int h, int bytespp, quint8 * pixel ) -{ - QByteArray data = f.readAll(); - - int c = 0; - int o = 0; - - quint8 rl; - while ( c < w * h ) - { - rl = data[o++]; - if ( rl & 0x80 ) - { - quint8 px[4]; - for ( int b = 0; b < bytespp; b++ ) - px[b] = data[o++]; - rl &= 0x7f; - do - { - for ( int b = 0; b < bytespp; b++ ) - *pixel++ = px[b]; - } - while ( ++c < w*h && rl-- > 0 ); - } - else - { - do - { - for ( int b = 0; b < bytespp; b++ ) - *pixel++ = data[o++]; - } - while ( ++c < w*h && rl-- > 0 ); - } - if ( o >= data.count() ) return false; - } - return true; -} - -void convertToRGBA( const quint8 * data, int w, int h, int bytespp, const quint32 mask[], bool flipV, bool flipH, quint8 * pixl ) -{ - memset( pixl, 0, w * h * 4 ); - - static const int rgbashift[4] = { 0, 8, 16, 24 }; - - for ( int a = 0; a < 4; a++ ) - { - if ( mask[a] ) - { - quint32 msk = mask[ a ]; - int rshift = 0; - while ( msk != 0 && ( msk & 0xffffff00 ) ) { msk = msk >> 1; rshift++; } - int lshift = rgbashift[ a ]; - while ( msk != 0 && ( ( msk & 0x80 ) == 0 ) ) { msk = msk << 1; lshift++; } - msk = mask[ a ]; - - const quint8 * src = data; - const quint32 inc = ( flipH ? -1 : 1 ); - for ( int y = 0; y < h; y++ ) - { - quint32 * dst = (quint32 *) ( pixl + 4 * ( w * ( flipV ? h - y - 1 : y ) + ( flipH ? w - 1 : 0 ) ) ); - if ( rshift == lshift ) - { - for ( int x = 0; x < w; x++ ) - { - *dst |= *( (const quint32 *) src ) & msk; - dst += inc; - src += bytespp; - } - } - else - { - for ( int x = 0; x < w; x++ ) - { - *dst |= ( *( (const quint32 *) src ) & msk ) >> rshift << lshift; - dst += inc; - src += bytespp; - } - } - } - } - else if ( a == 3 ) - { - quint32 * dst = (quint32 *) pixl; - quint32 x = 0xff << rgbashift[ a ]; - for ( int c = w * h; c > 0; c-- ) - *dst++ |= x; - } - } -} - -int texLoadRaw( QIODevice & f, int width, int height, int num_mipmaps, int bpp, int bytespp, const quint32 mask[], bool flipV = false, bool flipH = false, bool rle = false ) -{ - if ( bytespp * 8 != bpp || bpp > 32 || bpp < 8 ) - throw QString( "unsupported image depth %1 / %2" ).arg( bpp ).arg( bytespp ); - - glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); - glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE ); - - quint8 * data1 = (quint8 *) malloc( width * height * 4 ); - quint8 * data2 = (quint8 *) malloc( width * height * 4 ); - - int w = width; - int h = height; - int m = 0; - - while ( m < num_mipmaps ) - { - w = width >> m; - h = height >> m; - - if ( w == 0 ) w = 1; - if ( h == 0 ) h = 1; - - if ( rle ) - { - if ( ! uncompressRLE( f, w, h, bytespp, data1 ) ) - { - free( data2 ); - free( data1 ); - throw QString( "unexpected EOF" ); - } - } - else if ( f.read( (char *) data1, w * h * bytespp ) != w * h * bytespp ) - { - - free( data2 ); - free( data1 ); - throw QString( "unexpected EOF" ); - } - - convertToRGBA( data1, w, h, bytespp, mask, flipV, flipH, data2 ); - - glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2 ); - - if ( w == 1 && h == 1 ) - break; - } - - free( data2 ); - free( data1 ); - - if ( w > 1 || h > 1 ) - m = generateMipMaps( m ); - - return m; -} - -int texLoadPal( QIODevice & f, int width, int height, int num_mipmaps, int bpp, int bytespp, const quint32 colormap[], bool flipV, bool flipH, bool rle ) -{ - if ( bpp != 8 || bytespp != 1 ) - throw QString( "unsupported image depth %1 / %2" ).arg( bpp ).arg( bytespp ); - - glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); - glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE ); - - quint8 * data = (quint8 *) malloc( width * height * 1 ); - quint8 * pixl = (quint8 *) malloc( width * height * 4 ); - - int w = width; - int h = height; - int m = 0; - - while ( m < num_mipmaps ) - { - w = width >> m; - h = height >> m; - - if ( w == 0 ) w = 1; - if ( h == 0 ) h = 1; - - if ( rle ) - { - if ( ! uncompressRLE( f, w, h, bytespp, data ) ) - { - free( pixl ); - free( data ); - throw QString( "unexpected EOF" ); - } - } - else if ( f.read( (char *) data, w * h * bytespp ) != w * h * bytespp ) - { - free( pixl ); - free( data ); - throw QString( "unexpected EOF" ); - } - - quint8 * src = data; - for ( int y = 0; y < h; y++ ) - { - quint32 * dst = (quint32 *) ( pixl + 4 * ( w * ( flipV ? h - y - 1 : y ) + ( flipH ? w - 1 : 0 ) ) ); - for ( int x = 0; x < w; x++ ) - { - *dst++ = colormap[*src++]; - } - } - - glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixl ); - - if ( w == 1 && h == 1 ) - break; - } - - free( pixl ); - free( data ); - - if ( w > 1 || h > 1 ) - m = generateMipMaps( m ); - - return m; -} - -#define DDSD_MIPMAPCOUNT 0x00020000 -#define DDPF_FOURCC 0x00000004 - -// DDS format structure -struct DDSFormat { - quint32 dwSize; - quint32 dwFlags; - quint32 dwHeight; - quint32 dwWidth; - quint32 dwLinearSize; - quint32 dummy1; - quint32 dwMipMapCount; - quint32 dummy2[11]; - struct { - quint32 dwSize; - quint32 dwFlags; - quint32 dwFourCC; - quint32 dwBPP; - quint32 dwRMask; - quint32 dwGMask; - quint32 dwBMask; - quint32 dwAMask; - } ddsPixelFormat; -}; - -// compressed texture pixel formats -#define FOURCC_DXT1 0x31545844 -#define FOURCC_DXT2 0x32545844 -#define FOURCC_DXT3 0x33545844 -#define FOURCC_DXT4 0x34545844 -#define FOURCC_DXT5 0x35545844 - -// thanks nvidia for providing the source code to flip dxt images - -typedef struct -{ - unsigned short col0, col1; - unsigned char row[4]; -} DXTColorBlock_t; - -typedef struct -{ - unsigned short row[4]; -} DXT3AlphaBlock_t; - -typedef struct -{ - unsigned char alpha0, alpha1; - unsigned char row[6]; -} DXT5AlphaBlock_t; - -void SwapMem(void *byte1, void *byte2, int size) -{ - unsigned char *tmp=(unsigned char *)malloc(sizeof(unsigned char)*size); - memcpy(tmp, byte1, size); - memcpy(byte1, byte2, size); - memcpy(byte2, tmp, size); - free(tmp); -} - -inline void SwapChar( unsigned char * x, unsigned char * y ) -{ - unsigned char z = *x; - *x = *y; - *y = z; -} - -inline void SwapShort( unsigned short * x, unsigned short * y ) -{ - unsigned short z = *x; - *x = *y; - *y = z; -} - -void flipDXT1Blocks(DXTColorBlock_t *Block, int NumBlocks) -{ - int i; - DXTColorBlock_t *ColorBlock=Block; - for(i=0;irow[0], &ColorBlock->row[3] ); - SwapChar( &ColorBlock->row[1], &ColorBlock->row[2] ); - ColorBlock++; - } -} - -void flipDXT3Blocks(DXTColorBlock_t *Block, int NumBlocks) -{ - int i; - DXTColorBlock_t *ColorBlock=Block; - DXT3AlphaBlock_t *AlphaBlock; - for(i=0;irow[0], &AlphaBlock->row[3] ); - SwapShort( &AlphaBlock->row[1], &AlphaBlock->row[2] ); - ColorBlock++; - SwapChar( &ColorBlock->row[0], &ColorBlock->row[3] ); - SwapChar( &ColorBlock->row[1], &ColorBlock->row[2] ); - ColorBlock++; - } -} - -void flipDXT5Alpha(DXT5AlphaBlock_t *Block) -{ - unsigned long *Bits, Bits0=0, Bits1=0; - - memcpy(&Bits0, &Block->row[0], sizeof(unsigned char)*3); - memcpy(&Bits1, &Block->row[3], sizeof(unsigned char)*3); - - Bits=((unsigned long *)&(Block->row[0])); - *Bits&=0xff000000; - *Bits|=(unsigned char)(Bits1>>12)&0x00000007; - *Bits|=(unsigned char)((Bits1>>15)&0x00000007)<<3; - *Bits|=(unsigned char)((Bits1>>18)&0x00000007)<<6; - *Bits|=(unsigned char)((Bits1>>21)&0x00000007)<<9; - *Bits|=(unsigned char)(Bits1&0x00000007)<<12; - *Bits|=(unsigned char)((Bits1>>3)&0x00000007)<<15; - *Bits|=(unsigned char)((Bits1>>6)&0x00000007)<<18; - *Bits|=(unsigned char)((Bits1>>9)&0x00000007)<<21; - - Bits=((unsigned long *)&(Block->row[3])); - *Bits&=0xff000000; - *Bits|=(unsigned char)(Bits0>>12)&0x00000007; - *Bits|=(unsigned char)((Bits0>>15)&0x00000007)<<3; - *Bits|=(unsigned char)((Bits0>>18)&0x00000007)<<6; - *Bits|=(unsigned char)((Bits0>>21)&0x00000007)<<9; - *Bits|=(unsigned char)(Bits0&0x00000007)<<12; - *Bits|=(unsigned char)((Bits0>>3)&0x00000007)<<15; - *Bits|=(unsigned char)((Bits0>>6)&0x00000007)<<18; - *Bits|=(unsigned char)((Bits0>>9)&0x00000007)<<21; -} - -void flipDXT5Blocks(DXTColorBlock_t *Block, int NumBlocks) -{ - DXTColorBlock_t *ColorBlock=Block; - DXT5AlphaBlock_t *AlphaBlock; - int i; - - for(i=0;irow[0], &ColorBlock->row[3] ); - SwapChar( &ColorBlock->row[1], &ColorBlock->row[2] ); - ColorBlock++; - } -} - -void flipDXT( GLenum glFormat, int width, int height, unsigned char * image ) -{ - int linesize, j; - - DXTColorBlock_t *top; - DXTColorBlock_t *bottom; - int xblocks=width/4; - int yblocks=height/4; - - switch ( glFormat) - { - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - linesize=xblocks*8; - for(j=0;j<(yblocks>>1);j++) - { - top=(DXTColorBlock_t *)(image+j*linesize); - bottom=(DXTColorBlock_t *)(image+(((yblocks-j)-1)*linesize)); - flipDXT1Blocks(top, xblocks); - flipDXT1Blocks(bottom, xblocks); - SwapMem(bottom, top, linesize); - } - break; - - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - linesize=xblocks*16; - for(j=0;j<(yblocks>>1);j++) - { - top=(DXTColorBlock_t *)(image+j*linesize); - bottom=(DXTColorBlock_t *)(image+(((yblocks-j)-1)*linesize)); - flipDXT3Blocks(top, xblocks); - flipDXT3Blocks(bottom, xblocks); - SwapMem(bottom, top, linesize); - } - break; - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - linesize=xblocks*16; - for(j=0;j<(yblocks>>1);j++) - { - top=(DXTColorBlock_t *)(image+j*linesize); - bottom=(DXTColorBlock_t *)(image+(((yblocks-j)-1)*linesize)); - flipDXT5Blocks(top, xblocks); - flipDXT5Blocks(bottom, xblocks); - SwapMem(bottom, top, linesize); - } - break; - default: - return; - } -} - - -GLuint texLoadDXT( QIODevice & f, GLenum glFormat, int blockSize, quint32 width, quint32 height, quint32 mipmaps, bool flipV = false ) -{ -/* -#ifdef WIN32 - if ( !_glCompressedTexImage2D ) - { -#endif -*/ - // load the pixels - f.seek(0); - QByteArray bytes = f.readAll(); - Image * img = load_dds((unsigned char*)bytes.data(), bytes.size()); - if (!img) - return(0); - // convert texture to OpenGL RGBA format - unsigned int w = img->width(); - unsigned int h = img->height(); - GLubyte * pixels = new GLubyte[w * h * 4]; - Color32 * src = img->pixels(); - GLubyte * dst = pixels; - //qWarning() << "flipV = " << flipV; - for ( quint32 y = 0; y < h; y++ ) - { - for ( quint32 x = 0; x < w; x++ ) - { - *dst++ = src->r; - *dst++ = src->g; - *dst++ = src->b; - *dst++ = src->a; - src++; - } - } - delete img; - // load the texture into OpenGL - GLuint m = 0; - glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels ); - delete [] pixels; - m = generateMipMaps( m ); - return m; -/* -#ifdef WIN32 - } - GLubyte * pixels = (GLubyte *) malloc( ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ) * blockSize ); - unsigned int w = width, h = height, s; - unsigned int m = 0; - - while ( m < mipmaps ) - { - w = width >> m; - h = height >> m; - - if ( w == 0 ) w = 1; - if ( h == 0 ) h = 1; - - s = ((w+3)/4) * ((h+3)/4) * blockSize; - - if ( f.read( (char *) pixels, s ) != s ) - { - free( pixels ); - throw QString ( "unexpected EOF" ); - } - - if ( flipV ) - flipDXT( glFormat, w, h, pixels ); - - _glCompressedTexImage2D( GL_TEXTURE_2D, m++, glFormat, w, h, 0, s, pixels ); - - if ( w == 1 && h == 1 ) - break; - } - - if ( w > 1 || h > 1 ) - return 1; - else - return m; -#endif -*/ -} - -//! Load a (possibly compressed) dds texture. -GLuint texLoadDDS( QIODevice & f, QString & texformat ) -{ - char tag[4]; - f.read(&tag[0], 4); - DDSFormat ddsHeader; - - if ( strncmp( tag,"DDS ", 4 ) != 0 || f.read((char *) &ddsHeader, sizeof(DDSFormat)) != sizeof( DDSFormat ) ) - throw QString( "not a DDS file" ); - - texformat = "DDS"; - - if ( !( ddsHeader.dwFlags & DDSD_MIPMAPCOUNT ) ) - ddsHeader.dwMipMapCount = 1; - - if ( ! ( isPowerOfTwo( ddsHeader.dwWidth ) && isPowerOfTwo( ddsHeader.dwHeight ) ) ) - throw QString( "image dimensions must be power of two" ); - - f.seek(ddsHeader.dwSize + 4); - - if ( ddsHeader.ddsPixelFormat.dwFlags & DDPF_FOURCC ) - { - int blockSize = 8; - GLenum glFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - - switch( ddsHeader.ddsPixelFormat.dwFourCC ) - { - case FOURCC_DXT1: - glFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - blockSize = 8; - texformat += " (DXT1)"; - break; - case FOURCC_DXT3: - glFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; - blockSize = 16; - texformat += " (DXT3)"; - break; - case FOURCC_DXT5: - glFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - blockSize = 16; - texformat += " (DXT5)"; - break; - default: - throw QString( "unknown texture compression" ); - } - - return texLoadDXT( f, glFormat, blockSize, ddsHeader.dwWidth, ddsHeader.dwHeight, ddsHeader.dwMipMapCount ); - } - else - { - texformat += " (RAW)"; - - if ( ddsHeader.ddsPixelFormat.dwRMask != 0 && ddsHeader.ddsPixelFormat.dwGMask == 0 && ddsHeader.ddsPixelFormat.dwBMask == 0 ) - { // fixup greyscale - ddsHeader.ddsPixelFormat.dwGMask = ddsHeader.ddsPixelFormat.dwRMask; - ddsHeader.ddsPixelFormat.dwBMask = ddsHeader.ddsPixelFormat.dwRMask; - } - - return texLoadRaw( f, ddsHeader.dwWidth, ddsHeader.dwHeight, - ddsHeader.dwMipMapCount, ddsHeader.ddsPixelFormat.dwBPP, ddsHeader.ddsPixelFormat.dwBPP / 8, - &ddsHeader.ddsPixelFormat.dwRMask ); - } -} - - -// TGA constants - -#define TGA_COLORMAP 1 -#define TGA_COLOR 2 -#define TGA_GREY 3 -#define TGA_COLORMAP_RLE 9 -#define TGA_COLOR_RLE 10 -#define TGA_GREY_RLE 11 - -//! Load a TGA texture. -GLuint texLoadTGA( QIODevice & f, QString & texformat ) -{ - texformat = "TGA"; - - // read in tga header - quint8 hdr[18]; - qint64 readBytes = f.read((char *)hdr, 18); - if ( readBytes != 18 ) - throw QString( "unexpected EOF" ); - - if ( hdr[0] ) f.read( hdr[0] ); - - quint8 depth = hdr[16]; - //quint8 alphaDepth = hdr[17] & 15; - bool flipV = ! ( hdr[17] & 32 ); - bool flipH = hdr[17] & 16; - quint16 width = hdr[12] + 256 * hdr[13]; - quint16 height = hdr[14] + 256 * hdr[15]; - - if ( ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) - throw QString( "image dimensions must be power of two" ); - - quint32 colormap[256]; - - if ( hdr[1] ) - { // color map present - quint16 offset = hdr[3] + 256 * hdr[4]; - quint16 length = hdr[5] + 256 * hdr[6]; - quint8 bits = hdr[7]; - quint8 bytes = bits / 8; - - //qWarning() << "COLORMAP" << "offset" << offset << "length" << length << "bits" << bits << "depth" << depth; - - if ( bits != 32 && bits != 24 ) - throw QString( "image sub format not supported" ); - - quint32 cnt = offset; - quint32 col; - while ( length-- ) - { - col = 0; - if ( f.read( (char *) &col, bytes ) != bytes ) - throw QString( "unexpected EOF" ); - - if ( cnt < 256 ) - { - switch ( bits ) - { - case 24: - colormap[cnt] = ( ( col & 0x00ff0000 ) >> 16 ) | ( col & 0x0000ff00 ) | ( ( col & 0xff ) << 16 ) | 0xff000000; - break; - case 32: - colormap[cnt] = ( ( col & 0x00ff0000 ) >> 16 ) | ( col & 0xff00ff00 ) | ( ( col & 0xff ) << 16 ); - break; - } - } - cnt++; - } - } - - // check format and call texLoadPal / texLoadRaw - switch( hdr[2] ) - { - case TGA_COLORMAP: - case TGA_COLORMAP_RLE: - if ( depth == 8 && hdr[1] ) - { - texformat += " (palettized)"; - if ( hdr[2] == TGA_COLORMAP_RLE ) - texformat += " (RLE)"; - - return texLoadPal( f, width, height, 1, depth, depth/8, colormap, flipV, flipH, hdr[2] == TGA_COLORMAP_RLE ); - } - break; - case TGA_GREY: - case TGA_GREY_RLE: - if ( depth == 8 ) - { - texformat += " (greyscale)"; - if ( hdr[2] == TGA_GREY_RLE ) - texformat += " (RLE)"; - - static const quint32 TGA_L_MASK[4] = { 0xff, 0xff, 0xff, 0x00 }; - return texLoadRaw( f, width, height, 1, 8, 1, TGA_L_MASK, flipV, flipH, hdr[2] == TGA_GREY_RLE ); - } - else if ( depth == 16 ) - { - texformat += " (greyscale) (alpha)"; - if ( hdr[2] == TGA_GREY_RLE ) - texformat += " (RLE)"; - - static const quint32 TGA_LA_MASK[4] = { 0x00ff, 0x00ff, 0x00ff, 0xff00 }; - return texLoadRaw( f, width, height, 1, 16, 2, TGA_LA_MASK, flipV, flipH, hdr[2] == TGA_GREY_RLE ); - } - break; - case TGA_COLOR: - case TGA_COLOR_RLE: - if ( depth == 32 ) - { - texformat += " (truecolor) (alpha)"; - if ( hdr[2] == TGA_GREY_RLE ) - texformat += " (RLE)"; - - static const quint32 TGA_RGBA_MASK[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }; - return texLoadRaw( f, width, height, 1, 32, 4, TGA_RGBA_MASK, flipV, flipH, hdr[2] == TGA_COLOR_RLE ); - } - else if ( depth == 24 ) - { - texformat += " (truecolor)"; - if ( hdr[2] == TGA_COLOR_RLE ) - texformat += " (RLE)"; - - static const quint32 TGA_RGB_MASK[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; - return texLoadRaw( f, width, height, 1, 24, 3, TGA_RGB_MASK, flipV, flipH, hdr[2] == TGA_COLOR_RLE ); - } - break; - } - throw QString( "image sub format not supported" ); - return 0; -} - -quint32 get32( quint8 * x ) -{ - return *( (quint32 *) x ); -} - -quint16 get16( quint8 * x ) -{ - return *( (quint16 *) x ); -} - -//! Load a BMP texture. -GLuint texLoadBMP( QIODevice & f, QString & texformat ) -{ - // read in bmp header - quint8 hdr[54]; - qint64 readBytes = f.read((char *)hdr, 54); - - if ( readBytes != 54 || strncmp((char*)hdr,"BM", 2) != 0) - throw QString( "not a BMP file" ); - - texformat = "BMP"; - - unsigned int width = get32( &hdr[18] ); - unsigned int height = get32( &hdr[22] ); - unsigned int bpp = get16( &hdr[28] ); - unsigned int compression = get32( &hdr[30] ); - unsigned int offset = get32( &hdr[10] ); - - f.seek( offset ); - - if ( ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) - throw QString( "image dimensions must be power of two" ); - - switch ( compression ) - { - case 0: - if ( bpp == 24 ) - { - static const quint32 BMP_RGBA_MASK[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; - return texLoadRaw( f, width, height, 1, bpp, 3, BMP_RGBA_MASK, true ); - } - break; - case FOURCC_DXT5: - texformat += " (DXT5)"; - return texLoadDXT( f, compression, width, height, 1, true ); - case FOURCC_DXT3: - texformat += " (DXT3)"; - return texLoadDXT( f, compression, width, height, 1, true ); - case FOURCC_DXT1: - texformat += " (DXT1)"; - return texLoadDXT( f, compression, width, height, 1, true ); - } - - throw QString( "unknown image sub format" ); - /* - qWarning( "size %i,%i", width, height ); - qWarning( "plns %i", get16( &hdr[26] ) ); - qWarning( "bpp %i", bpp ); - qWarning( "cmpr %08x", compression ); - qWarning( "ofs %i", offset ); - */ - return 0; -} - -// (public function, documented in gltexloaders.h) -bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ) -{ - width = height = mipmaps = 0; - - QFile f( filepath ); - if ( ! f.open( QIODevice::ReadOnly ) ) - throw QString( "could not open file" ); - - if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) - mipmaps = texLoadDDS( f, format ); - else if ( filepath.endsWith( ".tga", Qt::CaseInsensitive ) ) - mipmaps = texLoadTGA( f, format ); - else if ( filepath.endsWith( ".bmp", Qt::CaseInsensitive ) ) - mipmaps = texLoadBMP( f, format ); - else - throw QString( "unknown texture format" ); - - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint *) & width ); - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint *) & height ); - - return mipmaps > 0; -} - -// (public function, documented in gltexloaders.h) -bool texCanLoad( const QString & filepath ) -{ - QFileInfo i( filepath ); - return i.exists() && i.isReadable() && ( - filepath.endsWith( ".dds", Qt::CaseInsensitive ) || - filepath.endsWith( ".tga", Qt::CaseInsensitive ) || - filepath.endsWith( ".bmp", Qt::CaseInsensitive ) ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include + +#include +#include "dds/dds_api.h" + +extern PFNGLCOMPRESSEDTEXIMAGE2DPROC _glCompressedTexImage2D; + +//! Check whether a number is a power of two. +bool isPowerOfTwo( unsigned int x ) +{ + while ( ! ( x == 0 || x & 1 ) ) + x = x >> 1; + return ( x == 1 ); +} + +//! Completes mipmap sequence of the current active OpenGL texture. +/*! + * \param m Number of mipmaps that are already in the texture. + * \return Total number of mipmaps. + */ +int generateMipMaps( int m ) +{ + GLint w = 0, h = 0; + + // load the (m-1)'th mipmap as a basis + glGetTexLevelParameteriv( GL_TEXTURE_2D, m-1, GL_TEXTURE_WIDTH, &w ); + glGetTexLevelParameteriv( GL_TEXTURE_2D, m-1, GL_TEXTURE_HEIGHT, &h ); + + //qWarning() << m-1 << w << h; + + quint8 * data = (quint8 *) malloc( w * h * 4 ); + glGetTexImage( GL_TEXTURE_2D, m-1, GL_RGBA, GL_UNSIGNED_BYTE, data ); + + // now generate the mipmaps until width is one or height is one. + while ( w > 1 || h > 1 ) + { + // the buffer overwrites itself to save memory + const quint8 * src = data; + quint8 * dst = data; + + quint32 xo = ( w > 1 ? 1*4 : 0 ); + quint32 yo = ( h > 1 ? w*4 : 0 ); + + w /= 2; + h /= 2; + + if ( w == 0 ) w = 1; + if ( h == 0 ) h = 1; + + //qWarning() << m << w << h; + + for ( int y = 0; y < h; y++ ) + { + for ( int x = 0; x < w; x++ ) + { + for ( int b = 0; b < 4; b++ ) + { + *dst++ = ( *(src+xo) + *(src+yo) + *(src+xo+yo) + *src++ ) / 4; + } + src += xo; + } + src += yo; + } + + glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + + free( data ); + + return m; +} + +bool uncompressRLE( QIODevice & f, int w, int h, int bytespp, quint8 * pixel ) +{ + QByteArray data = f.readAll(); + + int c = 0; + int o = 0; + + quint8 rl; + while ( c < w * h ) + { + rl = data[o++]; + if ( rl & 0x80 ) + { + quint8 px[4]; + for ( int b = 0; b < bytespp; b++ ) + px[b] = data[o++]; + rl &= 0x7f; + do + { + for ( int b = 0; b < bytespp; b++ ) + *pixel++ = px[b]; + } + while ( ++c < w*h && rl-- > 0 ); + } + else + { + do + { + for ( int b = 0; b < bytespp; b++ ) + *pixel++ = data[o++]; + } + while ( ++c < w*h && rl-- > 0 ); + } + if ( o >= data.count() ) return false; + } + return true; +} + +void convertToRGBA( const quint8 * data, int w, int h, int bytespp, const quint32 mask[], bool flipV, bool flipH, quint8 * pixl ) +{ + memset( pixl, 0, w * h * 4 ); + + static const int rgbashift[4] = { 0, 8, 16, 24 }; + + for ( int a = 0; a < 4; a++ ) + { + if ( mask[a] ) + { + quint32 msk = mask[ a ]; + int rshift = 0; + while ( msk != 0 && ( msk & 0xffffff00 ) ) { msk = msk >> 1; rshift++; } + int lshift = rgbashift[ a ]; + while ( msk != 0 && ( ( msk & 0x80 ) == 0 ) ) { msk = msk << 1; lshift++; } + msk = mask[ a ]; + + const quint8 * src = data; + const quint32 inc = ( flipH ? -1 : 1 ); + for ( int y = 0; y < h; y++ ) + { + quint32 * dst = (quint32 *) ( pixl + 4 * ( w * ( flipV ? h - y - 1 : y ) + ( flipH ? w - 1 : 0 ) ) ); + if ( rshift == lshift ) + { + for ( int x = 0; x < w; x++ ) + { + *dst |= *( (const quint32 *) src ) & msk; + dst += inc; + src += bytespp; + } + } + else + { + for ( int x = 0; x < w; x++ ) + { + *dst |= ( *( (const quint32 *) src ) & msk ) >> rshift << lshift; + dst += inc; + src += bytespp; + } + } + } + } + else if ( a == 3 ) + { + quint32 * dst = (quint32 *) pixl; + quint32 x = 0xff << rgbashift[ a ]; + for ( int c = w * h; c > 0; c-- ) + *dst++ |= x; + } + } +} + +int texLoadRaw( QIODevice & f, int width, int height, int num_mipmaps, int bpp, int bytespp, const quint32 mask[], bool flipV = false, bool flipH = false, bool rle = false ) +{ + if ( bytespp * 8 != bpp || bpp > 32 || bpp < 8 ) + throw QString( "unsupported image depth %1 / %2" ).arg( bpp ).arg( bytespp ); + + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE ); + + quint8 * data1 = (quint8 *) malloc( width * height * 4 ); + quint8 * data2 = (quint8 *) malloc( width * height * 4 ); + + int w = width; + int h = height; + int m = 0; + + while ( m < num_mipmaps ) + { + w = width >> m; + h = height >> m; + + if ( w == 0 ) w = 1; + if ( h == 0 ) h = 1; + + if ( rle ) + { + if ( ! uncompressRLE( f, w, h, bytespp, data1 ) ) + { + free( data2 ); + free( data1 ); + throw QString( "unexpected EOF" ); + } + } + else if ( f.read( (char *) data1, w * h * bytespp ) != w * h * bytespp ) + { + + free( data2 ); + free( data1 ); + throw QString( "unexpected EOF" ); + } + + convertToRGBA( data1, w, h, bytespp, mask, flipV, flipH, data2 ); + + glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2 ); + + if ( w == 1 && h == 1 ) + break; + } + + free( data2 ); + free( data1 ); + + if ( w > 1 || h > 1 ) + m = generateMipMaps( m ); + + return m; +} + +int texLoadPal( QIODevice & f, int width, int height, int num_mipmaps, int bpp, int bytespp, const quint32 colormap[], bool flipV, bool flipH, bool rle ) +{ + if ( bpp != 8 || bytespp != 1 ) + throw QString( "unsupported image depth %1 / %2" ).arg( bpp ).arg( bytespp ); + + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE ); + + quint8 * data = (quint8 *) malloc( width * height * 1 ); + quint8 * pixl = (quint8 *) malloc( width * height * 4 ); + + int w = width; + int h = height; + int m = 0; + + while ( m < num_mipmaps ) + { + w = width >> m; + h = height >> m; + + if ( w == 0 ) w = 1; + if ( h == 0 ) h = 1; + + if ( rle ) + { + if ( ! uncompressRLE( f, w, h, bytespp, data ) ) + { + free( pixl ); + free( data ); + throw QString( "unexpected EOF" ); + } + } + else if ( f.read( (char *) data, w * h * bytespp ) != w * h * bytespp ) + { + free( pixl ); + free( data ); + throw QString( "unexpected EOF" ); + } + + quint8 * src = data; + for ( int y = 0; y < h; y++ ) + { + quint32 * dst = (quint32 *) ( pixl + 4 * ( w * ( flipV ? h - y - 1 : y ) + ( flipH ? w - 1 : 0 ) ) ); + for ( int x = 0; x < w; x++ ) + { + *dst++ = colormap[*src++]; + } + } + + glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixl ); + + if ( w == 1 && h == 1 ) + break; + } + + free( pixl ); + free( data ); + + if ( w > 1 || h > 1 ) + m = generateMipMaps( m ); + + return m; +} + +#define DDSD_MIPMAPCOUNT 0x00020000 +#define DDPF_FOURCC 0x00000004 + +// DDS format structure +struct DDSFormat { + quint32 dwSize; + quint32 dwFlags; + quint32 dwHeight; + quint32 dwWidth; + quint32 dwLinearSize; + quint32 dummy1; + quint32 dwMipMapCount; + quint32 dummy2[11]; + struct { + quint32 dwSize; + quint32 dwFlags; + quint32 dwFourCC; + quint32 dwBPP; + quint32 dwRMask; + quint32 dwGMask; + quint32 dwBMask; + quint32 dwAMask; + } ddsPixelFormat; +}; + +// compressed texture pixel formats +#define FOURCC_DXT1 0x31545844 +#define FOURCC_DXT2 0x32545844 +#define FOURCC_DXT3 0x33545844 +#define FOURCC_DXT4 0x34545844 +#define FOURCC_DXT5 0x35545844 + +// thanks nvidia for providing the source code to flip dxt images + +typedef struct +{ + unsigned short col0, col1; + unsigned char row[4]; +} DXTColorBlock_t; + +typedef struct +{ + unsigned short row[4]; +} DXT3AlphaBlock_t; + +typedef struct +{ + unsigned char alpha0, alpha1; + unsigned char row[6]; +} DXT5AlphaBlock_t; + +void SwapMem(void *byte1, void *byte2, int size) +{ + unsigned char *tmp=(unsigned char *)malloc(sizeof(unsigned char)*size); + memcpy(tmp, byte1, size); + memcpy(byte1, byte2, size); + memcpy(byte2, tmp, size); + free(tmp); +} + +inline void SwapChar( unsigned char * x, unsigned char * y ) +{ + unsigned char z = *x; + *x = *y; + *y = z; +} + +inline void SwapShort( unsigned short * x, unsigned short * y ) +{ + unsigned short z = *x; + *x = *y; + *y = z; +} + +void flipDXT1Blocks(DXTColorBlock_t *Block, int NumBlocks) +{ + int i; + DXTColorBlock_t *ColorBlock=Block; + for(i=0;irow[0], &ColorBlock->row[3] ); + SwapChar( &ColorBlock->row[1], &ColorBlock->row[2] ); + ColorBlock++; + } +} + +void flipDXT3Blocks(DXTColorBlock_t *Block, int NumBlocks) +{ + int i; + DXTColorBlock_t *ColorBlock=Block; + DXT3AlphaBlock_t *AlphaBlock; + for(i=0;irow[0], &AlphaBlock->row[3] ); + SwapShort( &AlphaBlock->row[1], &AlphaBlock->row[2] ); + ColorBlock++; + SwapChar( &ColorBlock->row[0], &ColorBlock->row[3] ); + SwapChar( &ColorBlock->row[1], &ColorBlock->row[2] ); + ColorBlock++; + } +} + +void flipDXT5Alpha(DXT5AlphaBlock_t *Block) +{ + unsigned long *Bits, Bits0=0, Bits1=0; + + memcpy(&Bits0, &Block->row[0], sizeof(unsigned char)*3); + memcpy(&Bits1, &Block->row[3], sizeof(unsigned char)*3); + + Bits=((unsigned long *)&(Block->row[0])); + *Bits&=0xff000000; + *Bits|=(unsigned char)(Bits1>>12)&0x00000007; + *Bits|=(unsigned char)((Bits1>>15)&0x00000007)<<3; + *Bits|=(unsigned char)((Bits1>>18)&0x00000007)<<6; + *Bits|=(unsigned char)((Bits1>>21)&0x00000007)<<9; + *Bits|=(unsigned char)(Bits1&0x00000007)<<12; + *Bits|=(unsigned char)((Bits1>>3)&0x00000007)<<15; + *Bits|=(unsigned char)((Bits1>>6)&0x00000007)<<18; + *Bits|=(unsigned char)((Bits1>>9)&0x00000007)<<21; + + Bits=((unsigned long *)&(Block->row[3])); + *Bits&=0xff000000; + *Bits|=(unsigned char)(Bits0>>12)&0x00000007; + *Bits|=(unsigned char)((Bits0>>15)&0x00000007)<<3; + *Bits|=(unsigned char)((Bits0>>18)&0x00000007)<<6; + *Bits|=(unsigned char)((Bits0>>21)&0x00000007)<<9; + *Bits|=(unsigned char)(Bits0&0x00000007)<<12; + *Bits|=(unsigned char)((Bits0>>3)&0x00000007)<<15; + *Bits|=(unsigned char)((Bits0>>6)&0x00000007)<<18; + *Bits|=(unsigned char)((Bits0>>9)&0x00000007)<<21; +} + +void flipDXT5Blocks(DXTColorBlock_t *Block, int NumBlocks) +{ + DXTColorBlock_t *ColorBlock=Block; + DXT5AlphaBlock_t *AlphaBlock; + int i; + + for(i=0;irow[0], &ColorBlock->row[3] ); + SwapChar( &ColorBlock->row[1], &ColorBlock->row[2] ); + ColorBlock++; + } +} + +void flipDXT( GLenum glFormat, int width, int height, unsigned char * image ) +{ + int linesize, j; + + DXTColorBlock_t *top; + DXTColorBlock_t *bottom; + int xblocks=width/4; + int yblocks=height/4; + + switch ( glFormat) + { + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + linesize=xblocks*8; + for(j=0;j<(yblocks>>1);j++) + { + top=(DXTColorBlock_t *)(image+j*linesize); + bottom=(DXTColorBlock_t *)(image+(((yblocks-j)-1)*linesize)); + flipDXT1Blocks(top, xblocks); + flipDXT1Blocks(bottom, xblocks); + SwapMem(bottom, top, linesize); + } + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + linesize=xblocks*16; + for(j=0;j<(yblocks>>1);j++) + { + top=(DXTColorBlock_t *)(image+j*linesize); + bottom=(DXTColorBlock_t *)(image+(((yblocks-j)-1)*linesize)); + flipDXT3Blocks(top, xblocks); + flipDXT3Blocks(bottom, xblocks); + SwapMem(bottom, top, linesize); + } + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + linesize=xblocks*16; + for(j=0;j<(yblocks>>1);j++) + { + top=(DXTColorBlock_t *)(image+j*linesize); + bottom=(DXTColorBlock_t *)(image+(((yblocks-j)-1)*linesize)); + flipDXT5Blocks(top, xblocks); + flipDXT5Blocks(bottom, xblocks); + SwapMem(bottom, top, linesize); + } + break; + default: + return; + } +} + + +GLuint texLoadDXT( QIODevice & f, GLenum glFormat, int blockSize, quint32 width, quint32 height, quint32 mipmaps, bool flipV = false ) +{ +/* +#ifdef WIN32 + if ( !_glCompressedTexImage2D ) + { +#endif +*/ + // load the pixels + f.seek(0); + QByteArray bytes = f.readAll(); + Image * img = load_dds((unsigned char*)bytes.data(), bytes.size()); + if (!img) + return(0); + // convert texture to OpenGL RGBA format + unsigned int w = img->width(); + unsigned int h = img->height(); + GLubyte * pixels = new GLubyte[w * h * 4]; + Color32 * src = img->pixels(); + GLubyte * dst = pixels; + //qWarning() << "flipV = " << flipV; + for ( quint32 y = 0; y < h; y++ ) + { + for ( quint32 x = 0; x < w; x++ ) + { + *dst++ = src->r; + *dst++ = src->g; + *dst++ = src->b; + *dst++ = src->a; + src++; + } + } + delete img; + // load the texture into OpenGL + GLuint m = 0; + glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels ); + delete [] pixels; + m = generateMipMaps( m ); + return m; +/* +#ifdef WIN32 + } + GLubyte * pixels = (GLubyte *) malloc( ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ) * blockSize ); + unsigned int w = width, h = height, s; + unsigned int m = 0; + + while ( m < mipmaps ) + { + w = width >> m; + h = height >> m; + + if ( w == 0 ) w = 1; + if ( h == 0 ) h = 1; + + s = ((w+3)/4) * ((h+3)/4) * blockSize; + + if ( f.read( (char *) pixels, s ) != s ) + { + free( pixels ); + throw QString ( "unexpected EOF" ); + } + + if ( flipV ) + flipDXT( glFormat, w, h, pixels ); + + _glCompressedTexImage2D( GL_TEXTURE_2D, m++, glFormat, w, h, 0, s, pixels ); + + if ( w == 1 && h == 1 ) + break; + } + + if ( w > 1 || h > 1 ) + return 1; + else + return m; +#endif +*/ +} + +//! Load a (possibly compressed) dds texture. +GLuint texLoadDDS( QIODevice & f, QString & texformat ) +{ + char tag[4]; + f.read(&tag[0], 4); + DDSFormat ddsHeader; + + if ( strncmp( tag,"DDS ", 4 ) != 0 || f.read((char *) &ddsHeader, sizeof(DDSFormat)) != sizeof( DDSFormat ) ) + throw QString( "not a DDS file" ); + + texformat = "DDS"; + + if ( !( ddsHeader.dwFlags & DDSD_MIPMAPCOUNT ) ) + ddsHeader.dwMipMapCount = 1; + + if ( ! ( isPowerOfTwo( ddsHeader.dwWidth ) && isPowerOfTwo( ddsHeader.dwHeight ) ) ) + throw QString( "image dimensions must be power of two" ); + + f.seek(ddsHeader.dwSize + 4); + + if ( ddsHeader.ddsPixelFormat.dwFlags & DDPF_FOURCC ) + { + int blockSize = 8; + GLenum glFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + + switch( ddsHeader.ddsPixelFormat.dwFourCC ) + { + case FOURCC_DXT1: + glFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + blockSize = 8; + texformat += " (DXT1)"; + break; + case FOURCC_DXT3: + glFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + blockSize = 16; + texformat += " (DXT3)"; + break; + case FOURCC_DXT5: + glFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + blockSize = 16; + texformat += " (DXT5)"; + break; + default: + throw QString( "unknown texture compression" ); + } + + return texLoadDXT( f, glFormat, blockSize, ddsHeader.dwWidth, ddsHeader.dwHeight, ddsHeader.dwMipMapCount ); + } + else + { + texformat += " (RAW)"; + + if ( ddsHeader.ddsPixelFormat.dwRMask != 0 && ddsHeader.ddsPixelFormat.dwGMask == 0 && ddsHeader.ddsPixelFormat.dwBMask == 0 ) + { // fixup greyscale + ddsHeader.ddsPixelFormat.dwGMask = ddsHeader.ddsPixelFormat.dwRMask; + ddsHeader.ddsPixelFormat.dwBMask = ddsHeader.ddsPixelFormat.dwRMask; + } + + return texLoadRaw( f, ddsHeader.dwWidth, ddsHeader.dwHeight, + ddsHeader.dwMipMapCount, ddsHeader.ddsPixelFormat.dwBPP, ddsHeader.ddsPixelFormat.dwBPP / 8, + &ddsHeader.ddsPixelFormat.dwRMask ); + } +} + + +// TGA constants + +#define TGA_COLORMAP 1 +#define TGA_COLOR 2 +#define TGA_GREY 3 +#define TGA_COLORMAP_RLE 9 +#define TGA_COLOR_RLE 10 +#define TGA_GREY_RLE 11 + +//! Load a TGA texture. +GLuint texLoadTGA( QIODevice & f, QString & texformat ) +{ + texformat = "TGA"; + + // read in tga header + quint8 hdr[18]; + qint64 readBytes = f.read((char *)hdr, 18); + if ( readBytes != 18 ) + throw QString( "unexpected EOF" ); + + if ( hdr[0] ) f.read( hdr[0] ); + + quint8 depth = hdr[16]; + //quint8 alphaDepth = hdr[17] & 15; + bool flipV = ! ( hdr[17] & 32 ); + bool flipH = hdr[17] & 16; + quint16 width = hdr[12] + 256 * hdr[13]; + quint16 height = hdr[14] + 256 * hdr[15]; + + if ( ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) + throw QString( "image dimensions must be power of two" ); + + quint32 colormap[256]; + + if ( hdr[1] ) + { // color map present + quint16 offset = hdr[3] + 256 * hdr[4]; + quint16 length = hdr[5] + 256 * hdr[6]; + quint8 bits = hdr[7]; + quint8 bytes = bits / 8; + + //qWarning() << "COLORMAP" << "offset" << offset << "length" << length << "bits" << bits << "depth" << depth; + + if ( bits != 32 && bits != 24 ) + throw QString( "image sub format not supported" ); + + quint32 cnt = offset; + quint32 col; + while ( length-- ) + { + col = 0; + if ( f.read( (char *) &col, bytes ) != bytes ) + throw QString( "unexpected EOF" ); + + if ( cnt < 256 ) + { + switch ( bits ) + { + case 24: + colormap[cnt] = ( ( col & 0x00ff0000 ) >> 16 ) | ( col & 0x0000ff00 ) | ( ( col & 0xff ) << 16 ) | 0xff000000; + break; + case 32: + colormap[cnt] = ( ( col & 0x00ff0000 ) >> 16 ) | ( col & 0xff00ff00 ) | ( ( col & 0xff ) << 16 ); + break; + } + } + cnt++; + } + } + + // check format and call texLoadPal / texLoadRaw + switch( hdr[2] ) + { + case TGA_COLORMAP: + case TGA_COLORMAP_RLE: + if ( depth == 8 && hdr[1] ) + { + texformat += " (palettized)"; + if ( hdr[2] == TGA_COLORMAP_RLE ) + texformat += " (RLE)"; + + return texLoadPal( f, width, height, 1, depth, depth/8, colormap, flipV, flipH, hdr[2] == TGA_COLORMAP_RLE ); + } + break; + case TGA_GREY: + case TGA_GREY_RLE: + if ( depth == 8 ) + { + texformat += " (greyscale)"; + if ( hdr[2] == TGA_GREY_RLE ) + texformat += " (RLE)"; + + static const quint32 TGA_L_MASK[4] = { 0xff, 0xff, 0xff, 0x00 }; + return texLoadRaw( f, width, height, 1, 8, 1, TGA_L_MASK, flipV, flipH, hdr[2] == TGA_GREY_RLE ); + } + else if ( depth == 16 ) + { + texformat += " (greyscale) (alpha)"; + if ( hdr[2] == TGA_GREY_RLE ) + texformat += " (RLE)"; + + static const quint32 TGA_LA_MASK[4] = { 0x00ff, 0x00ff, 0x00ff, 0xff00 }; + return texLoadRaw( f, width, height, 1, 16, 2, TGA_LA_MASK, flipV, flipH, hdr[2] == TGA_GREY_RLE ); + } + break; + case TGA_COLOR: + case TGA_COLOR_RLE: + if ( depth == 32 ) + { + texformat += " (truecolor) (alpha)"; + if ( hdr[2] == TGA_GREY_RLE ) + texformat += " (RLE)"; + + static const quint32 TGA_RGBA_MASK[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }; + return texLoadRaw( f, width, height, 1, 32, 4, TGA_RGBA_MASK, flipV, flipH, hdr[2] == TGA_COLOR_RLE ); + } + else if ( depth == 24 ) + { + texformat += " (truecolor)"; + if ( hdr[2] == TGA_COLOR_RLE ) + texformat += " (RLE)"; + + static const quint32 TGA_RGB_MASK[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; + return texLoadRaw( f, width, height, 1, 24, 3, TGA_RGB_MASK, flipV, flipH, hdr[2] == TGA_COLOR_RLE ); + } + break; + } + throw QString( "image sub format not supported" ); + return 0; +} + +quint32 get32( quint8 * x ) +{ + return *( (quint32 *) x ); +} + +quint16 get16( quint8 * x ) +{ + return *( (quint16 *) x ); +} + +//! Load a BMP texture. +GLuint texLoadBMP( QIODevice & f, QString & texformat ) +{ + // read in bmp header + quint8 hdr[54]; + qint64 readBytes = f.read((char *)hdr, 54); + + if ( readBytes != 54 || strncmp((char*)hdr,"BM", 2) != 0) + throw QString( "not a BMP file" ); + + texformat = "BMP"; + + unsigned int width = get32( &hdr[18] ); + unsigned int height = get32( &hdr[22] ); + unsigned int bpp = get16( &hdr[28] ); + unsigned int compression = get32( &hdr[30] ); + unsigned int offset = get32( &hdr[10] ); + + f.seek( offset ); + + if ( ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) + throw QString( "image dimensions must be power of two" ); + + switch ( compression ) + { + case 0: + if ( bpp == 24 ) + { + static const quint32 BMP_RGBA_MASK[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; + return texLoadRaw( f, width, height, 1, bpp, 3, BMP_RGBA_MASK, true ); + } + break; + case FOURCC_DXT5: + texformat += " (DXT5)"; + return texLoadDXT( f, compression, width, height, 1, true ); + case FOURCC_DXT3: + texformat += " (DXT3)"; + return texLoadDXT( f, compression, width, height, 1, true ); + case FOURCC_DXT1: + texformat += " (DXT1)"; + return texLoadDXT( f, compression, width, height, 1, true ); + } + + throw QString( "unknown image sub format" ); + /* + qWarning( "size %i,%i", width, height ); + qWarning( "plns %i", get16( &hdr[26] ) ); + qWarning( "bpp %i", bpp ); + qWarning( "cmpr %08x", compression ); + qWarning( "ofs %i", offset ); + */ + return 0; +} + +// (public function, documented in gltexloaders.h) +bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ) +{ + width = height = mipmaps = 0; + + QFile f( filepath ); + if ( ! f.open( QIODevice::ReadOnly ) ) + throw QString( "could not open file" ); + + if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) + mipmaps = texLoadDDS( f, format ); + else if ( filepath.endsWith( ".tga", Qt::CaseInsensitive ) ) + mipmaps = texLoadTGA( f, format ); + else if ( filepath.endsWith( ".bmp", Qt::CaseInsensitive ) ) + mipmaps = texLoadBMP( f, format ); + else + throw QString( "unknown texture format" ); + + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint *) & width ); + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint *) & height ); + + return mipmaps > 0; +} + +// (public function, documented in gltexloaders.h) +bool texCanLoad( const QString & filepath ) +{ + QFileInfo i( filepath ); + return i.exists() && i.isReadable() && ( + filepath.endsWith( ".dds", Qt::CaseInsensitive ) || + filepath.endsWith( ".tga", Qt::CaseInsensitive ) || + filepath.endsWith( ".bmp", Qt::CaseInsensitive ) ); +} diff --git a/gl/gltexloaders.h b/gl/gltexloaders.h index 300dbbad9..ad20a4c79 100644 --- a/gl/gltexloaders.h +++ b/gl/gltexloaders.h @@ -1,61 +1,61 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLTEXLOADERS_H -#define GLTEXLOADERS_H - -//! A function for loading textures. -/*! - * Loads a texture pointed to by filepath. - * Returns true on success, and throws a QString otherwise. - * The parameters format, width, height and mipmaps will be filled with information about - * the loaded texture. - * - * \param filepath The full path to the texture that must be loaded. - * \param format Contain the format, for instance "DDS (DXT3)" or "TGA", on successful load. - * \param width Contains the texture width on successful load. - * \param height Contains the texture height on successful load. - * \param mipmaps Contains the number of mipmaps on successful load. - * \return true if the load was successful, false otherwise. - */ -extern bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ); - -//! A function which checks whether the given file can be loaded. -/*! - * The function checks whether the file exists, is readable, and whether its extension - * is that of a supported file format (dds, tga, or bmp). - * - * \param filepath The full path to the texture that must be checked. - */ -extern bool texCanLoad( const QString & filepath ); - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLTEXLOADERS_H +#define GLTEXLOADERS_H + +//! A function for loading textures. +/*! + * Loads a texture pointed to by filepath. + * Returns true on success, and throws a QString otherwise. + * The parameters format, width, height and mipmaps will be filled with information about + * the loaded texture. + * + * \param filepath The full path to the texture that must be loaded. + * \param format Contain the format, for instance "DDS (DXT3)" or "TGA", on successful load. + * \param width Contains the texture width on successful load. + * \param height Contains the texture height on successful load. + * \param mipmaps Contains the number of mipmaps on successful load. + * \return true if the load was successful, false otherwise. + */ +extern bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ); + +//! A function which checks whether the given file can be loaded. +/*! + * The function checks whether the file exists, is readable, and whether its extension + * is that of a supported file format (dds, tga, or bmp). + * + * \param filepath The full path to the texture that must be checked. + */ +extern bool texCanLoad( const QString & filepath ); + +#endif diff --git a/gl/gltools.cpp b/gl/gltools.cpp index 3411e2977..6deebee43 100644 --- a/gl/gltools.cpp +++ b/gl/gltools.cpp @@ -1,610 +1,610 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "gltools.h" - -#include "nifmodel.h" - -#include - - -BoneWeights::BoneWeights( const NifModel * nif, const QModelIndex & index, int b ) -{ - trans = Transform( nif, index ); - center = nif->get( index, "Center" ); - radius = nif->get( index, "Radius" ); - bone = b; - - QModelIndex idxWeights = nif->getIndex( index, "Vertex Weights" ); - if ( idxWeights.isValid() ) - { - for ( int c = 0; c < nif->rowCount( idxWeights ); c++ ) - { - QModelIndex idx = idxWeights.child( c, 0 ); - weights.append( VertexWeight( nif->get( idx, "Index" ), nif->get( idx, "Weight" ) ) ); - } - } - else - qWarning() << nif->getBlockNumber( index ) << "vertex weights not found"; -} - - -SkinPartition::SkinPartition( const NifModel * nif, const QModelIndex & index ) -{ - numWeightsPerVertex = nif->get( index, "Num Weights Per Vertex" ); - - vertexMap = nif->getArray( index, "Vertex Map" ); - - if ( vertexMap.isEmpty() ) - { - vertexMap.resize( nif->get( index, "Num Vertices" ) ); - for ( int x = 0; x < vertexMap.count(); x++ ) - vertexMap[x] = x; - } - - boneMap = nif->getArray( index, "Bones" ); - - QModelIndex iWeights = nif->getIndex( index, "Vertex Weights" ); - QModelIndex iBoneIndices = nif->getIndex( index, "Bone Indices" ); - - weights.resize( vertexMap.count() * numWeightsPerVertex ); - - for ( int v = 0; v < vertexMap.count(); v++ ) - { - for ( int w = 0; w < numWeightsPerVertex; w++ ) - { - QModelIndex iw = iWeights.child( v, 0 ).child( w, 0 ); - QModelIndex ib = iBoneIndices.child( v, 0 ).child( w, 0 ); - - weights[ v * numWeightsPerVertex + w ].first = ( ib.isValid() ? nif->get( ib ) : 0 ); - weights[ v * numWeightsPerVertex + w ].second = ( iw.isValid() ? nif->get( iw ) : 0 ); - } - } - - QModelIndex iStrips = nif->getIndex( index, "Strips" ); - for ( int s = 0; s < nif->rowCount( iStrips ); s++ ) - { - tristrips << nif->getArray( iStrips.child( s, 0 ) ); - } - - triangles = nif->getArray( index, "Triangles" ); -} - -/* - * Bound Sphere - */ - - -BoundSphere::BoundSphere() -{ - radius = -1; -} - -BoundSphere::BoundSphere( const Vector3 & c, float r ) -{ - center = c; - radius = r; -} - -BoundSphere::BoundSphere( const BoundSphere & other ) -{ - operator=( other ); -} - -BoundSphere::BoundSphere( const QVector & verts ) -{ - if ( verts.isEmpty() ) - { - center = Vector3(); - radius = -1; - } - else - { - center = Vector3(); - foreach ( Vector3 v, verts ) - { - center += v; - } - center /= verts.count(); - - radius = 0; - foreach ( Vector3 v, verts ) - { - float d = ( center - v ).squaredLength(); - if ( d > radius ) - radius = d; - } - radius = sqrt( radius ); - } -} - -BoundSphere & BoundSphere::operator=( const BoundSphere & o ) -{ - center = o.center; - radius = o.radius; - return *this; -} - -BoundSphere & BoundSphere::operator|=( const BoundSphere & o ) -{ - if ( o.radius < 0 ) - return *this; - if ( radius < 0 ) - return operator=( o ); - - float d = ( center - o.center ).length(); - - if ( radius >= d + o.radius ) - return * this; - if ( o.radius >= d + radius ) - return operator=( o ); - - if ( o.radius > radius ) radius = o.radius; - radius += d / 2; - center = ( center + o.center ) / 2; - - return *this; -} - -BoundSphere BoundSphere::operator|( const BoundSphere & other ) -{ - BoundSphere b( *this ); - b |= other; - return b; -} - -BoundSphere & BoundSphere::apply( const Transform & t ) -{ - center = t * center; - radius *= fabs( t.scale ); - return *this; -} - -BoundSphere & BoundSphere::applyInv( const Transform & t ) -{ - center = t.rotation.inverted() * ( center - t.translation ) / t.scale; - radius /= fabs( t.scale ); - return *this; -} - -BoundSphere operator*( const Transform & t, const BoundSphere & sphere ) -{ - BoundSphere bs( sphere ); - return bs.apply( t ); -} - - -/* - * draw primitives - */ - -void drawAxes( Vector3 c, float axis ) -{ - glPushMatrix(); - glTranslate( c ); - GLfloat arrow = axis / 36.0; - glBegin( GL_LINES ); - glColor3f( 1.0, 0.0, 0.0 ); - glVertex3f( - axis, 0, 0 ); - glVertex3f( + axis, 0, 0 ); - glVertex3f( + axis, 0, 0 ); - glVertex3f( + axis - 3 * arrow, + arrow, + arrow ); - glVertex3f( + axis, 0, 0 ); - glVertex3f( + axis - 3 * arrow, - arrow, + arrow ); - glVertex3f( + axis, 0, 0 ); - glVertex3f( + axis - 3 * arrow, + arrow, - arrow ); - glVertex3f( + axis, 0, 0 ); - glVertex3f( + axis - 3 * arrow, - arrow, - arrow ); - glColor3f( 0.0, 1.0, 0.0 ); - glVertex3f( 0, - axis, 0 ); - glVertex3f( 0, + axis, 0 ); - glVertex3f( 0, + axis, 0 ); - glVertex3f( + arrow, + axis - 3 * arrow, + arrow ); - glVertex3f( 0, + axis, 0 ); - glVertex3f( - arrow, + axis - 3 * arrow, + arrow ); - glVertex3f( 0, + axis, 0 ); - glVertex3f( + arrow, + axis - 3 * arrow, - arrow ); - glVertex3f( 0, + axis, 0 ); - glVertex3f( - arrow, + axis - 3 * arrow, - arrow ); - glColor3f( 0.0, 0.0, 1.0 ); - glVertex3f( 0, 0, - axis ); - glVertex3f( 0, 0, + axis ); - glVertex3f( 0, 0, + axis ); - glVertex3f( + arrow, + arrow, + axis - 3 * arrow ); - glVertex3f( 0, 0, + axis ); - glVertex3f( - arrow, + arrow, + axis - 3 * arrow ); - glVertex3f( 0, 0, + axis ); - glVertex3f( + arrow, - arrow, + axis - 3 * arrow ); - glVertex3f( 0, 0, + axis ); - glVertex3f( - arrow, - arrow, + axis - 3 * arrow ); - glEnd(); - glPopMatrix(); -} - -void drawBox( Vector3 a, Vector3 b ) -{ - glBegin( GL_LINE_STRIP ); - glVertex3f( a[0], a[1], a[2] ); - glVertex3f( a[0], b[1], a[2] ); - glVertex3f( a[0], b[1], b[2] ); - glVertex3f( a[0], a[1], b[2] ); - glVertex3f( a[0], a[1], a[2] ); - glEnd(); - glBegin( GL_LINE_STRIP ); - glVertex3f( b[0], a[1], a[2] ); - glVertex3f( b[0], b[1], a[2] ); - glVertex3f( b[0], b[1], b[2] ); - glVertex3f( b[0], a[1], b[2] ); - glVertex3f( b[0], a[1], a[2] ); - glEnd(); - glBegin( GL_LINES ); - glVertex3f( a[0], a[1], a[2] ); - glVertex3f( b[0], a[1], a[2] ); - glVertex3f( a[0], b[1], a[2] ); - glVertex3f( b[0], b[1], a[2] ); - glVertex3f( a[0], b[1], b[2] ); - glVertex3f( b[0], b[1], b[2] ); - glVertex3f( a[0], a[1], b[2] ); - glVertex3f( b[0], a[1], b[2] ); - glEnd(); -} - -void drawCircle( Vector3 c, Vector3 n, float r, int sd ) -{ - Vector3 x = Vector3::crossproduct( n, Vector3( n[1], n[2], n[0] ) ); - Vector3 y = Vector3::crossproduct( n, x ); - drawArc( c, x * r, y * r, - PI, + PI, sd ); -} - -void drawArc( Vector3 c, Vector3 x, Vector3 y, float an, float ax, int sd ) -{ - glBegin( GL_LINE_STRIP ); - for ( int j = 0; j <= sd; j++ ) - { - float f = ( ax - an ) * float( j ) / float( sd ) + an; - - glVertex( c + x * sin( f ) + y * cos( f ) ); - } - glEnd(); -} - -void drawCone( Vector3 c, Vector3 n, float a, int sd ) -{ - Vector3 x = Vector3::crossproduct( n, Vector3( n[1], n[2], n[0] ) ); - Vector3 y = Vector3::crossproduct( n, x ); - - x = x * sin( a ); - y = y * sin( a ); - n = n * cos( a ); - - glBegin( GL_TRIANGLE_FAN ); - glVertex( c ); - for ( int i = 0; i <= sd; i++ ) - { - float f = ( 2 * PI * float( i ) / float( sd ) ); - - glVertex( c + n + x * sin( f ) + y * cos( f ) ); - } - glEnd(); - - // double-sided, please - - glBegin( GL_TRIANGLE_FAN ); - glVertex( c ); - for ( int i = 0; i <= sd; i++ ) - { - float f = ( 2 * PI * float( i ) / float( sd ) ); - - glVertex( c + n + x * sin( -f ) + y * cos( -f ) ); - } - glEnd(); -} - -void drawRagdollCone( Vector3 pivot, Vector3 twist, Vector3 plane, float coneAngle, float minPlaneAngle, float maxPlaneAngle, int sd ) -{ - Vector3 z = twist; - Vector3 y = plane; - Vector3 x = Vector3::crossproduct( z, y ); - - x = x * sin( coneAngle ); - y = y; - z = z; - - glBegin( GL_TRIANGLE_FAN ); - glVertex( pivot ); - for ( int i = 0; i <= sd; i++ ) - { - float f = ( 2.0f * PI * float( i ) / float( sd ) ); - - Vector3 xy = x * sin( f ) + y * sin( f <= PI / 2 || f >= 3 * PI / 2 ? maxPlaneAngle : -minPlaneAngle ) * cos( f ); - - glVertex( pivot + z * sqrt( 1 - xy.length() * xy.length() ) + xy ); - } - glEnd(); - - // double-sided, please - - glBegin( GL_TRIANGLE_FAN ); - glVertex( pivot ); - for ( int i = 0; i <= sd; i++ ) - { - float f = ( 2.0f * PI * float( i ) / float( sd ) ); - - Vector3 xy = x * sin( -f ) + y * sin( -f <= PI / 2 || -f >= 3 * PI / 2 ? maxPlaneAngle : -minPlaneAngle ) * cos( -f ); - - glVertex( pivot + z * sqrt( 1 - xy.length() * xy.length() ) + xy ); - } - glEnd(); -} - -void drawSpring( Vector3 a, Vector3 b, float stiffness, int sd, bool solid ) -{ // draw a spring with stiffness turns - bool cull = glIsEnabled( GL_CULL_FACE ); - glDisable( GL_CULL_FACE ); - - Vector3 h = b - a; - - float r = h.length() / 5; - - Vector3 n = h; - n.normalize(); - - Vector3 x = Vector3::crossproduct( n, Vector3( n[1], n[2], n[0] ) ); - Vector3 y = Vector3::crossproduct( n, x ); - - x.normalize(); - y.normalize(); - - x*=r; - y*=r; - - glBegin( GL_LINES ); - glVertex( a ); - glVertex( a + x * sinf( 0 ) + y * cosf( 0 ) ); - glEnd(); - glBegin( solid ? GL_QUAD_STRIP : GL_LINE_STRIP ); - int m = int( stiffness * sd ); - for ( int i = 0; i <= m; i++ ) - { - float f = 2 * PI * float( i ) / float( sd ); - - glVertex( a + h * i / m + x * sinf( f ) + y * cosf( f ) ); - if ( solid ) - glVertex( a + h * i / m + x * 0.8f * sinf( f ) + y * 0.8f * cosf( f ) ); - } - glEnd(); - glBegin( GL_LINES ); - glVertex( b + x * sinf( 2 * PI * float( m ) / float( sd ) ) + y * cosf( 2 * PI * float( m ) / float( sd ) ) ); - glVertex( b ); - glEnd(); - if ( cull ) - glEnable( GL_CULL_FACE ); -} - -void drawRail( const Vector3 &a, const Vector3 &b ) -{ - /* offset between beginning and end points */ - Vector3 off = b - a; - - /* direction vector of "rail track width", in xy-plane */ - Vector3 x = Vector3( - off[1], off[0], 0 ); - if( x.length() < 0.0001f ) { - x[0] = 1.0f; } - x.normalize(); - - glBegin( GL_POINTS ); - glVertex( a ); - glVertex( b ); - glEnd(); - - /* draw the rail */ - glBegin( GL_LINES ); - glVertex( a + x ); - glVertex( b + x ); - glVertex( a - x ); - glVertex( b - x ); - glEnd(); - - int len = int( off.length() ); - - /* draw the logs */ - glBegin( GL_LINES ); - for ( int i = 0; i <= len; i++ ) - { - float rel_off = ( 1.0f * i ) / len; - glVertex( a + off * rel_off + x * 1.3f ); - glVertex( a + off * rel_off - x * 1.3f ); - } - glEnd(); -} - -void drawSolidArc( Vector3 c, Vector3 n, Vector3 x, Vector3 y, float an, float ax, float r, int sd ) -{ - bool cull = glIsEnabled( GL_CULL_FACE ); - glDisable( GL_CULL_FACE ); - glBegin( GL_QUAD_STRIP ); - for ( int j = 0; j <= sd; j++ ) - { - float f = ( ax - an ) * float( j ) / float( sd ) + an; - - glVertex( c + x * r * sin( f ) + y * r * cos( f ) + n ); - glVertex( c + x * r * sin( f ) + y * r * cos( f ) - n ); - } - glEnd(); - if ( cull ) - glEnable( GL_CULL_FACE ); -} - -void drawSphere( Vector3 c, float r, int sd ) -{ - for ( int j = -sd; j <= sd; j++ ) - { - float f = PI * float( j ) / float( sd ); - Vector3 cj = c + Vector3( 0, 0, r * cos( f ) ); - float rj = r * sin( f ); - - glBegin( GL_LINE_STRIP ); - for ( int i = 0; i <= sd*2; i++ ) - glVertex( Vector3( sin( PI / sd * i ), cos( PI / sd * i ), 0 ) * rj + cj ); - glEnd(); - } - for ( int j = -sd; j <= sd; j++ ) - { - float f = PI * float( j ) / float( sd ); - Vector3 cj = c + Vector3( 0, r * cos( f ), 0 ); - float rj = r * sin( f ); - - glBegin( GL_LINE_STRIP ); - for ( int i = 0; i <= sd*2; i++ ) - glVertex( Vector3( sin( PI / sd * i ), 0, cos( PI / sd * i ) ) * rj + cj ); - glEnd(); - } - for ( int j = -sd; j <= sd; j++ ) - { - float f = PI * float( j ) / float( sd ); - Vector3 cj = c + Vector3( r * cos( f ), 0, 0 ); - float rj = r * sin( f ); - - glBegin( GL_LINE_STRIP ); - for ( int i = 0; i <= sd*2; i++ ) - glVertex( Vector3( 0, sin( PI / sd * i ), cos( PI / sd * i ) ) * rj + cj ); - glEnd(); - } -} - -void drawCapsule( Vector3 a, Vector3 b, float r, int sd ) -{ - Vector3 d = b - a; - if ( d.length() < 0.001 ) - { - drawSphere( a, r ); - return; - } - - Vector3 n = d; - n.normalize(); - - Vector3 x( n[1], n[2], n[0] ); - Vector3 y = Vector3::crossproduct( n, x ); - x = Vector3::crossproduct( n, y ); - - x *= r; - y *= r; - - glBegin( GL_LINE_STRIP ); - for ( int i = 0; i <= sd*2; i++ ) - glVertex( a + d/2 + x * sin( PI / sd * i ) + y * cos( PI / sd * i ) ); - glEnd(); - glBegin( GL_LINES ); - for ( int i = 0; i <= sd*2; i++ ) - { - glVertex( a + x * sin( PI / sd * i ) + y * cos( PI / sd * i ) ); - glVertex( b + x * sin( PI / sd * i ) + y * cos( PI / sd * i ) ); - } - glEnd(); - for ( int j = 0; j <= sd; j++ ) - { - float f = PI * float( j ) / float( sd * 2 ); - Vector3 dj = n * r * cos( f ); - float rj = sin( f ); - - glBegin( GL_LINE_STRIP ); - for ( int i = 0; i <= sd*2; i++ ) - glVertex( a - dj + x * sin( PI / sd * i ) * rj + y * cos( PI / sd * i ) * rj ); - glEnd(); - glBegin( GL_LINE_STRIP ); - for ( int i = 0; i <= sd*2; i++ ) - glVertex( b + dj + x * sin( PI / sd * i ) * rj + y * cos( PI / sd * i ) * rj ); - glEnd(); - } -} - -void drawDashLine( Vector3 a, Vector3 b, int sd ) -{ - Vector3 d = ( b - a ) / float( sd ); - glBegin( GL_LINES ); - for ( int c = 0; c <= sd; c++ ) - { - glVertex( a + d * c ); - } - glEnd(); -} - -void drawConvexHull( QVector vertices, QVector normals ) -{ - glBegin( GL_LINES ); - for ( int i = 1; i < vertices.count(); i++ ) - { - for ( int j = 0; j < i; j++ ) - { - glVertex( vertices[i] ); - glVertex( vertices[j] ); - } - } - /* - Vector3 c; - foreach ( Vector4 v, vertices ) - c += Vector3( v ); - if ( vertices.count() ) - c /= vertices.count(); - foreach ( Vector4 n, normals ) - { - glVertex( c + Vector3( n ) * ( n[3] ) ); - glVertex( c + Vector3( n ) * ( - 1 + n[3] ) ); - } - */ - glEnd(); -} - -// Renders text using the font initialized in the primary view class -void renderText(const Vector3& c, const QString & str) -{ - renderText(c[0], c[1], c[2], str); -} -void renderText(double x, double y, double z, const QString & str) -{ - glPushAttrib(GL_ALL_ATTRIB_BITS); - - glDisable(GL_TEXTURE_1D); - glDisable(GL_TEXTURE_2D); - glDisable(GL_CULL_FACE); - - glRasterPos3d(x, y, z); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - glAlphaFunc(GL_GREATER, 0.0); - glEnable(GL_ALPHA_TEST); - - QByteArray cstr(str.toLatin1()); - glCallLists(cstr.size(), GL_UNSIGNED_BYTE, cstr.constData()); - glPopAttrib(); +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "gltools.h" + +#include "nifmodel.h" + +#include + + +BoneWeights::BoneWeights( const NifModel * nif, const QModelIndex & index, int b ) +{ + trans = Transform( nif, index ); + center = nif->get( index, "Center" ); + radius = nif->get( index, "Radius" ); + bone = b; + + QModelIndex idxWeights = nif->getIndex( index, "Vertex Weights" ); + if ( idxWeights.isValid() ) + { + for ( int c = 0; c < nif->rowCount( idxWeights ); c++ ) + { + QModelIndex idx = idxWeights.child( c, 0 ); + weights.append( VertexWeight( nif->get( idx, "Index" ), nif->get( idx, "Weight" ) ) ); + } + } + else + qWarning() << nif->getBlockNumber( index ) << "vertex weights not found"; +} + + +SkinPartition::SkinPartition( const NifModel * nif, const QModelIndex & index ) +{ + numWeightsPerVertex = nif->get( index, "Num Weights Per Vertex" ); + + vertexMap = nif->getArray( index, "Vertex Map" ); + + if ( vertexMap.isEmpty() ) + { + vertexMap.resize( nif->get( index, "Num Vertices" ) ); + for ( int x = 0; x < vertexMap.count(); x++ ) + vertexMap[x] = x; + } + + boneMap = nif->getArray( index, "Bones" ); + + QModelIndex iWeights = nif->getIndex( index, "Vertex Weights" ); + QModelIndex iBoneIndices = nif->getIndex( index, "Bone Indices" ); + + weights.resize( vertexMap.count() * numWeightsPerVertex ); + + for ( int v = 0; v < vertexMap.count(); v++ ) + { + for ( int w = 0; w < numWeightsPerVertex; w++ ) + { + QModelIndex iw = iWeights.child( v, 0 ).child( w, 0 ); + QModelIndex ib = iBoneIndices.child( v, 0 ).child( w, 0 ); + + weights[ v * numWeightsPerVertex + w ].first = ( ib.isValid() ? nif->get( ib ) : 0 ); + weights[ v * numWeightsPerVertex + w ].second = ( iw.isValid() ? nif->get( iw ) : 0 ); + } + } + + QModelIndex iStrips = nif->getIndex( index, "Strips" ); + for ( int s = 0; s < nif->rowCount( iStrips ); s++ ) + { + tristrips << nif->getArray( iStrips.child( s, 0 ) ); + } + + triangles = nif->getArray( index, "Triangles" ); +} + +/* + * Bound Sphere + */ + + +BoundSphere::BoundSphere() +{ + radius = -1; +} + +BoundSphere::BoundSphere( const Vector3 & c, float r ) +{ + center = c; + radius = r; +} + +BoundSphere::BoundSphere( const BoundSphere & other ) +{ + operator=( other ); +} + +BoundSphere::BoundSphere( const QVector & verts ) +{ + if ( verts.isEmpty() ) + { + center = Vector3(); + radius = -1; + } + else + { + center = Vector3(); + foreach ( Vector3 v, verts ) + { + center += v; + } + center /= verts.count(); + + radius = 0; + foreach ( Vector3 v, verts ) + { + float d = ( center - v ).squaredLength(); + if ( d > radius ) + radius = d; + } + radius = sqrt( radius ); + } +} + +BoundSphere & BoundSphere::operator=( const BoundSphere & o ) +{ + center = o.center; + radius = o.radius; + return *this; +} + +BoundSphere & BoundSphere::operator|=( const BoundSphere & o ) +{ + if ( o.radius < 0 ) + return *this; + if ( radius < 0 ) + return operator=( o ); + + float d = ( center - o.center ).length(); + + if ( radius >= d + o.radius ) + return * this; + if ( o.radius >= d + radius ) + return operator=( o ); + + if ( o.radius > radius ) radius = o.radius; + radius += d / 2; + center = ( center + o.center ) / 2; + + return *this; +} + +BoundSphere BoundSphere::operator|( const BoundSphere & other ) +{ + BoundSphere b( *this ); + b |= other; + return b; +} + +BoundSphere & BoundSphere::apply( const Transform & t ) +{ + center = t * center; + radius *= fabs( t.scale ); + return *this; +} + +BoundSphere & BoundSphere::applyInv( const Transform & t ) +{ + center = t.rotation.inverted() * ( center - t.translation ) / t.scale; + radius /= fabs( t.scale ); + return *this; +} + +BoundSphere operator*( const Transform & t, const BoundSphere & sphere ) +{ + BoundSphere bs( sphere ); + return bs.apply( t ); +} + + +/* + * draw primitives + */ + +void drawAxes( Vector3 c, float axis ) +{ + glPushMatrix(); + glTranslate( c ); + GLfloat arrow = axis / 36.0; + glBegin( GL_LINES ); + glColor3f( 1.0, 0.0, 0.0 ); + glVertex3f( - axis, 0, 0 ); + glVertex3f( + axis, 0, 0 ); + glVertex3f( + axis, 0, 0 ); + glVertex3f( + axis - 3 * arrow, + arrow, + arrow ); + glVertex3f( + axis, 0, 0 ); + glVertex3f( + axis - 3 * arrow, - arrow, + arrow ); + glVertex3f( + axis, 0, 0 ); + glVertex3f( + axis - 3 * arrow, + arrow, - arrow ); + glVertex3f( + axis, 0, 0 ); + glVertex3f( + axis - 3 * arrow, - arrow, - arrow ); + glColor3f( 0.0, 1.0, 0.0 ); + glVertex3f( 0, - axis, 0 ); + glVertex3f( 0, + axis, 0 ); + glVertex3f( 0, + axis, 0 ); + glVertex3f( + arrow, + axis - 3 * arrow, + arrow ); + glVertex3f( 0, + axis, 0 ); + glVertex3f( - arrow, + axis - 3 * arrow, + arrow ); + glVertex3f( 0, + axis, 0 ); + glVertex3f( + arrow, + axis - 3 * arrow, - arrow ); + glVertex3f( 0, + axis, 0 ); + glVertex3f( - arrow, + axis - 3 * arrow, - arrow ); + glColor3f( 0.0, 0.0, 1.0 ); + glVertex3f( 0, 0, - axis ); + glVertex3f( 0, 0, + axis ); + glVertex3f( 0, 0, + axis ); + glVertex3f( + arrow, + arrow, + axis - 3 * arrow ); + glVertex3f( 0, 0, + axis ); + glVertex3f( - arrow, + arrow, + axis - 3 * arrow ); + glVertex3f( 0, 0, + axis ); + glVertex3f( + arrow, - arrow, + axis - 3 * arrow ); + glVertex3f( 0, 0, + axis ); + glVertex3f( - arrow, - arrow, + axis - 3 * arrow ); + glEnd(); + glPopMatrix(); +} + +void drawBox( Vector3 a, Vector3 b ) +{ + glBegin( GL_LINE_STRIP ); + glVertex3f( a[0], a[1], a[2] ); + glVertex3f( a[0], b[1], a[2] ); + glVertex3f( a[0], b[1], b[2] ); + glVertex3f( a[0], a[1], b[2] ); + glVertex3f( a[0], a[1], a[2] ); + glEnd(); + glBegin( GL_LINE_STRIP ); + glVertex3f( b[0], a[1], a[2] ); + glVertex3f( b[0], b[1], a[2] ); + glVertex3f( b[0], b[1], b[2] ); + glVertex3f( b[0], a[1], b[2] ); + glVertex3f( b[0], a[1], a[2] ); + glEnd(); + glBegin( GL_LINES ); + glVertex3f( a[0], a[1], a[2] ); + glVertex3f( b[0], a[1], a[2] ); + glVertex3f( a[0], b[1], a[2] ); + glVertex3f( b[0], b[1], a[2] ); + glVertex3f( a[0], b[1], b[2] ); + glVertex3f( b[0], b[1], b[2] ); + glVertex3f( a[0], a[1], b[2] ); + glVertex3f( b[0], a[1], b[2] ); + glEnd(); +} + +void drawCircle( Vector3 c, Vector3 n, float r, int sd ) +{ + Vector3 x = Vector3::crossproduct( n, Vector3( n[1], n[2], n[0] ) ); + Vector3 y = Vector3::crossproduct( n, x ); + drawArc( c, x * r, y * r, - PI, + PI, sd ); +} + +void drawArc( Vector3 c, Vector3 x, Vector3 y, float an, float ax, int sd ) +{ + glBegin( GL_LINE_STRIP ); + for ( int j = 0; j <= sd; j++ ) + { + float f = ( ax - an ) * float( j ) / float( sd ) + an; + + glVertex( c + x * sin( f ) + y * cos( f ) ); + } + glEnd(); +} + +void drawCone( Vector3 c, Vector3 n, float a, int sd ) +{ + Vector3 x = Vector3::crossproduct( n, Vector3( n[1], n[2], n[0] ) ); + Vector3 y = Vector3::crossproduct( n, x ); + + x = x * sin( a ); + y = y * sin( a ); + n = n * cos( a ); + + glBegin( GL_TRIANGLE_FAN ); + glVertex( c ); + for ( int i = 0; i <= sd; i++ ) + { + float f = ( 2 * PI * float( i ) / float( sd ) ); + + glVertex( c + n + x * sin( f ) + y * cos( f ) ); + } + glEnd(); + + // double-sided, please + + glBegin( GL_TRIANGLE_FAN ); + glVertex( c ); + for ( int i = 0; i <= sd; i++ ) + { + float f = ( 2 * PI * float( i ) / float( sd ) ); + + glVertex( c + n + x * sin( -f ) + y * cos( -f ) ); + } + glEnd(); +} + +void drawRagdollCone( Vector3 pivot, Vector3 twist, Vector3 plane, float coneAngle, float minPlaneAngle, float maxPlaneAngle, int sd ) +{ + Vector3 z = twist; + Vector3 y = plane; + Vector3 x = Vector3::crossproduct( z, y ); + + x = x * sin( coneAngle ); + y = y; + z = z; + + glBegin( GL_TRIANGLE_FAN ); + glVertex( pivot ); + for ( int i = 0; i <= sd; i++ ) + { + float f = ( 2.0f * PI * float( i ) / float( sd ) ); + + Vector3 xy = x * sin( f ) + y * sin( f <= PI / 2 || f >= 3 * PI / 2 ? maxPlaneAngle : -minPlaneAngle ) * cos( f ); + + glVertex( pivot + z * sqrt( 1 - xy.length() * xy.length() ) + xy ); + } + glEnd(); + + // double-sided, please + + glBegin( GL_TRIANGLE_FAN ); + glVertex( pivot ); + for ( int i = 0; i <= sd; i++ ) + { + float f = ( 2.0f * PI * float( i ) / float( sd ) ); + + Vector3 xy = x * sin( -f ) + y * sin( -f <= PI / 2 || -f >= 3 * PI / 2 ? maxPlaneAngle : -minPlaneAngle ) * cos( -f ); + + glVertex( pivot + z * sqrt( 1 - xy.length() * xy.length() ) + xy ); + } + glEnd(); +} + +void drawSpring( Vector3 a, Vector3 b, float stiffness, int sd, bool solid ) +{ // draw a spring with stiffness turns + bool cull = glIsEnabled( GL_CULL_FACE ); + glDisable( GL_CULL_FACE ); + + Vector3 h = b - a; + + float r = h.length() / 5; + + Vector3 n = h; + n.normalize(); + + Vector3 x = Vector3::crossproduct( n, Vector3( n[1], n[2], n[0] ) ); + Vector3 y = Vector3::crossproduct( n, x ); + + x.normalize(); + y.normalize(); + + x*=r; + y*=r; + + glBegin( GL_LINES ); + glVertex( a ); + glVertex( a + x * sinf( 0 ) + y * cosf( 0 ) ); + glEnd(); + glBegin( solid ? GL_QUAD_STRIP : GL_LINE_STRIP ); + int m = int( stiffness * sd ); + for ( int i = 0; i <= m; i++ ) + { + float f = 2 * PI * float( i ) / float( sd ); + + glVertex( a + h * i / m + x * sinf( f ) + y * cosf( f ) ); + if ( solid ) + glVertex( a + h * i / m + x * 0.8f * sinf( f ) + y * 0.8f * cosf( f ) ); + } + glEnd(); + glBegin( GL_LINES ); + glVertex( b + x * sinf( 2 * PI * float( m ) / float( sd ) ) + y * cosf( 2 * PI * float( m ) / float( sd ) ) ); + glVertex( b ); + glEnd(); + if ( cull ) + glEnable( GL_CULL_FACE ); +} + +void drawRail( const Vector3 &a, const Vector3 &b ) +{ + /* offset between beginning and end points */ + Vector3 off = b - a; + + /* direction vector of "rail track width", in xy-plane */ + Vector3 x = Vector3( - off[1], off[0], 0 ); + if( x.length() < 0.0001f ) { + x[0] = 1.0f; } + x.normalize(); + + glBegin( GL_POINTS ); + glVertex( a ); + glVertex( b ); + glEnd(); + + /* draw the rail */ + glBegin( GL_LINES ); + glVertex( a + x ); + glVertex( b + x ); + glVertex( a - x ); + glVertex( b - x ); + glEnd(); + + int len = int( off.length() ); + + /* draw the logs */ + glBegin( GL_LINES ); + for ( int i = 0; i <= len; i++ ) + { + float rel_off = ( 1.0f * i ) / len; + glVertex( a + off * rel_off + x * 1.3f ); + glVertex( a + off * rel_off - x * 1.3f ); + } + glEnd(); +} + +void drawSolidArc( Vector3 c, Vector3 n, Vector3 x, Vector3 y, float an, float ax, float r, int sd ) +{ + bool cull = glIsEnabled( GL_CULL_FACE ); + glDisable( GL_CULL_FACE ); + glBegin( GL_QUAD_STRIP ); + for ( int j = 0; j <= sd; j++ ) + { + float f = ( ax - an ) * float( j ) / float( sd ) + an; + + glVertex( c + x * r * sin( f ) + y * r * cos( f ) + n ); + glVertex( c + x * r * sin( f ) + y * r * cos( f ) - n ); + } + glEnd(); + if ( cull ) + glEnable( GL_CULL_FACE ); +} + +void drawSphere( Vector3 c, float r, int sd ) +{ + for ( int j = -sd; j <= sd; j++ ) + { + float f = PI * float( j ) / float( sd ); + Vector3 cj = c + Vector3( 0, 0, r * cos( f ) ); + float rj = r * sin( f ); + + glBegin( GL_LINE_STRIP ); + for ( int i = 0; i <= sd*2; i++ ) + glVertex( Vector3( sin( PI / sd * i ), cos( PI / sd * i ), 0 ) * rj + cj ); + glEnd(); + } + for ( int j = -sd; j <= sd; j++ ) + { + float f = PI * float( j ) / float( sd ); + Vector3 cj = c + Vector3( 0, r * cos( f ), 0 ); + float rj = r * sin( f ); + + glBegin( GL_LINE_STRIP ); + for ( int i = 0; i <= sd*2; i++ ) + glVertex( Vector3( sin( PI / sd * i ), 0, cos( PI / sd * i ) ) * rj + cj ); + glEnd(); + } + for ( int j = -sd; j <= sd; j++ ) + { + float f = PI * float( j ) / float( sd ); + Vector3 cj = c + Vector3( r * cos( f ), 0, 0 ); + float rj = r * sin( f ); + + glBegin( GL_LINE_STRIP ); + for ( int i = 0; i <= sd*2; i++ ) + glVertex( Vector3( 0, sin( PI / sd * i ), cos( PI / sd * i ) ) * rj + cj ); + glEnd(); + } +} + +void drawCapsule( Vector3 a, Vector3 b, float r, int sd ) +{ + Vector3 d = b - a; + if ( d.length() < 0.001 ) + { + drawSphere( a, r ); + return; + } + + Vector3 n = d; + n.normalize(); + + Vector3 x( n[1], n[2], n[0] ); + Vector3 y = Vector3::crossproduct( n, x ); + x = Vector3::crossproduct( n, y ); + + x *= r; + y *= r; + + glBegin( GL_LINE_STRIP ); + for ( int i = 0; i <= sd*2; i++ ) + glVertex( a + d/2 + x * sin( PI / sd * i ) + y * cos( PI / sd * i ) ); + glEnd(); + glBegin( GL_LINES ); + for ( int i = 0; i <= sd*2; i++ ) + { + glVertex( a + x * sin( PI / sd * i ) + y * cos( PI / sd * i ) ); + glVertex( b + x * sin( PI / sd * i ) + y * cos( PI / sd * i ) ); + } + glEnd(); + for ( int j = 0; j <= sd; j++ ) + { + float f = PI * float( j ) / float( sd * 2 ); + Vector3 dj = n * r * cos( f ); + float rj = sin( f ); + + glBegin( GL_LINE_STRIP ); + for ( int i = 0; i <= sd*2; i++ ) + glVertex( a - dj + x * sin( PI / sd * i ) * rj + y * cos( PI / sd * i ) * rj ); + glEnd(); + glBegin( GL_LINE_STRIP ); + for ( int i = 0; i <= sd*2; i++ ) + glVertex( b + dj + x * sin( PI / sd * i ) * rj + y * cos( PI / sd * i ) * rj ); + glEnd(); + } +} + +void drawDashLine( Vector3 a, Vector3 b, int sd ) +{ + Vector3 d = ( b - a ) / float( sd ); + glBegin( GL_LINES ); + for ( int c = 0; c <= sd; c++ ) + { + glVertex( a + d * c ); + } + glEnd(); +} + +void drawConvexHull( QVector vertices, QVector normals ) +{ + glBegin( GL_LINES ); + for ( int i = 1; i < vertices.count(); i++ ) + { + for ( int j = 0; j < i; j++ ) + { + glVertex( vertices[i] ); + glVertex( vertices[j] ); + } + } + /* + Vector3 c; + foreach ( Vector4 v, vertices ) + c += Vector3( v ); + if ( vertices.count() ) + c /= vertices.count(); + foreach ( Vector4 n, normals ) + { + glVertex( c + Vector3( n ) * ( n[3] ) ); + glVertex( c + Vector3( n ) * ( - 1 + n[3] ) ); + } + */ + glEnd(); +} + +// Renders text using the font initialized in the primary view class +void renderText(const Vector3& c, const QString & str) +{ + renderText(c[0], c[1], c[2], str); +} +void renderText(double x, double y, double z, const QString & str) +{ + glPushAttrib(GL_ALL_ATTRIB_BITS); + + glDisable(GL_TEXTURE_1D); + glDisable(GL_TEXTURE_2D); + glDisable(GL_CULL_FACE); + + glRasterPos3d(x, y, z); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glAlphaFunc(GL_GREATER, 0.0); + glEnable(GL_ALPHA_TEST); + + QByteArray cstr(str.toLatin1()); + glCallLists(cstr.size(), GL_UNSIGNED_BYTE, cstr.constData()); + glPopAttrib(); } \ No newline at end of file diff --git a/gl/gltools.h b/gl/gltools.h index f46119164..e9cd97b76 100644 --- a/gl/gltools.h +++ b/gl/gltools.h @@ -1,207 +1,207 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLTOOLS_H -#define GLTOOLS_H - -#include - -#include "niftypes.h" - -class BoundSphere -{ -public: - BoundSphere(); - BoundSphere( const BoundSphere & ); - BoundSphere( const Vector3 & center, float radius ); - BoundSphere( const QVector & vertices ); - - Vector3 center; - float radius; - - BoundSphere & operator=( const BoundSphere & ); - BoundSphere & operator|=( const BoundSphere & ); - - BoundSphere operator|( const BoundSphere & o ); - - BoundSphere & apply( const Transform & t ); - BoundSphere & applyInv( const Transform & t ); - - friend BoundSphere operator*( const Transform & t, const BoundSphere & s ); -}; - -class VertexWeight -{ -public: - VertexWeight() - { vertex = 0; weight = 0.0; } - VertexWeight( int v, float w ) - { vertex = v; weight = w; } - - int vertex; - float weight; -}; - -class BoneWeights -{ -public: - BoneWeights() { bone = 0; } - BoneWeights( const NifModel * nif, const QModelIndex & index, int b ); - - Transform trans; - Vector3 center; float radius; - Vector3 tcenter; - int bone; - QVector weights; -}; - -class SkinPartition -{ -public: - SkinPartition() { numWeightsPerVertex = 0; } - SkinPartition( const NifModel * nif, const QModelIndex & index ); - - QVector boneMap; - QVector vertexMap; - - int numWeightsPerVertex; - QVector< QPair< int, float > > weights; - - QVector< Triangle > triangles; - QList< QVector< quint16 > > tristrips; -}; - -void drawAxes( Vector3 c, float axis ); -void drawBox( Vector3 a, Vector3 b ); -void drawCircle( Vector3 c, Vector3 n, float r, int sd = 16 ); -void drawArc( Vector3 c, Vector3 x, Vector3 y, float an, float ax, int sd = 8 ); -void drawSolidArc( Vector3 c, Vector3 n, Vector3 x, Vector3 y, float an, float ax, float r, int sd = 8 ); -void drawCone( Vector3 c, Vector3 n, float a, int sd = 16 ); -void drawRagdollCone( Vector3 pivot, Vector3 twist, Vector3 plane, float coneAngle, float minPlaneAngle, float maxPlaneAngle, int sd = 16 ); -void drawSphere( Vector3 c, float r, int sd = 8 ); -void drawCapsule( Vector3 a, Vector3 b, float r, int sd = 5 ); -void drawDashLine( Vector3 a, Vector3 b, int sd = 15 ); -void drawConvexHull( QVector vertices, QVector normals ); -void drawSpring( Vector3 a, Vector3 b, float stiffness, int sd = 16, bool solid = false ); -void drawRail( const Vector3 &a, const Vector3 &b ); - -inline void glTranslate( const Vector3 & v ) -{ - glTranslatef( v[0], v[1], v[2] ); -} - -inline void glScale( const Vector3 & v ) -{ - glScalef( v[0], v[1], v[2] ); -} - -inline void glVertex( const Vector2 & v ) -{ - glVertex2fv( v.data() ); -} - -inline void glVertex( const Vector3 & v ) -{ - glVertex3fv( v.data() ); -} - -inline void glVertex( const Vector4 & v ) -{ - glVertex3fv( v.data() ); -} - -inline void glNormal( const Vector3 & v ) -{ - glNormal3fv( v.data() ); -} - -inline void glTexCoord( const Vector2 & v ) -{ - glTexCoord2fv( v.data() ); -} - -inline void glColor( const Color3 & c ) -{ - glColor3fv( c.data() ); -} - -inline void glColor( const Color4 & c ) -{ - glColor4fv( c.data() ); -} - -inline void glMaterial( GLenum x, GLenum y, const Color4 & c ) -{ - glMaterialfv( x, y, c.data() ); -} - -inline void glLoadMatrix( const Matrix4 & m ) -{ - glLoadMatrixf( m.data() ); -} - -inline void glMultMatrix( const Matrix4 & m ) -{ - glMultMatrixf( m.data() ); -} - -inline void glLoadMatrix( const Transform & t ) -{ - glLoadMatrix( t.toMatrix4() ); -} - -inline void glMultMatrix( const Transform & t ) -{ - glMultMatrix( t.toMatrix4() ); -} - - -inline GLuint glClosestMatch( GLuint * buffer, GLint hits ) -{ // a little helper function, returns the closest matching hit from the name buffer - GLuint choose = buffer[ 3 ]; - GLuint depth = buffer[ 1 ]; - for ( int loop = 1; loop < hits; loop++ ) - { - if ( buffer[ loop * 4 + 1 ] < depth ) - { - choose = buffer[ loop * 4 + 3 ]; - depth = buffer[ loop * 4 + 1 ]; - } - } - return choose; -} - -void renderText(double x, double y, double z, const QString & str); -void renderText(const Vector3& c, const QString & str); - - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLTOOLS_H +#define GLTOOLS_H + +#include + +#include "niftypes.h" + +class BoundSphere +{ +public: + BoundSphere(); + BoundSphere( const BoundSphere & ); + BoundSphere( const Vector3 & center, float radius ); + BoundSphere( const QVector & vertices ); + + Vector3 center; + float radius; + + BoundSphere & operator=( const BoundSphere & ); + BoundSphere & operator|=( const BoundSphere & ); + + BoundSphere operator|( const BoundSphere & o ); + + BoundSphere & apply( const Transform & t ); + BoundSphere & applyInv( const Transform & t ); + + friend BoundSphere operator*( const Transform & t, const BoundSphere & s ); +}; + +class VertexWeight +{ +public: + VertexWeight() + { vertex = 0; weight = 0.0; } + VertexWeight( int v, float w ) + { vertex = v; weight = w; } + + int vertex; + float weight; +}; + +class BoneWeights +{ +public: + BoneWeights() { bone = 0; } + BoneWeights( const NifModel * nif, const QModelIndex & index, int b ); + + Transform trans; + Vector3 center; float radius; + Vector3 tcenter; + int bone; + QVector weights; +}; + +class SkinPartition +{ +public: + SkinPartition() { numWeightsPerVertex = 0; } + SkinPartition( const NifModel * nif, const QModelIndex & index ); + + QVector boneMap; + QVector vertexMap; + + int numWeightsPerVertex; + QVector< QPair< int, float > > weights; + + QVector< Triangle > triangles; + QList< QVector< quint16 > > tristrips; +}; + +void drawAxes( Vector3 c, float axis ); +void drawBox( Vector3 a, Vector3 b ); +void drawCircle( Vector3 c, Vector3 n, float r, int sd = 16 ); +void drawArc( Vector3 c, Vector3 x, Vector3 y, float an, float ax, int sd = 8 ); +void drawSolidArc( Vector3 c, Vector3 n, Vector3 x, Vector3 y, float an, float ax, float r, int sd = 8 ); +void drawCone( Vector3 c, Vector3 n, float a, int sd = 16 ); +void drawRagdollCone( Vector3 pivot, Vector3 twist, Vector3 plane, float coneAngle, float minPlaneAngle, float maxPlaneAngle, int sd = 16 ); +void drawSphere( Vector3 c, float r, int sd = 8 ); +void drawCapsule( Vector3 a, Vector3 b, float r, int sd = 5 ); +void drawDashLine( Vector3 a, Vector3 b, int sd = 15 ); +void drawConvexHull( QVector vertices, QVector normals ); +void drawSpring( Vector3 a, Vector3 b, float stiffness, int sd = 16, bool solid = false ); +void drawRail( const Vector3 &a, const Vector3 &b ); + +inline void glTranslate( const Vector3 & v ) +{ + glTranslatef( v[0], v[1], v[2] ); +} + +inline void glScale( const Vector3 & v ) +{ + glScalef( v[0], v[1], v[2] ); +} + +inline void glVertex( const Vector2 & v ) +{ + glVertex2fv( v.data() ); +} + +inline void glVertex( const Vector3 & v ) +{ + glVertex3fv( v.data() ); +} + +inline void glVertex( const Vector4 & v ) +{ + glVertex3fv( v.data() ); +} + +inline void glNormal( const Vector3 & v ) +{ + glNormal3fv( v.data() ); +} + +inline void glTexCoord( const Vector2 & v ) +{ + glTexCoord2fv( v.data() ); +} + +inline void glColor( const Color3 & c ) +{ + glColor3fv( c.data() ); +} + +inline void glColor( const Color4 & c ) +{ + glColor4fv( c.data() ); +} + +inline void glMaterial( GLenum x, GLenum y, const Color4 & c ) +{ + glMaterialfv( x, y, c.data() ); +} + +inline void glLoadMatrix( const Matrix4 & m ) +{ + glLoadMatrixf( m.data() ); +} + +inline void glMultMatrix( const Matrix4 & m ) +{ + glMultMatrixf( m.data() ); +} + +inline void glLoadMatrix( const Transform & t ) +{ + glLoadMatrix( t.toMatrix4() ); +} + +inline void glMultMatrix( const Transform & t ) +{ + glMultMatrix( t.toMatrix4() ); +} + + +inline GLuint glClosestMatch( GLuint * buffer, GLint hits ) +{ // a little helper function, returns the closest matching hit from the name buffer + GLuint choose = buffer[ 3 ]; + GLuint depth = buffer[ 1 ]; + for ( int loop = 1; loop < hits; loop++ ) + { + if ( buffer[ loop * 4 + 1 ] < depth ) + { + choose = buffer[ loop * 4 + 3 ]; + depth = buffer[ loop * 4 + 1 ]; + } + } + return choose; +} + +void renderText(double x, double y, double z, const QString & str); +void renderText(const Vector3& c, const QString & str); + + +#endif diff --git a/gl/marker/constraints.h b/gl/marker/constraints.h index 9d1743d64..359947fc6 100644 --- a/gl/marker/constraints.h +++ b/gl/marker/constraints.h @@ -1,283 +1,283 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "../glmarker.h" - -static const float BumperMarker01Verts[] = -{ --0.653245f, 0.900000f, 1.213643f, --0.653245f, -0.900000f, 1.213643f, --2.700000f, -0.900000f, 0.100000f, --2.474048f, -0.900000f, 0.100000f, --2.700000f, -0.900000f, -0.100000f, --2.125359f, -0.900000f, 0.100000f, -0.000000f, -0.900000f, -0.100000f, -0.000000f, -0.900000f, 0.100000f, --2.700000f, -1.100000f, -0.100000f, --2.474048f, -1.100000f, 0.100000f, --2.700001f, -1.100000f, 0.100000f, --2.125359f, -1.100000f, 0.100000f, -0.000000f, -1.100000f, -0.100000f, --0.000001f, -1.100000f, 0.100000f, --2.700000f, 1.100000f, 0.100000f, --2.474048f, 1.100000f, 0.100000f, --2.700000f, 1.100000f, -0.100000f, --2.125359f, 1.100000f, 0.100000f, -0.000000f, 1.100000f, -0.100000f, -0.000000f, 1.100000f, 0.100000f, --2.700000f, 0.900000f, -0.100000f, --2.474048f, 0.900000f, 0.100000f, --2.700001f, 0.900000f, 0.100000f, --2.125359f, 0.900000f, 0.100000f, -0.000000f, 0.900000f, -0.100000f, --0.000001f, 0.900000f, 0.100000f, --1.056096f, -0.900000f, 0.848706f, --0.364301f, -0.900000f, 0.100000f, --1.056096f, -1.100000f, 0.848706f, --0.706968f, -1.100000f, 0.100000f, --0.364301f, -1.100000f, 0.100000f, --0.706968f, 1.100000f, 0.100000f, --1.056096f, 1.100000f, 0.848706f, --0.364301f, 1.100000f, 0.100000f, --1.056096f, 0.900000f, 0.848706f, --0.364301f, 0.900000f, 0.100000f, --0.653245f, -1.100000f, 0.719644f, --0.653245f, -0.900000f, 0.719644f, --0.653245f, 0.900000f, 0.719644f, --0.653245f, 1.100000f, 0.719644f, --0.883601f, -0.900000f, 1.213643f, --1.141923f, -0.900000f, 1.032764f, --0.883601f, -1.100000f, 1.213643f, --1.141923f, -1.100000f, 1.032764f, --0.883601f, 1.100000f, 1.213643f, --1.141923f, 1.100000f, 1.032764f, --0.883601f, 0.900000f, 1.213643f, --1.141923f, 0.900000f, 1.032764f, --0.403245f, -0.900000f, 0.719644f, --0.403245f, -1.100000f, 0.719644f, --0.403245f, 1.100000f, 0.719644f, --0.403245f, 0.900000f, 0.719644f, --0.403245f, -0.900000f, 1.213643f, --0.403245f, -1.100000f, 1.213643f, --0.403245f, 1.100000f, 1.213643f, --0.403245f, 0.900000f, 1.213643f, --0.505289f, 0.900000f, 0.400000f, --0.705289f, 0.900000f, 0.400000f, --0.705289f, -0.899999f, 0.400000f, --0.505289f, -0.900001f, 0.400000f, --0.505289f, 0.899999f, 0.100000f, --0.706968f, 0.900000f, 0.100000f, --0.706968f, -0.900000f, 0.100000f, --0.505289f, -0.900000f, 0.100000f, --0.403245f, 1.000000f, 1.095567f, --0.403245f, 1.091924f, 1.003643f, --0.403245f, -1.000000f, 1.095567f, --0.403245f, -0.908076f, 1.003643f, --0.003245f, 1.070711f, 1.003643f, --0.003245f, 1.000000f, 1.074354f, --0.003245f, -0.929289f, 1.003643f, --0.003245f, -1.000000f, 1.074354f, --0.403245f, 1.000000f, 0.911719f, --0.003245f, 1.000000f, 0.932932f, --0.003245f, 0.929289f, 1.003643f, --0.403245f, 0.908076f, 1.003643f, --0.403245f, -1.000000f, 0.911719f, --0.003245f, -1.000000f, 0.932932f, --0.403245f, -1.091924f, 1.003643f, --0.003245f, -1.070711f, 1.003643f, --0.003245f, -1.000000f, 1.215775f, --0.003245f, -0.787868f, 1.003643f, --0.003245f, -1.000000f, 0.791511f, --0.003245f, -1.212132f, 1.003643f, --0.003245f, 0.787868f, 1.003643f, --0.003245f, 1.000000f, 0.791511f, --0.003245f, 1.212132f, 1.003643f, --0.003245f, 1.000000f, 1.215775f, --0.003245f, -1.212132f, 1.003643f, --0.003245f, -1.000000f, 0.791511f, --0.003245f, -0.787868f, 1.003643f, --0.003245f, -1.000000f, 1.215775f, --0.003245f, 1.000000f, 1.215775f, --0.003245f, 1.212132f, 1.003643f, --0.003245f, 1.000000f, 0.791511f, --0.003245f, 0.787868f, 1.003643f, -}; - -static const unsigned short BumperMarker01Faces[] = -{ -2, 3, 4, -5, 4, 3, -5, 6, 4, -5, 7, 6, -8, 9, 10, -8, 11, 9, -12, 11, 8, -12, 13, 11, -14, 15, 16, -17, 16, 15, -17, 18, 16, -17, 19, 18, -20, 21, 22, -20, 23, 21, -24, 23, 20, -24, 25, 23, -37, 62, 26, -37, 27, 62, -36, 28, 29, -30, 36, 29, -39, 31, 32, -39, 33, 31, -38, 34, 61, -35, 38, 61, -1, 40, 42, -28, 42, 43, -28, 36, 42, -26, 41, 40, -34, 46, 47, -32, 45, 44, -32, 44, 39, -0, 44, 46, -72, 75, 74, -72, 74, 73, -76, 78, 79, -76, 79, 77, -66, 71, 79, -66, 79, 78, -70, 67, 76, -70, 76, 77, -64, 69, 74, -64, 74, 75, -68, 65, 72, -68, 72, 73, -66, 67, 70, -66, 70, 71, -64, 65, 68, -64, 68, 69, -61, 60, 62, -60, 63, 62, -59, 56, 58, -56, 57, 58, -62, 58, 61, -58, 57, 61, -60, 56, 59, -60, 59, 63, -48, 51, 55, -48, 55, 52, -37, 38, 51, -37, 51, 48, -0, 1, 52, -0, 52, 55, -0, 55, 54, -0, 54, 44, -50, 39, 54, -39, 44, 54, -0, 46, 34, -0, 34, 38, -26, 40, 1, -26, 1, 37, -36, 49, 53, -36, 53, 42, -1, 42, 53, -1, 53, 52, -0, 37, 1, -0, 38, 37, -54, 55, 51, -54, 51, 50, -52, 53, 49, -52, 49, 48, -38, 39, 50, -38, 50, 51, -36, 37, 48, -36, 48, 49, -33, 39, 38, -33, 38, 35, -27, 37, 36, -27, 36, 30, -32, 17, 45, -17, 15, 45, -17, 32, 34, -17, 34, 23, -61, 34, 32, -61, 32, 31, -61, 31, 17, -61, 17, 23, -21, 15, 14, -21, 14, 22, -15, 21, 47, -15, 47, 45, -34, 47, 23, -47, 21, 23, -19, 33, 25, -33, 35, 25, -47, 46, 44, -47, 44, 45, -18, 19, 24, -19, 25, 24, -14, 16, 20, -14, 20, 22, -26, 5, 41, -5, 3, 41, -5, 26, 28, -5, 28, 11, -29, 28, 26, -29, 26, 62, -29, 62, 5, -29, 5, 11, -2, 10, 9, -2, 9, 3, -3, 9, 43, -3, 43, 41, -28, 43, 11, -43, 9, 11, -7, 27, 13, -27, 30, 13, -43, 42, 40, -43, 40, 41, -6, 7, 12, -7, 13, 12, -2, 4, 8, -2, 8, 10, -16, 18, 24, -16, 24, 20, -8, 4, 12, -4, 6, 12, -85, 84, 87, -85, 87, 86, -82, 83, 80, -82, 80, 81, -89, 90, 91, -89, 91, 88, -94, 93, 92, -94, 92, 95, -}; - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "../glmarker.h" + +static const float BumperMarker01Verts[] = +{ +-0.653245f, 0.900000f, 1.213643f, +-0.653245f, -0.900000f, 1.213643f, +-2.700000f, -0.900000f, 0.100000f, +-2.474048f, -0.900000f, 0.100000f, +-2.700000f, -0.900000f, -0.100000f, +-2.125359f, -0.900000f, 0.100000f, +0.000000f, -0.900000f, -0.100000f, +0.000000f, -0.900000f, 0.100000f, +-2.700000f, -1.100000f, -0.100000f, +-2.474048f, -1.100000f, 0.100000f, +-2.700001f, -1.100000f, 0.100000f, +-2.125359f, -1.100000f, 0.100000f, +0.000000f, -1.100000f, -0.100000f, +-0.000001f, -1.100000f, 0.100000f, +-2.700000f, 1.100000f, 0.100000f, +-2.474048f, 1.100000f, 0.100000f, +-2.700000f, 1.100000f, -0.100000f, +-2.125359f, 1.100000f, 0.100000f, +0.000000f, 1.100000f, -0.100000f, +0.000000f, 1.100000f, 0.100000f, +-2.700000f, 0.900000f, -0.100000f, +-2.474048f, 0.900000f, 0.100000f, +-2.700001f, 0.900000f, 0.100000f, +-2.125359f, 0.900000f, 0.100000f, +0.000000f, 0.900000f, -0.100000f, +-0.000001f, 0.900000f, 0.100000f, +-1.056096f, -0.900000f, 0.848706f, +-0.364301f, -0.900000f, 0.100000f, +-1.056096f, -1.100000f, 0.848706f, +-0.706968f, -1.100000f, 0.100000f, +-0.364301f, -1.100000f, 0.100000f, +-0.706968f, 1.100000f, 0.100000f, +-1.056096f, 1.100000f, 0.848706f, +-0.364301f, 1.100000f, 0.100000f, +-1.056096f, 0.900000f, 0.848706f, +-0.364301f, 0.900000f, 0.100000f, +-0.653245f, -1.100000f, 0.719644f, +-0.653245f, -0.900000f, 0.719644f, +-0.653245f, 0.900000f, 0.719644f, +-0.653245f, 1.100000f, 0.719644f, +-0.883601f, -0.900000f, 1.213643f, +-1.141923f, -0.900000f, 1.032764f, +-0.883601f, -1.100000f, 1.213643f, +-1.141923f, -1.100000f, 1.032764f, +-0.883601f, 1.100000f, 1.213643f, +-1.141923f, 1.100000f, 1.032764f, +-0.883601f, 0.900000f, 1.213643f, +-1.141923f, 0.900000f, 1.032764f, +-0.403245f, -0.900000f, 0.719644f, +-0.403245f, -1.100000f, 0.719644f, +-0.403245f, 1.100000f, 0.719644f, +-0.403245f, 0.900000f, 0.719644f, +-0.403245f, -0.900000f, 1.213643f, +-0.403245f, -1.100000f, 1.213643f, +-0.403245f, 1.100000f, 1.213643f, +-0.403245f, 0.900000f, 1.213643f, +-0.505289f, 0.900000f, 0.400000f, +-0.705289f, 0.900000f, 0.400000f, +-0.705289f, -0.899999f, 0.400000f, +-0.505289f, -0.900001f, 0.400000f, +-0.505289f, 0.899999f, 0.100000f, +-0.706968f, 0.900000f, 0.100000f, +-0.706968f, -0.900000f, 0.100000f, +-0.505289f, -0.900000f, 0.100000f, +-0.403245f, 1.000000f, 1.095567f, +-0.403245f, 1.091924f, 1.003643f, +-0.403245f, -1.000000f, 1.095567f, +-0.403245f, -0.908076f, 1.003643f, +-0.003245f, 1.070711f, 1.003643f, +-0.003245f, 1.000000f, 1.074354f, +-0.003245f, -0.929289f, 1.003643f, +-0.003245f, -1.000000f, 1.074354f, +-0.403245f, 1.000000f, 0.911719f, +-0.003245f, 1.000000f, 0.932932f, +-0.003245f, 0.929289f, 1.003643f, +-0.403245f, 0.908076f, 1.003643f, +-0.403245f, -1.000000f, 0.911719f, +-0.003245f, -1.000000f, 0.932932f, +-0.403245f, -1.091924f, 1.003643f, +-0.003245f, -1.070711f, 1.003643f, +-0.003245f, -1.000000f, 1.215775f, +-0.003245f, -0.787868f, 1.003643f, +-0.003245f, -1.000000f, 0.791511f, +-0.003245f, -1.212132f, 1.003643f, +-0.003245f, 0.787868f, 1.003643f, +-0.003245f, 1.000000f, 0.791511f, +-0.003245f, 1.212132f, 1.003643f, +-0.003245f, 1.000000f, 1.215775f, +-0.003245f, -1.212132f, 1.003643f, +-0.003245f, -1.000000f, 0.791511f, +-0.003245f, -0.787868f, 1.003643f, +-0.003245f, -1.000000f, 1.215775f, +-0.003245f, 1.000000f, 1.215775f, +-0.003245f, 1.212132f, 1.003643f, +-0.003245f, 1.000000f, 0.791511f, +-0.003245f, 0.787868f, 1.003643f, +}; + +static const unsigned short BumperMarker01Faces[] = +{ +2, 3, 4, +5, 4, 3, +5, 6, 4, +5, 7, 6, +8, 9, 10, +8, 11, 9, +12, 11, 8, +12, 13, 11, +14, 15, 16, +17, 16, 15, +17, 18, 16, +17, 19, 18, +20, 21, 22, +20, 23, 21, +24, 23, 20, +24, 25, 23, +37, 62, 26, +37, 27, 62, +36, 28, 29, +30, 36, 29, +39, 31, 32, +39, 33, 31, +38, 34, 61, +35, 38, 61, +1, 40, 42, +28, 42, 43, +28, 36, 42, +26, 41, 40, +34, 46, 47, +32, 45, 44, +32, 44, 39, +0, 44, 46, +72, 75, 74, +72, 74, 73, +76, 78, 79, +76, 79, 77, +66, 71, 79, +66, 79, 78, +70, 67, 76, +70, 76, 77, +64, 69, 74, +64, 74, 75, +68, 65, 72, +68, 72, 73, +66, 67, 70, +66, 70, 71, +64, 65, 68, +64, 68, 69, +61, 60, 62, +60, 63, 62, +59, 56, 58, +56, 57, 58, +62, 58, 61, +58, 57, 61, +60, 56, 59, +60, 59, 63, +48, 51, 55, +48, 55, 52, +37, 38, 51, +37, 51, 48, +0, 1, 52, +0, 52, 55, +0, 55, 54, +0, 54, 44, +50, 39, 54, +39, 44, 54, +0, 46, 34, +0, 34, 38, +26, 40, 1, +26, 1, 37, +36, 49, 53, +36, 53, 42, +1, 42, 53, +1, 53, 52, +0, 37, 1, +0, 38, 37, +54, 55, 51, +54, 51, 50, +52, 53, 49, +52, 49, 48, +38, 39, 50, +38, 50, 51, +36, 37, 48, +36, 48, 49, +33, 39, 38, +33, 38, 35, +27, 37, 36, +27, 36, 30, +32, 17, 45, +17, 15, 45, +17, 32, 34, +17, 34, 23, +61, 34, 32, +61, 32, 31, +61, 31, 17, +61, 17, 23, +21, 15, 14, +21, 14, 22, +15, 21, 47, +15, 47, 45, +34, 47, 23, +47, 21, 23, +19, 33, 25, +33, 35, 25, +47, 46, 44, +47, 44, 45, +18, 19, 24, +19, 25, 24, +14, 16, 20, +14, 20, 22, +26, 5, 41, +5, 3, 41, +5, 26, 28, +5, 28, 11, +29, 28, 26, +29, 26, 62, +29, 62, 5, +29, 5, 11, +2, 10, 9, +2, 9, 3, +3, 9, 43, +3, 43, 41, +28, 43, 11, +43, 9, 11, +7, 27, 13, +27, 30, 13, +43, 42, 40, +43, 40, 41, +6, 7, 12, +7, 13, 12, +2, 4, 8, +2, 8, 10, +16, 18, 24, +16, 24, 20, +8, 4, 12, +4, 6, 12, +85, 84, 87, +85, 87, 86, +82, 83, 80, +82, 80, 81, +89, 90, 91, +89, 91, 88, +94, 93, 92, +94, 92, 95, +}; + static const GLMarker BumperMarker01 = { 96, 144, BumperMarker01Verts, BumperMarker01Faces }; \ No newline at end of file diff --git a/gl/marker/furniture.h b/gl/marker/furniture.h index 7ebaf88a5..643a7fdd6 100644 --- a/gl/marker/furniture.h +++ b/gl/marker/furniture.h @@ -1,1071 +1,1071 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "../glmarker.h" - -static const float FurnitureMarker01Verts[] = -{ - 12.160423f, 3.242646f, 59.486294f, - -12.304700f, 3.242646f, 59.486294f, - 12.160423f, 3.242644f, 103.837936f, - -12.304699f, 3.242644f, 103.837936f, - 12.157164f, -5.104313f, 59.511795f, - -12.270079f, -5.104312f, 59.511795f, - 12.160423f, -5.092517f, 103.837936f, - -12.304699f, -5.092517f, 103.837936f, - 12.168214f, -3.784476f, 53.848057f, - 12.168213f, 1.593239f, 53.848057f, - 16.825449f, -1.095619f, 53.848057f, - 12.168212f, -3.784476f, 103.848053f, - 12.168214f, 1.593239f, 103.848053f, - 16.825451f, -1.095618f, 98.413109f, - -12.298068f, -3.784476f, 53.848057f, - -12.298067f, 1.593239f, 53.848057f, - -16.955305f, -1.095619f, 53.848057f, - -12.298068f, -3.784476f, 103.848053f, - -12.298067f, 1.593239f, 103.848053f, - -16.955305f, -1.095618f, 98.413109f, - -7.397070f, 3.264224f, 0.000000f, - -2.558683f, -5.116109f, -0.000001f, - -12.235458f, -5.116108f, 0.000000f, - -7.397069f, 3.264223f, 59.537296f, - -2.558684f, -5.116110f, 59.537296f, - 7.315517f, 3.264224f, -0.000001f, - 12.153903f, -5.116109f, -0.000001f, - 2.477128f, -5.116108f, -0.000001f, - 7.315516f, 3.264223f, 59.537296f, - 2.477129f, -5.116108f, 59.537296f, - 6.665659f, -5.248195f, 113.712250f, - -6.685288f, -5.248195f, 113.712250f, - 6.665661f, 5.512237f, 113.712250f, - -6.685289f, 5.512238f, 113.712250f, - 6.665660f, -5.248195f, 126.712250f, - -6.685288f, -5.248195f, 126.712250f, - 6.665660f, 5.512237f, 126.712250f, - -6.685287f, 5.512237f, 126.712250f, - -19.978813f, 56.808506f, 46.409012f, - -19.978819f, 81.273628f, 46.409016f, - 24.372831f, 56.808514f, 46.409008f, - 24.372828f, 81.273636f, 46.409012f, - -19.953314f, 56.811768f, 38.062054f, - -19.953318f, 81.239006f, 38.062057f, - 24.372831f, 56.808514f, 38.073849f, - 24.372828f, 81.273636f, 38.073853f, - -25.617052f, 56.800713f, 39.381889f, - -25.617052f, 56.800713f, 44.759605f, - -25.617052f, 52.143478f, 42.070747f, - 24.382948f, 56.800724f, 39.381889f, - 24.382948f, 56.800724f, 44.759605f, - 18.948000f, 52.143486f, 42.070747f, - -25.617056f, 81.266998f, 39.381893f, - -25.617056f, 81.266991f, 44.759609f, - -25.617056f, 85.924232f, 42.070751f, - 24.382944f, 81.267006f, 39.381893f, - 24.382944f, 81.267006f, 44.759609f, - 18.947996f, 85.924240f, 42.070751f, - -79.465111f, 76.365990f, 46.430595f, - -79.465111f, 71.527603f, 38.050262f, - -79.465111f, 81.204376f, 38.050262f, - -19.927814f, 76.365997f, 46.430592f, - -19.927814f, 71.527611f, 38.050259f, - -79.465111f, 61.653400f, 46.430592f, - -79.465111f, 56.815014f, 38.050259f, - -79.465111f, 66.491791f, 38.050262f, - -19.927813f, 61.653412f, 46.430592f, - -19.927814f, 66.491798f, 38.050259f, - 34.247139f, 62.303280f, 37.918171f, - 34.247139f, 75.654228f, 37.918171f, - 34.247139f, 62.303276f, 48.678604f, - 34.247139f, 75.654228f, 48.678604f, - 47.247139f, 62.303284f, 37.918171f, - 47.247139f, 75.654228f, 37.918171f, - 47.247139f, 62.303280f, 48.678600f, - 47.247139f, 75.654228f, 48.678604f, - -100.184074f, 32.677177f, 38.047600f, - 66.438126f, 32.677177f, 38.047600f, - -100.184074f, 103.524979f, 38.047600f, - 66.438126f, 103.524979f, 38.047600f, - -100.184074f, 32.677177f, 0.000000f, - -100.184074f, 32.677177f, 38.047600f, - -100.184074f, 103.524979f, 0.000000f, - -100.184074f, 103.524979f, 38.047600f, - 66.438126f, 32.677177f, 0.000000f, - 66.438126f, 32.677177f, 38.047600f, - -100.184074f, 32.677177f, 0.000000f, - -100.184074f, 32.677177f, 38.047600f, - 66.438126f, 103.524979f, 0.000000f, - 66.438126f, 103.524979f, 38.047600f, - 66.438126f, 32.677177f, 0.000000f, - 66.438126f, 32.677177f, 38.047600f, - -100.184074f, 103.524979f, 0.000000f, - -100.184074f, 103.524979f, 38.047600f, - 66.438126f, 103.524979f, 0.000000f, - 66.438126f, 103.524979f, 38.047600f -}; - -static const unsigned short FurnitureMarker01Faces[] = -{ - 3, 2, 0, - 0, 1, 3, - 7, 5, 4, - 4, 6, 7, - 13, 10, 9, - 12, 13, 9, - 11, 8, 10, - 13, 11, 10, - 16, 19, 15, - 19, 18, 15, - 14, 17, 16, - 17, 19, 16, - 24, 21, 20, - 23, 24, 20, - 23, 20, 22, - 5, 23, 22, - 4, 26, 25, - 28, 4, 25, - 28, 25, 27, - 29, 28, 27, - 35, 31, 30, - 30, 34, 35, - 37, 33, 31, - 31, 35, 37, - 36, 32, 33, - 33, 37, 36, - 34, 30, 32, - 32, 36, 34, - 38, 40, 41, - 41, 39, 38, - 42, 43, 45, - 45, 44, 42, - 47, 48, 51, - 47, 51, 50, - 48, 46, 49, - 48, 49, 51, - 53, 57, 54, - 53, 56, 57, - 54, 55, 52, - 54, 57, 55, - 58, 59, 62, - 58, 62, 61, - 60, 58, 61, - 60, 61, 43, - 63, 64, 42, - 63, 42, 66, - 65, 63, 66, - 65, 66, 67, - 68, 69, 73, - 73, 72, 68, - 69, 71, 75, - 75, 73, 69, - 71, 70, 74, - 74, 75, 71, - 70, 68, 72, - 72, 74, 70, - 76, 77, 78, - 77, 79, 78, - 80, 81, 82, - 81, 83, 82, - 84, 85, 86, - 85, 87, 86, - 88, 89, 90, - 89, 91, 90, - 92, 93, 94, - 93, 95, 94 -}; - -static const GLMarker FurnitureMarker01 = { 96, 66, FurnitureMarker01Verts, FurnitureMarker01Faces }; - -static const float FurnitureMarker03Verts[] = -{ - 12.160423f, 3.242646f, 59.486294f, - -12.304700f, 3.242646f, 59.486294f, - 12.160423f, 3.242644f, 103.837936f, - -12.304699f, 3.242644f, 103.837936f, - 12.157164f, -5.104313f, 59.511795f, - -12.270079f, -5.104312f, 59.511795f, - 12.160423f, -5.092517f, 103.837936f, - -12.304699f, -5.092517f, 103.837936f, - 12.168214f, -3.784476f, 53.848057f, - 12.168213f, 1.593239f, 53.848057f, - 16.825449f, -1.095619f, 53.848057f, - 12.168212f, -3.784476f, 103.848053f, - 12.168214f, 1.593239f, 103.848053f, - 16.825451f, -1.095618f, 98.413109f, - -12.298068f, -3.784476f, 53.848057f, - -12.298067f, 1.593239f, 53.848057f, - -16.955305f, -1.095619f, 53.848057f, - -12.298068f, -3.784476f, 103.848053f, - -12.298067f, 1.593239f, 103.848053f, - -16.955305f, -1.095618f, 98.413109f, - -7.397070f, 3.264224f, 0.000000f, - -2.558683f, -5.116109f, -0.000001f, - -12.235458f, -5.116108f, 0.000000f, - -7.397069f, 3.264223f, 59.537296f, - -2.558684f, -5.116110f, 59.537296f, - 7.315517f, 3.264224f, -0.000001f, - 12.153903f, -5.116109f, -0.000001f, - 2.477128f, -5.116108f, -0.000001f, - 7.315516f, 3.264223f, 59.537296f, - 2.477129f, -5.116108f, 59.537296f, - 6.665659f, -5.248195f, 113.712250f, - -6.685288f, -5.248195f, 113.712250f, - 6.665661f, 5.512237f, 113.712250f, - -6.685289f, 5.512238f, 113.712250f, - 6.665660f, -5.248195f, 126.712250f, - -6.685288f, -5.248195f, 126.712250f, - 6.665660f, 5.512237f, 126.712250f, - -6.685287f, 5.512237f, 126.712250f, - -9.398697f, 60.559513f, 11.966876f, - -9.398703f, 85.024635f, 11.966880f, - 34.952950f, 60.559521f, 11.966873f, - 34.952946f, 85.024643f, 11.966877f, - -9.373198f, 60.562775f, 3.619917f, - -9.373201f, 84.990013f, 3.619922f, - 34.952946f, 60.559521f, 3.631711f, - 34.952942f, 85.024643f, 3.631715f, - -15.036936f, 60.551720f, 4.939754f, - -15.036936f, 60.551720f, 10.317469f, - -15.036935f, 55.894485f, 7.628611f, - 34.963062f, 60.551731f, 4.939752f, - 34.963066f, 60.551731f, 10.317468f, - 29.528116f, 55.894493f, 7.628610f, - -15.036940f, 85.018005f, 4.939758f, - -15.036940f, 85.017998f, 10.317472f, - -15.036941f, 89.675240f, 7.628616f, - 34.963058f, 85.018013f, 4.939756f, - 34.963062f, 85.018013f, 10.317472f, - 29.528112f, 89.675247f, 7.628615f, - -68.884995f, 80.116997f, 11.988459f, - -68.884995f, 75.278610f, 3.608125f, - -68.884995f, 84.955383f, 3.608128f, - -9.347699f, 80.117004f, 11.988457f, - -9.347699f, 75.278618f, 3.608123f, - -68.884995f, 65.404404f, 11.988456f, - -68.884995f, 60.566021f, 3.608122f, - -68.884995f, 70.242798f, 3.608126f, - -9.347696f, 65.404419f, 11.988454f, - -9.347698f, 70.242805f, 3.608123f, - 44.827255f, 66.054291f, 3.476035f, - 44.827255f, 79.405235f, 3.476037f, - 44.827255f, 66.054283f, 14.236466f, - 44.827255f, 79.405235f, 14.236468f, - 57.827255f, 66.054291f, 3.476034f, - 57.827255f, 79.405235f, 3.476036f, - 57.827255f, 66.054291f, 14.236465f, - 57.827255f, 79.405235f, 14.236467f -}; - -static const unsigned short FurnitureMarker03Faces[] = -{ - 3, 2, 0, - 0, 1, 3, - 7, 5, 4, - 4, 6, 7, - 13, 10, 9, - 12, 13, 9, - 11, 8, 10, - 13, 11, 10, - 16, 19, 15, - 19, 18, 15, - 14, 17, 16, - 17, 19, 16, - 24, 21, 20, - 23, 24, 20, - 23, 20, 22, - 5, 23, 22, - 4, 26, 25, - 28, 4, 25, - 28, 25, 27, - 29, 28, 27, - 35, 31, 30, - 30, 34, 35, - 37, 33, 31, - 31, 35, 37, - 36, 32, 33, - 33, 37, 36, - 34, 30, 32, - 32, 36, 34, - 38, 40, 41, - 41, 39, 38, - 42, 43, 45, - 45, 44, 42, - 47, 48, 51, - 47, 51, 50, - 48, 46, 49, - 48, 49, 51, - 53, 57, 54, - 53, 56, 57, - 54, 55, 52, - 54, 57, 55, - 58, 59, 62, - 58, 62, 61, - 60, 58, 61, - 60, 61, 43, - 63, 64, 42, - 63, 42, 66, - 65, 63, 66, - 65, 66, 67, - 68, 69, 73, - 73, 72, 68, - 69, 71, 75, - 75, 73, 69, - 71, 70, 74, - 74, 75, 71, - 70, 68, 72, - 72, 74, 70 -}; - -static const GLMarker FurnitureMarker03 = { 76, 56, FurnitureMarker03Verts, FurnitureMarker03Faces }; - -static const float FurnitureMarker04Verts[] = -{ - 12.160423f, 3.242646f, 59.486294f, - -12.304700f, 3.242646f, 59.486294f, - 12.160423f, 3.242644f, 103.837936f, - -12.304699f, 3.242644f, 103.837936f, - 12.157164f, -5.104313f, 59.511795f, - -12.270079f, -5.104312f, 59.511795f, - 12.160423f, -5.092517f, 103.837936f, - -12.304699f, -5.092517f, 103.837936f, - 12.168214f, -3.784476f, 53.848057f, - 12.168213f, 1.593239f, 53.848057f, - 16.825449f, -1.095619f, 53.848057f, - 12.168212f, -3.784476f, 103.848053f, - 12.168214f, 1.593239f, 103.848053f, - 16.825451f, -1.095618f, 98.413109f, - -12.298068f, -3.784476f, 53.848057f, - -12.298067f, 1.593239f, 53.848057f, - -16.955305f, -1.095619f, 53.848057f, - -12.298068f, -3.784476f, 103.848053f, - -12.298067f, 1.593239f, 103.848053f, - -16.955305f, -1.095618f, 98.413109f, - -7.397070f, 3.264224f, 0.000000f, - -2.558683f, -5.116109f, -0.000001f, - -12.235458f, -5.116108f, 0.000000f, - -7.397069f, 3.264223f, 59.537296f, - -2.558684f, -5.116110f, 59.537296f, - 7.315517f, 3.264224f, -0.000001f, - 12.153903f, -5.116109f, -0.000001f, - 2.477128f, -5.116108f, -0.000001f, - 7.315516f, 3.264223f, 59.537296f, - 2.477129f, -5.116108f, 59.537296f, - 6.665659f, -5.248195f, 113.712250f, - -6.685288f, -5.248195f, 113.712250f, - 6.665661f, 5.512237f, 113.712250f, - -6.685289f, 5.512238f, 113.712250f, - 6.665660f, -5.248195f, 126.712250f, - -6.685288f, -5.248195f, 126.712250f, - 6.665660f, 5.512237f, 126.712250f, - -6.685287f, 5.512237f, 126.712250f, - -12.253868f, 116.276588f, 15.000266f, - 12.211255f, 116.276588f, 15.000268f, - -12.253861f, 71.924942f, 15.000266f, - 12.211262f, 71.924942f, 15.000268f, - -12.250607f, 116.251091f, 6.653307f, - 12.176635f, 116.251091f, 6.653310f, - -12.253860f, 71.924942f, 6.665105f, - 12.211262f, 71.924942f, 6.665107f, - -12.261660f, 121.914825f, 7.973144f, - -12.261659f, 121.914825f, 13.350859f, - -16.918896f, 121.914825f, 10.662001f, - -12.261649f, 71.914825f, 7.973146f, - -12.261651f, 71.914825f, 13.350862f, - -16.918890f, 77.349770f, 10.662003f, - 12.204622f, 121.914825f, 7.973146f, - 12.204621f, 121.914825f, 13.350861f, - 16.861860f, 121.914825f, 10.662004f, - 12.204631f, 71.914825f, 7.973148f, - 12.204630f, 71.914825f, 13.350863f, - 16.861868f, 77.349777f, 10.662006f, - 7.303616f, 175.762878f, 15.021843f, - 2.465230f, 175.762878f, 6.641510f, - 12.142004f, 175.762878f, 6.641512f, - 7.303625f, 116.225586f, 15.021845f, - 2.465240f, 116.225586f, 6.641511f, - -7.408972f, 175.762878f, 15.021841f, - -12.247356f, 175.762878f, 6.641508f, - -2.570582f, 175.762878f, 6.641510f, - -7.408961f, 116.225586f, 15.021844f, - -2.570573f, 116.225586f, 6.641512f, - -6.759095f, 62.050632f, 6.509429f, - 6.591853f, 62.050636f, 6.509429f, - -6.759098f, 62.050632f, 17.269859f, - 6.591853f, 62.050636f, 17.269861f, - -6.759093f, 49.050632f, 6.509429f, - 6.591856f, 49.050636f, 6.509430f, - -6.759094f, 49.050632f, 17.269861f, - 6.591853f, 49.050636f, 17.269861f -}; - -static const unsigned short FurnitureMarker04Faces[] = -{ - 3, 2, 0, - 0, 1, 3, - 7, 5, 4, - 4, 6, 7, - 13, 10, 9, - 12, 13, 9, - 11, 8, 10, - 13, 11, 10, - 16, 19, 15, - 19, 18, 15, - 14, 17, 16, - 17, 19, 16, - 24, 21, 20, - 23, 24, 20, - 23, 20, 22, - 5, 23, 22, - 4, 26, 25, - 28, 4, 25, - 28, 25, 27, - 29, 28, 27, - 35, 31, 30, - 30, 34, 35, - 37, 33, 31, - 31, 35, 37, - 36, 32, 33, - 33, 37, 36, - 34, 30, 32, - 32, 36, 34, - 38, 40, 41, - 41, 39, 38, - 42, 43, 45, - 45, 44, 42, - 47, 48, 51, - 47, 51, 50, - 48, 46, 49, - 48, 49, 51, - 53, 57, 54, - 53, 56, 57, - 54, 55, 52, - 54, 57, 55, - 58, 59, 62, - 58, 62, 61, - 60, 58, 61, - 60, 61, 43, - 63, 64, 42, - 63, 42, 66, - 65, 63, 66, - 65, 66, 67, - 68, 69, 73, - 73, 72, 68, - 69, 71, 75, - 75, 73, 69, - 71, 70, 74, - 74, 75, 71, - 70, 68, 72, - 72, 74, 70 -}; - -static const GLMarker FurnitureMarker04 = { 76, 56, FurnitureMarker04Verts, FurnitureMarker04Faces }; - -static const float FurnitureMarker11Verts[] = -{ - 12.160423f, 3.242646f, 59.486294f, - -12.304700f, 3.242645f, 59.486294f, - 12.160423f, 3.242644f, 103.837936f, - -12.304699f, 3.242643f, 103.837936f, - 12.157164f, -5.104314f, 59.511795f, - -12.270079f, -5.104313f, 59.511795f, - 12.160423f, -5.092518f, 103.837936f, - -12.304699f, -5.092518f, 103.837936f, - 12.168214f, -3.784477f, 53.848057f, - 12.168213f, 1.593238f, 53.848057f, - 16.825449f, -1.095620f, 53.848057f, - 12.168212f, -3.784477f, 103.848053f, - 12.168214f, 1.593238f, 103.848053f, - 16.825451f, -1.095619f, 98.413109f, - -12.298068f, -3.784477f, 53.848057f, - -12.298067f, 1.593238f, 53.848057f, - -16.955305f, -1.095619f, 53.848057f, - -12.298068f, -3.784477f, 103.848053f, - -12.298067f, 1.593238f, 103.848053f, - -16.955305f, -1.095619f, 98.413109f, - -7.397070f, 3.264223f, 0.000000f, - -2.558683f, -5.116110f, -0.000001f, - -12.235458f, -5.116108f, 0.000000f, - -7.397069f, 3.264223f, 59.537296f, - -2.558684f, -5.116111f, 59.537296f, - 7.315517f, 3.264223f, -0.000001f, - 12.153903f, -5.116110f, -0.000001f, - 2.477128f, -5.116108f, -0.000001f, - 7.315516f, 3.264223f, 59.537296f, - 2.477129f, -5.116109f, 59.537296f, - 6.665659f, -5.248195f, 113.712250f, - -6.685288f, -5.248196f, 113.712250f, - 6.665661f, 5.512237f, 113.712250f, - -6.685289f, 5.512237f, 113.712250f, - 6.665660f, -5.248196f, 126.712250f, - -6.685288f, -5.248196f, 126.712250f, - 6.665660f, 5.512236f, 126.712250f, - -6.685287f, 5.512236f, 126.712250f, - -5.279642f, 63.934200f, 32.117043f, - -5.279637f, 39.469078f, 32.117043f, - -5.279640f, 63.934200f, 89.762779f, - -5.279635f, 39.469078f, 89.762779f, - 3.170247f, 63.930939f, 32.127567f, - 3.169106f, 39.503700f, 32.130669f, - 3.055522f, 63.934200f, 89.762779f, - 3.055527f, 39.469078f, 89.762779f, - 1.747481f, 63.941990f, 39.772892f, - -3.630234f, 63.941990f, 39.772892f, - -0.941377f, 68.599228f, 39.772896f, - 1.747481f, 63.941990f, 89.772896f, - -3.630234f, 63.941990f, 89.772896f, - -0.941378f, 68.599228f, 84.337944f, - 1.747485f, 39.475712f, 39.772892f, - -3.630230f, 39.475708f, 39.772892f, - -0.941372f, 34.818470f, 39.772892f, - 1.747485f, 39.475712f, 89.772896f, - -3.630230f, 39.475708f, 89.772896f, - -0.941372f, 34.818470f, 84.337944f, - -34.635773f, 44.376709f, 40.468124f, - -26.299620f, 49.215092f, 32.118874f, - -26.299623f, 39.538319f, 32.118874f, - -5.277596f, 44.376709f, 40.499199f, - -5.277597f, 49.215096f, 32.118874f, - -34.635777f, 59.089291f, 40.468128f, - -26.298481f, 63.927677f, 32.115772f, - -26.298479f, 54.250904f, 32.115768f, - -5.276455f, 59.089291f, 40.496094f, - -5.276457f, 54.250904f, 32.115765f, - 3.211200f, 58.439438f, 101.666718f, - 3.211203f, 45.088490f, 101.666718f, - -7.549232f, 58.439438f, 101.666718f, - -7.549230f, 45.088486f, 101.666718f, - 3.211200f, 58.439438f, 114.666718f, - 3.211203f, 45.088490f, 114.666718f, - -7.549231f, 58.439434f, 114.666718f, - -7.549229f, 45.088490f, 114.666718f, - -39.159565f, 44.376709f, -0.158797f, - -30.779234f, 49.215096f, -0.158798f, - -30.779236f, 39.538319f, -0.158805f, - -39.159565f, 59.089291f, -0.158798f, - -30.779228f, 63.927677f, -0.158798f, - -30.779228f, 54.250904f, -0.158798f, - -5.301957f, 63.930939f, 32.127563f, - -5.303098f, 39.503700f, 32.130665f, - 15.970100f, 67.928596f, 0.000000f, - 15.970100f, 34.994801f, 0.000000f, - -15.804100f, 67.928596f, 0.000000f, - -15.804100f, 34.994801f, 0.000000f, - -15.804100f, 34.994801f, 32.000000f, - 15.970100f, 34.994801f, 32.000000f, - -15.804100f, 67.928596f, 32.000000f, - 15.970100f, 67.928596f, 32.000000f, - -15.804100f, 34.994801f, 0.000000f, - -15.804100f, 34.994801f, 32.000000f, - -15.804100f, 67.928596f, 0.000000f, - -15.804100f, 67.928596f, 32.000000f, - 15.970100f, 34.994801f, 0.000000f, - 15.970100f, 34.994801f, 32.000000f, - -15.804100f, 34.994801f, 0.000000f, - -15.804100f, 34.994801f, 32.000000f, - 15.970100f, 67.928596f, 0.000000f, - 15.970100f, 67.928596f, 32.000000f, - 15.970100f, 34.994801f, 0.000000f, - 15.970100f, 34.994801f, 32.000000f, - -15.804100f, 67.928596f, 0.000000f, - -15.804100f, 67.928596f, 32.000000f, - 15.970100f, 67.928596f, 0.000000f, - 15.970100f, 67.928596f, 32.000000f, -}; - -static const unsigned short FurnitureMarker11Faces[] = -{ - 3, 2, 0, - 0, 1, 3, - 7, 5, 4, - 4, 6, 7, - 13, 10, 9, - 12, 13, 9, - 11, 8, 10, - 13, 11, 10, - 16, 19, 15, - 19, 18, 15, - 14, 17, 16, - 17, 19, 16, - 24, 21, 20, - 23, 24, 20, - 23, 20, 22, - 5, 23, 22, - 4, 26, 25, - 28, 4, 25, - 28, 25, 27, - 29, 28, 27, - 35, 31, 30, - 30, 34, 35, - 37, 33, 31, - 31, 35, 37, - 36, 32, 33, - 33, 37, 36, - 34, 30, 32, - 32, 36, 34, - 41, 40, 38, - 38, 39, 41, - 45, 43, 42, - 42, 44, 45, - 51, 48, 47, - 50, 51, 47, - 49, 46, 48, - 51, 49, 48, - 54, 57, 53, - 57, 56, 53, - 52, 55, 54, - 55, 57, 54, - 62, 59, 58, - 61, 62, 58, - 61, 58, 60, - 83, 61, 60, - 82, 64, 63, - 66, 82, 63, - 66, 63, 65, - 67, 66, 65, - 73, 69, 68, - 68, 72, 73, - 75, 71, 69, - 69, 73, 75, - 74, 70, 71, - 71, 75, 74, - 72, 68, 70, - 70, 74, 72, - 59, 77, 76, - 58, 59, 76, - 58, 76, 78, - 60, 58, 78, - 64, 80, 79, - 63, 64, 79, - 63, 79, 81, - 65, 63, 81, - 84, 85, 86, - 85, 87, 86, - 88, 89, 90, - 89, 91, 90, - 92, 93, 94, - 93, 95, 94, - 96, 97, 98, - 97, 99, 98, - 100, 101, 102, - 101, 103, 102, - 104, 105, 106, - 105, 107, 106 -}; - -static const GLMarker FurnitureMarker11 = { 108, 76, FurnitureMarker11Verts, FurnitureMarker11Faces }; - -static const float FurnitureMarker13Verts[] = -{ - 12.160423f, 3.242646f, 59.486294f, - -12.304700f, 3.242645f, 59.486294f, - 12.160423f, 3.242644f, 103.837936f, - -12.304699f, 3.242643f, 103.837936f, - 12.157164f, -5.104314f, 59.511795f, - -12.270079f, -5.104313f, 59.511795f, - 12.160423f, -5.092518f, 103.837936f, - -12.304699f, -5.092518f, 103.837936f, - 12.168214f, -3.784477f, 53.848057f, - 12.168213f, 1.593238f, 53.848057f, - 16.825449f, -1.095620f, 53.848057f, - 12.168212f, -3.784477f, 103.848053f, - 12.168214f, 1.593238f, 103.848053f, - 16.825451f, -1.095619f, 98.413109f, - -12.298068f, -3.784477f, 53.848057f, - -12.298067f, 1.593238f, 53.848057f, - -16.955305f, -1.095619f, 53.848057f, - -12.298068f, -3.784477f, 103.848053f, - -12.298067f, 1.593238f, 103.848053f, - -16.955305f, -1.095619f, 98.413109f, - -7.397070f, 3.264223f, 0.000000f, - -2.558683f, -5.116110f, -0.000001f, - -12.235458f, -5.116108f, 0.000000f, - -7.397069f, 3.264223f, 59.537296f, - -2.558684f, -5.116111f, 59.537296f, - 7.315517f, 3.264223f, -0.000001f, - 12.153903f, -5.116110f, -0.000001f, - 2.477128f, -5.116108f, -0.000001f, - 7.315516f, 3.264223f, 59.537296f, - 2.477129f, -5.116109f, 59.537296f, - 6.665659f, -5.248195f, 113.712250f, - -6.685288f, -5.248196f, 113.712250f, - 6.665661f, 5.512237f, 113.712250f, - -6.685289f, 5.512237f, 113.712250f, - 6.665660f, -5.248196f, 126.712250f, - -6.685288f, -5.248196f, 126.712250f, - 6.665660f, 5.512236f, 126.712250f, - -6.685287f, 5.512236f, 126.712250f, - 12.537734f, 60.403820f, 32.117043f, - -11.927388f, 60.403816f, 32.117043f, - 12.537737f, 60.403820f, 89.762779f, - -11.927387f, 60.403816f, 89.762779f, - 12.534475f, 51.953934f, 32.127567f, - -11.892765f, 51.955074f, 32.130669f, - 12.537735f, 52.068657f, 89.762779f, - -11.927386f, 52.068653f, 89.762779f, - 12.545526f, 53.376698f, 39.772892f, - 12.545527f, 58.754414f, 39.772892f, - 17.202761f, 56.065556f, 39.772896f, - 12.545525f, 53.376698f, 89.772896f, - 12.545525f, 58.754414f, 89.772896f, - 17.202763f, 56.065556f, 84.337944f, - -11.920755f, 53.376694f, 39.772892f, - -11.920755f, 58.754410f, 39.772892f, - -16.577993f, 56.065552f, 39.772892f, - -11.920755f, 53.376694f, 89.772896f, - -11.920755f, 58.754410f, 89.772896f, - -16.577993f, 56.065552f, 84.337944f, - -7.019755f, 89.759956f, 40.468124f, - -2.181371f, 81.423798f, 32.118874f, - -11.858147f, 81.423805f, 32.118874f, - -7.019757f, 60.401775f, 40.499199f, - -2.181369f, 60.401775f, 32.118874f, - 7.692828f, 89.759956f, 40.468128f, - 12.531213f, 81.422661f, 32.115772f, - 2.854439f, 81.422661f, 32.115768f, - 7.692825f, 60.400635f, 40.496094f, - 2.854440f, 60.400639f, 32.115765f, - 7.042971f, 51.912979f, 101.666718f, - -6.307976f, 51.912979f, 101.666718f, - 7.042972f, 62.673412f, 101.666718f, - -6.307978f, 62.673409f, 101.666718f, - 7.042972f, 51.912979f, 114.666718f, - -6.307976f, 51.912979f, 114.666718f, - 7.042971f, 62.673412f, 114.666718f, - -6.307977f, 62.673409f, 114.666718f, - -7.019756f, 94.283745f, -0.158797f, - -2.181369f, 85.903412f, -0.158798f, - -11.858145f, 85.903419f, -0.158805f, - 7.692829f, 94.283745f, -0.158798f, - 12.531214f, 85.903412f, -0.158798f, - 2.854441f, 85.903404f, -0.158798f, - 12.534474f, 60.426136f, 32.127563f, - -11.892765f, 60.427280f, 32.130665f, - 16.532131f, 39.154079f, -0.000006f, - -16.401665f, 39.154083f, -0.000003f, - 16.532131f, 70.928284f, -0.000006f, - -16.401665f, 70.928284f, -0.000003f, - -16.401661f, 70.928284f, 31.999996f, - -16.401663f, 39.154083f, 31.999996f, - 16.532135f, 70.928284f, 31.999994f, - 16.532133f, 39.154079f, 31.999994f, - -16.401665f, 70.928284f, -0.000003f, - -16.401661f, 70.928284f, 31.999996f, - 16.532131f, 70.928284f, -0.000006f, - 16.532135f, 70.928284f, 31.999994f, - -16.401665f, 39.154083f, -0.000003f, - -16.401663f, 39.154083f, 31.999996f, - -16.401665f, 70.928284f, -0.000003f, - -16.401661f, 70.928284f, 31.999996f, - 16.532131f, 39.154079f, -0.000006f, - 16.532133f, 39.154079f, 31.999994f, - -16.401665f, 39.154083f, -0.000003f, - -16.401663f, 39.154083f, 31.999996f, - 16.532131f, 70.928284f, -0.000006f, - 16.532135f, 70.928284f, 31.999994f, - 16.532131f, 39.154079f, -0.000006f, - 16.532133f, 39.154079f, 31.999994f, -}; - -static const unsigned short FurnitureMarker13Faces[] = -{ - 3, 2, 0, - 0, 1, 3, - 7, 5, 4, - 4, 6, 7, - 13, 10, 9, - 12, 13, 9, - 11, 8, 10, - 13, 11, 10, - 16, 19, 15, - 19, 18, 15, - 14, 17, 16, - 17, 19, 16, - 24, 21, 20, - 23, 24, 20, - 23, 20, 22, - 5, 23, 22, - 4, 26, 25, - 28, 4, 25, - 28, 25, 27, - 29, 28, 27, - 35, 31, 30, - 30, 34, 35, - 37, 33, 31, - 31, 35, 37, - 36, 32, 33, - 33, 37, 36, - 34, 30, 32, - 32, 36, 34, - 41, 40, 38, - 38, 39, 41, - 45, 43, 42, - 42, 44, 45, - 51, 48, 47, - 50, 51, 47, - 49, 46, 48, - 51, 49, 48, - 54, 57, 53, - 57, 56, 53, - 52, 55, 54, - 55, 57, 54, - 62, 59, 58, - 61, 62, 58, - 61, 58, 60, - 83, 61, 60, - 82, 64, 63, - 66, 82, 63, - 66, 63, 65, - 67, 66, 65, - 73, 69, 68, - 68, 72, 73, - 75, 71, 69, - 69, 73, 75, - 74, 70, 71, - 71, 75, 74, - 72, 68, 70, - 70, 74, 72, - 59, 77, 76, - 58, 59, 76, - 58, 76, 78, - 60, 58, 78, - 64, 80, 79, - 63, 64, 79, - 63, 79, 81, - 65, 63, 81, - 84, 85, 86, - 85, 87, 86, - 88, 89, 90, - 89, 91, 90, - 92, 93, 94, - 93, 95, 94, - 96, 97, 98, - 97, 99, 98, - 100, 101, 102, - 101, 103, 102, - 104, 105, 106, - 105, 107, 106 -}; - -static const GLMarker FurnitureMarker13 = { 108, 76, FurnitureMarker13Verts, FurnitureMarker13Faces }; - -static const float FurnitureMarker14Verts[] = -{ - 12.160423f, 3.242646f, 59.486294f, - -12.304700f, 3.242645f, 59.486294f, - 12.160423f, 3.242644f, 103.837936f, - -12.304699f, 3.242643f, 103.837936f, - 12.157164f, -5.104314f, 59.511795f, - -12.270079f, -5.104313f, 59.511795f, - 12.160423f, -5.092518f, 103.837936f, - -12.304699f, -5.092518f, 103.837936f, - 12.168214f, -3.784477f, 53.848057f, - 12.168213f, 1.593238f, 53.848057f, - 16.825449f, -1.095620f, 53.848057f, - 12.168212f, -3.784477f, 103.848053f, - 12.168214f, 1.593238f, 103.848053f, - 16.825451f, -1.095619f, 98.413109f, - -12.298068f, -3.784477f, 53.848057f, - -12.298067f, 1.593238f, 53.848057f, - -16.955305f, -1.095619f, 53.848057f, - -12.298068f, -3.784477f, 103.848053f, - -12.298067f, 1.593238f, 103.848053f, - -16.955305f, -1.095619f, 98.413109f, - -7.397070f, 3.264223f, 0.000000f, - -2.558683f, -5.116110f, -0.000001f, - -12.235458f, -5.116108f, 0.000000f, - -7.397069f, 3.264223f, 59.537296f, - -2.558684f, -5.116111f, 59.537296f, - 7.315517f, 3.264223f, -0.000001f, - 12.153903f, -5.116110f, -0.000001f, - 2.477128f, -5.116108f, -0.000001f, - 7.315516f, 3.264223f, 59.537296f, - 2.477129f, -5.116109f, 59.537296f, - 6.665659f, -5.248195f, 113.712250f, - -6.685288f, -5.248196f, 113.712250f, - 6.665661f, 5.512237f, 113.712250f, - -6.685289f, 5.512237f, 113.712250f, - 6.665660f, -5.248196f, 126.712250f, - -6.685288f, -5.248196f, 126.712250f, - 6.665660f, 5.512236f, 126.712250f, - -6.685287f, 5.512236f, 126.712250f, - -11.912970f, 51.759579f, 32.117043f, - 12.552153f, 51.759583f, 32.117043f, - -11.912972f, 51.759579f, 89.762779f, - 12.552152f, 51.759586f, 89.762779f, - -11.909712f, 60.209465f, 32.127567f, - 12.517528f, 60.208328f, 32.130669f, - -11.912972f, 60.094742f, 89.762779f, - 12.552150f, 60.094749f, 89.762779f, - -11.920763f, 58.786701f, 39.772892f, - -11.920762f, 53.408985f, 39.772892f, - -16.577997f, 56.097843f, 39.772896f, - -11.920761f, 58.786701f, 89.772896f, - -11.920761f, 53.408985f, 89.772896f, - -16.577999f, 56.097839f, 84.337944f, - 12.545519f, 58.786705f, 39.772892f, - 12.545520f, 53.408993f, 39.772892f, - 17.202757f, 56.097851f, 39.772892f, - 12.545518f, 58.786705f, 89.772896f, - 12.545520f, 53.408993f, 89.772896f, - 17.202757f, 56.097851f, 84.337944f, - 7.644523f, 22.403448f, 40.468124f, - 2.806138f, 30.739601f, 32.118874f, - 12.482914f, 30.739597f, 32.118874f, - 7.644521f, 51.761623f, 40.499199f, - 2.806133f, 51.761623f, 32.118874f, - -7.068059f, 22.403442f, 40.468128f, - -11.906446f, 30.740736f, 32.115772f, - -2.229672f, 30.740742f, 32.115768f, - -7.068060f, 51.762764f, 40.496094f, - -2.229676f, 51.762764f, 32.115765f, - -6.418209f, 60.250420f, 101.666718f, - 6.932739f, 60.250423f, 101.666718f, - -6.418208f, 49.489986f, 101.666718f, - 6.932743f, 49.489990f, 101.666718f, - -6.418209f, 60.250420f, 114.666718f, - 6.932739f, 60.250423f, 114.666718f, - -6.418206f, 49.489986f, 114.666718f, - 6.932741f, 49.489990f, 114.666718f, - 7.644525f, 17.879654f, -0.158797f, - 2.806137f, 26.259987f, -0.158798f, - 12.482912f, 26.259985f, -0.158805f, - -7.068059f, 17.879656f, -0.158798f, - -11.906446f, 26.259991f, -0.158798f, - -2.229673f, 26.259993f, -0.158798f, - -11.909710f, 51.737263f, 32.127563f, - 12.517529f, 51.736122f, 32.130665f, - 16.532131f, 39.154079f, -0.000006f, - -16.401665f, 39.154083f, -0.000003f, - 16.532131f, 70.928284f, -0.000006f, - -16.401665f, 70.928284f, -0.000003f, - -16.401661f, 70.928284f, 31.999996f, - -16.401663f, 39.154083f, 31.999996f, - 16.532135f, 70.928284f, 31.999994f, - 16.532133f, 39.154079f, 31.999994f, - -16.401665f, 70.928284f, -0.000003f, - -16.401661f, 70.928284f, 31.999996f, - 16.532131f, 70.928284f, -0.000006f, - 16.532135f, 70.928284f, 31.999994f, - -16.401665f, 39.154083f, -0.000003f, - -16.401663f, 39.154083f, 31.999996f, - -16.401665f, 70.928284f, -0.000003f, - -16.401661f, 70.928284f, 31.999996f, - 16.532131f, 39.154079f, -0.000006f, - 16.532133f, 39.154079f, 31.999994f, - -16.401665f, 39.154083f, -0.000003f, - -16.401663f, 39.154083f, 31.999996f, - 16.532131f, 70.928284f, -0.000006f, - 16.532135f, 70.928284f, 31.999994f, - 16.532131f, 39.154079f, -0.000006f, - 16.532133f, 39.154079f, 31.999994f, -}; - -static const unsigned short FurnitureMarker14Faces[] = -{ - 3, 2, 0, - 0, 1, 3, - 7, 5, 4, - 4, 6, 7, - 13, 10, 9, - 12, 13, 9, - 11, 8, 10, - 13, 11, 10, - 16, 19, 15, - 19, 18, 15, - 14, 17, 16, - 17, 19, 16, - 24, 21, 20, - 23, 24, 20, - 23, 20, 22, - 5, 23, 22, - 4, 26, 25, - 28, 4, 25, - 28, 25, 27, - 29, 28, 27, - 35, 31, 30, - 30, 34, 35, - 37, 33, 31, - 31, 35, 37, - 36, 32, 33, - 33, 37, 36, - 34, 30, 32, - 32, 36, 34, - 41, 40, 38, - 38, 39, 41, - 45, 43, 42, - 42, 44, 45, - 51, 48, 47, - 50, 51, 47, - 49, 46, 48, - 51, 49, 48, - 54, 57, 53, - 57, 56, 53, - 52, 55, 54, - 55, 57, 54, - 62, 59, 58, - 61, 62, 58, - 61, 58, 60, - 83, 61, 60, - 82, 64, 63, - 66, 82, 63, - 66, 63, 65, - 67, 66, 65, - 73, 69, 68, - 68, 72, 73, - 75, 71, 69, - 69, 73, 75, - 74, 70, 71, - 71, 75, 74, - 72, 68, 70, - 70, 74, 72, - 59, 77, 76, - 58, 59, 76, - 58, 76, 78, - 60, 58, 78, - 64, 80, 79, - 63, 64, 79, - 63, 79, 81, - 65, 63, 81, - 84, 85, 86, - 85, 87, 86, - 88, 89, 90, - 89, 91, 90, - 92, 93, 94, - 93, 95, 94, - 96, 97, 98, - 97, 99, 98, - 100, 101, 102, - 101, 103, 102, - 104, 105, 106, - 105, 107, 106 -}; - -static const GLMarker FurnitureMarker14 = { 108, 76, FurnitureMarker14Verts, FurnitureMarker14Faces }; +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "../glmarker.h" + +static const float FurnitureMarker01Verts[] = +{ + 12.160423f, 3.242646f, 59.486294f, + -12.304700f, 3.242646f, 59.486294f, + 12.160423f, 3.242644f, 103.837936f, + -12.304699f, 3.242644f, 103.837936f, + 12.157164f, -5.104313f, 59.511795f, + -12.270079f, -5.104312f, 59.511795f, + 12.160423f, -5.092517f, 103.837936f, + -12.304699f, -5.092517f, 103.837936f, + 12.168214f, -3.784476f, 53.848057f, + 12.168213f, 1.593239f, 53.848057f, + 16.825449f, -1.095619f, 53.848057f, + 12.168212f, -3.784476f, 103.848053f, + 12.168214f, 1.593239f, 103.848053f, + 16.825451f, -1.095618f, 98.413109f, + -12.298068f, -3.784476f, 53.848057f, + -12.298067f, 1.593239f, 53.848057f, + -16.955305f, -1.095619f, 53.848057f, + -12.298068f, -3.784476f, 103.848053f, + -12.298067f, 1.593239f, 103.848053f, + -16.955305f, -1.095618f, 98.413109f, + -7.397070f, 3.264224f, 0.000000f, + -2.558683f, -5.116109f, -0.000001f, + -12.235458f, -5.116108f, 0.000000f, + -7.397069f, 3.264223f, 59.537296f, + -2.558684f, -5.116110f, 59.537296f, + 7.315517f, 3.264224f, -0.000001f, + 12.153903f, -5.116109f, -0.000001f, + 2.477128f, -5.116108f, -0.000001f, + 7.315516f, 3.264223f, 59.537296f, + 2.477129f, -5.116108f, 59.537296f, + 6.665659f, -5.248195f, 113.712250f, + -6.685288f, -5.248195f, 113.712250f, + 6.665661f, 5.512237f, 113.712250f, + -6.685289f, 5.512238f, 113.712250f, + 6.665660f, -5.248195f, 126.712250f, + -6.685288f, -5.248195f, 126.712250f, + 6.665660f, 5.512237f, 126.712250f, + -6.685287f, 5.512237f, 126.712250f, + -19.978813f, 56.808506f, 46.409012f, + -19.978819f, 81.273628f, 46.409016f, + 24.372831f, 56.808514f, 46.409008f, + 24.372828f, 81.273636f, 46.409012f, + -19.953314f, 56.811768f, 38.062054f, + -19.953318f, 81.239006f, 38.062057f, + 24.372831f, 56.808514f, 38.073849f, + 24.372828f, 81.273636f, 38.073853f, + -25.617052f, 56.800713f, 39.381889f, + -25.617052f, 56.800713f, 44.759605f, + -25.617052f, 52.143478f, 42.070747f, + 24.382948f, 56.800724f, 39.381889f, + 24.382948f, 56.800724f, 44.759605f, + 18.948000f, 52.143486f, 42.070747f, + -25.617056f, 81.266998f, 39.381893f, + -25.617056f, 81.266991f, 44.759609f, + -25.617056f, 85.924232f, 42.070751f, + 24.382944f, 81.267006f, 39.381893f, + 24.382944f, 81.267006f, 44.759609f, + 18.947996f, 85.924240f, 42.070751f, + -79.465111f, 76.365990f, 46.430595f, + -79.465111f, 71.527603f, 38.050262f, + -79.465111f, 81.204376f, 38.050262f, + -19.927814f, 76.365997f, 46.430592f, + -19.927814f, 71.527611f, 38.050259f, + -79.465111f, 61.653400f, 46.430592f, + -79.465111f, 56.815014f, 38.050259f, + -79.465111f, 66.491791f, 38.050262f, + -19.927813f, 61.653412f, 46.430592f, + -19.927814f, 66.491798f, 38.050259f, + 34.247139f, 62.303280f, 37.918171f, + 34.247139f, 75.654228f, 37.918171f, + 34.247139f, 62.303276f, 48.678604f, + 34.247139f, 75.654228f, 48.678604f, + 47.247139f, 62.303284f, 37.918171f, + 47.247139f, 75.654228f, 37.918171f, + 47.247139f, 62.303280f, 48.678600f, + 47.247139f, 75.654228f, 48.678604f, + -100.184074f, 32.677177f, 38.047600f, + 66.438126f, 32.677177f, 38.047600f, + -100.184074f, 103.524979f, 38.047600f, + 66.438126f, 103.524979f, 38.047600f, + -100.184074f, 32.677177f, 0.000000f, + -100.184074f, 32.677177f, 38.047600f, + -100.184074f, 103.524979f, 0.000000f, + -100.184074f, 103.524979f, 38.047600f, + 66.438126f, 32.677177f, 0.000000f, + 66.438126f, 32.677177f, 38.047600f, + -100.184074f, 32.677177f, 0.000000f, + -100.184074f, 32.677177f, 38.047600f, + 66.438126f, 103.524979f, 0.000000f, + 66.438126f, 103.524979f, 38.047600f, + 66.438126f, 32.677177f, 0.000000f, + 66.438126f, 32.677177f, 38.047600f, + -100.184074f, 103.524979f, 0.000000f, + -100.184074f, 103.524979f, 38.047600f, + 66.438126f, 103.524979f, 0.000000f, + 66.438126f, 103.524979f, 38.047600f +}; + +static const unsigned short FurnitureMarker01Faces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 38, 40, 41, + 41, 39, 38, + 42, 43, 45, + 45, 44, 42, + 47, 48, 51, + 47, 51, 50, + 48, 46, 49, + 48, 49, 51, + 53, 57, 54, + 53, 56, 57, + 54, 55, 52, + 54, 57, 55, + 58, 59, 62, + 58, 62, 61, + 60, 58, 61, + 60, 61, 43, + 63, 64, 42, + 63, 42, 66, + 65, 63, 66, + 65, 66, 67, + 68, 69, 73, + 73, 72, 68, + 69, 71, 75, + 75, 73, 69, + 71, 70, 74, + 74, 75, 71, + 70, 68, 72, + 72, 74, 70, + 76, 77, 78, + 77, 79, 78, + 80, 81, 82, + 81, 83, 82, + 84, 85, 86, + 85, 87, 86, + 88, 89, 90, + 89, 91, 90, + 92, 93, 94, + 93, 95, 94 +}; + +static const GLMarker FurnitureMarker01 = { 96, 66, FurnitureMarker01Verts, FurnitureMarker01Faces }; + +static const float FurnitureMarker03Verts[] = +{ + 12.160423f, 3.242646f, 59.486294f, + -12.304700f, 3.242646f, 59.486294f, + 12.160423f, 3.242644f, 103.837936f, + -12.304699f, 3.242644f, 103.837936f, + 12.157164f, -5.104313f, 59.511795f, + -12.270079f, -5.104312f, 59.511795f, + 12.160423f, -5.092517f, 103.837936f, + -12.304699f, -5.092517f, 103.837936f, + 12.168214f, -3.784476f, 53.848057f, + 12.168213f, 1.593239f, 53.848057f, + 16.825449f, -1.095619f, 53.848057f, + 12.168212f, -3.784476f, 103.848053f, + 12.168214f, 1.593239f, 103.848053f, + 16.825451f, -1.095618f, 98.413109f, + -12.298068f, -3.784476f, 53.848057f, + -12.298067f, 1.593239f, 53.848057f, + -16.955305f, -1.095619f, 53.848057f, + -12.298068f, -3.784476f, 103.848053f, + -12.298067f, 1.593239f, 103.848053f, + -16.955305f, -1.095618f, 98.413109f, + -7.397070f, 3.264224f, 0.000000f, + -2.558683f, -5.116109f, -0.000001f, + -12.235458f, -5.116108f, 0.000000f, + -7.397069f, 3.264223f, 59.537296f, + -2.558684f, -5.116110f, 59.537296f, + 7.315517f, 3.264224f, -0.000001f, + 12.153903f, -5.116109f, -0.000001f, + 2.477128f, -5.116108f, -0.000001f, + 7.315516f, 3.264223f, 59.537296f, + 2.477129f, -5.116108f, 59.537296f, + 6.665659f, -5.248195f, 113.712250f, + -6.685288f, -5.248195f, 113.712250f, + 6.665661f, 5.512237f, 113.712250f, + -6.685289f, 5.512238f, 113.712250f, + 6.665660f, -5.248195f, 126.712250f, + -6.685288f, -5.248195f, 126.712250f, + 6.665660f, 5.512237f, 126.712250f, + -6.685287f, 5.512237f, 126.712250f, + -9.398697f, 60.559513f, 11.966876f, + -9.398703f, 85.024635f, 11.966880f, + 34.952950f, 60.559521f, 11.966873f, + 34.952946f, 85.024643f, 11.966877f, + -9.373198f, 60.562775f, 3.619917f, + -9.373201f, 84.990013f, 3.619922f, + 34.952946f, 60.559521f, 3.631711f, + 34.952942f, 85.024643f, 3.631715f, + -15.036936f, 60.551720f, 4.939754f, + -15.036936f, 60.551720f, 10.317469f, + -15.036935f, 55.894485f, 7.628611f, + 34.963062f, 60.551731f, 4.939752f, + 34.963066f, 60.551731f, 10.317468f, + 29.528116f, 55.894493f, 7.628610f, + -15.036940f, 85.018005f, 4.939758f, + -15.036940f, 85.017998f, 10.317472f, + -15.036941f, 89.675240f, 7.628616f, + 34.963058f, 85.018013f, 4.939756f, + 34.963062f, 85.018013f, 10.317472f, + 29.528112f, 89.675247f, 7.628615f, + -68.884995f, 80.116997f, 11.988459f, + -68.884995f, 75.278610f, 3.608125f, + -68.884995f, 84.955383f, 3.608128f, + -9.347699f, 80.117004f, 11.988457f, + -9.347699f, 75.278618f, 3.608123f, + -68.884995f, 65.404404f, 11.988456f, + -68.884995f, 60.566021f, 3.608122f, + -68.884995f, 70.242798f, 3.608126f, + -9.347696f, 65.404419f, 11.988454f, + -9.347698f, 70.242805f, 3.608123f, + 44.827255f, 66.054291f, 3.476035f, + 44.827255f, 79.405235f, 3.476037f, + 44.827255f, 66.054283f, 14.236466f, + 44.827255f, 79.405235f, 14.236468f, + 57.827255f, 66.054291f, 3.476034f, + 57.827255f, 79.405235f, 3.476036f, + 57.827255f, 66.054291f, 14.236465f, + 57.827255f, 79.405235f, 14.236467f +}; + +static const unsigned short FurnitureMarker03Faces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 38, 40, 41, + 41, 39, 38, + 42, 43, 45, + 45, 44, 42, + 47, 48, 51, + 47, 51, 50, + 48, 46, 49, + 48, 49, 51, + 53, 57, 54, + 53, 56, 57, + 54, 55, 52, + 54, 57, 55, + 58, 59, 62, + 58, 62, 61, + 60, 58, 61, + 60, 61, 43, + 63, 64, 42, + 63, 42, 66, + 65, 63, 66, + 65, 66, 67, + 68, 69, 73, + 73, 72, 68, + 69, 71, 75, + 75, 73, 69, + 71, 70, 74, + 74, 75, 71, + 70, 68, 72, + 72, 74, 70 +}; + +static const GLMarker FurnitureMarker03 = { 76, 56, FurnitureMarker03Verts, FurnitureMarker03Faces }; + +static const float FurnitureMarker04Verts[] = +{ + 12.160423f, 3.242646f, 59.486294f, + -12.304700f, 3.242646f, 59.486294f, + 12.160423f, 3.242644f, 103.837936f, + -12.304699f, 3.242644f, 103.837936f, + 12.157164f, -5.104313f, 59.511795f, + -12.270079f, -5.104312f, 59.511795f, + 12.160423f, -5.092517f, 103.837936f, + -12.304699f, -5.092517f, 103.837936f, + 12.168214f, -3.784476f, 53.848057f, + 12.168213f, 1.593239f, 53.848057f, + 16.825449f, -1.095619f, 53.848057f, + 12.168212f, -3.784476f, 103.848053f, + 12.168214f, 1.593239f, 103.848053f, + 16.825451f, -1.095618f, 98.413109f, + -12.298068f, -3.784476f, 53.848057f, + -12.298067f, 1.593239f, 53.848057f, + -16.955305f, -1.095619f, 53.848057f, + -12.298068f, -3.784476f, 103.848053f, + -12.298067f, 1.593239f, 103.848053f, + -16.955305f, -1.095618f, 98.413109f, + -7.397070f, 3.264224f, 0.000000f, + -2.558683f, -5.116109f, -0.000001f, + -12.235458f, -5.116108f, 0.000000f, + -7.397069f, 3.264223f, 59.537296f, + -2.558684f, -5.116110f, 59.537296f, + 7.315517f, 3.264224f, -0.000001f, + 12.153903f, -5.116109f, -0.000001f, + 2.477128f, -5.116108f, -0.000001f, + 7.315516f, 3.264223f, 59.537296f, + 2.477129f, -5.116108f, 59.537296f, + 6.665659f, -5.248195f, 113.712250f, + -6.685288f, -5.248195f, 113.712250f, + 6.665661f, 5.512237f, 113.712250f, + -6.685289f, 5.512238f, 113.712250f, + 6.665660f, -5.248195f, 126.712250f, + -6.685288f, -5.248195f, 126.712250f, + 6.665660f, 5.512237f, 126.712250f, + -6.685287f, 5.512237f, 126.712250f, + -12.253868f, 116.276588f, 15.000266f, + 12.211255f, 116.276588f, 15.000268f, + -12.253861f, 71.924942f, 15.000266f, + 12.211262f, 71.924942f, 15.000268f, + -12.250607f, 116.251091f, 6.653307f, + 12.176635f, 116.251091f, 6.653310f, + -12.253860f, 71.924942f, 6.665105f, + 12.211262f, 71.924942f, 6.665107f, + -12.261660f, 121.914825f, 7.973144f, + -12.261659f, 121.914825f, 13.350859f, + -16.918896f, 121.914825f, 10.662001f, + -12.261649f, 71.914825f, 7.973146f, + -12.261651f, 71.914825f, 13.350862f, + -16.918890f, 77.349770f, 10.662003f, + 12.204622f, 121.914825f, 7.973146f, + 12.204621f, 121.914825f, 13.350861f, + 16.861860f, 121.914825f, 10.662004f, + 12.204631f, 71.914825f, 7.973148f, + 12.204630f, 71.914825f, 13.350863f, + 16.861868f, 77.349777f, 10.662006f, + 7.303616f, 175.762878f, 15.021843f, + 2.465230f, 175.762878f, 6.641510f, + 12.142004f, 175.762878f, 6.641512f, + 7.303625f, 116.225586f, 15.021845f, + 2.465240f, 116.225586f, 6.641511f, + -7.408972f, 175.762878f, 15.021841f, + -12.247356f, 175.762878f, 6.641508f, + -2.570582f, 175.762878f, 6.641510f, + -7.408961f, 116.225586f, 15.021844f, + -2.570573f, 116.225586f, 6.641512f, + -6.759095f, 62.050632f, 6.509429f, + 6.591853f, 62.050636f, 6.509429f, + -6.759098f, 62.050632f, 17.269859f, + 6.591853f, 62.050636f, 17.269861f, + -6.759093f, 49.050632f, 6.509429f, + 6.591856f, 49.050636f, 6.509430f, + -6.759094f, 49.050632f, 17.269861f, + 6.591853f, 49.050636f, 17.269861f +}; + +static const unsigned short FurnitureMarker04Faces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 38, 40, 41, + 41, 39, 38, + 42, 43, 45, + 45, 44, 42, + 47, 48, 51, + 47, 51, 50, + 48, 46, 49, + 48, 49, 51, + 53, 57, 54, + 53, 56, 57, + 54, 55, 52, + 54, 57, 55, + 58, 59, 62, + 58, 62, 61, + 60, 58, 61, + 60, 61, 43, + 63, 64, 42, + 63, 42, 66, + 65, 63, 66, + 65, 66, 67, + 68, 69, 73, + 73, 72, 68, + 69, 71, 75, + 75, 73, 69, + 71, 70, 74, + 74, 75, 71, + 70, 68, 72, + 72, 74, 70 +}; + +static const GLMarker FurnitureMarker04 = { 76, 56, FurnitureMarker04Verts, FurnitureMarker04Faces }; + +static const float FurnitureMarker11Verts[] = +{ + 12.160423f, 3.242646f, 59.486294f, + -12.304700f, 3.242645f, 59.486294f, + 12.160423f, 3.242644f, 103.837936f, + -12.304699f, 3.242643f, 103.837936f, + 12.157164f, -5.104314f, 59.511795f, + -12.270079f, -5.104313f, 59.511795f, + 12.160423f, -5.092518f, 103.837936f, + -12.304699f, -5.092518f, 103.837936f, + 12.168214f, -3.784477f, 53.848057f, + 12.168213f, 1.593238f, 53.848057f, + 16.825449f, -1.095620f, 53.848057f, + 12.168212f, -3.784477f, 103.848053f, + 12.168214f, 1.593238f, 103.848053f, + 16.825451f, -1.095619f, 98.413109f, + -12.298068f, -3.784477f, 53.848057f, + -12.298067f, 1.593238f, 53.848057f, + -16.955305f, -1.095619f, 53.848057f, + -12.298068f, -3.784477f, 103.848053f, + -12.298067f, 1.593238f, 103.848053f, + -16.955305f, -1.095619f, 98.413109f, + -7.397070f, 3.264223f, 0.000000f, + -2.558683f, -5.116110f, -0.000001f, + -12.235458f, -5.116108f, 0.000000f, + -7.397069f, 3.264223f, 59.537296f, + -2.558684f, -5.116111f, 59.537296f, + 7.315517f, 3.264223f, -0.000001f, + 12.153903f, -5.116110f, -0.000001f, + 2.477128f, -5.116108f, -0.000001f, + 7.315516f, 3.264223f, 59.537296f, + 2.477129f, -5.116109f, 59.537296f, + 6.665659f, -5.248195f, 113.712250f, + -6.685288f, -5.248196f, 113.712250f, + 6.665661f, 5.512237f, 113.712250f, + -6.685289f, 5.512237f, 113.712250f, + 6.665660f, -5.248196f, 126.712250f, + -6.685288f, -5.248196f, 126.712250f, + 6.665660f, 5.512236f, 126.712250f, + -6.685287f, 5.512236f, 126.712250f, + -5.279642f, 63.934200f, 32.117043f, + -5.279637f, 39.469078f, 32.117043f, + -5.279640f, 63.934200f, 89.762779f, + -5.279635f, 39.469078f, 89.762779f, + 3.170247f, 63.930939f, 32.127567f, + 3.169106f, 39.503700f, 32.130669f, + 3.055522f, 63.934200f, 89.762779f, + 3.055527f, 39.469078f, 89.762779f, + 1.747481f, 63.941990f, 39.772892f, + -3.630234f, 63.941990f, 39.772892f, + -0.941377f, 68.599228f, 39.772896f, + 1.747481f, 63.941990f, 89.772896f, + -3.630234f, 63.941990f, 89.772896f, + -0.941378f, 68.599228f, 84.337944f, + 1.747485f, 39.475712f, 39.772892f, + -3.630230f, 39.475708f, 39.772892f, + -0.941372f, 34.818470f, 39.772892f, + 1.747485f, 39.475712f, 89.772896f, + -3.630230f, 39.475708f, 89.772896f, + -0.941372f, 34.818470f, 84.337944f, + -34.635773f, 44.376709f, 40.468124f, + -26.299620f, 49.215092f, 32.118874f, + -26.299623f, 39.538319f, 32.118874f, + -5.277596f, 44.376709f, 40.499199f, + -5.277597f, 49.215096f, 32.118874f, + -34.635777f, 59.089291f, 40.468128f, + -26.298481f, 63.927677f, 32.115772f, + -26.298479f, 54.250904f, 32.115768f, + -5.276455f, 59.089291f, 40.496094f, + -5.276457f, 54.250904f, 32.115765f, + 3.211200f, 58.439438f, 101.666718f, + 3.211203f, 45.088490f, 101.666718f, + -7.549232f, 58.439438f, 101.666718f, + -7.549230f, 45.088486f, 101.666718f, + 3.211200f, 58.439438f, 114.666718f, + 3.211203f, 45.088490f, 114.666718f, + -7.549231f, 58.439434f, 114.666718f, + -7.549229f, 45.088490f, 114.666718f, + -39.159565f, 44.376709f, -0.158797f, + -30.779234f, 49.215096f, -0.158798f, + -30.779236f, 39.538319f, -0.158805f, + -39.159565f, 59.089291f, -0.158798f, + -30.779228f, 63.927677f, -0.158798f, + -30.779228f, 54.250904f, -0.158798f, + -5.301957f, 63.930939f, 32.127563f, + -5.303098f, 39.503700f, 32.130665f, + 15.970100f, 67.928596f, 0.000000f, + 15.970100f, 34.994801f, 0.000000f, + -15.804100f, 67.928596f, 0.000000f, + -15.804100f, 34.994801f, 0.000000f, + -15.804100f, 34.994801f, 32.000000f, + 15.970100f, 34.994801f, 32.000000f, + -15.804100f, 67.928596f, 32.000000f, + 15.970100f, 67.928596f, 32.000000f, + -15.804100f, 34.994801f, 0.000000f, + -15.804100f, 34.994801f, 32.000000f, + -15.804100f, 67.928596f, 0.000000f, + -15.804100f, 67.928596f, 32.000000f, + 15.970100f, 34.994801f, 0.000000f, + 15.970100f, 34.994801f, 32.000000f, + -15.804100f, 34.994801f, 0.000000f, + -15.804100f, 34.994801f, 32.000000f, + 15.970100f, 67.928596f, 0.000000f, + 15.970100f, 67.928596f, 32.000000f, + 15.970100f, 34.994801f, 0.000000f, + 15.970100f, 34.994801f, 32.000000f, + -15.804100f, 67.928596f, 0.000000f, + -15.804100f, 67.928596f, 32.000000f, + 15.970100f, 67.928596f, 0.000000f, + 15.970100f, 67.928596f, 32.000000f, +}; + +static const unsigned short FurnitureMarker11Faces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 41, 40, 38, + 38, 39, 41, + 45, 43, 42, + 42, 44, 45, + 51, 48, 47, + 50, 51, 47, + 49, 46, 48, + 51, 49, 48, + 54, 57, 53, + 57, 56, 53, + 52, 55, 54, + 55, 57, 54, + 62, 59, 58, + 61, 62, 58, + 61, 58, 60, + 83, 61, 60, + 82, 64, 63, + 66, 82, 63, + 66, 63, 65, + 67, 66, 65, + 73, 69, 68, + 68, 72, 73, + 75, 71, 69, + 69, 73, 75, + 74, 70, 71, + 71, 75, 74, + 72, 68, 70, + 70, 74, 72, + 59, 77, 76, + 58, 59, 76, + 58, 76, 78, + 60, 58, 78, + 64, 80, 79, + 63, 64, 79, + 63, 79, 81, + 65, 63, 81, + 84, 85, 86, + 85, 87, 86, + 88, 89, 90, + 89, 91, 90, + 92, 93, 94, + 93, 95, 94, + 96, 97, 98, + 97, 99, 98, + 100, 101, 102, + 101, 103, 102, + 104, 105, 106, + 105, 107, 106 +}; + +static const GLMarker FurnitureMarker11 = { 108, 76, FurnitureMarker11Verts, FurnitureMarker11Faces }; + +static const float FurnitureMarker13Verts[] = +{ + 12.160423f, 3.242646f, 59.486294f, + -12.304700f, 3.242645f, 59.486294f, + 12.160423f, 3.242644f, 103.837936f, + -12.304699f, 3.242643f, 103.837936f, + 12.157164f, -5.104314f, 59.511795f, + -12.270079f, -5.104313f, 59.511795f, + 12.160423f, -5.092518f, 103.837936f, + -12.304699f, -5.092518f, 103.837936f, + 12.168214f, -3.784477f, 53.848057f, + 12.168213f, 1.593238f, 53.848057f, + 16.825449f, -1.095620f, 53.848057f, + 12.168212f, -3.784477f, 103.848053f, + 12.168214f, 1.593238f, 103.848053f, + 16.825451f, -1.095619f, 98.413109f, + -12.298068f, -3.784477f, 53.848057f, + -12.298067f, 1.593238f, 53.848057f, + -16.955305f, -1.095619f, 53.848057f, + -12.298068f, -3.784477f, 103.848053f, + -12.298067f, 1.593238f, 103.848053f, + -16.955305f, -1.095619f, 98.413109f, + -7.397070f, 3.264223f, 0.000000f, + -2.558683f, -5.116110f, -0.000001f, + -12.235458f, -5.116108f, 0.000000f, + -7.397069f, 3.264223f, 59.537296f, + -2.558684f, -5.116111f, 59.537296f, + 7.315517f, 3.264223f, -0.000001f, + 12.153903f, -5.116110f, -0.000001f, + 2.477128f, -5.116108f, -0.000001f, + 7.315516f, 3.264223f, 59.537296f, + 2.477129f, -5.116109f, 59.537296f, + 6.665659f, -5.248195f, 113.712250f, + -6.685288f, -5.248196f, 113.712250f, + 6.665661f, 5.512237f, 113.712250f, + -6.685289f, 5.512237f, 113.712250f, + 6.665660f, -5.248196f, 126.712250f, + -6.685288f, -5.248196f, 126.712250f, + 6.665660f, 5.512236f, 126.712250f, + -6.685287f, 5.512236f, 126.712250f, + 12.537734f, 60.403820f, 32.117043f, + -11.927388f, 60.403816f, 32.117043f, + 12.537737f, 60.403820f, 89.762779f, + -11.927387f, 60.403816f, 89.762779f, + 12.534475f, 51.953934f, 32.127567f, + -11.892765f, 51.955074f, 32.130669f, + 12.537735f, 52.068657f, 89.762779f, + -11.927386f, 52.068653f, 89.762779f, + 12.545526f, 53.376698f, 39.772892f, + 12.545527f, 58.754414f, 39.772892f, + 17.202761f, 56.065556f, 39.772896f, + 12.545525f, 53.376698f, 89.772896f, + 12.545525f, 58.754414f, 89.772896f, + 17.202763f, 56.065556f, 84.337944f, + -11.920755f, 53.376694f, 39.772892f, + -11.920755f, 58.754410f, 39.772892f, + -16.577993f, 56.065552f, 39.772892f, + -11.920755f, 53.376694f, 89.772896f, + -11.920755f, 58.754410f, 89.772896f, + -16.577993f, 56.065552f, 84.337944f, + -7.019755f, 89.759956f, 40.468124f, + -2.181371f, 81.423798f, 32.118874f, + -11.858147f, 81.423805f, 32.118874f, + -7.019757f, 60.401775f, 40.499199f, + -2.181369f, 60.401775f, 32.118874f, + 7.692828f, 89.759956f, 40.468128f, + 12.531213f, 81.422661f, 32.115772f, + 2.854439f, 81.422661f, 32.115768f, + 7.692825f, 60.400635f, 40.496094f, + 2.854440f, 60.400639f, 32.115765f, + 7.042971f, 51.912979f, 101.666718f, + -6.307976f, 51.912979f, 101.666718f, + 7.042972f, 62.673412f, 101.666718f, + -6.307978f, 62.673409f, 101.666718f, + 7.042972f, 51.912979f, 114.666718f, + -6.307976f, 51.912979f, 114.666718f, + 7.042971f, 62.673412f, 114.666718f, + -6.307977f, 62.673409f, 114.666718f, + -7.019756f, 94.283745f, -0.158797f, + -2.181369f, 85.903412f, -0.158798f, + -11.858145f, 85.903419f, -0.158805f, + 7.692829f, 94.283745f, -0.158798f, + 12.531214f, 85.903412f, -0.158798f, + 2.854441f, 85.903404f, -0.158798f, + 12.534474f, 60.426136f, 32.127563f, + -11.892765f, 60.427280f, 32.130665f, + 16.532131f, 39.154079f, -0.000006f, + -16.401665f, 39.154083f, -0.000003f, + 16.532131f, 70.928284f, -0.000006f, + -16.401665f, 70.928284f, -0.000003f, + -16.401661f, 70.928284f, 31.999996f, + -16.401663f, 39.154083f, 31.999996f, + 16.532135f, 70.928284f, 31.999994f, + 16.532133f, 39.154079f, 31.999994f, + -16.401665f, 70.928284f, -0.000003f, + -16.401661f, 70.928284f, 31.999996f, + 16.532131f, 70.928284f, -0.000006f, + 16.532135f, 70.928284f, 31.999994f, + -16.401665f, 39.154083f, -0.000003f, + -16.401663f, 39.154083f, 31.999996f, + -16.401665f, 70.928284f, -0.000003f, + -16.401661f, 70.928284f, 31.999996f, + 16.532131f, 39.154079f, -0.000006f, + 16.532133f, 39.154079f, 31.999994f, + -16.401665f, 39.154083f, -0.000003f, + -16.401663f, 39.154083f, 31.999996f, + 16.532131f, 70.928284f, -0.000006f, + 16.532135f, 70.928284f, 31.999994f, + 16.532131f, 39.154079f, -0.000006f, + 16.532133f, 39.154079f, 31.999994f, +}; + +static const unsigned short FurnitureMarker13Faces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 41, 40, 38, + 38, 39, 41, + 45, 43, 42, + 42, 44, 45, + 51, 48, 47, + 50, 51, 47, + 49, 46, 48, + 51, 49, 48, + 54, 57, 53, + 57, 56, 53, + 52, 55, 54, + 55, 57, 54, + 62, 59, 58, + 61, 62, 58, + 61, 58, 60, + 83, 61, 60, + 82, 64, 63, + 66, 82, 63, + 66, 63, 65, + 67, 66, 65, + 73, 69, 68, + 68, 72, 73, + 75, 71, 69, + 69, 73, 75, + 74, 70, 71, + 71, 75, 74, + 72, 68, 70, + 70, 74, 72, + 59, 77, 76, + 58, 59, 76, + 58, 76, 78, + 60, 58, 78, + 64, 80, 79, + 63, 64, 79, + 63, 79, 81, + 65, 63, 81, + 84, 85, 86, + 85, 87, 86, + 88, 89, 90, + 89, 91, 90, + 92, 93, 94, + 93, 95, 94, + 96, 97, 98, + 97, 99, 98, + 100, 101, 102, + 101, 103, 102, + 104, 105, 106, + 105, 107, 106 +}; + +static const GLMarker FurnitureMarker13 = { 108, 76, FurnitureMarker13Verts, FurnitureMarker13Faces }; + +static const float FurnitureMarker14Verts[] = +{ + 12.160423f, 3.242646f, 59.486294f, + -12.304700f, 3.242645f, 59.486294f, + 12.160423f, 3.242644f, 103.837936f, + -12.304699f, 3.242643f, 103.837936f, + 12.157164f, -5.104314f, 59.511795f, + -12.270079f, -5.104313f, 59.511795f, + 12.160423f, -5.092518f, 103.837936f, + -12.304699f, -5.092518f, 103.837936f, + 12.168214f, -3.784477f, 53.848057f, + 12.168213f, 1.593238f, 53.848057f, + 16.825449f, -1.095620f, 53.848057f, + 12.168212f, -3.784477f, 103.848053f, + 12.168214f, 1.593238f, 103.848053f, + 16.825451f, -1.095619f, 98.413109f, + -12.298068f, -3.784477f, 53.848057f, + -12.298067f, 1.593238f, 53.848057f, + -16.955305f, -1.095619f, 53.848057f, + -12.298068f, -3.784477f, 103.848053f, + -12.298067f, 1.593238f, 103.848053f, + -16.955305f, -1.095619f, 98.413109f, + -7.397070f, 3.264223f, 0.000000f, + -2.558683f, -5.116110f, -0.000001f, + -12.235458f, -5.116108f, 0.000000f, + -7.397069f, 3.264223f, 59.537296f, + -2.558684f, -5.116111f, 59.537296f, + 7.315517f, 3.264223f, -0.000001f, + 12.153903f, -5.116110f, -0.000001f, + 2.477128f, -5.116108f, -0.000001f, + 7.315516f, 3.264223f, 59.537296f, + 2.477129f, -5.116109f, 59.537296f, + 6.665659f, -5.248195f, 113.712250f, + -6.685288f, -5.248196f, 113.712250f, + 6.665661f, 5.512237f, 113.712250f, + -6.685289f, 5.512237f, 113.712250f, + 6.665660f, -5.248196f, 126.712250f, + -6.685288f, -5.248196f, 126.712250f, + 6.665660f, 5.512236f, 126.712250f, + -6.685287f, 5.512236f, 126.712250f, + -11.912970f, 51.759579f, 32.117043f, + 12.552153f, 51.759583f, 32.117043f, + -11.912972f, 51.759579f, 89.762779f, + 12.552152f, 51.759586f, 89.762779f, + -11.909712f, 60.209465f, 32.127567f, + 12.517528f, 60.208328f, 32.130669f, + -11.912972f, 60.094742f, 89.762779f, + 12.552150f, 60.094749f, 89.762779f, + -11.920763f, 58.786701f, 39.772892f, + -11.920762f, 53.408985f, 39.772892f, + -16.577997f, 56.097843f, 39.772896f, + -11.920761f, 58.786701f, 89.772896f, + -11.920761f, 53.408985f, 89.772896f, + -16.577999f, 56.097839f, 84.337944f, + 12.545519f, 58.786705f, 39.772892f, + 12.545520f, 53.408993f, 39.772892f, + 17.202757f, 56.097851f, 39.772892f, + 12.545518f, 58.786705f, 89.772896f, + 12.545520f, 53.408993f, 89.772896f, + 17.202757f, 56.097851f, 84.337944f, + 7.644523f, 22.403448f, 40.468124f, + 2.806138f, 30.739601f, 32.118874f, + 12.482914f, 30.739597f, 32.118874f, + 7.644521f, 51.761623f, 40.499199f, + 2.806133f, 51.761623f, 32.118874f, + -7.068059f, 22.403442f, 40.468128f, + -11.906446f, 30.740736f, 32.115772f, + -2.229672f, 30.740742f, 32.115768f, + -7.068060f, 51.762764f, 40.496094f, + -2.229676f, 51.762764f, 32.115765f, + -6.418209f, 60.250420f, 101.666718f, + 6.932739f, 60.250423f, 101.666718f, + -6.418208f, 49.489986f, 101.666718f, + 6.932743f, 49.489990f, 101.666718f, + -6.418209f, 60.250420f, 114.666718f, + 6.932739f, 60.250423f, 114.666718f, + -6.418206f, 49.489986f, 114.666718f, + 6.932741f, 49.489990f, 114.666718f, + 7.644525f, 17.879654f, -0.158797f, + 2.806137f, 26.259987f, -0.158798f, + 12.482912f, 26.259985f, -0.158805f, + -7.068059f, 17.879656f, -0.158798f, + -11.906446f, 26.259991f, -0.158798f, + -2.229673f, 26.259993f, -0.158798f, + -11.909710f, 51.737263f, 32.127563f, + 12.517529f, 51.736122f, 32.130665f, + 16.532131f, 39.154079f, -0.000006f, + -16.401665f, 39.154083f, -0.000003f, + 16.532131f, 70.928284f, -0.000006f, + -16.401665f, 70.928284f, -0.000003f, + -16.401661f, 70.928284f, 31.999996f, + -16.401663f, 39.154083f, 31.999996f, + 16.532135f, 70.928284f, 31.999994f, + 16.532133f, 39.154079f, 31.999994f, + -16.401665f, 70.928284f, -0.000003f, + -16.401661f, 70.928284f, 31.999996f, + 16.532131f, 70.928284f, -0.000006f, + 16.532135f, 70.928284f, 31.999994f, + -16.401665f, 39.154083f, -0.000003f, + -16.401663f, 39.154083f, 31.999996f, + -16.401665f, 70.928284f, -0.000003f, + -16.401661f, 70.928284f, 31.999996f, + 16.532131f, 39.154079f, -0.000006f, + 16.532133f, 39.154079f, 31.999994f, + -16.401665f, 39.154083f, -0.000003f, + -16.401663f, 39.154083f, 31.999996f, + 16.532131f, 70.928284f, -0.000006f, + 16.532135f, 70.928284f, 31.999994f, + 16.532131f, 39.154079f, -0.000006f, + 16.532133f, 39.154079f, 31.999994f, +}; + +static const unsigned short FurnitureMarker14Faces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 41, 40, 38, + 38, 39, 41, + 45, 43, 42, + 42, 44, 45, + 51, 48, 47, + 50, 51, 47, + 49, 46, 48, + 51, 49, 48, + 54, 57, 53, + 57, 56, 53, + 52, 55, 54, + 55, 57, 54, + 62, 59, 58, + 61, 62, 58, + 61, 58, 60, + 83, 61, 60, + 82, 64, 63, + 66, 82, 63, + 66, 63, 65, + 67, 66, 65, + 73, 69, 68, + 68, 72, 73, + 75, 71, 69, + 69, 73, 75, + 74, 70, 71, + 71, 75, 74, + 72, 68, 70, + 70, 74, 72, + 59, 77, 76, + 58, 59, 76, + 58, 76, 78, + 60, 58, 78, + 64, 80, 79, + 63, 64, 79, + 63, 79, 81, + 65, 63, 81, + 84, 85, 86, + 85, 87, 86, + 88, 89, 90, + 89, 91, 90, + 92, 93, 94, + 93, 95, 94, + 96, 97, 98, + 97, 99, 98, + 100, 101, 102, + 101, 103, 102, + 104, 105, 106, + 105, 107, 106 +}; + +static const GLMarker FurnitureMarker14 = { 108, 76, FurnitureMarker14Verts, FurnitureMarker14Faces }; diff --git a/gl/renderer.cpp b/gl/renderer.cpp index bea6067e2..2b5f928eb 100644 --- a/gl/renderer.cpp +++ b/gl/renderer.cpp @@ -1,817 +1,817 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "renderer.h" - -#include -#include -#include -#include -#include - -#include - -#include "gltex.h" -#include "glmesh.h" -#include "glscene.h" -#include "glproperty.h" -#include "options.h" - -// GL_ARB_shader_objects -PFNGLCREATEPROGRAMOBJECTARBPROC _glCreateProgramObjectARB = NULL; -PFNGLDELETEOBJECTARBPROC _glDeleteObjectARB = NULL; -PFNGLUSEPROGRAMOBJECTARBPROC _glUseProgramObjectARB = NULL; -PFNGLCREATESHADEROBJECTARBPROC _glCreateShaderObjectARB = NULL; -PFNGLSHADERSOURCEARBPROC _glShaderSourceARB = NULL; -PFNGLCOMPILESHADERARBPROC _glCompileShaderARB = NULL; -PFNGLGETOBJECTPARAMETERIVARBPROC _glGetObjectParameterivARB = NULL; -PFNGLATTACHOBJECTARBPROC _glAttachObjectARB = NULL; -PFNGLGETINFOLOGARBPROC _glGetInfoLogARB = NULL; -PFNGLLINKPROGRAMARBPROC _glLinkProgramARB = NULL; -PFNGLGETUNIFORMLOCATIONARBPROC _glGetUniformLocationARB = NULL; -PFNGLUNIFORM4FARBPROC _glUniform4fARB = NULL; -PFNGLUNIFORM1IARBPROC _glUniform1iARB = NULL; - -bool shader_initialized = false; -bool shader_ready = false; - -bool Renderer::initialize( const QGLContext * cx ) -{ - if ( shader_initialized ) - return shader_ready; - - shader_initialized = true; - - QString extensions( (const char *) glGetString(GL_EXTENSIONS) ); - - if ( ! extensions.contains( "GL_ARB_shading_language_100" ) || ! extensions.contains( "GL_ARB_shader_objects" ) - || ! extensions.contains( "GL_ARB_vertex_shader" ) || ! extensions.contains( "GL_ARB_fragment_shader" ) ) - { - return false; - } - - _glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC) cx->getProcAddress("glCreateProgramObjectARB"); - _glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC) cx->getProcAddress("glDeleteObjectARB"); - _glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC) cx->getProcAddress("glUseProgramObjectARB"); - _glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC) cx->getProcAddress("glCreateShaderObjectARB"); - _glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC) cx->getProcAddress("glShaderSourceARB"); - _glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC) cx->getProcAddress("glCompileShaderARB"); - _glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC) cx->getProcAddress("glGetObjectParameterivARB"); - _glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC) cx->getProcAddress("glAttachObjectARB"); - _glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC) cx->getProcAddress("glGetInfoLogARB"); - _glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC) cx->getProcAddress("glLinkProgramARB"); - _glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC) cx->getProcAddress("glGetUniformLocationARB"); - _glUniform4fARB = (PFNGLUNIFORM4FARBPROC) cx->getProcAddress("glUniform4fARB"); - _glUniform1iARB = (PFNGLUNIFORM1IARBPROC) cx->getProcAddress("glUniform1iARB"); - - if( !_glCreateProgramObjectARB || !_glDeleteObjectARB || !_glUseProgramObjectARB || - !_glCreateShaderObjectARB || !_glShaderSourceARB || !_glCompileShaderARB || - !_glGetObjectParameterivARB || !_glAttachObjectARB || !_glGetInfoLogARB || - !_glLinkProgramARB || !_glGetUniformLocationARB || !_glUniform4fARB || - !_glUniform1iARB ) - { - return false; - } - - shader_ready = true; - return true; -} - -bool Renderer::hasShaderSupport() -{ - return shader_ready; -} - -QHash Renderer::ConditionSingle::compStrs; - -Renderer::ConditionSingle::ConditionSingle( const QString & line, bool neg ) : invert( neg ) -{ - if ( compStrs.isEmpty() ) - { - compStrs.insert( EQ, " == " ); - compStrs.insert( NE, " != " ); - compStrs.insert( LE, " <= " ); - compStrs.insert( GE, " >= " ); - compStrs.insert( LT, " < " ); - compStrs.insert( GT, " > " ); - compStrs.insert( AND, " & " ); - } - - QHashIterator i( compStrs ); - int pos = -1; - while ( i.hasNext() ) - { - i.next(); - pos = line.indexOf( i.value() ); - if ( pos > 0 ) - break; - } - - if ( pos > 0 ) - { - left = line.left( pos ).trimmed(); - right = line.right( line.length() - pos - i.value().length() ).trimmed(); - if ( right.startsWith( "\"" ) && right.endsWith( "\"" ) ) - right = right.mid( 1, right.length() - 2 ); - comp = i.key(); - } - else - { - left = line; - comp = None; - } -} - -QModelIndex Renderer::ConditionSingle::getIndex( const NifModel * nif, const QList & iBlocks, QString blkid ) const -{ - QString childid; - int pos = blkid.indexOf( "/" ); - if ( pos > 0 ) - { - childid = blkid.right( blkid.length() - pos - 1 ); - blkid = blkid.left( pos ); - } - foreach ( QModelIndex iBlock, iBlocks ) - { - if ( nif->inherits( iBlock, blkid ) ) - { - if ( childid.isEmpty() ) - return iBlock; - else - return nif->getIndex( iBlock, childid ); - } - } - return QModelIndex(); -} - -bool Renderer::ConditionSingle::eval( const NifModel * nif, const QList & iBlocks ) const -{ - QModelIndex iLeft = getIndex( nif, iBlocks, left ); - if ( ! iLeft.isValid() ) - return invert; - if ( comp == None ) - return ! invert; - NifValue val = nif->getValue( iLeft ); - if ( val.isString() ) - return compare( val.toString(), right ) ^ invert; - else if ( val.isCount() ) - return compare( val.toCount(), right.toUInt() ) ^ invert; - else if ( val.isFloat() ) - return compare( val.toFloat(), (float) right.toDouble() ) ^ invert; - else - return false; -} - -bool Renderer::ConditionGroup::eval( const NifModel * nif, const QList & iBlocks ) const -{ - if ( conditions.isEmpty() ) - return true; - - if ( isOrGroup() ) - { - foreach ( Condition * cond, conditions ) - if ( cond->eval( nif, iBlocks ) ) - return true; - return false; - } - else - { - foreach ( Condition * cond, conditions ) - if ( ! cond->eval( nif, iBlocks ) ) - return false; - return true; - } -} - -void Renderer::ConditionGroup::addCondition( Condition * c ) -{ - conditions.append( c ); -} - -Renderer::Shader::Shader( const QString & n, GLenum t ) : name( n ), id( 0 ), status( false ), type( t ) -{ - id = _glCreateShaderObjectARB( type ); -} - -Renderer::Shader::~Shader() -{ - if ( id ) - _glDeleteObjectARB( id ); -} - -bool Renderer::Shader::load( const QString & filepath ) -{ - try - { - QFile file( filepath ); - if ( ! file.open( QIODevice::ReadOnly ) ) - throw QString( "couldn't open %1 for read access" ).arg( filepath ); - - QByteArray data = file.readAll(); - - const char * src = data.constData(); - - _glShaderSourceARB( id, 1, & src, 0 ); - _glCompileShaderARB( id ); - - GLint result; - _glGetObjectParameterivARB( id, GL_OBJECT_COMPILE_STATUS_ARB, & result ); - - if ( result != GL_TRUE ) - { - int logLen; - _glGetObjectParameterivARB( id, GL_OBJECT_INFO_LOG_LENGTH_ARB, & logLen ); - char * log = new char[ logLen ]; - _glGetInfoLogARB( id, logLen, 0, log ); - QString errlog( log ); - delete[] log; - throw errlog; - } - } - catch ( QString err ) - { - status = false; - qWarning() << "error loading shader" << name << ":\r\n" << err.toAscii().data(); - return false; - } - status = true; - return true; -} - -Renderer::Program::Program( const QString & n ) : name( n ), id( 0 ), status( false ) -{ - id = _glCreateProgramObjectARB(); -} - -Renderer::Program::~Program() -{ - if ( id ) - _glDeleteObjectARB( id ); -} - -bool Renderer::Program::load( const QString & filepath, Renderer * renderer ) -{ - try - { - QFile file( filepath ); - if ( ! file.open( QIODevice::ReadOnly ) ) - throw QString( "couldn't open %1 for read access" ).arg( filepath ); - - QTextStream stream( &file ); - - QStack chkgrps; - chkgrps.push( & conditions ); - - while ( ! stream.atEnd() ) - { - QString line = stream.readLine().trimmed(); - - if ( line.startsWith( "shaders" ) ) - { - QStringList list = line.simplified().split( " " ); - for ( int i = 1; i < list.count(); i++ ) - { - Shader * shader = renderer->shaders.value( list[ i ] ); - if ( shader ) - { - if ( shader->status ) - _glAttachObjectARB( id, shader->id ); - else - throw QString( "depends on shader %1 which was not compiled successful" ).arg( list[ i ] ); - } - else - throw QString( "shader %1 not found" ).arg( list[ i ] ); - } - } - else if ( line.startsWith( "checkgroup" ) ) - { - QStringList list = line.simplified().split( " " ); - if ( list.value( 1 ) == "begin" ) - { - ConditionGroup * group = new ConditionGroup( list.value( 2 ) == "or" ); - chkgrps.top()->addCondition( group ); - chkgrps.push( group ); - } - else if ( list.value( 1 ) == "end" ) - { - if ( chkgrps.count() > 1 ) - chkgrps.pop(); - else - throw QString( "mismatching checkgroup end tag" ); - } - else - throw QString( "expected begin or end after checkgroup" ); - } - else if ( line.startsWith( "check" ) ) - { - line = line.remove( 0, 5 ).trimmed(); - - bool invert = false; - if ( line.startsWith ( "not " ) ) - { - invert = true; - line = line.remove( 0, 4 ).trimmed(); - } - - chkgrps.top()->addCondition( new ConditionSingle( line, invert ) ); - } - else if ( line.startsWith( "texcoords" ) ) - { - line = line.remove( 0, 9 ).simplified(); - QStringList list = line.split( " " ); - bool ok; - int unit = list.value( 0 ).toInt( & ok ); - QString id = list.value( 1 ).toLower(); - if ( ! ok || id.isEmpty() ) - throw QString( "malformed texcoord tag" ); - if ( id != "tangents" && id != "binormals" && TexturingProperty::getId( id ) < 0 ) - throw QString( "texcoord tag referres to unknown texture id '%1'" ).arg( id ); - if ( texcoords.contains( unit ) ) - throw QString( "texture unit %1 is assigned twiced" ).arg( unit ); - texcoords.insert( unit, id ); - } - } - - _glLinkProgramARB( id ); - - int result; - _glGetObjectParameterivARB( id, GL_OBJECT_LINK_STATUS_ARB, & result ); - - if ( result != GL_TRUE ) - { - int logLen; - _glGetObjectParameterivARB( id, GL_OBJECT_INFO_LOG_LENGTH_ARB, & logLen ); - char * log = new char[ logLen ]; - _glGetInfoLogARB( id, logLen, 0, log ); - QString errlog( log ); - delete[] log; - throw errlog; - } - } - catch ( QString x ) - { - status = false; - qWarning() << "error loading shader program " << name << ":\r\n" << x.toAscii().data(); - return false; - } - status = true; - return true; -} - -Renderer::Renderer() -{ -} - -Renderer::~Renderer() -{ - releaseShaders(); -} - -void Renderer::updateShaders() -{ - if ( ! shader_ready ) - return; - - releaseShaders(); - - QDir dir( QApplication::applicationDirPath() ); - if ( dir.exists( "shaders" ) ) - dir.cd( "shaders" ); - else if ( dir.exists( "/usr/share/nifskope/shaders" ) ) - dir.cd( "/usr/share/nifskope/shaders" ); - -// linux does not want to load the shaders so disable them for now -#ifdef WIN32 - dir.setNameFilters( QStringList() << "*.vert" ); - foreach ( QString name, dir.entryList() ) - { - Shader * shader = new Shader( name, GL_VERTEX_SHADER_ARB ); - shader->load( dir.filePath( name ) ); - shaders.insert( name, shader ); - } - - dir.setNameFilters( QStringList() << "*.frag" ); - foreach ( QString name, dir.entryList() ) - { - Shader * shader = new Shader( name, GL_FRAGMENT_SHADER_ARB ); - shader->load( dir.filePath( name ) ); - shaders.insert( name, shader ); - } - - dir.setNameFilters( QStringList() << "*.prog" ); - foreach ( QString name, dir.entryList() ) - { - Program * program = new Program( name ); - program->load( dir.filePath( name ), this ); - programs.insert( name, program ); - } -#endif -} - -void Renderer::releaseShaders() -{ - if ( ! shader_ready ) - return; - - qDeleteAll( programs ); - programs.clear(); - qDeleteAll( shaders ); - shaders.clear(); -} - -QString Renderer::setupProgram( Mesh * mesh, const QString & hint ) -{ - PropertyList props; - mesh->activeProperties( props ); - - if ( ! shader_ready || ! Options::shaders() ) - { - setupFixedFunction( mesh, props ); - return QString( "fixed function pipeline" ); - } - - QList iBlocks; - iBlocks << mesh->index(); - iBlocks << mesh->iData; - foreach ( Property * p, props.list() ) - iBlocks.append( p->index() ); - - if ( ! hint.isEmpty() ) - { - Program * program = programs.value( hint ); - if ( program && program->status && setupProgram( program, mesh, props, iBlocks ) ) - return hint; - } - - foreach ( Program * program, programs ) - { - if ( program->status && setupProgram( program, mesh, props, iBlocks ) ) - return program->name; - } - - stopProgram(); - setupFixedFunction( mesh, props ); - return QString( "fixed function pipeline" ); -} - -void Renderer::stopProgram() -{ - if ( shader_ready ) - _glUseProgramObjectARB( 0 ); - resetTextureUnits(); -} - -bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & props, const QList & iBlocks ) -{ - const NifModel * nif = qobject_cast( mesh->index().model() ); - if ( ! mesh->index().isValid() || ! nif ) - return false; - - if ( ! prog->conditions.eval( nif, iBlocks ) ) - return false; - - _glUseProgramObjectARB( prog->id ); - - // texturing - - TexturingProperty * texprop = props.get< TexturingProperty >(); - - int texunit = 0; - - GLint uniBaseMap = _glGetUniformLocationARB( prog->id, "BaseMap" ); - - if ( uniBaseMap >= 0 ) - { - if ( ! texprop || ! activateTextureUnit( texunit ) || ! texprop->bind( 0 ) ) - return false; - - _glUniform1iARB( uniBaseMap, texunit++ ); - } - - GLint uniNormalMap = _glGetUniformLocationARB( prog->id, "NormalMap" ); - - if ( uniNormalMap >= 0 ) - { - QString fname = texprop->fileName( 0 ); - if ( fname.isEmpty() ) - return false; - - int pos = fname.indexOf( "_" ); - if ( pos >= 0 ) - fname = fname.left( pos ) + "_n.dds"; - else if ( ( pos = fname.lastIndexOf( "." ) ) >= 0 ) - fname = fname.insert( pos, "_n" ); - - if ( ! activateTextureUnit( texunit ) || ! texprop->bind( 0, fname ) ) - return false; - - _glUniform1iARB( uniNormalMap, texunit++ ); - } - - GLint uniGlowMap = _glGetUniformLocationARB( prog->id, "GlowMap" ); - - if ( uniGlowMap >= 0 ) - { - QString fname = texprop->fileName( 0 ); - if ( fname.isEmpty() ) - return false; - - int pos = fname.indexOf( "_" ); - if ( pos >= 0 ) - fname = fname.left( pos ) + "_g.dds"; - else if ( ( pos = fname.lastIndexOf( "." ) ) >= 0 ) - fname = fname.insert( pos, "_g" ); - - if ( ! activateTextureUnit( texunit ) || ! texprop->bind( 0, fname ) ) - return false; - - _glUniform1iARB( uniGlowMap, texunit++ ); - } - else - { - QString fname = texprop->fileName( 0 ); - if ( ! fname.isEmpty() ) - { - int pos = fname.indexOf( "_" ); - if ( pos >= 0 ) - fname = fname.left( pos ) + "_g.dds"; - else if ( ( pos = fname.lastIndexOf( "." ) ) >= 0 ) - fname = fname.insert( pos, "_g" ); - - if ( activateTextureUnit( texunit ) && texprop->bind( 0, fname ) ) - return false; - } - } - - QMapIterator itx( prog->texcoords ); - while ( itx.hasNext() ) - { - itx.next(); - if ( ! activateTextureUnit( itx.key() ) ) - return false; - if ( itx.value() == "tangents" ) - { - if ( ! mesh->transTangents.count() ) - return false; - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transTangents.data() ); - } - else if ( itx.value() == "binormals" ) - { - if ( ! mesh->transBinormals.count() ) - return false; - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transBinormals.data() ); - } - else - { - int txid = TexturingProperty::getId( itx.value() ); - if ( txid < 0 ) - return false; - int set = texprop->coordSet( txid ); - if ( set >= 0 && set < mesh->coords.count() && mesh->coords[set].count() ) - { - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 2, GL_FLOAT, 0, mesh->coords[set].data() ); - } - else - return false; - } - } - - // setup lighting - - glEnable( GL_LIGHTING ); - - // setup blending - - glProperty( props.get< AlphaProperty >() ); - - // setup vertex colors - - //glProperty( props.get< VertexColorProperty >(), glIsEnabled( GL_COLOR_ARRAY ) ); - glDisable( GL_COLOR_MATERIAL ); - - // setup material - - glProperty( props.get< MaterialProperty >(), props.get< SpecularProperty >() ); - - // setup z buffer - - glProperty( props.get< ZBufferProperty >() ); - - // setup stencil - - glProperty( props.get< StencilProperty >() ); - - // wireframe ? - - glProperty( props.get< WireframeProperty >() ); - - return true; -} - -void Renderer::setupFixedFunction( Mesh * mesh, const PropertyList & props ) -{ - // setup lighting - - glEnable( GL_LIGHTING ); - - // setup blending - - glProperty( props.get< AlphaProperty >() ); - - // setup vertex colors - - glProperty( props.get< VertexColorProperty >(), glIsEnabled( GL_COLOR_ARRAY ) ); - - // setup material - - glProperty( props.get< MaterialProperty >(), props.get< SpecularProperty >() ); - - // setup texturing - - //glProperty( props.get< TexturingProperty >() ); - - // setup z buffer - - glProperty( props.get< ZBufferProperty >() ); - - // setup stencil - - glProperty( props.get< StencilProperty >() ); - - // wireframe ? - - glProperty( props.get< WireframeProperty >() ); - - // normalize - - if ( glIsEnabled( GL_NORMAL_ARRAY ) ) - glEnable( GL_NORMALIZE ); - else - glDisable( GL_NORMALIZE ); - - // setup texturing - - if ( TexturingProperty * texprop = props.get< TexturingProperty >() ) - { // standard multi texturing property - int stage = 0; - - if ( texprop->bind( 1, mesh->coords, stage ) ) - { // dark - stage++; - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA ); - - glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); - } - - if ( texprop->bind( 0, mesh->coords, stage ) ) - { // base - stage++; - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA ); - - glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); - } - - if ( texprop->bind( 2, mesh->coords, stage ) ) - { // detail - stage++; - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA ); - - glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2.0 ); - } - - if ( texprop->bind( 6, mesh->coords, stage ) ) - { // decal 0 - stage++; - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); - - glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); - } - - if ( texprop->bind( 7, mesh->coords, stage ) ) - { // decal 1 - stage++; - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); - - glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); - } - - if ( texprop->bind( 4, mesh->coords, stage ) ) - { // glow - stage++; - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); - - glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE ); - glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); - glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); - - glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); - } - } - else if ( TextureProperty * texprop = props.get< TextureProperty >() ) - { // old single texture property - texprop->bind( mesh->coords ); - } - else - { - glDisable( GL_TEXTURE_2D ); - } -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "renderer.h" + +#include +#include +#include +#include +#include + +#include + +#include "gltex.h" +#include "glmesh.h" +#include "glscene.h" +#include "glproperty.h" +#include "options.h" + +// GL_ARB_shader_objects +PFNGLCREATEPROGRAMOBJECTARBPROC _glCreateProgramObjectARB = NULL; +PFNGLDELETEOBJECTARBPROC _glDeleteObjectARB = NULL; +PFNGLUSEPROGRAMOBJECTARBPROC _glUseProgramObjectARB = NULL; +PFNGLCREATESHADEROBJECTARBPROC _glCreateShaderObjectARB = NULL; +PFNGLSHADERSOURCEARBPROC _glShaderSourceARB = NULL; +PFNGLCOMPILESHADERARBPROC _glCompileShaderARB = NULL; +PFNGLGETOBJECTPARAMETERIVARBPROC _glGetObjectParameterivARB = NULL; +PFNGLATTACHOBJECTARBPROC _glAttachObjectARB = NULL; +PFNGLGETINFOLOGARBPROC _glGetInfoLogARB = NULL; +PFNGLLINKPROGRAMARBPROC _glLinkProgramARB = NULL; +PFNGLGETUNIFORMLOCATIONARBPROC _glGetUniformLocationARB = NULL; +PFNGLUNIFORM4FARBPROC _glUniform4fARB = NULL; +PFNGLUNIFORM1IARBPROC _glUniform1iARB = NULL; + +bool shader_initialized = false; +bool shader_ready = false; + +bool Renderer::initialize( const QGLContext * cx ) +{ + if ( shader_initialized ) + return shader_ready; + + shader_initialized = true; + + QString extensions( (const char *) glGetString(GL_EXTENSIONS) ); + + if ( ! extensions.contains( "GL_ARB_shading_language_100" ) || ! extensions.contains( "GL_ARB_shader_objects" ) + || ! extensions.contains( "GL_ARB_vertex_shader" ) || ! extensions.contains( "GL_ARB_fragment_shader" ) ) + { + return false; + } + + _glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC) cx->getProcAddress("glCreateProgramObjectARB"); + _glDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC) cx->getProcAddress("glDeleteObjectARB"); + _glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC) cx->getProcAddress("glUseProgramObjectARB"); + _glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC) cx->getProcAddress("glCreateShaderObjectARB"); + _glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC) cx->getProcAddress("glShaderSourceARB"); + _glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC) cx->getProcAddress("glCompileShaderARB"); + _glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC) cx->getProcAddress("glGetObjectParameterivARB"); + _glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC) cx->getProcAddress("glAttachObjectARB"); + _glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC) cx->getProcAddress("glGetInfoLogARB"); + _glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC) cx->getProcAddress("glLinkProgramARB"); + _glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC) cx->getProcAddress("glGetUniformLocationARB"); + _glUniform4fARB = (PFNGLUNIFORM4FARBPROC) cx->getProcAddress("glUniform4fARB"); + _glUniform1iARB = (PFNGLUNIFORM1IARBPROC) cx->getProcAddress("glUniform1iARB"); + + if( !_glCreateProgramObjectARB || !_glDeleteObjectARB || !_glUseProgramObjectARB || + !_glCreateShaderObjectARB || !_glShaderSourceARB || !_glCompileShaderARB || + !_glGetObjectParameterivARB || !_glAttachObjectARB || !_glGetInfoLogARB || + !_glLinkProgramARB || !_glGetUniformLocationARB || !_glUniform4fARB || + !_glUniform1iARB ) + { + return false; + } + + shader_ready = true; + return true; +} + +bool Renderer::hasShaderSupport() +{ + return shader_ready; +} + +QHash Renderer::ConditionSingle::compStrs; + +Renderer::ConditionSingle::ConditionSingle( const QString & line, bool neg ) : invert( neg ) +{ + if ( compStrs.isEmpty() ) + { + compStrs.insert( EQ, " == " ); + compStrs.insert( NE, " != " ); + compStrs.insert( LE, " <= " ); + compStrs.insert( GE, " >= " ); + compStrs.insert( LT, " < " ); + compStrs.insert( GT, " > " ); + compStrs.insert( AND, " & " ); + } + + QHashIterator i( compStrs ); + int pos = -1; + while ( i.hasNext() ) + { + i.next(); + pos = line.indexOf( i.value() ); + if ( pos > 0 ) + break; + } + + if ( pos > 0 ) + { + left = line.left( pos ).trimmed(); + right = line.right( line.length() - pos - i.value().length() ).trimmed(); + if ( right.startsWith( "\"" ) && right.endsWith( "\"" ) ) + right = right.mid( 1, right.length() - 2 ); + comp = i.key(); + } + else + { + left = line; + comp = None; + } +} + +QModelIndex Renderer::ConditionSingle::getIndex( const NifModel * nif, const QList & iBlocks, QString blkid ) const +{ + QString childid; + int pos = blkid.indexOf( "/" ); + if ( pos > 0 ) + { + childid = blkid.right( blkid.length() - pos - 1 ); + blkid = blkid.left( pos ); + } + foreach ( QModelIndex iBlock, iBlocks ) + { + if ( nif->inherits( iBlock, blkid ) ) + { + if ( childid.isEmpty() ) + return iBlock; + else + return nif->getIndex( iBlock, childid ); + } + } + return QModelIndex(); +} + +bool Renderer::ConditionSingle::eval( const NifModel * nif, const QList & iBlocks ) const +{ + QModelIndex iLeft = getIndex( nif, iBlocks, left ); + if ( ! iLeft.isValid() ) + return invert; + if ( comp == None ) + return ! invert; + NifValue val = nif->getValue( iLeft ); + if ( val.isString() ) + return compare( val.toString(), right ) ^ invert; + else if ( val.isCount() ) + return compare( val.toCount(), right.toUInt() ) ^ invert; + else if ( val.isFloat() ) + return compare( val.toFloat(), (float) right.toDouble() ) ^ invert; + else + return false; +} + +bool Renderer::ConditionGroup::eval( const NifModel * nif, const QList & iBlocks ) const +{ + if ( conditions.isEmpty() ) + return true; + + if ( isOrGroup() ) + { + foreach ( Condition * cond, conditions ) + if ( cond->eval( nif, iBlocks ) ) + return true; + return false; + } + else + { + foreach ( Condition * cond, conditions ) + if ( ! cond->eval( nif, iBlocks ) ) + return false; + return true; + } +} + +void Renderer::ConditionGroup::addCondition( Condition * c ) +{ + conditions.append( c ); +} + +Renderer::Shader::Shader( const QString & n, GLenum t ) : name( n ), id( 0 ), status( false ), type( t ) +{ + id = _glCreateShaderObjectARB( type ); +} + +Renderer::Shader::~Shader() +{ + if ( id ) + _glDeleteObjectARB( id ); +} + +bool Renderer::Shader::load( const QString & filepath ) +{ + try + { + QFile file( filepath ); + if ( ! file.open( QIODevice::ReadOnly ) ) + throw QString( "couldn't open %1 for read access" ).arg( filepath ); + + QByteArray data = file.readAll(); + + const char * src = data.constData(); + + _glShaderSourceARB( id, 1, & src, 0 ); + _glCompileShaderARB( id ); + + GLint result; + _glGetObjectParameterivARB( id, GL_OBJECT_COMPILE_STATUS_ARB, & result ); + + if ( result != GL_TRUE ) + { + int logLen; + _glGetObjectParameterivARB( id, GL_OBJECT_INFO_LOG_LENGTH_ARB, & logLen ); + char * log = new char[ logLen ]; + _glGetInfoLogARB( id, logLen, 0, log ); + QString errlog( log ); + delete[] log; + throw errlog; + } + } + catch ( QString err ) + { + status = false; + qWarning() << "error loading shader" << name << ":\r\n" << err.toAscii().data(); + return false; + } + status = true; + return true; +} + +Renderer::Program::Program( const QString & n ) : name( n ), id( 0 ), status( false ) +{ + id = _glCreateProgramObjectARB(); +} + +Renderer::Program::~Program() +{ + if ( id ) + _glDeleteObjectARB( id ); +} + +bool Renderer::Program::load( const QString & filepath, Renderer * renderer ) +{ + try + { + QFile file( filepath ); + if ( ! file.open( QIODevice::ReadOnly ) ) + throw QString( "couldn't open %1 for read access" ).arg( filepath ); + + QTextStream stream( &file ); + + QStack chkgrps; + chkgrps.push( & conditions ); + + while ( ! stream.atEnd() ) + { + QString line = stream.readLine().trimmed(); + + if ( line.startsWith( "shaders" ) ) + { + QStringList list = line.simplified().split( " " ); + for ( int i = 1; i < list.count(); i++ ) + { + Shader * shader = renderer->shaders.value( list[ i ] ); + if ( shader ) + { + if ( shader->status ) + _glAttachObjectARB( id, shader->id ); + else + throw QString( "depends on shader %1 which was not compiled successful" ).arg( list[ i ] ); + } + else + throw QString( "shader %1 not found" ).arg( list[ i ] ); + } + } + else if ( line.startsWith( "checkgroup" ) ) + { + QStringList list = line.simplified().split( " " ); + if ( list.value( 1 ) == "begin" ) + { + ConditionGroup * group = new ConditionGroup( list.value( 2 ) == "or" ); + chkgrps.top()->addCondition( group ); + chkgrps.push( group ); + } + else if ( list.value( 1 ) == "end" ) + { + if ( chkgrps.count() > 1 ) + chkgrps.pop(); + else + throw QString( "mismatching checkgroup end tag" ); + } + else + throw QString( "expected begin or end after checkgroup" ); + } + else if ( line.startsWith( "check" ) ) + { + line = line.remove( 0, 5 ).trimmed(); + + bool invert = false; + if ( line.startsWith ( "not " ) ) + { + invert = true; + line = line.remove( 0, 4 ).trimmed(); + } + + chkgrps.top()->addCondition( new ConditionSingle( line, invert ) ); + } + else if ( line.startsWith( "texcoords" ) ) + { + line = line.remove( 0, 9 ).simplified(); + QStringList list = line.split( " " ); + bool ok; + int unit = list.value( 0 ).toInt( & ok ); + QString id = list.value( 1 ).toLower(); + if ( ! ok || id.isEmpty() ) + throw QString( "malformed texcoord tag" ); + if ( id != "tangents" && id != "binormals" && TexturingProperty::getId( id ) < 0 ) + throw QString( "texcoord tag referres to unknown texture id '%1'" ).arg( id ); + if ( texcoords.contains( unit ) ) + throw QString( "texture unit %1 is assigned twiced" ).arg( unit ); + texcoords.insert( unit, id ); + } + } + + _glLinkProgramARB( id ); + + int result; + _glGetObjectParameterivARB( id, GL_OBJECT_LINK_STATUS_ARB, & result ); + + if ( result != GL_TRUE ) + { + int logLen; + _glGetObjectParameterivARB( id, GL_OBJECT_INFO_LOG_LENGTH_ARB, & logLen ); + char * log = new char[ logLen ]; + _glGetInfoLogARB( id, logLen, 0, log ); + QString errlog( log ); + delete[] log; + throw errlog; + } + } + catch ( QString x ) + { + status = false; + qWarning() << "error loading shader program " << name << ":\r\n" << x.toAscii().data(); + return false; + } + status = true; + return true; +} + +Renderer::Renderer() +{ +} + +Renderer::~Renderer() +{ + releaseShaders(); +} + +void Renderer::updateShaders() +{ + if ( ! shader_ready ) + return; + + releaseShaders(); + + QDir dir( QApplication::applicationDirPath() ); + if ( dir.exists( "shaders" ) ) + dir.cd( "shaders" ); + else if ( dir.exists( "/usr/share/nifskope/shaders" ) ) + dir.cd( "/usr/share/nifskope/shaders" ); + +// linux does not want to load the shaders so disable them for now +#ifdef WIN32 + dir.setNameFilters( QStringList() << "*.vert" ); + foreach ( QString name, dir.entryList() ) + { + Shader * shader = new Shader( name, GL_VERTEX_SHADER_ARB ); + shader->load( dir.filePath( name ) ); + shaders.insert( name, shader ); + } + + dir.setNameFilters( QStringList() << "*.frag" ); + foreach ( QString name, dir.entryList() ) + { + Shader * shader = new Shader( name, GL_FRAGMENT_SHADER_ARB ); + shader->load( dir.filePath( name ) ); + shaders.insert( name, shader ); + } + + dir.setNameFilters( QStringList() << "*.prog" ); + foreach ( QString name, dir.entryList() ) + { + Program * program = new Program( name ); + program->load( dir.filePath( name ), this ); + programs.insert( name, program ); + } +#endif +} + +void Renderer::releaseShaders() +{ + if ( ! shader_ready ) + return; + + qDeleteAll( programs ); + programs.clear(); + qDeleteAll( shaders ); + shaders.clear(); +} + +QString Renderer::setupProgram( Mesh * mesh, const QString & hint ) +{ + PropertyList props; + mesh->activeProperties( props ); + + if ( ! shader_ready || ! Options::shaders() ) + { + setupFixedFunction( mesh, props ); + return QString( "fixed function pipeline" ); + } + + QList iBlocks; + iBlocks << mesh->index(); + iBlocks << mesh->iData; + foreach ( Property * p, props.list() ) + iBlocks.append( p->index() ); + + if ( ! hint.isEmpty() ) + { + Program * program = programs.value( hint ); + if ( program && program->status && setupProgram( program, mesh, props, iBlocks ) ) + return hint; + } + + foreach ( Program * program, programs ) + { + if ( program->status && setupProgram( program, mesh, props, iBlocks ) ) + return program->name; + } + + stopProgram(); + setupFixedFunction( mesh, props ); + return QString( "fixed function pipeline" ); +} + +void Renderer::stopProgram() +{ + if ( shader_ready ) + _glUseProgramObjectARB( 0 ); + resetTextureUnits(); +} + +bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & props, const QList & iBlocks ) +{ + const NifModel * nif = qobject_cast( mesh->index().model() ); + if ( ! mesh->index().isValid() || ! nif ) + return false; + + if ( ! prog->conditions.eval( nif, iBlocks ) ) + return false; + + _glUseProgramObjectARB( prog->id ); + + // texturing + + TexturingProperty * texprop = props.get< TexturingProperty >(); + + int texunit = 0; + + GLint uniBaseMap = _glGetUniformLocationARB( prog->id, "BaseMap" ); + + if ( uniBaseMap >= 0 ) + { + if ( ! texprop || ! activateTextureUnit( texunit ) || ! texprop->bind( 0 ) ) + return false; + + _glUniform1iARB( uniBaseMap, texunit++ ); + } + + GLint uniNormalMap = _glGetUniformLocationARB( prog->id, "NormalMap" ); + + if ( uniNormalMap >= 0 ) + { + QString fname = texprop->fileName( 0 ); + if ( fname.isEmpty() ) + return false; + + int pos = fname.indexOf( "_" ); + if ( pos >= 0 ) + fname = fname.left( pos ) + "_n.dds"; + else if ( ( pos = fname.lastIndexOf( "." ) ) >= 0 ) + fname = fname.insert( pos, "_n" ); + + if ( ! activateTextureUnit( texunit ) || ! texprop->bind( 0, fname ) ) + return false; + + _glUniform1iARB( uniNormalMap, texunit++ ); + } + + GLint uniGlowMap = _glGetUniformLocationARB( prog->id, "GlowMap" ); + + if ( uniGlowMap >= 0 ) + { + QString fname = texprop->fileName( 0 ); + if ( fname.isEmpty() ) + return false; + + int pos = fname.indexOf( "_" ); + if ( pos >= 0 ) + fname = fname.left( pos ) + "_g.dds"; + else if ( ( pos = fname.lastIndexOf( "." ) ) >= 0 ) + fname = fname.insert( pos, "_g" ); + + if ( ! activateTextureUnit( texunit ) || ! texprop->bind( 0, fname ) ) + return false; + + _glUniform1iARB( uniGlowMap, texunit++ ); + } + else + { + QString fname = texprop->fileName( 0 ); + if ( ! fname.isEmpty() ) + { + int pos = fname.indexOf( "_" ); + if ( pos >= 0 ) + fname = fname.left( pos ) + "_g.dds"; + else if ( ( pos = fname.lastIndexOf( "." ) ) >= 0 ) + fname = fname.insert( pos, "_g" ); + + if ( activateTextureUnit( texunit ) && texprop->bind( 0, fname ) ) + return false; + } + } + + QMapIterator itx( prog->texcoords ); + while ( itx.hasNext() ) + { + itx.next(); + if ( ! activateTextureUnit( itx.key() ) ) + return false; + if ( itx.value() == "tangents" ) + { + if ( ! mesh->transTangents.count() ) + return false; + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transTangents.data() ); + } + else if ( itx.value() == "binormals" ) + { + if ( ! mesh->transBinormals.count() ) + return false; + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transBinormals.data() ); + } + else + { + int txid = TexturingProperty::getId( itx.value() ); + if ( txid < 0 ) + return false; + int set = texprop->coordSet( txid ); + if ( set >= 0 && set < mesh->coords.count() && mesh->coords[set].count() ) + { + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 2, GL_FLOAT, 0, mesh->coords[set].data() ); + } + else + return false; + } + } + + // setup lighting + + glEnable( GL_LIGHTING ); + + // setup blending + + glProperty( props.get< AlphaProperty >() ); + + // setup vertex colors + + //glProperty( props.get< VertexColorProperty >(), glIsEnabled( GL_COLOR_ARRAY ) ); + glDisable( GL_COLOR_MATERIAL ); + + // setup material + + glProperty( props.get< MaterialProperty >(), props.get< SpecularProperty >() ); + + // setup z buffer + + glProperty( props.get< ZBufferProperty >() ); + + // setup stencil + + glProperty( props.get< StencilProperty >() ); + + // wireframe ? + + glProperty( props.get< WireframeProperty >() ); + + return true; +} + +void Renderer::setupFixedFunction( Mesh * mesh, const PropertyList & props ) +{ + // setup lighting + + glEnable( GL_LIGHTING ); + + // setup blending + + glProperty( props.get< AlphaProperty >() ); + + // setup vertex colors + + glProperty( props.get< VertexColorProperty >(), glIsEnabled( GL_COLOR_ARRAY ) ); + + // setup material + + glProperty( props.get< MaterialProperty >(), props.get< SpecularProperty >() ); + + // setup texturing + + //glProperty( props.get< TexturingProperty >() ); + + // setup z buffer + + glProperty( props.get< ZBufferProperty >() ); + + // setup stencil + + glProperty( props.get< StencilProperty >() ); + + // wireframe ? + + glProperty( props.get< WireframeProperty >() ); + + // normalize + + if ( glIsEnabled( GL_NORMAL_ARRAY ) ) + glEnable( GL_NORMALIZE ); + else + glDisable( GL_NORMALIZE ); + + // setup texturing + + if ( TexturingProperty * texprop = props.get< TexturingProperty >() ) + { // standard multi texturing property + int stage = 0; + + if ( texprop->bind( 1, mesh->coords, stage ) ) + { // dark + stage++; + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA ); + + glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); + } + + if ( texprop->bind( 0, mesh->coords, stage ) ) + { // base + stage++; + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA ); + + glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); + } + + if ( texprop->bind( 2, mesh->coords, stage ) ) + { // detail + stage++; + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA ); + + glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2.0 ); + } + + if ( texprop->bind( 6, mesh->coords, stage ) ) + { // decal 0 + stage++; + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); + + glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); + } + + if ( texprop->bind( 7, mesh->coords, stage ) ) + { // decal 1 + stage++; + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_ALPHA ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); + + glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); + } + + if ( texprop->bind( 4, mesh->coords, stage ) ) + { // glow + stage++; + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + + glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE ); + glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); + glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); + + glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0 ); + } + } + else if ( TextureProperty * texprop = props.get< TextureProperty >() ) + { // old single texture property + texprop->bind( mesh->coords ); + } + else + { + glDisable( GL_TEXTURE_2D ); + } +} + diff --git a/gl/renderer.h b/gl/renderer.h index 8eeb58279..abd7feeda 100644 --- a/gl/renderer.h +++ b/gl/renderer.h @@ -1,205 +1,205 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLSHADER_H -#define GLSHADER_H - -#include - -#include "nifmodel.h" - -class Mesh; -class PropertyList; - -class Renderer -{ -public: - Renderer(); - ~Renderer(); - - static bool initialize( const QGLContext * ); - static bool hasShaderSupport(); - - void updateShaders(); - void releaseShaders(); - - QString setupProgram( Mesh *, const QString & hint = QString() ); - void stopProgram(); - -protected: - class Condition - { - public: - Condition() {} - virtual ~Condition() {} - - virtual bool eval( const NifModel * nif, const QList & iBlocks ) const = 0; - }; - - class ConditionSingle : public Condition - { - public: - ConditionSingle( const QString & line, bool neg = false ); - - bool eval( const NifModel * nif, const QList & iBlocks ) const; - - protected: - QString left, right; - enum Type - { - None, EQ, NE, LE, GE, LT, GT, AND - }; - Type comp; - static QHash compStrs; - - bool invert; - - QModelIndex getIndex( const NifModel * nif, const QList & iBlock, QString name ) const; - template bool compare( T a, T b ) const; - }; - - class ConditionGroup : public Condition - { - public: - ConditionGroup( bool o = false ) { _or = o; } - ~ConditionGroup() { qDeleteAll( conditions ); } - - bool eval( const NifModel * nif, const QList & iBlocks ) const; - - void addCondition( Condition * c ); - - bool isOrGroup() const { return _or; } - - protected: - QList conditions; - bool _or; - }; - - class Shader - { - public: - Shader( const QString & name, GLenum type ); - ~Shader(); - - bool load( const QString & filepath ); - - QString name; - GLuint id; - bool status; - - protected: - GLenum type; - }; - - class Program - { - public: - Program( const QString & name ); - ~Program(); - - bool load( const QString & filepath, Renderer * ); - - QString name; - GLuint id; - bool status; - - ConditionGroup conditions; - QMap texcoords; - }; - - QMap shaders; - QMap programs; - - friend class Program; - - bool setupProgram( Program *, Mesh *, const PropertyList &, const QList & iBlocks ); - void setupFixedFunction( Mesh *, const PropertyList & ); -}; - -template inline bool Renderer::ConditionSingle::compare( T a, T b ) const -{ - switch ( comp ) - { - case EQ: - return a == b; - case NE: - return a != b; - case LE: - return a <= b; - case GE: - return a >= b; - case LT: - return a < b; - case GT: - return a > b; - case AND: - return a & b; - default: - return true; - } -} - -template <> inline bool Renderer::ConditionSingle::compare( float a, float b ) const -{ - switch ( comp ) - { - case EQ: - return a == b; - case NE: - return a != b; - case LE: - return a <= b; - case GE: - return a >= b; - case LT: - return a < b; - case GT: - return a > b; - default: - return true; - } -} - -template <> inline bool Renderer::ConditionSingle::compare( QString a, QString b ) const -{ - switch ( comp ) - { - case EQ: - return a == b; - case NE: - return a != b; - default: - return false; - } -} - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLSHADER_H +#define GLSHADER_H + +#include + +#include "nifmodel.h" + +class Mesh; +class PropertyList; + +class Renderer +{ +public: + Renderer(); + ~Renderer(); + + static bool initialize( const QGLContext * ); + static bool hasShaderSupport(); + + void updateShaders(); + void releaseShaders(); + + QString setupProgram( Mesh *, const QString & hint = QString() ); + void stopProgram(); + +protected: + class Condition + { + public: + Condition() {} + virtual ~Condition() {} + + virtual bool eval( const NifModel * nif, const QList & iBlocks ) const = 0; + }; + + class ConditionSingle : public Condition + { + public: + ConditionSingle( const QString & line, bool neg = false ); + + bool eval( const NifModel * nif, const QList & iBlocks ) const; + + protected: + QString left, right; + enum Type + { + None, EQ, NE, LE, GE, LT, GT, AND + }; + Type comp; + static QHash compStrs; + + bool invert; + + QModelIndex getIndex( const NifModel * nif, const QList & iBlock, QString name ) const; + template bool compare( T a, T b ) const; + }; + + class ConditionGroup : public Condition + { + public: + ConditionGroup( bool o = false ) { _or = o; } + ~ConditionGroup() { qDeleteAll( conditions ); } + + bool eval( const NifModel * nif, const QList & iBlocks ) const; + + void addCondition( Condition * c ); + + bool isOrGroup() const { return _or; } + + protected: + QList conditions; + bool _or; + }; + + class Shader + { + public: + Shader( const QString & name, GLenum type ); + ~Shader(); + + bool load( const QString & filepath ); + + QString name; + GLuint id; + bool status; + + protected: + GLenum type; + }; + + class Program + { + public: + Program( const QString & name ); + ~Program(); + + bool load( const QString & filepath, Renderer * ); + + QString name; + GLuint id; + bool status; + + ConditionGroup conditions; + QMap texcoords; + }; + + QMap shaders; + QMap programs; + + friend class Program; + + bool setupProgram( Program *, Mesh *, const PropertyList &, const QList & iBlocks ); + void setupFixedFunction( Mesh *, const PropertyList & ); +}; + +template inline bool Renderer::ConditionSingle::compare( T a, T b ) const +{ + switch ( comp ) + { + case EQ: + return a == b; + case NE: + return a != b; + case LE: + return a <= b; + case GE: + return a >= b; + case LT: + return a < b; + case GT: + return a > b; + case AND: + return a & b; + default: + return true; + } +} + +template <> inline bool Renderer::ConditionSingle::compare( float a, float b ) const +{ + switch ( comp ) + { + case EQ: + return a == b; + case NE: + return a != b; + case LE: + return a <= b; + case GE: + return a >= b; + case LT: + return a < b; + case GT: + return a > b; + default: + return true; + } +} + +template <> inline bool Renderer::ConditionSingle::compare( QString a, QString b ) const +{ + switch ( comp ) + { + case EQ: + return a == b; + case NE: + return a != b; + default: + return false; + } +} + +#endif diff --git a/glview.cpp b/glview.cpp index 477f766f5..ed866ac01 100644 --- a/glview.cpp +++ b/glview.cpp @@ -1,1262 +1,1262 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "glview.h" - -#include -#include -#include -#include -#include -#include -#include - - -#include - -#include "nifmodel.h" -#include "gl/glscene.h" -#include "gl/gltex.h" -#include "options.h" - -#include "widgets/floatedit.h" -#include "widgets/floatslider.h" - -#define FPS 25 - -#define FOV 45.0 - -#define MOV_SPD 350 -#define ROT_SPD 45 - -#define ZOOM_MIN 1.0 -#define ZOOM_MAX 1000.0 - -#ifndef M_PI -#define M_PI 3.1415926535897932385 -#endif - -GLView * GLView::create() -{ - static QList< QPointer > views; - - QGLWidget * share = 0; - foreach ( QPointer v, views ) - if ( v ) share = v; - - QGLFormat fmt; - if ( share ) - fmt = share->format(); - else - fmt.setSampleBuffers( Options::antialias() ); - - views.append( QPointer( new GLView( fmt, share ) ) ); - - return views.last(); -} - -GLView::GLView( const QGLFormat & format, const QGLWidget * shareWidget ) - : QGLWidget( format, 0, shareWidget ) -{ - setFocusPolicy( Qt::ClickFocus ); - setAttribute( Qt::WA_NoSystemBackground ); - setAcceptDrops( true ); - //setContextMenuPolicy( Qt::CustomContextMenu ); - - Zoom = 1.0; - zInc = 1; - - doCenter = false; - doCompile = false; - - model = 0; - - time = 0.0; - lastTime = QTime::currentTime(); - - fpsact = 0.0; - fpsacc = 0.0; - fpscnt = 0; - - textures = new TexCache( this ); - - scene = new Scene( textures ); - connect( textures, SIGNAL( sigRefresh() ), this, SLOT( update() ) ); - - timer = new QTimer(this); - timer->setInterval( 1000 / FPS ); - timer->start(); - connect( timer, SIGNAL( timeout() ), this, SLOT( advanceGears() ) ); - - - grpView = new QActionGroup( this ); - grpView->setExclusive( false ); - connect( grpView, SIGNAL( triggered( QAction * ) ), this, SLOT( viewAction( QAction * ) ) ); - - aViewTop = new QAction( QIcon( ":/btn/viewTop" ), tr("Top"), grpView ); - aViewTop->setToolTip( tr("View from above") ); - aViewTop->setCheckable( true ); - aViewTop->setShortcut( Qt::Key_F5 ); - grpView->addAction( aViewTop ); - - aViewFront = new QAction( QIcon( ":/btn/viewFront" ), tr("Front"), grpView ); - aViewFront->setToolTip( tr("View from the front") ); - aViewFront->setCheckable( true ); - aViewFront->setChecked( true ); - aViewFront->setShortcut( Qt::Key_F6 ); - grpView->addAction( aViewFront ); - - aViewSide = new QAction( QIcon( ":/btn/viewSide" ), tr("Side"), grpView ); - aViewSide->setToolTip( tr("View from the side") ); - aViewSide->setCheckable( true ); - aViewSide->setShortcut( Qt::Key_F7 ); - grpView->addAction( aViewSide ); - - aViewUser = new QAction( QIcon( ":/btn/viewUser" ), tr("User"), grpView ); - aViewUser->setToolTip( tr("Restore the view as it was when Save User View was activated") ); - aViewUser->setCheckable( true ); - aViewUser->setShortcut( Qt::Key_F8 ); - grpView->addAction( aViewUser ); - - - aViewWalk = new QAction( QIcon( ":/btn/viewWalk" ), tr("Walk"), grpView ); - aViewWalk->setToolTip( tr("Enable walk mode") ); - aViewWalk->setCheckable( true ); - aViewWalk->setShortcut( Qt::Key_F9 ); - grpView->addAction( aViewWalk ); - - aViewFlip = new QAction( QIcon( ":/btn/viewFlip" ), tr("Flip"), this ); - aViewFlip->setToolTip( tr("Flip View from Front to Back, Top to Bottom, Side to Other Side") ); - aViewFlip->setCheckable( true ); - aViewFlip->setShortcut( Qt::Key_F11 ); - grpView->addAction( aViewFlip ); - - aViewPerspective = new QAction( QIcon( ":/btn/viewPers" ), tr("Perspective"), this ); - aViewPerspective->setToolTip( tr("Perspective View Transformation or Orthogonal View Transformation") ); - aViewPerspective->setCheckable( true ); - aViewPerspective->setShortcut( Qt::Key_F10 ); - grpView->addAction( aViewPerspective ); - - aViewUserSave = new QAction( tr("Save User View"), this ); - aViewUserSave->setToolTip( tr("Save current view rotation, position and distance") ); - aViewUserSave->setShortcut( Qt::CTRL + Qt::Key_F9 ); - connect( aViewUserSave, SIGNAL( triggered() ), this, SLOT( sltSaveUserView() ) ); - - aAnimate = new QAction( tr("&Animations"), this ); - aAnimate->setToolTip( tr("enables evaluation of animation controllers") ); - aAnimate->setCheckable( true ); - aAnimate->setChecked( true ); - connect( aAnimate, SIGNAL( toggled( bool ) ), this, SLOT( checkActions() ) ); - addAction( aAnimate ); - - aAnimPlay = new QAction( QIcon( ":/btn/play" ), tr("&Play"), this ); - aAnimPlay->setCheckable( true ); - aAnimPlay->setChecked( true ); - connect( aAnimPlay, SIGNAL( toggled( bool ) ), this, SLOT( checkActions() ) ); - - aAnimLoop = new QAction( QIcon( ":/btn/loop" ), tr("&Loop"), this ); - aAnimLoop->setCheckable( true ); - aAnimLoop->setChecked( true ); - - aAnimSwitch = new QAction( QIcon( ":/btn/switch" ), tr("&Switch"), this ); - aAnimSwitch->setCheckable( true ); - aAnimSwitch->setChecked( true ); - - // animation tool bar - - tAnim = new QToolBar( tr("Animation") ); - tAnim->setObjectName( "AnimTool" ); - tAnim->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); - tAnim->setIconSize( QSize( 16, 16 ) ); - - connect( aAnimate, SIGNAL( toggled( bool ) ), tAnim->toggleViewAction(), SLOT( setChecked( bool ) ) ); - connect( aAnimate, SIGNAL( toggled( bool ) ), tAnim, SLOT( setVisible( bool ) ) ); - connect( tAnim->toggleViewAction(), SIGNAL( toggled( bool ) ), aAnimate, SLOT( setChecked( bool ) ) ); - - tAnim->addAction( aAnimPlay ); - - FloatSlider * sldTime = new FloatSlider( Qt::Horizontal, true, true ); - sldTime->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Maximum ); - connect( this, SIGNAL( sigTime( float, float, float ) ), sldTime, SLOT( set( float, float, float ) ) ); - connect( sldTime, SIGNAL( valueChanged( float ) ), this, SLOT( sltTime( float ) ) ); - tAnim->addWidget( sldTime ); - - FloatEdit * edtTime = new FloatEdit; - edtTime->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Maximum ); - - connect( this, SIGNAL( sigTime( float, float, float ) ), edtTime, SLOT( set( float, float, float ) ) ); - connect( edtTime, SIGNAL( sigEdited( float ) ), this, SLOT( sltTime( float ) ) ); - connect( sldTime, SIGNAL( valueChanged( float ) ), edtTime, SLOT( setValue( float ) ) ); - connect( edtTime, SIGNAL( sigEdited( float ) ), sldTime, SLOT( setValue( float ) ) ); - sldTime->addEditor( edtTime ); - - tAnim->addAction( aAnimLoop ); - - tAnim->addAction( aAnimSwitch ); - - animGroups = new QComboBox(); - animGroups->setMinimumWidth( 100 ); - connect( animGroups, SIGNAL( activated( const QString & ) ), this, SLOT( sltSequence( const QString & ) ) ); - tAnim->addWidget( animGroups ); - - connect( Options::get(), SIGNAL( sigChanged() ), textures, SLOT( flush() ) ); - connect( Options::get(), SIGNAL( sigChanged() ), this, SLOT( update() ) ); - - tView = new QToolBar( tr("View") ); - tView->setObjectName( "ViewTool" ); - tView->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); - tView->setIconSize( QSize( 16, 16 ) ); - - tView->addAction( aViewTop ); - tView->addAction( aViewFront ); - tView->addAction( aViewSide ); - tView->addAction( aViewUser ); - tView->addAction( aViewWalk ); - tView->addSeparator(); - tView->addAction( aViewFlip ); - tView->addAction( aViewPerspective ); -} - -GLView::~GLView() -{ - delete scene; -} - -QList GLView::toolbars() const -{ - return QList() << tView << tAnim; -} - -QMenu * GLView::createMenu() const -{ - QMenu * m = new QMenu( tr("&Render") ); - m->addAction( aViewTop ); - m->addAction( aViewFront ); - m->addAction( aViewSide ); - m->addAction( aViewWalk ); - m->addAction( aViewUser ); - m->addSeparator(); - m->addAction( aViewFlip ); - m->addAction( aViewPerspective ); - m->addAction( aViewUserSave ); - m->addSeparator(); - m->addActions( Options::actions() ); - return m; -} - -void GLView::updateShaders() -{ - makeCurrent(); - scene->updateShaders(); - update(); -} - - -/* - * OpenGL stuff - */ - -void GLView::initializeGL() -{ - initializeTextureUnits( context() ); - - if ( Renderer::initialize( context() ) ) - updateShaders(); - - // check for errors - - GLenum err; - while ( ( err = glGetError() ) != GL_NO_ERROR ) - qDebug() << "GL ERROR (init) : " << (const char *) gluErrorString( err ); -} - -void GLView::glProjection( int x, int y ) -{ - GLint viewport[4]; - glGetIntegerv( GL_VIEWPORT, viewport ); - GLdouble aspect = (GLdouble) viewport[2] / (GLdouble) viewport[3]; - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - if ( x >= 0 && y >= 0 ) - { - gluPickMatrix( (GLdouble) x, (GLdouble) (viewport[3]-y), 5.0f, 5.0f, viewport); - } - - BoundSphere bs = scene->view * scene->bounds(); - if ( Options::drawAxes() ) - bs |= BoundSphere( scene->view * Vector3(), axis ); - - GLdouble nr = fabs( bs.center[2] ) - bs.radius * 1.2; - GLdouble fr = fabs( bs.center[2] ) + bs.radius * 1.2; - - //qWarning() << nr << fr; - - if ( aViewPerspective->isChecked() || aViewWalk->isChecked() ) - { - if ( nr < 1.0 ) nr = 1.0; - if ( fr < 2.0 ) fr = 2.0; - GLdouble h2 = tan( ( FOV / Zoom ) / 360 * M_PI ) * nr; - GLdouble w2 = h2 * aspect; - glFrustum( - w2, + w2, - h2, + h2, nr, fr ); - } - else - { - GLdouble h2 = Dist / Zoom; - GLdouble w2 = h2 * aspect; - glOrtho( - w2, + w2, - h2, + h2, nr, fr ); - } - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); -} - -void GLView::center() -{ - doCenter = true; - update(); -} - -#ifdef USE_GL_QPAINTER -void GLView::paintEvent( QPaintEvent * event ) -{ - makeCurrent(); - - QPainter painter; - painter.begin( this ); - painter.setRenderHint( QPainter::TextAntialiasing ); -#else -void GLView::paintGL() -{ -#endif - // save gl state for later use by qpainter - - glPushAttrib(GL_ALL_ATTRIB_BITS); - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - - glViewport( 0, 0, width(), height() ); - qglClearColor( Options::bgColor() ); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); - glShadeModel( GL_SMOOTH ); - - // compile the model - - if ( doCompile ) - { - textures->setNifFolder( model->getFolder() ); - scene->make( model ); - scene->transform( Transform(), scene->timeMin() ); - axis = scene->bounds().radius * 1.4; - if ( axis == 0 ) axis = 1; - - if ( time < scene->timeMin() || time > scene->timeMax() ) - time = scene->timeMin(); - emit sigTime( time, scene->timeMin(), scene->timeMax() ); - - animGroups->clear(); - animGroups->addItems( scene->animGroups ); - animGroups->setCurrentIndex( scene->animGroups.indexOf( scene->animGroup ) ); - - doCompile = false; - } - - // center the model - - if ( doCenter ) - { - viewAction( checkedViewAction() ); - doCenter = false; - } - - // transform the scene - - Matrix ap; - - if ( Options::upAxis() == Options::YAxis ) - { - ap( 0, 0 ) = 0; ap( 0, 1 ) = 0; ap( 0, 2 ) = 1; - ap( 1, 0 ) = 1; ap( 1, 1 ) = 0; ap( 1, 2 ) = 0; - ap( 2, 0 ) = 0; ap( 2, 1 ) = 1; ap( 2, 2 ) = 0; - } - else if ( Options::upAxis() == Options::XAxis ) - { - ap( 0, 0 ) = 0; ap( 0, 1 ) = 1; ap( 0, 2 ) = 0; - ap( 1, 0 ) = 0; ap( 1, 1 ) = 0; ap( 1, 2 ) = 1; - ap( 2, 0 ) = 1; ap( 2, 1 ) = 0; ap( 2, 2 ) = 0; - } - - Transform viewTrans; - viewTrans.rotation.fromEuler( Rot[0] / 180.0 * PI, Rot[1] / 180.0 * PI, Rot[2] / 180.0 * PI ); - viewTrans.rotation = viewTrans.rotation * ap; - viewTrans.translation = viewTrans.rotation * Pos; - if ( ! aViewWalk->isChecked() ) - viewTrans.translation[2] -= Dist * 2; - - scene->transform( viewTrans, time ); - - // setup projection mode - - glProjection(); - - glLoadIdentity(); - - // draw the axis - - if ( Options::drawAxes() ) - { - glDisable( GL_ALPHA_TEST ); - glDisable( GL_BLEND ); - glDisable( GL_LIGHTING ); - glDisable( GL_COLOR_MATERIAL ); - glEnable( GL_DEPTH_TEST ); - glDepthMask( GL_TRUE ); - glDepthFunc( GL_LESS ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_NORMALIZE ); - glLineWidth( 1.2f ); - - glPushMatrix(); - glLoadMatrix( viewTrans ); - - drawAxes( Vector3(), axis ); - - glPopMatrix(); - } - - // setup light - - Vector4 lightDir( 0.0, 0.0, 1.0, 0.0 ); - if ( ! Options::lightFrontal() ) - { - float decl = Options::lightDeclination() / 180.0 * PI; - Vector3 v( sin( decl ), 0, cos( decl ) ); - Matrix m; m.fromEuler( 0, 0, Options::lightPlanarAngle() / 180.0 * PI ); - v = m * v; - lightDir = Vector4( viewTrans.rotation * v, 0.0 ); - } - glLightfv( GL_LIGHT0, GL_POSITION, lightDir.data() ); - glLightfv( GL_LIGHT0, GL_AMBIENT, Color4( Options::ambient() ).data() ); - glLightfv( GL_LIGHT0, GL_DIFFUSE, Color4( Options::diffuse() ).data() ); - glLightfv( GL_LIGHT0, GL_SPECULAR, Color4( Options::specular() ).data() ); - glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE ); - glEnable( GL_LIGHT0 ); - glEnable( GL_LIGHTING ); - - // Initialize Rendering Font - glListBase(fontDisplayListBase(QFont(), 2000)); - - // draw the model - scene->draw(); - - // restore gl state - glPopAttrib(); - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - - // check for errors - - GLenum err; - while ( ( err = glGetError() ) != GL_NO_ERROR ) - qDebug() << "GL ERROR (paint): " << (const char *) gluErrorString( err ); - - // update fps counter - - if ( fpsacc > 1.0 && fpscnt ) - { - fpsacc /= fpscnt; - if ( fpsacc > 0.0001 ) - fpsact = 1.0 / fpsacc; - else - fpsact = 10000; - fpsacc = 0; - fpscnt = 0; - } - -#ifdef USE_GL_QPAINTER - // draw text on top using QPainter - - if ( Options::benchmark() || Options::drawStats() ) - { - int ls = QFontMetrics( font() ).lineSpacing(); - int y = 1; - - painter.setPen( Options::hlColor() ); - - if ( Options::benchmark() ) - { - painter.drawText( 10, y++ * ls, QString( "FPS %1" ).arg( int( fpsact ) ) ); - y++; - } - - if ( Options::drawStats() ) - { - QString stats = scene->textStats(); - QStringList lines = stats.split( "\n" ); - foreach ( QString line, lines ) - painter.drawText( 10, y++ * ls, line ); - } - } - - painter.end(); -#endif -} - -bool compareHits( const QPair< GLuint, GLuint > & a, const QPair< GLuint, GLuint > & b ) -{ - if ( a.second < b.second ) - return true; - else - return false; -} - -typedef void (Scene::*DrawFunc)(void); - -int indexAt( GLuint *buffer, NifModel *model, Scene *scene, QList drawFunc, int cycle ) -{ - glRenderMode( GL_SELECT ); - glInitNames(); - glPushName( 0 ); - - foreach ( DrawFunc df, drawFunc ) - (scene->*df)(); - - GLint hits = glRenderMode( GL_RENDER ); - if ( hits > 0 ) - { - QList< QPair< GLuint, GLuint > > hitList; - - for ( int l = 0; l < hits; l++ ) - { - hitList << QPair< GLuint, GLuint >( buffer[ l * 4 + 3 ], buffer[ l * 4 + 1 ] ); - } - - qSort( hitList.begin(), hitList.end(), compareHits ); - - return hitList.value( cycle % hits ).first; - } - - return -1; -} - -QModelIndex GLView::indexAt( const QPoint & pos, int cycle ) -{ - if ( ! ( model && isVisible() && height() ) ) - return QModelIndex(); - - makeCurrent(); - - glPushAttrib(GL_ALL_ATTRIB_BITS); - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - - glViewport( 0, 0, width(), height() ); - glProjection( pos.x(), pos.y() ); - - GLuint buffer[512]; - glSelectBuffer( 512, buffer ); - - int choose; - if ( Options::drawFurn() ) - { - choose = ::indexAt( buffer, model, scene, QList() << &Scene::drawFurn, cycle ); - if ( choose != -1 ) - { - glPopAttrib(); - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - - QModelIndex parent = model->index( 3, 0, model->getBlock( choose&0x0ffff ) ); - return model->index( choose>>16, 0, parent ); - } - } - - QList df; - - if ( Options::drawHavok() ) - df << &Scene::drawHavok; - if ( Options::drawNodes() ) - df << &Scene::drawNodes; - df << &Scene::drawShapes; - - choose = ::indexAt( buffer, model, scene, df, cycle ); - - glPopAttrib(); - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - - if ( choose != -1 ) - return model->getBlock( choose ); - - return QModelIndex(); -} - -void GLView::resizeGL(int width, int height) -{ - glViewport(0, 0, width, height ); -} - - -/* - * NifModel stuff - */ - -void GLView::setNif( NifModel * nif ) -{ - if ( model ) - { - disconnect( model ); - } - model = nif; - if ( model ) - { - connect( model, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( dataChanged( const QModelIndex &, const QModelIndex & ) ) ); - connect( model, SIGNAL( linksChanged() ), this, SLOT( modelLinked() ) ); - connect( model, SIGNAL( modelReset() ), this, SLOT( modelChanged() ) ); - connect( model, SIGNAL( destroyed() ), this, SLOT( modelDestroyed() ) ); - } - doCompile = true; -} - -void GLView::setCurrentIndex( const QModelIndex & index ) -{ - if ( ! ( model && index.model() == model ) ) - return; - - scene->currentBlock = model->getBlock( index ); - scene->currentIndex = index.sibling( index.row(), 0 ); - - update(); -} - -QModelIndex parent( QModelIndex ix, QModelIndex xi ) -{ - ix = ix.sibling( ix.row(), 0 ); - xi = xi.sibling( xi.row(), 0 ); - - while ( ix.isValid() ) - { - QModelIndex x = xi; - while ( x.isValid() ) - { - if ( ix == x ) return ix; - x = x.parent(); - } - ix = ix.parent(); - } - return QModelIndex(); -} - -void GLView::dataChanged( const QModelIndex & idx, const QModelIndex & xdi ) -{ - if ( doCompile ) - return; - - QModelIndex ix = idx; - - if ( idx == xdi ) - { - if ( idx.column() != 0 ) - ix = idx.sibling( idx.row(), 0 ); - } - else - { - ix = ::parent( idx, xdi ); - } - - if ( ix.isValid() ) - { - scene->update( model, idx ); - update(); - } - else - modelChanged(); -} - -void GLView::modelChanged() -{ - doCompile = true; - doCenter = true; - update(); -} - -void GLView::modelLinked() -{ - doCompile = true; //scene->update( model, QModelIndex() ); - update(); -} - -void GLView::modelDestroyed() -{ - setNif( 0 ); -} - - -void GLView::sltTime( float t ) -{ - time = t; - update(); -} - -void GLView::sltSequence( const QString & seqname ) -{ - animGroups->setCurrentIndex( scene->animGroups.indexOf( seqname ) ); - scene->setSequence( seqname ); - time = scene->timeMin(); - emit sigTime( time, scene->timeMin(), scene->timeMax() ); - update(); -} - - - -void GLView::viewAction( QAction * act ) -{ - BoundSphere bs = scene->bounds(); - if ( Options::drawAxes() ) - bs |= BoundSphere( Vector3(), axis ); - - if ( bs.radius < 1 ) bs.radius = 1; - setDistance( bs.radius ); - setZoom( 1.0 ); - - if ( act == aViewWalk ) - { - setRotation( -90, 0, 0 ); - setPosition( Vector3() - scene->bounds().center ); - setZoom( 1.0 ); - aViewWalk->setChecked( true ); - aViewTop->setChecked( false ); - aViewFront->setChecked( false ); - aViewSide->setChecked( false ); - aViewUser->setChecked( false ); - } - - if ( ! act || act == aViewFlip ) - { - act = checkedViewAction(); - } - - if ( act != aViewWalk ) - setPosition( Vector3() - bs.center ); - - if ( act == aViewTop ) - { - if ( aViewFlip->isChecked() ) - setRotation( 180, 0, 0 ); - else - setRotation( 0, 0, 0 ); - aViewWalk->setChecked( false ); - aViewTop->setChecked( true ); - aViewFront->setChecked( false ); - aViewSide->setChecked( false ); - aViewUser->setChecked( false ); - } - else if ( act == aViewFront ) - { - if ( aViewFlip->isChecked() ) - setRotation( -90, 0, 180 ); - else - setRotation( -90, 0, 0 ); - aViewWalk->setChecked( false ); - aViewTop->setChecked( false ); - aViewFront->setChecked( true ); - aViewSide->setChecked( false ); - aViewUser->setChecked( false ); - } - else if ( act == aViewSide ) - { - if ( aViewFlip->isChecked() ) - setRotation( - 90, 0, - 90 ); - else - setRotation( - 90, 0, 90 ); - aViewWalk->setChecked( false ); - aViewTop->setChecked( false ); - aViewFront->setChecked( false ); - aViewSide->setChecked( true ); - aViewUser->setChecked( false ); - } - else if ( act == aViewUser ) - { - QSettings cfg; - cfg.beginGroup( "GLView" ); - cfg.beginGroup( "User View" ); - setRotation( cfg.value( "RotX" ).toDouble(), cfg.value( "RotY" ).toDouble(), cfg.value( "RotZ" ).toDouble() ); - setPosition( cfg.value( "PosX" ).toDouble(), cfg.value( "PosY" ).toDouble(), cfg.value( "PosZ" ).toDouble() ); - setDistance( cfg.value( "Dist" ).toDouble() ); - aViewWalk->setChecked( false ); - aViewTop->setChecked( false ); - aViewFront->setChecked( false ); - aViewSide->setChecked( false ); - aViewUser->setChecked( true ); - } - update(); -} - -void GLView::sltSaveUserView() -{ - QSettings cfg; - cfg.beginGroup( "GLView" ); - cfg.beginGroup( "User View" ); - cfg.setValue( "RotX", Rot[0] ); - cfg.setValue( "RotY", Rot[1] ); - cfg.setValue( "RotZ", Rot[2] ); - cfg.setValue( "PosX", Pos[0] ); - cfg.setValue( "PosY", Pos[1] ); - cfg.setValue( "PosZ", Pos[2] ); - cfg.setValue( "Dist", Dist ); - viewAction( aViewUser ); -} - -QAction * GLView::checkedViewAction() const -{ - if ( aViewTop->isChecked() ) - return aViewTop; - else if ( aViewFront->isChecked() ) - return aViewFront; - else if ( aViewSide->isChecked() ) - return aViewSide; - else if ( aViewWalk->isChecked() ) - return aViewWalk; - else if ( aViewUser->isChecked() ) - return aViewUser; - else - return 0; -} - -void GLView::uncheckViewAction() -{ - QAction * act = checkedViewAction(); - if ( act && act != aViewWalk ) - act->setChecked( false ); -} - -void GLView::advanceGears() -{ - QTime t = QTime::currentTime(); - float dT = lastTime.msecsTo( t ) / 1000.0; - - if ( Options::benchmark() ) - { - fpsacc += dT; - fpscnt++; - update(); - } - - if ( dT < 0 ) dT = 0; - if ( dT > 1.0 ) dT = 1.0; - lastTime = t; - - if ( ! isVisible() ) - return; - - if ( aAnimate->isChecked() && aAnimPlay->isChecked() && scene->timeMin() != scene->timeMax() ) - { - time += dT; - if ( time > scene->timeMax() ) - { - if ( aAnimSwitch->isChecked() && ! scene->animGroups.isEmpty() ) - { - int ix = scene->animGroups.indexOf( scene->animGroup ); - if ( ++ix >= scene->animGroups.count() ) - ix -= scene->animGroups.count(); - sltSequence( scene->animGroups.value( ix ) ); - } - else if ( aAnimLoop->isChecked() ) - { - time = scene->timeMin(); - } - } - - emit sigTime( time, scene->timeMin(), scene->timeMax() ); - update(); - } - - if ( kbd[ Qt::Key_Up ] ) rotate( - ROT_SPD * dT, 0, 0 ); - if ( kbd[ Qt::Key_Down ] ) rotate( + ROT_SPD * dT, 0, 0 ); - if ( kbd[ Qt::Key_Left ] ) rotate( 0, 0, - ROT_SPD * dT ); - if ( kbd[ Qt::Key_Right ] ) rotate( 0, 0, + ROT_SPD * dT ); - if ( kbd[ Qt::Key_A ] ) move( + MOV_SPD * dT, 0, 0 ); - if ( kbd[ Qt::Key_D ] ) move( - MOV_SPD * dT, 0, 0 ); - if ( kbd[ Qt::Key_W ] ) move( 0, 0, + MOV_SPD * dT ); - if ( kbd[ Qt::Key_S ] ) move( 0, 0, - MOV_SPD * dT ); - if ( kbd[ Qt::Key_F ] ) move( 0, + MOV_SPD * dT, 0 ); - if ( kbd[ Qt::Key_R ] ) move( 0, - MOV_SPD * dT, 0 ); - if ( kbd[ Qt::Key_Q ] ) setDistance( Dist * 1.0 / 1.1 ); - if ( kbd[ Qt::Key_E ] ) setDistance( Dist * 1.1 ); - if ( kbd[ Qt::Key_PageUp ] ) zoom( 1.1f ); - if ( kbd[ Qt::Key_PageDown ] ) zoom( 1 / 1.1f ); - - if ( mouseMov[0] != 0 || mouseMov[1] != 0 || mouseMov[2] != 0 ) - { - move( mouseMov[0], mouseMov[1], mouseMov[2] ); - mouseMov = Vector3(); - } - - if ( mouseRot[0] != 0 || mouseRot[1] != 0 || mouseRot[2] != 0 ) - { - rotate( mouseRot[0], mouseRot[1], mouseRot[2] ); - mouseRot = Vector3(); - } -} - -void GLView::keyPressEvent( QKeyEvent * event ) -{ - switch ( event->key() ) - { - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_PageUp: - case Qt::Key_PageDown: - case Qt::Key_A: - case Qt::Key_D: - case Qt::Key_W: - case Qt::Key_S: - case Qt::Key_R: - case Qt::Key_F: - case Qt::Key_Q: - case Qt::Key_E: - kbd[ event->key() ] = true; - break; - case Qt::Key_Escape: - doCompile = true; - if ( ! aViewWalk->isChecked() ) - doCenter = true; - update(); - break; - case Qt::Key_C: - { - Node * node = scene->getNode( model, scene->currentBlock ); - - if ( node != 0 ) - { - BoundSphere bs = node->bounds(); - - this->setPosition( -bs.center ); - - if ( bs.radius > 0 ) - { - setDistance( bs.radius * 1.5f ); - } - } - } - break; - default: - event->ignore(); - break; - } -} - -void GLView::keyReleaseEvent( QKeyEvent * event ) -{ - switch ( event->key() ) - { - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_PageUp: - case Qt::Key_PageDown: - case Qt::Key_A: - case Qt::Key_D: - case Qt::Key_W: - case Qt::Key_S: - case Qt::Key_R: - case Qt::Key_F: - case Qt::Key_Q: - case Qt::Key_E: - kbd[ event->key() ] = false; - break; - default: - event->ignore(); - break; - } -} - -void GLView::focusOutEvent( QFocusEvent * ) -{ - kbd.clear(); -} - -void GLView::mousePressEvent(QMouseEvent *event) -{ - lastPos = event->pos(); - - if ( ( pressPos - event->pos() ).manhattanLength() <= 3 ) - cycleSelect++; - else - cycleSelect = 0; - - pressPos = event->pos(); -} - -void GLView::mouseReleaseEvent( QMouseEvent *event ) -{ - if ( ! ( model && ( pressPos - event->pos() ).manhattanLength() <= 3 ) ) - return; - - QModelIndex idx = indexAt( event->pos(), cycleSelect ); - scene->currentBlock = model->getBlock( idx ); - scene->currentIndex = idx.sibling( idx.row(), 0 ); - if ( idx.isValid() ) - { - emit clicked( idx ); - } - update(); - - if ( event->button() == Qt::RightButton ) - { // workaround for Qt 4.2.2 ( QMainWindow Menu pops out of nowhere ) - popPos = event->pos(); - QTimer::singleShot( 0, this, SLOT( popMenu() ) ); - } -} - -void GLView::popMenu() -{ - emit customContextMenuRequested( popPos ); -} - -void GLView::mouseMoveEvent(QMouseEvent *event) -{ - int dx = event->x() - lastPos.x(); - int dy = event->y() - lastPos.y(); - - if (event->buttons() & Qt::LeftButton) - { - mouseRot += Vector3( dy * .5, 0, dx * .5 ); - } - else if ( event->buttons() & Qt::MidButton) - { - float d = axis / ( qMax( width(), height() ) + 1 ); - mouseMov += Vector3( dx * d, - dy * d, 0 ); - } - else if ( event->buttons() & Qt::RightButton ) - { - setDistance( Dist - (dx + dy) * ( axis / ( qMax( width(), height() ) + 1 ) ) ); - } - lastPos = event->pos(); -} - -void GLView::mouseDoubleClickEvent( QMouseEvent * ) -{ - /* - doCompile = true; - if ( ! aViewWalk->isChecked() ) - doCenter = true; - update(); - */ -} - -void GLView::wheelEvent( QWheelEvent * event ) -{ - if ( aViewWalk->isChecked() ) - mouseMov += Vector3( 0, 0, event->delta() ); - else - setDistance( Dist * ( event->delta() < 0 ? 1.0 / 0.8 : 0.8 ) ); -} - -void GLView::checkActions() -{ - scene->animate = aAnimate->isChecked(); - - lastTime = QTime::currentTime(); - - if ( Options::benchmark() ) - timer->setInterval( 0 ); - else - timer->setInterval( 1000 / FPS ); - - update(); -} - -void GLView::save( QSettings & settings ) -{ - //settings.beginGroup( "OpenGL" ); - settings.setValue( "enable animations", aAnimate->isChecked() ); - settings.setValue( "play animation", aAnimPlay->isChecked() ); - settings.setValue( "loop animation", aAnimLoop->isChecked() ); - settings.setValue( "switch animation", aAnimSwitch->isChecked() ); - settings.setValue( "perspective", aViewPerspective->isChecked() ); - if ( checkedViewAction() ) - settings.setValue( "view action", checkedViewAction()->text() ); - //settings.endGroup(); -} - -void GLView::restore( const QSettings & settings ) -{ - //settings.beginGroup( "OpenGL" ); - aAnimate->setChecked( settings.value( "enable animations", true ).toBool() ); - aAnimPlay->setChecked( settings.value( "play animation", true ).toBool() ); - aAnimLoop->setChecked( settings.value( "loop animation", true ).toBool() ); - aAnimSwitch->setChecked( settings.value( "switch animation", true ).toBool() ); - aViewPerspective->setChecked( settings.value( "perspective", true ).toBool() ); - checkActions(); - QString viewAct = settings.value( "view action", "Front" ).toString(); - foreach ( QAction * act, grpView->actions() ) - if ( act->text() == viewAct ) - viewAction( act ); - //settings.endGroup(); -} - -void GLView::move( float x, float y, float z ) -{ - Pos += Matrix::euler( Rot[0] / 180 * PI, Rot[1] / 180 * PI, Rot[2] / 180 * PI ).inverted() * Vector3( x, y, z ); - update(); -} - -void GLView::rotate( float x, float y, float z ) -{ - Rot += Vector3( x, y, z ); - uncheckViewAction(); - update(); -} - -void GLView::zoom( float z ) -{ - Zoom *= z; - if ( Zoom < ZOOM_MIN ) Zoom = ZOOM_MIN; - if ( Zoom > ZOOM_MAX ) Zoom = ZOOM_MAX; - update(); -} - -void GLView::setPosition( float x, float y, float z ) -{ - Pos = Vector3( x, y, z ); - update(); -} - -void GLView::setPosition( Vector3 v ) -{ - Pos = v; - update(); -} - -void GLView::setRotation( float x, float y, float z ) -{ - Rot = Vector3( x, y, z ); - update(); -} - -void GLView::setZoom( float z ) -{ - Zoom = z; - update(); -} - -void GLView::setDistance( float x ) -{ - Dist = x; - update(); -} - -void GLView::dragEnterEvent( QDragEnterEvent * e ) -{ - if ( e->mimeData()->hasUrls() && e->mimeData()->urls().count() == 1 ) - { - QUrl url = e->mimeData()->urls().first(); - if ( url.scheme() == "file" ) - { - QString fn = url.toLocalFile(); - if ( textures->canLoad( fn ) ) - { - fnDragTex = textures->stripPath( fn, model->getFolder() ); - e->accept(); - return; - } - } - } - - e->ignore(); -} - -void GLView::dragMoveEvent( QDragMoveEvent * e ) -{ - if ( iDragTarget.isValid() ) - { - model->set( iDragTarget, fnDragTexOrg ); - iDragTarget = QModelIndex(); - fnDragTexOrg = QString(); - } - - QModelIndex iObj = model->getBlock( indexAt( e->pos() ), "NiAVObject" ); - if ( iObj.isValid() ) - { - foreach ( qint32 l, model->getChildLinks( model->getBlockNumber( iObj ) ) ) - { - QModelIndex iTxt = model->getBlock( l, "NiTexturingProperty" ); - if ( iTxt.isValid() ) - { - QModelIndex iSrc = model->getBlock( model->getLink( iTxt, "Base Texture/Source" ), "NiSourceTexture" ); - if ( iSrc.isValid() ) - { - iDragTarget = model->getIndex( iSrc, "File Name" ); - if ( iDragTarget.isValid() ) - { - fnDragTexOrg = model->get( iDragTarget ); - model->set( iDragTarget, fnDragTex ); - e->accept(); - return; - } - } - } - } - } - - e->ignore(); -} - -void GLView::dropEvent( QDropEvent * e ) -{ - iDragTarget = QModelIndex(); - fnDragTex = fnDragTexOrg = QString(); - e->accept(); -} - -void GLView::dragLeaveEvent( QDragLeaveEvent * e ) -{ - if ( iDragTarget.isValid() ) - { - model->set( iDragTarget, fnDragTexOrg ); - iDragTarget = QModelIndex(); - fnDragTex = fnDragTexOrg = QString(); - } -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "glview.h" + +#include +#include +#include +#include +#include +#include +#include + + +#include + +#include "nifmodel.h" +#include "gl/glscene.h" +#include "gl/gltex.h" +#include "options.h" + +#include "widgets/floatedit.h" +#include "widgets/floatslider.h" + +#define FPS 25 + +#define FOV 45.0 + +#define MOV_SPD 350 +#define ROT_SPD 45 + +#define ZOOM_MIN 1.0 +#define ZOOM_MAX 1000.0 + +#ifndef M_PI +#define M_PI 3.1415926535897932385 +#endif + +GLView * GLView::create() +{ + static QList< QPointer > views; + + QGLWidget * share = 0; + foreach ( QPointer v, views ) + if ( v ) share = v; + + QGLFormat fmt; + if ( share ) + fmt = share->format(); + else + fmt.setSampleBuffers( Options::antialias() ); + + views.append( QPointer( new GLView( fmt, share ) ) ); + + return views.last(); +} + +GLView::GLView( const QGLFormat & format, const QGLWidget * shareWidget ) + : QGLWidget( format, 0, shareWidget ) +{ + setFocusPolicy( Qt::ClickFocus ); + setAttribute( Qt::WA_NoSystemBackground ); + setAcceptDrops( true ); + //setContextMenuPolicy( Qt::CustomContextMenu ); + + Zoom = 1.0; + zInc = 1; + + doCenter = false; + doCompile = false; + + model = 0; + + time = 0.0; + lastTime = QTime::currentTime(); + + fpsact = 0.0; + fpsacc = 0.0; + fpscnt = 0; + + textures = new TexCache( this ); + + scene = new Scene( textures ); + connect( textures, SIGNAL( sigRefresh() ), this, SLOT( update() ) ); + + timer = new QTimer(this); + timer->setInterval( 1000 / FPS ); + timer->start(); + connect( timer, SIGNAL( timeout() ), this, SLOT( advanceGears() ) ); + + + grpView = new QActionGroup( this ); + grpView->setExclusive( false ); + connect( grpView, SIGNAL( triggered( QAction * ) ), this, SLOT( viewAction( QAction * ) ) ); + + aViewTop = new QAction( QIcon( ":/btn/viewTop" ), tr("Top"), grpView ); + aViewTop->setToolTip( tr("View from above") ); + aViewTop->setCheckable( true ); + aViewTop->setShortcut( Qt::Key_F5 ); + grpView->addAction( aViewTop ); + + aViewFront = new QAction( QIcon( ":/btn/viewFront" ), tr("Front"), grpView ); + aViewFront->setToolTip( tr("View from the front") ); + aViewFront->setCheckable( true ); + aViewFront->setChecked( true ); + aViewFront->setShortcut( Qt::Key_F6 ); + grpView->addAction( aViewFront ); + + aViewSide = new QAction( QIcon( ":/btn/viewSide" ), tr("Side"), grpView ); + aViewSide->setToolTip( tr("View from the side") ); + aViewSide->setCheckable( true ); + aViewSide->setShortcut( Qt::Key_F7 ); + grpView->addAction( aViewSide ); + + aViewUser = new QAction( QIcon( ":/btn/viewUser" ), tr("User"), grpView ); + aViewUser->setToolTip( tr("Restore the view as it was when Save User View was activated") ); + aViewUser->setCheckable( true ); + aViewUser->setShortcut( Qt::Key_F8 ); + grpView->addAction( aViewUser ); + + + aViewWalk = new QAction( QIcon( ":/btn/viewWalk" ), tr("Walk"), grpView ); + aViewWalk->setToolTip( tr("Enable walk mode") ); + aViewWalk->setCheckable( true ); + aViewWalk->setShortcut( Qt::Key_F9 ); + grpView->addAction( aViewWalk ); + + aViewFlip = new QAction( QIcon( ":/btn/viewFlip" ), tr("Flip"), this ); + aViewFlip->setToolTip( tr("Flip View from Front to Back, Top to Bottom, Side to Other Side") ); + aViewFlip->setCheckable( true ); + aViewFlip->setShortcut( Qt::Key_F11 ); + grpView->addAction( aViewFlip ); + + aViewPerspective = new QAction( QIcon( ":/btn/viewPers" ), tr("Perspective"), this ); + aViewPerspective->setToolTip( tr("Perspective View Transformation or Orthogonal View Transformation") ); + aViewPerspective->setCheckable( true ); + aViewPerspective->setShortcut( Qt::Key_F10 ); + grpView->addAction( aViewPerspective ); + + aViewUserSave = new QAction( tr("Save User View"), this ); + aViewUserSave->setToolTip( tr("Save current view rotation, position and distance") ); + aViewUserSave->setShortcut( Qt::CTRL + Qt::Key_F9 ); + connect( aViewUserSave, SIGNAL( triggered() ), this, SLOT( sltSaveUserView() ) ); + + aAnimate = new QAction( tr("&Animations"), this ); + aAnimate->setToolTip( tr("enables evaluation of animation controllers") ); + aAnimate->setCheckable( true ); + aAnimate->setChecked( true ); + connect( aAnimate, SIGNAL( toggled( bool ) ), this, SLOT( checkActions() ) ); + addAction( aAnimate ); + + aAnimPlay = new QAction( QIcon( ":/btn/play" ), tr("&Play"), this ); + aAnimPlay->setCheckable( true ); + aAnimPlay->setChecked( true ); + connect( aAnimPlay, SIGNAL( toggled( bool ) ), this, SLOT( checkActions() ) ); + + aAnimLoop = new QAction( QIcon( ":/btn/loop" ), tr("&Loop"), this ); + aAnimLoop->setCheckable( true ); + aAnimLoop->setChecked( true ); + + aAnimSwitch = new QAction( QIcon( ":/btn/switch" ), tr("&Switch"), this ); + aAnimSwitch->setCheckable( true ); + aAnimSwitch->setChecked( true ); + + // animation tool bar + + tAnim = new QToolBar( tr("Animation") ); + tAnim->setObjectName( "AnimTool" ); + tAnim->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); + tAnim->setIconSize( QSize( 16, 16 ) ); + + connect( aAnimate, SIGNAL( toggled( bool ) ), tAnim->toggleViewAction(), SLOT( setChecked( bool ) ) ); + connect( aAnimate, SIGNAL( toggled( bool ) ), tAnim, SLOT( setVisible( bool ) ) ); + connect( tAnim->toggleViewAction(), SIGNAL( toggled( bool ) ), aAnimate, SLOT( setChecked( bool ) ) ); + + tAnim->addAction( aAnimPlay ); + + FloatSlider * sldTime = new FloatSlider( Qt::Horizontal, true, true ); + sldTime->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Maximum ); + connect( this, SIGNAL( sigTime( float, float, float ) ), sldTime, SLOT( set( float, float, float ) ) ); + connect( sldTime, SIGNAL( valueChanged( float ) ), this, SLOT( sltTime( float ) ) ); + tAnim->addWidget( sldTime ); + + FloatEdit * edtTime = new FloatEdit; + edtTime->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Maximum ); + + connect( this, SIGNAL( sigTime( float, float, float ) ), edtTime, SLOT( set( float, float, float ) ) ); + connect( edtTime, SIGNAL( sigEdited( float ) ), this, SLOT( sltTime( float ) ) ); + connect( sldTime, SIGNAL( valueChanged( float ) ), edtTime, SLOT( setValue( float ) ) ); + connect( edtTime, SIGNAL( sigEdited( float ) ), sldTime, SLOT( setValue( float ) ) ); + sldTime->addEditor( edtTime ); + + tAnim->addAction( aAnimLoop ); + + tAnim->addAction( aAnimSwitch ); + + animGroups = new QComboBox(); + animGroups->setMinimumWidth( 100 ); + connect( animGroups, SIGNAL( activated( const QString & ) ), this, SLOT( sltSequence( const QString & ) ) ); + tAnim->addWidget( animGroups ); + + connect( Options::get(), SIGNAL( sigChanged() ), textures, SLOT( flush() ) ); + connect( Options::get(), SIGNAL( sigChanged() ), this, SLOT( update() ) ); + + tView = new QToolBar( tr("View") ); + tView->setObjectName( "ViewTool" ); + tView->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); + tView->setIconSize( QSize( 16, 16 ) ); + + tView->addAction( aViewTop ); + tView->addAction( aViewFront ); + tView->addAction( aViewSide ); + tView->addAction( aViewUser ); + tView->addAction( aViewWalk ); + tView->addSeparator(); + tView->addAction( aViewFlip ); + tView->addAction( aViewPerspective ); +} + +GLView::~GLView() +{ + delete scene; +} + +QList GLView::toolbars() const +{ + return QList() << tView << tAnim; +} + +QMenu * GLView::createMenu() const +{ + QMenu * m = new QMenu( tr("&Render") ); + m->addAction( aViewTop ); + m->addAction( aViewFront ); + m->addAction( aViewSide ); + m->addAction( aViewWalk ); + m->addAction( aViewUser ); + m->addSeparator(); + m->addAction( aViewFlip ); + m->addAction( aViewPerspective ); + m->addAction( aViewUserSave ); + m->addSeparator(); + m->addActions( Options::actions() ); + return m; +} + +void GLView::updateShaders() +{ + makeCurrent(); + scene->updateShaders(); + update(); +} + + +/* + * OpenGL stuff + */ + +void GLView::initializeGL() +{ + initializeTextureUnits( context() ); + + if ( Renderer::initialize( context() ) ) + updateShaders(); + + // check for errors + + GLenum err; + while ( ( err = glGetError() ) != GL_NO_ERROR ) + qDebug() << "GL ERROR (init) : " << (const char *) gluErrorString( err ); +} + +void GLView::glProjection( int x, int y ) +{ + GLint viewport[4]; + glGetIntegerv( GL_VIEWPORT, viewport ); + GLdouble aspect = (GLdouble) viewport[2] / (GLdouble) viewport[3]; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + if ( x >= 0 && y >= 0 ) + { + gluPickMatrix( (GLdouble) x, (GLdouble) (viewport[3]-y), 5.0f, 5.0f, viewport); + } + + BoundSphere bs = scene->view * scene->bounds(); + if ( Options::drawAxes() ) + bs |= BoundSphere( scene->view * Vector3(), axis ); + + GLdouble nr = fabs( bs.center[2] ) - bs.radius * 1.2; + GLdouble fr = fabs( bs.center[2] ) + bs.radius * 1.2; + + //qWarning() << nr << fr; + + if ( aViewPerspective->isChecked() || aViewWalk->isChecked() ) + { + if ( nr < 1.0 ) nr = 1.0; + if ( fr < 2.0 ) fr = 2.0; + GLdouble h2 = tan( ( FOV / Zoom ) / 360 * M_PI ) * nr; + GLdouble w2 = h2 * aspect; + glFrustum( - w2, + w2, - h2, + h2, nr, fr ); + } + else + { + GLdouble h2 = Dist / Zoom; + GLdouble w2 = h2 * aspect; + glOrtho( - w2, + w2, - h2, + h2, nr, fr ); + } + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +void GLView::center() +{ + doCenter = true; + update(); +} + +#ifdef USE_GL_QPAINTER +void GLView::paintEvent( QPaintEvent * event ) +{ + makeCurrent(); + + QPainter painter; + painter.begin( this ); + painter.setRenderHint( QPainter::TextAntialiasing ); +#else +void GLView::paintGL() +{ +#endif + // save gl state for later use by qpainter + + glPushAttrib(GL_ALL_ATTRIB_BITS); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + glViewport( 0, 0, width(), height() ); + qglClearColor( Options::bgColor() ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + glShadeModel( GL_SMOOTH ); + + // compile the model + + if ( doCompile ) + { + textures->setNifFolder( model->getFolder() ); + scene->make( model ); + scene->transform( Transform(), scene->timeMin() ); + axis = scene->bounds().radius * 1.4; + if ( axis == 0 ) axis = 1; + + if ( time < scene->timeMin() || time > scene->timeMax() ) + time = scene->timeMin(); + emit sigTime( time, scene->timeMin(), scene->timeMax() ); + + animGroups->clear(); + animGroups->addItems( scene->animGroups ); + animGroups->setCurrentIndex( scene->animGroups.indexOf( scene->animGroup ) ); + + doCompile = false; + } + + // center the model + + if ( doCenter ) + { + viewAction( checkedViewAction() ); + doCenter = false; + } + + // transform the scene + + Matrix ap; + + if ( Options::upAxis() == Options::YAxis ) + { + ap( 0, 0 ) = 0; ap( 0, 1 ) = 0; ap( 0, 2 ) = 1; + ap( 1, 0 ) = 1; ap( 1, 1 ) = 0; ap( 1, 2 ) = 0; + ap( 2, 0 ) = 0; ap( 2, 1 ) = 1; ap( 2, 2 ) = 0; + } + else if ( Options::upAxis() == Options::XAxis ) + { + ap( 0, 0 ) = 0; ap( 0, 1 ) = 1; ap( 0, 2 ) = 0; + ap( 1, 0 ) = 0; ap( 1, 1 ) = 0; ap( 1, 2 ) = 1; + ap( 2, 0 ) = 1; ap( 2, 1 ) = 0; ap( 2, 2 ) = 0; + } + + Transform viewTrans; + viewTrans.rotation.fromEuler( Rot[0] / 180.0 * PI, Rot[1] / 180.0 * PI, Rot[2] / 180.0 * PI ); + viewTrans.rotation = viewTrans.rotation * ap; + viewTrans.translation = viewTrans.rotation * Pos; + if ( ! aViewWalk->isChecked() ) + viewTrans.translation[2] -= Dist * 2; + + scene->transform( viewTrans, time ); + + // setup projection mode + + glProjection(); + + glLoadIdentity(); + + // draw the axis + + if ( Options::drawAxes() ) + { + glDisable( GL_ALPHA_TEST ); + glDisable( GL_BLEND ); + glDisable( GL_LIGHTING ); + glDisable( GL_COLOR_MATERIAL ); + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_TRUE ); + glDepthFunc( GL_LESS ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_NORMALIZE ); + glLineWidth( 1.2f ); + + glPushMatrix(); + glLoadMatrix( viewTrans ); + + drawAxes( Vector3(), axis ); + + glPopMatrix(); + } + + // setup light + + Vector4 lightDir( 0.0, 0.0, 1.0, 0.0 ); + if ( ! Options::lightFrontal() ) + { + float decl = Options::lightDeclination() / 180.0 * PI; + Vector3 v( sin( decl ), 0, cos( decl ) ); + Matrix m; m.fromEuler( 0, 0, Options::lightPlanarAngle() / 180.0 * PI ); + v = m * v; + lightDir = Vector4( viewTrans.rotation * v, 0.0 ); + } + glLightfv( GL_LIGHT0, GL_POSITION, lightDir.data() ); + glLightfv( GL_LIGHT0, GL_AMBIENT, Color4( Options::ambient() ).data() ); + glLightfv( GL_LIGHT0, GL_DIFFUSE, Color4( Options::diffuse() ).data() ); + glLightfv( GL_LIGHT0, GL_SPECULAR, Color4( Options::specular() ).data() ); + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE ); + glEnable( GL_LIGHT0 ); + glEnable( GL_LIGHTING ); + + // Initialize Rendering Font + glListBase(fontDisplayListBase(QFont(), 2000)); + + // draw the model + scene->draw(); + + // restore gl state + glPopAttrib(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + + // check for errors + + GLenum err; + while ( ( err = glGetError() ) != GL_NO_ERROR ) + qDebug() << "GL ERROR (paint): " << (const char *) gluErrorString( err ); + + // update fps counter + + if ( fpsacc > 1.0 && fpscnt ) + { + fpsacc /= fpscnt; + if ( fpsacc > 0.0001 ) + fpsact = 1.0 / fpsacc; + else + fpsact = 10000; + fpsacc = 0; + fpscnt = 0; + } + +#ifdef USE_GL_QPAINTER + // draw text on top using QPainter + + if ( Options::benchmark() || Options::drawStats() ) + { + int ls = QFontMetrics( font() ).lineSpacing(); + int y = 1; + + painter.setPen( Options::hlColor() ); + + if ( Options::benchmark() ) + { + painter.drawText( 10, y++ * ls, QString( "FPS %1" ).arg( int( fpsact ) ) ); + y++; + } + + if ( Options::drawStats() ) + { + QString stats = scene->textStats(); + QStringList lines = stats.split( "\n" ); + foreach ( QString line, lines ) + painter.drawText( 10, y++ * ls, line ); + } + } + + painter.end(); +#endif +} + +bool compareHits( const QPair< GLuint, GLuint > & a, const QPair< GLuint, GLuint > & b ) +{ + if ( a.second < b.second ) + return true; + else + return false; +} + +typedef void (Scene::*DrawFunc)(void); + +int indexAt( GLuint *buffer, NifModel *model, Scene *scene, QList drawFunc, int cycle ) +{ + glRenderMode( GL_SELECT ); + glInitNames(); + glPushName( 0 ); + + foreach ( DrawFunc df, drawFunc ) + (scene->*df)(); + + GLint hits = glRenderMode( GL_RENDER ); + if ( hits > 0 ) + { + QList< QPair< GLuint, GLuint > > hitList; + + for ( int l = 0; l < hits; l++ ) + { + hitList << QPair< GLuint, GLuint >( buffer[ l * 4 + 3 ], buffer[ l * 4 + 1 ] ); + } + + qSort( hitList.begin(), hitList.end(), compareHits ); + + return hitList.value( cycle % hits ).first; + } + + return -1; +} + +QModelIndex GLView::indexAt( const QPoint & pos, int cycle ) +{ + if ( ! ( model && isVisible() && height() ) ) + return QModelIndex(); + + makeCurrent(); + + glPushAttrib(GL_ALL_ATTRIB_BITS); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + glViewport( 0, 0, width(), height() ); + glProjection( pos.x(), pos.y() ); + + GLuint buffer[512]; + glSelectBuffer( 512, buffer ); + + int choose; + if ( Options::drawFurn() ) + { + choose = ::indexAt( buffer, model, scene, QList() << &Scene::drawFurn, cycle ); + if ( choose != -1 ) + { + glPopAttrib(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + + QModelIndex parent = model->index( 3, 0, model->getBlock( choose&0x0ffff ) ); + return model->index( choose>>16, 0, parent ); + } + } + + QList df; + + if ( Options::drawHavok() ) + df << &Scene::drawHavok; + if ( Options::drawNodes() ) + df << &Scene::drawNodes; + df << &Scene::drawShapes; + + choose = ::indexAt( buffer, model, scene, df, cycle ); + + glPopAttrib(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + + if ( choose != -1 ) + return model->getBlock( choose ); + + return QModelIndex(); +} + +void GLView::resizeGL(int width, int height) +{ + glViewport(0, 0, width, height ); +} + + +/* + * NifModel stuff + */ + +void GLView::setNif( NifModel * nif ) +{ + if ( model ) + { + disconnect( model ); + } + model = nif; + if ( model ) + { + connect( model, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( dataChanged( const QModelIndex &, const QModelIndex & ) ) ); + connect( model, SIGNAL( linksChanged() ), this, SLOT( modelLinked() ) ); + connect( model, SIGNAL( modelReset() ), this, SLOT( modelChanged() ) ); + connect( model, SIGNAL( destroyed() ), this, SLOT( modelDestroyed() ) ); + } + doCompile = true; +} + +void GLView::setCurrentIndex( const QModelIndex & index ) +{ + if ( ! ( model && index.model() == model ) ) + return; + + scene->currentBlock = model->getBlock( index ); + scene->currentIndex = index.sibling( index.row(), 0 ); + + update(); +} + +QModelIndex parent( QModelIndex ix, QModelIndex xi ) +{ + ix = ix.sibling( ix.row(), 0 ); + xi = xi.sibling( xi.row(), 0 ); + + while ( ix.isValid() ) + { + QModelIndex x = xi; + while ( x.isValid() ) + { + if ( ix == x ) return ix; + x = x.parent(); + } + ix = ix.parent(); + } + return QModelIndex(); +} + +void GLView::dataChanged( const QModelIndex & idx, const QModelIndex & xdi ) +{ + if ( doCompile ) + return; + + QModelIndex ix = idx; + + if ( idx == xdi ) + { + if ( idx.column() != 0 ) + ix = idx.sibling( idx.row(), 0 ); + } + else + { + ix = ::parent( idx, xdi ); + } + + if ( ix.isValid() ) + { + scene->update( model, idx ); + update(); + } + else + modelChanged(); +} + +void GLView::modelChanged() +{ + doCompile = true; + doCenter = true; + update(); +} + +void GLView::modelLinked() +{ + doCompile = true; //scene->update( model, QModelIndex() ); + update(); +} + +void GLView::modelDestroyed() +{ + setNif( 0 ); +} + + +void GLView::sltTime( float t ) +{ + time = t; + update(); +} + +void GLView::sltSequence( const QString & seqname ) +{ + animGroups->setCurrentIndex( scene->animGroups.indexOf( seqname ) ); + scene->setSequence( seqname ); + time = scene->timeMin(); + emit sigTime( time, scene->timeMin(), scene->timeMax() ); + update(); +} + + + +void GLView::viewAction( QAction * act ) +{ + BoundSphere bs = scene->bounds(); + if ( Options::drawAxes() ) + bs |= BoundSphere( Vector3(), axis ); + + if ( bs.radius < 1 ) bs.radius = 1; + setDistance( bs.radius ); + setZoom( 1.0 ); + + if ( act == aViewWalk ) + { + setRotation( -90, 0, 0 ); + setPosition( Vector3() - scene->bounds().center ); + setZoom( 1.0 ); + aViewWalk->setChecked( true ); + aViewTop->setChecked( false ); + aViewFront->setChecked( false ); + aViewSide->setChecked( false ); + aViewUser->setChecked( false ); + } + + if ( ! act || act == aViewFlip ) + { + act = checkedViewAction(); + } + + if ( act != aViewWalk ) + setPosition( Vector3() - bs.center ); + + if ( act == aViewTop ) + { + if ( aViewFlip->isChecked() ) + setRotation( 180, 0, 0 ); + else + setRotation( 0, 0, 0 ); + aViewWalk->setChecked( false ); + aViewTop->setChecked( true ); + aViewFront->setChecked( false ); + aViewSide->setChecked( false ); + aViewUser->setChecked( false ); + } + else if ( act == aViewFront ) + { + if ( aViewFlip->isChecked() ) + setRotation( -90, 0, 180 ); + else + setRotation( -90, 0, 0 ); + aViewWalk->setChecked( false ); + aViewTop->setChecked( false ); + aViewFront->setChecked( true ); + aViewSide->setChecked( false ); + aViewUser->setChecked( false ); + } + else if ( act == aViewSide ) + { + if ( aViewFlip->isChecked() ) + setRotation( - 90, 0, - 90 ); + else + setRotation( - 90, 0, 90 ); + aViewWalk->setChecked( false ); + aViewTop->setChecked( false ); + aViewFront->setChecked( false ); + aViewSide->setChecked( true ); + aViewUser->setChecked( false ); + } + else if ( act == aViewUser ) + { + QSettings cfg; + cfg.beginGroup( "GLView" ); + cfg.beginGroup( "User View" ); + setRotation( cfg.value( "RotX" ).toDouble(), cfg.value( "RotY" ).toDouble(), cfg.value( "RotZ" ).toDouble() ); + setPosition( cfg.value( "PosX" ).toDouble(), cfg.value( "PosY" ).toDouble(), cfg.value( "PosZ" ).toDouble() ); + setDistance( cfg.value( "Dist" ).toDouble() ); + aViewWalk->setChecked( false ); + aViewTop->setChecked( false ); + aViewFront->setChecked( false ); + aViewSide->setChecked( false ); + aViewUser->setChecked( true ); + } + update(); +} + +void GLView::sltSaveUserView() +{ + QSettings cfg; + cfg.beginGroup( "GLView" ); + cfg.beginGroup( "User View" ); + cfg.setValue( "RotX", Rot[0] ); + cfg.setValue( "RotY", Rot[1] ); + cfg.setValue( "RotZ", Rot[2] ); + cfg.setValue( "PosX", Pos[0] ); + cfg.setValue( "PosY", Pos[1] ); + cfg.setValue( "PosZ", Pos[2] ); + cfg.setValue( "Dist", Dist ); + viewAction( aViewUser ); +} + +QAction * GLView::checkedViewAction() const +{ + if ( aViewTop->isChecked() ) + return aViewTop; + else if ( aViewFront->isChecked() ) + return aViewFront; + else if ( aViewSide->isChecked() ) + return aViewSide; + else if ( aViewWalk->isChecked() ) + return aViewWalk; + else if ( aViewUser->isChecked() ) + return aViewUser; + else + return 0; +} + +void GLView::uncheckViewAction() +{ + QAction * act = checkedViewAction(); + if ( act && act != aViewWalk ) + act->setChecked( false ); +} + +void GLView::advanceGears() +{ + QTime t = QTime::currentTime(); + float dT = lastTime.msecsTo( t ) / 1000.0; + + if ( Options::benchmark() ) + { + fpsacc += dT; + fpscnt++; + update(); + } + + if ( dT < 0 ) dT = 0; + if ( dT > 1.0 ) dT = 1.0; + lastTime = t; + + if ( ! isVisible() ) + return; + + if ( aAnimate->isChecked() && aAnimPlay->isChecked() && scene->timeMin() != scene->timeMax() ) + { + time += dT; + if ( time > scene->timeMax() ) + { + if ( aAnimSwitch->isChecked() && ! scene->animGroups.isEmpty() ) + { + int ix = scene->animGroups.indexOf( scene->animGroup ); + if ( ++ix >= scene->animGroups.count() ) + ix -= scene->animGroups.count(); + sltSequence( scene->animGroups.value( ix ) ); + } + else if ( aAnimLoop->isChecked() ) + { + time = scene->timeMin(); + } + } + + emit sigTime( time, scene->timeMin(), scene->timeMax() ); + update(); + } + + if ( kbd[ Qt::Key_Up ] ) rotate( - ROT_SPD * dT, 0, 0 ); + if ( kbd[ Qt::Key_Down ] ) rotate( + ROT_SPD * dT, 0, 0 ); + if ( kbd[ Qt::Key_Left ] ) rotate( 0, 0, - ROT_SPD * dT ); + if ( kbd[ Qt::Key_Right ] ) rotate( 0, 0, + ROT_SPD * dT ); + if ( kbd[ Qt::Key_A ] ) move( + MOV_SPD * dT, 0, 0 ); + if ( kbd[ Qt::Key_D ] ) move( - MOV_SPD * dT, 0, 0 ); + if ( kbd[ Qt::Key_W ] ) move( 0, 0, + MOV_SPD * dT ); + if ( kbd[ Qt::Key_S ] ) move( 0, 0, - MOV_SPD * dT ); + if ( kbd[ Qt::Key_F ] ) move( 0, + MOV_SPD * dT, 0 ); + if ( kbd[ Qt::Key_R ] ) move( 0, - MOV_SPD * dT, 0 ); + if ( kbd[ Qt::Key_Q ] ) setDistance( Dist * 1.0 / 1.1 ); + if ( kbd[ Qt::Key_E ] ) setDistance( Dist * 1.1 ); + if ( kbd[ Qt::Key_PageUp ] ) zoom( 1.1f ); + if ( kbd[ Qt::Key_PageDown ] ) zoom( 1 / 1.1f ); + + if ( mouseMov[0] != 0 || mouseMov[1] != 0 || mouseMov[2] != 0 ) + { + move( mouseMov[0], mouseMov[1], mouseMov[2] ); + mouseMov = Vector3(); + } + + if ( mouseRot[0] != 0 || mouseRot[1] != 0 || mouseRot[2] != 0 ) + { + rotate( mouseRot[0], mouseRot[1], mouseRot[2] ); + mouseRot = Vector3(); + } +} + +void GLView::keyPressEvent( QKeyEvent * event ) +{ + switch ( event->key() ) + { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_A: + case Qt::Key_D: + case Qt::Key_W: + case Qt::Key_S: + case Qt::Key_R: + case Qt::Key_F: + case Qt::Key_Q: + case Qt::Key_E: + kbd[ event->key() ] = true; + break; + case Qt::Key_Escape: + doCompile = true; + if ( ! aViewWalk->isChecked() ) + doCenter = true; + update(); + break; + case Qt::Key_C: + { + Node * node = scene->getNode( model, scene->currentBlock ); + + if ( node != 0 ) + { + BoundSphere bs = node->bounds(); + + this->setPosition( -bs.center ); + + if ( bs.radius > 0 ) + { + setDistance( bs.radius * 1.5f ); + } + } + } + break; + default: + event->ignore(); + break; + } +} + +void GLView::keyReleaseEvent( QKeyEvent * event ) +{ + switch ( event->key() ) + { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_A: + case Qt::Key_D: + case Qt::Key_W: + case Qt::Key_S: + case Qt::Key_R: + case Qt::Key_F: + case Qt::Key_Q: + case Qt::Key_E: + kbd[ event->key() ] = false; + break; + default: + event->ignore(); + break; + } +} + +void GLView::focusOutEvent( QFocusEvent * ) +{ + kbd.clear(); +} + +void GLView::mousePressEvent(QMouseEvent *event) +{ + lastPos = event->pos(); + + if ( ( pressPos - event->pos() ).manhattanLength() <= 3 ) + cycleSelect++; + else + cycleSelect = 0; + + pressPos = event->pos(); +} + +void GLView::mouseReleaseEvent( QMouseEvent *event ) +{ + if ( ! ( model && ( pressPos - event->pos() ).manhattanLength() <= 3 ) ) + return; + + QModelIndex idx = indexAt( event->pos(), cycleSelect ); + scene->currentBlock = model->getBlock( idx ); + scene->currentIndex = idx.sibling( idx.row(), 0 ); + if ( idx.isValid() ) + { + emit clicked( idx ); + } + update(); + + if ( event->button() == Qt::RightButton ) + { // workaround for Qt 4.2.2 ( QMainWindow Menu pops out of nowhere ) + popPos = event->pos(); + QTimer::singleShot( 0, this, SLOT( popMenu() ) ); + } +} + +void GLView::popMenu() +{ + emit customContextMenuRequested( popPos ); +} + +void GLView::mouseMoveEvent(QMouseEvent *event) +{ + int dx = event->x() - lastPos.x(); + int dy = event->y() - lastPos.y(); + + if (event->buttons() & Qt::LeftButton) + { + mouseRot += Vector3( dy * .5, 0, dx * .5 ); + } + else if ( event->buttons() & Qt::MidButton) + { + float d = axis / ( qMax( width(), height() ) + 1 ); + mouseMov += Vector3( dx * d, - dy * d, 0 ); + } + else if ( event->buttons() & Qt::RightButton ) + { + setDistance( Dist - (dx + dy) * ( axis / ( qMax( width(), height() ) + 1 ) ) ); + } + lastPos = event->pos(); +} + +void GLView::mouseDoubleClickEvent( QMouseEvent * ) +{ + /* + doCompile = true; + if ( ! aViewWalk->isChecked() ) + doCenter = true; + update(); + */ +} + +void GLView::wheelEvent( QWheelEvent * event ) +{ + if ( aViewWalk->isChecked() ) + mouseMov += Vector3( 0, 0, event->delta() ); + else + setDistance( Dist * ( event->delta() < 0 ? 1.0 / 0.8 : 0.8 ) ); +} + +void GLView::checkActions() +{ + scene->animate = aAnimate->isChecked(); + + lastTime = QTime::currentTime(); + + if ( Options::benchmark() ) + timer->setInterval( 0 ); + else + timer->setInterval( 1000 / FPS ); + + update(); +} + +void GLView::save( QSettings & settings ) +{ + //settings.beginGroup( "OpenGL" ); + settings.setValue( "enable animations", aAnimate->isChecked() ); + settings.setValue( "play animation", aAnimPlay->isChecked() ); + settings.setValue( "loop animation", aAnimLoop->isChecked() ); + settings.setValue( "switch animation", aAnimSwitch->isChecked() ); + settings.setValue( "perspective", aViewPerspective->isChecked() ); + if ( checkedViewAction() ) + settings.setValue( "view action", checkedViewAction()->text() ); + //settings.endGroup(); +} + +void GLView::restore( const QSettings & settings ) +{ + //settings.beginGroup( "OpenGL" ); + aAnimate->setChecked( settings.value( "enable animations", true ).toBool() ); + aAnimPlay->setChecked( settings.value( "play animation", true ).toBool() ); + aAnimLoop->setChecked( settings.value( "loop animation", true ).toBool() ); + aAnimSwitch->setChecked( settings.value( "switch animation", true ).toBool() ); + aViewPerspective->setChecked( settings.value( "perspective", true ).toBool() ); + checkActions(); + QString viewAct = settings.value( "view action", "Front" ).toString(); + foreach ( QAction * act, grpView->actions() ) + if ( act->text() == viewAct ) + viewAction( act ); + //settings.endGroup(); +} + +void GLView::move( float x, float y, float z ) +{ + Pos += Matrix::euler( Rot[0] / 180 * PI, Rot[1] / 180 * PI, Rot[2] / 180 * PI ).inverted() * Vector3( x, y, z ); + update(); +} + +void GLView::rotate( float x, float y, float z ) +{ + Rot += Vector3( x, y, z ); + uncheckViewAction(); + update(); +} + +void GLView::zoom( float z ) +{ + Zoom *= z; + if ( Zoom < ZOOM_MIN ) Zoom = ZOOM_MIN; + if ( Zoom > ZOOM_MAX ) Zoom = ZOOM_MAX; + update(); +} + +void GLView::setPosition( float x, float y, float z ) +{ + Pos = Vector3( x, y, z ); + update(); +} + +void GLView::setPosition( Vector3 v ) +{ + Pos = v; + update(); +} + +void GLView::setRotation( float x, float y, float z ) +{ + Rot = Vector3( x, y, z ); + update(); +} + +void GLView::setZoom( float z ) +{ + Zoom = z; + update(); +} + +void GLView::setDistance( float x ) +{ + Dist = x; + update(); +} + +void GLView::dragEnterEvent( QDragEnterEvent * e ) +{ + if ( e->mimeData()->hasUrls() && e->mimeData()->urls().count() == 1 ) + { + QUrl url = e->mimeData()->urls().first(); + if ( url.scheme() == "file" ) + { + QString fn = url.toLocalFile(); + if ( textures->canLoad( fn ) ) + { + fnDragTex = textures->stripPath( fn, model->getFolder() ); + e->accept(); + return; + } + } + } + + e->ignore(); +} + +void GLView::dragMoveEvent( QDragMoveEvent * e ) +{ + if ( iDragTarget.isValid() ) + { + model->set( iDragTarget, fnDragTexOrg ); + iDragTarget = QModelIndex(); + fnDragTexOrg = QString(); + } + + QModelIndex iObj = model->getBlock( indexAt( e->pos() ), "NiAVObject" ); + if ( iObj.isValid() ) + { + foreach ( qint32 l, model->getChildLinks( model->getBlockNumber( iObj ) ) ) + { + QModelIndex iTxt = model->getBlock( l, "NiTexturingProperty" ); + if ( iTxt.isValid() ) + { + QModelIndex iSrc = model->getBlock( model->getLink( iTxt, "Base Texture/Source" ), "NiSourceTexture" ); + if ( iSrc.isValid() ) + { + iDragTarget = model->getIndex( iSrc, "File Name" ); + if ( iDragTarget.isValid() ) + { + fnDragTexOrg = model->get( iDragTarget ); + model->set( iDragTarget, fnDragTex ); + e->accept(); + return; + } + } + } + } + } + + e->ignore(); +} + +void GLView::dropEvent( QDropEvent * e ) +{ + iDragTarget = QModelIndex(); + fnDragTex = fnDragTexOrg = QString(); + e->accept(); +} + +void GLView::dragLeaveEvent( QDragLeaveEvent * e ) +{ + if ( iDragTarget.isValid() ) + { + model->set( iDragTarget, fnDragTexOrg ); + iDragTarget = QModelIndex(); + fnDragTex = fnDragTexOrg = QString(); + } +} diff --git a/glview.h b/glview.h index 780d09709..d18447568 100644 --- a/glview.h +++ b/glview.h @@ -1,218 +1,218 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GLVIEW -#define GLVIEW - -#include -#include -#include -#include -#include -#include - -#include "nifmodel.h" - -class Scene; - -class QActionGroup; -class QComboBox; -class QMenu; -class QSettings; -class QToolBar; -class QTimer; - -class GLView : public QGLWidget -{ - Q_OBJECT - - GLView( const QGLFormat & format, const QGLWidget * shareWidget = 0 ); - ~GLView(); - -public: - static GLView * create(); - - QModelIndex indexAt( const QPoint & p, int cycle = 0 ); - - void move( float, float, float ); - void setPosition( float, float, float ); - void setPosition( Vector3 ); - - void setDistance( float ); - - void rotate( float, float, float ); - void setRotation( float, float, float ); - - void zoom( float ); - void setZoom( float ); - - bool highlight() const; - bool drawNodes() const; - - QSize minimumSizeHint() const { return QSize( 50, 50 ); } - QSize sizeHint() const { return QSize( 400, 400 ); } - - void center(); - - QMenu * createMenu() const; - QList toolbars() const; - - QActionGroup * grpView; - - QAction * aViewWalk; - QAction * aViewTop; - QAction * aViewFront; - QAction * aViewSide; - QAction * aViewFlip; - QAction * aViewPerspective; - QAction * aViewUser; - QAction * aViewUserSave; - - QAction * aAnimate; - QAction * aAnimPlay; - QAction * aAnimLoop; - QAction * aAnimSwitch; - - QToolBar * tAnim; - QComboBox * animGroups; - - QToolBar * tView; - - void save( QSettings & ); - void restore( const QSettings & ); - -public slots: - void setNif( NifModel * ); - - void setCurrentIndex( const QModelIndex & ); - - void sltTime( float ); - void sltSequence( const QString & ); - - void updateShaders(); - - void sltSaveUserView(); - -signals: - void clicked( const QModelIndex & ); - - void sigTime( float t, float mn, float mx ); - -protected: - void initializeGL(); - int pickGL( int x, int y ); - void resizeGL( int width, int height ); - void glProjection( int x = -1, int y = -1 ); - -#ifdef USE_GL_QPAINTER - void paintEvent( QPaintEvent * ); -#else - void paintGL(); -#endif - - void mousePressEvent( QMouseEvent * ); - void mouseReleaseEvent( QMouseEvent * ); - void mouseDoubleClickEvent( QMouseEvent * ); - void mouseMoveEvent( QMouseEvent * ); - void wheelEvent( QWheelEvent * ); - void keyPressEvent( QKeyEvent * ); - void keyReleaseEvent( QKeyEvent * ); - void focusOutEvent( QFocusEvent * ); - - void dragEnterEvent( QDragEnterEvent * ); - void dragMoveEvent( QDragMoveEvent * ); - void dropEvent( QDropEvent * ); - void dragLeaveEvent( QDragLeaveEvent * ); - -private slots: - void advanceGears(); - - void modelChanged(); - void modelLinked(); - void modelDestroyed(); - void dataChanged( const QModelIndex &, const QModelIndex & ); - - void checkActions(); - void viewAction( QAction * ); - -private: - QAction * checkedViewAction() const; - void uncheckViewAction(); - - Vector3 Pos; - Vector3 Rot; - float Dist; - - GLdouble Zoom; - - GLdouble axis; - - Transform viewTrans; - - int zInc; - - bool doCompile; - bool doCenter; - - QPoint lastPos; - QPoint pressPos; - - int cycleSelect; - - NifModel * model; - - Scene * scene; - - QTimer * timer; - - float time; - QTime lastTime; - - QHash kbd; - Vector3 mouseMov; - Vector3 mouseRot; - - int fpscnt; - float fpsact; - float fpsacc; - - class TexCache * textures; - - QPersistentModelIndex iDragTarget; - QString fnDragTex, fnDragTexOrg; - - QPoint popPos; -protected slots: - void popMenu(); -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GLVIEW +#define GLVIEW + +#include +#include +#include +#include +#include +#include + +#include "nifmodel.h" + +class Scene; + +class QActionGroup; +class QComboBox; +class QMenu; +class QSettings; +class QToolBar; +class QTimer; + +class GLView : public QGLWidget +{ + Q_OBJECT + + GLView( const QGLFormat & format, const QGLWidget * shareWidget = 0 ); + ~GLView(); + +public: + static GLView * create(); + + QModelIndex indexAt( const QPoint & p, int cycle = 0 ); + + void move( float, float, float ); + void setPosition( float, float, float ); + void setPosition( Vector3 ); + + void setDistance( float ); + + void rotate( float, float, float ); + void setRotation( float, float, float ); + + void zoom( float ); + void setZoom( float ); + + bool highlight() const; + bool drawNodes() const; + + QSize minimumSizeHint() const { return QSize( 50, 50 ); } + QSize sizeHint() const { return QSize( 400, 400 ); } + + void center(); + + QMenu * createMenu() const; + QList toolbars() const; + + QActionGroup * grpView; + + QAction * aViewWalk; + QAction * aViewTop; + QAction * aViewFront; + QAction * aViewSide; + QAction * aViewFlip; + QAction * aViewPerspective; + QAction * aViewUser; + QAction * aViewUserSave; + + QAction * aAnimate; + QAction * aAnimPlay; + QAction * aAnimLoop; + QAction * aAnimSwitch; + + QToolBar * tAnim; + QComboBox * animGroups; + + QToolBar * tView; + + void save( QSettings & ); + void restore( const QSettings & ); + +public slots: + void setNif( NifModel * ); + + void setCurrentIndex( const QModelIndex & ); + + void sltTime( float ); + void sltSequence( const QString & ); + + void updateShaders(); + + void sltSaveUserView(); + +signals: + void clicked( const QModelIndex & ); + + void sigTime( float t, float mn, float mx ); + +protected: + void initializeGL(); + int pickGL( int x, int y ); + void resizeGL( int width, int height ); + void glProjection( int x = -1, int y = -1 ); + +#ifdef USE_GL_QPAINTER + void paintEvent( QPaintEvent * ); +#else + void paintGL(); +#endif + + void mousePressEvent( QMouseEvent * ); + void mouseReleaseEvent( QMouseEvent * ); + void mouseDoubleClickEvent( QMouseEvent * ); + void mouseMoveEvent( QMouseEvent * ); + void wheelEvent( QWheelEvent * ); + void keyPressEvent( QKeyEvent * ); + void keyReleaseEvent( QKeyEvent * ); + void focusOutEvent( QFocusEvent * ); + + void dragEnterEvent( QDragEnterEvent * ); + void dragMoveEvent( QDragMoveEvent * ); + void dropEvent( QDropEvent * ); + void dragLeaveEvent( QDragLeaveEvent * ); + +private slots: + void advanceGears(); + + void modelChanged(); + void modelLinked(); + void modelDestroyed(); + void dataChanged( const QModelIndex &, const QModelIndex & ); + + void checkActions(); + void viewAction( QAction * ); + +private: + QAction * checkedViewAction() const; + void uncheckViewAction(); + + Vector3 Pos; + Vector3 Rot; + float Dist; + + GLdouble Zoom; + + GLdouble axis; + + Transform viewTrans; + + int zInc; + + bool doCompile; + bool doCenter; + + QPoint lastPos; + QPoint pressPos; + + int cycleSelect; + + NifModel * model; + + Scene * scene; + + QTimer * timer; + + float time; + QTime lastTime; + + QHash kbd; + Vector3 mouseMov; + Vector3 mouseRot; + + int fpscnt; + float fpsact; + float fpsacc; + + class TexCache * textures; + + QPersistentModelIndex iDragTarget; + QString fnDragTex, fnDragTexOrg; + + QPoint popPos; +protected slots: + void popMenu(); +}; + +#endif diff --git a/importex/3ds.cpp b/importex/3ds.cpp index 856ab10d0..50fb6c4b6 100644 --- a/importex/3ds.cpp +++ b/importex/3ds.cpp @@ -1,746 +1,746 @@ -#include "3ds.h" - -#include "../spellbook.h" - -#include "../NvTriStrip/qtwrapper.h" - -#include "../gl/gltex.h" - -#include -#include -#include -#include -#include -#include - -struct objPoint -{ - int v, t, n; - - bool operator==( const objPoint & other ) const - { - return v == other.v && t == other.t && n == other.n; - } -}; - -struct objFace -{ - int v1, v2, v3; - bool dblside; -}; - -struct objMaterial -{ - QString name; - Color3 Ka, Kd, Ks; - float alpha, glossiness; - QString map_Kd; - - objMaterial() : name( "Untextured" ), alpha( 1.0f ), glossiness( 15.0f ) {} -}; - -struct objMatFace { - QString matName; - QVector< short > subFaces; -}; - -// The 3ds file can be made up of several objects -struct objMesh { - QString name; // The object name - QVector vertices; // The array of vertices - QVector normals; // The array of the normals for the vertices - QVector texcoords; // The array of texture coordinates for the vertices - QVector faces; // The array of face indices - QVector matfaces; // The array of materials for this mesh - Vector3 pos; // The position to move the object to - Vector3 rot; // The angles to rotate the object - - objMesh() : pos( 0.0f, 0.0f, 0.0f ), rot( 0.0f, 0.0f, 0.0f ) {} -}; - -struct objKeyframe { - Vector3 pos; - float rotAngle; - Vector3 rotAxis; - float scale; - - objKeyframe() - : pos( 0.0f, 0.0f, 0.0f ), rotAngle( 0 ), rotAxis( 0.0f, 0.0f, 0.0f ), scale( 0.0f ) - {} -}; - -struct objKfSequence { - short objectId; - QString objectName; - long startTime, endTime, curTime; - Vector3 pivot; - QMap< short, objKeyframe > frames; - - objKfSequence() : pivot( 0.0f, 0.0f, 0.0f ) {} -}; - -static void addLink( NifModel * nif, QModelIndex iBlock, QString name, qint32 link ) -{ - QModelIndex iArray = nif->getIndex( iBlock, name ); - QModelIndex iSize = nif->getIndex( iBlock, QString( "Num %1" ).arg( name ) ); - int numIndices = nif->get( iSize ); - nif->set( iSize, numIndices + 1 ); - nif->updateArray( iArray ); - nif->setLink( iArray.child( numIndices, 0 ), link ); -} - -static Color3 GetColorFromChunk( Chunk * cnk ) -{ - float r = 1.0f; - float g = 1.0f; - float b = 1.0f; - - Chunk * ColorChunk; - - ColorChunk = cnk->getChild( COLOR_F ); - if( !ColorChunk ) { - ColorChunk = cnk->getChild( LIN_COLOR_F ); - } - if( ColorChunk ) { - r = ColorChunk->read< float >(); - g = ColorChunk->read< float >(); - b = ColorChunk->read< float >(); - } - - ColorChunk = cnk->getChild( COLOR_24 ); - if( !ColorChunk ) { - ColorChunk = cnk->getChild( LIN_COLOR_24 ); - } - if( ColorChunk ) { - r = (float)( ColorChunk->read< unsigned char >() ) / 255.0f; - g = (float)( ColorChunk->read< unsigned char >() ) / 255.0f; - b = (float)( ColorChunk->read< unsigned char >() ) / 255.0f; - } - - return Color3( r, g, b ); -} - -static float GetPercentageFromChunk( Chunk * cnk ) -{ - float f = 0.0f; - - Chunk * PercChunk = cnk->getChild( FLOAT_PERCENTAGE ); - if( PercChunk ) { - f = PercChunk->read< float >(); - } - - PercChunk = cnk->getChild( INT_PERCENTAGE ); - if( PercChunk ) { - f = (float)( PercChunk->read< unsigned short >() / 255.0f ); - } - - return f; -} - - -void import3ds( NifModel * nif, const QModelIndex & index ) -{ - - //--Determine how the file will import, and be sure the user wants to continue--// - - // If no existing node is selected, create a group node. Otherwise use selected node - QPersistentModelIndex iRoot, iNode, iShape, iMaterial, iData, iTexProp, iTexSource; - QModelIndex iBlock = nif->getBlock( index ); - - //Be sure the user hasn't clicked on a NiTriStrips object - if ( iBlock.isValid() && nif->itemName(iBlock) == "NiTriStrips" ) - { - int result = QMessageBox::information( 0, "Import OBJ", "You cannot import an OBJ file over a NiTriStrips object. Please convert it to a NiTriShape object first by right-clicking and choosing Mesh > Triangulate" ); - return; - } - - if ( iBlock.isValid() && nif->itemName( index ) == "NiNode" ) - { - iNode = index; - } - else if ( iBlock.isValid() && nif->itemName( index ) == "NiTriShape" ) - { - iShape = index; - //Find parent of NiTriShape - int par_num = nif->getParent( nif->getBlockNumber( index ) ); - if ( par_num != -1 ) - { - iNode = nif->getBlock( par_num ); - } - - //Find material, texture, and data objects - QList children = nif->getChildLinks( nif->getBlockNumber(iShape) ); - for( QList::iterator it = children.begin(); it != children.end(); ++it ) - { - if ( *it != -1 ) - { - QModelIndex temp = nif->getBlock( *it ); - QString type = nif->itemName( temp ); - if ( type == "NiMaterialProperty" ) - { - iMaterial = temp; - } - else if ( type == "NiTriShapeData" ) - { - iData = temp; - } - else if ( (type == "NiTexturingProperty") || (type == "NiTextureProperty") ) - { - iTexProp = temp; - - //Search children of texture property for texture sources/images - QList children = nif->getChildLinks( nif->getBlockNumber(iTexProp) ); - for( QList::iterator it = children.begin(); it != children.end(); ++it ) - { - QModelIndex temp = nif->getBlock( *it ); - QString type = nif->itemName( temp ); - if ( (type == "NiSourceTexture") || (type == "NiImage") ) - { - iTexSource = temp; - } - } - } - } - } - } - - QString question; - - if ( iNode.isValid() == true ) - { - if ( iShape.isValid() == true ) - { - question = "NiTriShape selected. The first imported mesh will replace the selected one."; - } - else - { - question = "NiNode selected. Meshes will be attached to the selected node."; - } - } - else - { - question = "No NiNode or NiTriShape selected. Meshes will be imported to the root of the file."; - } - - int result = QMessageBox::question( 0, "Import 3DS", question, QMessageBox::Ok, QMessageBox::Cancel ); - if ( result == QMessageBox::Cancel ) { - return; - } - - - //--Read the file--// - - float ObjScale; - QVector< objMesh > ObjMeshes; - QMap< QString, objMaterial > ObjMaterials; - QMap< QString, objKfSequence > ObjKeyframes; - - QSettings settings; - settings.beginGroup( "import-export" ); - settings.beginGroup( "3ds" ); - - QString fname = QFileDialog::getOpenFileName( 0, Spell::tr("Choose a .3ds file to import"), settings.value( Spell::tr("File Name") ).toString(), "*.3ds" ); - if ( fname.isEmpty() ) { - return; - } - - QFile fobj( fname ); - if ( !fobj.open( QIODevice::ReadOnly ) ) - { - qWarning() << Spell::tr("Could not open %1 for read access").arg( fobj.fileName() ); - return; - } - - Chunk * FileChunk = Chunk::LoadFile( &fobj ); - if( !FileChunk ) { - qWarning() << Spell::tr("Could not get 3ds data"); - return; - } - - Chunk * Model = FileChunk->getChild( M3DMAGIC ); - if( !Model ) { - qWarning() << Spell::tr("Could not get 3ds model"); - return; - } - - Chunk * ModelData = Model->getChild( MDATA ); - if( !ModelData ) { - qWarning() << Spell::tr("Could not get 3ds model data"); - return; - } - - Chunk * MasterScale = ModelData->getChild( MASTER_SCALE ); - if( MasterScale ) { - ObjScale = MasterScale->read< float >(); - } - else { - ObjScale = 1.0f; - } - - QList< Chunk * > Materials = ModelData->getChildren( MATERIAL ); - QList< Chunk * > Meshes = ModelData->getChildren( NAMED_OBJECT ); - - foreach( Chunk * mat, Materials ) - { - objMaterial newMat; - - // material name - Chunk * matName = mat->getChild( MAT_NAME ); - if( matName ) { - newMat.name = matName->readString(); - } - - // material colors - Chunk * matColor = mat->getChild( MAT_AMBIENT ); - if( matColor ) { - newMat.Ka = GetColorFromChunk( matColor ); - } - - matColor = mat->getChild( MAT_DIFFUSE ); - if( matColor ) { - newMat.Kd = GetColorFromChunk( matColor ); - } - - matColor = mat->getChild( MAT_SPECULAR ); - if( matColor ) { - newMat.Ks = GetColorFromChunk( matColor ); - } - - // material textures - Chunk * matTexture = mat->getChild( MAT_TEXMAP ); - if( matTexture ) { - Chunk * matTexProperty = matTexture->getChild( MAT_MAPNAME ); - if( matTexProperty ) { - newMat.map_Kd = matTexProperty->readString(); - } - } - - // material alpha - Chunk * matAlpha = mat->getChild( MAT_TRANSPARENCY ); - if( matAlpha ) { - newMat.alpha = 1.0f - GetPercentageFromChunk( matAlpha ); - } - - ObjMaterials.insert( newMat.name, newMat ); - } - - foreach( Chunk * mesh, Meshes ) - { - objMesh newMesh; - - newMesh.name = mesh->readString(); - - foreach( Chunk * TriObj, mesh->getChildren( N_TRI_OBJECT ) ) - { - Chunk * PointArray = TriObj->getChild( POINT_ARRAY ); - if( PointArray ) { - unsigned short nPoints = PointArray->read< unsigned short >(); - - for( unsigned short i = 0; i < nPoints; i++ ) - { - float x, y, z; - x = PointArray->read< float >(); - y = PointArray->read< float >(); - z = PointArray->read< float >(); - - newMesh.vertices.append( Vector3( x, y, z ) ); - newMesh.normals.append( Vector3( 0.0f, 0.0f, 1.0f ) ); - } - } - - Chunk * FaceArray = TriObj->getChild( FACE_ARRAY ); - if( FaceArray ) { - - unsigned short nFaces = FaceArray->read< unsigned short >(); - - for( unsigned short i = 0; i < nFaces; i++ ) - { - Chunk::ChunkTypeFaceArray f; - - f.vertex1 = FaceArray->read< unsigned short >(); - f.vertex2 = FaceArray->read< unsigned short >(); - f.vertex3 = FaceArray->read< unsigned short >(); - f.flags = FaceArray->read< unsigned short >(); - - objFace newFace; - - newFace.v1 = f.vertex1; - newFace.v2 = f.vertex2; - newFace.v3 = f.vertex3; - - newFace.dblside = !(f.flags & FACE_FLAG_ONESIDE); - - Vector3 n1 = newMesh.vertices[newFace.v2] - newMesh.vertices[newFace.v1]; - Vector3 n2 = newMesh.vertices[newFace.v3] - newMesh.vertices[newFace.v1]; - Vector3 FaceNormal = Vector3::crossproduct(n1, n2); - FaceNormal.normalize(); - newMesh.normals[newFace.v1] += FaceNormal; - newMesh.normals[newFace.v2] += FaceNormal; - newMesh.normals[newFace.v3] += FaceNormal; - - newMesh.faces.append( newFace ); - - } - - objMatFace newMatFace; - - foreach( Chunk * MatFaces, FaceArray->getChildren( MSH_MAT_GROUP ) ) - { - //Chunk * MatFaces = FaceArray->getChild( MSH_MAT_GROUP ); - if( MatFaces ) { - newMatFace.matName = MatFaces->readString(); - - unsigned short nFaces = MatFaces->read< unsigned short >(); - - for( unsigned short i = 0; i < nFaces; i++ ) { - unsigned short FaceNum = MatFaces->read< unsigned short >(); - newMatFace.subFaces.append( FaceNum ); - } - - newMesh.matfaces.append( newMatFace ); - } - } - } - - Chunk * TexVerts = TriObj->getChild( TEX_VERTS ); - if( TexVerts ) { - unsigned short nVerts = TexVerts->read< unsigned short >(); - - for( unsigned short i = 0; i < nVerts; i++ ) - { - float x, y; - x = TexVerts->read< float >(); - y = TexVerts->read< float >(); - - newMesh.texcoords.append( Vector2( x, -y ) ); - } - } - - } - - for( int i = 0; i < newMesh.normals.size(); i++ ) - { - newMesh.normals[i].normalize(); - } - - ObjMeshes.append( newMesh ); - } - - Chunk * Keyframes = Model->getChild( KFDATA ); - if( Keyframes ) { - if( Chunk * KfHdr = Keyframes->getChild( KFHDR ) ) - { - - } - - QList< Chunk * > KfSegs = Keyframes->getChildren( KFSEG ); - QList< Chunk * > KfCurTimes = Keyframes->getChildren( KFCURTIME ); - - for( int i = 0; i < KfSegs.size(); i++ ) - { - /* - Chunk::ChunkData * rawData = KfSegs[i]->getData(); - newKfSeg.startTime = *( (long *) rawData ); - rawData += sizeof( long ); - newKfSeg.endTime = *( (long *) rawData ); - KfSegs[i]->clearData(); - - Chunk * KfCurTime = KfCurTimes[i]; - - rawData = KfCurTimes[i]->getData(); - newKfSeg.curTime = *( (long *) rawData ); - KfCurTimes[i]->clearData(); - */ - } - - foreach( Chunk * KfObj, Keyframes->getChildren( OBJECT_NODE_TAG ) ) - { - objKfSequence newKfSeq; - - if( Chunk * NodeId = KfObj->getChild( NODE_ID ) ) { - newKfSeq.objectId = NodeId->read< unsigned short >(); - } - - if( Chunk * NodeHdr = KfObj->getChild( NODE_HDR ) ) { - newKfSeq.objectName = NodeHdr->readString(); - - unsigned short Flags1 = NodeHdr->read< unsigned short >(); - unsigned short Flags2 = NodeHdr->read< unsigned short >(); - unsigned short Hierarchy = NodeHdr->read< unsigned short >(); - } - - if( Chunk * Pivot = KfObj->getChild( PIVOT ) ) { - float x = Pivot->read< float >(); - float y = Pivot->read< float >(); - float z = Pivot->read< float >(); - - newKfSeq.pivot = Vector3( x, y, z ); - } - - if( Chunk * PosTrack = KfObj->getChild( POS_TRACK_TAG ) ) { - unsigned short flags = PosTrack->read< unsigned short >(); - - unsigned short unknown1 = PosTrack->read< unsigned short >(); - unsigned short unknown2 = PosTrack->read< unsigned short >(); - unsigned short unknown3 = PosTrack->read< unsigned short >(); - unsigned short unknown4 = PosTrack->read< unsigned short >(); - - unsigned short keys = PosTrack->read< unsigned short >(); - - unsigned short unknown = PosTrack->read< unsigned short >(); - - for( int key = 0; key < keys; key++ ) - { - unsigned short kfNum = PosTrack->read< unsigned short >(); - unsigned long kfUnknown = PosTrack->read< unsigned long >(); - float kfPosX = PosTrack->read< float >(); - float kfPosY = PosTrack->read< float >(); - float kfPosZ = PosTrack->read< float >(); - - newKfSeq.frames[kfNum].pos = Vector3( kfPosX, kfPosY, kfPosZ ); - } - } - - if( Chunk * RotTrack = KfObj->getChild( ROT_TRACK_TAG ) ) { - unsigned short flags = RotTrack->read< unsigned short >(); - - unsigned short unknown1 = RotTrack->read< unsigned short >(); - unsigned short unknown2 = RotTrack->read< unsigned short >(); - unsigned short unknown3 = RotTrack->read< unsigned short >(); - unsigned short unknown4 = RotTrack->read< unsigned short >(); - - unsigned short keys = RotTrack->read< unsigned short >(); - - unsigned short unknown = RotTrack->read< unsigned short >(); - - for( unsigned short key = 0; key < keys; key++ ) - { - unsigned short kfNum = RotTrack->read< unsigned short >(); - unsigned long kfUnknown = RotTrack->read< unsigned long >(); - float kfRotAngle = RotTrack->read< float >(); - float kfAxisX = RotTrack->read< float >(); - float kfAxisY = RotTrack->read< float >(); - float kfAxisZ = RotTrack->read< float >(); - - newKfSeq.frames[kfNum].rotAngle = kfRotAngle; - newKfSeq.frames[kfNum].rotAxis = Vector3( kfAxisX, kfAxisY, kfAxisZ ); - } - } - - if( Chunk * SclTrack = KfObj->getChild( SCL_TRACK_TAG ) ) { - unsigned short flags = SclTrack->read< unsigned short >(); - - unsigned short unknown1 = SclTrack->read< unsigned short >(); - unsigned short unknown2 = SclTrack->read< unsigned short >(); - unsigned short unknown3 = SclTrack->read< unsigned short >(); - unsigned short unknown4 = SclTrack->read< unsigned short >(); - - unsigned short keys = SclTrack->read< unsigned short >(); - - unsigned short unknown = SclTrack->read< unsigned short >(); - - for( unsigned short key = 0; key < keys; key++ ) - { - unsigned short kfNum = SclTrack->read< unsigned short >(); - unsigned long kfUnknown = SclTrack->read< unsigned long >(); - float kfSclX = SclTrack->read< float >(); - float kfSclY = SclTrack->read< float >(); - float kfSclZ = SclTrack->read< float >(); - - newKfSeq.frames[kfNum].scale = ( kfSclX + kfSclY + kfSclZ ) / 3.0f; - } - } - - ObjKeyframes.insertMulti( newKfSeq.objectName, newKfSeq ); - } - } - - fobj.close(); - - //--Translate file structures into NIF ones--// - - if ( iNode.isValid() == false ) - { - iNode = nif->insertNiBlock( "NiNode" ); - nif->set( iNode, "Name", "Scene Root" ); - } - - //Record root object - iRoot = iNode; - - // create a NiTriShape foreach material in the object - for(int objIndex = 0; objIndex < ObjMeshes.size(); objIndex++) { - objMesh * mesh = &ObjMeshes[objIndex]; - - - - // create group node if there is more than 1 material - bool groupNode = false; - QPersistentModelIndex iNode = iRoot; - if ( mesh->matfaces.size() > 1 ) - { - groupNode = true; - - iNode = nif->insertNiBlock( "NiNode" ); - nif->set( iNode, "Name", mesh->name ); - addLink( nif, iRoot, "Children", nif->getBlockNumber( iNode ) ); - } - - int shapecount = 0; - for( int i = 0; i < mesh->matfaces.size(); i++ ) - { - if ( !ObjMaterials.contains( mesh->matfaces[i].matName ) ) { - qWarning() << Spell::tr("Material '%1' not found in list!").arg( mesh->matfaces[i].matName ); - } - - objMaterial * mat = &ObjMaterials[mesh->matfaces[i].matName]; - - if ( iShape.isValid() == false || objIndex != 0 ) - { - iShape = nif->insertNiBlock( "NiTriShape" ); - } - if ( groupNode ) - { - nif->set( iShape, "Name", QString( "%1:%2" ).arg( nif->get( iNode, "Name" ) ).arg( shapecount++ ) ); - addLink( nif, iNode, "Children", nif->getBlockNumber( iShape ) ); - } - else - { - nif->set( iShape, "Name", mesh->name ); - addLink( nif, iRoot, "Children", nif->getBlockNumber( iShape ) ); - } - - if ( iMaterial.isValid() == false || objIndex != 0 ) - { - iMaterial = nif->insertNiBlock( "NiMaterialProperty" ); - } - nif->set( iMaterial, "Name", mat->name ); - nif->set( iMaterial, "Ambient Color", mat->Ka ); - nif->set( iMaterial, "Diffuse Color", mat->Kd ); - nif->set( iMaterial, "Specular Color", mat->Ks ); - nif->set( iMaterial, "Emissive Color", Color3( 0, 0, 0 ) ); - nif->set( iMaterial, "Alpha", mat->alpha ); - nif->set( iMaterial, "Glossiness", mat->glossiness ); - - addLink( nif, iShape, "Properties", nif->getBlockNumber( iMaterial ) ); - - if ( !mat->map_Kd.isEmpty() ) - { - if ( nif->getVersionNumber() >= 0x0303000D ) - { - //Newer versions use NiTexturingProperty and NiSourceTexture - if ( iTexProp.isValid() == false || objIndex != 0 || nif->itemType(iTexProp) != "NiTexturingProperty" ) - { - iTexProp = nif->insertNiBlock( "NiTexturingProperty" ); - } - addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); - - nif->set( iTexProp, "Has Base Texture", 1 ); - QModelIndex iBaseMap = nif->getIndex( iTexProp, "Base Texture" ); - nif->set( iBaseMap, "Clamp Mode", 3 ); - nif->set( iBaseMap, "Filter Mode", 2 ); - - if ( iTexSource.isValid() == false || objIndex != 0 || nif->itemType(iTexSource) != "NiSourceTexture" ) - { - iTexSource = nif->insertNiBlock( "NiSourceTexture" ); - } - nif->setLink( iBaseMap, "Source", nif->getBlockNumber( iTexSource ) ); - - nif->set( iTexSource, "Pixel Layout", nif->getVersion() == "20.0.0.5" ? 6 : 5 ); - nif->set( iTexSource, "Use Mipmaps", 2 ); - nif->set( iTexSource, "Alpha Format", 3 ); - nif->set( iTexSource, "Unknown Byte", 1 ); - nif->set( iTexSource, "Unknown Byte 2", 1 ); - - nif->set( iTexSource, "Use External", 1 ); - nif->set( iTexSource, "File Name", mat->map_Kd ); - } - else - { - //Older versions use NiTextureProperty and NiImage - if ( iTexProp.isValid() == false || objIndex != 0 || nif->itemType(iTexProp) != "NiTextureProperty" ) - { - iTexProp = nif->insertNiBlock( "NiTextureProperty" ); - } - addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); - - if ( iTexSource.isValid() == false || objIndex != 0 || nif->itemType(iTexSource) != "NiImage" ) - { - iTexSource = nif->insertNiBlock( "NiImage" ); - } - - nif->setLink( iTexProp, "Image", nif->getBlockNumber( iTexSource ) ); - - nif->set( iTexSource, "External", 1 ); - nif->set( iTexSource, "File Name", mat->map_Kd ); - } - } - - if ( iData.isValid() == false || objIndex != 0 ) - { - iData = nif->insertNiBlock( "NiTriShapeData" ); - } - nif->setLink( iShape, "Data", nif->getBlockNumber( iData ) ); - - QVector< Triangle > triangles; - QVector< objPoint > points; - - foreach( short faceIndex, mesh->matfaces[i].subFaces ) - { - objFace face = mesh->faces[faceIndex]; - - Triangle tri; - - tri.set( face.v1, face.v2, face.v3 ); - - triangles.append( tri ); - } - - nif->set( iData, "Num Vertices", mesh->vertices.count() ); - nif->set( iData, "Has Vertices", 1 ); - nif->updateArray( iData, "Vertices" ); - nif->setArray( iData, "Vertices", mesh->vertices ); - nif->set( iData, "Has Normals", 1 ); - nif->updateArray( iData, "Normals" ); - nif->setArray( iData, "Normals", mesh->normals ); - nif->set( iData, "Has UV", 1 ); - nif->set( iData, "Num UV Sets", 1 ); - nif->set( iData, "Num UV Sets 2", 1 ); - QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); - if ( !iTexCo.isValid() ) { - iTexCo = nif->getIndex( iData, "UV Sets 2" ); - } - nif->updateArray( iTexCo ); - nif->updateArray( iTexCo.child( 0, 0 ) ); - nif->setArray( iTexCo.child( 0, 0 ), mesh->texcoords ); - - nif->set( iData, "Has Triangles", 1 ); - nif->set( iData, "Num Triangles", triangles.count() ); - nif->set( iData, "Num Triangle Points", triangles.count() * 3 ); - nif->updateArray( iData, "Triangles" ); - nif->setArray( iData, "Triangles", triangles ); - - Vector3 center; - foreach ( Vector3 v, mesh->vertices ) - center += v; - if ( mesh->vertices.count() > 0 ) center /= mesh->vertices.count(); - nif->set( iData, "Center", center ); - float radius = 0; - foreach ( Vector3 v, mesh->vertices ) - { - float d = ( center - v ).length(); - if ( d > radius ) radius = d; - } - nif->set( iData, "Radius", radius ); - - nif->set( iData, "Unknown Short 2", 0x4000 ); - } - - // set up a controller for animated objects - } - - settings.setValue( "File Name", fname ); - - nif->reset(); - return; -} +#include "3ds.h" + +#include "../spellbook.h" + +#include "../NvTriStrip/qtwrapper.h" + +#include "../gl/gltex.h" + +#include +#include +#include +#include +#include +#include + +struct objPoint +{ + int v, t, n; + + bool operator==( const objPoint & other ) const + { + return v == other.v && t == other.t && n == other.n; + } +}; + +struct objFace +{ + int v1, v2, v3; + bool dblside; +}; + +struct objMaterial +{ + QString name; + Color3 Ka, Kd, Ks; + float alpha, glossiness; + QString map_Kd; + + objMaterial() : name( "Untextured" ), alpha( 1.0f ), glossiness( 15.0f ) {} +}; + +struct objMatFace { + QString matName; + QVector< short > subFaces; +}; + +// The 3ds file can be made up of several objects +struct objMesh { + QString name; // The object name + QVector vertices; // The array of vertices + QVector normals; // The array of the normals for the vertices + QVector texcoords; // The array of texture coordinates for the vertices + QVector faces; // The array of face indices + QVector matfaces; // The array of materials for this mesh + Vector3 pos; // The position to move the object to + Vector3 rot; // The angles to rotate the object + + objMesh() : pos( 0.0f, 0.0f, 0.0f ), rot( 0.0f, 0.0f, 0.0f ) {} +}; + +struct objKeyframe { + Vector3 pos; + float rotAngle; + Vector3 rotAxis; + float scale; + + objKeyframe() + : pos( 0.0f, 0.0f, 0.0f ), rotAngle( 0 ), rotAxis( 0.0f, 0.0f, 0.0f ), scale( 0.0f ) + {} +}; + +struct objKfSequence { + short objectId; + QString objectName; + long startTime, endTime, curTime; + Vector3 pivot; + QMap< short, objKeyframe > frames; + + objKfSequence() : pivot( 0.0f, 0.0f, 0.0f ) {} +}; + +static void addLink( NifModel * nif, QModelIndex iBlock, QString name, qint32 link ) +{ + QModelIndex iArray = nif->getIndex( iBlock, name ); + QModelIndex iSize = nif->getIndex( iBlock, QString( "Num %1" ).arg( name ) ); + int numIndices = nif->get( iSize ); + nif->set( iSize, numIndices + 1 ); + nif->updateArray( iArray ); + nif->setLink( iArray.child( numIndices, 0 ), link ); +} + +static Color3 GetColorFromChunk( Chunk * cnk ) +{ + float r = 1.0f; + float g = 1.0f; + float b = 1.0f; + + Chunk * ColorChunk; + + ColorChunk = cnk->getChild( COLOR_F ); + if( !ColorChunk ) { + ColorChunk = cnk->getChild( LIN_COLOR_F ); + } + if( ColorChunk ) { + r = ColorChunk->read< float >(); + g = ColorChunk->read< float >(); + b = ColorChunk->read< float >(); + } + + ColorChunk = cnk->getChild( COLOR_24 ); + if( !ColorChunk ) { + ColorChunk = cnk->getChild( LIN_COLOR_24 ); + } + if( ColorChunk ) { + r = (float)( ColorChunk->read< unsigned char >() ) / 255.0f; + g = (float)( ColorChunk->read< unsigned char >() ) / 255.0f; + b = (float)( ColorChunk->read< unsigned char >() ) / 255.0f; + } + + return Color3( r, g, b ); +} + +static float GetPercentageFromChunk( Chunk * cnk ) +{ + float f = 0.0f; + + Chunk * PercChunk = cnk->getChild( FLOAT_PERCENTAGE ); + if( PercChunk ) { + f = PercChunk->read< float >(); + } + + PercChunk = cnk->getChild( INT_PERCENTAGE ); + if( PercChunk ) { + f = (float)( PercChunk->read< unsigned short >() / 255.0f ); + } + + return f; +} + + +void import3ds( NifModel * nif, const QModelIndex & index ) +{ + + //--Determine how the file will import, and be sure the user wants to continue--// + + // If no existing node is selected, create a group node. Otherwise use selected node + QPersistentModelIndex iRoot, iNode, iShape, iMaterial, iData, iTexProp, iTexSource; + QModelIndex iBlock = nif->getBlock( index ); + + //Be sure the user hasn't clicked on a NiTriStrips object + if ( iBlock.isValid() && nif->itemName(iBlock) == "NiTriStrips" ) + { + int result = QMessageBox::information( 0, "Import OBJ", "You cannot import an OBJ file over a NiTriStrips object. Please convert it to a NiTriShape object first by right-clicking and choosing Mesh > Triangulate" ); + return; + } + + if ( iBlock.isValid() && nif->itemName( index ) == "NiNode" ) + { + iNode = index; + } + else if ( iBlock.isValid() && nif->itemName( index ) == "NiTriShape" ) + { + iShape = index; + //Find parent of NiTriShape + int par_num = nif->getParent( nif->getBlockNumber( index ) ); + if ( par_num != -1 ) + { + iNode = nif->getBlock( par_num ); + } + + //Find material, texture, and data objects + QList children = nif->getChildLinks( nif->getBlockNumber(iShape) ); + for( QList::iterator it = children.begin(); it != children.end(); ++it ) + { + if ( *it != -1 ) + { + QModelIndex temp = nif->getBlock( *it ); + QString type = nif->itemName( temp ); + if ( type == "NiMaterialProperty" ) + { + iMaterial = temp; + } + else if ( type == "NiTriShapeData" ) + { + iData = temp; + } + else if ( (type == "NiTexturingProperty") || (type == "NiTextureProperty") ) + { + iTexProp = temp; + + //Search children of texture property for texture sources/images + QList children = nif->getChildLinks( nif->getBlockNumber(iTexProp) ); + for( QList::iterator it = children.begin(); it != children.end(); ++it ) + { + QModelIndex temp = nif->getBlock( *it ); + QString type = nif->itemName( temp ); + if ( (type == "NiSourceTexture") || (type == "NiImage") ) + { + iTexSource = temp; + } + } + } + } + } + } + + QString question; + + if ( iNode.isValid() == true ) + { + if ( iShape.isValid() == true ) + { + question = "NiTriShape selected. The first imported mesh will replace the selected one."; + } + else + { + question = "NiNode selected. Meshes will be attached to the selected node."; + } + } + else + { + question = "No NiNode or NiTriShape selected. Meshes will be imported to the root of the file."; + } + + int result = QMessageBox::question( 0, "Import 3DS", question, QMessageBox::Ok, QMessageBox::Cancel ); + if ( result == QMessageBox::Cancel ) { + return; + } + + + //--Read the file--// + + float ObjScale; + QVector< objMesh > ObjMeshes; + QMap< QString, objMaterial > ObjMaterials; + QMap< QString, objKfSequence > ObjKeyframes; + + QSettings settings; + settings.beginGroup( "import-export" ); + settings.beginGroup( "3ds" ); + + QString fname = QFileDialog::getOpenFileName( 0, Spell::tr("Choose a .3ds file to import"), settings.value( Spell::tr("File Name") ).toString(), "*.3ds" ); + if ( fname.isEmpty() ) { + return; + } + + QFile fobj( fname ); + if ( !fobj.open( QIODevice::ReadOnly ) ) + { + qWarning() << Spell::tr("Could not open %1 for read access").arg( fobj.fileName() ); + return; + } + + Chunk * FileChunk = Chunk::LoadFile( &fobj ); + if( !FileChunk ) { + qWarning() << Spell::tr("Could not get 3ds data"); + return; + } + + Chunk * Model = FileChunk->getChild( M3DMAGIC ); + if( !Model ) { + qWarning() << Spell::tr("Could not get 3ds model"); + return; + } + + Chunk * ModelData = Model->getChild( MDATA ); + if( !ModelData ) { + qWarning() << Spell::tr("Could not get 3ds model data"); + return; + } + + Chunk * MasterScale = ModelData->getChild( MASTER_SCALE ); + if( MasterScale ) { + ObjScale = MasterScale->read< float >(); + } + else { + ObjScale = 1.0f; + } + + QList< Chunk * > Materials = ModelData->getChildren( MATERIAL ); + QList< Chunk * > Meshes = ModelData->getChildren( NAMED_OBJECT ); + + foreach( Chunk * mat, Materials ) + { + objMaterial newMat; + + // material name + Chunk * matName = mat->getChild( MAT_NAME ); + if( matName ) { + newMat.name = matName->readString(); + } + + // material colors + Chunk * matColor = mat->getChild( MAT_AMBIENT ); + if( matColor ) { + newMat.Ka = GetColorFromChunk( matColor ); + } + + matColor = mat->getChild( MAT_DIFFUSE ); + if( matColor ) { + newMat.Kd = GetColorFromChunk( matColor ); + } + + matColor = mat->getChild( MAT_SPECULAR ); + if( matColor ) { + newMat.Ks = GetColorFromChunk( matColor ); + } + + // material textures + Chunk * matTexture = mat->getChild( MAT_TEXMAP ); + if( matTexture ) { + Chunk * matTexProperty = matTexture->getChild( MAT_MAPNAME ); + if( matTexProperty ) { + newMat.map_Kd = matTexProperty->readString(); + } + } + + // material alpha + Chunk * matAlpha = mat->getChild( MAT_TRANSPARENCY ); + if( matAlpha ) { + newMat.alpha = 1.0f - GetPercentageFromChunk( matAlpha ); + } + + ObjMaterials.insert( newMat.name, newMat ); + } + + foreach( Chunk * mesh, Meshes ) + { + objMesh newMesh; + + newMesh.name = mesh->readString(); + + foreach( Chunk * TriObj, mesh->getChildren( N_TRI_OBJECT ) ) + { + Chunk * PointArray = TriObj->getChild( POINT_ARRAY ); + if( PointArray ) { + unsigned short nPoints = PointArray->read< unsigned short >(); + + for( unsigned short i = 0; i < nPoints; i++ ) + { + float x, y, z; + x = PointArray->read< float >(); + y = PointArray->read< float >(); + z = PointArray->read< float >(); + + newMesh.vertices.append( Vector3( x, y, z ) ); + newMesh.normals.append( Vector3( 0.0f, 0.0f, 1.0f ) ); + } + } + + Chunk * FaceArray = TriObj->getChild( FACE_ARRAY ); + if( FaceArray ) { + + unsigned short nFaces = FaceArray->read< unsigned short >(); + + for( unsigned short i = 0; i < nFaces; i++ ) + { + Chunk::ChunkTypeFaceArray f; + + f.vertex1 = FaceArray->read< unsigned short >(); + f.vertex2 = FaceArray->read< unsigned short >(); + f.vertex3 = FaceArray->read< unsigned short >(); + f.flags = FaceArray->read< unsigned short >(); + + objFace newFace; + + newFace.v1 = f.vertex1; + newFace.v2 = f.vertex2; + newFace.v3 = f.vertex3; + + newFace.dblside = !(f.flags & FACE_FLAG_ONESIDE); + + Vector3 n1 = newMesh.vertices[newFace.v2] - newMesh.vertices[newFace.v1]; + Vector3 n2 = newMesh.vertices[newFace.v3] - newMesh.vertices[newFace.v1]; + Vector3 FaceNormal = Vector3::crossproduct(n1, n2); + FaceNormal.normalize(); + newMesh.normals[newFace.v1] += FaceNormal; + newMesh.normals[newFace.v2] += FaceNormal; + newMesh.normals[newFace.v3] += FaceNormal; + + newMesh.faces.append( newFace ); + + } + + objMatFace newMatFace; + + foreach( Chunk * MatFaces, FaceArray->getChildren( MSH_MAT_GROUP ) ) + { + //Chunk * MatFaces = FaceArray->getChild( MSH_MAT_GROUP ); + if( MatFaces ) { + newMatFace.matName = MatFaces->readString(); + + unsigned short nFaces = MatFaces->read< unsigned short >(); + + for( unsigned short i = 0; i < nFaces; i++ ) { + unsigned short FaceNum = MatFaces->read< unsigned short >(); + newMatFace.subFaces.append( FaceNum ); + } + + newMesh.matfaces.append( newMatFace ); + } + } + } + + Chunk * TexVerts = TriObj->getChild( TEX_VERTS ); + if( TexVerts ) { + unsigned short nVerts = TexVerts->read< unsigned short >(); + + for( unsigned short i = 0; i < nVerts; i++ ) + { + float x, y; + x = TexVerts->read< float >(); + y = TexVerts->read< float >(); + + newMesh.texcoords.append( Vector2( x, -y ) ); + } + } + + } + + for( int i = 0; i < newMesh.normals.size(); i++ ) + { + newMesh.normals[i].normalize(); + } + + ObjMeshes.append( newMesh ); + } + + Chunk * Keyframes = Model->getChild( KFDATA ); + if( Keyframes ) { + if( Chunk * KfHdr = Keyframes->getChild( KFHDR ) ) + { + + } + + QList< Chunk * > KfSegs = Keyframes->getChildren( KFSEG ); + QList< Chunk * > KfCurTimes = Keyframes->getChildren( KFCURTIME ); + + for( int i = 0; i < KfSegs.size(); i++ ) + { + /* + Chunk::ChunkData * rawData = KfSegs[i]->getData(); + newKfSeg.startTime = *( (long *) rawData ); + rawData += sizeof( long ); + newKfSeg.endTime = *( (long *) rawData ); + KfSegs[i]->clearData(); + + Chunk * KfCurTime = KfCurTimes[i]; + + rawData = KfCurTimes[i]->getData(); + newKfSeg.curTime = *( (long *) rawData ); + KfCurTimes[i]->clearData(); + */ + } + + foreach( Chunk * KfObj, Keyframes->getChildren( OBJECT_NODE_TAG ) ) + { + objKfSequence newKfSeq; + + if( Chunk * NodeId = KfObj->getChild( NODE_ID ) ) { + newKfSeq.objectId = NodeId->read< unsigned short >(); + } + + if( Chunk * NodeHdr = KfObj->getChild( NODE_HDR ) ) { + newKfSeq.objectName = NodeHdr->readString(); + + unsigned short Flags1 = NodeHdr->read< unsigned short >(); + unsigned short Flags2 = NodeHdr->read< unsigned short >(); + unsigned short Hierarchy = NodeHdr->read< unsigned short >(); + } + + if( Chunk * Pivot = KfObj->getChild( PIVOT ) ) { + float x = Pivot->read< float >(); + float y = Pivot->read< float >(); + float z = Pivot->read< float >(); + + newKfSeq.pivot = Vector3( x, y, z ); + } + + if( Chunk * PosTrack = KfObj->getChild( POS_TRACK_TAG ) ) { + unsigned short flags = PosTrack->read< unsigned short >(); + + unsigned short unknown1 = PosTrack->read< unsigned short >(); + unsigned short unknown2 = PosTrack->read< unsigned short >(); + unsigned short unknown3 = PosTrack->read< unsigned short >(); + unsigned short unknown4 = PosTrack->read< unsigned short >(); + + unsigned short keys = PosTrack->read< unsigned short >(); + + unsigned short unknown = PosTrack->read< unsigned short >(); + + for( int key = 0; key < keys; key++ ) + { + unsigned short kfNum = PosTrack->read< unsigned short >(); + unsigned long kfUnknown = PosTrack->read< unsigned long >(); + float kfPosX = PosTrack->read< float >(); + float kfPosY = PosTrack->read< float >(); + float kfPosZ = PosTrack->read< float >(); + + newKfSeq.frames[kfNum].pos = Vector3( kfPosX, kfPosY, kfPosZ ); + } + } + + if( Chunk * RotTrack = KfObj->getChild( ROT_TRACK_TAG ) ) { + unsigned short flags = RotTrack->read< unsigned short >(); + + unsigned short unknown1 = RotTrack->read< unsigned short >(); + unsigned short unknown2 = RotTrack->read< unsigned short >(); + unsigned short unknown3 = RotTrack->read< unsigned short >(); + unsigned short unknown4 = RotTrack->read< unsigned short >(); + + unsigned short keys = RotTrack->read< unsigned short >(); + + unsigned short unknown = RotTrack->read< unsigned short >(); + + for( unsigned short key = 0; key < keys; key++ ) + { + unsigned short kfNum = RotTrack->read< unsigned short >(); + unsigned long kfUnknown = RotTrack->read< unsigned long >(); + float kfRotAngle = RotTrack->read< float >(); + float kfAxisX = RotTrack->read< float >(); + float kfAxisY = RotTrack->read< float >(); + float kfAxisZ = RotTrack->read< float >(); + + newKfSeq.frames[kfNum].rotAngle = kfRotAngle; + newKfSeq.frames[kfNum].rotAxis = Vector3( kfAxisX, kfAxisY, kfAxisZ ); + } + } + + if( Chunk * SclTrack = KfObj->getChild( SCL_TRACK_TAG ) ) { + unsigned short flags = SclTrack->read< unsigned short >(); + + unsigned short unknown1 = SclTrack->read< unsigned short >(); + unsigned short unknown2 = SclTrack->read< unsigned short >(); + unsigned short unknown3 = SclTrack->read< unsigned short >(); + unsigned short unknown4 = SclTrack->read< unsigned short >(); + + unsigned short keys = SclTrack->read< unsigned short >(); + + unsigned short unknown = SclTrack->read< unsigned short >(); + + for( unsigned short key = 0; key < keys; key++ ) + { + unsigned short kfNum = SclTrack->read< unsigned short >(); + unsigned long kfUnknown = SclTrack->read< unsigned long >(); + float kfSclX = SclTrack->read< float >(); + float kfSclY = SclTrack->read< float >(); + float kfSclZ = SclTrack->read< float >(); + + newKfSeq.frames[kfNum].scale = ( kfSclX + kfSclY + kfSclZ ) / 3.0f; + } + } + + ObjKeyframes.insertMulti( newKfSeq.objectName, newKfSeq ); + } + } + + fobj.close(); + + //--Translate file structures into NIF ones--// + + if ( iNode.isValid() == false ) + { + iNode = nif->insertNiBlock( "NiNode" ); + nif->set( iNode, "Name", "Scene Root" ); + } + + //Record root object + iRoot = iNode; + + // create a NiTriShape foreach material in the object + for(int objIndex = 0; objIndex < ObjMeshes.size(); objIndex++) { + objMesh * mesh = &ObjMeshes[objIndex]; + + + + // create group node if there is more than 1 material + bool groupNode = false; + QPersistentModelIndex iNode = iRoot; + if ( mesh->matfaces.size() > 1 ) + { + groupNode = true; + + iNode = nif->insertNiBlock( "NiNode" ); + nif->set( iNode, "Name", mesh->name ); + addLink( nif, iRoot, "Children", nif->getBlockNumber( iNode ) ); + } + + int shapecount = 0; + for( int i = 0; i < mesh->matfaces.size(); i++ ) + { + if ( !ObjMaterials.contains( mesh->matfaces[i].matName ) ) { + qWarning() << Spell::tr("Material '%1' not found in list!").arg( mesh->matfaces[i].matName ); + } + + objMaterial * mat = &ObjMaterials[mesh->matfaces[i].matName]; + + if ( iShape.isValid() == false || objIndex != 0 ) + { + iShape = nif->insertNiBlock( "NiTriShape" ); + } + if ( groupNode ) + { + nif->set( iShape, "Name", QString( "%1:%2" ).arg( nif->get( iNode, "Name" ) ).arg( shapecount++ ) ); + addLink( nif, iNode, "Children", nif->getBlockNumber( iShape ) ); + } + else + { + nif->set( iShape, "Name", mesh->name ); + addLink( nif, iRoot, "Children", nif->getBlockNumber( iShape ) ); + } + + if ( iMaterial.isValid() == false || objIndex != 0 ) + { + iMaterial = nif->insertNiBlock( "NiMaterialProperty" ); + } + nif->set( iMaterial, "Name", mat->name ); + nif->set( iMaterial, "Ambient Color", mat->Ka ); + nif->set( iMaterial, "Diffuse Color", mat->Kd ); + nif->set( iMaterial, "Specular Color", mat->Ks ); + nif->set( iMaterial, "Emissive Color", Color3( 0, 0, 0 ) ); + nif->set( iMaterial, "Alpha", mat->alpha ); + nif->set( iMaterial, "Glossiness", mat->glossiness ); + + addLink( nif, iShape, "Properties", nif->getBlockNumber( iMaterial ) ); + + if ( !mat->map_Kd.isEmpty() ) + { + if ( nif->getVersionNumber() >= 0x0303000D ) + { + //Newer versions use NiTexturingProperty and NiSourceTexture + if ( iTexProp.isValid() == false || objIndex != 0 || nif->itemType(iTexProp) != "NiTexturingProperty" ) + { + iTexProp = nif->insertNiBlock( "NiTexturingProperty" ); + } + addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); + + nif->set( iTexProp, "Has Base Texture", 1 ); + QModelIndex iBaseMap = nif->getIndex( iTexProp, "Base Texture" ); + nif->set( iBaseMap, "Clamp Mode", 3 ); + nif->set( iBaseMap, "Filter Mode", 2 ); + + if ( iTexSource.isValid() == false || objIndex != 0 || nif->itemType(iTexSource) != "NiSourceTexture" ) + { + iTexSource = nif->insertNiBlock( "NiSourceTexture" ); + } + nif->setLink( iBaseMap, "Source", nif->getBlockNumber( iTexSource ) ); + + nif->set( iTexSource, "Pixel Layout", nif->getVersion() == "20.0.0.5" ? 6 : 5 ); + nif->set( iTexSource, "Use Mipmaps", 2 ); + nif->set( iTexSource, "Alpha Format", 3 ); + nif->set( iTexSource, "Unknown Byte", 1 ); + nif->set( iTexSource, "Unknown Byte 2", 1 ); + + nif->set( iTexSource, "Use External", 1 ); + nif->set( iTexSource, "File Name", mat->map_Kd ); + } + else + { + //Older versions use NiTextureProperty and NiImage + if ( iTexProp.isValid() == false || objIndex != 0 || nif->itemType(iTexProp) != "NiTextureProperty" ) + { + iTexProp = nif->insertNiBlock( "NiTextureProperty" ); + } + addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); + + if ( iTexSource.isValid() == false || objIndex != 0 || nif->itemType(iTexSource) != "NiImage" ) + { + iTexSource = nif->insertNiBlock( "NiImage" ); + } + + nif->setLink( iTexProp, "Image", nif->getBlockNumber( iTexSource ) ); + + nif->set( iTexSource, "External", 1 ); + nif->set( iTexSource, "File Name", mat->map_Kd ); + } + } + + if ( iData.isValid() == false || objIndex != 0 ) + { + iData = nif->insertNiBlock( "NiTriShapeData" ); + } + nif->setLink( iShape, "Data", nif->getBlockNumber( iData ) ); + + QVector< Triangle > triangles; + QVector< objPoint > points; + + foreach( short faceIndex, mesh->matfaces[i].subFaces ) + { + objFace face = mesh->faces[faceIndex]; + + Triangle tri; + + tri.set( face.v1, face.v2, face.v3 ); + + triangles.append( tri ); + } + + nif->set( iData, "Num Vertices", mesh->vertices.count() ); + nif->set( iData, "Has Vertices", 1 ); + nif->updateArray( iData, "Vertices" ); + nif->setArray( iData, "Vertices", mesh->vertices ); + nif->set( iData, "Has Normals", 1 ); + nif->updateArray( iData, "Normals" ); + nif->setArray( iData, "Normals", mesh->normals ); + nif->set( iData, "Has UV", 1 ); + nif->set( iData, "Num UV Sets", 1 ); + nif->set( iData, "Num UV Sets 2", 1 ); + QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); + if ( !iTexCo.isValid() ) { + iTexCo = nif->getIndex( iData, "UV Sets 2" ); + } + nif->updateArray( iTexCo ); + nif->updateArray( iTexCo.child( 0, 0 ) ); + nif->setArray( iTexCo.child( 0, 0 ), mesh->texcoords ); + + nif->set( iData, "Has Triangles", 1 ); + nif->set( iData, "Num Triangles", triangles.count() ); + nif->set( iData, "Num Triangle Points", triangles.count() * 3 ); + nif->updateArray( iData, "Triangles" ); + nif->setArray( iData, "Triangles", triangles ); + + Vector3 center; + foreach ( Vector3 v, mesh->vertices ) + center += v; + if ( mesh->vertices.count() > 0 ) center /= mesh->vertices.count(); + nif->set( iData, "Center", center ); + float radius = 0; + foreach ( Vector3 v, mesh->vertices ) + { + float d = ( center - v ).length(); + if ( d > radius ) radius = d; + } + nif->set( iData, "Radius", radius ); + + nif->set( iData, "Unknown Short 2", 0x4000 ); + } + + // set up a controller for animated objects + } + + settings.setValue( "File Name", fname ); + + nif->reset(); + return; +} diff --git a/importex/3ds.h b/importex/3ds.h index 8b7664ee8..7a516d05c 100644 --- a/importex/3ds.h +++ b/importex/3ds.h @@ -1,593 +1,593 @@ -#ifndef IMPORT_3DS_H -#define IMPORT_3DS_H - -// Chunk Type definitions -#define NULL_CHUNK 0x0000 -#define M3D_VERSION 0x0002 -#define M3D_KFVERSION 0x0003 -#define COLOR_F 0x0010 -#define COLOR_24 0x0011 -#define LIN_COLOR_24 0x0012 -#define LIN_COLOR_F 0x0013 -#define INT_PERCENTAGE 0x0030 -#define FLOAT_PERCENTAGE 0x0031 -#define MASTER_SCALE 0x0100 - -#define DEFAULT_VIEW 0x3000 -#define VIEW_TOP 0x3010 -#define VIEW_BOTTOM 0x3020 -#define VIEW_LEFT 0x3030 -#define VIEW_RIGHT 0x3040 -#define VIEW_FRONT 0x3050 -#define VIEW_BACK 0x3060 -#define VIEW_USER 0x3070 -#define VIEW_CAMERA 0x3080 -#define MDATA 0x3D3D -#define MESH_VERSION 0x3D3E - -#define NAMED_OBJECT 0x4000 -#define N_TRI_OBJECT 0x4100 -#define POINT_ARRAY 0x4110 -#define POINT_FLAG_ARRAY 0x4111 -#define FACE_ARRAY 0x4120 -#define MSH_MAT_GROUP 0x4130 -#define TEX_VERTS 0x4140 -#define SMOOTH_GROUP 0x4150 -#define MESH_MATRIX 0x4160 -#define MESH_COLOR 0x4165 -#define M3DMAGIC 0x4D4D - -#define MAT_NAME 0xA000 -#define MAT_AMBIENT 0xA010 -#define MAT_DIFFUSE 0xA020 -#define MAT_SPECULAR 0xA030 -#define MAT_SHININESS 0xA040 -#define MAT_SHIN2PCT 0xA041 -#define MAT_SHIN3PCT 0xA042 -#define MAT_TRANSPARENCY 0xA050 -#define MAT_XPFALL 0xA052 -#define MAT_REFBLUR 0xA053 -#define MAT_SELF_ILLUM 0xA080 -#define MAT_TWOSIDE 0xA081 -#define MAT_SELF_ILPCT 0xA084 -#define MAT_WIRESIZE 0xA087 -#define MAT_XPFALLIN 0xA08A -#define MAT_SHADING 0xA100 -#define MAT_TEXMAP 0xA200 -#define MAT_SPECMAP 0xA204 -#define MAT_OPACMAP 0xA210 -#define MAT_REFLMAP 0xA220 -#define MAT_BUMPMAP 0xA230 -#define MAT_MAPNAME 0xA300 -#define MATERIAL 0xAFFF - -#define KFDATA 0xB000 -#define OBJECT_NODE_TAG 0xB002 -#define KFSEG 0xB008 -#define KFCURTIME 0xB009 -#define KFHDR 0xB00A -#define NODE_HDR 0xB010 -#define PIVOT 0xB013 -#define POS_TRACK_TAG 0xB020 -#define ROT_TRACK_TAG 0xB021 -#define SCL_TRACK_TAG 0xB022 -#define FOV_TRACK_TAG 0xB023 -#define ROLL_TRACK_TAG 0xB024 -#define COL_TRACK_TAG 0xB025 -#define MORPH_TRACK_TAG 0xB026 -#define HOT_TRACK_TAG 0xB027 -#define FALL_TRACK_TAG 0xB028 -#define HIDE_TRACK_TAG 0xB029 -#define NODE_ID 0xB030 - -#define CHUNKHEADERSIZE 6 -#define FILE_DUMMY 0xFFFF - -#define FACE_FLAG_ONESIDE 0x0400 - -#include -#include -#include -#include -#include - -class Chunk { -public: - - // general chunk properties - - typedef unsigned short ChunkType; - typedef unsigned int ChunkPos; - typedef unsigned long ChunkLength; - typedef bool ChunkDataFlag; - typedef unsigned int ChunkDataPos; - typedef unsigned int ChunkDataLength; - typedef unsigned short ChunkDataCount; - - struct ChunkHeader - { - ChunkType t; - ChunkLength l; - }; - - // data structures for various chunks - - typedef float ChunkTypeFloat; - - struct ChunkTypeFloat2 - { - ChunkTypeFloat x, y; - }; - - struct ChunkTypeFloat3 - { - ChunkTypeFloat x, y, z; - }; - - struct ChunkTypeChar3 - { - unsigned char x, y, z; - }; - - typedef long ChunkTypeLong; - - typedef short ChunkTypeShort; - - struct ChunkTypeShort3 { - ChunkTypeShort x, y, z; - }; - - struct ChunkTypeFaceArray { - ChunkTypeShort vertex1, vertex2, vertex3; - ChunkTypeShort flags; - }; - - struct ChunkTypeMeshMatrix - { - ChunkTypeShort matrix[4][3]; - }; - - struct ChunkTypeKfPos - { - ChunkTypeShort framenum; - ChunkTypeLong unknown; - ChunkTypeFloat pos_x, pos_y, pos_z; - }; - - struct ChunkTypeKfRot - { - ChunkTypeShort framenum; - ChunkTypeFloat rotation_rad; - ChunkTypeFloat axis_x, axis_y, axis_z; - ChunkTypeLong unknown; - }; - - struct ChunkTypeKfScl - { - ChunkTypeShort framenum; - ChunkTypeLong unknown; - ChunkTypeFloat scale_x, scale_y, scale_z; - }; - - // class members - - Chunk( QFile * _f, ChunkHeader _h, ChunkPos _p ) - : f( _f ), h( _h ), p( _p ), df( false ), dp( 0 ), dl( 0 ), dc( 0 ) - { - if( h.t == FILE_DUMMY ) { - this->addchildren(); - } - else { - subproc(); - } - } - - ~Chunk() - { - qDeleteAll( c ); - } - - static Chunk * LoadFile( QFile * file ) { - file->seek( 0 ); - - ChunkHeader hdr; - hdr.t = FILE_DUMMY; - hdr.l = file->size(); - - Chunk * cnk = new Chunk( file, hdr, ( - CHUNKHEADERSIZE ) ); - - return cnk; - } - - ChunkType getType() - { - return h.t; - } - - QList< Chunk * > getChildren( ChunkType ct = NULL_CHUNK ) - { - if( ct == NULL_CHUNK ) { - return c.values(); - } - - return c.values( ct ); - } - - Chunk * getChild( ChunkType ct ) - { - return c[ct]; - } - - void reset() - { - dp = 0; - } - - template< class T > - T read() - { - T r = T(); - - if( !df || dp > ( h.l - CHUNKHEADERSIZE ) ) { - return r; - } - - f->seek( p + CHUNKHEADERSIZE + dp ); - dp += f->read( (char *) &r, sizeof( r ) ); - - return r; - } - - QString readString() - { - QString s; - char n; - - f->seek( p + CHUNKHEADERSIZE + dp ); - - while( true ) { - dp += f->read( &n, sizeof( char ) ); - - if( n == NULL ) { - break; - } - - s.append( n ); - } - - return s; - } - -private: - ChunkHeader h; - ChunkPos p; - ChunkDataFlag df; - ChunkDataPos dp; - ChunkDataLength dl; - ChunkDataCount dc; - - QFile * f; - QMap< ChunkType, Chunk * > c; - - void subproc() { - switch( h.t & 0xf000 ) { - case 0x0000: - - switch( h.t ) - { - case M3D_VERSION: - case M3D_KFVERSION: - adddata( sizeof( ChunkTypeShort ) ); - break; - - case COLOR_F: - case LIN_COLOR_F: - adddata( sizeof( ChunkTypeFloat3 ) ); - break; - - case COLOR_24: - case LIN_COLOR_24: - adddata(); - break; - - case INT_PERCENTAGE: - adddata( sizeof( ChunkTypeShort ) ); - break; - - case FLOAT_PERCENTAGE: - adddata( sizeof( ChunkTypeFloat ) ); - break; - - case MASTER_SCALE: - adddata( sizeof( ChunkTypeFloat ) ); - break; - } - - break; - - case 0x3000: - - switch( h.t ) - { - case DEFAULT_VIEW: - addchildren(); - break; - - case VIEW_TOP: - case VIEW_BOTTOM: - case VIEW_LEFT: - case VIEW_RIGHT: - case VIEW_FRONT: - case VIEW_BACK: - case VIEW_USER: - adddata(); - break; - - case VIEW_CAMERA: - addname(); - adddata(); - break; - - case MDATA: - addchildren(); - break; - } - - break; - - case 0x4000: - - switch( h.t ) - { - case NAMED_OBJECT: - addname(); - adddata(); - addchildren(); - break; - - case N_TRI_OBJECT: - addchildren(); - break; - - case POINT_ARRAY: - addcount( sizeof( ChunkTypeFloat3 ) ); - adddata(); - break; - - case POINT_FLAG_ARRAY: - addcount( sizeof( ChunkTypeShort ) ); - adddata(); - break; - - case FACE_ARRAY: - addcount( sizeof( ChunkTypeFaceArray ) ); - adddata(); - addchildren(); - break; - - case MSH_MAT_GROUP: - addname(); - addcount( sizeof( ChunkTypeShort ) ); - adddata(); - break; - - case TEX_VERTS: - addcount( sizeof( ChunkTypeFloat2 ) ); - adddata(); - break; - - case MESH_MATRIX: - adddata( sizeof( ChunkTypeMeshMatrix ) ); - break; - - case M3DMAGIC: - addchildren(); - break; - - } - - break; - - case 0xa000: - - switch( h.t ) { - - case MAT_NAME: - addname(); - adddata(); - break; - - case MAT_AMBIENT: - case MAT_DIFFUSE: - case MAT_SPECULAR: - addchildren(); - break; - - case MAT_SHININESS: - case MAT_SHIN2PCT: - case MAT_SHIN3PCT: - case MAT_TRANSPARENCY: - case MAT_XPFALL: - case MAT_REFBLUR: - case MAT_SELF_ILPCT: - addchildren(); - break; - - case MAT_WIRESIZE: - adddata( sizeof( ChunkTypeFloat ) ); - break; - - case MAT_SHADING: - adddata( sizeof( ChunkTypeShort) ); - break; - - case MAT_TEXMAP: - case MAT_SPECMAP: - case MAT_OPACMAP: - case MAT_REFLMAP: - case MAT_BUMPMAP: - addchildren(); - break; - - case MAT_MAPNAME: - addname(); - adddata(); - break; - - case MATERIAL: - addchildren(); - break; - } - - break; - - case 0xb000: - - switch( h.t ) - { - case KFDATA: - addchildren(); - break; - - case KFHDR: - adddata( sizeof( ChunkTypeShort ) ); - addname(); - adddata( sizeof( ChunkTypeShort ) ); - adddata( sizeof( ChunkTypeShort ) ); - break; - - case KFSEG: - adddata( sizeof( ChunkTypeLong ) ); - adddata( sizeof( ChunkTypeLong ) ); - break; - - case KFCURTIME: - adddata( sizeof( ChunkTypeLong ) ); - break; - - case OBJECT_NODE_TAG: - addchildren(); - break; - - case NODE_ID: - adddata( sizeof( ChunkTypeShort ) ); - break; - - case NODE_HDR: - addname(); - adddata( sizeof( ChunkTypeShort ) ); - adddata( sizeof( ChunkTypeShort ) ); - adddata( sizeof( ChunkTypeShort ) ); - break; - - case PIVOT: - adddata( sizeof( ChunkTypeFloat ) ); - adddata( sizeof( ChunkTypeFloat ) ); - adddata( sizeof( ChunkTypeFloat ) ); - break; - - case POS_TRACK_TAG: - adddata( sizeof( ChunkTypeShort ) ); - adddata( sizeof( ChunkTypeShort[4] ) ); - addcount( sizeof( ChunkTypeKfPos ) ); - adddata( sizeof( ChunkTypeShort ) ); - break; - - case ROT_TRACK_TAG: - adddata( sizeof( ChunkTypeShort ) ); - adddata( sizeof( ChunkTypeShort[4] ) ); - addcount( sizeof( ChunkTypeKfRot ) ); - adddata( sizeof( ChunkTypeShort ) ); - break; - - case SCL_TRACK_TAG: - adddata( sizeof( ChunkTypeShort ) ); - adddata( sizeof( ChunkTypeShort[4] ) ); - addcount( sizeof( ChunkTypeKfScl ) ); - adddata( sizeof( ChunkTypeShort ) ); - break; - - } - - break; - } - } - - void addchildren() - { - f->seek( p + CHUNKHEADERSIZE + dl ); - - QMap< ChunkType, Chunk * > temp; - - while( f->pos() < ( p + h.l ) ) - { - ChunkPos q = f->pos(); - - ChunkHeader k; - f->read( (char *)( &k.t ), sizeof( k.t ) ); - f->read( (char *)( &k.l ), sizeof( k.l ) ); - - Chunk * z = new Chunk( f, k, q ); - - temp.insertMulti( k.t, z ); - - f->seek( q + k.l ); - } - - QMapIterator< ChunkType, Chunk * > tempIter( temp ); - while( tempIter.hasNext() ) - { - tempIter.next(); - c.insertMulti( tempIter.key(), tempIter.value() ); - } - - f->seek( p + h.l ); - } - - void addname() - { - f->seek( p + CHUNKHEADERSIZE + dl ); - - char n = 0x01; - int nl = 0; - - while( n != NULL ) - { - if( !f->getChar( &n ) ) { - break; - } - - nl++; - } - - dl += nl; - } - - void addcount( ChunkDataLength _dl ) - { - f->seek( p + CHUNKHEADERSIZE + dl ); - - int n = sizeof( dc ); - - f->read( (char *)( &dc ), sizeof( dc ) ); - - dl += ( sizeof( ChunkDataCount ) + ( dc * _dl ) ); - } - - - void adddata( ChunkDataLength _dl = 0 ) - { - dl += _dl; - - if( dl == 0 ) { - dl = ( h.l - CHUNKHEADERSIZE ); - } - - df = true; - - f->seek( p + CHUNKHEADERSIZE + dl ); - } - -}; - -#endif +#ifndef IMPORT_3DS_H +#define IMPORT_3DS_H + +// Chunk Type definitions +#define NULL_CHUNK 0x0000 +#define M3D_VERSION 0x0002 +#define M3D_KFVERSION 0x0003 +#define COLOR_F 0x0010 +#define COLOR_24 0x0011 +#define LIN_COLOR_24 0x0012 +#define LIN_COLOR_F 0x0013 +#define INT_PERCENTAGE 0x0030 +#define FLOAT_PERCENTAGE 0x0031 +#define MASTER_SCALE 0x0100 + +#define DEFAULT_VIEW 0x3000 +#define VIEW_TOP 0x3010 +#define VIEW_BOTTOM 0x3020 +#define VIEW_LEFT 0x3030 +#define VIEW_RIGHT 0x3040 +#define VIEW_FRONT 0x3050 +#define VIEW_BACK 0x3060 +#define VIEW_USER 0x3070 +#define VIEW_CAMERA 0x3080 +#define MDATA 0x3D3D +#define MESH_VERSION 0x3D3E + +#define NAMED_OBJECT 0x4000 +#define N_TRI_OBJECT 0x4100 +#define POINT_ARRAY 0x4110 +#define POINT_FLAG_ARRAY 0x4111 +#define FACE_ARRAY 0x4120 +#define MSH_MAT_GROUP 0x4130 +#define TEX_VERTS 0x4140 +#define SMOOTH_GROUP 0x4150 +#define MESH_MATRIX 0x4160 +#define MESH_COLOR 0x4165 +#define M3DMAGIC 0x4D4D + +#define MAT_NAME 0xA000 +#define MAT_AMBIENT 0xA010 +#define MAT_DIFFUSE 0xA020 +#define MAT_SPECULAR 0xA030 +#define MAT_SHININESS 0xA040 +#define MAT_SHIN2PCT 0xA041 +#define MAT_SHIN3PCT 0xA042 +#define MAT_TRANSPARENCY 0xA050 +#define MAT_XPFALL 0xA052 +#define MAT_REFBLUR 0xA053 +#define MAT_SELF_ILLUM 0xA080 +#define MAT_TWOSIDE 0xA081 +#define MAT_SELF_ILPCT 0xA084 +#define MAT_WIRESIZE 0xA087 +#define MAT_XPFALLIN 0xA08A +#define MAT_SHADING 0xA100 +#define MAT_TEXMAP 0xA200 +#define MAT_SPECMAP 0xA204 +#define MAT_OPACMAP 0xA210 +#define MAT_REFLMAP 0xA220 +#define MAT_BUMPMAP 0xA230 +#define MAT_MAPNAME 0xA300 +#define MATERIAL 0xAFFF + +#define KFDATA 0xB000 +#define OBJECT_NODE_TAG 0xB002 +#define KFSEG 0xB008 +#define KFCURTIME 0xB009 +#define KFHDR 0xB00A +#define NODE_HDR 0xB010 +#define PIVOT 0xB013 +#define POS_TRACK_TAG 0xB020 +#define ROT_TRACK_TAG 0xB021 +#define SCL_TRACK_TAG 0xB022 +#define FOV_TRACK_TAG 0xB023 +#define ROLL_TRACK_TAG 0xB024 +#define COL_TRACK_TAG 0xB025 +#define MORPH_TRACK_TAG 0xB026 +#define HOT_TRACK_TAG 0xB027 +#define FALL_TRACK_TAG 0xB028 +#define HIDE_TRACK_TAG 0xB029 +#define NODE_ID 0xB030 + +#define CHUNKHEADERSIZE 6 +#define FILE_DUMMY 0xFFFF + +#define FACE_FLAG_ONESIDE 0x0400 + +#include +#include +#include +#include +#include + +class Chunk { +public: + + // general chunk properties + + typedef unsigned short ChunkType; + typedef unsigned int ChunkPos; + typedef unsigned long ChunkLength; + typedef bool ChunkDataFlag; + typedef unsigned int ChunkDataPos; + typedef unsigned int ChunkDataLength; + typedef unsigned short ChunkDataCount; + + struct ChunkHeader + { + ChunkType t; + ChunkLength l; + }; + + // data structures for various chunks + + typedef float ChunkTypeFloat; + + struct ChunkTypeFloat2 + { + ChunkTypeFloat x, y; + }; + + struct ChunkTypeFloat3 + { + ChunkTypeFloat x, y, z; + }; + + struct ChunkTypeChar3 + { + unsigned char x, y, z; + }; + + typedef long ChunkTypeLong; + + typedef short ChunkTypeShort; + + struct ChunkTypeShort3 { + ChunkTypeShort x, y, z; + }; + + struct ChunkTypeFaceArray { + ChunkTypeShort vertex1, vertex2, vertex3; + ChunkTypeShort flags; + }; + + struct ChunkTypeMeshMatrix + { + ChunkTypeShort matrix[4][3]; + }; + + struct ChunkTypeKfPos + { + ChunkTypeShort framenum; + ChunkTypeLong unknown; + ChunkTypeFloat pos_x, pos_y, pos_z; + }; + + struct ChunkTypeKfRot + { + ChunkTypeShort framenum; + ChunkTypeFloat rotation_rad; + ChunkTypeFloat axis_x, axis_y, axis_z; + ChunkTypeLong unknown; + }; + + struct ChunkTypeKfScl + { + ChunkTypeShort framenum; + ChunkTypeLong unknown; + ChunkTypeFloat scale_x, scale_y, scale_z; + }; + + // class members + + Chunk( QFile * _f, ChunkHeader _h, ChunkPos _p ) + : f( _f ), h( _h ), p( _p ), df( false ), dp( 0 ), dl( 0 ), dc( 0 ) + { + if( h.t == FILE_DUMMY ) { + this->addchildren(); + } + else { + subproc(); + } + } + + ~Chunk() + { + qDeleteAll( c ); + } + + static Chunk * LoadFile( QFile * file ) { + file->seek( 0 ); + + ChunkHeader hdr; + hdr.t = FILE_DUMMY; + hdr.l = file->size(); + + Chunk * cnk = new Chunk( file, hdr, ( - CHUNKHEADERSIZE ) ); + + return cnk; + } + + ChunkType getType() + { + return h.t; + } + + QList< Chunk * > getChildren( ChunkType ct = NULL_CHUNK ) + { + if( ct == NULL_CHUNK ) { + return c.values(); + } + + return c.values( ct ); + } + + Chunk * getChild( ChunkType ct ) + { + return c[ct]; + } + + void reset() + { + dp = 0; + } + + template< class T > + T read() + { + T r = T(); + + if( !df || dp > ( h.l - CHUNKHEADERSIZE ) ) { + return r; + } + + f->seek( p + CHUNKHEADERSIZE + dp ); + dp += f->read( (char *) &r, sizeof( r ) ); + + return r; + } + + QString readString() + { + QString s; + char n; + + f->seek( p + CHUNKHEADERSIZE + dp ); + + while( true ) { + dp += f->read( &n, sizeof( char ) ); + + if( n == NULL ) { + break; + } + + s.append( n ); + } + + return s; + } + +private: + ChunkHeader h; + ChunkPos p; + ChunkDataFlag df; + ChunkDataPos dp; + ChunkDataLength dl; + ChunkDataCount dc; + + QFile * f; + QMap< ChunkType, Chunk * > c; + + void subproc() { + switch( h.t & 0xf000 ) { + case 0x0000: + + switch( h.t ) + { + case M3D_VERSION: + case M3D_KFVERSION: + adddata( sizeof( ChunkTypeShort ) ); + break; + + case COLOR_F: + case LIN_COLOR_F: + adddata( sizeof( ChunkTypeFloat3 ) ); + break; + + case COLOR_24: + case LIN_COLOR_24: + adddata(); + break; + + case INT_PERCENTAGE: + adddata( sizeof( ChunkTypeShort ) ); + break; + + case FLOAT_PERCENTAGE: + adddata( sizeof( ChunkTypeFloat ) ); + break; + + case MASTER_SCALE: + adddata( sizeof( ChunkTypeFloat ) ); + break; + } + + break; + + case 0x3000: + + switch( h.t ) + { + case DEFAULT_VIEW: + addchildren(); + break; + + case VIEW_TOP: + case VIEW_BOTTOM: + case VIEW_LEFT: + case VIEW_RIGHT: + case VIEW_FRONT: + case VIEW_BACK: + case VIEW_USER: + adddata(); + break; + + case VIEW_CAMERA: + addname(); + adddata(); + break; + + case MDATA: + addchildren(); + break; + } + + break; + + case 0x4000: + + switch( h.t ) + { + case NAMED_OBJECT: + addname(); + adddata(); + addchildren(); + break; + + case N_TRI_OBJECT: + addchildren(); + break; + + case POINT_ARRAY: + addcount( sizeof( ChunkTypeFloat3 ) ); + adddata(); + break; + + case POINT_FLAG_ARRAY: + addcount( sizeof( ChunkTypeShort ) ); + adddata(); + break; + + case FACE_ARRAY: + addcount( sizeof( ChunkTypeFaceArray ) ); + adddata(); + addchildren(); + break; + + case MSH_MAT_GROUP: + addname(); + addcount( sizeof( ChunkTypeShort ) ); + adddata(); + break; + + case TEX_VERTS: + addcount( sizeof( ChunkTypeFloat2 ) ); + adddata(); + break; + + case MESH_MATRIX: + adddata( sizeof( ChunkTypeMeshMatrix ) ); + break; + + case M3DMAGIC: + addchildren(); + break; + + } + + break; + + case 0xa000: + + switch( h.t ) { + + case MAT_NAME: + addname(); + adddata(); + break; + + case MAT_AMBIENT: + case MAT_DIFFUSE: + case MAT_SPECULAR: + addchildren(); + break; + + case MAT_SHININESS: + case MAT_SHIN2PCT: + case MAT_SHIN3PCT: + case MAT_TRANSPARENCY: + case MAT_XPFALL: + case MAT_REFBLUR: + case MAT_SELF_ILPCT: + addchildren(); + break; + + case MAT_WIRESIZE: + adddata( sizeof( ChunkTypeFloat ) ); + break; + + case MAT_SHADING: + adddata( sizeof( ChunkTypeShort) ); + break; + + case MAT_TEXMAP: + case MAT_SPECMAP: + case MAT_OPACMAP: + case MAT_REFLMAP: + case MAT_BUMPMAP: + addchildren(); + break; + + case MAT_MAPNAME: + addname(); + adddata(); + break; + + case MATERIAL: + addchildren(); + break; + } + + break; + + case 0xb000: + + switch( h.t ) + { + case KFDATA: + addchildren(); + break; + + case KFHDR: + adddata( sizeof( ChunkTypeShort ) ); + addname(); + adddata( sizeof( ChunkTypeShort ) ); + adddata( sizeof( ChunkTypeShort ) ); + break; + + case KFSEG: + adddata( sizeof( ChunkTypeLong ) ); + adddata( sizeof( ChunkTypeLong ) ); + break; + + case KFCURTIME: + adddata( sizeof( ChunkTypeLong ) ); + break; + + case OBJECT_NODE_TAG: + addchildren(); + break; + + case NODE_ID: + adddata( sizeof( ChunkTypeShort ) ); + break; + + case NODE_HDR: + addname(); + adddata( sizeof( ChunkTypeShort ) ); + adddata( sizeof( ChunkTypeShort ) ); + adddata( sizeof( ChunkTypeShort ) ); + break; + + case PIVOT: + adddata( sizeof( ChunkTypeFloat ) ); + adddata( sizeof( ChunkTypeFloat ) ); + adddata( sizeof( ChunkTypeFloat ) ); + break; + + case POS_TRACK_TAG: + adddata( sizeof( ChunkTypeShort ) ); + adddata( sizeof( ChunkTypeShort[4] ) ); + addcount( sizeof( ChunkTypeKfPos ) ); + adddata( sizeof( ChunkTypeShort ) ); + break; + + case ROT_TRACK_TAG: + adddata( sizeof( ChunkTypeShort ) ); + adddata( sizeof( ChunkTypeShort[4] ) ); + addcount( sizeof( ChunkTypeKfRot ) ); + adddata( sizeof( ChunkTypeShort ) ); + break; + + case SCL_TRACK_TAG: + adddata( sizeof( ChunkTypeShort ) ); + adddata( sizeof( ChunkTypeShort[4] ) ); + addcount( sizeof( ChunkTypeKfScl ) ); + adddata( sizeof( ChunkTypeShort ) ); + break; + + } + + break; + } + } + + void addchildren() + { + f->seek( p + CHUNKHEADERSIZE + dl ); + + QMap< ChunkType, Chunk * > temp; + + while( f->pos() < ( p + h.l ) ) + { + ChunkPos q = f->pos(); + + ChunkHeader k; + f->read( (char *)( &k.t ), sizeof( k.t ) ); + f->read( (char *)( &k.l ), sizeof( k.l ) ); + + Chunk * z = new Chunk( f, k, q ); + + temp.insertMulti( k.t, z ); + + f->seek( q + k.l ); + } + + QMapIterator< ChunkType, Chunk * > tempIter( temp ); + while( tempIter.hasNext() ) + { + tempIter.next(); + c.insertMulti( tempIter.key(), tempIter.value() ); + } + + f->seek( p + h.l ); + } + + void addname() + { + f->seek( p + CHUNKHEADERSIZE + dl ); + + char n = 0x01; + int nl = 0; + + while( n != NULL ) + { + if( !f->getChar( &n ) ) { + break; + } + + nl++; + } + + dl += nl; + } + + void addcount( ChunkDataLength _dl ) + { + f->seek( p + CHUNKHEADERSIZE + dl ); + + int n = sizeof( dc ); + + f->read( (char *)( &dc ), sizeof( dc ) ); + + dl += ( sizeof( ChunkDataCount ) + ( dc * _dl ) ); + } + + + void adddata( ChunkDataLength _dl = 0 ) + { + dl += _dl; + + if( dl == 0 ) { + dl = ( h.l - CHUNKHEADERSIZE ); + } + + df = true; + + f->seek( p + CHUNKHEADERSIZE + dl ); + } + +}; + +#endif diff --git a/importex/importex.cpp b/importex/importex.cpp index 1e35a9c49..82e4f2f9e 100644 --- a/importex/importex.cpp +++ b/importex/importex.cpp @@ -1,93 +1,93 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - - - -#include "../nifskope.h" -#include "../widgets/nifview.h" -#include "../nifproxy.h" -#include "../nifmodel.h" - -#include -#include -#include - - -void exportObj( const NifModel * nif, const QModelIndex & index ); -void importObj( NifModel * nif, const QModelIndex & index ); - -void import3ds( NifModel * nif, const QModelIndex & index ); - - -void NifSkope::fillImportExportMenus() -{ - mExport->addAction( tr( "Export .OBJ" ) ); - mImport->addAction( tr( "Import .3DS" ) ); - mImport->addAction( tr( "Import .OBJ" ) ); -} - -void NifSkope::sltImportExport( QAction * a ) -{ - QModelIndex index; - - - //Get the currently selected NiBlock index in the list or tree view - if ( dList->isVisible() ) - { - if ( list->model() == proxy ) - { - index = proxy->mapTo( list->currentIndex() ); - } - else if ( list->model() == nif ) - { - index = list->currentIndex(); - } - } - else if ( dTree->isVisible() ) - { - if ( tree->model() == proxy ) - { - index = proxy->mapTo( tree->currentIndex() ); - } - else if ( tree->model() == nif ) - { - index = tree->currentIndex(); - } - } - - if ( a->text() == tr( "Export .OBJ" ) ) - exportObj( nif, index ); - else if ( a->text() == tr( "Import .OBJ" ) ) - importObj( nif, index ); - else if ( a->text() == tr( "Import .3DS" ) ) - import3ds( nif, index ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + + + +#include "../nifskope.h" +#include "../widgets/nifview.h" +#include "../nifproxy.h" +#include "../nifmodel.h" + +#include +#include +#include + + +void exportObj( const NifModel * nif, const QModelIndex & index ); +void importObj( NifModel * nif, const QModelIndex & index ); + +void import3ds( NifModel * nif, const QModelIndex & index ); + + +void NifSkope::fillImportExportMenus() +{ + mExport->addAction( tr( "Export .OBJ" ) ); + mImport->addAction( tr( "Import .3DS" ) ); + mImport->addAction( tr( "Import .OBJ" ) ); +} + +void NifSkope::sltImportExport( QAction * a ) +{ + QModelIndex index; + + + //Get the currently selected NiBlock index in the list or tree view + if ( dList->isVisible() ) + { + if ( list->model() == proxy ) + { + index = proxy->mapTo( list->currentIndex() ); + } + else if ( list->model() == nif ) + { + index = list->currentIndex(); + } + } + else if ( dTree->isVisible() ) + { + if ( tree->model() == proxy ) + { + index = proxy->mapTo( tree->currentIndex() ); + } + else if ( tree->model() == nif ) + { + index = tree->currentIndex(); + } + } + + if ( a->text() == tr( "Export .OBJ" ) ) + exportObj( nif, index ); + else if ( a->text() == tr( "Import .OBJ" ) ) + importObj( nif, index ); + else if ( a->text() == tr( "Import .3DS" ) ) + import3ds( nif, index ); +} diff --git a/importex/obj.cpp b/importex/obj.cpp index 304d07302..e5c3bce42 100644 --- a/importex/obj.cpp +++ b/importex/obj.cpp @@ -1,914 +1,914 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - - - -#include "../nifmodel.h" - -#include "../NvTriStrip/qtwrapper.h" - -#include "../gl/gltex.h" - -#include -#include -#include -#include -#include -#include - - - -/* - * .OBJ EXPORT - */ - - - -static void writeData( const NifModel * nif, const QModelIndex & iData, QTextStream & obj, int ofs[1], Transform t ) -{ - // copy vertices - - QVector verts = nif->getArray( iData, "Vertices" ); - foreach ( Vector3 v, verts ) - { - v = t * v; - obj << "v " << v[0] << " " << v[1] << " " << v[2] << "\r\n"; - } - - // copy texcoords - - QModelIndex iUV = nif->getIndex( iData, "UV Sets" ); - if ( ! iUV.isValid() ) - iUV = nif->getIndex( iData, "UV Sets 2" ); - - QVector texco = nif->getArray( iUV.child( 0, 0 ) ); - foreach( Vector2 t, texco ) - obj << "vt " << t[0] << " " << 1.0 - t[1] << "\r\n"; - - // copy normals - - QVector norms = nif->getArray( iData, "Normals" ); - foreach ( Vector3 n, norms ) - { - n = t.rotation * n; - obj << "vn " << n[0] << " " << n[1] << " " << n[2] << "\r\n"; - } - - // get the triangles - - QVector tris; - - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - if ( iPoints.isValid() ) - { - QList< QVector > strips; - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); - tris = triangulate( strips ); - } - else - { - tris = nif->getArray( iData, "Triangles" ); - } - - // write the triangles - - foreach ( Triangle t, tris ) - { - obj << "f"; - for ( int p = 0; p < 3; p++ ) - { - obj << " " << ofs[0] + t[p]; - if ( norms.count() ) - if ( texco.count() ) - obj << "/" << ofs[1] + t[p] << "/" << ofs[2] + t[p]; - else - obj << "//" << ofs[2] + t[p]; - else - if ( texco.count() ) - obj << "/" << ofs[1] + t[p]; - } - obj << "\r\n"; - } - - ofs[0] += verts.count(); - ofs[1] += texco.count(); - ofs[2] += norms.count(); -} - -static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextStream & obj, QTextStream & mtl, int ofs[], Transform t ) -{ - QString name = nif->get( iShape, "Name" ); - QString matn = name, texfn; - - Color3 mata, matd, mats; - float matt = 1.0, matg = 33.0; - - foreach ( qint32 link, nif->getChildLinks( nif->getBlockNumber( iShape ) ) ) - { - QModelIndex iProp = nif->getBlock( link ); - if ( nif->isNiBlock( iProp, "NiMaterialProperty" ) ) - { - mata = nif->get( iProp, "Ambient Color" ); - matd = nif->get( iProp, "Diffuse Color" ); - mats = nif->get( iProp, "Specular Color" ); - matt = nif->get( iProp, "Alpha" ); - matg = nif->get( iProp, "Glossiness" ); - //matn = nif->get( iProp, "Name" ); - } - else if ( nif->isNiBlock( iProp, "NiTexturingProperty" ) ) - { - QModelIndex iSource = nif->getBlock( nif->getLink( nif->getIndex( iProp, "Base Texture" ), "Source" ), "NiSourceTexture" ); - - texfn = TexCache::find( nif->get( iSource, "File Name" ), nif->getFolder() ); - } - else if ( nif->isNiBlock( iProp, "NiTextureProperty" ) ) - { - QModelIndex iSource = nif->getBlock( nif->getLink( iProp, "Image" ), "NiImage" ); - texfn = TexCache::find( nif->get( iSource, "File Name" ), nif->getFolder() ); - } - } - - //if ( ! texfn.isEmpty() ) - // matn += ":" + texfn; - - matn = QString( "Material.%1" ).arg( ofs[0], 6, 16, QChar( '0' ) ); - - mtl << "\r\n"; - mtl << "newmtl " << matn << "\r\n"; - mtl << "Ka " << mata[0] << " " << mata[1] << " " << mata[2] << "\r\n"; - mtl << "Kd " << matd[0] << " " << matd[1] << " " << matd[2] << "\r\n"; - mtl << "Ks " << mats[0] << " " << mats[1] << " " << mats[2] << "\r\n"; - mtl << "d " << matt << "\r\n"; - mtl << "Ns " << matg << "\r\n"; - if ( ! texfn.isEmpty() ) - mtl << "map_Kd " << texfn << "\r\n\r\n"; - - obj << "\r\n# " << name << "\r\n\r\ng " << name << "\r\n" << "usemtl " << matn << "\r\n\r\n"; - - writeData( nif, nif->getBlock( nif->getLink( iShape, "Data" ) ), obj, ofs, t ); -} - -static void writeParent( const NifModel * nif, const QModelIndex & iNode, QTextStream & obj, QTextStream & mtl, int ofs[], Transform t ) -{ - t = t * Transform( nif, iNode ); - foreach ( int l, nif->getChildLinks( nif->getBlockNumber( iNode ) ) ) - { - QModelIndex iChild = nif->getBlock( l ); - if ( nif->inherits( iChild, "NiNode" ) ) - writeParent( nif, iChild, obj, mtl, ofs, t ); - else if ( nif->isNiBlock( iChild, "NiTriShape" ) || nif->isNiBlock( iChild, "NiTriStrips" ) ) - writeShape( nif, iChild, obj, mtl, ofs, t * Transform( nif, iChild ) ); - else if ( nif->inherits( iChild, "NiCollisionObject" ) ) - { - QModelIndex iBody = nif->getBlock( nif->getLink( iChild, "Body" ) ); - if ( iBody.isValid() ) - { - Transform bt; - bt.scale = 7; - if ( nif->isNiBlock( iBody, "bhkRigidBodyT" ) ) - { - bt.rotation.fromQuat( nif->get( iBody, "Rotation" ) ); - bt.translation = nif->get( iBody, "Translation" ) * 7; - } - QModelIndex iShape = nif->getBlock( nif->getLink( iBody, "Shape" ) ); - if ( nif->isNiBlock( iShape, "bhkMoppBvTreeShape" ) ) - { - iShape = nif->getBlock( nif->getLink( iShape, "Shape" ) ); - if ( nif->isNiBlock( iShape, "bhkPackedNiTriStripsShape" ) ) - { - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); - if ( nif->isNiBlock( iData, "hkPackedNiTriStripsData" ) ) - { - bt = t * bt; - obj << "\r\n# bhkPackedNiTriStripsShape\r\n\r\ng collision\r\n" << "usemtl collision\r\n\r\n"; - QVector verts = nif->getArray( iData, "Vertices" ); - foreach ( Vector3 v, verts ) - { - v = bt * v; - obj << "v " << v[0] << " " << v[1] << " " << v[2] << "\r\n"; - } - - QModelIndex iTris = nif->getIndex( iData, "Triangles" ); - for ( int t = 0; t < nif->rowCount( iTris ); t++ ) - { - Triangle tri = nif->get( iTris.child( t, 0 ), "Triangle" ); - Vector3 n = nif->get( iTris.child( t, 0 ), "Normal" ); - - Vector3 a = verts.value( tri[0] ); - Vector3 b = verts.value( tri[1] ); - Vector3 c = verts.value( tri[2] ); - - Vector3 fn = Vector3::crossproduct( b - a, c - a ); - fn.normalize(); - - bool flip = Vector3::dotproduct( n, fn ) < 0; - - obj << "f" - << " " << tri[0] + ofs[0] - << " " << tri[ flip ? 2 : 1 ] + ofs[0] - << " " << tri[ flip ? 1 : 2 ] + ofs[0] - << "\r\n"; - } - ofs[0] += verts.count(); - } - } - } - else if ( nif->isNiBlock( iShape, "bhkNiTriStripsShape" ) ) - { - bt.scale = 1; - obj << "\r\n# bhkNiTriStripsShape\r\n\r\ng collision\r\n" << "usemtl collision\r\n\r\n"; - QModelIndex iStrips = nif->getIndex( iShape, "Strips Data" ); - for ( int r = 0; r < nif->rowCount( iStrips ); r++ ) - writeData( nif, nif->getBlock( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ), obj, ofs, t * bt ); - } - } - } - } -} - -void exportObj( const NifModel * nif, const QModelIndex & index ) -{ - //--Determine how the file will export, and be sure the user wants to continue--// - QList roots; - QModelIndex iBlock = nif->getBlock( index ); - - QString question; - if ( iBlock.isValid() ) - { - roots.append( nif->getBlockNumber(index) ); - if ( nif->itemName(index) == "NiNode" ) - { - question = "NiNode selected. All children of selected node will be exported."; - } else if ( nif->itemName(index) == "NiTriShape" || nif->itemName(index) == "NiTriStrips" ) - { - question = nif->itemName(index) + QString(" selected. Selected mesh will be exported."); - } - } - - if ( question.size() == 0 ) - { - question = "No NiNode, NiTriShape,or NiTriStrips is selected. Entire scene will be exported."; - roots = nif->getRootLinks(); - } - - int result = QMessageBox::question( 0, "Export OBJ", question, QMessageBox::Ok, QMessageBox::Cancel ); - if ( result == QMessageBox::Cancel ) { - return; - } - - //--Allow the user to select the file--// - - QSettings settings; - settings.beginGroup( "import-export" ); - settings.beginGroup( "obj" ); - - QString fname = QFileDialog::getSaveFileName( 0, "Choose a .OBJ file for export", settings.value( "File Name" ).toString(), "*.obj" ); - if ( fname.isEmpty() ) - return; - - while ( fname.endsWith( ".obj", Qt::CaseInsensitive ) ) - fname = fname.left( fname.length() - 4 ); - - QFile fobj( fname + ".obj" ); - if ( ! fobj.open( QIODevice::WriteOnly ) ) - { - qWarning() << "could not open " << fobj.fileName() << " for write access"; - return; - } - - QFile fmtl( fname + ".mtl" ); - if ( ! fmtl.open( QIODevice::WriteOnly ) ) - { - qWarning() << "could not open " << fmtl.fileName() << " for write access"; - return; - } - - fname = fmtl.fileName(); - int i = fname.lastIndexOf( "/" ); - if ( i >= 0 ) - fname = fname.remove( 0, i+1 ); - - QTextStream sobj( &fobj ); - QTextStream smtl( &fmtl ); - - sobj << "# exported with NifSkope\r\n\r\n" << "mtllib " << fname << "\r\n"; - - //--Translate NIF structure into file structure --// - - int ofs[3] = { 1, 1, 1 }; - foreach ( int l, roots ) - { - QModelIndex iBlock = nif->getBlock( l ); - if ( nif->inherits( iBlock, "NiNode" ) ) - writeParent( nif, iBlock, sobj, smtl, ofs, Transform() ); - else if ( nif->isNiBlock( iBlock, "NiTriShape" ) || nif->isNiBlock( iBlock, "NiTriStrips" ) ) - writeShape( nif, iBlock, sobj, smtl, ofs, Transform() ); - } - - settings.setValue( "File Name", fobj.fileName() ); -} - - - -/* - * .OBJ IMPORT - */ - - -struct ObjPoint -{ - int v, t, n; - - bool operator==( const ObjPoint & other ) const - { - return v == other.v && t == other.t && n == other.n; - } -}; - -struct ObjFace -{ - ObjPoint p[3]; -}; - -struct ObjMaterial -{ - Color3 Ka, Kd, Ks; - float d, Ns; - QString map_Kd; - - ObjMaterial() : d( 1.0 ), Ns( 31.0 ) {} -}; - -static void readMtlLib( const QString & fname, QMap< QString, ObjMaterial > & omaterials ) -{ - QFile file( fname ); - if ( ! file.open( QIODevice::ReadOnly ) ) - { - qWarning() << "failed to open" << fname; - return; - } - - QTextStream smtl( &file ); - - QString mtlid; - ObjMaterial mtl; - - while ( ! smtl.atEnd() ) - { - QString line = smtl.readLine(); - - QStringList t = line.split( " ", QString::SkipEmptyParts ); - - if ( t.value( 0 ) == "newmtl" ) - { - if ( ! mtlid.isEmpty() ) - omaterials.insert( mtlid, mtl ); - mtlid = t.value( 1 ); - mtl = ObjMaterial(); - } - else if ( t.value( 0 ) == "Ka" ) - { - mtl.Ka = Color3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ); - } - else if ( t.value( 0 ) == "Kd" ) - { - mtl.Kd = Color3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ); - } - else if ( t.value( 0 ) == "Ks" ) - { - mtl.Ks = Color3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ); - } - else if ( t.value( 0 ) == "d" ) - { - mtl.d = t.value( 1 ).toDouble(); - } - else if ( t.value( 0 ) == "Ns" ) - { - mtl.Ns = t.value( 1 ).toDouble(); - } - else if ( t.value( 0 ) == "map_Kd" ) - { - mtl.map_Kd = t.value( 1 ); - } - } - if ( ! mtlid.isEmpty() ) - omaterials.insert( mtlid, mtl ); -} - -static void addLink( NifModel * nif, QModelIndex iBlock, QString name, qint32 link ) -{ - QModelIndex iArray = nif->getIndex( iBlock, name ); - QModelIndex iSize = nif->getIndex( iBlock, QString( "Num %1" ).arg( name ) ); - int numIndices = nif->get( iSize ); - nif->set( iSize, numIndices + 1 ); - nif->updateArray( iArray ); - nif->setLink( iArray.child( numIndices, 0 ), link ); -} - -void importObj( NifModel * nif, const QModelIndex & index ) -{ - //--Determine how the file will import, and be sure the user wants to continue--// - - // If no existing node is selected, create a group node. Otherwise use selected node - QPersistentModelIndex iNode, iShape, iMaterial, iData, iTexProp, iTexSource; - QModelIndex iBlock = nif->getBlock( index ); - - //Be sure the user hasn't clicked on a NiTriStrips object - if ( iBlock.isValid() && nif->itemName(iBlock) == "NiTriStrips" ) - { - int result = QMessageBox::information( 0, "Import OBJ", "You cannot import an OBJ file over a NiTriStrips object. Please convert it to a NiTriShape object first by right-clicking and choosing Mesh > Triangulate" ); - return; - } - - if ( iBlock.isValid() && nif->itemName(iBlock) == "NiNode" ) - { - iNode = iBlock; - } - else if ( iBlock.isValid() && nif->itemName( iBlock ) == "NiTriShape" ) - { - iShape = iBlock; - //Find parent of NiTriShape - int par_num = nif->getParent( nif->getBlockNumber( iBlock ) ); - if ( par_num != -1 ) - { - iNode = nif->getBlock( par_num ); - } - - //Find material, texture, and data objects - QList children = nif->getChildLinks( nif->getBlockNumber(iShape) ); - for( QList::iterator it = children.begin(); it != children.end(); ++it ) - { - if ( *it != -1 ) - { - QModelIndex temp = nif->getBlock( *it ); - QString type = nif->itemName( temp ); - if ( type == "NiMaterialProperty" ) - { - iMaterial = temp; - } - else if ( type == "NiTriShapeData" ) - { - iData = temp; - } - else if ( (type == "NiTexturingProperty") || (type == "NiTextureProperty") ) - { - iTexProp = temp; - - //Search children of texture property for texture sources/images - QList children = nif->getChildLinks( nif->getBlockNumber(iTexProp) ); - for( QList::iterator it = children.begin(); it != children.end(); ++it ) - { - QModelIndex temp = nif->getBlock( *it ); - QString type = nif->itemName( temp ); - if ( (type == "NiSourceTexture") || (type == "NiImage") ) - { - iTexSource = temp; - } - } - } - } - } - } - - QString question; - if ( iNode.isValid() == true ) - { - if ( iShape.isValid() == true ) - { - question = "NiTriShape selected. The first imported mesh will replace the selected one."; - } - else - { - question = "NiNode selected. Meshes will be attached to the selected node."; - } - } - else - { - question = "No NiNode or NiTriShape selected. Meshes will be imported to the root of the file."; - } - - int result = QMessageBox::question( 0, "Import OBJ", question, QMessageBox::Ok, QMessageBox::Cancel ); - if ( result == QMessageBox::Cancel ) { - return; - } - - //--Read the file--// - - QSettings settings; - settings.beginGroup( "import-export" ); - settings.beginGroup( "obj" ); - - QString fname = QFileDialog::getOpenFileName( 0, "Choose a .OBJ file to import", settings.value( "File Name" ).toString(), "*.obj" ); - if ( fname.isEmpty() ) - return; - - QFile fobj( fname ); - if ( ! fobj.open( QIODevice::ReadOnly ) ) - { - qWarning() << "could not open " << fobj.fileName() << " for read access"; - return; - } - - QTextStream sobj( & fobj ); - - QVector overts; - QVector onorms; - QVector otexco; - QMap< QString, QVector * > ofaces; - QMap< QString, ObjMaterial > omaterials; - - QVector * mfaces = new QVector(); - - QString usemtl = "None"; - ofaces.insert( usemtl, mfaces ); - - while ( ! sobj.atEnd() ) - { // parse each line of the file - QString line = sobj.readLine(); - - QStringList t = line.split( " ", QString::SkipEmptyParts ); - - if ( t.value( 0 ) == "mtllib" ) - { - readMtlLib( fname.left( qMax( fname.lastIndexOf( "/" ), fname.lastIndexOf( "\\" ) ) + 1 ) + t.value( 1 ), omaterials ); - } - else if ( t.value( 0 ) == "usemtl" ) - { - usemtl = t.value( 1 ); - //if ( usemtl.contains( "_" ) ) - // usemtl = usemtl.left( usemtl.indexOf( "_" ) ); - - mfaces = ofaces.value( usemtl ); - if ( ! mfaces ) - { - mfaces = new QVector(); - ofaces.insert( usemtl, mfaces ); - } - } - else if ( t.value( 0 ) == "v" ) - { - overts.append( Vector3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ) ); - } - else if ( t.value( 0 ) == "vt" ) - { - otexco.append( Vector2( t.value( 1 ).toDouble(), 1.0 - t.value( 2 ).toDouble() ) ); - } - else if ( t.value( 0 ) == "vn" ) - { - onorms.append( Vector3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ) ); - } - else if ( t.value( 0 ) == "f" ) - { - if ( t.count() > 5 ) - { - qWarning() << "please triangulate your mesh before import"; - return; - } - - for ( int j = 1; j < t.count() - 2; j++ ) - { - ObjFace face; - for ( int i = 0; i < 3; i++ ) - { - QStringList lst = t.value( i == 0 ? 1 : j+i ).split( "/" ); - - int v = lst.value( 0 ).toInt(); - if ( v < 0 ) v += overts.count(); else v--; - - int t = lst.value( 1 ).toInt(); - if ( t < 0 ) v += otexco.count(); else t--; - - int n = lst.value( 2 ).toInt(); - if ( n < 0 ) n += onorms.count(); else n--; - - face.p[i].v = v; - face.p[i].t = t; - face.p[i].n = n; - } - mfaces->append( face ); - } - } - } - - //--Translate file structures into NIF ones--// - - if ( iNode.isValid() == false ) - { - iNode = nif->insertNiBlock( "NiNode" ); - nif->set( iNode, "Name", "Scene Root" ); - } - - // create a NiTriShape foreach material in the object - int shapecount = 0; - bool first_tri_shape = true; - QMapIterator< QString, QVector * > it( ofaces ); - while ( it.hasNext() ) - { - it.next(); - - if ( ! it.value()->count() ) - continue; - - if ( it.key() != "collision" ) - { - //If we are on the first shape, and one was selected in the 3D view, use the existing one - if ( iShape.isValid() == false || first_tri_shape == false ) - { - iShape = nif->insertNiBlock( "NiTriShape" ); - } - - nif->set( iShape, "Name", QString( "%1:%2" ).arg( nif->get( iNode, "Name" ) ).arg( shapecount++ ) ); - addLink( nif, iNode, "Children", nif->getBlockNumber( iShape ) ); - - if ( !omaterials.contains( it.key() ) ) - qWarning() << "material" << it.key() << "not found in mtllib"; - - ObjMaterial mtl = omaterials.value( it.key() ); - - if ( iMaterial.isValid() == false || first_tri_shape == false ) - { - iMaterial = nif->insertNiBlock( "NiMaterialProperty" ); - } - nif->set( iMaterial, "Name", it.key() ); - nif->set( iMaterial, "Ambient Color", mtl.Ka ); - nif->set( iMaterial, "Diffuse Color", mtl.Kd ); - nif->set( iMaterial, "Specular Color", mtl.Ks ); - nif->set( iMaterial, "Emissive Color", Color3( 0, 0, 0 ) ); - nif->set( iMaterial, "Alpha", mtl.d ); - nif->set( iMaterial, "Glossiness", mtl.Ns ); - - addLink( nif, iShape, "Properties", nif->getBlockNumber( iMaterial ) ); - - if ( ! mtl.map_Kd.isEmpty() ) - { - if ( nif->getVersionNumber() >= 0x0303000D ) - { - //Newer versions use NiTexturingProperty and NiSourceTexture - if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemType(iTexProp) != "NiTexturingProperty" ) - { - iTexProp = nif->insertNiBlock( "NiTexturingProperty" ); - } - addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); - - nif->set( iTexProp, "Has Base Texture", 1 ); - QModelIndex iBaseMap = nif->getIndex( iTexProp, "Base Texture" ); - nif->set( iBaseMap, "Clamp Mode", 3 ); - nif->set( iBaseMap, "Filter Mode", 2 ); - - if ( iTexSource.isValid() == false || first_tri_shape == false || nif->itemType(iTexSource) != "NiSourceTexture" ) - { - iTexSource = nif->insertNiBlock( "NiSourceTexture" ); - } - nif->setLink( iBaseMap, "Source", nif->getBlockNumber( iTexSource ) ); - - nif->set( iTexSource, "Pixel Layout", nif->getVersion() == "20.0.0.5" ? 6 : 5 ); - nif->set( iTexSource, "Use Mipmaps", 2 ); - nif->set( iTexSource, "Alpha Format", 3 ); - nif->set( iTexSource, "Unknown Byte", 1 ); - nif->set( iTexSource, "Unknown Byte 2", 1 ); - - nif->set( iTexSource, "Use External", 1 ); - nif->set( iTexSource, "File Name", TexCache::stripPath( mtl.map_Kd, nif->getFolder() ) ); - } else { - //Older versions use NiTextureProperty and NiImage - if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemType(iTexProp) != "NiTextureProperty" ) - { - iTexProp = nif->insertNiBlock( "NiTextureProperty" ); - } - addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); - - if ( iTexSource.isValid() == false || first_tri_shape == false || nif->itemType(iTexSource) != "NiImage" ) - { - iTexSource = nif->insertNiBlock( "NiImage" ); - } - - nif->setLink( iTexProp, "Image", nif->getBlockNumber( iTexSource ) ); - - nif->set( iTexSource, "External", 1 ); - nif->set( iTexSource, "File Name", TexCache::stripPath( mtl.map_Kd, nif->getFolder() ) ); - } - } - - if ( iData.isValid() == false || first_tri_shape == false ) - { - iData = nif->insertNiBlock( "NiTriShapeData" ); - } - nif->setLink( iShape, "Data", nif->getBlockNumber( iData ) ); - - QVector verts; - QVector norms; - QVector texco; - QVector triangles; - - QVector points; - - foreach ( ObjFace oface, *(it.value()) ) - { - Triangle tri; - - for ( int t = 0; t < 3; t++ ) - { - ObjPoint p = oface.p[t]; - int ix; - for ( ix = 0; ix < points.count(); ix++ ) - { - if ( points[ix] == p ) - break; - } - if ( ix == points.count() ) - { - points.append( p ); - verts.append( overts.value( p.v ) ); - norms.append( onorms.value( p.n ) ); - texco.append( otexco.value( p.t ) ); - } - tri[t] = ix; - } - - triangles.append( tri ); - } - - nif->set( iData, "Num Vertices", verts.count() ); - nif->set( iData, "Has Vertices", 1 ); - nif->updateArray( iData, "Vertices" ); - nif->setArray( iData, "Vertices", verts ); - nif->set( iData, "Has Normals", 1 ); - nif->updateArray( iData, "Normals" ); - nif->setArray( iData, "Normals", norms ); - nif->set( iData, "Has UV", 1 ); - nif->set( iData, "Num UV Sets", 1 ); - nif->set( iData, "Num UV Sets 2", 1 ); - QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); - if ( ! iTexCo.isValid() ) - iTexCo = nif->getIndex( iData, "UV Sets 2" ); - nif->updateArray( iTexCo ); - nif->updateArray( iTexCo.child( 0, 0 ) ); - nif->setArray( iTexCo.child( 0, 0 ), texco ); - - nif->set( iData, "Has Triangles", 1 ); - nif->set( iData, "Num Triangles", triangles.count() ); - nif->set( iData, "Num Triangle Points", triangles.count() * 3 ); - nif->updateArray( iData, "Triangles" ); - nif->setArray( iData, "Triangles", triangles ); - - Vector3 center; - foreach ( Vector3 v, verts ) - center += v; - if ( verts.count() > 0 ) center /= verts.count(); - nif->set( iData, "Center", center ); - float radius = 0; - foreach ( Vector3 v, verts ) - { - float d = ( center - v ).length(); - if ( d > radius ) radius = d; - } - nif->set( iData, "Radius", radius ); - - nif->set( iData, "Unknown Short 2", 0x4000 ); - } - else if ( nif->getVersionNumber() == 0x14000005 ) - { - // create experimental havok collision mesh - QVector verts; - QVector norms; - QVector triangles; - - QVector points; - - foreach ( ObjFace oface, *(it.value()) ) - { - Triangle tri; - - for ( int t = 0; t < 3; t++ ) - { - ObjPoint p = oface.p[t]; - int ix; - for ( ix = 0; ix < points.count(); ix++ ) - { - if ( points[ix] == p ) - break; - } - if ( ix == points.count() ) - { - points.append( p ); - verts.append( overts.value( p.v ) ); - norms.append( onorms.value( p.n ) ); - } - tri[t] = ix; - } - - triangles.append( tri ); - } - - QPersistentModelIndex iData = nif->insertNiBlock( "NiTriStripsData" ); - - nif->set( iData, "Num Vertices", verts.count() ); - nif->set( iData, "Has Vertices", 1 ); - nif->updateArray( iData, "Vertices" ); - nif->setArray( iData, "Vertices", verts ); - nif->set( iData, "Has Normals", 1 ); - nif->updateArray( iData, "Normals" ); - nif->setArray( iData, "Normals", norms ); - - Vector3 center; - foreach ( Vector3 v, verts ) - center += v; - if ( verts.count() > 0 ) center /= verts.count(); - nif->set( iData, "Center", center ); - float radius = 0; - foreach ( Vector3 v, verts ) - { - float d = ( center - v ).length(); - if ( d > radius ) radius = d; - } - nif->set( iData, "Radius", radius ); - - // do not stitch, because it looks better in the cs - QList< QVector< quint16 > > strips = stripify( triangles, false ); - - nif->set( iData, "Num Strips", strips.count() ); - nif->set( iData, "Has Points", 1 ); - - QModelIndex iLengths = nif->getIndex( iData, "Strip Lengths" ); - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - - if ( iLengths.isValid() && iPoints.isValid() ) - { - nif->updateArray( iLengths ); - nif->updateArray( iPoints ); - int x = 0; - int z = 0; - foreach ( QVector strip, strips ) - { - nif->set( iLengths.child( x, 0 ), strip.count() ); - QModelIndex iStrip = iPoints.child( x, 0 ); - nif->updateArray( iStrip ); - nif->setArray( iStrip, strip ); - x++; - z += strip.count() - 2; - } - nif->set( iData, "Num Triangles", z ); - } - - QPersistentModelIndex iShape = nif->insertNiBlock( "bhkNiTriStripsShape" ); - - nif->setArray( iShape, "Unknown Floats 1", QVector() << 0.1f << 0.0f ); - nif->setArray( iShape, "Unknown Ints 1", QVector() << 0 << 0 << 0 << 0 << 1 ); - nif->set( iShape, "Scale", Vector3( 1.0, 1.0, 1.0 ) ); - addLink( nif, iShape, "Strips Data", nif->getBlockNumber( iData ) ); - nif->set( iShape, "Num Data Layers", 1 ); - nif->updateArray( iShape, "Data Layers" ); - nif->setArray( iShape, "Data Layers", QVector() << 1 ); - - QPersistentModelIndex iBody = nif->insertNiBlock( "bhkRigidBody" ); - nif->setLink( iBody, "Shape", nif->getBlockNumber( iShape ) ); - - QPersistentModelIndex iObject = nif->insertNiBlock( "bhkCollisionObject" ); - nif->setLink( iObject, "Parent", nif->getBlockNumber( iNode ) ); - nif->set( iObject, "Unknown Short", 1 ); - nif->setLink( iObject, "Body", nif->getBlockNumber( iBody ) ); - - nif->setLink( iNode, "Collision Object", nif->getBlockNumber( iObject ) ); - } - - //Finished with the first shape which is the only one that can import over the top of existing data - first_tri_shape = false; - } - - qDeleteAll( ofaces ); - - settings.setValue( "File Name", fname ); - - nif->reset(); -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + + + +#include "../nifmodel.h" + +#include "../NvTriStrip/qtwrapper.h" + +#include "../gl/gltex.h" + +#include +#include +#include +#include +#include +#include + + + +/* + * .OBJ EXPORT + */ + + + +static void writeData( const NifModel * nif, const QModelIndex & iData, QTextStream & obj, int ofs[1], Transform t ) +{ + // copy vertices + + QVector verts = nif->getArray( iData, "Vertices" ); + foreach ( Vector3 v, verts ) + { + v = t * v; + obj << "v " << v[0] << " " << v[1] << " " << v[2] << "\r\n"; + } + + // copy texcoords + + QModelIndex iUV = nif->getIndex( iData, "UV Sets" ); + if ( ! iUV.isValid() ) + iUV = nif->getIndex( iData, "UV Sets 2" ); + + QVector texco = nif->getArray( iUV.child( 0, 0 ) ); + foreach( Vector2 t, texco ) + obj << "vt " << t[0] << " " << 1.0 - t[1] << "\r\n"; + + // copy normals + + QVector norms = nif->getArray( iData, "Normals" ); + foreach ( Vector3 n, norms ) + { + n = t.rotation * n; + obj << "vn " << n[0] << " " << n[1] << " " << n[2] << "\r\n"; + } + + // get the triangles + + QVector tris; + + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + if ( iPoints.isValid() ) + { + QList< QVector > strips; + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); + tris = triangulate( strips ); + } + else + { + tris = nif->getArray( iData, "Triangles" ); + } + + // write the triangles + + foreach ( Triangle t, tris ) + { + obj << "f"; + for ( int p = 0; p < 3; p++ ) + { + obj << " " << ofs[0] + t[p]; + if ( norms.count() ) + if ( texco.count() ) + obj << "/" << ofs[1] + t[p] << "/" << ofs[2] + t[p]; + else + obj << "//" << ofs[2] + t[p]; + else + if ( texco.count() ) + obj << "/" << ofs[1] + t[p]; + } + obj << "\r\n"; + } + + ofs[0] += verts.count(); + ofs[1] += texco.count(); + ofs[2] += norms.count(); +} + +static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextStream & obj, QTextStream & mtl, int ofs[], Transform t ) +{ + QString name = nif->get( iShape, "Name" ); + QString matn = name, texfn; + + Color3 mata, matd, mats; + float matt = 1.0, matg = 33.0; + + foreach ( qint32 link, nif->getChildLinks( nif->getBlockNumber( iShape ) ) ) + { + QModelIndex iProp = nif->getBlock( link ); + if ( nif->isNiBlock( iProp, "NiMaterialProperty" ) ) + { + mata = nif->get( iProp, "Ambient Color" ); + matd = nif->get( iProp, "Diffuse Color" ); + mats = nif->get( iProp, "Specular Color" ); + matt = nif->get( iProp, "Alpha" ); + matg = nif->get( iProp, "Glossiness" ); + //matn = nif->get( iProp, "Name" ); + } + else if ( nif->isNiBlock( iProp, "NiTexturingProperty" ) ) + { + QModelIndex iSource = nif->getBlock( nif->getLink( nif->getIndex( iProp, "Base Texture" ), "Source" ), "NiSourceTexture" ); + + texfn = TexCache::find( nif->get( iSource, "File Name" ), nif->getFolder() ); + } + else if ( nif->isNiBlock( iProp, "NiTextureProperty" ) ) + { + QModelIndex iSource = nif->getBlock( nif->getLink( iProp, "Image" ), "NiImage" ); + texfn = TexCache::find( nif->get( iSource, "File Name" ), nif->getFolder() ); + } + } + + //if ( ! texfn.isEmpty() ) + // matn += ":" + texfn; + + matn = QString( "Material.%1" ).arg( ofs[0], 6, 16, QChar( '0' ) ); + + mtl << "\r\n"; + mtl << "newmtl " << matn << "\r\n"; + mtl << "Ka " << mata[0] << " " << mata[1] << " " << mata[2] << "\r\n"; + mtl << "Kd " << matd[0] << " " << matd[1] << " " << matd[2] << "\r\n"; + mtl << "Ks " << mats[0] << " " << mats[1] << " " << mats[2] << "\r\n"; + mtl << "d " << matt << "\r\n"; + mtl << "Ns " << matg << "\r\n"; + if ( ! texfn.isEmpty() ) + mtl << "map_Kd " << texfn << "\r\n\r\n"; + + obj << "\r\n# " << name << "\r\n\r\ng " << name << "\r\n" << "usemtl " << matn << "\r\n\r\n"; + + writeData( nif, nif->getBlock( nif->getLink( iShape, "Data" ) ), obj, ofs, t ); +} + +static void writeParent( const NifModel * nif, const QModelIndex & iNode, QTextStream & obj, QTextStream & mtl, int ofs[], Transform t ) +{ + t = t * Transform( nif, iNode ); + foreach ( int l, nif->getChildLinks( nif->getBlockNumber( iNode ) ) ) + { + QModelIndex iChild = nif->getBlock( l ); + if ( nif->inherits( iChild, "NiNode" ) ) + writeParent( nif, iChild, obj, mtl, ofs, t ); + else if ( nif->isNiBlock( iChild, "NiTriShape" ) || nif->isNiBlock( iChild, "NiTriStrips" ) ) + writeShape( nif, iChild, obj, mtl, ofs, t * Transform( nif, iChild ) ); + else if ( nif->inherits( iChild, "NiCollisionObject" ) ) + { + QModelIndex iBody = nif->getBlock( nif->getLink( iChild, "Body" ) ); + if ( iBody.isValid() ) + { + Transform bt; + bt.scale = 7; + if ( nif->isNiBlock( iBody, "bhkRigidBodyT" ) ) + { + bt.rotation.fromQuat( nif->get( iBody, "Rotation" ) ); + bt.translation = nif->get( iBody, "Translation" ) * 7; + } + QModelIndex iShape = nif->getBlock( nif->getLink( iBody, "Shape" ) ); + if ( nif->isNiBlock( iShape, "bhkMoppBvTreeShape" ) ) + { + iShape = nif->getBlock( nif->getLink( iShape, "Shape" ) ); + if ( nif->isNiBlock( iShape, "bhkPackedNiTriStripsShape" ) ) + { + QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + if ( nif->isNiBlock( iData, "hkPackedNiTriStripsData" ) ) + { + bt = t * bt; + obj << "\r\n# bhkPackedNiTriStripsShape\r\n\r\ng collision\r\n" << "usemtl collision\r\n\r\n"; + QVector verts = nif->getArray( iData, "Vertices" ); + foreach ( Vector3 v, verts ) + { + v = bt * v; + obj << "v " << v[0] << " " << v[1] << " " << v[2] << "\r\n"; + } + + QModelIndex iTris = nif->getIndex( iData, "Triangles" ); + for ( int t = 0; t < nif->rowCount( iTris ); t++ ) + { + Triangle tri = nif->get( iTris.child( t, 0 ), "Triangle" ); + Vector3 n = nif->get( iTris.child( t, 0 ), "Normal" ); + + Vector3 a = verts.value( tri[0] ); + Vector3 b = verts.value( tri[1] ); + Vector3 c = verts.value( tri[2] ); + + Vector3 fn = Vector3::crossproduct( b - a, c - a ); + fn.normalize(); + + bool flip = Vector3::dotproduct( n, fn ) < 0; + + obj << "f" + << " " << tri[0] + ofs[0] + << " " << tri[ flip ? 2 : 1 ] + ofs[0] + << " " << tri[ flip ? 1 : 2 ] + ofs[0] + << "\r\n"; + } + ofs[0] += verts.count(); + } + } + } + else if ( nif->isNiBlock( iShape, "bhkNiTriStripsShape" ) ) + { + bt.scale = 1; + obj << "\r\n# bhkNiTriStripsShape\r\n\r\ng collision\r\n" << "usemtl collision\r\n\r\n"; + QModelIndex iStrips = nif->getIndex( iShape, "Strips Data" ); + for ( int r = 0; r < nif->rowCount( iStrips ); r++ ) + writeData( nif, nif->getBlock( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ), obj, ofs, t * bt ); + } + } + } + } +} + +void exportObj( const NifModel * nif, const QModelIndex & index ) +{ + //--Determine how the file will export, and be sure the user wants to continue--// + QList roots; + QModelIndex iBlock = nif->getBlock( index ); + + QString question; + if ( iBlock.isValid() ) + { + roots.append( nif->getBlockNumber(index) ); + if ( nif->itemName(index) == "NiNode" ) + { + question = "NiNode selected. All children of selected node will be exported."; + } else if ( nif->itemName(index) == "NiTriShape" || nif->itemName(index) == "NiTriStrips" ) + { + question = nif->itemName(index) + QString(" selected. Selected mesh will be exported."); + } + } + + if ( question.size() == 0 ) + { + question = "No NiNode, NiTriShape,or NiTriStrips is selected. Entire scene will be exported."; + roots = nif->getRootLinks(); + } + + int result = QMessageBox::question( 0, "Export OBJ", question, QMessageBox::Ok, QMessageBox::Cancel ); + if ( result == QMessageBox::Cancel ) { + return; + } + + //--Allow the user to select the file--// + + QSettings settings; + settings.beginGroup( "import-export" ); + settings.beginGroup( "obj" ); + + QString fname = QFileDialog::getSaveFileName( 0, "Choose a .OBJ file for export", settings.value( "File Name" ).toString(), "*.obj" ); + if ( fname.isEmpty() ) + return; + + while ( fname.endsWith( ".obj", Qt::CaseInsensitive ) ) + fname = fname.left( fname.length() - 4 ); + + QFile fobj( fname + ".obj" ); + if ( ! fobj.open( QIODevice::WriteOnly ) ) + { + qWarning() << "could not open " << fobj.fileName() << " for write access"; + return; + } + + QFile fmtl( fname + ".mtl" ); + if ( ! fmtl.open( QIODevice::WriteOnly ) ) + { + qWarning() << "could not open " << fmtl.fileName() << " for write access"; + return; + } + + fname = fmtl.fileName(); + int i = fname.lastIndexOf( "/" ); + if ( i >= 0 ) + fname = fname.remove( 0, i+1 ); + + QTextStream sobj( &fobj ); + QTextStream smtl( &fmtl ); + + sobj << "# exported with NifSkope\r\n\r\n" << "mtllib " << fname << "\r\n"; + + //--Translate NIF structure into file structure --// + + int ofs[3] = { 1, 1, 1 }; + foreach ( int l, roots ) + { + QModelIndex iBlock = nif->getBlock( l ); + if ( nif->inherits( iBlock, "NiNode" ) ) + writeParent( nif, iBlock, sobj, smtl, ofs, Transform() ); + else if ( nif->isNiBlock( iBlock, "NiTriShape" ) || nif->isNiBlock( iBlock, "NiTriStrips" ) ) + writeShape( nif, iBlock, sobj, smtl, ofs, Transform() ); + } + + settings.setValue( "File Name", fobj.fileName() ); +} + + + +/* + * .OBJ IMPORT + */ + + +struct ObjPoint +{ + int v, t, n; + + bool operator==( const ObjPoint & other ) const + { + return v == other.v && t == other.t && n == other.n; + } +}; + +struct ObjFace +{ + ObjPoint p[3]; +}; + +struct ObjMaterial +{ + Color3 Ka, Kd, Ks; + float d, Ns; + QString map_Kd; + + ObjMaterial() : d( 1.0 ), Ns( 31.0 ) {} +}; + +static void readMtlLib( const QString & fname, QMap< QString, ObjMaterial > & omaterials ) +{ + QFile file( fname ); + if ( ! file.open( QIODevice::ReadOnly ) ) + { + qWarning() << "failed to open" << fname; + return; + } + + QTextStream smtl( &file ); + + QString mtlid; + ObjMaterial mtl; + + while ( ! smtl.atEnd() ) + { + QString line = smtl.readLine(); + + QStringList t = line.split( " ", QString::SkipEmptyParts ); + + if ( t.value( 0 ) == "newmtl" ) + { + if ( ! mtlid.isEmpty() ) + omaterials.insert( mtlid, mtl ); + mtlid = t.value( 1 ); + mtl = ObjMaterial(); + } + else if ( t.value( 0 ) == "Ka" ) + { + mtl.Ka = Color3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ); + } + else if ( t.value( 0 ) == "Kd" ) + { + mtl.Kd = Color3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ); + } + else if ( t.value( 0 ) == "Ks" ) + { + mtl.Ks = Color3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ); + } + else if ( t.value( 0 ) == "d" ) + { + mtl.d = t.value( 1 ).toDouble(); + } + else if ( t.value( 0 ) == "Ns" ) + { + mtl.Ns = t.value( 1 ).toDouble(); + } + else if ( t.value( 0 ) == "map_Kd" ) + { + mtl.map_Kd = t.value( 1 ); + } + } + if ( ! mtlid.isEmpty() ) + omaterials.insert( mtlid, mtl ); +} + +static void addLink( NifModel * nif, QModelIndex iBlock, QString name, qint32 link ) +{ + QModelIndex iArray = nif->getIndex( iBlock, name ); + QModelIndex iSize = nif->getIndex( iBlock, QString( "Num %1" ).arg( name ) ); + int numIndices = nif->get( iSize ); + nif->set( iSize, numIndices + 1 ); + nif->updateArray( iArray ); + nif->setLink( iArray.child( numIndices, 0 ), link ); +} + +void importObj( NifModel * nif, const QModelIndex & index ) +{ + //--Determine how the file will import, and be sure the user wants to continue--// + + // If no existing node is selected, create a group node. Otherwise use selected node + QPersistentModelIndex iNode, iShape, iMaterial, iData, iTexProp, iTexSource; + QModelIndex iBlock = nif->getBlock( index ); + + //Be sure the user hasn't clicked on a NiTriStrips object + if ( iBlock.isValid() && nif->itemName(iBlock) == "NiTriStrips" ) + { + int result = QMessageBox::information( 0, "Import OBJ", "You cannot import an OBJ file over a NiTriStrips object. Please convert it to a NiTriShape object first by right-clicking and choosing Mesh > Triangulate" ); + return; + } + + if ( iBlock.isValid() && nif->itemName(iBlock) == "NiNode" ) + { + iNode = iBlock; + } + else if ( iBlock.isValid() && nif->itemName( iBlock ) == "NiTriShape" ) + { + iShape = iBlock; + //Find parent of NiTriShape + int par_num = nif->getParent( nif->getBlockNumber( iBlock ) ); + if ( par_num != -1 ) + { + iNode = nif->getBlock( par_num ); + } + + //Find material, texture, and data objects + QList children = nif->getChildLinks( nif->getBlockNumber(iShape) ); + for( QList::iterator it = children.begin(); it != children.end(); ++it ) + { + if ( *it != -1 ) + { + QModelIndex temp = nif->getBlock( *it ); + QString type = nif->itemName( temp ); + if ( type == "NiMaterialProperty" ) + { + iMaterial = temp; + } + else if ( type == "NiTriShapeData" ) + { + iData = temp; + } + else if ( (type == "NiTexturingProperty") || (type == "NiTextureProperty") ) + { + iTexProp = temp; + + //Search children of texture property for texture sources/images + QList children = nif->getChildLinks( nif->getBlockNumber(iTexProp) ); + for( QList::iterator it = children.begin(); it != children.end(); ++it ) + { + QModelIndex temp = nif->getBlock( *it ); + QString type = nif->itemName( temp ); + if ( (type == "NiSourceTexture") || (type == "NiImage") ) + { + iTexSource = temp; + } + } + } + } + } + } + + QString question; + if ( iNode.isValid() == true ) + { + if ( iShape.isValid() == true ) + { + question = "NiTriShape selected. The first imported mesh will replace the selected one."; + } + else + { + question = "NiNode selected. Meshes will be attached to the selected node."; + } + } + else + { + question = "No NiNode or NiTriShape selected. Meshes will be imported to the root of the file."; + } + + int result = QMessageBox::question( 0, "Import OBJ", question, QMessageBox::Ok, QMessageBox::Cancel ); + if ( result == QMessageBox::Cancel ) { + return; + } + + //--Read the file--// + + QSettings settings; + settings.beginGroup( "import-export" ); + settings.beginGroup( "obj" ); + + QString fname = QFileDialog::getOpenFileName( 0, "Choose a .OBJ file to import", settings.value( "File Name" ).toString(), "*.obj" ); + if ( fname.isEmpty() ) + return; + + QFile fobj( fname ); + if ( ! fobj.open( QIODevice::ReadOnly ) ) + { + qWarning() << "could not open " << fobj.fileName() << " for read access"; + return; + } + + QTextStream sobj( & fobj ); + + QVector overts; + QVector onorms; + QVector otexco; + QMap< QString, QVector * > ofaces; + QMap< QString, ObjMaterial > omaterials; + + QVector * mfaces = new QVector(); + + QString usemtl = "None"; + ofaces.insert( usemtl, mfaces ); + + while ( ! sobj.atEnd() ) + { // parse each line of the file + QString line = sobj.readLine(); + + QStringList t = line.split( " ", QString::SkipEmptyParts ); + + if ( t.value( 0 ) == "mtllib" ) + { + readMtlLib( fname.left( qMax( fname.lastIndexOf( "/" ), fname.lastIndexOf( "\\" ) ) + 1 ) + t.value( 1 ), omaterials ); + } + else if ( t.value( 0 ) == "usemtl" ) + { + usemtl = t.value( 1 ); + //if ( usemtl.contains( "_" ) ) + // usemtl = usemtl.left( usemtl.indexOf( "_" ) ); + + mfaces = ofaces.value( usemtl ); + if ( ! mfaces ) + { + mfaces = new QVector(); + ofaces.insert( usemtl, mfaces ); + } + } + else if ( t.value( 0 ) == "v" ) + { + overts.append( Vector3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ) ); + } + else if ( t.value( 0 ) == "vt" ) + { + otexco.append( Vector2( t.value( 1 ).toDouble(), 1.0 - t.value( 2 ).toDouble() ) ); + } + else if ( t.value( 0 ) == "vn" ) + { + onorms.append( Vector3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ) ); + } + else if ( t.value( 0 ) == "f" ) + { + if ( t.count() > 5 ) + { + qWarning() << "please triangulate your mesh before import"; + return; + } + + for ( int j = 1; j < t.count() - 2; j++ ) + { + ObjFace face; + for ( int i = 0; i < 3; i++ ) + { + QStringList lst = t.value( i == 0 ? 1 : j+i ).split( "/" ); + + int v = lst.value( 0 ).toInt(); + if ( v < 0 ) v += overts.count(); else v--; + + int t = lst.value( 1 ).toInt(); + if ( t < 0 ) v += otexco.count(); else t--; + + int n = lst.value( 2 ).toInt(); + if ( n < 0 ) n += onorms.count(); else n--; + + face.p[i].v = v; + face.p[i].t = t; + face.p[i].n = n; + } + mfaces->append( face ); + } + } + } + + //--Translate file structures into NIF ones--// + + if ( iNode.isValid() == false ) + { + iNode = nif->insertNiBlock( "NiNode" ); + nif->set( iNode, "Name", "Scene Root" ); + } + + // create a NiTriShape foreach material in the object + int shapecount = 0; + bool first_tri_shape = true; + QMapIterator< QString, QVector * > it( ofaces ); + while ( it.hasNext() ) + { + it.next(); + + if ( ! it.value()->count() ) + continue; + + if ( it.key() != "collision" ) + { + //If we are on the first shape, and one was selected in the 3D view, use the existing one + if ( iShape.isValid() == false || first_tri_shape == false ) + { + iShape = nif->insertNiBlock( "NiTriShape" ); + } + + nif->set( iShape, "Name", QString( "%1:%2" ).arg( nif->get( iNode, "Name" ) ).arg( shapecount++ ) ); + addLink( nif, iNode, "Children", nif->getBlockNumber( iShape ) ); + + if ( !omaterials.contains( it.key() ) ) + qWarning() << "material" << it.key() << "not found in mtllib"; + + ObjMaterial mtl = omaterials.value( it.key() ); + + if ( iMaterial.isValid() == false || first_tri_shape == false ) + { + iMaterial = nif->insertNiBlock( "NiMaterialProperty" ); + } + nif->set( iMaterial, "Name", it.key() ); + nif->set( iMaterial, "Ambient Color", mtl.Ka ); + nif->set( iMaterial, "Diffuse Color", mtl.Kd ); + nif->set( iMaterial, "Specular Color", mtl.Ks ); + nif->set( iMaterial, "Emissive Color", Color3( 0, 0, 0 ) ); + nif->set( iMaterial, "Alpha", mtl.d ); + nif->set( iMaterial, "Glossiness", mtl.Ns ); + + addLink( nif, iShape, "Properties", nif->getBlockNumber( iMaterial ) ); + + if ( ! mtl.map_Kd.isEmpty() ) + { + if ( nif->getVersionNumber() >= 0x0303000D ) + { + //Newer versions use NiTexturingProperty and NiSourceTexture + if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemType(iTexProp) != "NiTexturingProperty" ) + { + iTexProp = nif->insertNiBlock( "NiTexturingProperty" ); + } + addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); + + nif->set( iTexProp, "Has Base Texture", 1 ); + QModelIndex iBaseMap = nif->getIndex( iTexProp, "Base Texture" ); + nif->set( iBaseMap, "Clamp Mode", 3 ); + nif->set( iBaseMap, "Filter Mode", 2 ); + + if ( iTexSource.isValid() == false || first_tri_shape == false || nif->itemType(iTexSource) != "NiSourceTexture" ) + { + iTexSource = nif->insertNiBlock( "NiSourceTexture" ); + } + nif->setLink( iBaseMap, "Source", nif->getBlockNumber( iTexSource ) ); + + nif->set( iTexSource, "Pixel Layout", nif->getVersion() == "20.0.0.5" ? 6 : 5 ); + nif->set( iTexSource, "Use Mipmaps", 2 ); + nif->set( iTexSource, "Alpha Format", 3 ); + nif->set( iTexSource, "Unknown Byte", 1 ); + nif->set( iTexSource, "Unknown Byte 2", 1 ); + + nif->set( iTexSource, "Use External", 1 ); + nif->set( iTexSource, "File Name", TexCache::stripPath( mtl.map_Kd, nif->getFolder() ) ); + } else { + //Older versions use NiTextureProperty and NiImage + if ( iTexProp.isValid() == false || first_tri_shape == false || nif->itemType(iTexProp) != "NiTextureProperty" ) + { + iTexProp = nif->insertNiBlock( "NiTextureProperty" ); + } + addLink( nif, iShape, "Properties", nif->getBlockNumber( iTexProp ) ); + + if ( iTexSource.isValid() == false || first_tri_shape == false || nif->itemType(iTexSource) != "NiImage" ) + { + iTexSource = nif->insertNiBlock( "NiImage" ); + } + + nif->setLink( iTexProp, "Image", nif->getBlockNumber( iTexSource ) ); + + nif->set( iTexSource, "External", 1 ); + nif->set( iTexSource, "File Name", TexCache::stripPath( mtl.map_Kd, nif->getFolder() ) ); + } + } + + if ( iData.isValid() == false || first_tri_shape == false ) + { + iData = nif->insertNiBlock( "NiTriShapeData" ); + } + nif->setLink( iShape, "Data", nif->getBlockNumber( iData ) ); + + QVector verts; + QVector norms; + QVector texco; + QVector triangles; + + QVector points; + + foreach ( ObjFace oface, *(it.value()) ) + { + Triangle tri; + + for ( int t = 0; t < 3; t++ ) + { + ObjPoint p = oface.p[t]; + int ix; + for ( ix = 0; ix < points.count(); ix++ ) + { + if ( points[ix] == p ) + break; + } + if ( ix == points.count() ) + { + points.append( p ); + verts.append( overts.value( p.v ) ); + norms.append( onorms.value( p.n ) ); + texco.append( otexco.value( p.t ) ); + } + tri[t] = ix; + } + + triangles.append( tri ); + } + + nif->set( iData, "Num Vertices", verts.count() ); + nif->set( iData, "Has Vertices", 1 ); + nif->updateArray( iData, "Vertices" ); + nif->setArray( iData, "Vertices", verts ); + nif->set( iData, "Has Normals", 1 ); + nif->updateArray( iData, "Normals" ); + nif->setArray( iData, "Normals", norms ); + nif->set( iData, "Has UV", 1 ); + nif->set( iData, "Num UV Sets", 1 ); + nif->set( iData, "Num UV Sets 2", 1 ); + QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); + if ( ! iTexCo.isValid() ) + iTexCo = nif->getIndex( iData, "UV Sets 2" ); + nif->updateArray( iTexCo ); + nif->updateArray( iTexCo.child( 0, 0 ) ); + nif->setArray( iTexCo.child( 0, 0 ), texco ); + + nif->set( iData, "Has Triangles", 1 ); + nif->set( iData, "Num Triangles", triangles.count() ); + nif->set( iData, "Num Triangle Points", triangles.count() * 3 ); + nif->updateArray( iData, "Triangles" ); + nif->setArray( iData, "Triangles", triangles ); + + Vector3 center; + foreach ( Vector3 v, verts ) + center += v; + if ( verts.count() > 0 ) center /= verts.count(); + nif->set( iData, "Center", center ); + float radius = 0; + foreach ( Vector3 v, verts ) + { + float d = ( center - v ).length(); + if ( d > radius ) radius = d; + } + nif->set( iData, "Radius", radius ); + + nif->set( iData, "Unknown Short 2", 0x4000 ); + } + else if ( nif->getVersionNumber() == 0x14000005 ) + { + // create experimental havok collision mesh + QVector verts; + QVector norms; + QVector triangles; + + QVector points; + + foreach ( ObjFace oface, *(it.value()) ) + { + Triangle tri; + + for ( int t = 0; t < 3; t++ ) + { + ObjPoint p = oface.p[t]; + int ix; + for ( ix = 0; ix < points.count(); ix++ ) + { + if ( points[ix] == p ) + break; + } + if ( ix == points.count() ) + { + points.append( p ); + verts.append( overts.value( p.v ) ); + norms.append( onorms.value( p.n ) ); + } + tri[t] = ix; + } + + triangles.append( tri ); + } + + QPersistentModelIndex iData = nif->insertNiBlock( "NiTriStripsData" ); + + nif->set( iData, "Num Vertices", verts.count() ); + nif->set( iData, "Has Vertices", 1 ); + nif->updateArray( iData, "Vertices" ); + nif->setArray( iData, "Vertices", verts ); + nif->set( iData, "Has Normals", 1 ); + nif->updateArray( iData, "Normals" ); + nif->setArray( iData, "Normals", norms ); + + Vector3 center; + foreach ( Vector3 v, verts ) + center += v; + if ( verts.count() > 0 ) center /= verts.count(); + nif->set( iData, "Center", center ); + float radius = 0; + foreach ( Vector3 v, verts ) + { + float d = ( center - v ).length(); + if ( d > radius ) radius = d; + } + nif->set( iData, "Radius", radius ); + + // do not stitch, because it looks better in the cs + QList< QVector< quint16 > > strips = stripify( triangles, false ); + + nif->set( iData, "Num Strips", strips.count() ); + nif->set( iData, "Has Points", 1 ); + + QModelIndex iLengths = nif->getIndex( iData, "Strip Lengths" ); + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + + if ( iLengths.isValid() && iPoints.isValid() ) + { + nif->updateArray( iLengths ); + nif->updateArray( iPoints ); + int x = 0; + int z = 0; + foreach ( QVector strip, strips ) + { + nif->set( iLengths.child( x, 0 ), strip.count() ); + QModelIndex iStrip = iPoints.child( x, 0 ); + nif->updateArray( iStrip ); + nif->setArray( iStrip, strip ); + x++; + z += strip.count() - 2; + } + nif->set( iData, "Num Triangles", z ); + } + + QPersistentModelIndex iShape = nif->insertNiBlock( "bhkNiTriStripsShape" ); + + nif->setArray( iShape, "Unknown Floats 1", QVector() << 0.1f << 0.0f ); + nif->setArray( iShape, "Unknown Ints 1", QVector() << 0 << 0 << 0 << 0 << 1 ); + nif->set( iShape, "Scale", Vector3( 1.0, 1.0, 1.0 ) ); + addLink( nif, iShape, "Strips Data", nif->getBlockNumber( iData ) ); + nif->set( iShape, "Num Data Layers", 1 ); + nif->updateArray( iShape, "Data Layers" ); + nif->setArray( iShape, "Data Layers", QVector() << 1 ); + + QPersistentModelIndex iBody = nif->insertNiBlock( "bhkRigidBody" ); + nif->setLink( iBody, "Shape", nif->getBlockNumber( iShape ) ); + + QPersistentModelIndex iObject = nif->insertNiBlock( "bhkCollisionObject" ); + nif->setLink( iObject, "Parent", nif->getBlockNumber( iNode ) ); + nif->set( iObject, "Unknown Short", 1 ); + nif->setLink( iObject, "Body", nif->getBlockNumber( iBody ) ); + + nif->setLink( iNode, "Collision Object", nif->getBlockNumber( iObject ) ); + } + + //Finished with the first shape which is the only one that can import over the top of existing data + first_tri_shape = false; + } + + qDeleteAll( ofaces ); + + settings.setValue( "File Name", fname ); + + nif->reset(); +} + diff --git a/kfmmodel.cpp b/kfmmodel.cpp index 1e6ff1268..373f782ec 100644 --- a/kfmmodel.cpp +++ b/kfmmodel.cpp @@ -1,329 +1,329 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "kfmmodel.h" - -KfmModel::KfmModel( QObject * parent ) : BaseModel( parent ) -{ - clear(); -} - -QModelIndex KfmModel::getKFMroot() const -{ - if ( kfmroot ) - return createIndex( 0, 0, kfmroot ); - else - return QModelIndex(); -} - -QString KfmModel::version2string( quint32 v ) -{ - if ( v == 0 ) return QString(); - QString s = QString::number( ( v >> 24 ) & 0xff, 16 ) + "." - + QString::number( ( v >> 16 ) & 0xff, 16 ) + "." - + QString::number( ( v >> 8 ) & 0xff, 16 ) + "." - + QString::number( v & 0xff, 16 ); - return s; -} - -quint32 KfmModel::version2number( const QString & s ) -{ - if ( s.isEmpty() ) return 0; - QStringList l = s.split( "." ); - if ( l.count() <= 1 ) - { - bool ok; - quint32 i = s.toUInt( &ok ); - return ( i == 0xffffffff ? 0 : i ); - } - quint32 v = 0; - for ( int i = 0; i < l.count(); i++ ) - v += l[i].toInt( 0, 16 ) << ( (3-i) * 8 ); - return v; -} - -bool KfmModel::evalVersion( NifItem * item, bool chkParents ) const -{ - if ( item == root ) - return true; - - if ( chkParents && item->parent() ) - if ( ! evalVersion( item->parent(), true ) ) - return false; - - return item->evalVersion( version ); -} - -void KfmModel::clear() -{ - folder = QString(); - root->killChildren(); - insertType( root, NifData( "Kfm", "Kfm" ) ); - kfmroot = root->child( 0 ); - version = 0x0200000b; - reset(); - if ( kfmroot ) - set( kfmroot, "Header String", ";Gamebryo KFM File Version 2.0.0.0b" ); -} - -/* - * array functions - */ - -static QString parentPrefix( const QString & x ) -{ - for ( int c = 0; c < x.length(); c++ ) - if ( ! x[c].isNumber() ) - return QString( "../" ) + x; - return x; -} - -bool KfmModel::updateArrayItem( NifItem * array, bool fast ) -{ - if ( array->arr1().isEmpty() ) - return false; - - int d1 = getArraySize( array ); - if ( d1 > 1024 * 1024 * 8 ) - { - msg( Message() << "array" << array->name() << "much too large" ); - return false; - } - - int rows = array->childCount(); - if ( d1 > rows ) - { - NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::type( array->type() ) ), parentPrefix( array->arg() ), parentPrefix( array->arr2() ), QString(), QString(), 0, 0 ); - - if ( ! fast ) beginInsertRows( createIndex( array->row(), 0, array ), rows, d1-1 ); - array->prepareInsert( d1 - rows ); - for ( int c = rows; c < d1; c++ ) - insertType( array, data ); - if ( ! fast ) endInsertRows(); - } - if ( d1 < rows ) - { - if ( ! fast ) beginRemoveRows( createIndex( array->row(), 0, array ), d1, rows - 1 ); - array->removeChildren( d1, rows - d1 ); - if ( ! fast ) endRemoveRows(); - } - return true; -} - -/* - * basic and compound type functions - */ - -void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) -{ - if ( ! data.arr1().isEmpty() ) - { - NifItem * array = insertBranch( parent, data, at ); - - if ( evalCondition( array ) ) - updateArrayItem( array, true ); - return; - } - - NifBlock * compound = compounds.value( data.type() ); - if ( compound ) - { - NifItem * branch = insertBranch( parent, data, at ); - branch->prepareInsert( compound->types.count() ); - if ( ! data.arg().isEmpty() || ! data.temp().isEmpty() ) - { - QString arg = parentPrefix( data.arg() ); - QString tmp = data.temp(); - if ( tmp == "TEMPLATE" ) - { - NifItem * tItem = branch; - while ( tmp == "TEMPLATE" && tItem->parent() ) - { - tItem = tItem->parent(); - tmp = tItem->temp(); - } - } - foreach ( NifData d, compound->types ) - { - if ( d.type() == "TEMPLATE" ) - { - d.setType( tmp ); - d.value.changeType( NifValue::type( tmp ) ); - } - if ( d.arg() == "ARG" ) d.setArg( data.arg() ); - if ( d.arr1() == "ARG" ) d.setArr1( arg ); - if ( d.arr2() == "ARG" ) d.setArr2( arg ); - if ( d.cond().contains( "ARG" ) ) { QString x = d.cond(); x.replace( x.indexOf( "ARG" ), 5, arg ); d.setCond( x ); } - insertType( branch, d ); - } - } - else - foreach ( NifData d, compound->types ) - insertType( branch, d ); - } - else - parent->insertChild( data, at ); -} - - -/* - * item value functions - */ - -bool KfmModel::setItemValue( NifItem * item, const NifValue & val ) -{ - item->value() = val; - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - return true; -} - -/* - * load and save - */ - -bool KfmModel::setHeaderString( const QString & s ) -{ - //msg( DbgMsg() << s << s.right( s.length() - 27 ) ); - if ( s.startsWith( ";Gamebryo KFM File Version " ) ) - { - version = version2number( s.right( s.length() - 27 ) ); - if ( isVersionSupported( version ) ) - { - return true; - } - else - { - msg( Message() << "version" << version2string( version ) << "not supported yet" ); - return false; - } - } - else - { - msg( Message() << "this is not a KFM" ); - return false; - } -} - -bool KfmModel::load( QIODevice & device ) -{ - clear(); - - NifIStream stream( this, &device ); - - if ( !kfmroot || !load( kfmroot, stream, true ) ) - { - msg( Message() << "failed to load kfm file (version" << version << ")" ); - return false; - } - - reset(); - return true; -} - -bool KfmModel::save( QIODevice & device ) const -{ - NifOStream stream( this, &device ); - - if ( ! kfmroot || save( kfmroot, stream ) ) - { - msg( Message() << "failed to write kfm file" ); - return false; - } - return true; -} - -bool KfmModel::load( NifItem * parent, NifIStream & stream, bool fast ) -{ - if ( ! parent ) return false; - - for ( int row = 0; row < parent->childCount(); row++ ) - { - NifItem * child = parent->child( row ); - - if ( evalCondition( child ) ) - { - if ( ! child->arr1().isEmpty() ) - { - if ( ! updateArrayItem( child, fast ) ) - return false; - - if ( ! load( child, stream, fast ) ) - return false; - } - else if ( child->childCount() > 0 ) - { - if ( ! load( child, stream, fast ) ) - return false; - } - else - { - if ( ! stream.read( child->value() ) ) - return false; - } - } - } - return true; -} - -bool KfmModel::save( NifItem * parent, NifOStream & stream ) const -{ - if ( ! parent ) return false; - - for ( int row = 0; row < parent->childCount(); row++ ) - { - NifItem * child = parent->child( row ); - if ( evalCondition( child ) ) - { - if ( ! child->arr1().isEmpty() || ! child->arr2().isEmpty() || child->childCount() > 0 ) - { - if ( ! child->arr1().isEmpty() && child->childCount() != getArraySize( child ) ) - msg( Message() << child->name() << "array size mismatch" ); - - if ( !save( child, stream ) ) - return false; - } - else - { - if ( ! stream.write( child->value() ) ) - return false; - } - } - } - return true; -} - -NifItem * KfmModel::insertBranch( NifItem * parentItem, const NifData & data, int at ) -{ - NifItem * item = parentItem->insertChild( data, at ); - item->value().changeType( NifValue::tNone ); - return item; -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "kfmmodel.h" + +KfmModel::KfmModel( QObject * parent ) : BaseModel( parent ) +{ + clear(); +} + +QModelIndex KfmModel::getKFMroot() const +{ + if ( kfmroot ) + return createIndex( 0, 0, kfmroot ); + else + return QModelIndex(); +} + +QString KfmModel::version2string( quint32 v ) +{ + if ( v == 0 ) return QString(); + QString s = QString::number( ( v >> 24 ) & 0xff, 16 ) + "." + + QString::number( ( v >> 16 ) & 0xff, 16 ) + "." + + QString::number( ( v >> 8 ) & 0xff, 16 ) + "." + + QString::number( v & 0xff, 16 ); + return s; +} + +quint32 KfmModel::version2number( const QString & s ) +{ + if ( s.isEmpty() ) return 0; + QStringList l = s.split( "." ); + if ( l.count() <= 1 ) + { + bool ok; + quint32 i = s.toUInt( &ok ); + return ( i == 0xffffffff ? 0 : i ); + } + quint32 v = 0; + for ( int i = 0; i < l.count(); i++ ) + v += l[i].toInt( 0, 16 ) << ( (3-i) * 8 ); + return v; +} + +bool KfmModel::evalVersion( NifItem * item, bool chkParents ) const +{ + if ( item == root ) + return true; + + if ( chkParents && item->parent() ) + if ( ! evalVersion( item->parent(), true ) ) + return false; + + return item->evalVersion( version ); +} + +void KfmModel::clear() +{ + folder = QString(); + root->killChildren(); + insertType( root, NifData( "Kfm", "Kfm" ) ); + kfmroot = root->child( 0 ); + version = 0x0200000b; + reset(); + if ( kfmroot ) + set( kfmroot, "Header String", ";Gamebryo KFM File Version 2.0.0.0b" ); +} + +/* + * array functions + */ + +static QString parentPrefix( const QString & x ) +{ + for ( int c = 0; c < x.length(); c++ ) + if ( ! x[c].isNumber() ) + return QString( "../" ) + x; + return x; +} + +bool KfmModel::updateArrayItem( NifItem * array, bool fast ) +{ + if ( array->arr1().isEmpty() ) + return false; + + int d1 = getArraySize( array ); + if ( d1 > 1024 * 1024 * 8 ) + { + msg( Message() << "array" << array->name() << "much too large" ); + return false; + } + + int rows = array->childCount(); + if ( d1 > rows ) + { + NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::type( array->type() ) ), parentPrefix( array->arg() ), parentPrefix( array->arr2() ), QString(), QString(), 0, 0 ); + + if ( ! fast ) beginInsertRows( createIndex( array->row(), 0, array ), rows, d1-1 ); + array->prepareInsert( d1 - rows ); + for ( int c = rows; c < d1; c++ ) + insertType( array, data ); + if ( ! fast ) endInsertRows(); + } + if ( d1 < rows ) + { + if ( ! fast ) beginRemoveRows( createIndex( array->row(), 0, array ), d1, rows - 1 ); + array->removeChildren( d1, rows - d1 ); + if ( ! fast ) endRemoveRows(); + } + return true; +} + +/* + * basic and compound type functions + */ + +void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) +{ + if ( ! data.arr1().isEmpty() ) + { + NifItem * array = insertBranch( parent, data, at ); + + if ( evalCondition( array ) ) + updateArrayItem( array, true ); + return; + } + + NifBlock * compound = compounds.value( data.type() ); + if ( compound ) + { + NifItem * branch = insertBranch( parent, data, at ); + branch->prepareInsert( compound->types.count() ); + if ( ! data.arg().isEmpty() || ! data.temp().isEmpty() ) + { + QString arg = parentPrefix( data.arg() ); + QString tmp = data.temp(); + if ( tmp == "TEMPLATE" ) + { + NifItem * tItem = branch; + while ( tmp == "TEMPLATE" && tItem->parent() ) + { + tItem = tItem->parent(); + tmp = tItem->temp(); + } + } + foreach ( NifData d, compound->types ) + { + if ( d.type() == "TEMPLATE" ) + { + d.setType( tmp ); + d.value.changeType( NifValue::type( tmp ) ); + } + if ( d.arg() == "ARG" ) d.setArg( data.arg() ); + if ( d.arr1() == "ARG" ) d.setArr1( arg ); + if ( d.arr2() == "ARG" ) d.setArr2( arg ); + if ( d.cond().contains( "ARG" ) ) { QString x = d.cond(); x.replace( x.indexOf( "ARG" ), 5, arg ); d.setCond( x ); } + insertType( branch, d ); + } + } + else + foreach ( NifData d, compound->types ) + insertType( branch, d ); + } + else + parent->insertChild( data, at ); +} + + +/* + * item value functions + */ + +bool KfmModel::setItemValue( NifItem * item, const NifValue & val ) +{ + item->value() = val; + emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); + return true; +} + +/* + * load and save + */ + +bool KfmModel::setHeaderString( const QString & s ) +{ + //msg( DbgMsg() << s << s.right( s.length() - 27 ) ); + if ( s.startsWith( ";Gamebryo KFM File Version " ) ) + { + version = version2number( s.right( s.length() - 27 ) ); + if ( isVersionSupported( version ) ) + { + return true; + } + else + { + msg( Message() << "version" << version2string( version ) << "not supported yet" ); + return false; + } + } + else + { + msg( Message() << "this is not a KFM" ); + return false; + } +} + +bool KfmModel::load( QIODevice & device ) +{ + clear(); + + NifIStream stream( this, &device ); + + if ( !kfmroot || !load( kfmroot, stream, true ) ) + { + msg( Message() << "failed to load kfm file (version" << version << ")" ); + return false; + } + + reset(); + return true; +} + +bool KfmModel::save( QIODevice & device ) const +{ + NifOStream stream( this, &device ); + + if ( ! kfmroot || save( kfmroot, stream ) ) + { + msg( Message() << "failed to write kfm file" ); + return false; + } + return true; +} + +bool KfmModel::load( NifItem * parent, NifIStream & stream, bool fast ) +{ + if ( ! parent ) return false; + + for ( int row = 0; row < parent->childCount(); row++ ) + { + NifItem * child = parent->child( row ); + + if ( evalCondition( child ) ) + { + if ( ! child->arr1().isEmpty() ) + { + if ( ! updateArrayItem( child, fast ) ) + return false; + + if ( ! load( child, stream, fast ) ) + return false; + } + else if ( child->childCount() > 0 ) + { + if ( ! load( child, stream, fast ) ) + return false; + } + else + { + if ( ! stream.read( child->value() ) ) + return false; + } + } + } + return true; +} + +bool KfmModel::save( NifItem * parent, NifOStream & stream ) const +{ + if ( ! parent ) return false; + + for ( int row = 0; row < parent->childCount(); row++ ) + { + NifItem * child = parent->child( row ); + if ( evalCondition( child ) ) + { + if ( ! child->arr1().isEmpty() || ! child->arr2().isEmpty() || child->childCount() > 0 ) + { + if ( ! child->arr1().isEmpty() && child->childCount() != getArraySize( child ) ) + msg( Message() << child->name() << "array size mismatch" ); + + if ( !save( child, stream ) ) + return false; + } + else + { + if ( ! stream.write( child->value() ) ) + return false; + } + } + } + return true; +} + +NifItem * KfmModel::insertBranch( NifItem * parentItem, const NifData & data, int at ) +{ + NifItem * item = parentItem->insertChild( data, at ); + item->value().changeType( NifValue::tNone ); + return item; +} diff --git a/kfmmodel.h b/kfmmodel.h index 0e8c3b051..905d23535 100644 --- a/kfmmodel.h +++ b/kfmmodel.h @@ -1,125 +1,125 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef KFMMODEL_H -#define KFMMODEL_H - -#include "basemodel.h" - -#include -#include -#include - -class KfmModel : public BaseModel -{ -Q_OBJECT -public: - KfmModel( QObject * parent = 0 ); - - // call this once on startup to load the XML descriptions - static bool loadXML(); - - // when creating kfmmodels from outside the main thread better protect them with a QReadLocker - static QReadWriteLock XMLlock; - - // clear model data - void clear(); - - // generic load and save to and from QIODevice - bool load( QIODevice & device ); - bool save( QIODevice & device ) const; - - // is it a compound type? - static bool isCompound( const QString & name ); - - QModelIndex getKFMroot() const; - - // is this version supported ? - static bool isVersionSupported( quint32 ); - - // version conversion - static QString version2string( quint32 ); - static quint32 version2number( const QString & ); - - // check wether the current nif file version lies in the range since~until - bool checkVersion( quint32 since, quint32 until ) const; - - QString getVersion() const { return version2string( version ); } - quint32 getVersionNumber() const { return version; } - - static QAbstractItemDelegate * createDelegate(); - -protected: - void insertType( NifItem * parent, const NifData & data, int row = -1 ); - NifItem * insertBranch( NifItem * parent, const NifData & data, int row = -1 ); - - bool updateArrayItem( NifItem * array, bool fast ); - - bool load( NifItem * parent, NifIStream & stream, bool fast = true ); - bool save( NifItem * parent, NifOStream & stream ) const; - - bool setItemValue( NifItem * item, const NifValue & v ); - - bool setHeaderString( const QString & ); - - bool evalVersion( NifItem * item, bool chkParents = false ) const; - - QString ver2str( quint32 v ) const { return version2string( v ); } - quint32 str2ver( QString s ) const { return version2number( s ); } - - // kfm file version - quint32 version; - - NifItem * kfmroot; - - // XML structures - static QList supportedVersions; - - static QHash compounds; - - static QString parseXmlDescription( const QString & filename ); - - friend class KfmXmlHandler; -}; // class NifModel - - -inline bool KfmModel::isCompound( const QString & name ) -{ - return compounds.contains( name ); -} - -inline bool KfmModel::isVersionSupported( quint32 v ) -{ - return supportedVersions.contains( v ); -} - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef KFMMODEL_H +#define KFMMODEL_H + +#include "basemodel.h" + +#include +#include +#include + +class KfmModel : public BaseModel +{ +Q_OBJECT +public: + KfmModel( QObject * parent = 0 ); + + // call this once on startup to load the XML descriptions + static bool loadXML(); + + // when creating kfmmodels from outside the main thread better protect them with a QReadLocker + static QReadWriteLock XMLlock; + + // clear model data + void clear(); + + // generic load and save to and from QIODevice + bool load( QIODevice & device ); + bool save( QIODevice & device ) const; + + // is it a compound type? + static bool isCompound( const QString & name ); + + QModelIndex getKFMroot() const; + + // is this version supported ? + static bool isVersionSupported( quint32 ); + + // version conversion + static QString version2string( quint32 ); + static quint32 version2number( const QString & ); + + // check wether the current nif file version lies in the range since~until + bool checkVersion( quint32 since, quint32 until ) const; + + QString getVersion() const { return version2string( version ); } + quint32 getVersionNumber() const { return version; } + + static QAbstractItemDelegate * createDelegate(); + +protected: + void insertType( NifItem * parent, const NifData & data, int row = -1 ); + NifItem * insertBranch( NifItem * parent, const NifData & data, int row = -1 ); + + bool updateArrayItem( NifItem * array, bool fast ); + + bool load( NifItem * parent, NifIStream & stream, bool fast = true ); + bool save( NifItem * parent, NifOStream & stream ) const; + + bool setItemValue( NifItem * item, const NifValue & v ); + + bool setHeaderString( const QString & ); + + bool evalVersion( NifItem * item, bool chkParents = false ) const; + + QString ver2str( quint32 v ) const { return version2string( v ); } + quint32 str2ver( QString s ) const { return version2number( s ); } + + // kfm file version + quint32 version; + + NifItem * kfmroot; + + // XML structures + static QList supportedVersions; + + static QHash compounds; + + static QString parseXmlDescription( const QString & filename ); + + friend class KfmXmlHandler; +}; // class NifModel + + +inline bool KfmModel::isCompound( const QString & name ) +{ + return compounds.contains( name ); +} + +inline bool KfmModel::isVersionSupported( quint32 v ) +{ + return supportedVersions.contains( v ); +} + +#endif diff --git a/kfmxml.cpp b/kfmxml.cpp index 1b2e58070..880515b22 100644 --- a/kfmxml.cpp +++ b/kfmxml.cpp @@ -1,263 +1,263 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "kfmmodel.h" - -#include -#include -#include - -#define err( X ) { errorStr = X; return false; } - -QReadWriteLock KfmModel::XMLlock; - -QList KfmModel::supportedVersions; - -QHash KfmModel::compounds; - -class KfmXmlHandler : public QXmlDefaultHandler -{ -public: - KfmXmlHandler() - { - depth = 0; - elements << "niftoolsxml" << "version" << "compound" << "add"; - blk = 0; - } - - int depth; - int stack[10]; - QStringList elements; - QString errorStr; - - NifBlock * blk; - - int current() const - { - return stack[depth-1]; - } - void push( int x ) - { - stack[depth++] = x; - } - int pop() - { - return stack[--depth]; - } - - bool startElement( const QString &, const QString &, const QString & name, const QXmlAttributes & list ) - { - if ( depth >= 8 ) err( "error maximum nesting level exceeded" ); - - int x = elements.indexOf( name ); - if ( x < 0 ) err( "error unknown element '" + name + "'" ); - - if ( depth == 0 ) - { - if ( x != 0 ) err( "this is not a niftoolsxml file" ); - push( x ); - return true; - } - - int v; - switch ( current() ) - { - case 0: - if ( ! ( x == 1 || x == 2 ) ) err( "expected compound or version got " + name + " instead" ); - push( x ); - switch ( x ) - { - case 1: - v = KfmModel::version2number( list.value( "num" ).trimmed() ); - if ( v != 0 && ! list.value( "num" ).isEmpty() ) - KfmModel::supportedVersions.append( v ); - else - err( "invalid version string" ); - break; - case 2: - if ( x == 2 && NifValue::isValid( NifValue::type( list.value( "name" ) ) ) ) - err( "compound " + list.value( "name" ) + " is already registered as internal type" ); - if ( ! blk ) blk = new NifBlock; - blk->id = list.value( "name" ); - break; - } - break; - case 1: - err( "version tag must not contain any sub tags" ); - break; - case 2: - if ( x == 3 ) - { - NifData data( - list.value( "name" ), - list.value( "type" ), - list.value( "template" ), - NifValue( NifValue::type( list.value( "type" ) ) ), - list.value( "arg" ), - list.value( "arr1" ), - list.value( "arr2" ), - list.value( "cond" ), - KfmModel::version2number( list.value( "ver1" ) ), - KfmModel::version2number( list.value( "ver2" ) ) - ); - if ( data.name().isEmpty() || data.type().isEmpty() ) err( "add needs at least name and type attributes" ); - if ( blk ) blk->types.append( data ); - } - else - err( "only add tags allowed in compound type declaration" ); - push( x ); - break; - default: - err( "error unhandled tag " + name + " in " + elements.value( current() ) ); - break; - } - return true; - } - - bool endElement( const QString &, const QString &, const QString & name ) - { - if ( depth <= 0 ) err( "mismatching end element tag for element " + name ); - int x = elements.indexOf( name ); - if ( pop() != x ) err( "mismatching end element tag for element " + elements.value( current() ) ); - switch ( x ) - { - case 2: - if ( blk ) - { - if ( ! blk->id.isEmpty() ) - { - switch ( x ) - { - case 2: KfmModel::compounds.insert( blk->id, blk ); break; - } - blk = 0; - } - else - { - delete blk; - blk = 0; - err( "invalid " + elements.value( x ) + " declaration: name is empty" ); - } - } - break; - } - return true; - } - - bool checkType( const NifData & data ) - { - return KfmModel::compounds.contains( data.type() ) || NifValue::type( data.type() ) != NifValue::tNone || data.type() == "TEMPLATE"; - } - - bool checkTemp( const NifData & data ) - { - return data.temp().isEmpty() || NifValue::type( data.temp() ) != NifValue::tNone || data.temp() == "TEMPLATE"; - } - - bool endDocument() - { // make a rough check of the maps - foreach ( QString key, KfmModel::compounds.keys() ) - { - NifBlock * c = KfmModel::compounds.value( key ); - foreach ( NifData data, c->types ) - { - if ( ! checkType( data ) ) - err( "compound type " + key + " referes to unknown type " + data.type() ); - if ( ! checkTemp( data ) ) - err( "compound type " + key + " referes to unknown template type " + data.temp() ); - if ( data.type() == key ) - err( "compound type " + key + " contains itself" ); - } - } - return true; - } - - QString errorString() const - { - return errorStr; - } - bool fatalError( const QXmlParseException & exception ) - { - if ( errorStr.isEmpty() ) errorStr = "Syntax error"; - errorStr.prepend( QString( "XML parse error (line %1):
" ).arg( exception.lineNumber() ) ); - return false; - } -}; - -bool KfmModel::loadXML() -{ - QDir dir( QApplication::applicationDirPath() ); - QString fname; - if ( dir.exists( "../docsys/kfm.xml" ) ) - fname = dir.filePath( "../docsys/kfm.xml" ); - else if ( dir.exists( "../../docsys/kfm.xml" ) ) - fname = dir.filePath( "../../docsys/kfm.xml" ); - else if ( dir.exists( "/usr/share/nifskope/kfm.xml" ) ) - fname = dir.filePath( "/usr/share/nifskope/kfm.xml" ); - else - fname = dir.filePath( "kfm.xml" ); - QString result = KfmModel::parseXmlDescription( fname ); - if ( ! result.isEmpty() ) - { - QMessageBox::critical( 0, "NifSkope", result ); - return false; - } - return true; -} - -QString KfmModel::parseXmlDescription( const QString & filename ) -{ - QWriteLocker lck( &XMLlock ); - - qDeleteAll( compounds ); compounds.clear(); - supportedVersions.clear(); - - QFile f( filename ); - if ( ! f.open( QIODevice::ReadOnly | QIODevice::Text ) ) - return QString( "error: couldn't open xml description file: " + filename ); - - KfmXmlHandler handler; - QXmlSimpleReader reader; - reader.setContentHandler( &handler ); - reader.setErrorHandler( &handler ); - QXmlInputSource source( &f ); - reader.parse( source ); - - if ( ! handler.errorString().isEmpty() ) - { - qDeleteAll( compounds ); compounds.clear(); - supportedVersions.clear(); - } - - return handler.errorString(); -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "kfmmodel.h" + +#include +#include +#include + +#define err( X ) { errorStr = X; return false; } + +QReadWriteLock KfmModel::XMLlock; + +QList KfmModel::supportedVersions; + +QHash KfmModel::compounds; + +class KfmXmlHandler : public QXmlDefaultHandler +{ +public: + KfmXmlHandler() + { + depth = 0; + elements << "niftoolsxml" << "version" << "compound" << "add"; + blk = 0; + } + + int depth; + int stack[10]; + QStringList elements; + QString errorStr; + + NifBlock * blk; + + int current() const + { + return stack[depth-1]; + } + void push( int x ) + { + stack[depth++] = x; + } + int pop() + { + return stack[--depth]; + } + + bool startElement( const QString &, const QString &, const QString & name, const QXmlAttributes & list ) + { + if ( depth >= 8 ) err( "error maximum nesting level exceeded" ); + + int x = elements.indexOf( name ); + if ( x < 0 ) err( "error unknown element '" + name + "'" ); + + if ( depth == 0 ) + { + if ( x != 0 ) err( "this is not a niftoolsxml file" ); + push( x ); + return true; + } + + int v; + switch ( current() ) + { + case 0: + if ( ! ( x == 1 || x == 2 ) ) err( "expected compound or version got " + name + " instead" ); + push( x ); + switch ( x ) + { + case 1: + v = KfmModel::version2number( list.value( "num" ).trimmed() ); + if ( v != 0 && ! list.value( "num" ).isEmpty() ) + KfmModel::supportedVersions.append( v ); + else + err( "invalid version string" ); + break; + case 2: + if ( x == 2 && NifValue::isValid( NifValue::type( list.value( "name" ) ) ) ) + err( "compound " + list.value( "name" ) + " is already registered as internal type" ); + if ( ! blk ) blk = new NifBlock; + blk->id = list.value( "name" ); + break; + } + break; + case 1: + err( "version tag must not contain any sub tags" ); + break; + case 2: + if ( x == 3 ) + { + NifData data( + list.value( "name" ), + list.value( "type" ), + list.value( "template" ), + NifValue( NifValue::type( list.value( "type" ) ) ), + list.value( "arg" ), + list.value( "arr1" ), + list.value( "arr2" ), + list.value( "cond" ), + KfmModel::version2number( list.value( "ver1" ) ), + KfmModel::version2number( list.value( "ver2" ) ) + ); + if ( data.name().isEmpty() || data.type().isEmpty() ) err( "add needs at least name and type attributes" ); + if ( blk ) blk->types.append( data ); + } + else + err( "only add tags allowed in compound type declaration" ); + push( x ); + break; + default: + err( "error unhandled tag " + name + " in " + elements.value( current() ) ); + break; + } + return true; + } + + bool endElement( const QString &, const QString &, const QString & name ) + { + if ( depth <= 0 ) err( "mismatching end element tag for element " + name ); + int x = elements.indexOf( name ); + if ( pop() != x ) err( "mismatching end element tag for element " + elements.value( current() ) ); + switch ( x ) + { + case 2: + if ( blk ) + { + if ( ! blk->id.isEmpty() ) + { + switch ( x ) + { + case 2: KfmModel::compounds.insert( blk->id, blk ); break; + } + blk = 0; + } + else + { + delete blk; + blk = 0; + err( "invalid " + elements.value( x ) + " declaration: name is empty" ); + } + } + break; + } + return true; + } + + bool checkType( const NifData & data ) + { + return KfmModel::compounds.contains( data.type() ) || NifValue::type( data.type() ) != NifValue::tNone || data.type() == "TEMPLATE"; + } + + bool checkTemp( const NifData & data ) + { + return data.temp().isEmpty() || NifValue::type( data.temp() ) != NifValue::tNone || data.temp() == "TEMPLATE"; + } + + bool endDocument() + { // make a rough check of the maps + foreach ( QString key, KfmModel::compounds.keys() ) + { + NifBlock * c = KfmModel::compounds.value( key ); + foreach ( NifData data, c->types ) + { + if ( ! checkType( data ) ) + err( "compound type " + key + " referes to unknown type " + data.type() ); + if ( ! checkTemp( data ) ) + err( "compound type " + key + " referes to unknown template type " + data.temp() ); + if ( data.type() == key ) + err( "compound type " + key + " contains itself" ); + } + } + return true; + } + + QString errorString() const + { + return errorStr; + } + bool fatalError( const QXmlParseException & exception ) + { + if ( errorStr.isEmpty() ) errorStr = "Syntax error"; + errorStr.prepend( QString( "XML parse error (line %1):
" ).arg( exception.lineNumber() ) ); + return false; + } +}; + +bool KfmModel::loadXML() +{ + QDir dir( QApplication::applicationDirPath() ); + QString fname; + if ( dir.exists( "../docsys/kfm.xml" ) ) + fname = dir.filePath( "../docsys/kfm.xml" ); + else if ( dir.exists( "../../docsys/kfm.xml" ) ) + fname = dir.filePath( "../../docsys/kfm.xml" ); + else if ( dir.exists( "/usr/share/nifskope/kfm.xml" ) ) + fname = dir.filePath( "/usr/share/nifskope/kfm.xml" ); + else + fname = dir.filePath( "kfm.xml" ); + QString result = KfmModel::parseXmlDescription( fname ); + if ( ! result.isEmpty() ) + { + QMessageBox::critical( 0, "NifSkope", result ); + return false; + } + return true; +} + +QString KfmModel::parseXmlDescription( const QString & filename ) +{ + QWriteLocker lck( &XMLlock ); + + qDeleteAll( compounds ); compounds.clear(); + supportedVersions.clear(); + + QFile f( filename ); + if ( ! f.open( QIODevice::ReadOnly | QIODevice::Text ) ) + return QString( "error: couldn't open xml description file: " + filename ); + + KfmXmlHandler handler; + QXmlSimpleReader reader; + reader.setContentHandler( &handler ); + reader.setErrorHandler( &handler ); + QXmlInputSource source( &f ); + reader.parse( source ); + + if ( ! handler.errorString().isEmpty() ) + { + qDeleteAll( compounds ); compounds.clear(); + supportedVersions.clear(); + } + + return handler.errorString(); +} + diff --git a/message.cpp b/message.cpp index 22dd48b3f..6d5beb500 100644 --- a/message.cpp +++ b/message.cpp @@ -1,56 +1,56 @@ -#include "message.h" - -inline void space( QString & s ) -{ - if ( ! s.isEmpty() ) - s += " "; -} - -template <> Message & Message::operator<<( const char * x ) -{ - space( s ); - s += x; - return *this; -} - -template <> Message & Message::operator<<( QString x ) -{ - space( s ); - s += "\"" + x + "\""; - return *this; -} - -template <> Message & Message::operator<<( QByteArray x ) -{ - space( s ); - s += "\"" + x + "\""; - return *this; -} - -template <> Message & Message::operator<<( int x ) -{ - space( s ); - s += QString::number( x ); - return *this; -} - -template <> Message & Message::operator<<( unsigned int x ) -{ - space( s ); - s += QString::number( x ); - return *this; -} - -template <> Message & Message::operator<<( double x ) -{ - space( s ); - s += QString::number( x ); - return *this; -} - -template <> Message & Message::operator<<( float x ) -{ - space( s ); - s += QString::number( x ); - return *this; -} +#include "message.h" + +inline void space( QString & s ) +{ + if ( ! s.isEmpty() ) + s += " "; +} + +template <> Message & Message::operator<<( const char * x ) +{ + space( s ); + s += x; + return *this; +} + +template <> Message & Message::operator<<( QString x ) +{ + space( s ); + s += "\"" + x + "\""; + return *this; +} + +template <> Message & Message::operator<<( QByteArray x ) +{ + space( s ); + s += "\"" + x + "\""; + return *this; +} + +template <> Message & Message::operator<<( int x ) +{ + space( s ); + s += QString::number( x ); + return *this; +} + +template <> Message & Message::operator<<( unsigned int x ) +{ + space( s ); + s += QString::number( x ); + return *this; +} + +template <> Message & Message::operator<<( double x ) +{ + space( s ); + s += QString::number( x ); + return *this; +} + +template <> Message & Message::operator<<( float x ) +{ + space( s ); + s += QString::number( x ); + return *this; +} diff --git a/message.h b/message.h index 08df58db5..c4dd703e4 100644 --- a/message.h +++ b/message.h @@ -1,25 +1,25 @@ -#ifndef MESSAGE_H -#define MESSAGE_H - -#include -#include - -class Message -{ -public: - Message( QtMsgType t = QtWarningMsg ) : typ( t ) {} - - template Message & operator<<( T ); - - operator QString () const { return s; } - - QtMsgType type() const { return typ; } - -protected: - QString s; - QtMsgType typ; -}; - -#define DbgMsg() Message( QtDebugMsg ) - -#endif +#ifndef MESSAGE_H +#define MESSAGE_H + +#include +#include + +class Message +{ +public: + Message( QtMsgType t = QtWarningMsg ) : typ( t ) {} + + template Message & operator<<( T ); + + operator QString () const { return s; } + + QtMsgType type() const { return typ; } + +protected: + QString s; + QtMsgType typ; +}; + +#define DbgMsg() Message( QtDebugMsg ) + +#endif diff --git a/nifdelegate.cpp b/nifdelegate.cpp index 26ecb39c5..8d782dc47 100644 --- a/nifdelegate.cpp +++ b/nifdelegate.cpp @@ -1,295 +1,295 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "nifmodel.h" -#include "nifproxy.h" -#include "kfmmodel.h" - -#include - -#include "spellbook.h" - -#include "widgets/valueedit.h" - -#include -#include -#include -#include - -extern void qt_format_text(const QFont& font, const QRectF &_r, - int tf, const QString& str, QRectF *brect, - int tabstops, int* tabarray, int tabarraylen, - QPainter* painter); - -class NifDelegate : public QItemDelegate -{ - SpellBook * book; -public: - NifDelegate( SpellBook * sb = 0) : QItemDelegate(), book( sb ) {} - - virtual bool editorEvent( QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index ) - { - Q_ASSERT( event ); - Q_ASSERT( model ); - - switch ( event->type() ) - { - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - if ( static_cast(event)->button() == Qt::LeftButton - && decoRect( option ).contains( static_cast(event)->pos() ) ) - { - Spell * spell = SpellBook::lookup( model->data( index, Qt::UserRole ).toString() ); - if ( spell && ! spell->icon().isNull() ) - { - if ( event->type() == QEvent::MouseButtonRelease ) - { - NifModel * nif = 0; - QModelIndex buddy = index; - - if ( model->inherits( "NifModel" ) ) - { - nif = static_cast( model ); - } - else if ( model->inherits( "NifProxyModel" ) ) - { - NifProxyModel * proxy = static_cast( model ); - nif = static_cast( proxy->model() ); - buddy = proxy->mapTo( index ); - } - - if ( nif && spell->isApplicable( nif, buddy ) ) - { - if ( book ) - book->cast( nif, buddy, spell ); - else - spell->cast( nif, buddy ); - } - } - return true; - } - } break; - case QEvent::MouseButtonDblClick: - if ( static_cast(event)->button() == Qt::LeftButton ) - { - QVariant v = model->data( index, Qt::EditRole ); - if ( v.canConvert() ) - { - NifValue nv = v.value(); - if ( nv.type() == NifValue::tBool ) - { - nv.set( ! nv.get() ); - model->setData( index, nv.toVariant(), Qt::EditRole ); - return true; - } - } - } break; - default: - break; - } - return false; - } - - virtual void paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const - { - QString text = index.data( Qt::DisplayRole ).toString(); - QString deco = index.data( Qt::DecorationRole ).toString(); - - QString user = index.data( Qt::UserRole ).toString(); - QIcon icon; - if ( ! user.isEmpty() ) - { - Spell * spell = SpellBook::lookup( user ); - if ( spell ) icon = spell->icon(); - } - - QStyleOptionViewItem opt = option; - - QRect tRect = opt.rect; - QRect dRect; - - if ( ! icon.isNull() || ! deco.isEmpty() ) - { - dRect = decoRect( opt ); - tRect = textRect( opt ); - } - - opt.state |= QStyle::State_Active; - QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; - - QVariant color = index.data( Qt::BackgroundColorRole ); - if ( color.canConvert() ) - painter->fillRect( option.rect, color.value() ); - else if ( option.state & QStyle::State_Selected ) - painter->fillRect( option.rect, option.palette.brush( cg, QPalette::Highlight ) ); - - painter->save(); - painter->setPen( opt.palette.color( cg, opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text ) ); - painter->setFont( opt.font ); - - if ( ! icon.isNull() ) - icon.paint( painter, dRect ); - else if ( ! deco.isEmpty() ) - painter->drawText( dRect, opt.decorationAlignment, deco ); - - if ( ! text.isEmpty() ) - { - drawDisplay( painter, opt, tRect, text ); - drawFocus( painter, opt, tRect ); - } - - painter->restore(); - } - - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const - { - QString text = index.data( Qt::DisplayRole ).toString(); - QRect textRect( 0, 0, option.fontMetrics.width(text), option.fontMetrics.lineSpacing() * (text.count(QLatin1Char('\n')) + 1) ); - return textRect.size(); - } - - QWidget * createEditor( QWidget * parent, const QStyleOptionViewItem &, const QModelIndex & index ) const - { - if ( ! index.isValid() ) - return 0; - - QVariant v = index.data( Qt::EditRole ); - QWidget * w = 0; - - if ( v.canConvert() ) - { - NifValue nv = v.value(); - if ( nv.isCount() && index.column() == NifModel::ValueCol && ! NifValue::enumOptions( index.sibling( index.row(), NifModel::TypeCol ).data( Qt::DisplayRole ).toString() ).isEmpty() ) - { - QComboBox * c = new QComboBox( parent ); - w = c; - c->setEditable( true ); - } - else if ( ValueEdit::canEdit( nv.type() ) ) - w = new ValueEdit( parent ); - } - else if ( v.type() == QVariant::String ) - { - QLineEdit *le = new QLineEdit(parent); - le->setFrame(false); - w = le; - } - if ( w ) w->installEventFilter( const_cast( this ) ); - return w; - } - - void setEditorData(QWidget *editor, const QModelIndex &index) const - { - ValueEdit * vedit = qobject_cast( editor ); - QComboBox * cedit = qobject_cast( editor ); - QLineEdit * ledit = qobject_cast( editor ); - QVariant v = index.data( Qt::EditRole ); - - if ( vedit && v.canConvert() ) - { - vedit->setValue( v.value() ); - } - else if ( cedit && v.canConvert() && v.value().isCount() ) - { - cedit->clear(); - QString t = index.sibling( index.row(), NifModel::TypeCol ).data( Qt::DisplayRole ).toString(); - QStringList o = NifValue::enumOptions( t ); - cedit->addItems( o ); - QString x = NifValue::enumOptionName( t, v.value().toCount() ); - if ( ! x.isEmpty() ) - cedit->setCurrentIndex( o.indexOf( x ) ); - else - cedit->setEditText( QString::number( v.value().toCount() ) ); - } - else if ( ledit ) - { - ledit->setText( v.toString() ); - } - } - - void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const - { - Q_ASSERT(model); - ValueEdit * vedit = qobject_cast( editor ); - QComboBox * cedit = qobject_cast( editor ); - QLineEdit * ledit = qobject_cast( editor ); - QVariant v; - if ( vedit ) - { - v.setValue( vedit->getValue() ); - model->setData( index, v, Qt::EditRole ); - } - else if ( cedit ) - { - QString t = index.sibling( index.row(), NifModel::TypeCol ).data( Qt::DisplayRole ).toString(); - QVariant v = index.data( Qt::EditRole ); - bool ok; - quint32 x = NifValue::enumOptionValue( t, cedit->currentText(), &ok ); - if ( ! ok ) - x = cedit->currentText().toUInt(); - if ( v.canConvert() ) - { - NifValue nv = v.value(); - nv.setCount( x ); - v.setValue( nv ); - model->setData( index, v, Qt::EditRole ); - } - } - else if ( ledit ) - { - v.setValue( ledit->text() ); - model->setData( index, v, Qt::EditRole ); - } - } - - QRect decoRect( const QStyleOptionViewItem & opt ) const - { - // allways upper left - return QRect( opt.rect.topLeft(), opt.decorationSize ); - } - - QRect textRect( const QStyleOptionViewItem & opt ) const - { - return QRect( QPoint( opt.rect.x() + opt.decorationSize.width(), opt.rect.y() ), QSize( opt.rect.width() - opt.decorationSize.width(), opt.rect.height() ) ); - } - -}; - -QAbstractItemDelegate * NifModel::createDelegate( SpellBook * book ) -{ - return new NifDelegate( book ); -} - -QAbstractItemDelegate * KfmModel::createDelegate() -{ - return new NifDelegate; -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "nifmodel.h" +#include "nifproxy.h" +#include "kfmmodel.h" + +#include + +#include "spellbook.h" + +#include "widgets/valueedit.h" + +#include +#include +#include +#include + +extern void qt_format_text(const QFont& font, const QRectF &_r, + int tf, const QString& str, QRectF *brect, + int tabstops, int* tabarray, int tabarraylen, + QPainter* painter); + +class NifDelegate : public QItemDelegate +{ + SpellBook * book; +public: + NifDelegate( SpellBook * sb = 0) : QItemDelegate(), book( sb ) {} + + virtual bool editorEvent( QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index ) + { + Q_ASSERT( event ); + Q_ASSERT( model ); + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + if ( static_cast(event)->button() == Qt::LeftButton + && decoRect( option ).contains( static_cast(event)->pos() ) ) + { + Spell * spell = SpellBook::lookup( model->data( index, Qt::UserRole ).toString() ); + if ( spell && ! spell->icon().isNull() ) + { + if ( event->type() == QEvent::MouseButtonRelease ) + { + NifModel * nif = 0; + QModelIndex buddy = index; + + if ( model->inherits( "NifModel" ) ) + { + nif = static_cast( model ); + } + else if ( model->inherits( "NifProxyModel" ) ) + { + NifProxyModel * proxy = static_cast( model ); + nif = static_cast( proxy->model() ); + buddy = proxy->mapTo( index ); + } + + if ( nif && spell->isApplicable( nif, buddy ) ) + { + if ( book ) + book->cast( nif, buddy, spell ); + else + spell->cast( nif, buddy ); + } + } + return true; + } + } break; + case QEvent::MouseButtonDblClick: + if ( static_cast(event)->button() == Qt::LeftButton ) + { + QVariant v = model->data( index, Qt::EditRole ); + if ( v.canConvert() ) + { + NifValue nv = v.value(); + if ( nv.type() == NifValue::tBool ) + { + nv.set( ! nv.get() ); + model->setData( index, nv.toVariant(), Qt::EditRole ); + return true; + } + } + } break; + default: + break; + } + return false; + } + + virtual void paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const + { + QString text = index.data( Qt::DisplayRole ).toString(); + QString deco = index.data( Qt::DecorationRole ).toString(); + + QString user = index.data( Qt::UserRole ).toString(); + QIcon icon; + if ( ! user.isEmpty() ) + { + Spell * spell = SpellBook::lookup( user ); + if ( spell ) icon = spell->icon(); + } + + QStyleOptionViewItem opt = option; + + QRect tRect = opt.rect; + QRect dRect; + + if ( ! icon.isNull() || ! deco.isEmpty() ) + { + dRect = decoRect( opt ); + tRect = textRect( opt ); + } + + opt.state |= QStyle::State_Active; + QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; + + QVariant color = index.data( Qt::BackgroundColorRole ); + if ( color.canConvert() ) + painter->fillRect( option.rect, color.value() ); + else if ( option.state & QStyle::State_Selected ) + painter->fillRect( option.rect, option.palette.brush( cg, QPalette::Highlight ) ); + + painter->save(); + painter->setPen( opt.palette.color( cg, opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text ) ); + painter->setFont( opt.font ); + + if ( ! icon.isNull() ) + icon.paint( painter, dRect ); + else if ( ! deco.isEmpty() ) + painter->drawText( dRect, opt.decorationAlignment, deco ); + + if ( ! text.isEmpty() ) + { + drawDisplay( painter, opt, tRect, text ); + drawFocus( painter, opt, tRect ); + } + + painter->restore(); + } + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QString text = index.data( Qt::DisplayRole ).toString(); + QRect textRect( 0, 0, option.fontMetrics.width(text), option.fontMetrics.lineSpacing() * (text.count(QLatin1Char('\n')) + 1) ); + return textRect.size(); + } + + QWidget * createEditor( QWidget * parent, const QStyleOptionViewItem &, const QModelIndex & index ) const + { + if ( ! index.isValid() ) + return 0; + + QVariant v = index.data( Qt::EditRole ); + QWidget * w = 0; + + if ( v.canConvert() ) + { + NifValue nv = v.value(); + if ( nv.isCount() && index.column() == NifModel::ValueCol && ! NifValue::enumOptions( index.sibling( index.row(), NifModel::TypeCol ).data( Qt::DisplayRole ).toString() ).isEmpty() ) + { + QComboBox * c = new QComboBox( parent ); + w = c; + c->setEditable( true ); + } + else if ( ValueEdit::canEdit( nv.type() ) ) + w = new ValueEdit( parent ); + } + else if ( v.type() == QVariant::String ) + { + QLineEdit *le = new QLineEdit(parent); + le->setFrame(false); + w = le; + } + if ( w ) w->installEventFilter( const_cast( this ) ); + return w; + } + + void setEditorData(QWidget *editor, const QModelIndex &index) const + { + ValueEdit * vedit = qobject_cast( editor ); + QComboBox * cedit = qobject_cast( editor ); + QLineEdit * ledit = qobject_cast( editor ); + QVariant v = index.data( Qt::EditRole ); + + if ( vedit && v.canConvert() ) + { + vedit->setValue( v.value() ); + } + else if ( cedit && v.canConvert() && v.value().isCount() ) + { + cedit->clear(); + QString t = index.sibling( index.row(), NifModel::TypeCol ).data( Qt::DisplayRole ).toString(); + QStringList o = NifValue::enumOptions( t ); + cedit->addItems( o ); + QString x = NifValue::enumOptionName( t, v.value().toCount() ); + if ( ! x.isEmpty() ) + cedit->setCurrentIndex( o.indexOf( x ) ); + else + cedit->setEditText( QString::number( v.value().toCount() ) ); + } + else if ( ledit ) + { + ledit->setText( v.toString() ); + } + } + + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const + { + Q_ASSERT(model); + ValueEdit * vedit = qobject_cast( editor ); + QComboBox * cedit = qobject_cast( editor ); + QLineEdit * ledit = qobject_cast( editor ); + QVariant v; + if ( vedit ) + { + v.setValue( vedit->getValue() ); + model->setData( index, v, Qt::EditRole ); + } + else if ( cedit ) + { + QString t = index.sibling( index.row(), NifModel::TypeCol ).data( Qt::DisplayRole ).toString(); + QVariant v = index.data( Qt::EditRole ); + bool ok; + quint32 x = NifValue::enumOptionValue( t, cedit->currentText(), &ok ); + if ( ! ok ) + x = cedit->currentText().toUInt(); + if ( v.canConvert() ) + { + NifValue nv = v.value(); + nv.setCount( x ); + v.setValue( nv ); + model->setData( index, v, Qt::EditRole ); + } + } + else if ( ledit ) + { + v.setValue( ledit->text() ); + model->setData( index, v, Qt::EditRole ); + } + } + + QRect decoRect( const QStyleOptionViewItem & opt ) const + { + // allways upper left + return QRect( opt.rect.topLeft(), opt.decorationSize ); + } + + QRect textRect( const QStyleOptionViewItem & opt ) const + { + return QRect( QPoint( opt.rect.x() + opt.decorationSize.width(), opt.rect.y() ), QSize( opt.rect.width() - opt.decorationSize.width(), opt.rect.height() ) ); + } + +}; + +QAbstractItemDelegate * NifModel::createDelegate( SpellBook * book ) +{ + return new NifDelegate( book ); +} + +QAbstractItemDelegate * KfmModel::createDelegate() +{ + return new NifDelegate; +} diff --git a/nifitem.h b/nifitem.h index 6886ff5fb..effd7bf92 100644 --- a/nifitem.h +++ b/nifitem.h @@ -1,278 +1,278 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef NIFITEM_H -#define NIFITEM_H - -#include "nifvalue.h" - -#include -#include - -class NifSharedData : public QSharedData -{ - friend class NifData; - - NifSharedData( const QString & n, const QString & t, const QString & tt, const QString & a, const QString & a1, const QString & a2, const QString & c, quint32 v1, quint32 v2 ) - : QSharedData(), name( n ), type( t ), temp( tt ), arg( a ), arr1( a1 ), arr2( a2 ), cond( c ), ver1( v1 ), ver2( v2 ) {} - - NifSharedData( const QString & n, const QString & t ) - : QSharedData(), name( n ), type( t ), ver1( 0 ), ver2( 0 ) {} - - NifSharedData( const QString & n, const QString & t, const QString & txt ) - : QSharedData(), name( n ), type( t ), ver1( 0 ), ver2( 0 ), text( txt ) {} - - NifSharedData() - : QSharedData(), ver1( 0 ), ver2( 0 ) {} - - QString name; - QString type; - QString temp; - QString arg; - QString arr1; - QString arr2; - QString cond; - quint32 ver1; - quint32 ver2; - QString text; -}; - -class NifData -{ -public: - NifData( const QString & name, const QString & type, const QString & temp, const NifValue & val, const QString & arg, const QString & arr1, const QString & arr2, const QString & cond, quint32 ver1, quint32 ver2 ) - : d( new NifSharedData( name, type, temp, arg, arr1, arr2, cond, ver1, ver2 ) ), value( val ) {} - - NifData( const QString & name, const QString & type = QString(), const QString & text = QString() ) - : d( new NifSharedData( name, type, text ) ) {} - - NifData() - : d( new NifSharedData() ) {} - - inline const QString & name() const { return d->name; } - inline const QString & type() const { return d->type; } - inline const QString & temp() const { return d->temp; } - inline const QString & arg() const { return d->arg; } - inline const QString & arr1() const { return d->arr1; } - inline const QString & arr2() const { return d->arr2; } - inline const QString & cond() const { return d->cond; } - inline quint32 ver1() const { return d->ver1; } - inline quint32 ver2() const { return d->ver2; } - inline const QString & text() const { return d->text; } - - void setName( const QString & name ) { d->name = name; } - void setType( const QString & type ) { d->type = type; } - void setTemp( const QString & temp ) { d->temp = temp; } - void setArg( const QString & arg ) { d->arg = arg; } - void setArr1( const QString & arr1 ) { d->arr1 = arr1; } - void setArr2( const QString & arr2 ) { d->arr2 = arr2; } - void setCond( const QString & cond ) { d->cond = cond; } - void setVer1( quint32 ver1 ) { d->ver1 = ver1; } - void setVer2( quint32 ver2 ) { d->ver2 = ver2; } - void setText( const QString & text ) { d->text = text; } - -protected: - QSharedDataPointer d; - -public: - NifValue value; -}; - - -struct NifBlock -{ - QString id; - QString ancestor; - QString text; - bool abstract; - QList types; -}; - - -class NifItem -{ -public: - NifItem( NifItem * parent ) - : parentItem( parent ) {} - - NifItem( const NifData & data, NifItem * parent ) - : itemData( data ), parentItem( parent ) {} - - ~NifItem() - { - qDeleteAll( childItems ); - } - - NifItem * parent() const - { - return parentItem; - } - - int row() const - { - if ( parentItem ) - return parentItem->childItems.indexOf( const_cast(this) ); - return 0; - } - - void prepareInsert( int e ) - { - childItems.reserve( childItems.count() + e ); - } - - NifItem * insertChild( const NifData & data, int at = -1 ) - { - NifItem * item = new NifItem( data, this ); - if ( at < 0 || at > childItems.count() ) - childItems.append( item ); - else - childItems.insert( at, item ); - return item; - } - - int insertChild( NifItem * child, int at = -1 ) - { - child->parentItem = this; - if ( at < 0 || at > childItems.count() ) - childItems.append( child ); - else - childItems.insert( at, child ); - return child->row(); - } - - NifItem * takeChild( int row ) - { - NifItem * item = child( row ); - if ( item ) - { - childItems.remove( row ); - item->parentItem = 0; - } - return item; - } - - void removeChild( int row ) - { - NifItem * item = child( row ); - if ( item ) - { - childItems.remove( row ); - delete item; - } - } - - void removeChildren( int row, int count ) - { - for ( int c = row; c < row + count; c++ ) - { - NifItem * item = childItems.value( c ); - if ( item ) delete item; - } - childItems.remove( row, count ); - } - - NifItem * child( int row ) - { - return childItems.value( row ); - } - - NifItem * child( const QString & name ) - { - foreach ( NifItem * child, childItems ) - if ( child->name() == name ) - return child; - return 0; - } - - int childCount() - { - return childItems.count(); - } - - void killChildren() - { - qDeleteAll( childItems ); - childItems.clear(); - } - - inline const NifValue & value() const { return itemData.value; } - inline NifValue & value() { return itemData.value; } - - inline QString name() const { return itemData.name(); } - inline QString type() const { return itemData.type(); } - inline QString temp() const { return itemData.temp(); } - inline QString arg() const { return itemData.arg(); } - inline QString arr1() const { return itemData.arr1(); } - inline QString arr2() const { return itemData.arr2(); } - inline QString cond() const { return itemData.cond(); } - inline quint32 ver1() const { return itemData.ver1(); } - inline quint32 ver2() const { return itemData.ver2(); } - inline QString text() const { return itemData.text(); } - - inline void setName( const QString & name ) { itemData.setName( name ); } - inline void setType( const QString & type ) { itemData.setType( type ); } - inline void setTemp( const QString & temp ) { itemData.setTemp( temp ); } - inline void setArg( const QString & arg ) { itemData.setArg( arg ); } - inline void setArr1( const QString & arr1 ) { itemData.setArr1( arr1 ); } - inline void setArr2( const QString & arr2 ) { itemData.setArr2( arr2 ); } - inline void setCond( const QString & cond ) { itemData.setCond( cond ); } - inline void setVer1( int v1 ) { itemData.setVer1( v1 ); } - inline void setVer2( int v2 ) { itemData.setVer2( v2 ); } - inline void setText( const QString & text ) { itemData.setText( text ); } - - inline bool evalVersion( quint32 v ) - { - return ( ( ver1() == 0 || ver1() <= v ) && ( ver2() == 0 || v <= ver2() ) ); - } - - template QVector< T > getArray() const - { - QVector array; - foreach ( NifItem * child, childItems ) - array.append( child->itemData.value.get< T >() ); - return array; - } - - template void setArray( const QVector< T > & array ) - { - int x = 0; - foreach ( NifItem * child, childItems ) - child->itemData.value.set< T >( array.value( x++ ) ); - } - -private: - NifData itemData; - NifItem * parentItem; - QVector childItems; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef NIFITEM_H +#define NIFITEM_H + +#include "nifvalue.h" + +#include +#include + +class NifSharedData : public QSharedData +{ + friend class NifData; + + NifSharedData( const QString & n, const QString & t, const QString & tt, const QString & a, const QString & a1, const QString & a2, const QString & c, quint32 v1, quint32 v2 ) + : QSharedData(), name( n ), type( t ), temp( tt ), arg( a ), arr1( a1 ), arr2( a2 ), cond( c ), ver1( v1 ), ver2( v2 ) {} + + NifSharedData( const QString & n, const QString & t ) + : QSharedData(), name( n ), type( t ), ver1( 0 ), ver2( 0 ) {} + + NifSharedData( const QString & n, const QString & t, const QString & txt ) + : QSharedData(), name( n ), type( t ), ver1( 0 ), ver2( 0 ), text( txt ) {} + + NifSharedData() + : QSharedData(), ver1( 0 ), ver2( 0 ) {} + + QString name; + QString type; + QString temp; + QString arg; + QString arr1; + QString arr2; + QString cond; + quint32 ver1; + quint32 ver2; + QString text; +}; + +class NifData +{ +public: + NifData( const QString & name, const QString & type, const QString & temp, const NifValue & val, const QString & arg, const QString & arr1, const QString & arr2, const QString & cond, quint32 ver1, quint32 ver2 ) + : d( new NifSharedData( name, type, temp, arg, arr1, arr2, cond, ver1, ver2 ) ), value( val ) {} + + NifData( const QString & name, const QString & type = QString(), const QString & text = QString() ) + : d( new NifSharedData( name, type, text ) ) {} + + NifData() + : d( new NifSharedData() ) {} + + inline const QString & name() const { return d->name; } + inline const QString & type() const { return d->type; } + inline const QString & temp() const { return d->temp; } + inline const QString & arg() const { return d->arg; } + inline const QString & arr1() const { return d->arr1; } + inline const QString & arr2() const { return d->arr2; } + inline const QString & cond() const { return d->cond; } + inline quint32 ver1() const { return d->ver1; } + inline quint32 ver2() const { return d->ver2; } + inline const QString & text() const { return d->text; } + + void setName( const QString & name ) { d->name = name; } + void setType( const QString & type ) { d->type = type; } + void setTemp( const QString & temp ) { d->temp = temp; } + void setArg( const QString & arg ) { d->arg = arg; } + void setArr1( const QString & arr1 ) { d->arr1 = arr1; } + void setArr2( const QString & arr2 ) { d->arr2 = arr2; } + void setCond( const QString & cond ) { d->cond = cond; } + void setVer1( quint32 ver1 ) { d->ver1 = ver1; } + void setVer2( quint32 ver2 ) { d->ver2 = ver2; } + void setText( const QString & text ) { d->text = text; } + +protected: + QSharedDataPointer d; + +public: + NifValue value; +}; + + +struct NifBlock +{ + QString id; + QString ancestor; + QString text; + bool abstract; + QList types; +}; + + +class NifItem +{ +public: + NifItem( NifItem * parent ) + : parentItem( parent ) {} + + NifItem( const NifData & data, NifItem * parent ) + : itemData( data ), parentItem( parent ) {} + + ~NifItem() + { + qDeleteAll( childItems ); + } + + NifItem * parent() const + { + return parentItem; + } + + int row() const + { + if ( parentItem ) + return parentItem->childItems.indexOf( const_cast(this) ); + return 0; + } + + void prepareInsert( int e ) + { + childItems.reserve( childItems.count() + e ); + } + + NifItem * insertChild( const NifData & data, int at = -1 ) + { + NifItem * item = new NifItem( data, this ); + if ( at < 0 || at > childItems.count() ) + childItems.append( item ); + else + childItems.insert( at, item ); + return item; + } + + int insertChild( NifItem * child, int at = -1 ) + { + child->parentItem = this; + if ( at < 0 || at > childItems.count() ) + childItems.append( child ); + else + childItems.insert( at, child ); + return child->row(); + } + + NifItem * takeChild( int row ) + { + NifItem * item = child( row ); + if ( item ) + { + childItems.remove( row ); + item->parentItem = 0; + } + return item; + } + + void removeChild( int row ) + { + NifItem * item = child( row ); + if ( item ) + { + childItems.remove( row ); + delete item; + } + } + + void removeChildren( int row, int count ) + { + for ( int c = row; c < row + count; c++ ) + { + NifItem * item = childItems.value( c ); + if ( item ) delete item; + } + childItems.remove( row, count ); + } + + NifItem * child( int row ) + { + return childItems.value( row ); + } + + NifItem * child( const QString & name ) + { + foreach ( NifItem * child, childItems ) + if ( child->name() == name ) + return child; + return 0; + } + + int childCount() + { + return childItems.count(); + } + + void killChildren() + { + qDeleteAll( childItems ); + childItems.clear(); + } + + inline const NifValue & value() const { return itemData.value; } + inline NifValue & value() { return itemData.value; } + + inline QString name() const { return itemData.name(); } + inline QString type() const { return itemData.type(); } + inline QString temp() const { return itemData.temp(); } + inline QString arg() const { return itemData.arg(); } + inline QString arr1() const { return itemData.arr1(); } + inline QString arr2() const { return itemData.arr2(); } + inline QString cond() const { return itemData.cond(); } + inline quint32 ver1() const { return itemData.ver1(); } + inline quint32 ver2() const { return itemData.ver2(); } + inline QString text() const { return itemData.text(); } + + inline void setName( const QString & name ) { itemData.setName( name ); } + inline void setType( const QString & type ) { itemData.setType( type ); } + inline void setTemp( const QString & temp ) { itemData.setTemp( temp ); } + inline void setArg( const QString & arg ) { itemData.setArg( arg ); } + inline void setArr1( const QString & arr1 ) { itemData.setArr1( arr1 ); } + inline void setArr2( const QString & arr2 ) { itemData.setArr2( arr2 ); } + inline void setCond( const QString & cond ) { itemData.setCond( cond ); } + inline void setVer1( int v1 ) { itemData.setVer1( v1 ); } + inline void setVer2( int v2 ) { itemData.setVer2( v2 ); } + inline void setText( const QString & text ) { itemData.setText( text ); } + + inline bool evalVersion( quint32 v ) + { + return ( ( ver1() == 0 || ver1() <= v ) && ( ver2() == 0 || v <= ver2() ) ); + } + + template QVector< T > getArray() const + { + QVector array; + foreach ( NifItem * child, childItems ) + array.append( child->itemData.value.get< T >() ); + return array; + } + + template void setArray( const QVector< T > & array ) + { + int x = 0; + foreach ( NifItem * child, childItems ) + child->itemData.value.set< T >( array.value( x++ ) ); + } + +private: + NifData itemData; + NifItem * parentItem; + QVector childItems; +}; + +#endif diff --git a/nifmodel.cpp b/nifmodel.cpp index 76c36e683..510c24ba3 100644 --- a/nifmodel.cpp +++ b/nifmodel.cpp @@ -1,2168 +1,2168 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "nifmodel.h" -#include "niftypes.h" -#include "options.h" - -#include "spellbook.h" - -#include -#include -#include -#include - -NifModel::NifModel( QObject * parent ) : BaseModel( parent ) -{ - clear(); -} - -QString NifModel::version2string( quint32 v ) -{ - if ( v == 0 ) return QString(); - QString s; - if ( v < 0x0303000D ) { - //This is an old-style 2-number version with one period - s = QString::number( ( v >> 24 ) & 0xff, 10 ) + "." - + QString::number( ( v >> 16 ) & 0xff, 10 ); - - quint32 sub_num1 = ((v >> 8) & 0xff); - quint32 sub_num2 = (v & 0xff); - if ( sub_num1 > 0 || sub_num2 > 0 ) { - s = s + QString::number( sub_num1, 10 ); - } - - if ( sub_num2 > 0 ) { - s = s + QString::number( sub_num2, 10 ); - } - } else { - //This is a new-style 4-number version with 3 periods - s = QString::number( ( v >> 24 ) & 0xff, 10 ) + "." - + QString::number( ( v >> 16 ) & 0xff, 10 ) + "." - + QString::number( ( v >> 8 ) & 0xff, 10 ) + "." - + QString::number( v & 0xff, 10 ); - } - return s; -} - -quint32 NifModel::version2number( const QString & s ) -{ - if ( s.isEmpty() ) return 0; - - if ( s.contains( "." ) ) - { - QStringList l = s.split( "." ); - - quint32 v = 0; - - if ( l.count() > 4 ) { - //Should probaby post a warning here or something. Version # has more than 3 dots in it. - return 0; - } else if ( l.count() == 2 ) { - //This is an old style version number. Take each digit following the first one at a time. - //The first one is the major version - v += l[0].toInt() << (3 * 8); - - if ( l[1].size() >= 1 ) { - v += l[1].mid(0, 1).toInt() << (2 * 8); - } - if ( l[1].size() >= 2 ) { - v += l[1].mid(1, 1).toInt() << (1 * 8); - } - if ( l[1].size() >= 3 ) { - v += l[1].mid(2, -1).toInt(); - } - return v; - } else { - //This is a new style version number with dots separating the digits - for ( int i = 0; i < 4 && i < l.count(); i++ ) { - v += l[i].toInt( 0, 10 ) << ( (3-i) * 8 ); - } - return v; - } - - } else { - bool ok; - quint32 i = s.toUInt( &ok ); - return ( i == 0xffffffff ? 0 : i ); - } -} - -bool NifModel::evalVersion( NifItem * item, bool chkParents ) const -{ - if ( item == root ) - return true; - - if ( chkParents && item->parent() ) - if ( ! evalVersion( item->parent(), true ) ) - return false; - - return item->evalVersion( version ); -} - -void NifModel::clear() -{ - folder = QString(); - root->killChildren(); - insertType( root, NifData( "NiHeader", "Header" ) ); - insertType( root, NifData( "NiFooter", "Footer" ) ); - version = version2number( Options::startupVersion() ); - if( !isVersionSupported(version) ) { - msg( Message() << tr("Unsupported 'Startup Version' %1 specified, reverting to 20.0.0.5").arg( Options::startupVersion() ).toAscii() ); - version = 0x14000005; - } - reset(); - NifItem * item = getItem( getHeaderItem(), "Version" ); - if ( item ) item->value().setFileVersion( version ); - - QString header_string; - if ( version <= 0x0A000100 ) { - header_string = "NetImmerse File Format, Version "; - } else { - header_string = "Gamebryo File Format, Version "; - } - - header_string += version2string(version); - - set( getHeaderItem(), "Header String", header_string ); - - if ( version == 0x14000005 ) { - //Just set this if version is 20.0.0.5 for now. Probably should be a separate option. - set( getHeaderItem(), "User Version", 11 ); - set( getHeaderItem(), "User Version 2", 11 ); - } - set( getHeaderItem(), "Unknown Int 3", 11 ); - - if ( version < 0x0303000D ) { - QVector copyright(3); - copyright[0] = "Numerical Design Limited, Chapel Hill, NC 27514"; - copyright[1] = "Copyright (c) 1996-2000"; - copyright[2] = "All Rights Reserved"; - - setArray( getHeader(), "Copyright", copyright ); - } -} - -/* - * footer functions - */ - -NifItem * NifModel::getFooterItem() const -{ - return root->child( root->childCount() - 1 ); -} - -QModelIndex NifModel::getFooter() const -{ - NifItem * footer = getFooterItem(); - if ( footer ) - return createIndex( footer->row(), 0, footer ); - else - return QModelIndex(); -} - -void NifModel::updateFooter() -{ - NifItem * footer = getFooterItem(); - if ( ! footer ) return; - NifItem * roots = getItem( footer, "Roots" ); - if ( ! roots ) return; - set( footer, "Num Roots", rootLinks.count() ); - updateArrayItem( roots, false ); - for ( int r = 0; r < roots->childCount(); r++ ) - roots->child( r )->value().setLink( rootLinks.value( r ) ); -} - -/* - * header functions - */ - -QModelIndex NifModel::getHeader() const -{ - QModelIndex header = index( 0, 0 ); - return header; -} - -NifItem * NifModel::getHeaderItem() const -{ - return root->child( 0 ); -} - -void NifModel::updateHeader() -{ - NifItem * header = getHeaderItem(); - - set( header, "Num Blocks", getBlockCount() ); - - NifItem * idxBlockTypes = getItem( header, "Block Types" ); - NifItem * idxBlockTypeIndices = getItem( header, "Block Type Index" ); - if ( idxBlockTypes && idxBlockTypeIndices ) - { - QStringList blocktypes; - QList blocktypeindices; - - for ( int r = 1; r < root->childCount() - 1; r++ ) - { - NifItem * block = root->child( r ); - - if ( ! blocktypes.contains( block->name() ) ) - blocktypes.append( block->name() ); - blocktypeindices.append( blocktypes.indexOf( block->name() ) ); - } - - set( header, "Num Block Types", blocktypes.count() ); - - // Setting fast update to true to workaround bug where deleting blocks - // causes a crash. This probably means anyone listening to updates - // for these two arrays will not work. - updateArrayItem( idxBlockTypes, false ); - updateArrayItem( idxBlockTypeIndices, true ); - - for ( int r = 0; r < idxBlockTypes->childCount(); r++ ) - set( idxBlockTypes->child( r ), blocktypes.value( r ) ); - - for ( int r = 0; r < idxBlockTypeIndices->childCount(); r++ ) - set( idxBlockTypeIndices->child( r ), blocktypeindices.value( r ) ); - } -} - -/* - * search and find - */ - -NifItem * NifModel::getItem( NifItem * item, const QString & name ) const -{ - if ( name.startsWith( "HEADER/" ) ) - return getItem( getHeaderItem(), name.right( name.length() - 7 ) ); - - if ( ! item || item == root ) return 0; - - int slash = name.indexOf( "/" ); - if ( slash > 0 ) - { - QString left = name.left( slash ); - QString right = name.right( name.length() - slash - 1 ); - - if ( left == ".." ) - return getItem( item->parent(), right ); - else - return getItem( getItem( item, left ), right ); - } - - for ( int c = 0; c < item->childCount(); c++ ) - { - NifItem * child = item->child( c ); - - if ( child->name() == name && evalCondition( child ) ) - return child; - } - - return 0; -} - -/* - * array functions - */ - -static QString parentPrefix( const QString & x ) -{ - for ( int c = 0; c < x.length(); c++ ) - if ( ! x[c].isNumber() ) - return QString( "../" ) + x; - return x; -} - -bool NifModel::updateArrayItem( NifItem * array, bool fast ) -{ - if ( array->arr1().isEmpty() ) - return false; - - int d1 = getArraySize( array ); - if ( d1 > 1024 * 1024 * 8 ) - { - msg( Message() << "array" << array->name() << "much too large." << d1 << " bytes requested" ); - return false; - } - else if ( d1 < 0 ) - { - msg( Message() << "array" << array->name() << "invalid" ); - return false; - } - - int rows = array->childCount(); - if ( d1 > rows ) - { - NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::type( array->type() ) ), parentPrefix( array->arg() ), parentPrefix( array->arr2() ), QString(), QString(), 0, 0 ); - - if ( ! fast ) beginInsertRows( createIndex( array->row(), 0, array ), rows, d1-1 ); - array->prepareInsert( d1 - rows ); - for ( int c = rows; c < d1; c++ ) - insertType( array, data ); - if ( ! fast ) endInsertRows(); - } - if ( d1 < rows ) - { - if ( ! fast ) beginRemoveRows( createIndex( array->row(), 0, array ), d1, rows - 1 ); - array->removeChildren( d1, rows - d1 ); - if ( ! fast ) endRemoveRows(); - } - if ( !fast && d1 != rows && ( isCompound( array->type() ) || NifValue::isLink( NifValue::type( array->type() ) ) ) ) - { - NifItem * parent = array; - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); - if ( parent != getFooterItem() ) - { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - } - return true; -} - -/* - * block functions - */ - -QModelIndex NifModel::insertNiBlock( const QString & identifier, int at, bool fast ) -{ - NifBlock * block = blocks.value( identifier ); - if ( block ) - { - if ( at < 0 || at > getBlockCount() ) - at = -1; - if ( at >= 0 ) - adjustLinks( root, at, 1 ); - - if ( at >= 0 ) - at++; - else - at = getBlockCount() + 1; - - if ( ! fast ) beginInsertRows( QModelIndex(), at, at ); - NifItem * branch = insertBranch( root, NifData( identifier, "NiBlock", block->text ), at ); - if ( ! fast ) endInsertRows(); - - if ( ! block->ancestor.isEmpty() ) - insertAncestor( branch, block->ancestor ); - - branch->prepareInsert( block->types.count() ); - - foreach ( NifData data, block->types ) - insertType( branch, data ); - - if ( ! fast ) - { - updateHeader(); - updateLinks(); - updateFooter(); - emit linksChanged(); - } - return createIndex( branch->row(), 0, branch ); - } - else - { - msg( Message() << "unknown block " << identifier ); - return QModelIndex(); - } -} - -void NifModel::removeNiBlock( int blocknum ) -{ - if ( blocknum < 0 || blocknum >= getBlockCount() ) - return; - - adjustLinks( root, blocknum, 0 ); - adjustLinks( root, blocknum, -1 ); - beginRemoveRows( QModelIndex(), blocknum+1, blocknum+1 ); - root->removeChild( blocknum + 1 ); - endRemoveRows(); - updateHeader(); - updateLinks(); - updateFooter(); - emit linksChanged(); -} - -void NifModel::moveNiBlock( int src, int dst ) -{ - if ( src < 0 || src >= getBlockCount() ) - return; - - beginRemoveRows( QModelIndex(), src+1, src+1 ); - NifItem * block = root->takeChild( src+1 ); - endRemoveRows(); - - if ( dst >= 0 ) - dst++; - - beginInsertRows( QModelIndex(), dst, dst ); - dst = root->insertChild( block, dst ) - 1; - endInsertRows(); - - QMap map; - if ( src < dst ) - for ( int l = src; l <= dst; l++ ) - map.insert( l, l-1 ); - else - for ( int l = dst; l <= src; l++ ) - map.insert( l, l+1 ); - - map.insert( src, dst ); - - mapLinks( root, map ); - - updateLinks(); - updateHeader(); - updateFooter(); - emit linksChanged(); -} - -QMap NifModel::moveAllNiBlocks( NifModel * targetnif ) -{ - int bcnt = getBlockCount(); - - QMap map; - - beginRemoveRows( QModelIndex(), 1, bcnt ); - targetnif->beginInsertRows( QModelIndex(), targetnif->getBlockCount(), targetnif->getBlockCount() + bcnt - 1 ); - - for ( int i = 0; i < bcnt; i++ ) - { - map.insert( i, targetnif->root->insertChild( root->takeChild( 1 ), targetnif->root->childCount() - 1 ) - 1 ); - } - - endRemoveRows(); - targetnif->endInsertRows(); - - for ( int i = 0; i < bcnt; i++ ) - { - targetnif->mapLinks( targetnif->root->child( targetnif->getBlockCount() - i ), map ); - } - - updateLinks(); - updateHeader(); - updateFooter(); - emit linksChanged(); - - targetnif->updateLinks(); - targetnif->updateHeader(); - targetnif->updateFooter(); - emit targetnif->linksChanged(); - - return map; -} - -void NifModel::reorderBlocks( const QVector & order ) -{ - if ( getBlockCount() <= 1 ) - return; - - if ( order.count() != getBlockCount() ) - { - msg( Message() << "NifModel::reorderBlocks() - invalid argument" ); - return; - } - - QMap linkMap; - QMap blockMap; - - for ( qint32 n = 0; n < order.count(); n++ ) - { - if ( blockMap.contains( order[n] ) || order[n] < 0 || order[n] >= getBlockCount() ) - { - msg( Message() << "NifModel::reorderBlocks() - invalid argument" ); - return; - } - blockMap.insert( order[n], n ); - if ( order[n] != n ) - linkMap.insert( n, order[n] ); - } - - if ( linkMap.isEmpty() ) - return; - - // take all the blocks - beginRemoveRows( QModelIndex(), 1, root->childCount() - 2 ); - QList temp; - for ( qint32 n = 0; n < order.count(); n++ ) - temp.append( root->takeChild( 1 ) ); - endRemoveRows(); - - // then insert them again in the new order - beginInsertRows( QModelIndex(), 1, temp.count() ); - foreach ( qint32 n, blockMap ) - root->insertChild( temp[ n ], root->childCount()-1 ); - endInsertRows(); - - mapLinks( root, linkMap ); - updateLinks(); - emit linksChanged(); - - updateHeader(); - updateFooter(); -} - -void NifModel::mapLinks( const QMap & map ) -{ - mapLinks( root, map ); - updateLinks(); - emit linksChanged(); - - updateHeader(); - updateFooter(); -} - -QString NifModel::getBlockName( const QModelIndex & idx ) const -{ - const NifItem * block = static_cast( idx.internalPointer() ); - if( block ) { - return block->name(); - } - - return QString( "" ); -} - -QString NifModel::getBlockType( const QModelIndex & idx ) const -{ - const NifItem * block = static_cast( idx.internalPointer() ); - if( block ) { - return block->type(); - } - - return QString( "" ); -} - -int NifModel::getBlockNumber( const QModelIndex & idx ) const -{ - if ( ! ( idx.isValid() && idx.model() == this ) ) - return -1; - - const NifItem * block = static_cast( idx.internalPointer() ); - while ( block && block->parent() != root ) - block = block->parent(); - - if ( ! block ) - return -1; - - int num = block->row() - 1; - if ( num >= getBlockCount() ) num = -1; - return num; -} - -QModelIndex NifModel::getBlock( const QModelIndex & idx, const QString & id ) const -{ - return getBlock( getBlockNumber( idx ), id ); -} - -QModelIndex NifModel::getBlockOrHeader( const QModelIndex & idx ) const -{ - QModelIndex block = idx; - while ( block.isValid() && block.parent().isValid() ) - block = block.parent(); - return block; -} - -int NifModel::getBlockNumber( NifItem * block ) const -{ - while ( block && block->parent() != root ) - block = block->parent(); - - if ( ! block ) - return -1; - - int num = block->row() - 1; - if ( num >= getBlockCount() ) num = -1; - return num; -} - -QModelIndex NifModel::getBlock( int x, const QString & name ) const -{ - if ( x < 0 || x >= getBlockCount() ) - return QModelIndex(); - x += 1; //the first block is the NiHeader - QModelIndex idx = index( x, 0 ); - if ( inherits( idx, name ) ) - return idx; - else - return QModelIndex(); -} - -bool NifModel::isNiBlock( const QModelIndex & index, const QString & name ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( index.isValid() && item && item->parent() == root && getBlockNumber( item ) >= 0 ) - { - if ( name.isEmpty() ) - return true; - else - return item->name() == name; - } - return false; -} - -NifItem * NifModel::getBlockItem( int x ) const -{ - if ( x < 0 || x >= getBlockCount() ) return 0; - return root->child( x+1 ); -} - -int NifModel::getBlockCount() const -{ - return rowCount() - 2; -} - - -/* - * ancestor functions - */ - -void NifModel::insertAncestor( NifItem * parent, const QString & identifier, int at ) -{ - NifBlock * ancestor = blocks.value( identifier ); - if ( ancestor ) - { - if ( ! ancestor->ancestor.isEmpty() ) - insertAncestor( parent, ancestor->ancestor ); - //parent->insertChild( NifData( identifier, "Abstract" ) ); - parent->prepareInsert( ancestor->types.count() ); - foreach ( NifData data, ancestor->types ) - insertType( parent, data ); - } - else - msg( Message() << "unknown ancestor " << identifier ); -} - -bool NifModel::inherits( const QString & name, const QString & aunty ) -{ - if ( name == aunty ) return true; - NifBlock * type = blocks.value( name ); - if ( type && ( type->ancestor == aunty || inherits( type->ancestor, aunty ) ) ) - return true; - return false; -} - -bool NifModel::inherits( const QModelIndex & idx, const QString & aunty ) const -{ - int x = getBlockNumber( idx ); - if ( x < 0 ) - return false; - return inherits( itemName( index( x+1, 0 ) ), aunty ); -} - - -/* - * basic and compound type functions - */ - -void NifModel::insertType( const QModelIndex & parent, const NifData & data, int at ) -{ - NifItem * item = static_cast( parent.internalPointer() ); - if ( parent.isValid() && item && item != root ) - insertType( item, data, at ); -} - -void NifModel::insertType( NifItem * parent, const NifData & data, int at ) -{ - if ( ! data.arr1().isEmpty() ) - { - NifItem * array = insertBranch( parent, data, at ); - - if ( evalCondition( array ) ) - updateArrayItem( array, true ); - return; - } - - NifBlock * compound = compounds.value( data.type() ); - if ( compound ) - { - NifItem * branch = insertBranch( parent, data, at ); - branch->prepareInsert( compound->types.count() ); - foreach ( NifData d, compound->types ) - insertType( branch, d ); - } - else - { - if ( data.type() == "TEMPLATE" || data.temp() == "TEMPLATE" ) - { - QString tmp = parent->temp(); - NifItem * tItem = parent; - while ( tmp == "TEMPLATE" && tItem->parent() ) - { - tItem = tItem->parent(); - tmp = tItem->temp(); - } - NifData d( data ); - if ( d.type() == "TEMPLATE" ) - { - d.value.changeType( NifValue::type( tmp ) ); - d.setType( tmp ); - } - if ( d.temp() == "TEMPLATE" ) - d.setTemp( tmp ); - insertType( parent, d, at ); - } - else - parent->insertChild( data, at ); - } -} - - -/* - * item value functions - */ - -bool NifModel::setItemValue( NifItem * item, const NifValue & val ) -{ - item->value() = val; - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - if ( itemIsLink( item ) ) - { - NifItem * parent = item; - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); - if ( parent != getFooterItem() ) - { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - } - return true; -} - -/* - * QAbstractModel interface - */ - -QVariant NifModel::data( const QModelIndex & idx, int role ) const -{ - QModelIndex index = buddy( idx ); - - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) - return QVariant(); - - int column = index.column(); - - if ( column == ValueCol && item->parent() == root && item->type() == "NiBlock" ) - { - QModelIndex buddy; - if ( item->name() == "NiSourceTexture" || item->name() == "NiImage" ) - buddy = getIndex( index, "File Name" ); - else if ( item->name() == "NiStringExtraData" ) - buddy = getIndex( index, "String Data" ); - else - buddy = getIndex( index, "Name" ); - if ( buddy.isValid() ) - buddy = buddy.sibling( buddy.row(), index.column() ); - if ( buddy.isValid() ) - return data( buddy, role ); - } - else if ( column == ValueCol && item->parent() != root && item->type() == "ControllerLink" && role == Qt::DisplayRole ) - { - QModelIndex buddy; - if ( item->name() == "Controlled Blocks" ) - { - if (version >= 0x14010003) - { - buddy = getIndex( index, "Node Name" ); - if ( buddy.isValid() ) - buddy = buddy.sibling( buddy.row(), index.column() ); - if ( buddy.isValid() ) - return data( buddy, role ); - } - else if (version <= 0x14000005) - { - buddy = getIndex( index, "Node Name Offset" ); - if ( buddy.isValid() ) - buddy = buddy.sibling( buddy.row(), index.column() ); - if ( buddy.isValid() ) - return data( buddy, role ); - } - } - } - - - switch ( role ) - { - case Qt::DisplayRole: - { - switch ( column ) - { - case NameCol: - { - return item->name(); - } break; - case TypeCol: - { - if ( ! item->temp().isEmpty() ) - { - NifItem * i = item; - while ( i && i->temp() == "TEMPLATE" ) - i = i->parent(); - return QString( "%1<%2>" ).arg( item->type() ).arg( i ? i->temp() : QString() ); - } - else - { - return item->type(); - } - } break; - case ValueCol: - { - QString type = item->type(); - const NifValue& value = item->value(); - if (value.type() == NifValue::tString || value.type() == NifValue::tFilePath) - { - return QString(this->string(index)).replace("\n", " ").replace("\r", " "); - } - else if ( item->value().type() == NifValue::tStringOffset ) - { - int ofs = item->value().get(); - if ( ofs < 0 || ofs == 0x0000FFFF ) - return QString( "" ); - NifItem * palette = getItemX( item, "String Palette" ); - int link = ( palette ? palette->value().toLink() : -1 ); - if ( ( palette = getBlockItem( link ) ) && ( palette = getItem( palette, "Palette" ) ) ) - { - QByteArray bytes = palette->value().get(); - if ( ofs < bytes.count() ) - return QString( & bytes.data()[ofs] ); - else - return QString( "" ); - } - else - { - return QString( "" ); - } - } - else if ( item->value().type() == NifValue::tStringIndex ) - { - int idx = item->value().get(); - if (idx == -1) - return QString(); - NifItem * header = getHeaderItem(); - QModelIndex stringIndex = createIndex( header->row(), 0, header ); - QString string = get( this->index( idx, 0, getIndex( stringIndex, "Strings" ) ) ); - if ( idx >= 0 ) - return QString( "%2 [%1]").arg( idx ).arg( string ); - return QString( "%1 - " ).arg( idx ); - } - else if ( item->value().type() == NifValue::tBlockTypeIndex ) - { - int idx = item->value().get(); - int offset = idx & 0x7FFF; - NifItem * blocktypes = getItemX( item, "Block Types" ); - NifItem * blocktyp = ( blocktypes ? blocktypes->child( offset ) : 0 ); - if ( blocktyp ) - return QString( "%2 [%1]").arg( idx ).arg( blocktyp->value().get() ); - else - return QString( "%1 - " ).arg( idx ); - } - else if ( item->value().isLink() ) - { - int lnk = item->value().toLink(); - if ( lnk >= 0 ) - { - QModelIndex block = getBlock( lnk ); - if ( block.isValid() ) - { - QModelIndex block_name = getIndex( block, "Name" ); - if ( block_name.isValid() && ! get( block_name ).isEmpty() ) - return QString( "%1 (%2)" ).arg( lnk ).arg( get( block_name ) ); - else - return QString( "%1 [%2]" ).arg( lnk ).arg( itemName( block ) ); - } - else - { - return QString( "%1 " ).arg( lnk ); - } - } - else - return QString( "None" ); - } - else if ( item->value().isCount() ) - { - QString optId = NifValue::enumOptionName( item->type(), item->value().toCount() ); - if ( optId.isEmpty() ) - return item->value().toString(); - else - return QString( "%1" ).arg( optId ); - } - else - { - return item->value().toString().replace("\n", " ").replace("\r", " "); - } - } break; - case ArgCol: return item->arg(); - case Arr1Col: return item->arr1(); - case Arr2Col: return item->arr2(); - case CondCol: return item->cond(); - case Ver1Col: return version2string( item->ver1() ); - case Ver2Col: return version2string( item->ver2() ); - default: return QVariant(); - } - } - case Qt::DecorationRole: - { - switch ( column ) - { - case NameCol: - if ( itemType( index ) == "NiBlock" ) - return QString::number( getBlockNumber( index ) ); - default: - return QVariant(); - } - } - case Qt::EditRole: - { - switch ( column ) - { - case NameCol: return item->name(); - case TypeCol: return item->type(); - case ValueCol: - { - const NifValue& value = item->value(); - if (value.type() == NifValue::tString || value .type() == NifValue::tFilePath) - { - return string(index); - } - return item->value().toVariant(); - } - case ArgCol: return item->arg(); - case Arr1Col: return item->arr1(); - case Arr2Col: return item->arr2(); - case CondCol: return item->cond(); - case Ver1Col: return version2string( item->ver1() ); - case Ver2Col: return version2string( item->ver2() ); - default: return QVariant(); - } - } - case Qt::ToolTipRole: - { - QString tip; - switch ( column ) - { - case NameCol: - { - if ( item->parent() && ! item->parent()->arr1().isEmpty() ) - { - return QString( "array index: %1" ).arg( item->row() ); - } - else - { - QString tip = QString( "

%1

%2

" ).arg( item->name() ).arg( item->text() ); - - if ( NifBlock * blk = blocks.value( item->name() ) ) - { - tip += "

Ancestors:

    "; - while ( blocks.contains( blk->ancestor ) ) - { - tip += QString( "
  • %1
  • " ).arg( blk->ancestor ); - blk = blocks.value( blk->ancestor ); - } - tip += "

"; - } - return tip; - } - } break; - case TypeCol: - return NifValue::typeDescription( item->type() ); - case ValueCol: - { - switch ( item->value().type() ) - { - case NifValue::tByte: - case NifValue::tWord: - case NifValue::tShort: - case NifValue::tBool: - case NifValue::tInt: - case NifValue::tUInt: - { - return QString( "dec: %1\nhex: 0x%2" ) - .arg( item->value().toString() ) - .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); - } - case NifValue::tFloat: - { - return QString( "float: %1\nhex: 0x%2" ) - .arg( NumOrMinMax( item->value().toFloat(), 'g', 8 ) ) - .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); - } - case NifValue::tFlags: - { - quint16 f = item->value().toCount(); - return QString( "dec: %1\nhex: 0x%2\nbin: 0b%3" ) - .arg( f ) - .arg( f, 4, 16, QChar( '0' ) ) - .arg( f, 16, 2, QChar( '0' ) ); - } - case NifValue::tStringIndex: - return QString( "0x%1" ) - .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); - case NifValue::tStringOffset: - return QString( "0x%1" ) - .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); - case NifValue::tVector3: - return item->value().get().toHtml(); - case NifValue::tMatrix: - return item->value().get().toHtml(); - case NifValue::tMatrix4: - return item->value().get().toHtml(); - case NifValue::tQuat: - case NifValue::tQuatXYZW: - return item->value().get().toHtml(); - case NifValue::tColor3: - { - Color3 c = item->value().get(); - return QString( "R %1\nG %2\nB %3" ) - .arg( c[0] ) - .arg( c[1] ) - .arg( c[2] ); - } - case NifValue::tColor4: - { - Color4 c = item->value().get(); - return QString( "R %1\nG %2\nB %3\nA %4" ) - .arg( c[0] ) - .arg( c[1] ) - .arg( c[2] ) - .arg( c[3] ); - } - default: - break; - } - } break; - default: - break; - } - } return QVariant(); - case Qt::BackgroundColorRole: - { - if ( column == ValueCol && item->value().isColor() ) - { - return item->value().toColor(); - } - } return QVariant(); - case Qt::UserRole: - { - if ( column == ValueCol ) - { - Spell * spell = SpellBook::instant( this, index ); - if ( spell ) - return spell->page() + "/" + spell->name(); - } - } return QVariant(); - default: - return QVariant(); - } -} - -bool NifModel::setData( const QModelIndex & index, const QVariant & value, int role ) -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && role == Qt::EditRole && index.model() == this && item ) ) - return false; - - // buddy lookup - if ( index.column() == ValueCol && item->parent() == root && item->type() == "NiBlock" ) - { - QModelIndex buddy; - if ( item->name() == "NiSourceTexture" || item->name() == "NiImage" ) - buddy = getIndex( index, "File Name" ); - else if ( item->name() == "NiStringExtraData" ) - buddy = getIndex( index, "String Data" ); - else - buddy = getIndex( index, "Name" ); - if ( buddy.isValid() ) - buddy = buddy.sibling( buddy.row(), index.column() ); - if ( buddy.isValid() ) - return setData( buddy, value, role ); - } - - switch ( index.column() ) - { - case NifModel::NameCol: - item->setName( value.toString() ); - if ( item->parent() && item->parent() == root ) - updateHeader(); - break; - case NifModel::TypeCol: - item->setType( value.toString() ); - break; - case NifModel::ValueCol: - { - QString type = item->type(); - NifValue& val = item->value(); - if (val.type() == NifValue::tString || val.type() == NifValue::tFilePath) - { - val.changeType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); - assignString(index, value.toString(), true); - } - else - { - item->value().fromVariant( value ); - if ( isLink( index ) && getBlockOrHeader( index ) != getFooter() ) - { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - } - } break; - case NifModel::ArgCol: - item->setArg( value.toString() ); - break; - case NifModel::Arr1Col: - item->setArr1( value.toString() ); - break; - case NifModel::Arr2Col: - item->setArr2( value.toString() ); - break; - case NifModel::CondCol: - item->setCond( value.toString() ); - break; - case NifModel::Ver1Col: - item->setVer1( NifModel::version2number( value.toString() ) ); - break; - case NifModel::Ver2Col: - item->setVer2( NifModel::version2number( value.toString() ) ); - break; - default: - return false; - } - - // reverse buddy lookup - if ( index.column() == ValueCol ) - { - if ( item->name() == "File Name" ) - { - NifItem * parent = item->parent(); - if ( parent && ( parent->name() == "Texture Source" || parent->name() == "NiImage" ) ) - { - parent = parent->parent(); - if ( parent && parent->type() == "NiBlock" && parent->name() == "NiSourceTexture" ) - emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); - } - } - else if ( item->name() == "Name" ) - { - NifItem * parent = item->parent(); - if ( parent && parent->type() == "NiBlock" ) - emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); - } - } - - // update original index - emit dataChanged( index, index ); - - return true; -} - -void NifModel::reset() -{ - updateLinks(); - BaseModel::reset(); -} - -bool NifModel::removeRows( int row, int count, const QModelIndex & parent ) -{ - NifItem * item = static_cast( parent.internalPointer() ); - if ( ! ( parent.isValid() && parent.model() == this && item ) ) - return false; - - if ( row >= 0 && row+count < item->childCount() ) - { - bool link = false; - for ( int r = row; r < row + count; r++ ) - link |= itemIsLink( item->child( r ) ); - - beginRemoveRows( parent, row, row+count ); - item->removeChildren( row, count ); - endRemoveRows(); - - if ( link ) - { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - return true; - } - else - return false; -} - - -/* - * load and save - */ - -bool NifModel::setHeaderString( const QString & s ) -{ - //msg( DbgMsg() << s ); - if ( ! ( s.startsWith( "NetImmerse File Format" ) || s.startsWith( "Gamebryo" ) ) ) - { - msg( Message() << "this is not a NIF" ); - return false; - } - - int p = s.indexOf( "Version", 0, Qt::CaseInsensitive ); - if ( p >= 0 ) - { - QString v = s; - - v.remove( 0, p + 8 ); - - for ( int i = 0; i < v.length(); i++ ) - { - if ( v[i].isDigit() || v[i] == QChar( '.' ) ) - continue; - else - { - v = v.left( i ); - } - } - - version = version2number( v ); - - if ( ! isVersionSupported( version ) ) - { - msg( Message() << "version" << version2string( version ) << "(" << v << ")" << "is not supported yet" ); - return false; - } - - return true; - } - else - { - msg( Message() << "invalid header string" ); - return false; - } -} - -bool NifModel::load( QIODevice & device ) -{ - clear(); - - NifIStream stream( this, &device ); - - // read header - NifItem * header = getHeaderItem(); - if ( !header || !load( header, stream, true ) ) - { - msg( Message() << "failed to load file header (version" << version << ")" ); - return false; - } - - int numblocks = get( header, "Num Blocks" ); - //qDebug( "numblocks %i", numblocks ); - - emit sigProgress( 0, numblocks ); - QTime t = QTime::currentTime(); - - try - { - qint64 curpos = device.pos(); - if ( version >= 0x0303000d ) - { - // read in the NiBlocks - QString prevblktyp; - for ( int c = 0; c < numblocks; c++ ) - { - emit sigProgress( c + 1, numblocks ); - - if ( device.atEnd() ) - throw QString( "unexpected EOF during load" ); - - QString blktyp; - quint32 size = UINT_MAX; - try - { - if ( version >= 0x0a000000 ) - { - // block types are stored in the header for versions above 10.x.x.x - // the upper bit or the blocktypeindex seems to be related to PhysX - int blktypidx = get( index( c, 0, getIndex( createIndex( header->row(), 0, header ), "Block Type Index" ) ) ); - blktyp = get( index( blktypidx & 0x7FFF, 0, getIndex( createIndex( header->row(), 0, header ), "Block Types" ) ) ); - - if ( version < 0x0a020000 ) - device.read( 4 ); - - // for version 20.2.0.? and above the block size is stored in the header - if (version >= 0x14020000) - size = get( index( c, 0, getIndex( createIndex( header->row(), 0, header ), "Block Size" ) ) ); - } - else - { - int len; - device.read( (char *) &len, 4 ); - if ( len < 2 || len > 80 ) - throw QString( "next block does not start with a NiString" ); - blktyp = device.read( len ); - } - - if ( isNiBlock( blktyp ) ) - { - //msg( DbgMsg() << "loading block" << c << ":" << blktyp ); - insertNiBlock( blktyp, -1, true ); - if ( ! load( root->child( c+1 ), stream, true ) ) - { - NifItem * child = root->child( c ); - throw QString( "failed to load block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( child ? child->name() : prevblktyp ); - } - } - else - throw QString( "encountered unknown block (%1)" ).arg( blktyp ); - } - catch ( QString err ) - { - // version 20.3.0.3 can mostly recover from some failures because it store block sizes - if (size == UINT_MAX) - throw err; - } - // Check device position and emit warning if location is not expected - if (size != UINT_MAX) - { - qint64 pos = device.pos(); - if ((curpos + size) != pos) - { - // unable to seek to location... abort - if (device.seek(curpos + size)) - msg( Message() << tr("device position incorrect after block number %1 (%2) at %3 ended at %4 expected %5").arg( c ).arg( blktyp ).arg(curpos).arg(pos).arg(curpos+size).toAscii() ); - else - throw QString( "failed to reposition device at block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( root->child( c )->name() ); - - curpos = device.pos(); - } - else - { - curpos = pos; - } - } - prevblktyp = blktyp; - } - - // read in the footer - if ( !load( getFooterItem(), stream, true ) ) - throw QString( "failed to load file footer" ); - } - else - { // versions below 3.3.0.13 - QMap linkMap; - - try { - for ( qint32 c = 0; true; c++ ) - { - emit sigProgress( c + 1, 0 ); - - if ( device.atEnd() ) - throw QString( "unexpected EOF during load" ); - - int len; - device.read( (char *) &len, 4 ); - if ( len < 0 || len > 80 ) - throw QString( "next block does not start with a NiString" ); - - QString blktyp = device.read( len ); - - if ( blktyp == "End Of File" ) - { - break; - } - else if ( blktyp == "Top Level Object" ) - { - device.read( (char *) &len, 4 ); - if ( len < 0 || len > 80 ) - throw QString( "next block does not start with a NiString" ); - blktyp = device.read( len ); - } - - qint32 p; - device.read( (char *) &p, 4 ); - p -= 1; - if ( p != c ) - linkMap.insert( p, c ); - - if ( isNiBlock( blktyp ) ) - { - //msg( DbgMsg() << "loading block" << c << ":" << blktyp ); - insertNiBlock( blktyp, -1, true ); - if ( ! load( root->child( c+1 ), stream, true ) ) - throw QString( "failed to load block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( root->child( c )->name() ); - } - else - throw QString( "encountered unknown block (%1)" ).arg( blktyp ); - } - } - catch ( QString err ) { - //If this is an old file we should still map the links, even if it failed - mapLinks( linkMap ); - - //Re-throw exception so that the error is printed - throw err; - } - - //Also map links if nothing went wrong - mapLinks( linkMap ); - } - } - catch ( QString err ) - { - msg( Message() << err.toAscii().constData() ); - reset(); - return false; - } - //msg( Message() << t.msecsTo( QTime::currentTime() ) ); - reset(); // notify model views that a significant change to the data structure has occurded - return true; -} - -bool NifModel::save( QIODevice & device ) const -{ - NifOStream stream( this, &device ); - - emit sigProgress( 0, rowCount( QModelIndex() ) ); - - for ( int c = 0; c < rowCount( QModelIndex() ); c++ ) - { - emit sigProgress( c+1, rowCount( QModelIndex() ) ); - - //msg( DbgMsg() << "saving block" << c << ":" << itemName( index( c, 0 ) ) ); - if ( itemType( index( c, 0 ) ) == "NiBlock" ) - { - if ( version > 0x0a000000 ) - { - if ( version < 0x0a020000 ) - { - int null = 0; - device.write( (char *) &null, 4 ); - } - } - else - { - if ( version < 0x0303000d ) - { - if ( rootLinks.contains( c - 1 ) ) - { - QString string = "Top Level Object"; - int len = string.length(); - device.write( (char *) &len, 4 ); - device.write( (const char *) string.toAscii(), len ); - } - } - - QString string = itemName( index( c, 0 ) ); - int len = string.length(); - device.write( (char *) &len, 4 ); - device.write( (const char *) string.toAscii(), len ); - - if ( version < 0x0303000d ) - { - device.write( (char *) &c, 4 ); - } - } - } - if ( !save( root->child( c ), stream ) ) - { - msg( Message() << "failed to write block" << itemName( index( c, 0 ) ) << "(" << c-1 << ")" ); - return false; - } - } - - if ( version < 0x0303000d ) - { - QString string = "End Of File"; - int len = string.length(); - device.write( (char *) &len, 4 ); - device.write( (const char *) string.toAscii(), len ); - } - - return true; -} - -bool NifModel::load( QIODevice & device, const QModelIndex & index ) -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( item && index.isValid() && index.model() == this ) - { - NifIStream stream( this, &device ); - bool ok = load( item, stream, false ); - updateLinks(); - updateFooter(); - emit linksChanged(); - return ok; - } - reset(); - return false; -} - -bool NifModel::loadAndMapLinks( QIODevice & device, const QModelIndex & index, const QMap & map ) -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( item && index.isValid() && index.model() == this ) - { - NifIStream stream( this, & device ); - bool ok = load( item, stream, false ); - mapLinks( item, map ); - updateLinks(); - updateFooter(); - emit linksChanged(); - return ok; - } - reset(); - return false; -} - -bool NifModel::loadHeaderOnly( const QString & fname ) -{ - clear(); - - QFile f( fname ); - if ( ! f.open( QIODevice::ReadOnly ) ) - { - msg( Message() << "failed to open file" << fname ); - return false; - } - - NifIStream stream( this, &f ); - - // read header - NifItem * header = getHeaderItem(); - if ( !header || !load( header, stream, true ) ) - { - msg( Message() << "failed to load file header (version" << version << ")" ); - return false; - } - - return true; -} - -bool NifModel::earlyRejection( const QString & filepath, const QString & blockId, quint32 version ) -{ - NifModel nif; - if ( nif.loadHeaderOnly( filepath ) == false ) { - //File failed to read entierly - return false; - } - - bool ver_match = false; - if ( version == 0 ) - { - ver_match = true; - } - else if ( version != 0 && nif.getVersionNumber() == version ) - { - ver_match = true; - } - - bool blk_match = false; - if ( blockId.isEmpty() == true || version < 0x0A000100 ) - { - blk_match = true; - } - else - { - foreach ( QString s, nif.getArray( nif.getHeader(), "Block Types" ) ) - { - if ( inherits( s, blockId ) ) - { - blk_match = true; - break; - } - } - } - - return (ver_match && blk_match); -} - -bool NifModel::save( QIODevice & device, const QModelIndex & index ) const -{ - NifOStream stream( this, &device ); - NifItem * item = static_cast( index.internalPointer() ); - return ( item && index.isValid() && index.model() == this && save( item, stream ) ); -} - -int NifModel::fileOffset( const QModelIndex & index ) const -{ - NifSStream stream( this ); - NifItem * target = static_cast( index.internalPointer() ); - if ( target && index.isValid() && index.model() == this ) - { - int ofs = 0; - for ( int c = 0; c < root->childCount(); c++ ) - { - if ( c > 0 && c <= getBlockCount() ) - { - if ( version > 0x0a000000 ) - { - if ( version < 0x0a020000 ) - { - ofs += 4; - } - } - else - { - if ( version < 0x0303000d ) - { - if ( rootLinks.contains( c - 1 ) ) - { - QString string = "Top Level Object"; - ofs += 4 + string.length(); - } - } - - QString string = itemName( this->NifModel::index( c, 0 ) ); - ofs += 4 + string.length(); - - if ( version < 0x0303000d ) - { - ofs += 4; - } - } - } - - if ( fileOffset( root->child( c ), target, stream, ofs ) ) - return ofs; - } - } - return -1; -} - -bool NifModel::load( NifItem * parent, NifIStream & stream, bool fast ) -{ - if ( ! parent ) return false; - - for ( int row = 0; row < parent->childCount(); row++ ) - { - NifItem * child = parent->child( row ); - - if ( evalCondition( child ) ) - { - if ( ! child->arr1().isEmpty() ) - { - if ( ! updateArrayItem( child, fast ) ) - return false; - - if ( ! load( child, stream, fast ) ) - return false; - } - else if ( child->childCount() > 0 ) - { - if ( ! load( child, stream, fast ) ) - return false; - } - else - { - if ( ! stream.read( child->value() ) ) - return false; - } - } - } - return true; -} - -bool NifModel::save( NifItem * parent, NifOStream & stream ) const -{ - if ( ! parent ) return false; - - for ( int row = 0; row < parent->childCount(); row++ ) - { - NifItem * child = parent->child( row ); - if ( evalCondition( child ) ) - { - if ( ! child->arr1().isEmpty() || ! child->arr2().isEmpty() || child->childCount() > 0 ) - { - if ( ! child->arr1().isEmpty() && child->childCount() != getArraySize( child ) ) - msg( Message() << "block" << getBlockNumber( parent ) << child->name() << "array size mismatch" ); - - if ( !save( child, stream ) ) - return false; - } - else - { - if ( ! stream.write( child->value() ) ) - return false; - } - } - } - return true; -} - -bool NifModel::fileOffset( NifItem * parent, NifItem * target, NifSStream & stream, int & ofs ) const -{ - if ( parent == target ) - return true; - - for ( int row = 0; row < parent->childCount(); row++ ) - { - NifItem * child = parent->child( row ); - if ( child == target ) - return true; - - if ( evalCondition( child ) ) - { - if ( ! child->arr1().isEmpty() || ! child->arr2().isEmpty() || child->childCount() > 0 ) - { - if ( fileOffset( child, target, stream, ofs ) ) - return true; - } - else - { - ofs += stream.size( child->value() ); - } - } - } - return false; -} - -NifItem * NifModel::insertBranch( NifItem * parentItem, const NifData & data, int at ) -{ - NifItem * item = parentItem->insertChild( data, at ); - item->value().changeType( NifValue::tNone ); - return item; -} - -/* - * link functions - */ - -void NifModel::updateLinks( int block ) -{ - if ( block >= 0 ) - { - childLinks[ block ].clear(); - parentLinks[ block ].clear(); - updateLinks( block, getBlockItem( block ) ); - } - else - { - rootLinks.clear(); - childLinks.clear(); - parentLinks.clear(); - - for ( int c = 0; c < getBlockCount(); c++ ) - updateLinks( c ); - - for ( int c = 0; c < getBlockCount(); c++ ) - { - QStack stack; - checkLinks( c, stack ); - } - - for ( int c = 0; c < getBlockCount(); c++ ) - { - bool isRoot = true; - for ( int d = 0; d < getBlockCount(); d++ ) - { - if ( c != d && childLinks.value( d ).contains( c ) ) - { - isRoot = false; - break; - } - } - if ( isRoot ) - rootLinks.append( c ); - } - } -} - -void NifModel::updateLinks( int block, NifItem * parent ) -{ - if ( ! parent ) return; - - for ( int r = 0; r < parent->childCount(); r++ ) - { - NifItem * child = parent->child( r ); - - bool ischild; - bool islink = itemIsLink( child, &ischild ); - - if ( child->childCount() > 0 ) - { - updateLinks( block, child ); - } - else if ( islink ) - { - int l = child->value().toLink(); - if ( l >= 0 && child->arr1().isEmpty() ) - { - if ( ischild ) - { - if ( !childLinks[block].contains( l ) ) childLinks[block].append( l ); - } - else - { - if ( !parentLinks[block].contains( l ) ) parentLinks[block].append( l ); - } - } - } - } -} - -void NifModel::checkLinks( int block, QStack & parents ) -{ - parents.push( block ); - foreach ( int child, childLinks.value( block ) ) - { - if ( parents.contains( child ) ) - { - msg( Message() << "infinite recursive link construct detected" << block << "->" << child ); - childLinks[block].removeAll( child ); - } - else - { - checkLinks( child, parents ); - } - } - parents.pop(); -} - -void NifModel::adjustLinks( NifItem * parent, int block, int delta ) -{ - if ( ! parent ) return; - - if ( parent->childCount() > 0 ) - { - for ( int c = 0; c < parent->childCount(); c++ ) - adjustLinks( parent->child( c ), block, delta ); - } - else - { - int l = parent->value().toLink(); - if ( l >= 0 && ( ( delta != 0 && l >= block ) || l == block ) ) - { - if ( delta == 0 ) - parent->value().setLink( -1 ); - else - parent->value().setLink( l + delta ); - } - } -} - -void NifModel::mapLinks( NifItem * parent, const QMap & map ) -{ - if ( ! parent ) return; - - if ( parent->childCount() > 0 ) - { - for ( int c = 0; c < parent->childCount(); c++ ) - mapLinks( parent->child( c ), map ); - } - else - { - int l = parent->value().toLink(); - if ( l >= 0 ) - { - if ( map.contains( l ) ) - parent->value().setLink( map[ l ] ); - } - } -} - -qint32 NifModel::getLink( const QModelIndex & index ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return -1; - return item->value().toLink(); -} - -qint32 NifModel::getLink( const QModelIndex & parent, const QString & name ) const -{ - NifItem * parentItem = static_cast( parent.internalPointer() ); - if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) - return -1; - - NifItem * item = getItem( parentItem, name ); - if ( item ) - return item->value().toLink(); - - return -1; -} - -QVector NifModel::getLinkArray( const QModelIndex & iArray ) const -{ - QVector links; - NifItem * item = static_cast( iArray.internalPointer() ); - if ( isArray( iArray ) && item && iArray.model() == this ) - { - for ( int c = 0; c < item->childCount(); c++ ) - { - if ( itemIsLink( item->child( c ) ) ) - { - links.append( item->child( c )->value().toLink() ); - } - else - { - links.clear(); - break; - } - } - } - return links; -} - -QVector NifModel::getLinkArray( const QModelIndex & parent, const QString & name ) const -{ - return getLinkArray( getIndex( parent, name ) ); -} - -bool NifModel::setLink( const QModelIndex & parent, const QString & name, qint32 l ) -{ - NifItem * parentItem = static_cast( parent.internalPointer() ); - if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) - return false; - - NifItem * item = getItem( parentItem, name ); - if ( item && item->value().setLink( l ) ) - { - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - NifItem * parent = item; - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); - if ( parent != getFooterItem() ) - { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - return true; - } - else - return false; -} - -bool NifModel::setLink( const QModelIndex & index, qint32 l ) -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) - return false; - - if ( item && item->value().setLink( l ) ) - { - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); - NifItem * parent = item; - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); - if ( parent != getFooterItem() ) - { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - return true; - } - else - return false; -} - -bool NifModel::setLinkArray( const QModelIndex & iArray, const QVector & links ) -{ - NifItem * item = static_cast( iArray.internalPointer() ); - if ( isArray( iArray ) && item && iArray.model() == this ) - { - bool ret = true; - for ( int c = 0; c < item->childCount() && c < links.count(); c++ ) - { - ret &= item->child( c )->value().setLink( links[c] ); - } - ret &= item->childCount() == links.count(); - int x = item->childCount() - 1; - if ( x >= 0 ) - emit dataChanged( createIndex( 0, ValueCol, item->child( 0 ) ), createIndex( x, ValueCol, item->child( x ) ) ); - NifItem * parent = item; - while ( parent->parent() && parent->parent() != root ) - parent = parent->parent(); - if ( parent != getFooterItem() ) - { - updateLinks(); - updateFooter(); - emit linksChanged(); - } - return ret; - } - return false; -} - -bool NifModel::setLinkArray( const QModelIndex & parent, const QString & name, const QVector & links ) -{ - return setLinkArray( getIndex( parent, name ), links ); -} - -bool NifModel::isLink( const QModelIndex & index, bool * isChildLink ) const -{ - NifItem * item = static_cast( index.internalPointer() ); - if ( ! ( index.isValid() && item && index.model() == this ) ) return false; - return itemIsLink( item, isChildLink ); -} - -int NifModel::getParent( int block ) const -{ - int parent = -1; - for ( int b = 0; b < getBlockCount(); b++ ) - { - if ( childLinks.value( b ).contains( block ) ) - { - if ( parent < 0 ) - parent = b; - else - return -1; - } - } - return parent; -} - -QString NifModel::string( const QModelIndex & index, bool extraInfo ) const -{ - NifValue v = getValue( index ); - if (v.type() == NifValue::tSizedString) - return BaseModel::get( index ); - - if (getVersionNumber() >= 0x14010003) - { - QModelIndex iIndex; - int idx = -1; - if (v.type() == NifValue::tStringIndex) - idx = get( index ); - else if (!v.isValid()) - idx = get( getIndex( index, "Index" ) ); - if (idx == -1) - { - return QString(); - } - else if ( idx >= 0 ) - { - NifItem * header = this->getHeaderItem(); - QModelIndex stringIndex = createIndex( header->row(), 0, header ); - if ( stringIndex.isValid() ) - { - QString string = BaseModel::get( this->index( idx, 0, getIndex( stringIndex, "Strings" ) ) ); - if (extraInfo) - string = QString( "%2 [%1]").arg( idx ).arg( string ); - return string; - } - else - { - return QString( "%1 - " ).arg( idx ); - } - } - } - else - { - if (v.type() == NifValue::tNone) - { - QModelIndex iIndex = getIndex( index, "String" ); - if (iIndex.isValid()) - return BaseModel::get( iIndex ); - } - else - { - return BaseModel::get( index ); - } - } - return QString(); -} - -QString NifModel::string( const QModelIndex & index, const QString & name, bool extraInfo ) const -{ - return string( getIndex(index, name), extraInfo ); -} - - -bool NifModel::assignString( const QModelIndex & index, const QString & string, bool replace) -{ - NifValue v = getValue( index ); - - if (getVersionNumber() >= 0x14010003) - { - QModelIndex iIndex; - int idx = -1; - if (v.type() == NifValue::tStringIndex) - idx = get( iIndex = index ); - else if (!v.isValid()) - idx = get( iIndex = getIndex( index, "Index" ) ); - else - return BaseModel::set( index, string ); - - QModelIndex header = getHeader(); - int nstrings = get( header, "Num Strings" ); - if ( string.isEmpty() ) - { - if (replace && idx >= 0 && idx < nstrings) - { - // TODO: Can we remove the string safely here? - } - return set( iIndex, 0xffffffff ); - } - // Simply replace the string - QModelIndex iArray = getIndex( header, "Strings" ); - if (replace && idx >= 0 && idx < nstrings) - { - return BaseModel::set(iArray.child(idx, 0), string); - } - - QVector stringVector = getArray( iArray ); - idx = stringVector.indexOf(string); - // Already exists. Just update the Index - if (idx >= 0 && idx < stringVector.size()) - { - return set( iIndex, idx ); - } - else // append string to end of list - { - set( header, "Num Strings", nstrings+1); - updateArray(header, "Strings"); - QModelIndex iArray = getIndex( header, "Strings" ); - BaseModel::set(iArray.child(nstrings, 0), string); - - return set( iIndex, nstrings ); - } - } - else // handle the older simpler strings - { - if (v.type() == NifValue::tNone) - { - return BaseModel::set( getIndex( index, "String" ), string ); - } - else - { - return BaseModel::set( index, string ); - } - } - return false; -} - -bool NifModel::assignString( const QModelIndex & index, const QString & name, const QString & string, bool replace ) -{ - return assignString( getIndex(index, name), string, replace ); -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "nifmodel.h" +#include "niftypes.h" +#include "options.h" + +#include "spellbook.h" + +#include +#include +#include +#include + +NifModel::NifModel( QObject * parent ) : BaseModel( parent ) +{ + clear(); +} + +QString NifModel::version2string( quint32 v ) +{ + if ( v == 0 ) return QString(); + QString s; + if ( v < 0x0303000D ) { + //This is an old-style 2-number version with one period + s = QString::number( ( v >> 24 ) & 0xff, 10 ) + "." + + QString::number( ( v >> 16 ) & 0xff, 10 ); + + quint32 sub_num1 = ((v >> 8) & 0xff); + quint32 sub_num2 = (v & 0xff); + if ( sub_num1 > 0 || sub_num2 > 0 ) { + s = s + QString::number( sub_num1, 10 ); + } + + if ( sub_num2 > 0 ) { + s = s + QString::number( sub_num2, 10 ); + } + } else { + //This is a new-style 4-number version with 3 periods + s = QString::number( ( v >> 24 ) & 0xff, 10 ) + "." + + QString::number( ( v >> 16 ) & 0xff, 10 ) + "." + + QString::number( ( v >> 8 ) & 0xff, 10 ) + "." + + QString::number( v & 0xff, 10 ); + } + return s; +} + +quint32 NifModel::version2number( const QString & s ) +{ + if ( s.isEmpty() ) return 0; + + if ( s.contains( "." ) ) + { + QStringList l = s.split( "." ); + + quint32 v = 0; + + if ( l.count() > 4 ) { + //Should probaby post a warning here or something. Version # has more than 3 dots in it. + return 0; + } else if ( l.count() == 2 ) { + //This is an old style version number. Take each digit following the first one at a time. + //The first one is the major version + v += l[0].toInt() << (3 * 8); + + if ( l[1].size() >= 1 ) { + v += l[1].mid(0, 1).toInt() << (2 * 8); + } + if ( l[1].size() >= 2 ) { + v += l[1].mid(1, 1).toInt() << (1 * 8); + } + if ( l[1].size() >= 3 ) { + v += l[1].mid(2, -1).toInt(); + } + return v; + } else { + //This is a new style version number with dots separating the digits + for ( int i = 0; i < 4 && i < l.count(); i++ ) { + v += l[i].toInt( 0, 10 ) << ( (3-i) * 8 ); + } + return v; + } + + } else { + bool ok; + quint32 i = s.toUInt( &ok ); + return ( i == 0xffffffff ? 0 : i ); + } +} + +bool NifModel::evalVersion( NifItem * item, bool chkParents ) const +{ + if ( item == root ) + return true; + + if ( chkParents && item->parent() ) + if ( ! evalVersion( item->parent(), true ) ) + return false; + + return item->evalVersion( version ); +} + +void NifModel::clear() +{ + folder = QString(); + root->killChildren(); + insertType( root, NifData( "NiHeader", "Header" ) ); + insertType( root, NifData( "NiFooter", "Footer" ) ); + version = version2number( Options::startupVersion() ); + if( !isVersionSupported(version) ) { + msg( Message() << tr("Unsupported 'Startup Version' %1 specified, reverting to 20.0.0.5").arg( Options::startupVersion() ).toAscii() ); + version = 0x14000005; + } + reset(); + NifItem * item = getItem( getHeaderItem(), "Version" ); + if ( item ) item->value().setFileVersion( version ); + + QString header_string; + if ( version <= 0x0A000100 ) { + header_string = "NetImmerse File Format, Version "; + } else { + header_string = "Gamebryo File Format, Version "; + } + + header_string += version2string(version); + + set( getHeaderItem(), "Header String", header_string ); + + if ( version == 0x14000005 ) { + //Just set this if version is 20.0.0.5 for now. Probably should be a separate option. + set( getHeaderItem(), "User Version", 11 ); + set( getHeaderItem(), "User Version 2", 11 ); + } + set( getHeaderItem(), "Unknown Int 3", 11 ); + + if ( version < 0x0303000D ) { + QVector copyright(3); + copyright[0] = "Numerical Design Limited, Chapel Hill, NC 27514"; + copyright[1] = "Copyright (c) 1996-2000"; + copyright[2] = "All Rights Reserved"; + + setArray( getHeader(), "Copyright", copyright ); + } +} + +/* + * footer functions + */ + +NifItem * NifModel::getFooterItem() const +{ + return root->child( root->childCount() - 1 ); +} + +QModelIndex NifModel::getFooter() const +{ + NifItem * footer = getFooterItem(); + if ( footer ) + return createIndex( footer->row(), 0, footer ); + else + return QModelIndex(); +} + +void NifModel::updateFooter() +{ + NifItem * footer = getFooterItem(); + if ( ! footer ) return; + NifItem * roots = getItem( footer, "Roots" ); + if ( ! roots ) return; + set( footer, "Num Roots", rootLinks.count() ); + updateArrayItem( roots, false ); + for ( int r = 0; r < roots->childCount(); r++ ) + roots->child( r )->value().setLink( rootLinks.value( r ) ); +} + +/* + * header functions + */ + +QModelIndex NifModel::getHeader() const +{ + QModelIndex header = index( 0, 0 ); + return header; +} + +NifItem * NifModel::getHeaderItem() const +{ + return root->child( 0 ); +} + +void NifModel::updateHeader() +{ + NifItem * header = getHeaderItem(); + + set( header, "Num Blocks", getBlockCount() ); + + NifItem * idxBlockTypes = getItem( header, "Block Types" ); + NifItem * idxBlockTypeIndices = getItem( header, "Block Type Index" ); + if ( idxBlockTypes && idxBlockTypeIndices ) + { + QStringList blocktypes; + QList blocktypeindices; + + for ( int r = 1; r < root->childCount() - 1; r++ ) + { + NifItem * block = root->child( r ); + + if ( ! blocktypes.contains( block->name() ) ) + blocktypes.append( block->name() ); + blocktypeindices.append( blocktypes.indexOf( block->name() ) ); + } + + set( header, "Num Block Types", blocktypes.count() ); + + // Setting fast update to true to workaround bug where deleting blocks + // causes a crash. This probably means anyone listening to updates + // for these two arrays will not work. + updateArrayItem( idxBlockTypes, false ); + updateArrayItem( idxBlockTypeIndices, true ); + + for ( int r = 0; r < idxBlockTypes->childCount(); r++ ) + set( idxBlockTypes->child( r ), blocktypes.value( r ) ); + + for ( int r = 0; r < idxBlockTypeIndices->childCount(); r++ ) + set( idxBlockTypeIndices->child( r ), blocktypeindices.value( r ) ); + } +} + +/* + * search and find + */ + +NifItem * NifModel::getItem( NifItem * item, const QString & name ) const +{ + if ( name.startsWith( "HEADER/" ) ) + return getItem( getHeaderItem(), name.right( name.length() - 7 ) ); + + if ( ! item || item == root ) return 0; + + int slash = name.indexOf( "/" ); + if ( slash > 0 ) + { + QString left = name.left( slash ); + QString right = name.right( name.length() - slash - 1 ); + + if ( left == ".." ) + return getItem( item->parent(), right ); + else + return getItem( getItem( item, left ), right ); + } + + for ( int c = 0; c < item->childCount(); c++ ) + { + NifItem * child = item->child( c ); + + if ( child->name() == name && evalCondition( child ) ) + return child; + } + + return 0; +} + +/* + * array functions + */ + +static QString parentPrefix( const QString & x ) +{ + for ( int c = 0; c < x.length(); c++ ) + if ( ! x[c].isNumber() ) + return QString( "../" ) + x; + return x; +} + +bool NifModel::updateArrayItem( NifItem * array, bool fast ) +{ + if ( array->arr1().isEmpty() ) + return false; + + int d1 = getArraySize( array ); + if ( d1 > 1024 * 1024 * 8 ) + { + msg( Message() << "array" << array->name() << "much too large." << d1 << " bytes requested" ); + return false; + } + else if ( d1 < 0 ) + { + msg( Message() << "array" << array->name() << "invalid" ); + return false; + } + + int rows = array->childCount(); + if ( d1 > rows ) + { + NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::type( array->type() ) ), parentPrefix( array->arg() ), parentPrefix( array->arr2() ), QString(), QString(), 0, 0 ); + + if ( ! fast ) beginInsertRows( createIndex( array->row(), 0, array ), rows, d1-1 ); + array->prepareInsert( d1 - rows ); + for ( int c = rows; c < d1; c++ ) + insertType( array, data ); + if ( ! fast ) endInsertRows(); + } + if ( d1 < rows ) + { + if ( ! fast ) beginRemoveRows( createIndex( array->row(), 0, array ), d1, rows - 1 ); + array->removeChildren( d1, rows - d1 ); + if ( ! fast ) endRemoveRows(); + } + if ( !fast && d1 != rows && ( isCompound( array->type() ) || NifValue::isLink( NifValue::type( array->type() ) ) ) ) + { + NifItem * parent = array; + while ( parent->parent() && parent->parent() != root ) + parent = parent->parent(); + if ( parent != getFooterItem() ) + { + updateLinks(); + updateFooter(); + emit linksChanged(); + } + } + return true; +} + +/* + * block functions + */ + +QModelIndex NifModel::insertNiBlock( const QString & identifier, int at, bool fast ) +{ + NifBlock * block = blocks.value( identifier ); + if ( block ) + { + if ( at < 0 || at > getBlockCount() ) + at = -1; + if ( at >= 0 ) + adjustLinks( root, at, 1 ); + + if ( at >= 0 ) + at++; + else + at = getBlockCount() + 1; + + if ( ! fast ) beginInsertRows( QModelIndex(), at, at ); + NifItem * branch = insertBranch( root, NifData( identifier, "NiBlock", block->text ), at ); + if ( ! fast ) endInsertRows(); + + if ( ! block->ancestor.isEmpty() ) + insertAncestor( branch, block->ancestor ); + + branch->prepareInsert( block->types.count() ); + + foreach ( NifData data, block->types ) + insertType( branch, data ); + + if ( ! fast ) + { + updateHeader(); + updateLinks(); + updateFooter(); + emit linksChanged(); + } + return createIndex( branch->row(), 0, branch ); + } + else + { + msg( Message() << "unknown block " << identifier ); + return QModelIndex(); + } +} + +void NifModel::removeNiBlock( int blocknum ) +{ + if ( blocknum < 0 || blocknum >= getBlockCount() ) + return; + + adjustLinks( root, blocknum, 0 ); + adjustLinks( root, blocknum, -1 ); + beginRemoveRows( QModelIndex(), blocknum+1, blocknum+1 ); + root->removeChild( blocknum + 1 ); + endRemoveRows(); + updateHeader(); + updateLinks(); + updateFooter(); + emit linksChanged(); +} + +void NifModel::moveNiBlock( int src, int dst ) +{ + if ( src < 0 || src >= getBlockCount() ) + return; + + beginRemoveRows( QModelIndex(), src+1, src+1 ); + NifItem * block = root->takeChild( src+1 ); + endRemoveRows(); + + if ( dst >= 0 ) + dst++; + + beginInsertRows( QModelIndex(), dst, dst ); + dst = root->insertChild( block, dst ) - 1; + endInsertRows(); + + QMap map; + if ( src < dst ) + for ( int l = src; l <= dst; l++ ) + map.insert( l, l-1 ); + else + for ( int l = dst; l <= src; l++ ) + map.insert( l, l+1 ); + + map.insert( src, dst ); + + mapLinks( root, map ); + + updateLinks(); + updateHeader(); + updateFooter(); + emit linksChanged(); +} + +QMap NifModel::moveAllNiBlocks( NifModel * targetnif ) +{ + int bcnt = getBlockCount(); + + QMap map; + + beginRemoveRows( QModelIndex(), 1, bcnt ); + targetnif->beginInsertRows( QModelIndex(), targetnif->getBlockCount(), targetnif->getBlockCount() + bcnt - 1 ); + + for ( int i = 0; i < bcnt; i++ ) + { + map.insert( i, targetnif->root->insertChild( root->takeChild( 1 ), targetnif->root->childCount() - 1 ) - 1 ); + } + + endRemoveRows(); + targetnif->endInsertRows(); + + for ( int i = 0; i < bcnt; i++ ) + { + targetnif->mapLinks( targetnif->root->child( targetnif->getBlockCount() - i ), map ); + } + + updateLinks(); + updateHeader(); + updateFooter(); + emit linksChanged(); + + targetnif->updateLinks(); + targetnif->updateHeader(); + targetnif->updateFooter(); + emit targetnif->linksChanged(); + + return map; +} + +void NifModel::reorderBlocks( const QVector & order ) +{ + if ( getBlockCount() <= 1 ) + return; + + if ( order.count() != getBlockCount() ) + { + msg( Message() << "NifModel::reorderBlocks() - invalid argument" ); + return; + } + + QMap linkMap; + QMap blockMap; + + for ( qint32 n = 0; n < order.count(); n++ ) + { + if ( blockMap.contains( order[n] ) || order[n] < 0 || order[n] >= getBlockCount() ) + { + msg( Message() << "NifModel::reorderBlocks() - invalid argument" ); + return; + } + blockMap.insert( order[n], n ); + if ( order[n] != n ) + linkMap.insert( n, order[n] ); + } + + if ( linkMap.isEmpty() ) + return; + + // take all the blocks + beginRemoveRows( QModelIndex(), 1, root->childCount() - 2 ); + QList temp; + for ( qint32 n = 0; n < order.count(); n++ ) + temp.append( root->takeChild( 1 ) ); + endRemoveRows(); + + // then insert them again in the new order + beginInsertRows( QModelIndex(), 1, temp.count() ); + foreach ( qint32 n, blockMap ) + root->insertChild( temp[ n ], root->childCount()-1 ); + endInsertRows(); + + mapLinks( root, linkMap ); + updateLinks(); + emit linksChanged(); + + updateHeader(); + updateFooter(); +} + +void NifModel::mapLinks( const QMap & map ) +{ + mapLinks( root, map ); + updateLinks(); + emit linksChanged(); + + updateHeader(); + updateFooter(); +} + +QString NifModel::getBlockName( const QModelIndex & idx ) const +{ + const NifItem * block = static_cast( idx.internalPointer() ); + if( block ) { + return block->name(); + } + + return QString( "" ); +} + +QString NifModel::getBlockType( const QModelIndex & idx ) const +{ + const NifItem * block = static_cast( idx.internalPointer() ); + if( block ) { + return block->type(); + } + + return QString( "" ); +} + +int NifModel::getBlockNumber( const QModelIndex & idx ) const +{ + if ( ! ( idx.isValid() && idx.model() == this ) ) + return -1; + + const NifItem * block = static_cast( idx.internalPointer() ); + while ( block && block->parent() != root ) + block = block->parent(); + + if ( ! block ) + return -1; + + int num = block->row() - 1; + if ( num >= getBlockCount() ) num = -1; + return num; +} + +QModelIndex NifModel::getBlock( const QModelIndex & idx, const QString & id ) const +{ + return getBlock( getBlockNumber( idx ), id ); +} + +QModelIndex NifModel::getBlockOrHeader( const QModelIndex & idx ) const +{ + QModelIndex block = idx; + while ( block.isValid() && block.parent().isValid() ) + block = block.parent(); + return block; +} + +int NifModel::getBlockNumber( NifItem * block ) const +{ + while ( block && block->parent() != root ) + block = block->parent(); + + if ( ! block ) + return -1; + + int num = block->row() - 1; + if ( num >= getBlockCount() ) num = -1; + return num; +} + +QModelIndex NifModel::getBlock( int x, const QString & name ) const +{ + if ( x < 0 || x >= getBlockCount() ) + return QModelIndex(); + x += 1; //the first block is the NiHeader + QModelIndex idx = index( x, 0 ); + if ( inherits( idx, name ) ) + return idx; + else + return QModelIndex(); +} + +bool NifModel::isNiBlock( const QModelIndex & index, const QString & name ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( index.isValid() && item && item->parent() == root && getBlockNumber( item ) >= 0 ) + { + if ( name.isEmpty() ) + return true; + else + return item->name() == name; + } + return false; +} + +NifItem * NifModel::getBlockItem( int x ) const +{ + if ( x < 0 || x >= getBlockCount() ) return 0; + return root->child( x+1 ); +} + +int NifModel::getBlockCount() const +{ + return rowCount() - 2; +} + + +/* + * ancestor functions + */ + +void NifModel::insertAncestor( NifItem * parent, const QString & identifier, int at ) +{ + NifBlock * ancestor = blocks.value( identifier ); + if ( ancestor ) + { + if ( ! ancestor->ancestor.isEmpty() ) + insertAncestor( parent, ancestor->ancestor ); + //parent->insertChild( NifData( identifier, "Abstract" ) ); + parent->prepareInsert( ancestor->types.count() ); + foreach ( NifData data, ancestor->types ) + insertType( parent, data ); + } + else + msg( Message() << "unknown ancestor " << identifier ); +} + +bool NifModel::inherits( const QString & name, const QString & aunty ) +{ + if ( name == aunty ) return true; + NifBlock * type = blocks.value( name ); + if ( type && ( type->ancestor == aunty || inherits( type->ancestor, aunty ) ) ) + return true; + return false; +} + +bool NifModel::inherits( const QModelIndex & idx, const QString & aunty ) const +{ + int x = getBlockNumber( idx ); + if ( x < 0 ) + return false; + return inherits( itemName( index( x+1, 0 ) ), aunty ); +} + + +/* + * basic and compound type functions + */ + +void NifModel::insertType( const QModelIndex & parent, const NifData & data, int at ) +{ + NifItem * item = static_cast( parent.internalPointer() ); + if ( parent.isValid() && item && item != root ) + insertType( item, data, at ); +} + +void NifModel::insertType( NifItem * parent, const NifData & data, int at ) +{ + if ( ! data.arr1().isEmpty() ) + { + NifItem * array = insertBranch( parent, data, at ); + + if ( evalCondition( array ) ) + updateArrayItem( array, true ); + return; + } + + NifBlock * compound = compounds.value( data.type() ); + if ( compound ) + { + NifItem * branch = insertBranch( parent, data, at ); + branch->prepareInsert( compound->types.count() ); + foreach ( NifData d, compound->types ) + insertType( branch, d ); + } + else + { + if ( data.type() == "TEMPLATE" || data.temp() == "TEMPLATE" ) + { + QString tmp = parent->temp(); + NifItem * tItem = parent; + while ( tmp == "TEMPLATE" && tItem->parent() ) + { + tItem = tItem->parent(); + tmp = tItem->temp(); + } + NifData d( data ); + if ( d.type() == "TEMPLATE" ) + { + d.value.changeType( NifValue::type( tmp ) ); + d.setType( tmp ); + } + if ( d.temp() == "TEMPLATE" ) + d.setTemp( tmp ); + insertType( parent, d, at ); + } + else + parent->insertChild( data, at ); + } +} + + +/* + * item value functions + */ + +bool NifModel::setItemValue( NifItem * item, const NifValue & val ) +{ + item->value() = val; + emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); + if ( itemIsLink( item ) ) + { + NifItem * parent = item; + while ( parent->parent() && parent->parent() != root ) + parent = parent->parent(); + if ( parent != getFooterItem() ) + { + updateLinks(); + updateFooter(); + emit linksChanged(); + } + } + return true; +} + +/* + * QAbstractModel interface + */ + +QVariant NifModel::data( const QModelIndex & idx, int role ) const +{ + QModelIndex index = buddy( idx ); + + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) + return QVariant(); + + int column = index.column(); + + if ( column == ValueCol && item->parent() == root && item->type() == "NiBlock" ) + { + QModelIndex buddy; + if ( item->name() == "NiSourceTexture" || item->name() == "NiImage" ) + buddy = getIndex( index, "File Name" ); + else if ( item->name() == "NiStringExtraData" ) + buddy = getIndex( index, "String Data" ); + else + buddy = getIndex( index, "Name" ); + if ( buddy.isValid() ) + buddy = buddy.sibling( buddy.row(), index.column() ); + if ( buddy.isValid() ) + return data( buddy, role ); + } + else if ( column == ValueCol && item->parent() != root && item->type() == "ControllerLink" && role == Qt::DisplayRole ) + { + QModelIndex buddy; + if ( item->name() == "Controlled Blocks" ) + { + if (version >= 0x14010003) + { + buddy = getIndex( index, "Node Name" ); + if ( buddy.isValid() ) + buddy = buddy.sibling( buddy.row(), index.column() ); + if ( buddy.isValid() ) + return data( buddy, role ); + } + else if (version <= 0x14000005) + { + buddy = getIndex( index, "Node Name Offset" ); + if ( buddy.isValid() ) + buddy = buddy.sibling( buddy.row(), index.column() ); + if ( buddy.isValid() ) + return data( buddy, role ); + } + } + } + + + switch ( role ) + { + case Qt::DisplayRole: + { + switch ( column ) + { + case NameCol: + { + return item->name(); + } break; + case TypeCol: + { + if ( ! item->temp().isEmpty() ) + { + NifItem * i = item; + while ( i && i->temp() == "TEMPLATE" ) + i = i->parent(); + return QString( "%1<%2>" ).arg( item->type() ).arg( i ? i->temp() : QString() ); + } + else + { + return item->type(); + } + } break; + case ValueCol: + { + QString type = item->type(); + const NifValue& value = item->value(); + if (value.type() == NifValue::tString || value.type() == NifValue::tFilePath) + { + return QString(this->string(index)).replace("\n", " ").replace("\r", " "); + } + else if ( item->value().type() == NifValue::tStringOffset ) + { + int ofs = item->value().get(); + if ( ofs < 0 || ofs == 0x0000FFFF ) + return QString( "" ); + NifItem * palette = getItemX( item, "String Palette" ); + int link = ( palette ? palette->value().toLink() : -1 ); + if ( ( palette = getBlockItem( link ) ) && ( palette = getItem( palette, "Palette" ) ) ) + { + QByteArray bytes = palette->value().get(); + if ( ofs < bytes.count() ) + return QString( & bytes.data()[ofs] ); + else + return QString( "" ); + } + else + { + return QString( "" ); + } + } + else if ( item->value().type() == NifValue::tStringIndex ) + { + int idx = item->value().get(); + if (idx == -1) + return QString(); + NifItem * header = getHeaderItem(); + QModelIndex stringIndex = createIndex( header->row(), 0, header ); + QString string = get( this->index( idx, 0, getIndex( stringIndex, "Strings" ) ) ); + if ( idx >= 0 ) + return QString( "%2 [%1]").arg( idx ).arg( string ); + return QString( "%1 - " ).arg( idx ); + } + else if ( item->value().type() == NifValue::tBlockTypeIndex ) + { + int idx = item->value().get(); + int offset = idx & 0x7FFF; + NifItem * blocktypes = getItemX( item, "Block Types" ); + NifItem * blocktyp = ( blocktypes ? blocktypes->child( offset ) : 0 ); + if ( blocktyp ) + return QString( "%2 [%1]").arg( idx ).arg( blocktyp->value().get() ); + else + return QString( "%1 - " ).arg( idx ); + } + else if ( item->value().isLink() ) + { + int lnk = item->value().toLink(); + if ( lnk >= 0 ) + { + QModelIndex block = getBlock( lnk ); + if ( block.isValid() ) + { + QModelIndex block_name = getIndex( block, "Name" ); + if ( block_name.isValid() && ! get( block_name ).isEmpty() ) + return QString( "%1 (%2)" ).arg( lnk ).arg( get( block_name ) ); + else + return QString( "%1 [%2]" ).arg( lnk ).arg( itemName( block ) ); + } + else + { + return QString( "%1 " ).arg( lnk ); + } + } + else + return QString( "None" ); + } + else if ( item->value().isCount() ) + { + QString optId = NifValue::enumOptionName( item->type(), item->value().toCount() ); + if ( optId.isEmpty() ) + return item->value().toString(); + else + return QString( "%1" ).arg( optId ); + } + else + { + return item->value().toString().replace("\n", " ").replace("\r", " "); + } + } break; + case ArgCol: return item->arg(); + case Arr1Col: return item->arr1(); + case Arr2Col: return item->arr2(); + case CondCol: return item->cond(); + case Ver1Col: return version2string( item->ver1() ); + case Ver2Col: return version2string( item->ver2() ); + default: return QVariant(); + } + } + case Qt::DecorationRole: + { + switch ( column ) + { + case NameCol: + if ( itemType( index ) == "NiBlock" ) + return QString::number( getBlockNumber( index ) ); + default: + return QVariant(); + } + } + case Qt::EditRole: + { + switch ( column ) + { + case NameCol: return item->name(); + case TypeCol: return item->type(); + case ValueCol: + { + const NifValue& value = item->value(); + if (value.type() == NifValue::tString || value .type() == NifValue::tFilePath) + { + return string(index); + } + return item->value().toVariant(); + } + case ArgCol: return item->arg(); + case Arr1Col: return item->arr1(); + case Arr2Col: return item->arr2(); + case CondCol: return item->cond(); + case Ver1Col: return version2string( item->ver1() ); + case Ver2Col: return version2string( item->ver2() ); + default: return QVariant(); + } + } + case Qt::ToolTipRole: + { + QString tip; + switch ( column ) + { + case NameCol: + { + if ( item->parent() && ! item->parent()->arr1().isEmpty() ) + { + return QString( "array index: %1" ).arg( item->row() ); + } + else + { + QString tip = QString( "

%1

%2

" ).arg( item->name() ).arg( item->text() ); + + if ( NifBlock * blk = blocks.value( item->name() ) ) + { + tip += "

Ancestors:

    "; + while ( blocks.contains( blk->ancestor ) ) + { + tip += QString( "
  • %1
  • " ).arg( blk->ancestor ); + blk = blocks.value( blk->ancestor ); + } + tip += "

"; + } + return tip; + } + } break; + case TypeCol: + return NifValue::typeDescription( item->type() ); + case ValueCol: + { + switch ( item->value().type() ) + { + case NifValue::tByte: + case NifValue::tWord: + case NifValue::tShort: + case NifValue::tBool: + case NifValue::tInt: + case NifValue::tUInt: + { + return QString( "dec: %1\nhex: 0x%2" ) + .arg( item->value().toString() ) + .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); + } + case NifValue::tFloat: + { + return QString( "float: %1\nhex: 0x%2" ) + .arg( NumOrMinMax( item->value().toFloat(), 'g', 8 ) ) + .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); + } + case NifValue::tFlags: + { + quint16 f = item->value().toCount(); + return QString( "dec: %1\nhex: 0x%2\nbin: 0b%3" ) + .arg( f ) + .arg( f, 4, 16, QChar( '0' ) ) + .arg( f, 16, 2, QChar( '0' ) ); + } + case NifValue::tStringIndex: + return QString( "0x%1" ) + .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); + case NifValue::tStringOffset: + return QString( "0x%1" ) + .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); + case NifValue::tVector3: + return item->value().get().toHtml(); + case NifValue::tMatrix: + return item->value().get().toHtml(); + case NifValue::tMatrix4: + return item->value().get().toHtml(); + case NifValue::tQuat: + case NifValue::tQuatXYZW: + return item->value().get().toHtml(); + case NifValue::tColor3: + { + Color3 c = item->value().get(); + return QString( "R %1\nG %2\nB %3" ) + .arg( c[0] ) + .arg( c[1] ) + .arg( c[2] ); + } + case NifValue::tColor4: + { + Color4 c = item->value().get(); + return QString( "R %1\nG %2\nB %3\nA %4" ) + .arg( c[0] ) + .arg( c[1] ) + .arg( c[2] ) + .arg( c[3] ); + } + default: + break; + } + } break; + default: + break; + } + } return QVariant(); + case Qt::BackgroundColorRole: + { + if ( column == ValueCol && item->value().isColor() ) + { + return item->value().toColor(); + } + } return QVariant(); + case Qt::UserRole: + { + if ( column == ValueCol ) + { + Spell * spell = SpellBook::instant( this, index ); + if ( spell ) + return spell->page() + "/" + spell->name(); + } + } return QVariant(); + default: + return QVariant(); + } +} + +bool NifModel::setData( const QModelIndex & index, const QVariant & value, int role ) +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && role == Qt::EditRole && index.model() == this && item ) ) + return false; + + // buddy lookup + if ( index.column() == ValueCol && item->parent() == root && item->type() == "NiBlock" ) + { + QModelIndex buddy; + if ( item->name() == "NiSourceTexture" || item->name() == "NiImage" ) + buddy = getIndex( index, "File Name" ); + else if ( item->name() == "NiStringExtraData" ) + buddy = getIndex( index, "String Data" ); + else + buddy = getIndex( index, "Name" ); + if ( buddy.isValid() ) + buddy = buddy.sibling( buddy.row(), index.column() ); + if ( buddy.isValid() ) + return setData( buddy, value, role ); + } + + switch ( index.column() ) + { + case NifModel::NameCol: + item->setName( value.toString() ); + if ( item->parent() && item->parent() == root ) + updateHeader(); + break; + case NifModel::TypeCol: + item->setType( value.toString() ); + break; + case NifModel::ValueCol: + { + QString type = item->type(); + NifValue& val = item->value(); + if (val.type() == NifValue::tString || val.type() == NifValue::tFilePath) + { + val.changeType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); + assignString(index, value.toString(), true); + } + else + { + item->value().fromVariant( value ); + if ( isLink( index ) && getBlockOrHeader( index ) != getFooter() ) + { + updateLinks(); + updateFooter(); + emit linksChanged(); + } + } + } break; + case NifModel::ArgCol: + item->setArg( value.toString() ); + break; + case NifModel::Arr1Col: + item->setArr1( value.toString() ); + break; + case NifModel::Arr2Col: + item->setArr2( value.toString() ); + break; + case NifModel::CondCol: + item->setCond( value.toString() ); + break; + case NifModel::Ver1Col: + item->setVer1( NifModel::version2number( value.toString() ) ); + break; + case NifModel::Ver2Col: + item->setVer2( NifModel::version2number( value.toString() ) ); + break; + default: + return false; + } + + // reverse buddy lookup + if ( index.column() == ValueCol ) + { + if ( item->name() == "File Name" ) + { + NifItem * parent = item->parent(); + if ( parent && ( parent->name() == "Texture Source" || parent->name() == "NiImage" ) ) + { + parent = parent->parent(); + if ( parent && parent->type() == "NiBlock" && parent->name() == "NiSourceTexture" ) + emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); + } + } + else if ( item->name() == "Name" ) + { + NifItem * parent = item->parent(); + if ( parent && parent->type() == "NiBlock" ) + emit dataChanged( createIndex( parent->row(), ValueCol, parent ), createIndex( parent->row(), ValueCol, parent ) ); + } + } + + // update original index + emit dataChanged( index, index ); + + return true; +} + +void NifModel::reset() +{ + updateLinks(); + BaseModel::reset(); +} + +bool NifModel::removeRows( int row, int count, const QModelIndex & parent ) +{ + NifItem * item = static_cast( parent.internalPointer() ); + if ( ! ( parent.isValid() && parent.model() == this && item ) ) + return false; + + if ( row >= 0 && row+count < item->childCount() ) + { + bool link = false; + for ( int r = row; r < row + count; r++ ) + link |= itemIsLink( item->child( r ) ); + + beginRemoveRows( parent, row, row+count ); + item->removeChildren( row, count ); + endRemoveRows(); + + if ( link ) + { + updateLinks(); + updateFooter(); + emit linksChanged(); + } + return true; + } + else + return false; +} + + +/* + * load and save + */ + +bool NifModel::setHeaderString( const QString & s ) +{ + //msg( DbgMsg() << s ); + if ( ! ( s.startsWith( "NetImmerse File Format" ) || s.startsWith( "Gamebryo" ) ) ) + { + msg( Message() << "this is not a NIF" ); + return false; + } + + int p = s.indexOf( "Version", 0, Qt::CaseInsensitive ); + if ( p >= 0 ) + { + QString v = s; + + v.remove( 0, p + 8 ); + + for ( int i = 0; i < v.length(); i++ ) + { + if ( v[i].isDigit() || v[i] == QChar( '.' ) ) + continue; + else + { + v = v.left( i ); + } + } + + version = version2number( v ); + + if ( ! isVersionSupported( version ) ) + { + msg( Message() << "version" << version2string( version ) << "(" << v << ")" << "is not supported yet" ); + return false; + } + + return true; + } + else + { + msg( Message() << "invalid header string" ); + return false; + } +} + +bool NifModel::load( QIODevice & device ) +{ + clear(); + + NifIStream stream( this, &device ); + + // read header + NifItem * header = getHeaderItem(); + if ( !header || !load( header, stream, true ) ) + { + msg( Message() << "failed to load file header (version" << version << ")" ); + return false; + } + + int numblocks = get( header, "Num Blocks" ); + //qDebug( "numblocks %i", numblocks ); + + emit sigProgress( 0, numblocks ); + QTime t = QTime::currentTime(); + + try + { + qint64 curpos = device.pos(); + if ( version >= 0x0303000d ) + { + // read in the NiBlocks + QString prevblktyp; + for ( int c = 0; c < numblocks; c++ ) + { + emit sigProgress( c + 1, numblocks ); + + if ( device.atEnd() ) + throw QString( "unexpected EOF during load" ); + + QString blktyp; + quint32 size = UINT_MAX; + try + { + if ( version >= 0x0a000000 ) + { + // block types are stored in the header for versions above 10.x.x.x + // the upper bit or the blocktypeindex seems to be related to PhysX + int blktypidx = get( index( c, 0, getIndex( createIndex( header->row(), 0, header ), "Block Type Index" ) ) ); + blktyp = get( index( blktypidx & 0x7FFF, 0, getIndex( createIndex( header->row(), 0, header ), "Block Types" ) ) ); + + if ( version < 0x0a020000 ) + device.read( 4 ); + + // for version 20.2.0.? and above the block size is stored in the header + if (version >= 0x14020000) + size = get( index( c, 0, getIndex( createIndex( header->row(), 0, header ), "Block Size" ) ) ); + } + else + { + int len; + device.read( (char *) &len, 4 ); + if ( len < 2 || len > 80 ) + throw QString( "next block does not start with a NiString" ); + blktyp = device.read( len ); + } + + if ( isNiBlock( blktyp ) ) + { + //msg( DbgMsg() << "loading block" << c << ":" << blktyp ); + insertNiBlock( blktyp, -1, true ); + if ( ! load( root->child( c+1 ), stream, true ) ) + { + NifItem * child = root->child( c ); + throw QString( "failed to load block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( child ? child->name() : prevblktyp ); + } + } + else + throw QString( "encountered unknown block (%1)" ).arg( blktyp ); + } + catch ( QString err ) + { + // version 20.3.0.3 can mostly recover from some failures because it store block sizes + if (size == UINT_MAX) + throw err; + } + // Check device position and emit warning if location is not expected + if (size != UINT_MAX) + { + qint64 pos = device.pos(); + if ((curpos + size) != pos) + { + // unable to seek to location... abort + if (device.seek(curpos + size)) + msg( Message() << tr("device position incorrect after block number %1 (%2) at %3 ended at %4 expected %5").arg( c ).arg( blktyp ).arg(curpos).arg(pos).arg(curpos+size).toAscii() ); + else + throw QString( "failed to reposition device at block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( root->child( c )->name() ); + + curpos = device.pos(); + } + else + { + curpos = pos; + } + } + prevblktyp = blktyp; + } + + // read in the footer + if ( !load( getFooterItem(), stream, true ) ) + throw QString( "failed to load file footer" ); + } + else + { // versions below 3.3.0.13 + QMap linkMap; + + try { + for ( qint32 c = 0; true; c++ ) + { + emit sigProgress( c + 1, 0 ); + + if ( device.atEnd() ) + throw QString( "unexpected EOF during load" ); + + int len; + device.read( (char *) &len, 4 ); + if ( len < 0 || len > 80 ) + throw QString( "next block does not start with a NiString" ); + + QString blktyp = device.read( len ); + + if ( blktyp == "End Of File" ) + { + break; + } + else if ( blktyp == "Top Level Object" ) + { + device.read( (char *) &len, 4 ); + if ( len < 0 || len > 80 ) + throw QString( "next block does not start with a NiString" ); + blktyp = device.read( len ); + } + + qint32 p; + device.read( (char *) &p, 4 ); + p -= 1; + if ( p != c ) + linkMap.insert( p, c ); + + if ( isNiBlock( blktyp ) ) + { + //msg( DbgMsg() << "loading block" << c << ":" << blktyp ); + insertNiBlock( blktyp, -1, true ); + if ( ! load( root->child( c+1 ), stream, true ) ) + throw QString( "failed to load block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( root->child( c )->name() ); + } + else + throw QString( "encountered unknown block (%1)" ).arg( blktyp ); + } + } + catch ( QString err ) { + //If this is an old file we should still map the links, even if it failed + mapLinks( linkMap ); + + //Re-throw exception so that the error is printed + throw err; + } + + //Also map links if nothing went wrong + mapLinks( linkMap ); + } + } + catch ( QString err ) + { + msg( Message() << err.toAscii().constData() ); + reset(); + return false; + } + //msg( Message() << t.msecsTo( QTime::currentTime() ) ); + reset(); // notify model views that a significant change to the data structure has occurded + return true; +} + +bool NifModel::save( QIODevice & device ) const +{ + NifOStream stream( this, &device ); + + emit sigProgress( 0, rowCount( QModelIndex() ) ); + + for ( int c = 0; c < rowCount( QModelIndex() ); c++ ) + { + emit sigProgress( c+1, rowCount( QModelIndex() ) ); + + //msg( DbgMsg() << "saving block" << c << ":" << itemName( index( c, 0 ) ) ); + if ( itemType( index( c, 0 ) ) == "NiBlock" ) + { + if ( version > 0x0a000000 ) + { + if ( version < 0x0a020000 ) + { + int null = 0; + device.write( (char *) &null, 4 ); + } + } + else + { + if ( version < 0x0303000d ) + { + if ( rootLinks.contains( c - 1 ) ) + { + QString string = "Top Level Object"; + int len = string.length(); + device.write( (char *) &len, 4 ); + device.write( (const char *) string.toAscii(), len ); + } + } + + QString string = itemName( index( c, 0 ) ); + int len = string.length(); + device.write( (char *) &len, 4 ); + device.write( (const char *) string.toAscii(), len ); + + if ( version < 0x0303000d ) + { + device.write( (char *) &c, 4 ); + } + } + } + if ( !save( root->child( c ), stream ) ) + { + msg( Message() << "failed to write block" << itemName( index( c, 0 ) ) << "(" << c-1 << ")" ); + return false; + } + } + + if ( version < 0x0303000d ) + { + QString string = "End Of File"; + int len = string.length(); + device.write( (char *) &len, 4 ); + device.write( (const char *) string.toAscii(), len ); + } + + return true; +} + +bool NifModel::load( QIODevice & device, const QModelIndex & index ) +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( item && index.isValid() && index.model() == this ) + { + NifIStream stream( this, &device ); + bool ok = load( item, stream, false ); + updateLinks(); + updateFooter(); + emit linksChanged(); + return ok; + } + reset(); + return false; +} + +bool NifModel::loadAndMapLinks( QIODevice & device, const QModelIndex & index, const QMap & map ) +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( item && index.isValid() && index.model() == this ) + { + NifIStream stream( this, & device ); + bool ok = load( item, stream, false ); + mapLinks( item, map ); + updateLinks(); + updateFooter(); + emit linksChanged(); + return ok; + } + reset(); + return false; +} + +bool NifModel::loadHeaderOnly( const QString & fname ) +{ + clear(); + + QFile f( fname ); + if ( ! f.open( QIODevice::ReadOnly ) ) + { + msg( Message() << "failed to open file" << fname ); + return false; + } + + NifIStream stream( this, &f ); + + // read header + NifItem * header = getHeaderItem(); + if ( !header || !load( header, stream, true ) ) + { + msg( Message() << "failed to load file header (version" << version << ")" ); + return false; + } + + return true; +} + +bool NifModel::earlyRejection( const QString & filepath, const QString & blockId, quint32 version ) +{ + NifModel nif; + if ( nif.loadHeaderOnly( filepath ) == false ) { + //File failed to read entierly + return false; + } + + bool ver_match = false; + if ( version == 0 ) + { + ver_match = true; + } + else if ( version != 0 && nif.getVersionNumber() == version ) + { + ver_match = true; + } + + bool blk_match = false; + if ( blockId.isEmpty() == true || version < 0x0A000100 ) + { + blk_match = true; + } + else + { + foreach ( QString s, nif.getArray( nif.getHeader(), "Block Types" ) ) + { + if ( inherits( s, blockId ) ) + { + blk_match = true; + break; + } + } + } + + return (ver_match && blk_match); +} + +bool NifModel::save( QIODevice & device, const QModelIndex & index ) const +{ + NifOStream stream( this, &device ); + NifItem * item = static_cast( index.internalPointer() ); + return ( item && index.isValid() && index.model() == this && save( item, stream ) ); +} + +int NifModel::fileOffset( const QModelIndex & index ) const +{ + NifSStream stream( this ); + NifItem * target = static_cast( index.internalPointer() ); + if ( target && index.isValid() && index.model() == this ) + { + int ofs = 0; + for ( int c = 0; c < root->childCount(); c++ ) + { + if ( c > 0 && c <= getBlockCount() ) + { + if ( version > 0x0a000000 ) + { + if ( version < 0x0a020000 ) + { + ofs += 4; + } + } + else + { + if ( version < 0x0303000d ) + { + if ( rootLinks.contains( c - 1 ) ) + { + QString string = "Top Level Object"; + ofs += 4 + string.length(); + } + } + + QString string = itemName( this->NifModel::index( c, 0 ) ); + ofs += 4 + string.length(); + + if ( version < 0x0303000d ) + { + ofs += 4; + } + } + } + + if ( fileOffset( root->child( c ), target, stream, ofs ) ) + return ofs; + } + } + return -1; +} + +bool NifModel::load( NifItem * parent, NifIStream & stream, bool fast ) +{ + if ( ! parent ) return false; + + for ( int row = 0; row < parent->childCount(); row++ ) + { + NifItem * child = parent->child( row ); + + if ( evalCondition( child ) ) + { + if ( ! child->arr1().isEmpty() ) + { + if ( ! updateArrayItem( child, fast ) ) + return false; + + if ( ! load( child, stream, fast ) ) + return false; + } + else if ( child->childCount() > 0 ) + { + if ( ! load( child, stream, fast ) ) + return false; + } + else + { + if ( ! stream.read( child->value() ) ) + return false; + } + } + } + return true; +} + +bool NifModel::save( NifItem * parent, NifOStream & stream ) const +{ + if ( ! parent ) return false; + + for ( int row = 0; row < parent->childCount(); row++ ) + { + NifItem * child = parent->child( row ); + if ( evalCondition( child ) ) + { + if ( ! child->arr1().isEmpty() || ! child->arr2().isEmpty() || child->childCount() > 0 ) + { + if ( ! child->arr1().isEmpty() && child->childCount() != getArraySize( child ) ) + msg( Message() << "block" << getBlockNumber( parent ) << child->name() << "array size mismatch" ); + + if ( !save( child, stream ) ) + return false; + } + else + { + if ( ! stream.write( child->value() ) ) + return false; + } + } + } + return true; +} + +bool NifModel::fileOffset( NifItem * parent, NifItem * target, NifSStream & stream, int & ofs ) const +{ + if ( parent == target ) + return true; + + for ( int row = 0; row < parent->childCount(); row++ ) + { + NifItem * child = parent->child( row ); + if ( child == target ) + return true; + + if ( evalCondition( child ) ) + { + if ( ! child->arr1().isEmpty() || ! child->arr2().isEmpty() || child->childCount() > 0 ) + { + if ( fileOffset( child, target, stream, ofs ) ) + return true; + } + else + { + ofs += stream.size( child->value() ); + } + } + } + return false; +} + +NifItem * NifModel::insertBranch( NifItem * parentItem, const NifData & data, int at ) +{ + NifItem * item = parentItem->insertChild( data, at ); + item->value().changeType( NifValue::tNone ); + return item; +} + +/* + * link functions + */ + +void NifModel::updateLinks( int block ) +{ + if ( block >= 0 ) + { + childLinks[ block ].clear(); + parentLinks[ block ].clear(); + updateLinks( block, getBlockItem( block ) ); + } + else + { + rootLinks.clear(); + childLinks.clear(); + parentLinks.clear(); + + for ( int c = 0; c < getBlockCount(); c++ ) + updateLinks( c ); + + for ( int c = 0; c < getBlockCount(); c++ ) + { + QStack stack; + checkLinks( c, stack ); + } + + for ( int c = 0; c < getBlockCount(); c++ ) + { + bool isRoot = true; + for ( int d = 0; d < getBlockCount(); d++ ) + { + if ( c != d && childLinks.value( d ).contains( c ) ) + { + isRoot = false; + break; + } + } + if ( isRoot ) + rootLinks.append( c ); + } + } +} + +void NifModel::updateLinks( int block, NifItem * parent ) +{ + if ( ! parent ) return; + + for ( int r = 0; r < parent->childCount(); r++ ) + { + NifItem * child = parent->child( r ); + + bool ischild; + bool islink = itemIsLink( child, &ischild ); + + if ( child->childCount() > 0 ) + { + updateLinks( block, child ); + } + else if ( islink ) + { + int l = child->value().toLink(); + if ( l >= 0 && child->arr1().isEmpty() ) + { + if ( ischild ) + { + if ( !childLinks[block].contains( l ) ) childLinks[block].append( l ); + } + else + { + if ( !parentLinks[block].contains( l ) ) parentLinks[block].append( l ); + } + } + } + } +} + +void NifModel::checkLinks( int block, QStack & parents ) +{ + parents.push( block ); + foreach ( int child, childLinks.value( block ) ) + { + if ( parents.contains( child ) ) + { + msg( Message() << "infinite recursive link construct detected" << block << "->" << child ); + childLinks[block].removeAll( child ); + } + else + { + checkLinks( child, parents ); + } + } + parents.pop(); +} + +void NifModel::adjustLinks( NifItem * parent, int block, int delta ) +{ + if ( ! parent ) return; + + if ( parent->childCount() > 0 ) + { + for ( int c = 0; c < parent->childCount(); c++ ) + adjustLinks( parent->child( c ), block, delta ); + } + else + { + int l = parent->value().toLink(); + if ( l >= 0 && ( ( delta != 0 && l >= block ) || l == block ) ) + { + if ( delta == 0 ) + parent->value().setLink( -1 ); + else + parent->value().setLink( l + delta ); + } + } +} + +void NifModel::mapLinks( NifItem * parent, const QMap & map ) +{ + if ( ! parent ) return; + + if ( parent->childCount() > 0 ) + { + for ( int c = 0; c < parent->childCount(); c++ ) + mapLinks( parent->child( c ), map ); + } + else + { + int l = parent->value().toLink(); + if ( l >= 0 ) + { + if ( map.contains( l ) ) + parent->value().setLink( map[ l ] ); + } + } +} + +qint32 NifModel::getLink( const QModelIndex & index ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return -1; + return item->value().toLink(); +} + +qint32 NifModel::getLink( const QModelIndex & parent, const QString & name ) const +{ + NifItem * parentItem = static_cast( parent.internalPointer() ); + if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) + return -1; + + NifItem * item = getItem( parentItem, name ); + if ( item ) + return item->value().toLink(); + + return -1; +} + +QVector NifModel::getLinkArray( const QModelIndex & iArray ) const +{ + QVector links; + NifItem * item = static_cast( iArray.internalPointer() ); + if ( isArray( iArray ) && item && iArray.model() == this ) + { + for ( int c = 0; c < item->childCount(); c++ ) + { + if ( itemIsLink( item->child( c ) ) ) + { + links.append( item->child( c )->value().toLink() ); + } + else + { + links.clear(); + break; + } + } + } + return links; +} + +QVector NifModel::getLinkArray( const QModelIndex & parent, const QString & name ) const +{ + return getLinkArray( getIndex( parent, name ) ); +} + +bool NifModel::setLink( const QModelIndex & parent, const QString & name, qint32 l ) +{ + NifItem * parentItem = static_cast( parent.internalPointer() ); + if ( ! ( parent.isValid() && parentItem && parent.model() == this ) ) + return false; + + NifItem * item = getItem( parentItem, name ); + if ( item && item->value().setLink( l ) ) + { + emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); + NifItem * parent = item; + while ( parent->parent() && parent->parent() != root ) + parent = parent->parent(); + if ( parent != getFooterItem() ) + { + updateLinks(); + updateFooter(); + emit linksChanged(); + } + return true; + } + else + return false; +} + +bool NifModel::setLink( const QModelIndex & index, qint32 l ) +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) + return false; + + if ( item && item->value().setLink( l ) ) + { + emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); + NifItem * parent = item; + while ( parent->parent() && parent->parent() != root ) + parent = parent->parent(); + if ( parent != getFooterItem() ) + { + updateLinks(); + updateFooter(); + emit linksChanged(); + } + return true; + } + else + return false; +} + +bool NifModel::setLinkArray( const QModelIndex & iArray, const QVector & links ) +{ + NifItem * item = static_cast( iArray.internalPointer() ); + if ( isArray( iArray ) && item && iArray.model() == this ) + { + bool ret = true; + for ( int c = 0; c < item->childCount() && c < links.count(); c++ ) + { + ret &= item->child( c )->value().setLink( links[c] ); + } + ret &= item->childCount() == links.count(); + int x = item->childCount() - 1; + if ( x >= 0 ) + emit dataChanged( createIndex( 0, ValueCol, item->child( 0 ) ), createIndex( x, ValueCol, item->child( x ) ) ); + NifItem * parent = item; + while ( parent->parent() && parent->parent() != root ) + parent = parent->parent(); + if ( parent != getFooterItem() ) + { + updateLinks(); + updateFooter(); + emit linksChanged(); + } + return ret; + } + return false; +} + +bool NifModel::setLinkArray( const QModelIndex & parent, const QString & name, const QVector & links ) +{ + return setLinkArray( getIndex( parent, name ), links ); +} + +bool NifModel::isLink( const QModelIndex & index, bool * isChildLink ) const +{ + NifItem * item = static_cast( index.internalPointer() ); + if ( ! ( index.isValid() && item && index.model() == this ) ) return false; + return itemIsLink( item, isChildLink ); +} + +int NifModel::getParent( int block ) const +{ + int parent = -1; + for ( int b = 0; b < getBlockCount(); b++ ) + { + if ( childLinks.value( b ).contains( block ) ) + { + if ( parent < 0 ) + parent = b; + else + return -1; + } + } + return parent; +} + +QString NifModel::string( const QModelIndex & index, bool extraInfo ) const +{ + NifValue v = getValue( index ); + if (v.type() == NifValue::tSizedString) + return BaseModel::get( index ); + + if (getVersionNumber() >= 0x14010003) + { + QModelIndex iIndex; + int idx = -1; + if (v.type() == NifValue::tStringIndex) + idx = get( index ); + else if (!v.isValid()) + idx = get( getIndex( index, "Index" ) ); + if (idx == -1) + { + return QString(); + } + else if ( idx >= 0 ) + { + NifItem * header = this->getHeaderItem(); + QModelIndex stringIndex = createIndex( header->row(), 0, header ); + if ( stringIndex.isValid() ) + { + QString string = BaseModel::get( this->index( idx, 0, getIndex( stringIndex, "Strings" ) ) ); + if (extraInfo) + string = QString( "%2 [%1]").arg( idx ).arg( string ); + return string; + } + else + { + return QString( "%1 - " ).arg( idx ); + } + } + } + else + { + if (v.type() == NifValue::tNone) + { + QModelIndex iIndex = getIndex( index, "String" ); + if (iIndex.isValid()) + return BaseModel::get( iIndex ); + } + else + { + return BaseModel::get( index ); + } + } + return QString(); +} + +QString NifModel::string( const QModelIndex & index, const QString & name, bool extraInfo ) const +{ + return string( getIndex(index, name), extraInfo ); +} + + +bool NifModel::assignString( const QModelIndex & index, const QString & string, bool replace) +{ + NifValue v = getValue( index ); + + if (getVersionNumber() >= 0x14010003) + { + QModelIndex iIndex; + int idx = -1; + if (v.type() == NifValue::tStringIndex) + idx = get( iIndex = index ); + else if (!v.isValid()) + idx = get( iIndex = getIndex( index, "Index" ) ); + else + return BaseModel::set( index, string ); + + QModelIndex header = getHeader(); + int nstrings = get( header, "Num Strings" ); + if ( string.isEmpty() ) + { + if (replace && idx >= 0 && idx < nstrings) + { + // TODO: Can we remove the string safely here? + } + return set( iIndex, 0xffffffff ); + } + // Simply replace the string + QModelIndex iArray = getIndex( header, "Strings" ); + if (replace && idx >= 0 && idx < nstrings) + { + return BaseModel::set(iArray.child(idx, 0), string); + } + + QVector stringVector = getArray( iArray ); + idx = stringVector.indexOf(string); + // Already exists. Just update the Index + if (idx >= 0 && idx < stringVector.size()) + { + return set( iIndex, idx ); + } + else // append string to end of list + { + set( header, "Num Strings", nstrings+1); + updateArray(header, "Strings"); + QModelIndex iArray = getIndex( header, "Strings" ); + BaseModel::set(iArray.child(nstrings, 0), string); + + return set( iIndex, nstrings ); + } + } + else // handle the older simpler strings + { + if (v.type() == NifValue::tNone) + { + return BaseModel::set( getIndex( index, "String" ), string ); + } + else + { + return BaseModel::set( index, string ); + } + } + return false; +} + +bool NifModel::assignString( const QModelIndex & index, const QString & name, const QString & string, bool replace ) +{ + return assignString( getIndex(index, name), string, replace ); +} + diff --git a/nifmodel.h b/nifmodel.h index 4259d7096..b086e1dda 100644 --- a/nifmodel.h +++ b/nifmodel.h @@ -1,373 +1,373 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef NIFMODEL_H -#define NIFMODEL_H - -#include "basemodel.h" - -#include -#include -#include -#include - -class NifModel : public BaseModel -{ -Q_OBJECT -public: - NifModel( QObject * parent = 0 ); - - // call this once on startup to load the XML descriptions - static bool loadXML(); - - // when creating NifModels from outside the main thread protect them with a QReadLocker (see the XML check spell for an example) - static QReadWriteLock XMLlock; - - // clear model data - void clear(); - - // generic load and save to and from QIODevice - bool load( QIODevice & device ); - bool save( QIODevice & device ) const; - - bool load( QIODevice & device, const QModelIndex & ); - bool save( QIODevice & device, const QModelIndex & ) const; - - bool loadAndMapLinks( QIODevice & device, const QModelIndex &, const QMap & map ); - bool loadHeaderOnly( const QString & fname ); - - // this returns the the estimated file offset of the model index - int fileOffset( const QModelIndex & ) const; - - // checks if the nif pointed to by filepath contains the specified block id in its header and is of the specified version - // will not open the full file to look for block types - static bool earlyRejection( const QString & filepath, const QString & blockId, quint32 version ); - - // returns the model index of the NiHeader - QModelIndex getHeader() const; - // this updates the header infos ( num blocks etc. ) - void updateHeader(); - - QModelIndex getFooter() const; - void updateFooter(); - - // insert or append ( row == -1 ) a new NiBlock - QModelIndex insertNiBlock( const QString & identifier, int row = -1, bool fast = false ); - // remove a block from the list - void removeNiBlock( int blocknum ); - // move a block in the list - void moveNiBlock( int src, int dst ); - // return the block name - QString getBlockName( const QModelIndex & ) const; - // return the block type - QString getBlockType( const QModelIndex & ) const; - // returns the block number - int getBlockNumber( const QModelIndex & ) const; - // returns the parent block ( optional: check if it is of type name ) - QModelIndex getBlock( const QModelIndex &, const QString & name = QString() ) const; - // returns the parent block/header - QModelIndex getBlockOrHeader( const QModelIndex & ) const; - // get the NiBlock at index x ( optional: check if it is of type name ) - QModelIndex getBlock( int x, const QString & name = QString() ) const; - // get the number of NiBlocks - int getBlockCount() const; - // returns true if the index is a niblock ( optional: check if it is the specified type of block ) - bool isNiBlock( const QModelIndex & index, const QString & name = QString() ) const; - // returns a list with all known NiXXX ids - static QStringList allNiBlocks(); - // is name a NiBlock identifier? - static bool isNiBlock( const QString & name ); - // reorders the blocks according to a list of new block numbers - void reorderBlocks( const QVector & order ); - // moves all niblocks from this nif to another nif, returns a map which maps old block numbers to new block numbers - QMap moveAllNiBlocks( NifModel * targetnif ); - - void insertType( const QModelIndex & parent, const NifData & data, int atRow ); - - // return the root blocks - QList getRootLinks() const; - // return the list of block links - QList getChildLinks( int block ) const; - QList getParentLinks( int block ) const; - // return the parent block number or none (-1) if there is no parent or if there are multiple parents - int getParent( int block ) const; - - // is it a child or parent link? - bool isLink( const QModelIndex & index, bool * ischildLink = 0 ) const; - // this returns a block number if the index is a valid link - qint32 getLink( const QModelIndex & index ) const; - int getLink( const QModelIndex & parent, const QString & name ) const; - QVector getLinkArray( const QModelIndex & array ) const; - QVector getLinkArray( const QModelIndex & parent, const QString & name ) const; - bool setLink( const QModelIndex & index, qint32 l ); - bool setLink( const QModelIndex & parent, const QString & name, qint32 l ); - bool setLinkArray( const QModelIndex & array, const QVector & links ); - bool setLinkArray( const QModelIndex & parent, const QString & name, const QVector & links ); - - void mapLinks( const QMap & map ); - - // is it a compound type? - static bool isCompound( const QString & name ); - // is name an ancestor identifier? - static bool isAncestor( const QString & name ); - // returns true if name inherits ancestor - static bool inherits( const QString & name, const QString & ancestor ); - // returns true if the block containing index inherits ancestor - bool inherits( const QModelIndex & index, const QString & ancestor ) const; - - // is this version supported ? - static bool isVersionSupported( quint32 ); - - // version conversion - static QString version2string( quint32 ); - static quint32 version2number( const QString & ); - - // check wether the current nif file version lies in the range since~until - bool checkVersion( quint32 since, quint32 until ) const; - - QString getVersion() const { return version2string( version ); } - quint32 getVersionNumber() const { return version; } - quint32 getUserVersion() const { return get(getHeader(), "User Version"); } - - // QAbstractModel interface - QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; - bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ); - void reset(); - - // removes an item from the model - bool removeRows( int row, int count, const QModelIndex & parent ); - - QString string( const QModelIndex & index, bool extraInfo = false ) const; - QString string( const QModelIndex & index, const QString & name, bool extraInfo = false ) const; - - bool assignString( const QModelIndex & index, const QString & string, bool replace = false ); - bool assignString( const QModelIndex & index, const QString & name, const QString & string, bool replace = false ); - - - // BaseModel Overrides - template T get( const QModelIndex & index ) const; - template bool set( const QModelIndex & index, const T & d ); - - template T get( const QModelIndex & parent, const QString & name ) const; - template bool set( const QModelIndex & parent, const QString & name, const T & v ); - - - static QAbstractItemDelegate * createDelegate( class SpellBook * ); - -signals: - void linksChanged(); - -protected: - void insertAncestor( NifItem * parent, const QString & identifier, int row = -1 ); - void insertType( NifItem * parent, const NifData & data, int row = -1 ); - NifItem * insertBranch( NifItem * parent, const NifData & data, int row = -1 ); - - bool updateArrayItem( NifItem * array, bool fast ); - - NifItem * getHeaderItem() const; - NifItem * getFooterItem() const; - NifItem * getBlockItem( int ) const; - NifItem * getItem( NifItem * parent, const QString & name ) const; - - bool load( NifItem * parent, NifIStream & stream, bool fast = true ); - bool save( NifItem * parent, NifOStream & stream ) const; - bool fileOffset( NifItem * parent, NifItem * target, NifSStream & stream, int & ofs ) const; - - bool setItemValue( NifItem * item, const NifValue & v ); - - bool itemIsLink( NifItem * item, bool * ischildLink = 0 ) const; - int getBlockNumber( NifItem * item ) const; - - bool setHeaderString( const QString & ); - - QString ver2str( quint32 v ) const { return version2string( v ); } - quint32 str2ver( QString s ) const { return version2number( s ); } - - bool evalVersion( NifItem * item, bool chkParents = false ) const; - - // nif file version - quint32 version; - - QHash< int, QList > childLinks; - QHash< int, QList > parentLinks; - QList< int > rootLinks; - - void updateLinks( int block = -1 ); - void updateLinks( int block, NifItem * parent ); - void checkLinks( int block, QStack & parents ); - void adjustLinks( NifItem * parent, int block, int delta ); - void mapLinks( NifItem * parent, const QMap & map ); - - // XML structures - static QList supportedVersions; - - static QHash compounds; - static QHash blocks; - - static QString parseXmlDescription( const QString & filename ); - - // Get and Set template overloads from base model - template T get( NifItem * parent, const QString & name ) const; - template T get( NifItem * item ) const; - template bool set( NifItem * parent, const QString & name, const T & d ); - template bool set( NifItem * item, const T & d ); - - friend class NifXmlHandler; -}; // class NifModel - - -inline QStringList NifModel::allNiBlocks() -{ - QStringList lst; - foreach ( NifBlock * blk, blocks ) - if ( ! blk->abstract ) - lst.append( blk->id ); - return lst; -} - -inline bool NifModel::isNiBlock( const QString & name ) -{ - NifBlock * blk = blocks.value( name ); - return blk && ! blk->abstract; -} - -inline bool NifModel::isAncestor( const QString & name ) -{ - NifBlock * blk = blocks.value( name ); - return blk && blk->abstract; -} - -inline bool NifModel::isCompound( const QString & name ) -{ - return compounds.contains( name ); -} - -inline bool NifModel::isVersionSupported( quint32 v ) -{ - return supportedVersions.contains( v ); -} - -inline QList NifModel::getRootLinks() const -{ - return rootLinks; -} - -inline QList NifModel::getChildLinks( int block ) const -{ - return childLinks.value( block ); -} - -inline QList NifModel::getParentLinks( int block ) const -{ - return parentLinks.value( block ); -} - -inline bool NifModel::itemIsLink( NifItem * item, bool * isChildLink ) const -{ - if ( isChildLink ) - *isChildLink = ( item->value().type() == NifValue::tLink ); - return item->value().isLink(); -} - -inline bool NifModel::checkVersion( quint32 since, quint32 until ) const -{ - return ( ( since == 0 || since <= version ) && ( until == 0 || version <= until ) ); -} - - -// Overrides for get and set templates. -template inline T NifModel::get( const QModelIndex & index ) const { - return BaseModel::get( index ); -} - -template inline T NifModel::get( NifItem * item ) const { - return BaseModel::get( item ); -} - -template inline T NifModel::get( NifItem * parent, const QString & name ) const { - return BaseModel::get(parent, name); -} - -template inline T NifModel::get( const QModelIndex & parent, const QString & name ) const { - return BaseModel::get(parent, name); -} - -template inline bool NifModel::set( const QModelIndex & index, const T & d ) { - return BaseModel::set( index, d ); -} - -template inline bool NifModel::set( NifItem * item, const T & d ) { - return BaseModel::set( item, d ); -} - -template inline bool NifModel::set( const QModelIndex & parent, const QString & name, const T & d ){ - return BaseModel::set(parent, name, d); -} - -template inline bool NifModel::set( NifItem * parent, const QString & name, const T & d ) { - return BaseModel::set(parent, name, d); -} - - -// QString overloads for the get and set templates -template <> inline QString NifModel::get( const QModelIndex & index ) const { - return this->string( index ); -} - -//template <> inline QString NifModel::get( NifItem * item ) const { -// return this->string( this->item ); -//} - -//template <> inline QString NifModel::get( NifItem * parent, const QString & name ) const { -// return this->string(parent, name); -//} - -template <> inline QString NifModel::get( const QModelIndex & parent, const QString & name ) const { - return this->string(parent, name); -} - -template <> inline bool NifModel::set( const QModelIndex & index, const QString & d ) { - return this->assignString( index, d ); -} - -//template <> inline bool NifModel::set( NifItem * item, const QString & d ) { -// return this->assignString( item, d ); -//} - -template <> inline bool NifModel::set( const QModelIndex & parent, const QString & name, const QString & d ){ - return this->assignString(parent, name, d); -} - -//template <> inline bool NifModel::set( NifItem * parent, const QString & name, const QString & d ) { -// return this->assignString(parent, name, d); -//} -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef NIFMODEL_H +#define NIFMODEL_H + +#include "basemodel.h" + +#include +#include +#include +#include + +class NifModel : public BaseModel +{ +Q_OBJECT +public: + NifModel( QObject * parent = 0 ); + + // call this once on startup to load the XML descriptions + static bool loadXML(); + + // when creating NifModels from outside the main thread protect them with a QReadLocker (see the XML check spell for an example) + static QReadWriteLock XMLlock; + + // clear model data + void clear(); + + // generic load and save to and from QIODevice + bool load( QIODevice & device ); + bool save( QIODevice & device ) const; + + bool load( QIODevice & device, const QModelIndex & ); + bool save( QIODevice & device, const QModelIndex & ) const; + + bool loadAndMapLinks( QIODevice & device, const QModelIndex &, const QMap & map ); + bool loadHeaderOnly( const QString & fname ); + + // this returns the the estimated file offset of the model index + int fileOffset( const QModelIndex & ) const; + + // checks if the nif pointed to by filepath contains the specified block id in its header and is of the specified version + // will not open the full file to look for block types + static bool earlyRejection( const QString & filepath, const QString & blockId, quint32 version ); + + // returns the model index of the NiHeader + QModelIndex getHeader() const; + // this updates the header infos ( num blocks etc. ) + void updateHeader(); + + QModelIndex getFooter() const; + void updateFooter(); + + // insert or append ( row == -1 ) a new NiBlock + QModelIndex insertNiBlock( const QString & identifier, int row = -1, bool fast = false ); + // remove a block from the list + void removeNiBlock( int blocknum ); + // move a block in the list + void moveNiBlock( int src, int dst ); + // return the block name + QString getBlockName( const QModelIndex & ) const; + // return the block type + QString getBlockType( const QModelIndex & ) const; + // returns the block number + int getBlockNumber( const QModelIndex & ) const; + // returns the parent block ( optional: check if it is of type name ) + QModelIndex getBlock( const QModelIndex &, const QString & name = QString() ) const; + // returns the parent block/header + QModelIndex getBlockOrHeader( const QModelIndex & ) const; + // get the NiBlock at index x ( optional: check if it is of type name ) + QModelIndex getBlock( int x, const QString & name = QString() ) const; + // get the number of NiBlocks + int getBlockCount() const; + // returns true if the index is a niblock ( optional: check if it is the specified type of block ) + bool isNiBlock( const QModelIndex & index, const QString & name = QString() ) const; + // returns a list with all known NiXXX ids + static QStringList allNiBlocks(); + // is name a NiBlock identifier? + static bool isNiBlock( const QString & name ); + // reorders the blocks according to a list of new block numbers + void reorderBlocks( const QVector & order ); + // moves all niblocks from this nif to another nif, returns a map which maps old block numbers to new block numbers + QMap moveAllNiBlocks( NifModel * targetnif ); + + void insertType( const QModelIndex & parent, const NifData & data, int atRow ); + + // return the root blocks + QList getRootLinks() const; + // return the list of block links + QList getChildLinks( int block ) const; + QList getParentLinks( int block ) const; + // return the parent block number or none (-1) if there is no parent or if there are multiple parents + int getParent( int block ) const; + + // is it a child or parent link? + bool isLink( const QModelIndex & index, bool * ischildLink = 0 ) const; + // this returns a block number if the index is a valid link + qint32 getLink( const QModelIndex & index ) const; + int getLink( const QModelIndex & parent, const QString & name ) const; + QVector getLinkArray( const QModelIndex & array ) const; + QVector getLinkArray( const QModelIndex & parent, const QString & name ) const; + bool setLink( const QModelIndex & index, qint32 l ); + bool setLink( const QModelIndex & parent, const QString & name, qint32 l ); + bool setLinkArray( const QModelIndex & array, const QVector & links ); + bool setLinkArray( const QModelIndex & parent, const QString & name, const QVector & links ); + + void mapLinks( const QMap & map ); + + // is it a compound type? + static bool isCompound( const QString & name ); + // is name an ancestor identifier? + static bool isAncestor( const QString & name ); + // returns true if name inherits ancestor + static bool inherits( const QString & name, const QString & ancestor ); + // returns true if the block containing index inherits ancestor + bool inherits( const QModelIndex & index, const QString & ancestor ) const; + + // is this version supported ? + static bool isVersionSupported( quint32 ); + + // version conversion + static QString version2string( quint32 ); + static quint32 version2number( const QString & ); + + // check wether the current nif file version lies in the range since~until + bool checkVersion( quint32 since, quint32 until ) const; + + QString getVersion() const { return version2string( version ); } + quint32 getVersionNumber() const { return version; } + quint32 getUserVersion() const { return get(getHeader(), "User Version"); } + + // QAbstractModel interface + QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const; + bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ); + void reset(); + + // removes an item from the model + bool removeRows( int row, int count, const QModelIndex & parent ); + + QString string( const QModelIndex & index, bool extraInfo = false ) const; + QString string( const QModelIndex & index, const QString & name, bool extraInfo = false ) const; + + bool assignString( const QModelIndex & index, const QString & string, bool replace = false ); + bool assignString( const QModelIndex & index, const QString & name, const QString & string, bool replace = false ); + + + // BaseModel Overrides + template T get( const QModelIndex & index ) const; + template bool set( const QModelIndex & index, const T & d ); + + template T get( const QModelIndex & parent, const QString & name ) const; + template bool set( const QModelIndex & parent, const QString & name, const T & v ); + + + static QAbstractItemDelegate * createDelegate( class SpellBook * ); + +signals: + void linksChanged(); + +protected: + void insertAncestor( NifItem * parent, const QString & identifier, int row = -1 ); + void insertType( NifItem * parent, const NifData & data, int row = -1 ); + NifItem * insertBranch( NifItem * parent, const NifData & data, int row = -1 ); + + bool updateArrayItem( NifItem * array, bool fast ); + + NifItem * getHeaderItem() const; + NifItem * getFooterItem() const; + NifItem * getBlockItem( int ) const; + NifItem * getItem( NifItem * parent, const QString & name ) const; + + bool load( NifItem * parent, NifIStream & stream, bool fast = true ); + bool save( NifItem * parent, NifOStream & stream ) const; + bool fileOffset( NifItem * parent, NifItem * target, NifSStream & stream, int & ofs ) const; + + bool setItemValue( NifItem * item, const NifValue & v ); + + bool itemIsLink( NifItem * item, bool * ischildLink = 0 ) const; + int getBlockNumber( NifItem * item ) const; + + bool setHeaderString( const QString & ); + + QString ver2str( quint32 v ) const { return version2string( v ); } + quint32 str2ver( QString s ) const { return version2number( s ); } + + bool evalVersion( NifItem * item, bool chkParents = false ) const; + + // nif file version + quint32 version; + + QHash< int, QList > childLinks; + QHash< int, QList > parentLinks; + QList< int > rootLinks; + + void updateLinks( int block = -1 ); + void updateLinks( int block, NifItem * parent ); + void checkLinks( int block, QStack & parents ); + void adjustLinks( NifItem * parent, int block, int delta ); + void mapLinks( NifItem * parent, const QMap & map ); + + // XML structures + static QList supportedVersions; + + static QHash compounds; + static QHash blocks; + + static QString parseXmlDescription( const QString & filename ); + + // Get and Set template overloads from base model + template T get( NifItem * parent, const QString & name ) const; + template T get( NifItem * item ) const; + template bool set( NifItem * parent, const QString & name, const T & d ); + template bool set( NifItem * item, const T & d ); + + friend class NifXmlHandler; +}; // class NifModel + + +inline QStringList NifModel::allNiBlocks() +{ + QStringList lst; + foreach ( NifBlock * blk, blocks ) + if ( ! blk->abstract ) + lst.append( blk->id ); + return lst; +} + +inline bool NifModel::isNiBlock( const QString & name ) +{ + NifBlock * blk = blocks.value( name ); + return blk && ! blk->abstract; +} + +inline bool NifModel::isAncestor( const QString & name ) +{ + NifBlock * blk = blocks.value( name ); + return blk && blk->abstract; +} + +inline bool NifModel::isCompound( const QString & name ) +{ + return compounds.contains( name ); +} + +inline bool NifModel::isVersionSupported( quint32 v ) +{ + return supportedVersions.contains( v ); +} + +inline QList NifModel::getRootLinks() const +{ + return rootLinks; +} + +inline QList NifModel::getChildLinks( int block ) const +{ + return childLinks.value( block ); +} + +inline QList NifModel::getParentLinks( int block ) const +{ + return parentLinks.value( block ); +} + +inline bool NifModel::itemIsLink( NifItem * item, bool * isChildLink ) const +{ + if ( isChildLink ) + *isChildLink = ( item->value().type() == NifValue::tLink ); + return item->value().isLink(); +} + +inline bool NifModel::checkVersion( quint32 since, quint32 until ) const +{ + return ( ( since == 0 || since <= version ) && ( until == 0 || version <= until ) ); +} + + +// Overrides for get and set templates. +template inline T NifModel::get( const QModelIndex & index ) const { + return BaseModel::get( index ); +} + +template inline T NifModel::get( NifItem * item ) const { + return BaseModel::get( item ); +} + +template inline T NifModel::get( NifItem * parent, const QString & name ) const { + return BaseModel::get(parent, name); +} + +template inline T NifModel::get( const QModelIndex & parent, const QString & name ) const { + return BaseModel::get(parent, name); +} + +template inline bool NifModel::set( const QModelIndex & index, const T & d ) { + return BaseModel::set( index, d ); +} + +template inline bool NifModel::set( NifItem * item, const T & d ) { + return BaseModel::set( item, d ); +} + +template inline bool NifModel::set( const QModelIndex & parent, const QString & name, const T & d ){ + return BaseModel::set(parent, name, d); +} + +template inline bool NifModel::set( NifItem * parent, const QString & name, const T & d ) { + return BaseModel::set(parent, name, d); +} + + +// QString overloads for the get and set templates +template <> inline QString NifModel::get( const QModelIndex & index ) const { + return this->string( index ); +} + +//template <> inline QString NifModel::get( NifItem * item ) const { +// return this->string( this->item ); +//} + +//template <> inline QString NifModel::get( NifItem * parent, const QString & name ) const { +// return this->string(parent, name); +//} + +template <> inline QString NifModel::get( const QModelIndex & parent, const QString & name ) const { + return this->string(parent, name); +} + +template <> inline bool NifModel::set( const QModelIndex & index, const QString & d ) { + return this->assignString( index, d ); +} + +//template <> inline bool NifModel::set( NifItem * item, const QString & d ) { +// return this->assignString( item, d ); +//} + +template <> inline bool NifModel::set( const QModelIndex & parent, const QString & name, const QString & d ){ + return this->assignString(parent, name, d); +} + +//template <> inline bool NifModel::set( NifItem * parent, const QString & name, const QString & d ) { +// return this->assignString(parent, name, d); +//} +#endif diff --git a/nifproxy.cpp b/nifproxy.cpp index df24ebc15..8d1c0ee24 100644 --- a/nifproxy.cpp +++ b/nifproxy.cpp @@ -1,536 +1,536 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "nifproxy.h" - -#include "nifmodel.h" - -#include -#include - -class NifProxyItem -{ -public: - NifProxyItem( int number, NifProxyItem * parent ) - { - blockNumber = number; - parentItem = parent; - } - ~NifProxyItem() - { - qDeleteAll( childItems ); - } - - NifProxyItem * getLink( int link ) - { - foreach ( NifProxyItem * item, childItems ) - { - if ( item->block() == link ) - return item; - } - return 0; - } - - int rowLink( int link ) - { - int row = 0; - foreach ( NifProxyItem * item, childItems ) - { - if ( item->block() == link ) - return row; - row++; - } - return -1; - } - - NifProxyItem * addLink( int link ) - { - NifProxyItem * child = getLink( link ); - if ( child ) - { - return child; - } - else - { - child = new NifProxyItem( link, this ); - childItems.append( child ); - return child; - } - } - - void delLink( int link ) - { - NifProxyItem * child = getLink( link ); - if ( child ) - { - childItems.removeAll( child ); - delete child; - } - } - - NifProxyItem * parent() const - { - return parentItem; - } - - NifProxyItem * child( int row ) - { - return childItems.value( row ); - } - - int childCount() - { - return childItems.count(); - } - - void killChildren() - { - qDeleteAll( childItems ); - childItems.clear(); - } - - int row() const - { - if ( parentItem ) - return parentItem->childItems.indexOf( const_cast(this) ); - return 0; - } - - inline int block() const - { - return blockNumber; - } - - QList parentBlocks() const - { - QList parents; - NifProxyItem * parent = parentItem; - while ( parent && parent->parentItem ) - { - parents.append( parent->blockNumber ); - parent = parent->parentItem; - } - return parents; - } - - QList childBlocks() const - { - QList blocks; - foreach ( NifProxyItem * item, childItems ) - blocks.append( item->block() ); - return blocks; - } - - NifProxyItem * findItem( int b, bool scanParents = true ) - { - if ( blockNumber == b ) return this; - - foreach ( NifProxyItem * child, childItems ) - { - if ( child->blockNumber == b ) - return child; - } - - foreach ( NifProxyItem * child, childItems ) - { - if ( NifProxyItem * x = child->findItem( b, false ) ) - return x; - } - - if ( parentItem && scanParents ) - { - NifProxyItem * root = parentItem; - while ( root && root->parentItem ) - root = root->parentItem; - if ( NifProxyItem * x = root->findItem( b, false ) ) - return x; - } - - return 0; - } - - void findAllItems( int b, QList & list ) - { - foreach ( NifProxyItem * item, childItems ) - { - item->findAllItems( b, list ); - } - if ( blockNumber == b ) - list.append( this ); - } - - int blockNumber; - NifProxyItem * parentItem; - QList childItems; -}; - -NifProxyModel::NifProxyModel( QObject * parent ) : QAbstractItemModel( parent ) -{ - root = new NifProxyItem( -1, 0 ); - nif = 0; -} - -NifProxyModel::~NifProxyModel() -{ - delete root; -} - -QAbstractItemModel * NifProxyModel::model() const -{ - return nif; -} - -void NifProxyModel::setModel( QAbstractItemModel * model ) -{ - if ( nif ) - { - disconnect(nif, SIGNAL(dataChanged(const QModelIndex &,const QModelIndex &)), - this, SLOT(xDataChanged(const QModelIndex &,const QModelIndex &))); - disconnect(nif, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), - this, SLOT(xHeaderDataChanged(Qt::Orientation,int,int))); - disconnect( nif, SIGNAL( rowsAboutToBeRemoved( const QModelIndex &, int, int ) ), this, SLOT( xRowsAboutToBeRemoved( const QModelIndex &, int, int ) ) ); - disconnect( nif, SIGNAL( linksChanged() ), this, SLOT( xLinksChanged() ) ); - - disconnect(nif, SIGNAL(modelReset()), this, SLOT(reset())); - disconnect(nif, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); - } - - nif = qobject_cast( model ); - - if ( nif ) - { - connect( nif, SIGNAL(dataChanged( const QModelIndex &, const QModelIndex & )), - this, SLOT(xDataChanged( const QModelIndex &, const QModelIndex & ))); - connect( nif, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), - this, SLOT(xHeaderDataChanged(Qt::Orientation,int,int))); - connect( nif, SIGNAL( linksChanged() ), this, SLOT( xLinksChanged() ) ); - connect( nif, SIGNAL( rowsAboutToBeRemoved( const QModelIndex &, int, int ) ), this, SLOT( xRowsAboutToBeRemoved( const QModelIndex &, int, int ) ) ); - connect( nif, SIGNAL(modelReset()), this, SLOT(reset())); - connect( nif, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); - } - - reset(); -} - -void NifProxyModel::reset() -{ - //qDebug() << "proxy reset"; - root->killChildren(); - updateRoot( true ); - QAbstractItemModel::reset(); -} - -void NifProxyModel::updateRoot( bool fast ) -{ - if ( ! ( nif && nif->getBlockCount() > 0 ) ) - { - if ( root->childCount() > 0 ) - { - if ( ! fast ) beginRemoveRows( QModelIndex(), 0, root->childCount() - 1 ); - root->killChildren(); - if ( ! fast ) endRemoveRows(); - } - return; - } - - //qDebug() << "proxy update top level"; - - foreach ( NifProxyItem * item, root->childItems ) - { - if ( ! nif->getRootLinks().contains( item->block() ) ) - { - int at = root->rowLink( item->block() ); - if ( ! fast ) beginRemoveRows( QModelIndex(), at, at ); - root->delLink( item->block() ); - if ( ! fast ) endRemoveRows(); - } - } - - foreach ( int l, nif->getRootLinks() ) - { - NifProxyItem * item = root->getLink( l ); - if ( ! item ) - { - if ( ! fast ) beginInsertRows( QModelIndex(), root->childCount(), root->childCount() ); - item = root->addLink( l ); - if ( ! fast ) endInsertRows(); - } - updateItem( item, fast ); - } -} - -void NifProxyModel::updateItem( NifProxyItem * item, bool fast ) -{ - QModelIndex index( createIndex( item->row(), 0, item ) ); - - QList parents( item->parentBlocks() ); - - foreach( int l, item->childBlocks() ) - { - if ( ! ( nif->getChildLinks( item->block() ).contains( l ) || nif->getParentLinks( item->block() ).contains( l ) ) ) - { - int at = item->rowLink( l ); - if ( ! fast ) beginRemoveRows( index, at, at ); - item->delLink( l ); - if ( ! fast ) endRemoveRows(); - } - } - foreach ( int l, nif->getChildLinks(item->block()) ) - { - NifProxyItem * child = item->getLink( l ); - if ( ! child ) - { - int at = item->childCount(); - if ( ! fast ) beginInsertRows( index, at, at ); - child = item->addLink( l ); - if ( ! fast ) endInsertRows(); - } - if ( ! parents.contains( child->block() ) ) - { - updateItem( child, fast ); - } - else - qWarning() << "infinite recursing link construct detected" << item->block() << "->" << child->block(); - } - foreach ( int l, nif->getParentLinks( item->block() ) ) - { - if ( ! item->getLink( l ) ) - { - int at = item->childCount(); - if ( ! fast ) beginInsertRows( index, at, at ); - item->addLink( l ); - if ( ! fast ) endInsertRows(); - } - } -} - -int NifProxyModel::rowCount( const QModelIndex & parent ) const -{ - NifProxyItem * parentItem; - - if ( ! ( parent.isValid() && parent.model() == this ) ) - parentItem = root; - else - parentItem = static_cast( parent.internalPointer() ); - - return ( parentItem ? parentItem->childCount() : 0 ); -} - -QModelIndex NifProxyModel::index( int row, int column, const QModelIndex & parent ) const -{ - NifProxyItem * parentItem; - - if ( ! ( parent.isValid() && parent.model() == this ) ) - parentItem = root; - else - parentItem = static_cast( parent.internalPointer() ); - - NifProxyItem * childItem = ( parentItem ? parentItem->child( row ) : 0 ); - if ( childItem ) - return createIndex( row, column, childItem ); - else - return QModelIndex(); -} - -QModelIndex NifProxyModel::parent( const QModelIndex & child ) const -{ - if ( ! ( child.isValid() && child.model() == this ) ) - return QModelIndex(); - - NifProxyItem * childItem = static_cast( child.internalPointer() ); - NifProxyItem * parentItem = childItem->parent(); - - if ( parentItem == root || ! parentItem ) - return QModelIndex(); - return createIndex( parentItem->row(), 0, parentItem ); -} - -QModelIndex NifProxyModel::mapTo( const QModelIndex & idx ) const -{ - if ( ! ( nif && idx.isValid() ) ) return QModelIndex(); - - if ( idx.model() != this ) - { - qDebug() << "NifProxyModel::mapTo() called with wrong model"; - return QModelIndex(); - } - NifProxyItem * item = static_cast( idx.internalPointer() ); - if ( ! item ) return QModelIndex(); - QModelIndex nifidx = nif->getBlock( item->block() ); - if ( nifidx.isValid() ) nifidx = nifidx.sibling( nifidx.row(), ( idx.column() ? NifModel::ValueCol : NifModel::NameCol ) ); - return nifidx; -} - -QModelIndex NifProxyModel::mapFrom( const QModelIndex & idx, const QModelIndex & ref ) const -{ - if ( ! ( nif && idx.isValid() ) ) return QModelIndex(); - if ( idx.model() != nif ) - { - qDebug() << "NifProxyModel::mapFrom() called with wrong model"; - return QModelIndex(); - } - int blockNumber = nif->getBlockNumber( idx ); - if ( blockNumber < 0 ) return QModelIndex(); - NifProxyItem * item = root; - if ( ref.isValid() ) - { - if ( ref.model() == this ) - item = static_cast( ref.internalPointer() ); - else - qDebug() << "NifProxyModel::mapFrom() called with wrong ref model"; - } - item = item->findItem( blockNumber ); - if ( item ) - return createIndex( item->row(), 0, item ); - return QModelIndex(); -} - -QList NifProxyModel::mapFrom( const QModelIndex & idx ) const -{ - QList indices; - - if ( !( nif && idx.isValid() && ( idx.column() == NifModel::NameCol || idx.column() == NifModel::ValueCol ) ) ) return indices; - if ( idx.model() != nif ) - { - qDebug() << "NifProxyModel::mapFrom() plural called with wrong model"; - return indices; - } - if ( idx.parent().isValid() ) - return indices; - - int blockNumber = nif->getBlockNumber( idx ); - if ( blockNumber < 0 ) - return indices; - - QList items; - root->findAllItems( blockNumber, items ); - foreach( NifProxyItem * item, items ) - indices.append( createIndex( item->row(), idx.column() != NifModel::NameCol ? 1 : 0, item ) ); - - return indices; -} - -Qt::ItemFlags NifProxyModel::flags( const QModelIndex & index ) const -{ - if ( !nif ) return 0; - return nif->flags( mapTo( index ) ); -} - -QVariant NifProxyModel::data( const QModelIndex & index, int role ) const -{ - if ( !( nif && index.isValid() ) ) return QVariant(); - return nif->data( mapTo( index ), role ); -} - -bool NifProxyModel::setData( const QModelIndex & index, const QVariant & v, int role ) -{ - if ( !( nif && index.isValid() ) ) return false; - return nif->setData( mapTo( index ), v, role ); -} - -QVariant NifProxyModel::headerData( int section, Qt::Orientation orient, int role ) const -{ - if ( !nif || section < 0 || section > 1 ) return QVariant(); - return nif->headerData( ( section ? NifModel::ValueCol : NifModel::NameCol ), orient, role ); -} - -/* - * proxy slots - */ - -void NifProxyModel::xHeaderDataChanged( Qt::Orientation o, int a, int b ) -{ - emit headerDataChanged( o, 0, 1 ); -} - -void NifProxyModel::xDataChanged( const QModelIndex & begin, const QModelIndex & end ) -{ - if ( begin == end ) - { - QList indices = mapFrom( begin ); - foreach ( QModelIndex idx, indices ) - emit dataChanged( idx, idx ); - return; - } - else if ( begin.parent() == end.parent() ) - { - if ( begin.row() == end.row() ) - { - int m = qMax( begin.column(), end.column() ); - for ( int c = qMin( begin.column(), end.column() ); c < m; c++ ) - { - QList indices = mapFrom( begin.sibling( begin.row(), c ) ); - foreach ( QModelIndex idx, indices ) - emit dataChanged( idx, idx ); - } - return; - } - else if ( begin.column() == end.column() ) - { - int m = qMax( begin.row() , end.row() ); - for ( int r = qMin( begin.row(), end.row() ); r < m; r++ ) - { - QList indices = mapFrom( begin.sibling( r, begin.column() ) ); - foreach ( QModelIndex idx, indices ) - emit dataChanged( idx, idx ); - } - return; - } - } - - reset(); -} - -void NifProxyModel::xLinksChanged() -{ - updateRoot( false ); -} - -void NifProxyModel::xRowsAboutToBeRemoved( const QModelIndex & parent, int first, int last ) -{ - if ( ! parent.isValid() ) - { // block removed - for ( int c = first; c <= last; c++ ) - { - QList list; - root->findAllItems( c-1, list ); - foreach ( NifProxyItem * item, list ) - { - QModelIndex idx = createIndex( item->row(), 0, item ); - beginRemoveRows( idx.parent(), idx.row(), idx.row() ); - item->parentItem->childItems.removeAll( item ); - delete item; - endRemoveRows(); - } - } - } -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "nifproxy.h" + +#include "nifmodel.h" + +#include +#include + +class NifProxyItem +{ +public: + NifProxyItem( int number, NifProxyItem * parent ) + { + blockNumber = number; + parentItem = parent; + } + ~NifProxyItem() + { + qDeleteAll( childItems ); + } + + NifProxyItem * getLink( int link ) + { + foreach ( NifProxyItem * item, childItems ) + { + if ( item->block() == link ) + return item; + } + return 0; + } + + int rowLink( int link ) + { + int row = 0; + foreach ( NifProxyItem * item, childItems ) + { + if ( item->block() == link ) + return row; + row++; + } + return -1; + } + + NifProxyItem * addLink( int link ) + { + NifProxyItem * child = getLink( link ); + if ( child ) + { + return child; + } + else + { + child = new NifProxyItem( link, this ); + childItems.append( child ); + return child; + } + } + + void delLink( int link ) + { + NifProxyItem * child = getLink( link ); + if ( child ) + { + childItems.removeAll( child ); + delete child; + } + } + + NifProxyItem * parent() const + { + return parentItem; + } + + NifProxyItem * child( int row ) + { + return childItems.value( row ); + } + + int childCount() + { + return childItems.count(); + } + + void killChildren() + { + qDeleteAll( childItems ); + childItems.clear(); + } + + int row() const + { + if ( parentItem ) + return parentItem->childItems.indexOf( const_cast(this) ); + return 0; + } + + inline int block() const + { + return blockNumber; + } + + QList parentBlocks() const + { + QList parents; + NifProxyItem * parent = parentItem; + while ( parent && parent->parentItem ) + { + parents.append( parent->blockNumber ); + parent = parent->parentItem; + } + return parents; + } + + QList childBlocks() const + { + QList blocks; + foreach ( NifProxyItem * item, childItems ) + blocks.append( item->block() ); + return blocks; + } + + NifProxyItem * findItem( int b, bool scanParents = true ) + { + if ( blockNumber == b ) return this; + + foreach ( NifProxyItem * child, childItems ) + { + if ( child->blockNumber == b ) + return child; + } + + foreach ( NifProxyItem * child, childItems ) + { + if ( NifProxyItem * x = child->findItem( b, false ) ) + return x; + } + + if ( parentItem && scanParents ) + { + NifProxyItem * root = parentItem; + while ( root && root->parentItem ) + root = root->parentItem; + if ( NifProxyItem * x = root->findItem( b, false ) ) + return x; + } + + return 0; + } + + void findAllItems( int b, QList & list ) + { + foreach ( NifProxyItem * item, childItems ) + { + item->findAllItems( b, list ); + } + if ( blockNumber == b ) + list.append( this ); + } + + int blockNumber; + NifProxyItem * parentItem; + QList childItems; +}; + +NifProxyModel::NifProxyModel( QObject * parent ) : QAbstractItemModel( parent ) +{ + root = new NifProxyItem( -1, 0 ); + nif = 0; +} + +NifProxyModel::~NifProxyModel() +{ + delete root; +} + +QAbstractItemModel * NifProxyModel::model() const +{ + return nif; +} + +void NifProxyModel::setModel( QAbstractItemModel * model ) +{ + if ( nif ) + { + disconnect(nif, SIGNAL(dataChanged(const QModelIndex &,const QModelIndex &)), + this, SLOT(xDataChanged(const QModelIndex &,const QModelIndex &))); + disconnect(nif, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(xHeaderDataChanged(Qt::Orientation,int,int))); + disconnect( nif, SIGNAL( rowsAboutToBeRemoved( const QModelIndex &, int, int ) ), this, SLOT( xRowsAboutToBeRemoved( const QModelIndex &, int, int ) ) ); + disconnect( nif, SIGNAL( linksChanged() ), this, SLOT( xLinksChanged() ) ); + + disconnect(nif, SIGNAL(modelReset()), this, SLOT(reset())); + disconnect(nif, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); + } + + nif = qobject_cast( model ); + + if ( nif ) + { + connect( nif, SIGNAL(dataChanged( const QModelIndex &, const QModelIndex & )), + this, SLOT(xDataChanged( const QModelIndex &, const QModelIndex & ))); + connect( nif, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(xHeaderDataChanged(Qt::Orientation,int,int))); + connect( nif, SIGNAL( linksChanged() ), this, SLOT( xLinksChanged() ) ); + connect( nif, SIGNAL( rowsAboutToBeRemoved( const QModelIndex &, int, int ) ), this, SLOT( xRowsAboutToBeRemoved( const QModelIndex &, int, int ) ) ); + connect( nif, SIGNAL(modelReset()), this, SLOT(reset())); + connect( nif, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); + } + + reset(); +} + +void NifProxyModel::reset() +{ + //qDebug() << "proxy reset"; + root->killChildren(); + updateRoot( true ); + QAbstractItemModel::reset(); +} + +void NifProxyModel::updateRoot( bool fast ) +{ + if ( ! ( nif && nif->getBlockCount() > 0 ) ) + { + if ( root->childCount() > 0 ) + { + if ( ! fast ) beginRemoveRows( QModelIndex(), 0, root->childCount() - 1 ); + root->killChildren(); + if ( ! fast ) endRemoveRows(); + } + return; + } + + //qDebug() << "proxy update top level"; + + foreach ( NifProxyItem * item, root->childItems ) + { + if ( ! nif->getRootLinks().contains( item->block() ) ) + { + int at = root->rowLink( item->block() ); + if ( ! fast ) beginRemoveRows( QModelIndex(), at, at ); + root->delLink( item->block() ); + if ( ! fast ) endRemoveRows(); + } + } + + foreach ( int l, nif->getRootLinks() ) + { + NifProxyItem * item = root->getLink( l ); + if ( ! item ) + { + if ( ! fast ) beginInsertRows( QModelIndex(), root->childCount(), root->childCount() ); + item = root->addLink( l ); + if ( ! fast ) endInsertRows(); + } + updateItem( item, fast ); + } +} + +void NifProxyModel::updateItem( NifProxyItem * item, bool fast ) +{ + QModelIndex index( createIndex( item->row(), 0, item ) ); + + QList parents( item->parentBlocks() ); + + foreach( int l, item->childBlocks() ) + { + if ( ! ( nif->getChildLinks( item->block() ).contains( l ) || nif->getParentLinks( item->block() ).contains( l ) ) ) + { + int at = item->rowLink( l ); + if ( ! fast ) beginRemoveRows( index, at, at ); + item->delLink( l ); + if ( ! fast ) endRemoveRows(); + } + } + foreach ( int l, nif->getChildLinks(item->block()) ) + { + NifProxyItem * child = item->getLink( l ); + if ( ! child ) + { + int at = item->childCount(); + if ( ! fast ) beginInsertRows( index, at, at ); + child = item->addLink( l ); + if ( ! fast ) endInsertRows(); + } + if ( ! parents.contains( child->block() ) ) + { + updateItem( child, fast ); + } + else + qWarning() << "infinite recursing link construct detected" << item->block() << "->" << child->block(); + } + foreach ( int l, nif->getParentLinks( item->block() ) ) + { + if ( ! item->getLink( l ) ) + { + int at = item->childCount(); + if ( ! fast ) beginInsertRows( index, at, at ); + item->addLink( l ); + if ( ! fast ) endInsertRows(); + } + } +} + +int NifProxyModel::rowCount( const QModelIndex & parent ) const +{ + NifProxyItem * parentItem; + + if ( ! ( parent.isValid() && parent.model() == this ) ) + parentItem = root; + else + parentItem = static_cast( parent.internalPointer() ); + + return ( parentItem ? parentItem->childCount() : 0 ); +} + +QModelIndex NifProxyModel::index( int row, int column, const QModelIndex & parent ) const +{ + NifProxyItem * parentItem; + + if ( ! ( parent.isValid() && parent.model() == this ) ) + parentItem = root; + else + parentItem = static_cast( parent.internalPointer() ); + + NifProxyItem * childItem = ( parentItem ? parentItem->child( row ) : 0 ); + if ( childItem ) + return createIndex( row, column, childItem ); + else + return QModelIndex(); +} + +QModelIndex NifProxyModel::parent( const QModelIndex & child ) const +{ + if ( ! ( child.isValid() && child.model() == this ) ) + return QModelIndex(); + + NifProxyItem * childItem = static_cast( child.internalPointer() ); + NifProxyItem * parentItem = childItem->parent(); + + if ( parentItem == root || ! parentItem ) + return QModelIndex(); + return createIndex( parentItem->row(), 0, parentItem ); +} + +QModelIndex NifProxyModel::mapTo( const QModelIndex & idx ) const +{ + if ( ! ( nif && idx.isValid() ) ) return QModelIndex(); + + if ( idx.model() != this ) + { + qDebug() << "NifProxyModel::mapTo() called with wrong model"; + return QModelIndex(); + } + NifProxyItem * item = static_cast( idx.internalPointer() ); + if ( ! item ) return QModelIndex(); + QModelIndex nifidx = nif->getBlock( item->block() ); + if ( nifidx.isValid() ) nifidx = nifidx.sibling( nifidx.row(), ( idx.column() ? NifModel::ValueCol : NifModel::NameCol ) ); + return nifidx; +} + +QModelIndex NifProxyModel::mapFrom( const QModelIndex & idx, const QModelIndex & ref ) const +{ + if ( ! ( nif && idx.isValid() ) ) return QModelIndex(); + if ( idx.model() != nif ) + { + qDebug() << "NifProxyModel::mapFrom() called with wrong model"; + return QModelIndex(); + } + int blockNumber = nif->getBlockNumber( idx ); + if ( blockNumber < 0 ) return QModelIndex(); + NifProxyItem * item = root; + if ( ref.isValid() ) + { + if ( ref.model() == this ) + item = static_cast( ref.internalPointer() ); + else + qDebug() << "NifProxyModel::mapFrom() called with wrong ref model"; + } + item = item->findItem( blockNumber ); + if ( item ) + return createIndex( item->row(), 0, item ); + return QModelIndex(); +} + +QList NifProxyModel::mapFrom( const QModelIndex & idx ) const +{ + QList indices; + + if ( !( nif && idx.isValid() && ( idx.column() == NifModel::NameCol || idx.column() == NifModel::ValueCol ) ) ) return indices; + if ( idx.model() != nif ) + { + qDebug() << "NifProxyModel::mapFrom() plural called with wrong model"; + return indices; + } + if ( idx.parent().isValid() ) + return indices; + + int blockNumber = nif->getBlockNumber( idx ); + if ( blockNumber < 0 ) + return indices; + + QList items; + root->findAllItems( blockNumber, items ); + foreach( NifProxyItem * item, items ) + indices.append( createIndex( item->row(), idx.column() != NifModel::NameCol ? 1 : 0, item ) ); + + return indices; +} + +Qt::ItemFlags NifProxyModel::flags( const QModelIndex & index ) const +{ + if ( !nif ) return 0; + return nif->flags( mapTo( index ) ); +} + +QVariant NifProxyModel::data( const QModelIndex & index, int role ) const +{ + if ( !( nif && index.isValid() ) ) return QVariant(); + return nif->data( mapTo( index ), role ); +} + +bool NifProxyModel::setData( const QModelIndex & index, const QVariant & v, int role ) +{ + if ( !( nif && index.isValid() ) ) return false; + return nif->setData( mapTo( index ), v, role ); +} + +QVariant NifProxyModel::headerData( int section, Qt::Orientation orient, int role ) const +{ + if ( !nif || section < 0 || section > 1 ) return QVariant(); + return nif->headerData( ( section ? NifModel::ValueCol : NifModel::NameCol ), orient, role ); +} + +/* + * proxy slots + */ + +void NifProxyModel::xHeaderDataChanged( Qt::Orientation o, int a, int b ) +{ + emit headerDataChanged( o, 0, 1 ); +} + +void NifProxyModel::xDataChanged( const QModelIndex & begin, const QModelIndex & end ) +{ + if ( begin == end ) + { + QList indices = mapFrom( begin ); + foreach ( QModelIndex idx, indices ) + emit dataChanged( idx, idx ); + return; + } + else if ( begin.parent() == end.parent() ) + { + if ( begin.row() == end.row() ) + { + int m = qMax( begin.column(), end.column() ); + for ( int c = qMin( begin.column(), end.column() ); c < m; c++ ) + { + QList indices = mapFrom( begin.sibling( begin.row(), c ) ); + foreach ( QModelIndex idx, indices ) + emit dataChanged( idx, idx ); + } + return; + } + else if ( begin.column() == end.column() ) + { + int m = qMax( begin.row() , end.row() ); + for ( int r = qMin( begin.row(), end.row() ); r < m; r++ ) + { + QList indices = mapFrom( begin.sibling( r, begin.column() ) ); + foreach ( QModelIndex idx, indices ) + emit dataChanged( idx, idx ); + } + return; + } + } + + reset(); +} + +void NifProxyModel::xLinksChanged() +{ + updateRoot( false ); +} + +void NifProxyModel::xRowsAboutToBeRemoved( const QModelIndex & parent, int first, int last ) +{ + if ( ! parent.isValid() ) + { // block removed + for ( int c = first; c <= last; c++ ) + { + QList list; + root->findAllItems( c-1, list ); + foreach ( NifProxyItem * item, list ) + { + QModelIndex idx = createIndex( item->row(), 0, item ); + beginRemoveRows( idx.parent(), idx.row(), idx.row() ); + item->parentItem->childItems.removeAll( item ); + delete item; + endRemoveRows(); + } + } + } +} diff --git a/nifproxy.h b/nifproxy.h index 674837205..95280b8e0 100644 --- a/nifproxy.h +++ b/nifproxy.h @@ -1,94 +1,94 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef NIFPROXYMODEL_H -#define NIFPROXYMODEL_H - -#include - -#include - -class NifModel; - -class NifProxyItem; - -class NifProxyModel : public QAbstractItemModel -{ - Q_OBJECT -public: - NifProxyModel( QObject * parent = 0 ); - ~NifProxyModel(); - - virtual void setModel(QAbstractItemModel *model); - QAbstractItemModel *model() const; - - QModelIndex index( int row, int col, const QModelIndex & parent ) const; - QModelIndex parent( const QModelIndex & index ) const; - - Qt::ItemFlags flags( const QModelIndex & index ) const; - - int columnCount( const QModelIndex & index ) const { return 2; } - int rowCount( const QModelIndex & index ) const; - - bool hasChildren( const QModelIndex & index ) const - { return rowCount( index ) > 0; } - - QVariant data( const QModelIndex & index, int role ) const; - bool setData( const QModelIndex & index, const QVariant & v, int role ); - - QVariant headerData( int section, Qt::Orientation o, int role ) const; - - QModelIndex mapTo( const QModelIndex & index ) const; - QModelIndex mapFrom( const QModelIndex & index, const QModelIndex & ref ) const; - -public slots: - void reset(); - -protected slots: - void xDataChanged( const QModelIndex &, const QModelIndex & ); - void xHeaderDataChanged( Qt::Orientation, int, int ); - void xRowsAboutToBeRemoved( const QModelIndex &, int, int ); - - void xLinksChanged(); - -protected: - QList mapFrom( const QModelIndex & index ) const; - - void updateRoot( bool fast ); - void updateItem( NifProxyItem * item, bool fast ); - - NifModel * nif; - - NifProxyItem * root; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef NIFPROXYMODEL_H +#define NIFPROXYMODEL_H + +#include + +#include + +class NifModel; + +class NifProxyItem; + +class NifProxyModel : public QAbstractItemModel +{ + Q_OBJECT +public: + NifProxyModel( QObject * parent = 0 ); + ~NifProxyModel(); + + virtual void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; + + QModelIndex index( int row, int col, const QModelIndex & parent ) const; + QModelIndex parent( const QModelIndex & index ) const; + + Qt::ItemFlags flags( const QModelIndex & index ) const; + + int columnCount( const QModelIndex & index ) const { return 2; } + int rowCount( const QModelIndex & index ) const; + + bool hasChildren( const QModelIndex & index ) const + { return rowCount( index ) > 0; } + + QVariant data( const QModelIndex & index, int role ) const; + bool setData( const QModelIndex & index, const QVariant & v, int role ); + + QVariant headerData( int section, Qt::Orientation o, int role ) const; + + QModelIndex mapTo( const QModelIndex & index ) const; + QModelIndex mapFrom( const QModelIndex & index, const QModelIndex & ref ) const; + +public slots: + void reset(); + +protected slots: + void xDataChanged( const QModelIndex &, const QModelIndex & ); + void xHeaderDataChanged( Qt::Orientation, int, int ); + void xRowsAboutToBeRemoved( const QModelIndex &, int, int ); + + void xLinksChanged(); + +protected: + QList mapFrom( const QModelIndex & index ) const; + + void updateRoot( bool fast ); + void updateItem( NifProxyItem * item, bool fast ); + + NifModel * nif; + + NifProxyItem * root; +}; + +#endif diff --git a/nifskope.cpp b/nifskope.cpp index cf2a2c34f..a8178b021 100644 --- a/nifskope.cpp +++ b/nifskope.cpp @@ -1,1148 +1,1148 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -// MinGW hack to ensure that GetLongPathNameW is defined -#ifdef WIN32 -# ifdef __GNUC__ -# define WINVER 0x0500 -# endif -#endif - -#include "nifskope.h" -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "kfmmodel.h" -#include "nifmodel.h" -#include "nifproxy.h" -#include "widgets/nifview.h" -#include "widgets/refrbrowser.h" - -#include "glview.h" -#include "spellbook.h" -#include "widgets/fileselect.h" -#include "widgets/copyfnam.h" -#include "widgets/xmlcheck.h" - -#ifdef WIN32 -# define WINDOWS_LEAN_AND_MEAN -# include "windows.h" -#endif - - -#ifdef FSENGINE - -#include "fsengine/fsmanager.h" - -FSManager * fsmanager = 0; - -#endif - - -void NifSkope::copySettings(QSettings & cfg, const QSettings & oldcfg, const QString name) const -{ - if ((!cfg.contains(name)) && oldcfg.contains(name)) { - //qDebug() << "copying nifskope setting" << name; - cfg.setValue(name, oldcfg.value(name)); - }; -}; - -void NifSkope::migrateSettings() const -{ - // load current nifskope settings - NIFSKOPE_QSETTINGS(cfg); - // check for older nifskope settings - for (QStringList::ConstIterator it = NIFSKOPE_OLDERVERSIONS.begin(); it != NIFSKOPE_OLDERVERSIONS.end(); ++it ) { - QSettings oldcfg( "NifTools", *it ); - // check for non-binary missing keys and copy them from old settings - QStringList keys = oldcfg.allKeys(); - for (QStringList::ConstIterator key = keys.begin(); key != keys.end(); ++key) { - //qDebug() << "checking" << *key << oldcfg.value(*key).type(); - switch (oldcfg.value(*key).type()) { - case QVariant::Bool: - case QVariant::Int: - case QVariant::UInt: - case QVariant::Double: - case QVariant::String: - case QVariant::StringList: - // copy settings for these types - copySettings(cfg, oldcfg, *key); - default: - ; // do nothing - }; - }; - }; -}; - -/* - * main GUI window - */ - -void NifSkope::about() -{ - QString text = - "

NifSkope is a tool for analyzing and editing NetImmerse '.nif' files.

" - "

NifSkope is based on NifTool's XML file format specification. " - "For more informations visit our site at http://niftools.sourceforge.net

" - "

NifSkope is free software available under a BSD license. " - "The source is available via svn" - " on SourceForge.

" - "

The most recent version of NifSkope can always be downloaded from the " - "NifTools SourceForge Project page."; - - QMessageBox mb( tr("About NifSkope "NIFSKOPE_VERSION), text, QMessageBox::Information, - QMessageBox::Ok + QMessageBox::Default, 0, 0, this); - mb.setIconPixmap( QPixmap( ":/res/nifskope.png" ) ); - mb.exec(); -} - -NifSkope::NifSkope() - : QMainWindow(), selecting( false ), initialShowEvent( true ) -{ - // migrate settings from older versions of NifSkope - migrateSettings(); - - // create a new nif - nif = new NifModel( this ); - connect( nif, SIGNAL( sigMessage( const Message & ) ), this, SLOT( dispatchMessage( const Message & ) ) ); - - SpellBook * book = new SpellBook( nif, QModelIndex(), this, SLOT( select( const QModelIndex & ) ) ); - - // create a new hierarchical proxy nif - proxy = new NifProxyModel( this ); - proxy->setModel( nif ); - - // create a new kfm model - kfm = new KfmModel( this ); - connect( kfm, SIGNAL( sigMessage( const Message & ) ), this, SLOT( dispatchMessage( const Message & ) ) ); - - - // this view shows the block list - list = new NifTreeView; - list->setModel( nif ); - list->setItemDelegate( nif->createDelegate( book ) ); - list->header()->setStretchLastSection( true ); - list->header()->setMinimumSectionSize( 100 ); - list->installEventFilter( this ); - - connect( list, SIGNAL( sigCurrentIndexChanged( const QModelIndex & ) ), - this, SLOT( select( const QModelIndex & ) ) ); - connect( list, SIGNAL( customContextMenuRequested( const QPoint & ) ), - this, SLOT( contextMenu( const QPoint & ) ) ); - - // this view shows the whole nif file or the block details - tree = new NifTreeView; - tree->setModel( nif ); - tree->setItemDelegate( nif->createDelegate( book ) ); - tree->header()->setStretchLastSection( false ); - tree->installEventFilter( this ); - - connect( tree, SIGNAL( sigCurrentIndexChanged( const QModelIndex & ) ), - this, SLOT( select( const QModelIndex & ) ) ); - connect( tree, SIGNAL( customContextMenuRequested( const QPoint & ) ), - this, SLOT( contextMenu( const QPoint & ) ) ); - - - // this view shows the whole kfm file - kfmtree = new NifTreeView; - kfmtree->setModel( kfm ); - kfmtree->setItemDelegate( kfm->createDelegate() ); - kfmtree->header()->setStretchLastSection( false ); - kfmtree->installEventFilter( this ); - - connect( kfmtree, SIGNAL( customContextMenuRequested( const QPoint & ) ), - this, SLOT( contextMenu( const QPoint & ) ) ); - - // this browser shows the reference of current node - refrbrwsr = new ReferenceBrowser; - - refrbrwsr->setNifModel( nif ); - connect( tree, SIGNAL( sigCurrentIndexChanged( const QModelIndex & ) ), - refrbrwsr, SLOT( browse( const QModelIndex & ) ) ); - -#ifdef EDIT_ON_ACTIVATE - connect( list, SIGNAL( activated( const QModelIndex & ) ), - list, SLOT( edit( const QModelIndex & ) ) ); - connect( tree, SIGNAL( activated( const QModelIndex & ) ), - tree, SLOT( edit( const QModelIndex & ) ) ); - connect( kfmtree, SIGNAL( activated( const QModelIndex & ) ), - kfmtree, SLOT( edit( const QModelIndex & ) ) ); -#endif - - - // open gl - setCentralWidget( ogl = GLView::create() ); - ogl->setNif( nif ); - connect( ogl, SIGNAL( clicked( const QModelIndex & ) ), - this, SLOT( select( const QModelIndex & ) ) ); - connect( ogl, SIGNAL( customContextMenuRequested( const QPoint & ) ), - this, SLOT( contextMenu( const QPoint & ) ) ); - - - // actions - - aSanitize = new QAction( tr("&Auto Sanitize before Save"), this ); - aSanitize->setCheckable( true ); - aSanitize->setChecked( true ); - aLoadXML = new QAction( tr("Reload &XML"), this ); - connect( aLoadXML, SIGNAL( triggered() ), this, SLOT( loadXML() ) ); - aReload = new QAction( tr("&Reload XML + Nif"), this ); - aReload->setShortcut( Qt::ALT + Qt::Key_X ); - connect( aReload, SIGNAL( triggered() ), this, SLOT( reload() ) ); - aWindow = new QAction( tr("&New Window"), this ); - connect( aWindow, SIGNAL( triggered() ), this, SLOT( sltWindow() ) ); - aShredder = new QAction( tr("XML Checker" ), this ); - connect( aShredder, SIGNAL( triggered() ), this, SLOT( sltShredder() ) ); - aQuit = new QAction( tr("&Quit"), this ); - connect( aQuit, SIGNAL( triggered() ), qApp, SLOT( quit() ) ); - - aList = new QAction( tr("Show Blocks in List"), this ); - aList->setCheckable( true ); - aList->setChecked( list->model() == nif ); - - aHierarchy = new QAction( tr("Show Blocks in Tree"), this ); - aHierarchy->setCheckable( true ); - aHierarchy->setChecked( list->model() == proxy ); - - gListMode = new QActionGroup( this ); - connect( gListMode, SIGNAL( triggered( QAction * ) ), this, SLOT( setListMode() ) ); - gListMode->addAction( aList ); - gListMode->addAction( aHierarchy ); - gListMode->setExclusive( true ); - - aSelectFont = new QAction( tr("Select Font ..."), this ); - connect( aSelectFont, SIGNAL( triggered() ), this, SLOT( sltSelectFont() ) ); - - - /* help menu */ - - aHelpWebsite = new QAction( tr("NifSkope Documentation && &Tutorials"), this ); - aHelpWebsite->setData( QUrl("http://niftools.sourceforge.net/wiki/index.php/NifSkope") ); - connect( aHelpWebsite, SIGNAL( triggered() ), this, SLOT( openURL() ) ); - - aHelpForum = new QAction( tr("NifSkope Help && Bug Report &Forum"), this ); - aHelpForum->setData( QUrl("http://niftools.sourceforge.net/forum/viewforum.php?f=24") ); - connect( aHelpForum, SIGNAL( triggered() ), this, SLOT( openURL() ) ); - - aNifToolsWebsite = new QAction( tr("NifTools &Wiki"), this ); - aNifToolsWebsite->setData( QUrl("http://niftools.sourceforge.net") ); - connect( aNifToolsWebsite, SIGNAL( triggered() ), this, SLOT( openURL() ) ); - - aNifToolsDownloads = new QAction( tr("NifTools &Downloads"), this ); - aNifToolsDownloads->setData( QUrl("http://sourceforge.net/project/showfiles.php?group_id=149157") ); - connect( aNifToolsDownloads, SIGNAL( triggered() ), this, SLOT( openURL() ) ); - - aNifSkope = new QAction( tr("About &NifSkope"), this ); - connect( aNifSkope, SIGNAL( triggered() ), this, SLOT( about() ) ); - - aAboutQt = new QAction( tr("About &Qt"), this ); - connect( aAboutQt, SIGNAL( triggered() ), qApp, SLOT( aboutQt() ) ); - -#ifdef FSENGINE - if ( fsmanager ) - { - aResources = new QAction( tr("Resource Files"), this ); - connect( aResources, SIGNAL( triggered() ), fsmanager, SLOT( selectArchives() ) ); - } - else - { - aResources = 0; - } -#endif - - - // dock widgets - - dRefr = new QDockWidget( tr("Interactive Help") ); - dRefr->setObjectName( "RefrDock" ); - dRefr->setWidget( refrbrwsr ); - dRefr->toggleViewAction()->setShortcut( Qt::Key_F1 ); - dRefr->toggleViewAction()->setChecked( false ); - dRefr->setVisible( false ); - - dList = new QDockWidget( tr("Block List") ); - dList->setObjectName( "ListDock" ); - dList->setWidget( list ); - dList->toggleViewAction()->setShortcut( Qt::Key_F2 ); - connect( dList->toggleViewAction(), SIGNAL( toggled( bool ) ), tree, SLOT( clearRootIndex() ) ); - - dTree = new QDockWidget( tr("Block Details") ); - dTree->setObjectName( "TreeDock" ); - dTree->setWidget( tree ); - dTree->toggleViewAction()->setShortcut( Qt::Key_F3 ); - dTree->toggleViewAction()->setChecked( false ); - dTree->setVisible( false ); - - dKfm = new QDockWidget( tr("KFM") ); - dKfm->setObjectName( "KfmDock" ); - dKfm->setWidget( kfmtree ); - dKfm->toggleViewAction()->setShortcut( Qt::Key_F4 ); - dKfm->toggleViewAction()->setChecked( false ); - dKfm->setVisible( false ); - - addDockWidget( Qt::BottomDockWidgetArea, dRefr ); - addDockWidget( Qt::LeftDockWidgetArea, dList ); - addDockWidget( Qt::BottomDockWidgetArea, dTree ); - addDockWidget( Qt::RightDockWidgetArea, dKfm ); - - - /* ******** */ - - // tool bars - - // begin Load & Save toolbar - tool = new QToolBar( tr("Load & Save") ); - tool->setObjectName( "toolbar" ); - tool->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); - - QStringList fileExtensions( QStringList() << "*.nif" << "*.kf" << "*.kfa" << "*.kfm" << "*.nifcache" << "*.texcache" ); - - // create the load portion of the toolbar - tool->addWidget( lineLoad = new FileSelector( FileSelector::LoadFile, tr("&Load..."), QBoxLayout::RightToLeft ) ); - lineLoad->setFilter( fileExtensions ); - connect( lineLoad, SIGNAL( sigActivated( const QString & ) ), this, SLOT( load() ) ); - - // add the Load<=>Save filename copy widget - CopyFilename * cpFilename = new CopyFilename( this ); - cpFilename->setObjectName( "fileCopyWidget" ); - connect( cpFilename, SIGNAL( leftTriggered() ), - this, SLOT( copyFileNameSaveLoad() ) ); - connect( cpFilename, SIGNAL( rightTriggered() ), - this, SLOT( copyFileNameLoadSave() ) ); - tool->addWidget( cpFilename ); - - // create the save portion of the toolbar - tool->addWidget( lineSave = new FileSelector( FileSelector::SaveFile, tr("&Save As..."), QBoxLayout::LeftToRight ) ); - lineSave->setFilter( fileExtensions ); - connect( lineSave, SIGNAL( sigActivated( const QString & ) ), this, SLOT( save() ) ); - - addToolBar( Qt::TopToolBarArea, tool ); - // end Load & Save toolbar - - // begin OpenGL toolbars - foreach ( QToolBar * tb, ogl->toolbars() ) { - addToolBar( Qt::TopToolBarArea, tb ); - } - // end OpenGL toolbars - - /* ********* */ - - // menu - - // assemble the File menu - QMenu * mFile = new QMenu( tr("&File") ); - mFile->addActions( lineLoad->actions() ); - mFile->addActions( lineSave->actions() ); - mFile->addSeparator(); - mFile->addMenu( mImport = new QMenu( tr("Import") ) ); - mFile->addMenu( mExport = new QMenu( tr("Export") ) ); - mFile->addSeparator(); - mFile->addAction( aSanitize ); - mFile->addSeparator(); - mFile->addAction( aWindow ); - mFile->addSeparator(); - mFile->addAction( aLoadXML ); - mFile->addAction( aReload ); - mFile->addAction( aShredder ); - -#ifdef FSENGINE - if ( aResources ) - { - mFile->addSeparator(); - mFile->addAction( aResources ); - } -#endif - mFile->addSeparator(); - mFile->addAction( aQuit ); - - QMenu * mView = new QMenu( tr("&View") ); - mView->addAction( dRefr->toggleViewAction() ); - mView->addAction( dList->toggleViewAction() ); - mView->addAction( dTree->toggleViewAction() ); - mView->addAction( dKfm->toggleViewAction() ); - mView->addSeparator(); - QMenu * mTools = new QMenu( tr("&Toolbars") ); - mView->addMenu( mTools ); - foreach ( QObject * o, children() ) - { - QToolBar * tb = qobject_cast( o ); - if ( tb ) - mTools->addAction( tb->toggleViewAction() ); - } - mView->addSeparator(); - mView->addAction( aHierarchy ); - mView->addAction( aList ); - mView->addSeparator(); - mView->addAction( aSelectFont ); - - QMenu * mAbout = new QMenu( tr("&Help") ); - mAbout->addAction( dRefr->toggleViewAction() ); - mAbout->addAction( aHelpWebsite ); - mAbout->addAction( aHelpForum ); - mAbout->addSeparator(); - mAbout->addAction( aNifToolsWebsite ); - mAbout->addAction( aNifToolsDownloads ); - mAbout->addSeparator(); - mAbout->addAction( aAboutQt ); - mAbout->addAction( aNifSkope ); - - menuBar()->addMenu( mFile ); - menuBar()->addMenu( mView ); - menuBar()->addMenu( ogl->createMenu() ); - menuBar()->addMenu( book ); - menuBar()->addMenu( mAbout ); - - fillImportExportMenus(); - connect( mExport, SIGNAL( triggered( QAction * ) ), this, SLOT( sltImportExport( QAction * ) ) ); - connect( mImport, SIGNAL( triggered( QAction * ) ), this, SLOT( sltImportExport( QAction * ) ) ); -} - -NifSkope::~NifSkope() -{ -} - -void NifSkope::closeEvent( QCloseEvent * e ) -{ - NIFSKOPE_QSETTINGS(settings); - save( settings ); - - QMainWindow::closeEvent( e ); -} - -void restoreHeader( const QString & name, const QSettings & settings, QHeaderView * header ) -{ - QByteArray b = settings.value( name ).value(); - if ( b.isEmpty() ) - return; - QDataStream d( &b, QIODevice::ReadOnly ); - int s; - d >> s; - if ( s != header->count() ) - return; - for ( int c = 0; c < header->count(); c++ ) - { - d >> s; - header->resizeSection( c, s ); - } -} - -void NifSkope::restore( const QSettings & settings ) -{ - restoreGeometry( settings.value( "window geometry" ).toByteArray() ); - restoreState( settings.value( "window state" ).toByteArray(), 0x073 ); - - lineLoad->setText( settings.value( "last load", QString( "" ) ).toString() ); - lineSave->setText( settings.value( "last save", QString( "" ) ).toString() ); - aSanitize->setChecked( settings.value( "auto sanitize", true ).toBool() ); - - if ( settings.value( "list mode", "hirarchy" ).toString() == "list" ) - aList->setChecked( true ); - else - aHierarchy->setChecked( true ); - setListMode(); - - restoreHeader( "list sizes", settings, list->header() ); - restoreHeader( "tree sizes", settings, tree->header() ); - restoreHeader( "kfmtree sizes", settings, kfmtree->header() ); - - ogl->restore( settings ); - - QVariant fontVar = settings.value( "viewFont" ); - if ( fontVar.canConvert() ) - setViewFont( fontVar.value() ); -} - -void saveHeader( const QString & name, QSettings & settings, QHeaderView * header ) -{ - QByteArray b; - QDataStream d( &b, QIODevice::WriteOnly ); - d << header->count(); - for ( int c = 0; c < header->count(); c++ ) - d << header->sectionSize( c ); - settings.setValue( name, b ); -} - -void NifSkope::save( QSettings & settings ) const -{ - settings.setValue( "window state", saveState( 0x073 ) ); - settings.setValue( "window geometry", saveGeometry() ); - - settings.setValue( "last load", lineLoad->text() ); - settings.setValue( "last save", lineSave->text() ); - settings.setValue( "auto sanitize", aSanitize->isChecked() ); - - settings.setValue( "list mode", ( gListMode->checkedAction() == aList ? "list" : "hirarchy" ) ); - saveHeader( "list sizes", settings, list->header() ); - - saveHeader( "tree sizes", settings, tree->header() ); - - saveHeader( "kfmtree sizes", settings, kfmtree->header() ); - - ogl->save( settings ); -} - -void NifSkope::contextMenu( const QPoint & pos ) -{ - QModelIndex idx; - QPoint p = pos; - if ( sender() == tree ) - { - idx = tree->indexAt( pos ); - p = tree->mapToGlobal( pos ); - } - else if ( sender() == list ) - { - idx = list->indexAt( pos ); - p = list->mapToGlobal( pos ); - } - else if ( sender() == ogl ) - { - idx = ogl->indexAt( pos ); - p = ogl->mapToGlobal( pos ); - } - else - return; - - while ( idx.model() && idx.model()->inherits( "NifProxyModel" ) ) - { - idx = qobject_cast( idx.model() )->mapTo( idx ); - } - - SpellBook book( nif, idx, this, SLOT( select( const QModelIndex & ) ) ); - book.exec( p ); -} - -void NifSkope::select( const QModelIndex & index ) -{ - if ( selecting ) - return; - - QModelIndex idx = index; - - if ( idx.model() == proxy ) - idx = proxy->mapTo( index ); - - if ( idx.isValid() && idx.model() != nif ) - return; - - selecting = true; - - if ( sender() != ogl ) - { - ogl->setCurrentIndex( idx ); - } - - if ( sender() != list ) - { - if ( list->model() == proxy ) - { - QModelIndex pidx = proxy->mapFrom( nif->getBlock( idx ), list->currentIndex() ); - list->setCurrentIndex( pidx ); - } - else if ( list->model() == nif ) - { - list->setCurrentIndex( nif->getBlockOrHeader( idx ) ); - } - } - - if ( sender() != tree ) - { - if ( dList->isVisible() ) - { - QModelIndex root = nif->getBlockOrHeader( idx ); - if ( tree->rootIndex() != root ) - tree->setRootIndex( root ); - tree->setCurrentIndex( idx.sibling( idx.row(), 0 ) ); - } - else - { - if ( tree->rootIndex() != QModelIndex() ) - tree->setRootIndex( QModelIndex() ); - tree->setCurrentIndex( idx.sibling( idx.row(), 0 ) ); - } - } - selecting = false; -} - -void NifSkope::setListMode() -{ - QModelIndex idx = list->currentIndex(); - QAction * a = gListMode->checkedAction(); - if ( !a || a == aList ) - { - if ( list->model() != nif ) - { - QHeaderView * head = list->header(); - int s0 = head->sectionSize( head->logicalIndex( 0 ) ); - int s1 = head->sectionSize( head->logicalIndex( 1 ) ); - list->setModel( nif ); - list->setItemsExpandable( false ); - list->setRootIsDecorated( false ); - list->setCurrentIndex( proxy->mapTo( idx ) ); - list->setColumnHidden( NifModel::TypeCol, true ); - list->setColumnHidden( NifModel::ArgCol, true ); - list->setColumnHidden( NifModel::Arr1Col, true ); - list->setColumnHidden( NifModel::Arr2Col, true ); - list->setColumnHidden( NifModel::CondCol, true ); - list->setColumnHidden( NifModel::Ver1Col, true ); - list->setColumnHidden( NifModel::Ver2Col, true ); - head->resizeSection( 0, s0 ); - head->resizeSection( 1, s1 ); - } - } - else - { - if ( list->model() != proxy ) - { - QHeaderView * head = list->header(); - int s0 = head->sectionSize( head->logicalIndex( 0 ) ); - int s1 = head->sectionSize( head->logicalIndex( 1 ) ); - list->setModel( proxy ); - list->setItemsExpandable( true ); - list->setRootIsDecorated( true ); - QModelIndex pidx = proxy->mapFrom( idx, QModelIndex() ); - list->setCurrentIndex( pidx ); - head->resizeSection( 0, s0 ); - head->resizeSection( 1, s1 ); - } - } -} - -void NifSkope::load( const QString & filepath ) -{ - lineLoad->setText( filepath ); - QTimer::singleShot( 0, this, SLOT( load() ) ); -} - -void NifSkope::load() -{ - setEnabled( false ); - - QFileInfo niffile( QDir::fromNativeSeparators( lineLoad->text() ) ); - niffile.makeAbsolute(); - - if ( niffile.suffix().compare( "kfm", Qt::CaseInsensitive ) == 0 ) - { - lineLoad->rstState(); - lineSave->rstState(); - if ( !kfm->loadFromFile( niffile.filePath() ) ) { - qWarning() << tr("failed to load kfm from '%1'").arg( niffile.filePath() ); - lineLoad->setState( FileSelector::stError ); - } - else { - lineLoad->setState( FileSelector::stSuccess ); - lineLoad->setText( niffile.filePath() ); - lineSave->setText( niffile.filePath() ); - } - - niffile.setFile( kfm->getFolder(), - kfm->get( kfm->getKFMroot(), "NIF File Name" ) ); - } - - ogl->tAnim->setEnabled( false ); - - if ( !niffile.isFile() ) - { - nif->clear(); - lineLoad->setState( FileSelector::stError ); - setWindowTitle( "NifSkope" ); - } - else - { - ProgDlg prog; - prog.setLabelText( tr("loading nif...") ); - prog.setRange( 0, 1 ); - prog.setValue( 0 ); - prog.setMinimumDuration( 2100 ); - connect( nif, SIGNAL( sigProgress( int, int ) ), & prog, SLOT( sltProgress( int, int ) ) ); - - lineLoad->rstState(); - lineSave->rstState(); - if ( !nif->loadFromFile( niffile.filePath() ) ) { - qWarning() << tr("failed to load nif from '%1'").arg( niffile.filePath() ); - lineLoad->setState( FileSelector::stError ); - } - else { - lineLoad->setState( FileSelector::stSuccess ); - lineLoad->setText( niffile.filePath() ); - lineSave->setText( niffile.filePath() ); - } - - setWindowTitle( "NifSkope - " + niffile.fileName() ); - } - - ogl->tAnim->setEnabled( true ); - ogl->center(); - - setEnabled( true ); -} - -void ProgDlg::sltProgress( int x, int y ) -{ - setRange( 0, y ); - setValue( x ); - qApp->processEvents(); -} - -void NifSkope::save() -{ - // write to file - setEnabled( false ); - - QString nifname = lineSave->text(); - - if ( nifname.endsWith( ".KFM", Qt::CaseInsensitive ) ) - { - lineSave->rstState(); - if ( ! kfm->saveToFile( nifname ) ) { - qWarning() << tr("failed to write kfm file") << nifname; - lineSave->setState(FileSelector::stError); - } - else { - lineSave->setState(FileSelector::stSuccess); - } - } - else - { - lineSave->rstState(); - - if ( aSanitize->isChecked() ) - { - QModelIndex idx = SpellBook::sanitize( nif ); - if ( idx.isValid() ) - select( idx ); - } - - if ( ! nif->saveToFile( nifname ) ) { - qWarning() << tr("failed to write nif file ") << nifname; - lineSave->setState(FileSelector::stError); - } - else { - lineSave->setState(FileSelector::stSuccess); - } - - setWindowTitle( "NifSkope - " + nifname.right( nifname.length() - nifname.lastIndexOf( '/' ) - 1 ) ); - } - setEnabled( true ); -} - -void NifSkope::copyFileNameLoadSave() -{ - if(lineLoad->text().isEmpty()) { - return; - } - - lineSave->replaceText( lineLoad->text() ); -} - -void NifSkope::copyFileNameSaveLoad() -{ - if(lineSave->text().isEmpty()) { - return; - } - - lineLoad->replaceText( lineSave->text() ); -} - -void NifSkope::sltWindow() -{ - createWindow(); -} - -void NifSkope::sltShredder() -{ - TestShredder::create(); -} - -void NifSkope::openURL() -{ - if( !sender() ) return; - - QAction * aURL = qobject_cast( sender() ); - if( !aURL ) return; - - QUrl URL = aURL->data().toUrl(); - if( !URL.isValid() ) return; - - QDesktopServices::openUrl( URL ); -} - - -NifSkope * NifSkope::createWindow( const QString & fname ) -{ - NifSkope * skope = new NifSkope; - skope->setAttribute( Qt::WA_DeleteOnClose ); - NIFSKOPE_QSETTINGS(settings); - skope->restore( settings ); - skope->show(); - - skope->raise(); - - if ( ! fname.isEmpty() ) - { - skope->lineLoad->setFile( fname ); - QTimer::singleShot( 0, skope, SLOT( load() ) ); - } - return skope; -} - -void NifSkope::loadXML() -{ - NifModel::loadXML(); - KfmModel::loadXML(); -} - -void NifSkope::reload() -{ - if ( NifModel::loadXML() ) - { - load(); - } -} - -void NifSkope::sltSelectFont() -{ - bool ok; - QFont fnt = QFontDialog::getFont( & ok, list->font(), this ); - if ( ! ok ) - return; - setViewFont( fnt ); - QSettings settings; - settings.setValue( "viewFont", fnt ); -} - -void NifSkope::setViewFont( const QFont & font ) -{ - list->setFont( font ); - QFontMetrics metrics( list->font() ); - list->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); - tree->setFont( font ); - tree->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); - kfmtree->setFont( font ); - kfmtree->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); - ogl->setFont( font ); -} - -bool NifSkope::eventFilter( QObject * o, QEvent * e ) -{ - if ( e->type() == QEvent::Polish ) - { - QTimer::singleShot( 0, this, SLOT( overrideViewFont() ) ); - } - return QMainWindow::eventFilter( o, e ); -} - -void NifSkope::overrideViewFont() -{ - QSettings settings; - QVariant var = settings.value( "viewFont" ); - if ( var.canConvert() ) - { - setViewFont( var.value() ); - } -} - -void NifSkope::dispatchMessage( const Message & msg ) -{ - switch ( msg.type() ) - { - case QtCriticalMsg: - qCritical() << msg; - break; - case QtFatalMsg: - qFatal( QString( msg ).toAscii().data() ); - break; - case QtWarningMsg: - qWarning() << msg; - break; - case QtDebugMsg: - default: - qDebug() << msg; - break; - } -} - -QTextEdit * msgtarget = 0; - -void myMessageOutput(QtMsgType type, const char *msg) -{ - switch (type) - { - case QtDebugMsg: - printf( "%s\n", msg ); - break; - case QtWarningMsg: - // workaround for Qt 4.2.2 - if ( QString( "edit: editing failed" ) == msg ) - return; - case QtCriticalMsg: - if ( ! msgtarget ) - { - msgtarget = new QTextEdit; - msgtarget->setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); - } - if ( ! msgtarget->isVisible() ) - { - msgtarget->clear(); - msgtarget->show(); - } - - msgtarget->append( msg ); - break; - case QtFatalMsg: - QMessageBox::critical( 0, QMessageBox::tr("Fatal Error"), msg ); - abort(); - } -} - - -/* - * IPC socket - */ - -IPCsocket * IPCsocket::create() -{ - QUdpSocket * udp = new QUdpSocket(); - - if ( udp->bind( QHostAddress( QHostAddress::LocalHost ), NIFSKOPE_IPC_PORT, QUdpSocket::DontShareAddress ) ) - { - IPCsocket * ipc = new IPCsocket( udp ); - QDesktopServices::setUrlHandler( "nif", ipc, "openNif" ); - return ipc; - } - - return 0; -} - -void IPCsocket::sendCommand( const QString & cmd ) -{ - QUdpSocket udp; - udp.writeDatagram( (const char *) cmd.data(), cmd.length() * sizeof( QChar ), QHostAddress( QHostAddress::LocalHost ), NIFSKOPE_IPC_PORT ); -} - -IPCsocket::IPCsocket( QUdpSocket * s ) : QObject(), socket( s ) -{ - QObject::connect( socket, SIGNAL( readyRead() ), this, SLOT( processDatagram() ) ); - -#ifdef FSENGINE - if ( ! fsmanager ) - fsmanager = new FSManager( this ); -#endif -} - -IPCsocket::~IPCsocket() -{ - delete socket; -} - -void IPCsocket::processDatagram() -{ - while ( socket->hasPendingDatagrams() ) - { - QByteArray data; - data.resize( socket->pendingDatagramSize() ); - QHostAddress host; - quint16 port = 0; - - socket->readDatagram( data.data(), data.size(), &host, &port ); - if ( host == QHostAddress( QHostAddress::LocalHost ) && ( data.size() % sizeof( QChar ) ) == 0 ) - { - QString cmd; - cmd.setUnicode( (QChar *) data.data(), data.size() / sizeof( QChar ) ); - execCommand( cmd ); - } - } -} - -void IPCsocket::execCommand( const QString & cmd ) -{ - if ( cmd.startsWith( "NifSkope::open" ) ) - { - openNif( cmd.right( cmd.length() - 15 ) ); - } -} - -void IPCsocket::openNif( const QUrl & url ) -{ - NifSkope::createWindow( url.toString( url.scheme() == "nif" ? QUrl::RemoveScheme : QUrl::None ) ); -} - - -// Qt does not use the System Locale consistency so this basically forces all floating -// numbers into C format but leaves all other local specific settings. -class NifSystemLocale : QSystemLocale -{ - virtual QVariant query(QueryType type, QVariant in) const - { - switch (type) - { - case DecimalPoint: - return QVariant( QLocale::c().decimalPoint() ); - case GroupSeparator: - return QVariant( QLocale::c().groupSeparator() ); - default: - return QVariant(); - } - } -}; - -/* - * main - */ - -int main( int argc, char * argv[] ) -{ - NifSystemLocale mLocale; - - // set up the Qt Application - QApplication app( argc, argv ); - app.setOrganizationName( "NifTools" ); - app.setApplicationName( "NifSkope" ); - app.setOrganizationDomain( "niftools.sourceforge.net" ); - - // install message handler - qRegisterMetaType( "Message" ); -#ifndef NO_MESSAGEHANDLER - qInstallMsgHandler( myMessageOutput ); -#endif - - // if there is a style sheet present then load it - QDir qssDir; - bool qssFound; - // look for stylesheet in application directory - qssDir.setPath(QApplication::applicationDirPath()); - qssFound = qssDir.exists("style.qss"); - // look for stylesheet in linux nifskope data directory - if (!qssFound) - { - qssDir.setPath("/usr/share/nifskope"); - qssFound = qssDir.exists("style.qss"); - } - // load the style sheet if present - if (qssFound) - { - QFile style( qssDir.filePath( "style.qss" ) ); - if ( style.open( QFile::ReadOnly ) ) - { - app.setStyleSheet( style.readAll() ); - style.close(); - } - } - - // set the translation - QString locale = QLocale::system().name(); - - QTranslator translator; - translator.load( QString( ":lang/" ) + locale ); - app.installTranslator( &translator ); - - NifModel::loadXML(); - KfmModel::loadXML(); - - QString fname; - if ( app.argc() > 1 ) - { - //Getting a NIF file name from the OS - fname = QDir::current().filePath( QString( app.argv()[ app.argc() - 1 ] ) ); - -#ifdef WIN32 - //Windows passes an ugly 8.3 file path as an argument, so use a WinAPI function to fix that - wchar_t full[MAX_PATH]; - wchar_t * temp_name = new wchar_t[fname.size() + 1]; - - fname.toWCharArray( temp_name ); - temp_name[fname.size()] = 0; //The above function doesn't seem to write a null character, so add it. - - //Ensure that input is a full path, even if a partial one was given on the command line - DWORD ret = GetFullPathNameW( temp_name, MAX_PATH, full, NULL ); - - if ( ret != 0 ) - { - delete [] temp_name; - temp_name = new wchar_t[MAX_PATH]; - - //Finally get the full long file name version of the path - ret = GetLongPathNameW( full, temp_name, MAX_PATH ); - - //Copy the name back to the QString variable that Qt uses - if ( ret != 0 ) - { - //GetLongPath succeeded - fname = QString::fromWCharArray( temp_name); - } else - { - //GetLongPath failed, use result from GetFullPathName function - fname = QString::fromWCharArray( full ); - } - } - - delete [] temp_name; -#endif - } - - if ( IPCsocket * ipc = IPCsocket::create() ) - { - ipc->execCommand( QString( "NifSkope::open %1" ).arg( fname ) ); - return app.exec(); - } - else - { - IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fname ) ); - return 0; - } -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +// MinGW hack to ensure that GetLongPathNameW is defined +#ifdef WIN32 +# ifdef __GNUC__ +# define WINVER 0x0500 +# endif +#endif + +#include "nifskope.h" +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kfmmodel.h" +#include "nifmodel.h" +#include "nifproxy.h" +#include "widgets/nifview.h" +#include "widgets/refrbrowser.h" + +#include "glview.h" +#include "spellbook.h" +#include "widgets/fileselect.h" +#include "widgets/copyfnam.h" +#include "widgets/xmlcheck.h" + +#ifdef WIN32 +# define WINDOWS_LEAN_AND_MEAN +# include "windows.h" +#endif + + +#ifdef FSENGINE + +#include "fsengine/fsmanager.h" + +FSManager * fsmanager = 0; + +#endif + + +void NifSkope::copySettings(QSettings & cfg, const QSettings & oldcfg, const QString name) const +{ + if ((!cfg.contains(name)) && oldcfg.contains(name)) { + //qDebug() << "copying nifskope setting" << name; + cfg.setValue(name, oldcfg.value(name)); + }; +}; + +void NifSkope::migrateSettings() const +{ + // load current nifskope settings + NIFSKOPE_QSETTINGS(cfg); + // check for older nifskope settings + for (QStringList::ConstIterator it = NIFSKOPE_OLDERVERSIONS.begin(); it != NIFSKOPE_OLDERVERSIONS.end(); ++it ) { + QSettings oldcfg( "NifTools", *it ); + // check for non-binary missing keys and copy them from old settings + QStringList keys = oldcfg.allKeys(); + for (QStringList::ConstIterator key = keys.begin(); key != keys.end(); ++key) { + //qDebug() << "checking" << *key << oldcfg.value(*key).type(); + switch (oldcfg.value(*key).type()) { + case QVariant::Bool: + case QVariant::Int: + case QVariant::UInt: + case QVariant::Double: + case QVariant::String: + case QVariant::StringList: + // copy settings for these types + copySettings(cfg, oldcfg, *key); + default: + ; // do nothing + }; + }; + }; +}; + +/* + * main GUI window + */ + +void NifSkope::about() +{ + QString text = + "

NifSkope is a tool for analyzing and editing NetImmerse '.nif' files.

" + "

NifSkope is based on NifTool's XML file format specification. " + "For more informations visit our site at http://niftools.sourceforge.net

" + "

NifSkope is free software available under a BSD license. " + "The source is available via svn" + " on SourceForge.

" + "

The most recent version of NifSkope can always be downloaded from the " + "NifTools SourceForge Project page."; + + QMessageBox mb( tr("About NifSkope "NIFSKOPE_VERSION), text, QMessageBox::Information, + QMessageBox::Ok + QMessageBox::Default, 0, 0, this); + mb.setIconPixmap( QPixmap( ":/res/nifskope.png" ) ); + mb.exec(); +} + +NifSkope::NifSkope() + : QMainWindow(), selecting( false ), initialShowEvent( true ) +{ + // migrate settings from older versions of NifSkope + migrateSettings(); + + // create a new nif + nif = new NifModel( this ); + connect( nif, SIGNAL( sigMessage( const Message & ) ), this, SLOT( dispatchMessage( const Message & ) ) ); + + SpellBook * book = new SpellBook( nif, QModelIndex(), this, SLOT( select( const QModelIndex & ) ) ); + + // create a new hierarchical proxy nif + proxy = new NifProxyModel( this ); + proxy->setModel( nif ); + + // create a new kfm model + kfm = new KfmModel( this ); + connect( kfm, SIGNAL( sigMessage( const Message & ) ), this, SLOT( dispatchMessage( const Message & ) ) ); + + + // this view shows the block list + list = new NifTreeView; + list->setModel( nif ); + list->setItemDelegate( nif->createDelegate( book ) ); + list->header()->setStretchLastSection( true ); + list->header()->setMinimumSectionSize( 100 ); + list->installEventFilter( this ); + + connect( list, SIGNAL( sigCurrentIndexChanged( const QModelIndex & ) ), + this, SLOT( select( const QModelIndex & ) ) ); + connect( list, SIGNAL( customContextMenuRequested( const QPoint & ) ), + this, SLOT( contextMenu( const QPoint & ) ) ); + + // this view shows the whole nif file or the block details + tree = new NifTreeView; + tree->setModel( nif ); + tree->setItemDelegate( nif->createDelegate( book ) ); + tree->header()->setStretchLastSection( false ); + tree->installEventFilter( this ); + + connect( tree, SIGNAL( sigCurrentIndexChanged( const QModelIndex & ) ), + this, SLOT( select( const QModelIndex & ) ) ); + connect( tree, SIGNAL( customContextMenuRequested( const QPoint & ) ), + this, SLOT( contextMenu( const QPoint & ) ) ); + + + // this view shows the whole kfm file + kfmtree = new NifTreeView; + kfmtree->setModel( kfm ); + kfmtree->setItemDelegate( kfm->createDelegate() ); + kfmtree->header()->setStretchLastSection( false ); + kfmtree->installEventFilter( this ); + + connect( kfmtree, SIGNAL( customContextMenuRequested( const QPoint & ) ), + this, SLOT( contextMenu( const QPoint & ) ) ); + + // this browser shows the reference of current node + refrbrwsr = new ReferenceBrowser; + + refrbrwsr->setNifModel( nif ); + connect( tree, SIGNAL( sigCurrentIndexChanged( const QModelIndex & ) ), + refrbrwsr, SLOT( browse( const QModelIndex & ) ) ); + +#ifdef EDIT_ON_ACTIVATE + connect( list, SIGNAL( activated( const QModelIndex & ) ), + list, SLOT( edit( const QModelIndex & ) ) ); + connect( tree, SIGNAL( activated( const QModelIndex & ) ), + tree, SLOT( edit( const QModelIndex & ) ) ); + connect( kfmtree, SIGNAL( activated( const QModelIndex & ) ), + kfmtree, SLOT( edit( const QModelIndex & ) ) ); +#endif + + + // open gl + setCentralWidget( ogl = GLView::create() ); + ogl->setNif( nif ); + connect( ogl, SIGNAL( clicked( const QModelIndex & ) ), + this, SLOT( select( const QModelIndex & ) ) ); + connect( ogl, SIGNAL( customContextMenuRequested( const QPoint & ) ), + this, SLOT( contextMenu( const QPoint & ) ) ); + + + // actions + + aSanitize = new QAction( tr("&Auto Sanitize before Save"), this ); + aSanitize->setCheckable( true ); + aSanitize->setChecked( true ); + aLoadXML = new QAction( tr("Reload &XML"), this ); + connect( aLoadXML, SIGNAL( triggered() ), this, SLOT( loadXML() ) ); + aReload = new QAction( tr("&Reload XML + Nif"), this ); + aReload->setShortcut( Qt::ALT + Qt::Key_X ); + connect( aReload, SIGNAL( triggered() ), this, SLOT( reload() ) ); + aWindow = new QAction( tr("&New Window"), this ); + connect( aWindow, SIGNAL( triggered() ), this, SLOT( sltWindow() ) ); + aShredder = new QAction( tr("XML Checker" ), this ); + connect( aShredder, SIGNAL( triggered() ), this, SLOT( sltShredder() ) ); + aQuit = new QAction( tr("&Quit"), this ); + connect( aQuit, SIGNAL( triggered() ), qApp, SLOT( quit() ) ); + + aList = new QAction( tr("Show Blocks in List"), this ); + aList->setCheckable( true ); + aList->setChecked( list->model() == nif ); + + aHierarchy = new QAction( tr("Show Blocks in Tree"), this ); + aHierarchy->setCheckable( true ); + aHierarchy->setChecked( list->model() == proxy ); + + gListMode = new QActionGroup( this ); + connect( gListMode, SIGNAL( triggered( QAction * ) ), this, SLOT( setListMode() ) ); + gListMode->addAction( aList ); + gListMode->addAction( aHierarchy ); + gListMode->setExclusive( true ); + + aSelectFont = new QAction( tr("Select Font ..."), this ); + connect( aSelectFont, SIGNAL( triggered() ), this, SLOT( sltSelectFont() ) ); + + + /* help menu */ + + aHelpWebsite = new QAction( tr("NifSkope Documentation && &Tutorials"), this ); + aHelpWebsite->setData( QUrl("http://niftools.sourceforge.net/wiki/index.php/NifSkope") ); + connect( aHelpWebsite, SIGNAL( triggered() ), this, SLOT( openURL() ) ); + + aHelpForum = new QAction( tr("NifSkope Help && Bug Report &Forum"), this ); + aHelpForum->setData( QUrl("http://niftools.sourceforge.net/forum/viewforum.php?f=24") ); + connect( aHelpForum, SIGNAL( triggered() ), this, SLOT( openURL() ) ); + + aNifToolsWebsite = new QAction( tr("NifTools &Wiki"), this ); + aNifToolsWebsite->setData( QUrl("http://niftools.sourceforge.net") ); + connect( aNifToolsWebsite, SIGNAL( triggered() ), this, SLOT( openURL() ) ); + + aNifToolsDownloads = new QAction( tr("NifTools &Downloads"), this ); + aNifToolsDownloads->setData( QUrl("http://sourceforge.net/project/showfiles.php?group_id=149157") ); + connect( aNifToolsDownloads, SIGNAL( triggered() ), this, SLOT( openURL() ) ); + + aNifSkope = new QAction( tr("About &NifSkope"), this ); + connect( aNifSkope, SIGNAL( triggered() ), this, SLOT( about() ) ); + + aAboutQt = new QAction( tr("About &Qt"), this ); + connect( aAboutQt, SIGNAL( triggered() ), qApp, SLOT( aboutQt() ) ); + +#ifdef FSENGINE + if ( fsmanager ) + { + aResources = new QAction( tr("Resource Files"), this ); + connect( aResources, SIGNAL( triggered() ), fsmanager, SLOT( selectArchives() ) ); + } + else + { + aResources = 0; + } +#endif + + + // dock widgets + + dRefr = new QDockWidget( tr("Interactive Help") ); + dRefr->setObjectName( "RefrDock" ); + dRefr->setWidget( refrbrwsr ); + dRefr->toggleViewAction()->setShortcut( Qt::Key_F1 ); + dRefr->toggleViewAction()->setChecked( false ); + dRefr->setVisible( false ); + + dList = new QDockWidget( tr("Block List") ); + dList->setObjectName( "ListDock" ); + dList->setWidget( list ); + dList->toggleViewAction()->setShortcut( Qt::Key_F2 ); + connect( dList->toggleViewAction(), SIGNAL( toggled( bool ) ), tree, SLOT( clearRootIndex() ) ); + + dTree = new QDockWidget( tr("Block Details") ); + dTree->setObjectName( "TreeDock" ); + dTree->setWidget( tree ); + dTree->toggleViewAction()->setShortcut( Qt::Key_F3 ); + dTree->toggleViewAction()->setChecked( false ); + dTree->setVisible( false ); + + dKfm = new QDockWidget( tr("KFM") ); + dKfm->setObjectName( "KfmDock" ); + dKfm->setWidget( kfmtree ); + dKfm->toggleViewAction()->setShortcut( Qt::Key_F4 ); + dKfm->toggleViewAction()->setChecked( false ); + dKfm->setVisible( false ); + + addDockWidget( Qt::BottomDockWidgetArea, dRefr ); + addDockWidget( Qt::LeftDockWidgetArea, dList ); + addDockWidget( Qt::BottomDockWidgetArea, dTree ); + addDockWidget( Qt::RightDockWidgetArea, dKfm ); + + + /* ******** */ + + // tool bars + + // begin Load & Save toolbar + tool = new QToolBar( tr("Load & Save") ); + tool->setObjectName( "toolbar" ); + tool->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); + + QStringList fileExtensions( QStringList() << "*.nif" << "*.kf" << "*.kfa" << "*.kfm" << "*.nifcache" << "*.texcache" ); + + // create the load portion of the toolbar + tool->addWidget( lineLoad = new FileSelector( FileSelector::LoadFile, tr("&Load..."), QBoxLayout::RightToLeft ) ); + lineLoad->setFilter( fileExtensions ); + connect( lineLoad, SIGNAL( sigActivated( const QString & ) ), this, SLOT( load() ) ); + + // add the Load<=>Save filename copy widget + CopyFilename * cpFilename = new CopyFilename( this ); + cpFilename->setObjectName( "fileCopyWidget" ); + connect( cpFilename, SIGNAL( leftTriggered() ), + this, SLOT( copyFileNameSaveLoad() ) ); + connect( cpFilename, SIGNAL( rightTriggered() ), + this, SLOT( copyFileNameLoadSave() ) ); + tool->addWidget( cpFilename ); + + // create the save portion of the toolbar + tool->addWidget( lineSave = new FileSelector( FileSelector::SaveFile, tr("&Save As..."), QBoxLayout::LeftToRight ) ); + lineSave->setFilter( fileExtensions ); + connect( lineSave, SIGNAL( sigActivated( const QString & ) ), this, SLOT( save() ) ); + + addToolBar( Qt::TopToolBarArea, tool ); + // end Load & Save toolbar + + // begin OpenGL toolbars + foreach ( QToolBar * tb, ogl->toolbars() ) { + addToolBar( Qt::TopToolBarArea, tb ); + } + // end OpenGL toolbars + + /* ********* */ + + // menu + + // assemble the File menu + QMenu * mFile = new QMenu( tr("&File") ); + mFile->addActions( lineLoad->actions() ); + mFile->addActions( lineSave->actions() ); + mFile->addSeparator(); + mFile->addMenu( mImport = new QMenu( tr("Import") ) ); + mFile->addMenu( mExport = new QMenu( tr("Export") ) ); + mFile->addSeparator(); + mFile->addAction( aSanitize ); + mFile->addSeparator(); + mFile->addAction( aWindow ); + mFile->addSeparator(); + mFile->addAction( aLoadXML ); + mFile->addAction( aReload ); + mFile->addAction( aShredder ); + +#ifdef FSENGINE + if ( aResources ) + { + mFile->addSeparator(); + mFile->addAction( aResources ); + } +#endif + mFile->addSeparator(); + mFile->addAction( aQuit ); + + QMenu * mView = new QMenu( tr("&View") ); + mView->addAction( dRefr->toggleViewAction() ); + mView->addAction( dList->toggleViewAction() ); + mView->addAction( dTree->toggleViewAction() ); + mView->addAction( dKfm->toggleViewAction() ); + mView->addSeparator(); + QMenu * mTools = new QMenu( tr("&Toolbars") ); + mView->addMenu( mTools ); + foreach ( QObject * o, children() ) + { + QToolBar * tb = qobject_cast( o ); + if ( tb ) + mTools->addAction( tb->toggleViewAction() ); + } + mView->addSeparator(); + mView->addAction( aHierarchy ); + mView->addAction( aList ); + mView->addSeparator(); + mView->addAction( aSelectFont ); + + QMenu * mAbout = new QMenu( tr("&Help") ); + mAbout->addAction( dRefr->toggleViewAction() ); + mAbout->addAction( aHelpWebsite ); + mAbout->addAction( aHelpForum ); + mAbout->addSeparator(); + mAbout->addAction( aNifToolsWebsite ); + mAbout->addAction( aNifToolsDownloads ); + mAbout->addSeparator(); + mAbout->addAction( aAboutQt ); + mAbout->addAction( aNifSkope ); + + menuBar()->addMenu( mFile ); + menuBar()->addMenu( mView ); + menuBar()->addMenu( ogl->createMenu() ); + menuBar()->addMenu( book ); + menuBar()->addMenu( mAbout ); + + fillImportExportMenus(); + connect( mExport, SIGNAL( triggered( QAction * ) ), this, SLOT( sltImportExport( QAction * ) ) ); + connect( mImport, SIGNAL( triggered( QAction * ) ), this, SLOT( sltImportExport( QAction * ) ) ); +} + +NifSkope::~NifSkope() +{ +} + +void NifSkope::closeEvent( QCloseEvent * e ) +{ + NIFSKOPE_QSETTINGS(settings); + save( settings ); + + QMainWindow::closeEvent( e ); +} + +void restoreHeader( const QString & name, const QSettings & settings, QHeaderView * header ) +{ + QByteArray b = settings.value( name ).value(); + if ( b.isEmpty() ) + return; + QDataStream d( &b, QIODevice::ReadOnly ); + int s; + d >> s; + if ( s != header->count() ) + return; + for ( int c = 0; c < header->count(); c++ ) + { + d >> s; + header->resizeSection( c, s ); + } +} + +void NifSkope::restore( const QSettings & settings ) +{ + restoreGeometry( settings.value( "window geometry" ).toByteArray() ); + restoreState( settings.value( "window state" ).toByteArray(), 0x073 ); + + lineLoad->setText( settings.value( "last load", QString( "" ) ).toString() ); + lineSave->setText( settings.value( "last save", QString( "" ) ).toString() ); + aSanitize->setChecked( settings.value( "auto sanitize", true ).toBool() ); + + if ( settings.value( "list mode", "hirarchy" ).toString() == "list" ) + aList->setChecked( true ); + else + aHierarchy->setChecked( true ); + setListMode(); + + restoreHeader( "list sizes", settings, list->header() ); + restoreHeader( "tree sizes", settings, tree->header() ); + restoreHeader( "kfmtree sizes", settings, kfmtree->header() ); + + ogl->restore( settings ); + + QVariant fontVar = settings.value( "viewFont" ); + if ( fontVar.canConvert() ) + setViewFont( fontVar.value() ); +} + +void saveHeader( const QString & name, QSettings & settings, QHeaderView * header ) +{ + QByteArray b; + QDataStream d( &b, QIODevice::WriteOnly ); + d << header->count(); + for ( int c = 0; c < header->count(); c++ ) + d << header->sectionSize( c ); + settings.setValue( name, b ); +} + +void NifSkope::save( QSettings & settings ) const +{ + settings.setValue( "window state", saveState( 0x073 ) ); + settings.setValue( "window geometry", saveGeometry() ); + + settings.setValue( "last load", lineLoad->text() ); + settings.setValue( "last save", lineSave->text() ); + settings.setValue( "auto sanitize", aSanitize->isChecked() ); + + settings.setValue( "list mode", ( gListMode->checkedAction() == aList ? "list" : "hirarchy" ) ); + saveHeader( "list sizes", settings, list->header() ); + + saveHeader( "tree sizes", settings, tree->header() ); + + saveHeader( "kfmtree sizes", settings, kfmtree->header() ); + + ogl->save( settings ); +} + +void NifSkope::contextMenu( const QPoint & pos ) +{ + QModelIndex idx; + QPoint p = pos; + if ( sender() == tree ) + { + idx = tree->indexAt( pos ); + p = tree->mapToGlobal( pos ); + } + else if ( sender() == list ) + { + idx = list->indexAt( pos ); + p = list->mapToGlobal( pos ); + } + else if ( sender() == ogl ) + { + idx = ogl->indexAt( pos ); + p = ogl->mapToGlobal( pos ); + } + else + return; + + while ( idx.model() && idx.model()->inherits( "NifProxyModel" ) ) + { + idx = qobject_cast( idx.model() )->mapTo( idx ); + } + + SpellBook book( nif, idx, this, SLOT( select( const QModelIndex & ) ) ); + book.exec( p ); +} + +void NifSkope::select( const QModelIndex & index ) +{ + if ( selecting ) + return; + + QModelIndex idx = index; + + if ( idx.model() == proxy ) + idx = proxy->mapTo( index ); + + if ( idx.isValid() && idx.model() != nif ) + return; + + selecting = true; + + if ( sender() != ogl ) + { + ogl->setCurrentIndex( idx ); + } + + if ( sender() != list ) + { + if ( list->model() == proxy ) + { + QModelIndex pidx = proxy->mapFrom( nif->getBlock( idx ), list->currentIndex() ); + list->setCurrentIndex( pidx ); + } + else if ( list->model() == nif ) + { + list->setCurrentIndex( nif->getBlockOrHeader( idx ) ); + } + } + + if ( sender() != tree ) + { + if ( dList->isVisible() ) + { + QModelIndex root = nif->getBlockOrHeader( idx ); + if ( tree->rootIndex() != root ) + tree->setRootIndex( root ); + tree->setCurrentIndex( idx.sibling( idx.row(), 0 ) ); + } + else + { + if ( tree->rootIndex() != QModelIndex() ) + tree->setRootIndex( QModelIndex() ); + tree->setCurrentIndex( idx.sibling( idx.row(), 0 ) ); + } + } + selecting = false; +} + +void NifSkope::setListMode() +{ + QModelIndex idx = list->currentIndex(); + QAction * a = gListMode->checkedAction(); + if ( !a || a == aList ) + { + if ( list->model() != nif ) + { + QHeaderView * head = list->header(); + int s0 = head->sectionSize( head->logicalIndex( 0 ) ); + int s1 = head->sectionSize( head->logicalIndex( 1 ) ); + list->setModel( nif ); + list->setItemsExpandable( false ); + list->setRootIsDecorated( false ); + list->setCurrentIndex( proxy->mapTo( idx ) ); + list->setColumnHidden( NifModel::TypeCol, true ); + list->setColumnHidden( NifModel::ArgCol, true ); + list->setColumnHidden( NifModel::Arr1Col, true ); + list->setColumnHidden( NifModel::Arr2Col, true ); + list->setColumnHidden( NifModel::CondCol, true ); + list->setColumnHidden( NifModel::Ver1Col, true ); + list->setColumnHidden( NifModel::Ver2Col, true ); + head->resizeSection( 0, s0 ); + head->resizeSection( 1, s1 ); + } + } + else + { + if ( list->model() != proxy ) + { + QHeaderView * head = list->header(); + int s0 = head->sectionSize( head->logicalIndex( 0 ) ); + int s1 = head->sectionSize( head->logicalIndex( 1 ) ); + list->setModel( proxy ); + list->setItemsExpandable( true ); + list->setRootIsDecorated( true ); + QModelIndex pidx = proxy->mapFrom( idx, QModelIndex() ); + list->setCurrentIndex( pidx ); + head->resizeSection( 0, s0 ); + head->resizeSection( 1, s1 ); + } + } +} + +void NifSkope::load( const QString & filepath ) +{ + lineLoad->setText( filepath ); + QTimer::singleShot( 0, this, SLOT( load() ) ); +} + +void NifSkope::load() +{ + setEnabled( false ); + + QFileInfo niffile( QDir::fromNativeSeparators( lineLoad->text() ) ); + niffile.makeAbsolute(); + + if ( niffile.suffix().compare( "kfm", Qt::CaseInsensitive ) == 0 ) + { + lineLoad->rstState(); + lineSave->rstState(); + if ( !kfm->loadFromFile( niffile.filePath() ) ) { + qWarning() << tr("failed to load kfm from '%1'").arg( niffile.filePath() ); + lineLoad->setState( FileSelector::stError ); + } + else { + lineLoad->setState( FileSelector::stSuccess ); + lineLoad->setText( niffile.filePath() ); + lineSave->setText( niffile.filePath() ); + } + + niffile.setFile( kfm->getFolder(), + kfm->get( kfm->getKFMroot(), "NIF File Name" ) ); + } + + ogl->tAnim->setEnabled( false ); + + if ( !niffile.isFile() ) + { + nif->clear(); + lineLoad->setState( FileSelector::stError ); + setWindowTitle( "NifSkope" ); + } + else + { + ProgDlg prog; + prog.setLabelText( tr("loading nif...") ); + prog.setRange( 0, 1 ); + prog.setValue( 0 ); + prog.setMinimumDuration( 2100 ); + connect( nif, SIGNAL( sigProgress( int, int ) ), & prog, SLOT( sltProgress( int, int ) ) ); + + lineLoad->rstState(); + lineSave->rstState(); + if ( !nif->loadFromFile( niffile.filePath() ) ) { + qWarning() << tr("failed to load nif from '%1'").arg( niffile.filePath() ); + lineLoad->setState( FileSelector::stError ); + } + else { + lineLoad->setState( FileSelector::stSuccess ); + lineLoad->setText( niffile.filePath() ); + lineSave->setText( niffile.filePath() ); + } + + setWindowTitle( "NifSkope - " + niffile.fileName() ); + } + + ogl->tAnim->setEnabled( true ); + ogl->center(); + + setEnabled( true ); +} + +void ProgDlg::sltProgress( int x, int y ) +{ + setRange( 0, y ); + setValue( x ); + qApp->processEvents(); +} + +void NifSkope::save() +{ + // write to file + setEnabled( false ); + + QString nifname = lineSave->text(); + + if ( nifname.endsWith( ".KFM", Qt::CaseInsensitive ) ) + { + lineSave->rstState(); + if ( ! kfm->saveToFile( nifname ) ) { + qWarning() << tr("failed to write kfm file") << nifname; + lineSave->setState(FileSelector::stError); + } + else { + lineSave->setState(FileSelector::stSuccess); + } + } + else + { + lineSave->rstState(); + + if ( aSanitize->isChecked() ) + { + QModelIndex idx = SpellBook::sanitize( nif ); + if ( idx.isValid() ) + select( idx ); + } + + if ( ! nif->saveToFile( nifname ) ) { + qWarning() << tr("failed to write nif file ") << nifname; + lineSave->setState(FileSelector::stError); + } + else { + lineSave->setState(FileSelector::stSuccess); + } + + setWindowTitle( "NifSkope - " + nifname.right( nifname.length() - nifname.lastIndexOf( '/' ) - 1 ) ); + } + setEnabled( true ); +} + +void NifSkope::copyFileNameLoadSave() +{ + if(lineLoad->text().isEmpty()) { + return; + } + + lineSave->replaceText( lineLoad->text() ); +} + +void NifSkope::copyFileNameSaveLoad() +{ + if(lineSave->text().isEmpty()) { + return; + } + + lineLoad->replaceText( lineSave->text() ); +} + +void NifSkope::sltWindow() +{ + createWindow(); +} + +void NifSkope::sltShredder() +{ + TestShredder::create(); +} + +void NifSkope::openURL() +{ + if( !sender() ) return; + + QAction * aURL = qobject_cast( sender() ); + if( !aURL ) return; + + QUrl URL = aURL->data().toUrl(); + if( !URL.isValid() ) return; + + QDesktopServices::openUrl( URL ); +} + + +NifSkope * NifSkope::createWindow( const QString & fname ) +{ + NifSkope * skope = new NifSkope; + skope->setAttribute( Qt::WA_DeleteOnClose ); + NIFSKOPE_QSETTINGS(settings); + skope->restore( settings ); + skope->show(); + + skope->raise(); + + if ( ! fname.isEmpty() ) + { + skope->lineLoad->setFile( fname ); + QTimer::singleShot( 0, skope, SLOT( load() ) ); + } + return skope; +} + +void NifSkope::loadXML() +{ + NifModel::loadXML(); + KfmModel::loadXML(); +} + +void NifSkope::reload() +{ + if ( NifModel::loadXML() ) + { + load(); + } +} + +void NifSkope::sltSelectFont() +{ + bool ok; + QFont fnt = QFontDialog::getFont( & ok, list->font(), this ); + if ( ! ok ) + return; + setViewFont( fnt ); + QSettings settings; + settings.setValue( "viewFont", fnt ); +} + +void NifSkope::setViewFont( const QFont & font ) +{ + list->setFont( font ); + QFontMetrics metrics( list->font() ); + list->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); + tree->setFont( font ); + tree->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); + kfmtree->setFont( font ); + kfmtree->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); + ogl->setFont( font ); +} + +bool NifSkope::eventFilter( QObject * o, QEvent * e ) +{ + if ( e->type() == QEvent::Polish ) + { + QTimer::singleShot( 0, this, SLOT( overrideViewFont() ) ); + } + return QMainWindow::eventFilter( o, e ); +} + +void NifSkope::overrideViewFont() +{ + QSettings settings; + QVariant var = settings.value( "viewFont" ); + if ( var.canConvert() ) + { + setViewFont( var.value() ); + } +} + +void NifSkope::dispatchMessage( const Message & msg ) +{ + switch ( msg.type() ) + { + case QtCriticalMsg: + qCritical() << msg; + break; + case QtFatalMsg: + qFatal( QString( msg ).toAscii().data() ); + break; + case QtWarningMsg: + qWarning() << msg; + break; + case QtDebugMsg: + default: + qDebug() << msg; + break; + } +} + +QTextEdit * msgtarget = 0; + +void myMessageOutput(QtMsgType type, const char *msg) +{ + switch (type) + { + case QtDebugMsg: + printf( "%s\n", msg ); + break; + case QtWarningMsg: + // workaround for Qt 4.2.2 + if ( QString( "edit: editing failed" ) == msg ) + return; + case QtCriticalMsg: + if ( ! msgtarget ) + { + msgtarget = new QTextEdit; + msgtarget->setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); + } + if ( ! msgtarget->isVisible() ) + { + msgtarget->clear(); + msgtarget->show(); + } + + msgtarget->append( msg ); + break; + case QtFatalMsg: + QMessageBox::critical( 0, QMessageBox::tr("Fatal Error"), msg ); + abort(); + } +} + + +/* + * IPC socket + */ + +IPCsocket * IPCsocket::create() +{ + QUdpSocket * udp = new QUdpSocket(); + + if ( udp->bind( QHostAddress( QHostAddress::LocalHost ), NIFSKOPE_IPC_PORT, QUdpSocket::DontShareAddress ) ) + { + IPCsocket * ipc = new IPCsocket( udp ); + QDesktopServices::setUrlHandler( "nif", ipc, "openNif" ); + return ipc; + } + + return 0; +} + +void IPCsocket::sendCommand( const QString & cmd ) +{ + QUdpSocket udp; + udp.writeDatagram( (const char *) cmd.data(), cmd.length() * sizeof( QChar ), QHostAddress( QHostAddress::LocalHost ), NIFSKOPE_IPC_PORT ); +} + +IPCsocket::IPCsocket( QUdpSocket * s ) : QObject(), socket( s ) +{ + QObject::connect( socket, SIGNAL( readyRead() ), this, SLOT( processDatagram() ) ); + +#ifdef FSENGINE + if ( ! fsmanager ) + fsmanager = new FSManager( this ); +#endif +} + +IPCsocket::~IPCsocket() +{ + delete socket; +} + +void IPCsocket::processDatagram() +{ + while ( socket->hasPendingDatagrams() ) + { + QByteArray data; + data.resize( socket->pendingDatagramSize() ); + QHostAddress host; + quint16 port = 0; + + socket->readDatagram( data.data(), data.size(), &host, &port ); + if ( host == QHostAddress( QHostAddress::LocalHost ) && ( data.size() % sizeof( QChar ) ) == 0 ) + { + QString cmd; + cmd.setUnicode( (QChar *) data.data(), data.size() / sizeof( QChar ) ); + execCommand( cmd ); + } + } +} + +void IPCsocket::execCommand( const QString & cmd ) +{ + if ( cmd.startsWith( "NifSkope::open" ) ) + { + openNif( cmd.right( cmd.length() - 15 ) ); + } +} + +void IPCsocket::openNif( const QUrl & url ) +{ + NifSkope::createWindow( url.toString( url.scheme() == "nif" ? QUrl::RemoveScheme : QUrl::None ) ); +} + + +// Qt does not use the System Locale consistency so this basically forces all floating +// numbers into C format but leaves all other local specific settings. +class NifSystemLocale : QSystemLocale +{ + virtual QVariant query(QueryType type, QVariant in) const + { + switch (type) + { + case DecimalPoint: + return QVariant( QLocale::c().decimalPoint() ); + case GroupSeparator: + return QVariant( QLocale::c().groupSeparator() ); + default: + return QVariant(); + } + } +}; + +/* + * main + */ + +int main( int argc, char * argv[] ) +{ + NifSystemLocale mLocale; + + // set up the Qt Application + QApplication app( argc, argv ); + app.setOrganizationName( "NifTools" ); + app.setApplicationName( "NifSkope" ); + app.setOrganizationDomain( "niftools.sourceforge.net" ); + + // install message handler + qRegisterMetaType( "Message" ); +#ifndef NO_MESSAGEHANDLER + qInstallMsgHandler( myMessageOutput ); +#endif + + // if there is a style sheet present then load it + QDir qssDir; + bool qssFound; + // look for stylesheet in application directory + qssDir.setPath(QApplication::applicationDirPath()); + qssFound = qssDir.exists("style.qss"); + // look for stylesheet in linux nifskope data directory + if (!qssFound) + { + qssDir.setPath("/usr/share/nifskope"); + qssFound = qssDir.exists("style.qss"); + } + // load the style sheet if present + if (qssFound) + { + QFile style( qssDir.filePath( "style.qss" ) ); + if ( style.open( QFile::ReadOnly ) ) + { + app.setStyleSheet( style.readAll() ); + style.close(); + } + } + + // set the translation + QString locale = QLocale::system().name(); + + QTranslator translator; + translator.load( QString( ":lang/" ) + locale ); + app.installTranslator( &translator ); + + NifModel::loadXML(); + KfmModel::loadXML(); + + QString fname; + if ( app.argc() > 1 ) + { + //Getting a NIF file name from the OS + fname = QDir::current().filePath( QString( app.argv()[ app.argc() - 1 ] ) ); + +#ifdef WIN32 + //Windows passes an ugly 8.3 file path as an argument, so use a WinAPI function to fix that + wchar_t full[MAX_PATH]; + wchar_t * temp_name = new wchar_t[fname.size() + 1]; + + fname.toWCharArray( temp_name ); + temp_name[fname.size()] = 0; //The above function doesn't seem to write a null character, so add it. + + //Ensure that input is a full path, even if a partial one was given on the command line + DWORD ret = GetFullPathNameW( temp_name, MAX_PATH, full, NULL ); + + if ( ret != 0 ) + { + delete [] temp_name; + temp_name = new wchar_t[MAX_PATH]; + + //Finally get the full long file name version of the path + ret = GetLongPathNameW( full, temp_name, MAX_PATH ); + + //Copy the name back to the QString variable that Qt uses + if ( ret != 0 ) + { + //GetLongPath succeeded + fname = QString::fromWCharArray( temp_name); + } else + { + //GetLongPath failed, use result from GetFullPathName function + fname = QString::fromWCharArray( full ); + } + } + + delete [] temp_name; +#endif + } + + if ( IPCsocket * ipc = IPCsocket::create() ) + { + ipc->execCommand( QString( "NifSkope::open %1" ).arg( fname ) ); + return app.exec(); + } + else + { + IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fname ) ); + return 0; + } +} diff --git a/nifskope.h b/nifskope.h index a85cdbcef..2384dd493 100644 --- a/nifskope.h +++ b/nifskope.h @@ -1,264 +1,264 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef NIFSKOPE_H -#define NIFSKOPE_H - -#include -#include - -#define NIFSKOPE_IPC_PORT 12583 - -class NifModel; -class NifProxyModel; -class KfmModel; -class NifTreeView; -class ReferenceBrowser; - -class GLView; -class FileSelector; - -class QModelIndex; - -class QAction; -class QActionGroup; -class QSettings; -class QSlider; -class QSpinBox; -class QTextEdit; - -class QUdpSocket; - -#include "message.h" - -//! The main application class for NifSkope. -/*! - * This class encapsulates the main NifSkope window. It has members for saving - * and restoring settings, loading and saving nif files, loading an xml - * description, widgets for the various subwindows, menu's, and a socket by - * which NifSkope can communicate with itself. - */ -class NifSkope : public QMainWindow -{ -Q_OBJECT -public: - NifSkope(); - ~NifSkope(); - - //! Create and initialize a new NifSkope application window. - /*! - * \param fname The name of the file to load in the new NifSkope window. - * \return The newly created NifSkope instance. - */ - static NifSkope * createWindow( const QString & fname = QString() ); - - //! Save NifSkope application settings. - /*! - * \param settings The QSettings object used to store the settings. - */ - void save( QSettings & settings ) const; - - //! Restore NifSkope application settings. - /*! - * \param settings The QSettings object to restore the settings from. - */ - void restore( const QSettings & settings ); - -public slots: - //! Set the lineLoad string and load a nif, kf, or kfm file. - /*! - * \param filepath The file to load. - */ - void load( const QString & filepath ); - - //! Load a nif, kf, or kfm file, taking the file path from the lineLoad widget. - void load(); - - //! Save a nif, kf, or kfm file, taking the file path from the lineSave widget. - void save(); - - //! Reparse the nif.xml and kfm.xml files. - void loadXML(); - - //! Reparse the nif.xml and kfm.xml files and reload the current file. - void reload(); - - //! A slot that creates a new NifSkope application window. - void sltWindow(); - - //! A slot for starting the XML checker. - void sltShredder(); - - //! Display the "About NifSkope" window. - void about(); - -protected slots: - void select( const QModelIndex & ); - - void contextMenu( const QPoint & pos ); - - void setListMode(); - - void sltSelectFont(); - - void dispatchMessage( const Message & msg ); - - void overrideViewFont(); - - void copyFileNameLoadSave(); - void copyFileNameSaveLoad(); - - void fillImportExportMenus(); - void sltImportExport( QAction * action ); - - void openURL(); - -protected: - void closeEvent( QCloseEvent * e ); - bool eventFilter( QObject * o, QEvent * e ); - -private: - void initActions(); - void initDockWidgets(); - void initToolBars(); - void initMenu(); - - void setViewFont( const QFont & ); - - //! Copy settings from one config to another, without overwriting keys. - /*! - * This is a helper function for migrateSettings(). - */ - void copySettings(QSettings & cfg, const QSettings & oldcfg, const QString name) const; - - //! Migrate settings from older versions of nifskope. - void migrateSettings() const; - - //! Stores the nif file in memory. - NifModel * nif; - //! A hierarchical proxy for the nif file. - NifProxyModel * proxy; - //! Stores the kfm file in memory. - KfmModel * kfm; - - //! This view shows the block list. - NifTreeView * list; - //! This view shows the whole nif file or the block details. - NifTreeView * tree; - NifTreeView * kfmtree; - - ReferenceBrowser * refrbrwsr; - - GLView * ogl; - - bool selecting; - bool initialShowEvent; - - FileSelector * lineLoad; - FileSelector * lineSave; - - QDockWidget * dList; - QDockWidget * dTree; - QDockWidget * dKfm; - QDockWidget * dRefr; - - QToolBar * tool; - - QAction * aSanitize; - QAction * aLoadXML; - QAction * aReload; - QAction * aWindow; - QAction * aShredder; - QAction * aQuit; - -#ifdef FSENGINE - QAction * aResources; -#endif - - QActionGroup * gListMode; - QAction * aList; - QAction * aHierarchy; - - QAction * aSelectFont; - - QAction * aHelpWebsite; - QAction * aHelpForum; - QAction * aNifToolsWebsite; - QAction * aNifToolsDownloads; - - QAction * aNifSkope; - QAction * aAboutQt; - - QMenu * mExport; - QMenu * mImport; -}; - -class IPCsocket : public QObject -{ - Q_OBJECT -public: - static IPCsocket * create(); - - static void sendCommand( const QString & cmd ); - -public slots: - void execCommand( const QString & cmd ); - - void openNif( const QUrl & ); - -protected slots: - void processDatagram(); - -protected: - IPCsocket( QUdpSocket * ); - ~IPCsocket(); - - QUdpSocket * socket; -}; - -class ProgDlg : public QProgressDialog -{ - Q_OBJECT -public: - ProgDlg() {} - -public slots: - void sltProgress( int, int ); -}; - -/*! \mainpage NifSkope API Documentation - * - * A concise description of nifskope's inner workings should come here, with - * pointers to get people started. - */ - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef NIFSKOPE_H +#define NIFSKOPE_H + +#include +#include + +#define NIFSKOPE_IPC_PORT 12583 + +class NifModel; +class NifProxyModel; +class KfmModel; +class NifTreeView; +class ReferenceBrowser; + +class GLView; +class FileSelector; + +class QModelIndex; + +class QAction; +class QActionGroup; +class QSettings; +class QSlider; +class QSpinBox; +class QTextEdit; + +class QUdpSocket; + +#include "message.h" + +//! The main application class for NifSkope. +/*! + * This class encapsulates the main NifSkope window. It has members for saving + * and restoring settings, loading and saving nif files, loading an xml + * description, widgets for the various subwindows, menu's, and a socket by + * which NifSkope can communicate with itself. + */ +class NifSkope : public QMainWindow +{ +Q_OBJECT +public: + NifSkope(); + ~NifSkope(); + + //! Create and initialize a new NifSkope application window. + /*! + * \param fname The name of the file to load in the new NifSkope window. + * \return The newly created NifSkope instance. + */ + static NifSkope * createWindow( const QString & fname = QString() ); + + //! Save NifSkope application settings. + /*! + * \param settings The QSettings object used to store the settings. + */ + void save( QSettings & settings ) const; + + //! Restore NifSkope application settings. + /*! + * \param settings The QSettings object to restore the settings from. + */ + void restore( const QSettings & settings ); + +public slots: + //! Set the lineLoad string and load a nif, kf, or kfm file. + /*! + * \param filepath The file to load. + */ + void load( const QString & filepath ); + + //! Load a nif, kf, or kfm file, taking the file path from the lineLoad widget. + void load(); + + //! Save a nif, kf, or kfm file, taking the file path from the lineSave widget. + void save(); + + //! Reparse the nif.xml and kfm.xml files. + void loadXML(); + + //! Reparse the nif.xml and kfm.xml files and reload the current file. + void reload(); + + //! A slot that creates a new NifSkope application window. + void sltWindow(); + + //! A slot for starting the XML checker. + void sltShredder(); + + //! Display the "About NifSkope" window. + void about(); + +protected slots: + void select( const QModelIndex & ); + + void contextMenu( const QPoint & pos ); + + void setListMode(); + + void sltSelectFont(); + + void dispatchMessage( const Message & msg ); + + void overrideViewFont(); + + void copyFileNameLoadSave(); + void copyFileNameSaveLoad(); + + void fillImportExportMenus(); + void sltImportExport( QAction * action ); + + void openURL(); + +protected: + void closeEvent( QCloseEvent * e ); + bool eventFilter( QObject * o, QEvent * e ); + +private: + void initActions(); + void initDockWidgets(); + void initToolBars(); + void initMenu(); + + void setViewFont( const QFont & ); + + //! Copy settings from one config to another, without overwriting keys. + /*! + * This is a helper function for migrateSettings(). + */ + void copySettings(QSettings & cfg, const QSettings & oldcfg, const QString name) const; + + //! Migrate settings from older versions of nifskope. + void migrateSettings() const; + + //! Stores the nif file in memory. + NifModel * nif; + //! A hierarchical proxy for the nif file. + NifProxyModel * proxy; + //! Stores the kfm file in memory. + KfmModel * kfm; + + //! This view shows the block list. + NifTreeView * list; + //! This view shows the whole nif file or the block details. + NifTreeView * tree; + NifTreeView * kfmtree; + + ReferenceBrowser * refrbrwsr; + + GLView * ogl; + + bool selecting; + bool initialShowEvent; + + FileSelector * lineLoad; + FileSelector * lineSave; + + QDockWidget * dList; + QDockWidget * dTree; + QDockWidget * dKfm; + QDockWidget * dRefr; + + QToolBar * tool; + + QAction * aSanitize; + QAction * aLoadXML; + QAction * aReload; + QAction * aWindow; + QAction * aShredder; + QAction * aQuit; + +#ifdef FSENGINE + QAction * aResources; +#endif + + QActionGroup * gListMode; + QAction * aList; + QAction * aHierarchy; + + QAction * aSelectFont; + + QAction * aHelpWebsite; + QAction * aHelpForum; + QAction * aNifToolsWebsite; + QAction * aNifToolsDownloads; + + QAction * aNifSkope; + QAction * aAboutQt; + + QMenu * mExport; + QMenu * mImport; +}; + +class IPCsocket : public QObject +{ + Q_OBJECT +public: + static IPCsocket * create(); + + static void sendCommand( const QString & cmd ); + +public slots: + void execCommand( const QString & cmd ); + + void openNif( const QUrl & ); + +protected slots: + void processDatagram(); + +protected: + IPCsocket( QUdpSocket * ); + ~IPCsocket(); + + QUdpSocket * socket; +}; + +class ProgDlg : public QProgressDialog +{ + Q_OBJECT +public: + ProgDlg() {} + +public slots: + void sltProgress( int, int ); +}; + +/*! \mainpage NifSkope API Documentation + * + * A concise description of nifskope's inner workings should come here, with + * pointers to get people started. + */ + +#endif diff --git a/niftypes.cpp b/niftypes.cpp index ed34e6d1c..f1f16c1ac 100644 --- a/niftypes.cpp +++ b/niftypes.cpp @@ -1,424 +1,424 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "niftypes.h" -#include "nifmodel.h" - -#include - -const float Quat::identity[4] = { 1.0, 0.0, 0.0, 0.0 }; -const float Matrix::identity[9] = { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; -const float Matrix4::identity[16] = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 }; - -QString NumOrMinMax( float val, char f, int prec ) -{ - if( val == -FLT_MAX ) - return ""; - else if( val == FLT_MAX ) - return ""; - - return QString::number( val, f, prec ); -} - -void Vector2::fromString( QString str ) -{ - QStringList parts = str.split( "," ); - if( parts.size() != 2 ) - return; - bool xOk, yOk; - float x_ = parts[0].toFloat( &xOk ); - float y_ = parts[1].toFloat( &yOk ); - if( xOk && yOk ) { - xy[0] = x_; - xy[1] = y_; - } -} - -void Vector3::fromString( QString str ) -{ - QStringList parts = str.split( "," ); - if( parts.size() != 3 ) - return; - bool xOk, yOk, zOk; - float x_ = parts[0].toFloat( &xOk ); - float y_ = parts[1].toFloat( &yOk ); - float z_ = parts[2].toFloat( &zOk ); - if( xOk && yOk && zOk ) { - xyz[0] = x_; - xyz[1] = y_; - xyz[2] = z_; - } -} - -void Vector4::fromString( QString str ) -{ - QStringList parts = str.split( "," ); - if( parts.size() != 4 ) - return; - bool xOk, yOk, zOk, wOk; - float x_ = parts[0].toFloat( &xOk ); - float y_ = parts[1].toFloat( &yOk ); - float z_ = parts[2].toFloat( &zOk ); - float w_ = parts[3].toFloat( &wOk ); - if( xOk && yOk && zOk && wOk ) { - xyzw[0] = x_; - xyzw[1] = y_; - xyzw[2] = z_; - xyzw[3] = w_; - } -} - -void Quat::fromString( QString str ) -{ - QStringList parts = str.split( "," ); - if( parts.size() != 4 ) - return; - bool wOk, xOk, yOk, zOk; - float w_ = parts[3].toFloat( &wOk ); - float x_ = parts[0].toFloat( &xOk ); - float y_ = parts[1].toFloat( &yOk ); - float z_ = parts[2].toFloat( &zOk ); - if( wOk && xOk&& yOk && zOk ) { - wxyz[0] = w_; - wxyz[1] = x_; - wxyz[2] = y_; - wxyz[3] = z_; - } -} - -void Matrix::fromQuat( const Quat & q ) -{ - float fTx = ((float)2.0)*q[1]; - float fTy = ((float)2.0)*q[2]; - float fTz = ((float)2.0)*q[3]; - float fTwx = fTx*q[0]; - float fTwy = fTy*q[0]; - float fTwz = fTz*q[0]; - float fTxx = fTx*q[1]; - float fTxy = fTy*q[1]; - float fTxz = fTz*q[1]; - float fTyy = fTy*q[2]; - float fTyz = fTz*q[2]; - float fTzz = fTz*q[3]; - - m[0][0] = (float)1.0-(fTyy+fTzz); - m[0][1] = fTxy-fTwz; - m[0][2] = fTxz+fTwy; - m[1][0] = fTxy+fTwz; - m[1][1] = (float)1.0-(fTxx+fTzz); - m[1][2] = fTyz-fTwx; - m[2][0] = fTxz-fTwy; - m[2][1] = fTyz+fTwx; - m[2][2] = (float)1.0-(fTxx+fTyy); -} - -Quat Matrix::toQuat() const -{ - Quat q; - - float trace = m[0][0] + m[1][1] + m[2][2]; - float root; - - if (trace > 0.0) - { - root = sqrt( trace + 1.0 ); - q[0] = root / 2.0; - root = 0.5 / root; - q[1] = ( m[2][1] - m[1][2] ) * root; - q[2] = ( m[0][2] - m[2][0] ) * root; - q[3] = ( m[1][0] - m[0][1] ) * root; - } - else - { - int i = ( m[1][1] > m[0][0] ? 1 : 0 ); - if ( m[2][2] > m[i][i] ) - i = 2; - const int next[3] = { 1, 2, 0 }; - int j = next[i]; - int k = next[j]; - - root = sqrt( m[i][i] - m[j][j] - m[k][k] + 1.0 ); - q[i+1] = root / 2; - root = 0.5 / root; - q[0] = ( m[k][j] - m[j][k] ) * root; - q[j+1] = ( m[j][i] + m[i][j] ) * root; - q[k+1] = ( m[k][i] + m[i][k] ) * root; - } - return q; -} - -void Matrix::fromEuler( float x, float y, float z ) -{ - float sinX = sin( x ); - float cosX = cos( x ); - float sinY = sin( y ); - float cosY = cos( y ); - float sinZ = sin( z ); - float cosZ = cos( z ); - - m[0][0] = cosY * cosZ; - m[0][1] = - cosY * sinZ; - m[0][2] = sinY; - m[1][0] = sinX * sinY * cosZ + sinZ * cosX; - m[1][1] = cosX * cosZ - sinX * sinY * sinZ; - m[1][2] = - sinX * cosY; - m[2][0] = sinX * sinZ - cosX * sinY * cosZ; - m[2][1] = cosX * sinY * sinZ + sinX * cosZ; - m[2][2] = cosX * cosY; -} - -bool Matrix::toEuler( float & x, float & y, float & z ) const -{ - if ( m[0][2] < 1.0 ) - { - if ( m[0][2] > - 1.0 ) - { - x = atan2( - m[1][2], m[2][2] ); - y = asin( m[0][2] ); - z = atan2( - m[0][1], m[0][0] ); - return true; - } - else - { - x = - atan2( - m[1][0], m[1][1] ); - y = - PI / 2; - z = 0.0; - return false; - } - } - else - { - x = atan2( m[1][0], m[1][1] ); - y = PI / 2; - z = 0.0; - return false; - } -} - - -Matrix Matrix::inverted () const -{ - Matrix i; - - i(0,0) = m[1][1]*m[2][2] - m[1][2]*m[2][1]; - i(0,1) = m[0][2]*m[2][1] - m[0][1]*m[2][2]; - i(0,2) = m[0][1]*m[1][2] - m[0][2]*m[1][1]; - i(1,0) = m[1][2]*m[2][0] - m[1][0]*m[2][2]; - i(1,1) = m[0][0]*m[2][2] - m[0][2]*m[2][0]; - i(1,2) = m[0][2]*m[1][0] - m[0][0]*m[1][2]; - i(2,0) = m[1][0]*m[2][1] - m[1][1]*m[2][0]; - i(2,1) = m[0][1]*m[2][0] - m[0][0]*m[2][1]; - i(2,2) = m[0][0]*m[1][1] - m[0][1]*m[1][0]; - - float d = m[0][0]*i(0,0) + m[0][1]*i(1,0) + m[0][2]*i(2,0); - - if ( fabs( d ) <= 0.0 ) - return Matrix(); - - for ( int x = 0; x < 3; x++ ) - for ( int y = 0; y < 3; y++ ) - i(x,y) /= d; - - return i; -} - -QString Matrix::toHtml() const -{ - return QString( "" ) - + QString( "" ).arg( m[0][0], 0, 'f', 4 ).arg( m[0][1], 0, 'f', 4 ).arg( m[0][2], 0, 'f', 4 ) - + QString( "" ).arg( m[1][0], 0, 'f', 4 ).arg( m[1][1], 0, 'f', 4 ).arg( m[1][2], 0, 'f', 4 ) - + QString( "" ).arg( m[2][0], 0, 'f', 4 ).arg( m[2][1], 0, 'f', 4 ).arg( m[2][2], 0, 'f', 4 ) - + QString( "
%1%2%3
%1%2%3
%1%2%3
" ); -} - -QString Matrix4::toHtml() const -{ - return QString( "" ) - + QString( "" ).arg( m[0][0], 0, 'f', 4 ).arg( m[0][1], 0, 'f', 4 ).arg( m[0][2], 0, 'f', 4 ).arg( m[0][3], 0, 'f', 4 ) - + QString( "" ).arg( m[1][0], 0, 'f', 4 ).arg( m[1][1], 0, 'f', 4 ).arg( m[1][2], 0, 'f', 4 ).arg( m[1][3], 0, 'f', 4 ) - + QString( "" ).arg( m[2][0], 0, 'f', 4 ).arg( m[2][1], 0, 'f', 4 ).arg( m[2][2], 0, 'f', 4 ).arg( m[2][3], 0, 'f', 4 ) - + QString( "" ).arg( m[3][0], 0, 'f', 4 ).arg( m[3][1], 0, 'f', 4 ).arg( m[3][2], 0, 'f', 4 ).arg( m[3][3], 0, 'f', 4 ) - + QString( "
%1%2%3%4
%1%2%3%4
%1%2%3%4
%1%2%3%4
" ); -} - -void Matrix4::decompose( Vector3 & trans, Matrix & rot, Vector3 & scale ) const -{ - trans = Vector3( m[ 3 ][ 0 ], m[ 3 ][ 1 ], m[ 3 ][ 2 ] ); - - Matrix rotT; - - for ( int i = 0; i < 3; i++ ) - { - for ( int j = 0; j < 3; j++ ) - { - rot( j, i ) = m[ i ][ j ]; - rotT( i, j ) = m[ i ][ j ]; - } - } - - Matrix mtx = rot * rotT; - - scale = Vector3( sqrt( mtx( 0, 0 ) ), sqrt( mtx( 1, 1 ) ), sqrt( mtx( 2, 2 ) ) ); - - for ( int i = 0; i < 3; i++ ) - for ( int j = 0; j < 3; j++ ) - rot( i, j ) /= scale[ i ]; -} - -void Matrix4::compose( const Vector3 & trans, const Matrix & rot, const Vector3 & scale ) -{ - m[0][3] = 0.0; - m[1][3] = 0.0; - m[2][3] = 0.0; - m[3][3] = 1.0; - - m[3][0] = trans[0]; - m[3][1] = trans[1]; - m[3][2] = trans[2]; - - for ( int i = 0; i < 3; i++ ) - for ( int j = 0; j < 3; j++ ) - m[ i ][ j ] = rot( j, i ) * scale[ j ]; -} - -void Quat::fromAxisAngle( Vector3 axis, float angle ) -{ - axis.normalize(); - float s = sin( angle / 2 ); - wxyz[0] = cos( angle / 2 ); - wxyz[1] = s * axis[0]; - wxyz[2] = s * axis[1]; - wxyz[3] = s * axis[2]; -} - -void Quat::toAxisAngle( Vector3 & axis, float & angle ) const -{ - float squaredLength = wxyz[1]*wxyz[1] + wxyz[2]*wxyz[2] + wxyz[3]*wxyz[3]; - - if ( squaredLength > 0.0 ) - { - angle = acos( wxyz[0] ) * 2.0; - axis[0] = wxyz[1]; - axis[1] = wxyz[2]; - axis[2] = wxyz[3]; - axis /= sqrt( squaredLength ); - } - else - { - axis = Vector3( 1, 0, 0 ); - angle = 0; - } -} - -/* - * Transform - */ - -QDataStream & operator<<( QDataStream & ds, const Transform & t ) -{ - for ( int x = 0; x < 3; x++ ) - { - for ( int y = 0; y < 3; y++ ) - ds << t.rotation( x, y ); - ds << t.translation[ x ]; - } - ds << t.scale; - return ds; -} - -QDataStream & operator>>( QDataStream & ds, Transform & t ) -{ - for ( int x = 0; x < 3; x++ ) - { - for ( int y = 0; y < 3; y++ ) - ds >> t.rotation( x, y ); - ds >> t.translation[ x ]; - } - ds >> t.scale; - return ds; -} - -Transform operator*( const Transform & t1, const Transform & t2 ) -{ - Transform t; - t.rotation = t1.rotation * t2.rotation; - t.translation = t1.translation + t1.rotation * t2.translation * t1.scale; - t.scale = t1.scale * t2.scale; - return t; -} - -bool Transform::canConstruct( const NifModel * nif, const QModelIndex & parent ) -{ - return nif && parent.isValid() && nif->getIndex( parent, "Rotation" ).isValid() - && nif->getIndex( parent, "Translation" ).isValid() - && nif->getIndex( parent, "Scale" ).isValid(); -} - - -Transform::Transform( const NifModel * nif, const QModelIndex & transform ) -{ - rotation = nif->get( transform, "Rotation" ); - translation = nif->get( transform, "Translation" ); - scale = nif->get( transform, "Scale" ); -} - -void Transform::writeBack( NifModel * nif, const QModelIndex & transform ) const -{ - nif->set( transform, "Rotation", rotation ); - nif->set( transform, "Translation", translation ); - nif->set( transform, "Scale", scale ); -} - -QString Transform::toString() const -{ - float x, y, z; - rotation.toEuler( x, y, z ); - return QString( "TRANS( %1, %2, %3 ) ROT( %4, %5, %6 ) SCALE( %7 )" ).arg( translation[0] ).arg( translation[1] ).arg( translation[2] ).arg( x ).arg( y ).arg( z ).arg( scale ); -} - -Matrix4 Transform::toMatrix4() const -{ - Matrix4 m; - - for ( int c = 0; c < 3; c++ ) - { - for ( int d = 0; d < 3; d++ ) - m( c, d ) = rotation( d, c ) * scale; - m( 3, c ) = translation[ c ]; - } - m( 0, 3 ) = 0.0; - m( 1, 3 ) = 0.0; - m( 2, 3 ) = 0.0; - m( 3, 3 ) = 1.0; - return m; -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "niftypes.h" +#include "nifmodel.h" + +#include + +const float Quat::identity[4] = { 1.0, 0.0, 0.0, 0.0 }; +const float Matrix::identity[9] = { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; +const float Matrix4::identity[16] = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 }; + +QString NumOrMinMax( float val, char f, int prec ) +{ + if( val == -FLT_MAX ) + return ""; + else if( val == FLT_MAX ) + return ""; + + return QString::number( val, f, prec ); +} + +void Vector2::fromString( QString str ) +{ + QStringList parts = str.split( "," ); + if( parts.size() != 2 ) + return; + bool xOk, yOk; + float x_ = parts[0].toFloat( &xOk ); + float y_ = parts[1].toFloat( &yOk ); + if( xOk && yOk ) { + xy[0] = x_; + xy[1] = y_; + } +} + +void Vector3::fromString( QString str ) +{ + QStringList parts = str.split( "," ); + if( parts.size() != 3 ) + return; + bool xOk, yOk, zOk; + float x_ = parts[0].toFloat( &xOk ); + float y_ = parts[1].toFloat( &yOk ); + float z_ = parts[2].toFloat( &zOk ); + if( xOk && yOk && zOk ) { + xyz[0] = x_; + xyz[1] = y_; + xyz[2] = z_; + } +} + +void Vector4::fromString( QString str ) +{ + QStringList parts = str.split( "," ); + if( parts.size() != 4 ) + return; + bool xOk, yOk, zOk, wOk; + float x_ = parts[0].toFloat( &xOk ); + float y_ = parts[1].toFloat( &yOk ); + float z_ = parts[2].toFloat( &zOk ); + float w_ = parts[3].toFloat( &wOk ); + if( xOk && yOk && zOk && wOk ) { + xyzw[0] = x_; + xyzw[1] = y_; + xyzw[2] = z_; + xyzw[3] = w_; + } +} + +void Quat::fromString( QString str ) +{ + QStringList parts = str.split( "," ); + if( parts.size() != 4 ) + return; + bool wOk, xOk, yOk, zOk; + float w_ = parts[3].toFloat( &wOk ); + float x_ = parts[0].toFloat( &xOk ); + float y_ = parts[1].toFloat( &yOk ); + float z_ = parts[2].toFloat( &zOk ); + if( wOk && xOk&& yOk && zOk ) { + wxyz[0] = w_; + wxyz[1] = x_; + wxyz[2] = y_; + wxyz[3] = z_; + } +} + +void Matrix::fromQuat( const Quat & q ) +{ + float fTx = ((float)2.0)*q[1]; + float fTy = ((float)2.0)*q[2]; + float fTz = ((float)2.0)*q[3]; + float fTwx = fTx*q[0]; + float fTwy = fTy*q[0]; + float fTwz = fTz*q[0]; + float fTxx = fTx*q[1]; + float fTxy = fTy*q[1]; + float fTxz = fTz*q[1]; + float fTyy = fTy*q[2]; + float fTyz = fTz*q[2]; + float fTzz = fTz*q[3]; + + m[0][0] = (float)1.0-(fTyy+fTzz); + m[0][1] = fTxy-fTwz; + m[0][2] = fTxz+fTwy; + m[1][0] = fTxy+fTwz; + m[1][1] = (float)1.0-(fTxx+fTzz); + m[1][2] = fTyz-fTwx; + m[2][0] = fTxz-fTwy; + m[2][1] = fTyz+fTwx; + m[2][2] = (float)1.0-(fTxx+fTyy); +} + +Quat Matrix::toQuat() const +{ + Quat q; + + float trace = m[0][0] + m[1][1] + m[2][2]; + float root; + + if (trace > 0.0) + { + root = sqrt( trace + 1.0 ); + q[0] = root / 2.0; + root = 0.5 / root; + q[1] = ( m[2][1] - m[1][2] ) * root; + q[2] = ( m[0][2] - m[2][0] ) * root; + q[3] = ( m[1][0] - m[0][1] ) * root; + } + else + { + int i = ( m[1][1] > m[0][0] ? 1 : 0 ); + if ( m[2][2] > m[i][i] ) + i = 2; + const int next[3] = { 1, 2, 0 }; + int j = next[i]; + int k = next[j]; + + root = sqrt( m[i][i] - m[j][j] - m[k][k] + 1.0 ); + q[i+1] = root / 2; + root = 0.5 / root; + q[0] = ( m[k][j] - m[j][k] ) * root; + q[j+1] = ( m[j][i] + m[i][j] ) * root; + q[k+1] = ( m[k][i] + m[i][k] ) * root; + } + return q; +} + +void Matrix::fromEuler( float x, float y, float z ) +{ + float sinX = sin( x ); + float cosX = cos( x ); + float sinY = sin( y ); + float cosY = cos( y ); + float sinZ = sin( z ); + float cosZ = cos( z ); + + m[0][0] = cosY * cosZ; + m[0][1] = - cosY * sinZ; + m[0][2] = sinY; + m[1][0] = sinX * sinY * cosZ + sinZ * cosX; + m[1][1] = cosX * cosZ - sinX * sinY * sinZ; + m[1][2] = - sinX * cosY; + m[2][0] = sinX * sinZ - cosX * sinY * cosZ; + m[2][1] = cosX * sinY * sinZ + sinX * cosZ; + m[2][2] = cosX * cosY; +} + +bool Matrix::toEuler( float & x, float & y, float & z ) const +{ + if ( m[0][2] < 1.0 ) + { + if ( m[0][2] > - 1.0 ) + { + x = atan2( - m[1][2], m[2][2] ); + y = asin( m[0][2] ); + z = atan2( - m[0][1], m[0][0] ); + return true; + } + else + { + x = - atan2( - m[1][0], m[1][1] ); + y = - PI / 2; + z = 0.0; + return false; + } + } + else + { + x = atan2( m[1][0], m[1][1] ); + y = PI / 2; + z = 0.0; + return false; + } +} + + +Matrix Matrix::inverted () const +{ + Matrix i; + + i(0,0) = m[1][1]*m[2][2] - m[1][2]*m[2][1]; + i(0,1) = m[0][2]*m[2][1] - m[0][1]*m[2][2]; + i(0,2) = m[0][1]*m[1][2] - m[0][2]*m[1][1]; + i(1,0) = m[1][2]*m[2][0] - m[1][0]*m[2][2]; + i(1,1) = m[0][0]*m[2][2] - m[0][2]*m[2][0]; + i(1,2) = m[0][2]*m[1][0] - m[0][0]*m[1][2]; + i(2,0) = m[1][0]*m[2][1] - m[1][1]*m[2][0]; + i(2,1) = m[0][1]*m[2][0] - m[0][0]*m[2][1]; + i(2,2) = m[0][0]*m[1][1] - m[0][1]*m[1][0]; + + float d = m[0][0]*i(0,0) + m[0][1]*i(1,0) + m[0][2]*i(2,0); + + if ( fabs( d ) <= 0.0 ) + return Matrix(); + + for ( int x = 0; x < 3; x++ ) + for ( int y = 0; y < 3; y++ ) + i(x,y) /= d; + + return i; +} + +QString Matrix::toHtml() const +{ + return QString( "" ) + + QString( "" ).arg( m[0][0], 0, 'f', 4 ).arg( m[0][1], 0, 'f', 4 ).arg( m[0][2], 0, 'f', 4 ) + + QString( "" ).arg( m[1][0], 0, 'f', 4 ).arg( m[1][1], 0, 'f', 4 ).arg( m[1][2], 0, 'f', 4 ) + + QString( "" ).arg( m[2][0], 0, 'f', 4 ).arg( m[2][1], 0, 'f', 4 ).arg( m[2][2], 0, 'f', 4 ) + + QString( "
%1%2%3
%1%2%3
%1%2%3
" ); +} + +QString Matrix4::toHtml() const +{ + return QString( "" ) + + QString( "" ).arg( m[0][0], 0, 'f', 4 ).arg( m[0][1], 0, 'f', 4 ).arg( m[0][2], 0, 'f', 4 ).arg( m[0][3], 0, 'f', 4 ) + + QString( "" ).arg( m[1][0], 0, 'f', 4 ).arg( m[1][1], 0, 'f', 4 ).arg( m[1][2], 0, 'f', 4 ).arg( m[1][3], 0, 'f', 4 ) + + QString( "" ).arg( m[2][0], 0, 'f', 4 ).arg( m[2][1], 0, 'f', 4 ).arg( m[2][2], 0, 'f', 4 ).arg( m[2][3], 0, 'f', 4 ) + + QString( "" ).arg( m[3][0], 0, 'f', 4 ).arg( m[3][1], 0, 'f', 4 ).arg( m[3][2], 0, 'f', 4 ).arg( m[3][3], 0, 'f', 4 ) + + QString( "
%1%2%3%4
%1%2%3%4
%1%2%3%4
%1%2%3%4
" ); +} + +void Matrix4::decompose( Vector3 & trans, Matrix & rot, Vector3 & scale ) const +{ + trans = Vector3( m[ 3 ][ 0 ], m[ 3 ][ 1 ], m[ 3 ][ 2 ] ); + + Matrix rotT; + + for ( int i = 0; i < 3; i++ ) + { + for ( int j = 0; j < 3; j++ ) + { + rot( j, i ) = m[ i ][ j ]; + rotT( i, j ) = m[ i ][ j ]; + } + } + + Matrix mtx = rot * rotT; + + scale = Vector3( sqrt( mtx( 0, 0 ) ), sqrt( mtx( 1, 1 ) ), sqrt( mtx( 2, 2 ) ) ); + + for ( int i = 0; i < 3; i++ ) + for ( int j = 0; j < 3; j++ ) + rot( i, j ) /= scale[ i ]; +} + +void Matrix4::compose( const Vector3 & trans, const Matrix & rot, const Vector3 & scale ) +{ + m[0][3] = 0.0; + m[1][3] = 0.0; + m[2][3] = 0.0; + m[3][3] = 1.0; + + m[3][0] = trans[0]; + m[3][1] = trans[1]; + m[3][2] = trans[2]; + + for ( int i = 0; i < 3; i++ ) + for ( int j = 0; j < 3; j++ ) + m[ i ][ j ] = rot( j, i ) * scale[ j ]; +} + +void Quat::fromAxisAngle( Vector3 axis, float angle ) +{ + axis.normalize(); + float s = sin( angle / 2 ); + wxyz[0] = cos( angle / 2 ); + wxyz[1] = s * axis[0]; + wxyz[2] = s * axis[1]; + wxyz[3] = s * axis[2]; +} + +void Quat::toAxisAngle( Vector3 & axis, float & angle ) const +{ + float squaredLength = wxyz[1]*wxyz[1] + wxyz[2]*wxyz[2] + wxyz[3]*wxyz[3]; + + if ( squaredLength > 0.0 ) + { + angle = acos( wxyz[0] ) * 2.0; + axis[0] = wxyz[1]; + axis[1] = wxyz[2]; + axis[2] = wxyz[3]; + axis /= sqrt( squaredLength ); + } + else + { + axis = Vector3( 1, 0, 0 ); + angle = 0; + } +} + +/* + * Transform + */ + +QDataStream & operator<<( QDataStream & ds, const Transform & t ) +{ + for ( int x = 0; x < 3; x++ ) + { + for ( int y = 0; y < 3; y++ ) + ds << t.rotation( x, y ); + ds << t.translation[ x ]; + } + ds << t.scale; + return ds; +} + +QDataStream & operator>>( QDataStream & ds, Transform & t ) +{ + for ( int x = 0; x < 3; x++ ) + { + for ( int y = 0; y < 3; y++ ) + ds >> t.rotation( x, y ); + ds >> t.translation[ x ]; + } + ds >> t.scale; + return ds; +} + +Transform operator*( const Transform & t1, const Transform & t2 ) +{ + Transform t; + t.rotation = t1.rotation * t2.rotation; + t.translation = t1.translation + t1.rotation * t2.translation * t1.scale; + t.scale = t1.scale * t2.scale; + return t; +} + +bool Transform::canConstruct( const NifModel * nif, const QModelIndex & parent ) +{ + return nif && parent.isValid() && nif->getIndex( parent, "Rotation" ).isValid() + && nif->getIndex( parent, "Translation" ).isValid() + && nif->getIndex( parent, "Scale" ).isValid(); +} + + +Transform::Transform( const NifModel * nif, const QModelIndex & transform ) +{ + rotation = nif->get( transform, "Rotation" ); + translation = nif->get( transform, "Translation" ); + scale = nif->get( transform, "Scale" ); +} + +void Transform::writeBack( NifModel * nif, const QModelIndex & transform ) const +{ + nif->set( transform, "Rotation", rotation ); + nif->set( transform, "Translation", translation ); + nif->set( transform, "Scale", scale ); +} + +QString Transform::toString() const +{ + float x, y, z; + rotation.toEuler( x, y, z ); + return QString( "TRANS( %1, %2, %3 ) ROT( %4, %5, %6 ) SCALE( %7 )" ).arg( translation[0] ).arg( translation[1] ).arg( translation[2] ).arg( x ).arg( y ).arg( z ).arg( scale ); +} + +Matrix4 Transform::toMatrix4() const +{ + Matrix4 m; + + for ( int c = 0; c < 3; c++ ) + { + for ( int d = 0; d < 3; d++ ) + m( c, d ) = rotation( d, c ) * scale; + m( 3, c ) = translation[ c ]; + } + m( 0, 3 ) = 0.0; + m( 1, 3 ) = 0.0; + m( 2, 3 ) = 0.0; + m( 3, 3 ) = 1.0; + return m; +} + diff --git a/niftypes.h b/niftypes.h index 9e16147c2..be60222f2 100644 --- a/niftypes.h +++ b/niftypes.h @@ -1,1065 +1,1065 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef NIFTYPES_H -#define NIFTYPES_H - -#include - -#include -#include - -#ifndef PI -#ifdef M_PI -#define PI M_PI -#else -#define PI 3.1416f -#endif -#endif - -QString NumOrMinMax( float val, char f = 'g', int prec = 6 ); - -class NifModel; -class QModelIndex; -class QDataStream; - -class Vector2 -{ -public: - Vector2() - { - xy[0] = xy[1] = 0.0; - } - Vector2( float x, float y ) - { - xy[0] = x; xy[1] = y; - } - Vector2 & operator+=( const Vector2 & v ) - { - xy[0] += v.xy[0]; - xy[1] += v.xy[1]; - return *this; - } - Vector2 operator+( const Vector2 & v ) const - { - Vector2 w( *this ); - return ( w += v ); - } - Vector2 & operator-=( const Vector2 & v ) - { - xy[0] -= v.xy[0]; - xy[1] -= v.xy[1]; - return *this; - } - Vector2 operator-( const Vector2 & v ) const - { - Vector2 w( *this ); - return ( w -= v ); - } - Vector2 operator-() const - { - return Vector2() - *this; - } - Vector2 & operator*=( float s ) - { - xy[0] *= s; - xy[1] *= s; - return *this; - } - Vector2 operator*( float s ) const - { - Vector2 w( *this ); - return ( w *= s ); - } - Vector2 & operator/=( float s ) - { - xy[0] /= s; - xy[1] /= s; - return *this; - } - Vector2 operator/( float s ) const - { - Vector2 w( *this ); - return ( w /= s ); - } - - bool operator==( const Vector2 & v ) const - { - return xy[0] == v.xy[0] && xy[1] == v.xy[1]; - } - - float & operator[]( unsigned int i ) - { - Q_ASSERT( i < 2 ); - return xy[i]; - } - const float & operator[]( unsigned int i ) const - { - Q_ASSERT( i < 2 ); - return xy[i]; - } - - const float * data() const - { - return xy; - } - - void fromString( QString str ); - -protected: - float xy[2]; - - friend class NifIStream; - friend class NifOStream; -}; - -class Vector3 -{ -public: - Vector3() - { - xyz[ 0 ] = xyz[ 1 ] = xyz[ 2 ] = 0.0; - } - Vector3( float x, float y, float z ) - { - xyz[0] = x; - xyz[1] = y; - xyz[2] = z; - } - explicit Vector3( const Vector2 & v2, float z = 0 ) - { - xyz[0] = v2[0]; - xyz[1] = v2[1]; - xyz[2] = z; - } - explicit Vector3( const class Vector4 & ); - - Vector3 & operator+=( const Vector3 & v ) - { - xyz[0] += v.xyz[0]; - xyz[1] += v.xyz[1]; - xyz[2] += v.xyz[2]; - return *this; - } - Vector3 & operator-=( const Vector3 & v ) - { - xyz[0] -= v.xyz[0]; - xyz[1] -= v.xyz[1]; - xyz[2] -= v.xyz[2]; - return *this; - } - Vector3 & operator*=( float s ) - { - xyz[ 0 ] *= s; - xyz[ 1 ] *= s; - xyz[ 2 ] *= s; - return *this; - } - Vector3 & operator/=( float s ) - { - xyz[ 0 ] /= s; - xyz[ 1 ] /= s; - xyz[ 2 ] /= s; - return *this; - } - Vector3 operator+( Vector3 v ) const - { - Vector3 w( *this ); - return w += v; - } - Vector3 operator-( Vector3 v ) const - { - Vector3 w( *this ); - return w -= v; - } - Vector3 operator-() const - { - return Vector3() - *this; - } - Vector3 operator*( float s ) const - { - Vector3 v( *this ); - return v *= s; - } - Vector3 operator/( float s ) const - { - Vector3 v( *this ); - return v /= s; - } - - bool operator==( const Vector3 & v ) const - { - return xyz[0] == v.xyz[0] && xyz[1] == v.xyz[1] && xyz[2] == v.xyz[2]; - } - - float & operator[]( unsigned int i ) - { - Q_ASSERT( i < 3 ); - return xyz[i]; - } - const float & operator[]( unsigned int i ) const - { - Q_ASSERT( i < 3 ); - return xyz[i]; - } - - float length() const - { - return sqrt( xyz[0]*xyz[0] + xyz[1]*xyz[1] + xyz[2]*xyz[2] ); - } - - float squaredLength() const - { - return xyz[0]*xyz[0] + xyz[1]*xyz[1] + xyz[2]*xyz[2]; - } - - Vector3 & normalize() - { - float m = length(); - if ( m > 0.0 ) - m = 1.0 / m; - else - m = 0.0F; - xyz[0] *= m; - xyz[1] *= m; - xyz[2] *= m; - return *this; - } - - static float dotproduct( const Vector3 & v1, const Vector3 & v2 ) - { - return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]; - } - static Vector3 crossproduct( const Vector3 & a, const Vector3 & b ) - { - return Vector3( a[1]*b[2] - a[2]*b[1], a[2]*b[0] - a[0]*b[2], a[0]*b[1] - a[1]*b[0] ); - } - - static float angle( const Vector3 & v1, const Vector3 & v2 ) - { - float dot = dotproduct( v1, v2 ); - if ( dot > 1.0 ) - return 0.0; - else if ( dot < - 1.0 ) - return (float)PI; - else if ( dot == 0.0 ) - return (float)(PI/2); - else - return acos( dot ); - } - - void boundMin( const Vector3 & v ) - { - if ( v[0] < xyz[0] ) xyz[0] = v[0]; - if ( v[1] < xyz[1] ) xyz[1] = v[1]; - if ( v[2] < xyz[2] ) xyz[2] = v[2]; - } - void boundMax( const Vector3 & v ) - { - if ( v[0] > xyz[0] ) xyz[0] = v[0]; - if ( v[1] > xyz[1] ) xyz[1] = v[1]; - if ( v[2] > xyz[2] ) xyz[2] = v[2]; - } - - const float * data() const { return xyz; } - - void fromString( QString str ); - - QString toHtml() const - { - return QString( "X %1 Y %2 Z %3\nlength %4" ) - .arg( NumOrMinMax( xyz[0] ) ) - .arg( NumOrMinMax( xyz[1] ) ) - .arg( NumOrMinMax( xyz[2] ) ) - .arg( length() ); - } - -protected: - float xyz[3]; - - friend class NifIStream; - friend class NifOStream; -}; - -class Vector4 -{ -public: - Vector4() - { - xyzw[ 0 ] = xyzw[ 1 ] = xyzw[ 2 ] = xyzw[ 3 ] = 0.0; - } - Vector4( float x, float y, float z, float w ) - { - xyzw[ 0 ] = x; - xyzw[ 1 ] = y; - xyzw[ 2 ] = z; - xyzw[ 3 ] = w; - } - explicit Vector4( const Vector3 & v3, float w = 0.0 ) - { - xyzw[0] = v3[0]; - xyzw[1] = v3[1]; - xyzw[2] = v3[2]; - xyzw[3] = w; - } - Vector4 & operator+=( const Vector4 & v ) - { - xyzw[0] += v.xyzw[0]; - xyzw[1] += v.xyzw[1]; - xyzw[2] += v.xyzw[2]; - xyzw[3] += v.xyzw[3]; - return *this; - } - Vector4 & operator-=( const Vector4 & v ) - { - xyzw[0] -= v.xyzw[0]; - xyzw[1] -= v.xyzw[1]; - xyzw[2] -= v.xyzw[2]; - xyzw[3] -= v.xyzw[3]; - return *this; - } - Vector4 & operator*=( float s ) - { - xyzw[ 0 ] *= s; - xyzw[ 1 ] *= s; - xyzw[ 2 ] *= s; - xyzw[ 3 ] *= s; - return *this; - } - Vector4 & operator/=( float s ) - { - xyzw[ 0 ] /= s; - xyzw[ 1 ] /= s; - xyzw[ 2 ] /= s; - xyzw[ 3 ] /= s; - return *this; - } - Vector4 operator+( Vector4 v ) const - { - Vector4 w( *this ); - return w += v; - } - Vector4 operator-( Vector4 v ) const - { - Vector4 w( *this ); - return w -= v; - } - Vector4 operator-() const - { - return Vector4() - *this; - } - Vector4 operator*( float s ) const - { - Vector4 v( *this ); - return v *= s; - } - Vector4 operator/( float s ) const - { - Vector4 v( *this ); - return v /= s; - } - - bool operator==( const Vector4 & v ) const - { - return xyzw[0] == v.xyzw[0] && xyzw[1] == v.xyzw[1] && xyzw[2] == v.xyzw[2] && xyzw[3] == v.xyzw[3]; - } - - float & operator[]( unsigned int i ) - { - Q_ASSERT( i < 4 ); - return xyzw[i]; - } - const float & operator[]( unsigned int i ) const - { - Q_ASSERT( i < 4 ); - return xyzw[i]; - } - - float length() const - { - return sqrt( xyzw[0]*xyzw[0] + xyzw[1]*xyzw[1] + xyzw[2]*xyzw[2] + xyzw[3]*xyzw[3] ); - } - - float squaredLength() const - { - return xyzw[0]*xyzw[0] + xyzw[1]*xyzw[1] + xyzw[2]*xyzw[2] + xyzw[3]*xyzw[3]; - } - - void normalize() - { - float m = length(); - if ( m > 0.0 ) - m = 1.0 / m; - else - m = 0.0F; - xyzw[0] *= m; - xyzw[1] *= m; - xyzw[2] *= m; - xyzw[3] *= m; - } - - static float dotproduct( const Vector4 & v1, const Vector4 & v2 ) - { - return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]+v1[3]*v2[3]; - } - - static float angle( const Vector4 & v1, const Vector4 & v2 ) - { - float dot = dotproduct( v1, v2 ); - if ( dot > 1.0 ) - return 0.0; - else if ( dot < - 1.0 ) - return (float)PI; - else if ( dot == 0.0 ) - return (float)PI/2; - else - return (float)acos( dot ); - } - - const float * data() const { return xyzw; } - - void fromString( QString str ); - - QString toHtml() const - { - return QString( "X %1 Y %2 Z %3 W %4\nlength %5" ) - .arg( NumOrMinMax( xyzw[0] ) ) - .arg( NumOrMinMax( xyzw[1] ) ) - .arg( NumOrMinMax( xyzw[2] ) ) - .arg( NumOrMinMax( xyzw[3] ) ) - .arg( length() ); - } - -protected: - float xyzw[4]; - - friend class NifIStream; - friend class NifOStream; -}; - -inline Vector3::Vector3( const Vector4 & v4 ) -{ - xyz[0] = v4[0]; - xyz[1] = v4[1]; - xyz[2] = v4[2]; -} - -class Quat -{ -public: - Quat() - { - memcpy( wxyz, identity, 16 ); - } - Quat( float w, float x, float y, float z ) - { - wxyz[0] = w; - wxyz[1] = x; - wxyz[2] = y; - wxyz[3] = z; - } - - float & operator[]( unsigned int i ) - { - Q_ASSERT( i < 4 ); - return wxyz[ i ]; - } - const float & operator[]( unsigned int i ) const - { - Q_ASSERT( i < 4 ); - return wxyz[ i ]; - } - Quat & operator*=( float s ) - { - for ( int c = 0; c < 4; c++ ) - wxyz[ c ] *= s; - return *this; - } - Quat operator*( float s ) const - { - Quat q( *this ); - return ( q *= s ); - } - Quat & operator+=( const Quat & q ) - { - for ( int c = 0; c < 4; c++ ) - wxyz[ c ] += q.wxyz[ c ]; - return *this; - } - Quat operator+( const Quat & q ) const - { - Quat r( *this ); - return ( r += q ); - } - static float dotproduct( const Quat & q1, const Quat & q2 ) - { - return q1[0]*q2[0]+q1[1]*q2[1]+q1[2]*q2[2]+q1[3]*q2[3]; - } - - - void fromString( QString str ); - void fromAxisAngle( Vector3 axis, float angle ); - void toAxisAngle( Vector3 & axis, float & angle ) const; - - QString toHtml() const - { - return QString( "W %1\nX %2\nY %3\nZ %4" ) - .arg( NumOrMinMax( wxyz[0] ) ) - .arg( NumOrMinMax( wxyz[1] ) ) - .arg( NumOrMinMax( wxyz[2] ) ) - .arg( NumOrMinMax( wxyz[3] ) ); - } - -protected: - float wxyz[4]; - static const float identity[4]; - - friend class NifIStream; - friend class NifOStream; -}; - -class Matrix -{ -public: - Matrix() - { - memcpy( m, identity, 36 ); - } - Matrix operator*( const Matrix & m2 ) const - { - Matrix m3; - for ( int r = 0; r < 3; r++ ) - for ( int c = 0; c < 3; c++ ) - m3.m[r][c] = m[r][0]*m2.m[0][c] + m[r][1]*m2.m[1][c] + m[r][2]*m2.m[2][c]; - return m3; - } - Vector3 operator*( const Vector3 & v ) const - { - return Vector3( - m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]*v[2], - m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]*v[2], - m[2][0]*v[0] + m[2][1]*v[1] + m[2][2]*v[2] ); - } - float & operator()( unsigned int c, unsigned int d ) - { - Q_ASSERT( c < 3 && d < 3 ); - return m[c][d]; - } - float operator()( unsigned int c, unsigned int d ) const - { - Q_ASSERT( c < 3 && d < 3 ); - return m[c][d]; - } - - Matrix inverted() const; - - void fromQuat( const Quat & q ); - Quat toQuat() const; - - void fromEuler( float x, float y, float z ); - bool toEuler( float & x, float & y, float & z ) const; - - static Matrix euler( float x, float y, float z ) - { - Matrix m; m.fromEuler( x, y, z ); - return m; - } - - QString toHtml() const; - -protected: - float m[3][3]; - static const float identity[9]; - - friend class NifIStream; - friend class NifOStream; -}; - -class Matrix4 -{ -public: - Matrix4() - { - memcpy( m, identity, 64 ); - } - Matrix4 operator*( const Matrix4 & m2 ) const - { - Matrix4 m3; - for ( int r = 0; r < 4; r++ ) - for ( int c = 0; c < 4; c++ ) - m3.m[r][c] = m[r][0]*m2.m[0][c] + m[r][1]*m2.m[1][c] + m[r][2]*m2.m[2][c] + m[r][3]*m2.m[3][c]; - return m3; - } - Vector3 operator*( const Vector3 & v ) const - { - return Vector3( - m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]*v[2] + m[0][3], - m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]*v[2] + m[1][3], - m[2][0]*v[0] + m[2][1]*v[1] + m[2][2]*v[2] + m[2][3] ); - } - float & operator()( unsigned int c, unsigned int d ) - { - Q_ASSERT( c < 4 && d < 4 ); - return m[c][d]; - } - float operator()( unsigned int c, unsigned int d ) const - { - Q_ASSERT( c < 4 && d < 4 ); - return m[c][d]; - } - - Matrix rotation() const; - Vector3 translation() const; - Vector3 scale() const; - - void decompose( Vector3 & trans, Matrix & rot, Vector3 & scale ) const; - void compose( const Vector3 & trans, const Matrix & rot, const Vector3 & scale ); - - //Matrix44 inverted() const; - - QString toHtml() const; - - const float * data() const { return (float *) m; } - -protected: - float m[4][4]; - static const float identity[16]; - - friend class NifIStream; - friend class NifOStream; -}; - -class Transform -{ -public: - Transform( const NifModel * nif, const QModelIndex & transform ); - Transform() { scale = 1.0; } - - static bool canConstruct( const NifModel * nif, const QModelIndex & parent ); - - void writeBack( NifModel * nif, const QModelIndex & transform ) const; - - friend Transform operator*( const Transform & t1, const Transform & t2 ); - - Vector3 operator*( const Vector3 & v ) const - { - return rotation * v * scale + translation; - } - - Matrix4 toMatrix4() const; - - Matrix rotation; - Vector3 translation; - float scale; - - friend QDataStream & operator<<( QDataStream & ds, const Transform & t ); - friend QDataStream & operator>>( QDataStream & ds, Transform & t ); - - QString toString() const; -}; - -class Triangle -{ -public: - Triangle() { v[0] = v[1] = v[2] = 0; } - Triangle( quint16 a, quint16 b, quint16 c ) { set( a, b, c ); } - - quint16 & operator[]( unsigned int i ) - { - Q_ASSERT( i < 3 ); - return v[i]; - } - const quint16 & operator[]( unsigned int i ) const - { - Q_ASSERT( i < 3 ); - return v[i]; - } - void set( quint16 a, quint16 b, quint16 c ) - { - v[0] = a; v[1] = b; v[2] = c; - } - inline quint16 v1() const { return v[0]; } - inline quint16 v2() const { return v[1]; } - inline quint16 v3() const { return v[2]; } - - void flip() { quint16 x = v[0]; v[0] = v[1]; v[1] = x; } - - Triangle operator+( quint16 d ) - { - Triangle t( *this ); - t.v[0] += d; - t.v[1] += d; - t.v[2] += d; - return t; - } - -protected: - quint16 v[3]; - friend class NifIStream; - friend class NifOStream; -}; - -inline float clamp01( float a ) -{ - if ( a < 0 ) return 0; - if ( a > 1 ) return 1; - return a; -} - -class Color3 -{ -public: - Color3() { rgb[0] = rgb[1] = rgb[2] = 1.0; } - Color3( float r, float g, float b ) { setRGB( r, g, b ); } - explicit Color3( const QColor & c ) { fromQColor( c ); } - explicit Color3( const Vector3 & v ) { fromVector3( v ); } - explicit Color3( const class Color4 & c4 ); - - float & operator[]( unsigned int i ) - { - Q_ASSERT( i < 3 ); - return rgb[i]; - } - - const float & operator[]( unsigned int i ) const - { - Q_ASSERT( i < 3 ); - return rgb[i]; - } - - Color3 operator*( float x ) const - { - Color3 c( *this ); - c.rgb[0] *= x; - c.rgb[1] *= x; - c.rgb[2] *= x; - return c; - } - - Color3 & operator+=( const Color3 & o ) - { - for ( int x = 0; x < 3; x++ ) - rgb[x] += o.rgb[x]; - return *this; - } - - Color3 & operator-=( const Color3 & o ) - { - for ( int x = 0; x < 3; x++ ) - rgb[x] -= o.rgb[x]; - return *this; - } - - Color3 operator+( const Color3 & o ) const - { - Color3 c( *this ); - return ( c += o ); - } - - Color3 operator-( const Color3 & o ) const - { - Color3 c( *this ); - return ( c -= o ); - } - - float red() const { return rgb[0]; } - float green() const { return rgb[1]; } - float blue() const { return rgb[2]; } - - void setRed( float r ) { rgb[0] = r; } - void setGreen( float g ) { rgb[1] = g; } - void setBlue( float b ) { rgb[2] = b; } - - void setRGB( float r, float g, float b ) { rgb[0] = r; rgb[1] = g; rgb[2] = b; } - - QColor toQColor() const - { - return QColor::fromRgbF( clamp01( rgb[0] ), clamp01( rgb[1] ), clamp01( rgb[2] ) ); - } - - void fromQColor( const QColor & c ) - { - rgb[0] = c.redF(); - rgb[1] = c.greenF(); - rgb[2] = c.blueF(); - } - - void fromVector3( const Vector3 & v ) - { - rgb[0] = v[0]; - rgb[1] = v[1]; - rgb[2] = v[2]; - } - - const float * data() const { return rgb; } - -protected: - float rgb[3]; - - friend class NifIStream; - friend class NifOStream; -}; - -class Color4 -{ -public: - Color4() { rgba[0] = rgba[1] = rgba[2] = rgba[3] = 1.0; } - explicit Color4( const Color3 & c, float alpha = 1.0 ) { rgba[0] = c[0]; rgba[1] = c[1]; rgba[2] = c[2]; rgba[3] = alpha; } - explicit Color4( const QColor & c ) { fromQColor( c ); } - Color4( float r, float g, float b, float a ) { setRGBA( r, g, b, a ); } - - float & operator[]( unsigned int i ) - { - Q_ASSERT( i < 4 ); - return rgba[ i ]; - } - - const float & operator[]( unsigned int i ) const - { - Q_ASSERT( i < 4 ); - return rgba[ i ]; - } - - Color4 operator*( float x ) const - { - Color4 c( *this ); - c.rgba[0] *= x; - c.rgba[1] *= x; - c.rgba[2] *= x; - c.rgba[3] *= x; - return c; - } - - Color4 & operator+=( const Color4 & o ) - { - for ( int x = 0; x < 4; x++ ) - rgba[x] += o.rgba[x]; - return *this; - } - - Color4 & operator-=( const Color4 & o ) - { - for ( int x = 0; x < 4; x++ ) - rgba[x] -= o.rgba[x]; - return *this; - } - - Color4 operator+( const Color4 & o ) const - { - Color4 c( *this ); - return ( c += o ); - } - - Color4 operator-( const Color4 & o ) const - { - Color4 c( *this ); - return ( c -= o ); - } - - bool operator==( const Color4 & c ) const - { - return rgba[0] == c.rgba[0] && rgba[1] == c.rgba[1] && rgba[2] == c.rgba[2] && rgba[3] == c.rgba[3]; - } - - float red() const { return rgba[0]; } - float green() const { return rgba[1]; } - float blue() const { return rgba[2]; } - float alpha() const { return rgba[3]; } - - void setRed( float r ) { rgba[0] = r; } - void setGreen( float g ) { rgba[1] = g; } - void setBlue( float b ) { rgba[2] = b; } - void setAlpha( float a ) { rgba[3] = a; } - - void setRGBA( float r, float g, float b, float a ) { rgba[ 0 ] = r; rgba[ 1 ] = g; rgba[ 2 ] = b; rgba[ 3 ] = a; } - - QColor toQColor() const - { - return QColor::fromRgbF( clamp01( rgba[0] ), clamp01( rgba[1] ), clamp01( rgba[2] ), clamp01( rgba[3] ) ); - } - - void fromQColor( const QColor & c ) - { - rgba[0] = c.redF(); - rgba[1] = c.greenF(); - rgba[2] = c.blueF(); - rgba[3] = c.alphaF(); - } - - const float * data() const { return rgba; } - - Color4 blend( float alpha ) const - { - Color4 c( *this ); - c.setAlpha( c.alpha() * alpha ); - return c; - } - -protected: - float rgba[4]; - - friend class NifIStream; - friend class NifOStream; -}; - -inline Color3::Color3( const Color4 & c4 ) -{ - rgb[0] = c4[0]; - rgb[1] = c4[1]; - rgb[2] = c4[2]; -} - - -//! A fixed length vector of type T. -//! Data is allocated into a vector portion and the data section. -//! The vector simply points to appropriate places in the data section. -//! @param T Type of Vector -template -class FixedMatrix -{ -public: - //! Default Constructor: Allocates empty vector - FixedMatrix() : v_( NULL ), len0(0), len1(0) - {} - - //! Size Constructor - //! Allocate the requested number of elements. - FixedMatrix(int length1, int length2) - { - int length = length1*length2; - v_ = (T*)qMalloc(sizeof(T)*length); - len0 = length1; - len1 = length2; - } - - //! Copy Constructor - FixedMatrix(const FixedMatrix& other) - { - int datalen = other.count(); - len0 = other.count(0); - len1 = other.count(1); - v_ = (T*)qMalloc(sizeof(T) * datalen); - qMemCopy( array(), other.array(), datalen ); - } - - //! Default Destructor - ~FixedMatrix() - { qFree(v_); } - - //! Copy Assignment - FixedMatrix& operator=(const FixedMatrix& other) - { - FixedMatrix tmp( other ); - swap( tmp ); - return *this; - } - - T* operator[](int index) - { - // assert( index >= 0 && index < len_ ) - return &v_[index*count(0) + 0]; - } - - //! Accessor for element (i,j) in the matrix - const T& operator[](int index) const - { - // assert( index >= 0 && index < len_ ) - return &v_[index*count(0) + 0]; - } - - //! Accessor for element (i,j) in the matrix - T* operator()(int index) - { - // assert( index >= 0 && index < len_ ) - return &v_[index*count(0) + 0]; - } - - //! Accessor for element (i,j) in the matrix - T& operator()(int index1, int index2) - { - // assert( index >= 0 && index < len_ ) - return element(index1, index2); - } - - //! Accessor for element (i,j) in the matrix - operator T*() const - { - return array(); - } - - //! Accessor for element (i,j) in the matrix - T& element(int index1, int index2) - { - return v_[ calcindex(index1, index2) ]; - } - - int calcindex(int index1, int index2) - { - return index1*count(1) + index2; - } - - //! Number of items in the vector. - int count() const - { return len0 * len1; } - - int count(int dimension) const - { return (dimension == 0 ? len0 : (dimension == 1 ? len1 : 0)); } - - //! Start of the array portion of the vector - T*array() const { return v_; } - T*data() const { return v_; } - - //! Assign a string to vector at specified index - //! @param[in] index Index in array to assign - //! @param[in] value Value to copy into string - void assign(int index1, int index2, T value) - { - element(index1, index2) = value; - } - - //! Swap contents with another APRFixedMatrix - //! @param[in,out] other Other vector to swap with - void swap( FixedMatrix &other ) - { - qSwap(v_, other.v_); - qSwap(len0, other.len0); - qSwap(len1, other.len1); - } - -private: - T* v_; //! Vector data - int len0, len1; //! length -}; - -typedef FixedMatrix ByteMatrix; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef NIFTYPES_H +#define NIFTYPES_H + +#include + +#include +#include + +#ifndef PI +#ifdef M_PI +#define PI M_PI +#else +#define PI 3.1416f +#endif +#endif + +QString NumOrMinMax( float val, char f = 'g', int prec = 6 ); + +class NifModel; +class QModelIndex; +class QDataStream; + +class Vector2 +{ +public: + Vector2() + { + xy[0] = xy[1] = 0.0; + } + Vector2( float x, float y ) + { + xy[0] = x; xy[1] = y; + } + Vector2 & operator+=( const Vector2 & v ) + { + xy[0] += v.xy[0]; + xy[1] += v.xy[1]; + return *this; + } + Vector2 operator+( const Vector2 & v ) const + { + Vector2 w( *this ); + return ( w += v ); + } + Vector2 & operator-=( const Vector2 & v ) + { + xy[0] -= v.xy[0]; + xy[1] -= v.xy[1]; + return *this; + } + Vector2 operator-( const Vector2 & v ) const + { + Vector2 w( *this ); + return ( w -= v ); + } + Vector2 operator-() const + { + return Vector2() - *this; + } + Vector2 & operator*=( float s ) + { + xy[0] *= s; + xy[1] *= s; + return *this; + } + Vector2 operator*( float s ) const + { + Vector2 w( *this ); + return ( w *= s ); + } + Vector2 & operator/=( float s ) + { + xy[0] /= s; + xy[1] /= s; + return *this; + } + Vector2 operator/( float s ) const + { + Vector2 w( *this ); + return ( w /= s ); + } + + bool operator==( const Vector2 & v ) const + { + return xy[0] == v.xy[0] && xy[1] == v.xy[1]; + } + + float & operator[]( unsigned int i ) + { + Q_ASSERT( i < 2 ); + return xy[i]; + } + const float & operator[]( unsigned int i ) const + { + Q_ASSERT( i < 2 ); + return xy[i]; + } + + const float * data() const + { + return xy; + } + + void fromString( QString str ); + +protected: + float xy[2]; + + friend class NifIStream; + friend class NifOStream; +}; + +class Vector3 +{ +public: + Vector3() + { + xyz[ 0 ] = xyz[ 1 ] = xyz[ 2 ] = 0.0; + } + Vector3( float x, float y, float z ) + { + xyz[0] = x; + xyz[1] = y; + xyz[2] = z; + } + explicit Vector3( const Vector2 & v2, float z = 0 ) + { + xyz[0] = v2[0]; + xyz[1] = v2[1]; + xyz[2] = z; + } + explicit Vector3( const class Vector4 & ); + + Vector3 & operator+=( const Vector3 & v ) + { + xyz[0] += v.xyz[0]; + xyz[1] += v.xyz[1]; + xyz[2] += v.xyz[2]; + return *this; + } + Vector3 & operator-=( const Vector3 & v ) + { + xyz[0] -= v.xyz[0]; + xyz[1] -= v.xyz[1]; + xyz[2] -= v.xyz[2]; + return *this; + } + Vector3 & operator*=( float s ) + { + xyz[ 0 ] *= s; + xyz[ 1 ] *= s; + xyz[ 2 ] *= s; + return *this; + } + Vector3 & operator/=( float s ) + { + xyz[ 0 ] /= s; + xyz[ 1 ] /= s; + xyz[ 2 ] /= s; + return *this; + } + Vector3 operator+( Vector3 v ) const + { + Vector3 w( *this ); + return w += v; + } + Vector3 operator-( Vector3 v ) const + { + Vector3 w( *this ); + return w -= v; + } + Vector3 operator-() const + { + return Vector3() - *this; + } + Vector3 operator*( float s ) const + { + Vector3 v( *this ); + return v *= s; + } + Vector3 operator/( float s ) const + { + Vector3 v( *this ); + return v /= s; + } + + bool operator==( const Vector3 & v ) const + { + return xyz[0] == v.xyz[0] && xyz[1] == v.xyz[1] && xyz[2] == v.xyz[2]; + } + + float & operator[]( unsigned int i ) + { + Q_ASSERT( i < 3 ); + return xyz[i]; + } + const float & operator[]( unsigned int i ) const + { + Q_ASSERT( i < 3 ); + return xyz[i]; + } + + float length() const + { + return sqrt( xyz[0]*xyz[0] + xyz[1]*xyz[1] + xyz[2]*xyz[2] ); + } + + float squaredLength() const + { + return xyz[0]*xyz[0] + xyz[1]*xyz[1] + xyz[2]*xyz[2]; + } + + Vector3 & normalize() + { + float m = length(); + if ( m > 0.0 ) + m = 1.0 / m; + else + m = 0.0F; + xyz[0] *= m; + xyz[1] *= m; + xyz[2] *= m; + return *this; + } + + static float dotproduct( const Vector3 & v1, const Vector3 & v2 ) + { + return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]; + } + static Vector3 crossproduct( const Vector3 & a, const Vector3 & b ) + { + return Vector3( a[1]*b[2] - a[2]*b[1], a[2]*b[0] - a[0]*b[2], a[0]*b[1] - a[1]*b[0] ); + } + + static float angle( const Vector3 & v1, const Vector3 & v2 ) + { + float dot = dotproduct( v1, v2 ); + if ( dot > 1.0 ) + return 0.0; + else if ( dot < - 1.0 ) + return (float)PI; + else if ( dot == 0.0 ) + return (float)(PI/2); + else + return acos( dot ); + } + + void boundMin( const Vector3 & v ) + { + if ( v[0] < xyz[0] ) xyz[0] = v[0]; + if ( v[1] < xyz[1] ) xyz[1] = v[1]; + if ( v[2] < xyz[2] ) xyz[2] = v[2]; + } + void boundMax( const Vector3 & v ) + { + if ( v[0] > xyz[0] ) xyz[0] = v[0]; + if ( v[1] > xyz[1] ) xyz[1] = v[1]; + if ( v[2] > xyz[2] ) xyz[2] = v[2]; + } + + const float * data() const { return xyz; } + + void fromString( QString str ); + + QString toHtml() const + { + return QString( "X %1 Y %2 Z %3\nlength %4" ) + .arg( NumOrMinMax( xyz[0] ) ) + .arg( NumOrMinMax( xyz[1] ) ) + .arg( NumOrMinMax( xyz[2] ) ) + .arg( length() ); + } + +protected: + float xyz[3]; + + friend class NifIStream; + friend class NifOStream; +}; + +class Vector4 +{ +public: + Vector4() + { + xyzw[ 0 ] = xyzw[ 1 ] = xyzw[ 2 ] = xyzw[ 3 ] = 0.0; + } + Vector4( float x, float y, float z, float w ) + { + xyzw[ 0 ] = x; + xyzw[ 1 ] = y; + xyzw[ 2 ] = z; + xyzw[ 3 ] = w; + } + explicit Vector4( const Vector3 & v3, float w = 0.0 ) + { + xyzw[0] = v3[0]; + xyzw[1] = v3[1]; + xyzw[2] = v3[2]; + xyzw[3] = w; + } + Vector4 & operator+=( const Vector4 & v ) + { + xyzw[0] += v.xyzw[0]; + xyzw[1] += v.xyzw[1]; + xyzw[2] += v.xyzw[2]; + xyzw[3] += v.xyzw[3]; + return *this; + } + Vector4 & operator-=( const Vector4 & v ) + { + xyzw[0] -= v.xyzw[0]; + xyzw[1] -= v.xyzw[1]; + xyzw[2] -= v.xyzw[2]; + xyzw[3] -= v.xyzw[3]; + return *this; + } + Vector4 & operator*=( float s ) + { + xyzw[ 0 ] *= s; + xyzw[ 1 ] *= s; + xyzw[ 2 ] *= s; + xyzw[ 3 ] *= s; + return *this; + } + Vector4 & operator/=( float s ) + { + xyzw[ 0 ] /= s; + xyzw[ 1 ] /= s; + xyzw[ 2 ] /= s; + xyzw[ 3 ] /= s; + return *this; + } + Vector4 operator+( Vector4 v ) const + { + Vector4 w( *this ); + return w += v; + } + Vector4 operator-( Vector4 v ) const + { + Vector4 w( *this ); + return w -= v; + } + Vector4 operator-() const + { + return Vector4() - *this; + } + Vector4 operator*( float s ) const + { + Vector4 v( *this ); + return v *= s; + } + Vector4 operator/( float s ) const + { + Vector4 v( *this ); + return v /= s; + } + + bool operator==( const Vector4 & v ) const + { + return xyzw[0] == v.xyzw[0] && xyzw[1] == v.xyzw[1] && xyzw[2] == v.xyzw[2] && xyzw[3] == v.xyzw[3]; + } + + float & operator[]( unsigned int i ) + { + Q_ASSERT( i < 4 ); + return xyzw[i]; + } + const float & operator[]( unsigned int i ) const + { + Q_ASSERT( i < 4 ); + return xyzw[i]; + } + + float length() const + { + return sqrt( xyzw[0]*xyzw[0] + xyzw[1]*xyzw[1] + xyzw[2]*xyzw[2] + xyzw[3]*xyzw[3] ); + } + + float squaredLength() const + { + return xyzw[0]*xyzw[0] + xyzw[1]*xyzw[1] + xyzw[2]*xyzw[2] + xyzw[3]*xyzw[3]; + } + + void normalize() + { + float m = length(); + if ( m > 0.0 ) + m = 1.0 / m; + else + m = 0.0F; + xyzw[0] *= m; + xyzw[1] *= m; + xyzw[2] *= m; + xyzw[3] *= m; + } + + static float dotproduct( const Vector4 & v1, const Vector4 & v2 ) + { + return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]+v1[3]*v2[3]; + } + + static float angle( const Vector4 & v1, const Vector4 & v2 ) + { + float dot = dotproduct( v1, v2 ); + if ( dot > 1.0 ) + return 0.0; + else if ( dot < - 1.0 ) + return (float)PI; + else if ( dot == 0.0 ) + return (float)PI/2; + else + return (float)acos( dot ); + } + + const float * data() const { return xyzw; } + + void fromString( QString str ); + + QString toHtml() const + { + return QString( "X %1 Y %2 Z %3 W %4\nlength %5" ) + .arg( NumOrMinMax( xyzw[0] ) ) + .arg( NumOrMinMax( xyzw[1] ) ) + .arg( NumOrMinMax( xyzw[2] ) ) + .arg( NumOrMinMax( xyzw[3] ) ) + .arg( length() ); + } + +protected: + float xyzw[4]; + + friend class NifIStream; + friend class NifOStream; +}; + +inline Vector3::Vector3( const Vector4 & v4 ) +{ + xyz[0] = v4[0]; + xyz[1] = v4[1]; + xyz[2] = v4[2]; +} + +class Quat +{ +public: + Quat() + { + memcpy( wxyz, identity, 16 ); + } + Quat( float w, float x, float y, float z ) + { + wxyz[0] = w; + wxyz[1] = x; + wxyz[2] = y; + wxyz[3] = z; + } + + float & operator[]( unsigned int i ) + { + Q_ASSERT( i < 4 ); + return wxyz[ i ]; + } + const float & operator[]( unsigned int i ) const + { + Q_ASSERT( i < 4 ); + return wxyz[ i ]; + } + Quat & operator*=( float s ) + { + for ( int c = 0; c < 4; c++ ) + wxyz[ c ] *= s; + return *this; + } + Quat operator*( float s ) const + { + Quat q( *this ); + return ( q *= s ); + } + Quat & operator+=( const Quat & q ) + { + for ( int c = 0; c < 4; c++ ) + wxyz[ c ] += q.wxyz[ c ]; + return *this; + } + Quat operator+( const Quat & q ) const + { + Quat r( *this ); + return ( r += q ); + } + static float dotproduct( const Quat & q1, const Quat & q2 ) + { + return q1[0]*q2[0]+q1[1]*q2[1]+q1[2]*q2[2]+q1[3]*q2[3]; + } + + + void fromString( QString str ); + void fromAxisAngle( Vector3 axis, float angle ); + void toAxisAngle( Vector3 & axis, float & angle ) const; + + QString toHtml() const + { + return QString( "W %1\nX %2\nY %3\nZ %4" ) + .arg( NumOrMinMax( wxyz[0] ) ) + .arg( NumOrMinMax( wxyz[1] ) ) + .arg( NumOrMinMax( wxyz[2] ) ) + .arg( NumOrMinMax( wxyz[3] ) ); + } + +protected: + float wxyz[4]; + static const float identity[4]; + + friend class NifIStream; + friend class NifOStream; +}; + +class Matrix +{ +public: + Matrix() + { + memcpy( m, identity, 36 ); + } + Matrix operator*( const Matrix & m2 ) const + { + Matrix m3; + for ( int r = 0; r < 3; r++ ) + for ( int c = 0; c < 3; c++ ) + m3.m[r][c] = m[r][0]*m2.m[0][c] + m[r][1]*m2.m[1][c] + m[r][2]*m2.m[2][c]; + return m3; + } + Vector3 operator*( const Vector3 & v ) const + { + return Vector3( + m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]*v[2], + m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]*v[2], + m[2][0]*v[0] + m[2][1]*v[1] + m[2][2]*v[2] ); + } + float & operator()( unsigned int c, unsigned int d ) + { + Q_ASSERT( c < 3 && d < 3 ); + return m[c][d]; + } + float operator()( unsigned int c, unsigned int d ) const + { + Q_ASSERT( c < 3 && d < 3 ); + return m[c][d]; + } + + Matrix inverted() const; + + void fromQuat( const Quat & q ); + Quat toQuat() const; + + void fromEuler( float x, float y, float z ); + bool toEuler( float & x, float & y, float & z ) const; + + static Matrix euler( float x, float y, float z ) + { + Matrix m; m.fromEuler( x, y, z ); + return m; + } + + QString toHtml() const; + +protected: + float m[3][3]; + static const float identity[9]; + + friend class NifIStream; + friend class NifOStream; +}; + +class Matrix4 +{ +public: + Matrix4() + { + memcpy( m, identity, 64 ); + } + Matrix4 operator*( const Matrix4 & m2 ) const + { + Matrix4 m3; + for ( int r = 0; r < 4; r++ ) + for ( int c = 0; c < 4; c++ ) + m3.m[r][c] = m[r][0]*m2.m[0][c] + m[r][1]*m2.m[1][c] + m[r][2]*m2.m[2][c] + m[r][3]*m2.m[3][c]; + return m3; + } + Vector3 operator*( const Vector3 & v ) const + { + return Vector3( + m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]*v[2] + m[0][3], + m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]*v[2] + m[1][3], + m[2][0]*v[0] + m[2][1]*v[1] + m[2][2]*v[2] + m[2][3] ); + } + float & operator()( unsigned int c, unsigned int d ) + { + Q_ASSERT( c < 4 && d < 4 ); + return m[c][d]; + } + float operator()( unsigned int c, unsigned int d ) const + { + Q_ASSERT( c < 4 && d < 4 ); + return m[c][d]; + } + + Matrix rotation() const; + Vector3 translation() const; + Vector3 scale() const; + + void decompose( Vector3 & trans, Matrix & rot, Vector3 & scale ) const; + void compose( const Vector3 & trans, const Matrix & rot, const Vector3 & scale ); + + //Matrix44 inverted() const; + + QString toHtml() const; + + const float * data() const { return (float *) m; } + +protected: + float m[4][4]; + static const float identity[16]; + + friend class NifIStream; + friend class NifOStream; +}; + +class Transform +{ +public: + Transform( const NifModel * nif, const QModelIndex & transform ); + Transform() { scale = 1.0; } + + static bool canConstruct( const NifModel * nif, const QModelIndex & parent ); + + void writeBack( NifModel * nif, const QModelIndex & transform ) const; + + friend Transform operator*( const Transform & t1, const Transform & t2 ); + + Vector3 operator*( const Vector3 & v ) const + { + return rotation * v * scale + translation; + } + + Matrix4 toMatrix4() const; + + Matrix rotation; + Vector3 translation; + float scale; + + friend QDataStream & operator<<( QDataStream & ds, const Transform & t ); + friend QDataStream & operator>>( QDataStream & ds, Transform & t ); + + QString toString() const; +}; + +class Triangle +{ +public: + Triangle() { v[0] = v[1] = v[2] = 0; } + Triangle( quint16 a, quint16 b, quint16 c ) { set( a, b, c ); } + + quint16 & operator[]( unsigned int i ) + { + Q_ASSERT( i < 3 ); + return v[i]; + } + const quint16 & operator[]( unsigned int i ) const + { + Q_ASSERT( i < 3 ); + return v[i]; + } + void set( quint16 a, quint16 b, quint16 c ) + { + v[0] = a; v[1] = b; v[2] = c; + } + inline quint16 v1() const { return v[0]; } + inline quint16 v2() const { return v[1]; } + inline quint16 v3() const { return v[2]; } + + void flip() { quint16 x = v[0]; v[0] = v[1]; v[1] = x; } + + Triangle operator+( quint16 d ) + { + Triangle t( *this ); + t.v[0] += d; + t.v[1] += d; + t.v[2] += d; + return t; + } + +protected: + quint16 v[3]; + friend class NifIStream; + friend class NifOStream; +}; + +inline float clamp01( float a ) +{ + if ( a < 0 ) return 0; + if ( a > 1 ) return 1; + return a; +} + +class Color3 +{ +public: + Color3() { rgb[0] = rgb[1] = rgb[2] = 1.0; } + Color3( float r, float g, float b ) { setRGB( r, g, b ); } + explicit Color3( const QColor & c ) { fromQColor( c ); } + explicit Color3( const Vector3 & v ) { fromVector3( v ); } + explicit Color3( const class Color4 & c4 ); + + float & operator[]( unsigned int i ) + { + Q_ASSERT( i < 3 ); + return rgb[i]; + } + + const float & operator[]( unsigned int i ) const + { + Q_ASSERT( i < 3 ); + return rgb[i]; + } + + Color3 operator*( float x ) const + { + Color3 c( *this ); + c.rgb[0] *= x; + c.rgb[1] *= x; + c.rgb[2] *= x; + return c; + } + + Color3 & operator+=( const Color3 & o ) + { + for ( int x = 0; x < 3; x++ ) + rgb[x] += o.rgb[x]; + return *this; + } + + Color3 & operator-=( const Color3 & o ) + { + for ( int x = 0; x < 3; x++ ) + rgb[x] -= o.rgb[x]; + return *this; + } + + Color3 operator+( const Color3 & o ) const + { + Color3 c( *this ); + return ( c += o ); + } + + Color3 operator-( const Color3 & o ) const + { + Color3 c( *this ); + return ( c -= o ); + } + + float red() const { return rgb[0]; } + float green() const { return rgb[1]; } + float blue() const { return rgb[2]; } + + void setRed( float r ) { rgb[0] = r; } + void setGreen( float g ) { rgb[1] = g; } + void setBlue( float b ) { rgb[2] = b; } + + void setRGB( float r, float g, float b ) { rgb[0] = r; rgb[1] = g; rgb[2] = b; } + + QColor toQColor() const + { + return QColor::fromRgbF( clamp01( rgb[0] ), clamp01( rgb[1] ), clamp01( rgb[2] ) ); + } + + void fromQColor( const QColor & c ) + { + rgb[0] = c.redF(); + rgb[1] = c.greenF(); + rgb[2] = c.blueF(); + } + + void fromVector3( const Vector3 & v ) + { + rgb[0] = v[0]; + rgb[1] = v[1]; + rgb[2] = v[2]; + } + + const float * data() const { return rgb; } + +protected: + float rgb[3]; + + friend class NifIStream; + friend class NifOStream; +}; + +class Color4 +{ +public: + Color4() { rgba[0] = rgba[1] = rgba[2] = rgba[3] = 1.0; } + explicit Color4( const Color3 & c, float alpha = 1.0 ) { rgba[0] = c[0]; rgba[1] = c[1]; rgba[2] = c[2]; rgba[3] = alpha; } + explicit Color4( const QColor & c ) { fromQColor( c ); } + Color4( float r, float g, float b, float a ) { setRGBA( r, g, b, a ); } + + float & operator[]( unsigned int i ) + { + Q_ASSERT( i < 4 ); + return rgba[ i ]; + } + + const float & operator[]( unsigned int i ) const + { + Q_ASSERT( i < 4 ); + return rgba[ i ]; + } + + Color4 operator*( float x ) const + { + Color4 c( *this ); + c.rgba[0] *= x; + c.rgba[1] *= x; + c.rgba[2] *= x; + c.rgba[3] *= x; + return c; + } + + Color4 & operator+=( const Color4 & o ) + { + for ( int x = 0; x < 4; x++ ) + rgba[x] += o.rgba[x]; + return *this; + } + + Color4 & operator-=( const Color4 & o ) + { + for ( int x = 0; x < 4; x++ ) + rgba[x] -= o.rgba[x]; + return *this; + } + + Color4 operator+( const Color4 & o ) const + { + Color4 c( *this ); + return ( c += o ); + } + + Color4 operator-( const Color4 & o ) const + { + Color4 c( *this ); + return ( c -= o ); + } + + bool operator==( const Color4 & c ) const + { + return rgba[0] == c.rgba[0] && rgba[1] == c.rgba[1] && rgba[2] == c.rgba[2] && rgba[3] == c.rgba[3]; + } + + float red() const { return rgba[0]; } + float green() const { return rgba[1]; } + float blue() const { return rgba[2]; } + float alpha() const { return rgba[3]; } + + void setRed( float r ) { rgba[0] = r; } + void setGreen( float g ) { rgba[1] = g; } + void setBlue( float b ) { rgba[2] = b; } + void setAlpha( float a ) { rgba[3] = a; } + + void setRGBA( float r, float g, float b, float a ) { rgba[ 0 ] = r; rgba[ 1 ] = g; rgba[ 2 ] = b; rgba[ 3 ] = a; } + + QColor toQColor() const + { + return QColor::fromRgbF( clamp01( rgba[0] ), clamp01( rgba[1] ), clamp01( rgba[2] ), clamp01( rgba[3] ) ); + } + + void fromQColor( const QColor & c ) + { + rgba[0] = c.redF(); + rgba[1] = c.greenF(); + rgba[2] = c.blueF(); + rgba[3] = c.alphaF(); + } + + const float * data() const { return rgba; } + + Color4 blend( float alpha ) const + { + Color4 c( *this ); + c.setAlpha( c.alpha() * alpha ); + return c; + } + +protected: + float rgba[4]; + + friend class NifIStream; + friend class NifOStream; +}; + +inline Color3::Color3( const Color4 & c4 ) +{ + rgb[0] = c4[0]; + rgb[1] = c4[1]; + rgb[2] = c4[2]; +} + + +//! A fixed length vector of type T. +//! Data is allocated into a vector portion and the data section. +//! The vector simply points to appropriate places in the data section. +//! @param T Type of Vector +template +class FixedMatrix +{ +public: + //! Default Constructor: Allocates empty vector + FixedMatrix() : v_( NULL ), len0(0), len1(0) + {} + + //! Size Constructor + //! Allocate the requested number of elements. + FixedMatrix(int length1, int length2) + { + int length = length1*length2; + v_ = (T*)qMalloc(sizeof(T)*length); + len0 = length1; + len1 = length2; + } + + //! Copy Constructor + FixedMatrix(const FixedMatrix& other) + { + int datalen = other.count(); + len0 = other.count(0); + len1 = other.count(1); + v_ = (T*)qMalloc(sizeof(T) * datalen); + qMemCopy( array(), other.array(), datalen ); + } + + //! Default Destructor + ~FixedMatrix() + { qFree(v_); } + + //! Copy Assignment + FixedMatrix& operator=(const FixedMatrix& other) + { + FixedMatrix tmp( other ); + swap( tmp ); + return *this; + } + + T* operator[](int index) + { + // assert( index >= 0 && index < len_ ) + return &v_[index*count(0) + 0]; + } + + //! Accessor for element (i,j) in the matrix + const T& operator[](int index) const + { + // assert( index >= 0 && index < len_ ) + return &v_[index*count(0) + 0]; + } + + //! Accessor for element (i,j) in the matrix + T* operator()(int index) + { + // assert( index >= 0 && index < len_ ) + return &v_[index*count(0) + 0]; + } + + //! Accessor for element (i,j) in the matrix + T& operator()(int index1, int index2) + { + // assert( index >= 0 && index < len_ ) + return element(index1, index2); + } + + //! Accessor for element (i,j) in the matrix + operator T*() const + { + return array(); + } + + //! Accessor for element (i,j) in the matrix + T& element(int index1, int index2) + { + return v_[ calcindex(index1, index2) ]; + } + + int calcindex(int index1, int index2) + { + return index1*count(1) + index2; + } + + //! Number of items in the vector. + int count() const + { return len0 * len1; } + + int count(int dimension) const + { return (dimension == 0 ? len0 : (dimension == 1 ? len1 : 0)); } + + //! Start of the array portion of the vector + T*array() const { return v_; } + T*data() const { return v_; } + + //! Assign a string to vector at specified index + //! @param[in] index Index in array to assign + //! @param[in] value Value to copy into string + void assign(int index1, int index2, T value) + { + element(index1, index2) = value; + } + + //! Swap contents with another APRFixedMatrix + //! @param[in,out] other Other vector to swap with + void swap( FixedMatrix &other ) + { + qSwap(v_, other.v_); + qSwap(len0, other.len0); + qSwap(len1, other.len1); + } + +private: + T* v_; //! Vector data + int len0, len1; //! length +}; + +typedef FixedMatrix ByteMatrix; + +#endif diff --git a/nifvalue.cpp b/nifvalue.cpp index f53aa201d..31da36e8b 100644 --- a/nifvalue.cpp +++ b/nifvalue.cpp @@ -1,1179 +1,1179 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "nifvalue.h" -#include "nifmodel.h" - -#include - -/* - * NifValue - */ - -QHash NifValue::typeMap; -QHash NifValue::typeTxt; -QHash > > NifValue::enumMap; - -void NifValue::initialize() -{ - typeMap.clear(); - typeTxt.clear(); - - typeMap.insert( "bool", NifValue::tBool ); - typeMap.insert( "byte", NifValue::tByte ); - typeMap.insert( "word", NifValue::tWord ); - typeMap.insert( "short", NifValue::tShort ); - typeMap.insert( "int", NifValue::tInt ); - typeMap.insert( "flags", NifValue::tFlags ); - typeMap.insert( "ushort", NifValue::tWord ); - typeMap.insert( "uint", NifValue::tUInt ); - typeMap.insert( "link", NifValue::tLink ); - typeMap.insert( "uplink", NifValue::tUpLink ); - typeMap.insert( "float", NifValue::tFloat ); - typeMap.insert( "sizedstring", NifValue::tSizedString ); - typeMap.insert( "text", NifValue::tText ); - typeMap.insert( "shortstring", NifValue::tShortString ); - typeMap.insert( "color3", NifValue::tColor3 ); - typeMap.insert( "color4", NifValue::tColor4 ); - typeMap.insert( "vector4", NifValue::tVector4 ); - typeMap.insert( "vector3", NifValue::tVector3 ); - typeMap.insert( "quat", NifValue::tQuat ); - typeMap.insert( "quaternion", NifValue::tQuat ); - typeMap.insert( "quaternion_wxyz", NifValue::tQuat ); - typeMap.insert( "quaternion_xyzw", NifValue::tQuatXYZW ); - typeMap.insert( "matrix33", NifValue::tMatrix ); - typeMap.insert( "matrix44", NifValue::tMatrix4 ); - typeMap.insert( "vector2", NifValue::tVector2 ); - typeMap.insert( "triangle", NifValue::tTriangle ); - typeMap.insert( "bytearray", NifValue::tByteArray ); - typeMap.insert( "bytematrix", NifValue::tByteMatrix); - typeMap.insert( "fileversion", NifValue::tFileVersion ); - typeMap.insert( "headerstring", NifValue::tHeaderString ); - typeMap.insert( "linestring", NifValue::tLineString ); - typeMap.insert( "stringpalette", NifValue::tStringPalette ); - typeMap.insert( "stringoffset", NifValue::tStringOffset ); - typeMap.insert( "stringindex", NifValue::tStringIndex ); - typeMap.insert( "blocktypeindex", NifValue::tBlockTypeIndex ); - typeMap.insert( "char8string", NifValue::tChar8String ); - typeMap.insert( "string", NifValue::tString ); - typeMap.insert( "filepath", NifValue::tFilePath); - - enumMap.clear(); -} - -NifValue::Type NifValue::type( const QString & id ) -{ - if ( typeMap.isEmpty() ) - initialize(); - - if ( typeMap.contains( id ) ) - return typeMap[id]; - - return tNone; -} - -void NifValue::setTypeDescription( const QString & typId, const QString & txt ) -{ - typeTxt[typId] = QString( txt ).replace( "\n", "
" ); -} - -QString NifValue::typeDescription( const QString & typId ) -{ - QString txt = QString( "

%1

%2

" ).arg( typId ).arg( typeTxt.value( typId ) ); - - if ( enumMap.contains( typId ) ) - { - txt += "

"; - QHashIterator< quint32, QPair< QString, QString > > it( enumMap[ typId ] ); - int cnt = 0; - while ( it.hasNext() ) - { - if ( cnt++ > 30 ) - { - cnt = 0; - txt += "
"; - } - it.next(); - txt += QString( "" ).arg( it.value().first ).arg( it.key() ).arg( it.value().second ); - } - txt += "
%2%1%3

"; - } - - return txt; -} - -bool NifValue::registerAlias( const QString & alias, const QString & original ) -{ - if ( typeMap.isEmpty() ) - initialize(); - - if ( typeMap.contains( original ) && ! typeMap.contains( alias ) ) - { - typeMap.insert( alias, typeMap[original] ); - return true; - } - - return false; -} - -bool NifValue::registerEnumOption( const QString & eid, const QString & oid, quint32 oval, const QString & otxt ) -{ - QHash< quint32, QPair< QString, QString > > & e = enumMap[eid]; - - if ( e.contains( oval ) ) - return false; - - e[oval] = QPair( oid, otxt ); - return true; -} - -QStringList NifValue::enumOptions( const QString & eid ) -{ - QStringList opts; - if ( enumMap.contains( eid ) ) - { - QHashIterator< quint32, QPair< QString, QString > > it( enumMap[ eid ] ); - while ( it.hasNext() ) - { - it.next(); - opts << it.value().first; - } - } - return opts; -} - -QString NifValue::enumOptionName( const QString & eid, quint32 val ) -{ - return enumMap.value( eid ).value( val ).first; -} - -QString NifValue::enumOptionText( const QString & eid, quint32 val ) -{ - return enumMap.value( eid ).value( val ).second; -} - -quint32 NifValue::enumOptionValue( const QString & eid, const QString & oid, bool * ok ) -{ - if ( enumMap.contains( eid ) ) - { - QHashIterator< quint32, QPair< QString, QString > > it( enumMap[ eid ] ); - while ( it.hasNext() ) - { - it.next(); - if ( it.value().first == oid ) - { - if ( ok ) *ok = true; - return it.key(); - } - } - } - if ( ok ) *ok = false; - return 0; -} - - -NifValue::NifValue( Type t ) : typ( tNone ) -{ - changeType( t ); -} - -NifValue::NifValue( const NifValue & other ) : typ( tNone ) -{ - operator=( other ); -} - -NifValue::~NifValue() -{ - clear(); -} - -void NifValue::clear() -{ - switch ( typ ) - { - case tVector4: - delete static_cast( val.data ); - break; - case tVector3: - delete static_cast( val.data ); - break; - case tVector2: - delete static_cast( val.data ); - break; - case tMatrix: - delete static_cast( val.data ); - break; - case tMatrix4: - delete static_cast( val.data ); - break; - case tQuat: - case tQuatXYZW: - delete static_cast( val.data ); - break; - case tByteMatrix: - delete static_cast( val.data ); - break; - case tByteArray: - case tStringPalette: - delete static_cast( val.data ); - break; - case tTriangle: - delete static_cast( val.data ); - break; - case tString: - case tSizedString: - case tText: - case tShortString: - case tHeaderString: - case tLineString: - case tChar8String: - delete static_cast( val.data ); - break; - case tColor3: - delete static_cast( val.data ); - break; - case tColor4: - delete static_cast( val.data ); - break; - default: - break; - } - typ = tNone; - val.u32 = 0; -} - -void NifValue::changeType( Type t ) -{ - if ( typ == t ) - return; - - if ( typ != tNone ) - clear(); - - switch ( ( typ = t ) ) - { - case tLink: - case tUpLink: - val.i32 = -1; - return; - case tVector3: - val.data = new Vector3(); - break; - case tVector4: - val.data = new Vector4(); - return; - case tMatrix: - val.data = new Matrix(); - return; - case tMatrix4: - val.data = new Matrix4(); - return; - case tQuat: - case tQuatXYZW: - val.data = new Quat(); - return; - case tVector2: - val.data = new Vector2(); - return; - case tTriangle: - val.data = new Triangle(); - return; - case tString: - case tSizedString: - case tText: - case tShortString: - case tHeaderString: - case tLineString: - case tChar8String: - val.data = new QString(); - return; - case tColor3: - val.data = new Color3(); - return; - case tColor4: - val.data = new Color4(); - return; - case tByteArray: - case tStringPalette: - val.data = new QByteArray(); - return; - case tByteMatrix: - val.data = new ByteMatrix(); - return; - case tStringOffset: - case tStringIndex: - val.u32 = 0xffffffff; - return; - default: - val.u32 = 0; - return; - } -} - -void NifValue::operator=( const NifValue & other ) -{ - if ( typ != other.typ ) - changeType( other.typ ); - - switch ( typ ) - { - case tVector3: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tVector4: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tMatrix: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tMatrix4: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tQuat: - case tQuatXYZW: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tVector2: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tString: - case tSizedString: - case tText: - case tShortString: - case tHeaderString: - case tLineString: - case tChar8String: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tColor3: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tColor4: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tByteArray: - case tStringPalette: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tByteMatrix: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - case tTriangle: - *static_cast( val.data ) = *static_cast( other.val.data ); - return; - default: - val = other.val; - return; - } -} - -QVariant NifValue::toVariant() const -{ - QVariant v; - v.setValue( *this ); - return v; -} - -bool NifValue::fromVariant( const QVariant & var ) -{ - if ( var.canConvert() ) - { - operator=( var.value() ); - return true; - } - else if ( var.type() == QVariant::String ) - { - return set( var.toString() ); - } - return false; -} - -bool NifValue::fromString( const QString & s ) -{ - bool ok; - switch ( typ ) - { - case tBool: - if ( s == "yes" || s == "true" ) - { - val.u32 = 1; - return true; - } - else if ( s == "no" || s == "false" ) - { - val.u32 = 0; - return true; - } - case tByte: - val.u32 = 0; - val.u08 = s.toUInt( &ok ); - return ok; - case tWord: - case tFlags: - case tStringOffset: - case tBlockTypeIndex: - case tShort: - val.u32 = 0; - val.u16 = s.toUInt( &ok ); - return ok; - case tInt: - case tUInt: - val.u32 = s.toUInt( &ok ); - return ok; - case tStringIndex: - val.u32 = s.toUInt( &ok ); - return ok; - case tLink: - case tUpLink: - val.i32 = s.toInt( &ok ); - return ok; - case tFloat: - val.f32 = s.toDouble( &ok ); - return ok; - case tString: - case tSizedString: - case tText: - case tShortString: - case tHeaderString: - case tLineString: - case tChar8String: - *static_cast( val.data ) = s; - return true; - case tColor3: - static_cast( val.data )->fromQColor( QColor( s ) ); - return true; - case tColor4: - static_cast( val.data )->fromQColor( QColor( s ) ); - return true; - case tFileVersion: - val.u32 = NifModel::version2number( s ); - return val.u32 != 0; - case tVector2: - static_cast( val.data )->fromString( s ); - return true; - case tVector3: - static_cast( val.data )->fromString( s ); - return true; - case tVector4: - static_cast( val.data )->fromString( s ); - return true; - case tQuat: - case tQuatXYZW: - static_cast( val.data )->fromString( s ); - return true; - case tByteArray: - case tByteMatrix: - case tStringPalette: - case tMatrix: - case tMatrix4: - case tTriangle: - case tNone: - return false; - } - return false; -} - -QString NifValue::toString() const -{ - switch ( typ ) - { - case tBool: - return ( val.u32 ? "yes" : "no" ); - case tByte: - case tWord: - case tFlags: - case tStringOffset: - case tBlockTypeIndex: - case tUInt: - return QString::number( val.u32 ); - case tStringIndex: - return QString::number( val.u32 ); - case tShort: - return QString::number( (short)val.u16 ); - case tInt: - return QString::number( (int)val.u32 ); - case tLink: - case tUpLink: - return QString::number( val.i32 ); - case tFloat: - return NumOrMinMax( val.f32, 'f', 4 ); - case tString: - case tSizedString: - case tText: - case tShortString: - case tHeaderString: - case tLineString: - case tChar8String: - return *static_cast( val.data ); - case tColor3: - { - Color3 * col = static_cast( val.data ); - return QString( "#%1%2%3" ) - .arg( (int) ( col->red() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int) ( col->green() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int) ( col->blue() * 0xff ), 2, 16, QChar( '0' ) ); - } - case tColor4: - { - Color4 * col = static_cast( val.data ); - return QString( "#%1%2%3%4" ) - .arg( (int) ( col->red() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int) ( col->green() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int) ( col->blue() * 0xff ), 2, 16, QChar( '0' ) ) - .arg( (int) ( col->alpha() * 0xff ), 2, 16, QChar( '0' ) ); - } - case tVector2: - { - Vector2 * v = static_cast( val.data ); - - return QString( "X %1 Y %2" ) - .arg( NumOrMinMax( (*v)[0], 'f', 4 ) ) - .arg( NumOrMinMax( (*v)[1], 'f', 4 ) ); - } - case tVector3: - { - Vector3 * v = static_cast( val.data ); - - return QString( "X %1 Y %2 Z %3" ) - .arg( NumOrMinMax( (*v)[0], 'f', 4 ) ) - .arg( NumOrMinMax( (*v)[1], 'f', 4 ) ) - .arg( NumOrMinMax( (*v)[2], 'f', 4 ) ); - } - case tVector4: - { - Vector4 * v = static_cast( val.data ); - - return QString( "X %1 Y %2 Z %3 W %4" ) - .arg( NumOrMinMax( (*v)[0], 'f', 4 ) ) - .arg( NumOrMinMax( (*v)[1], 'f', 4 ) ) - .arg( NumOrMinMax( (*v)[2], 'f', 4 ) ) - .arg( NumOrMinMax( (*v)[3], 'f', 4 ) ); - } - case tMatrix: - case tQuat: - case tQuatXYZW: - { - Matrix m; - if( typ == tMatrix ) - m = *(static_cast( val.data )); - else - m.fromQuat( *(static_cast( val.data )) ); - float x, y, z; - QString pre, suf; - if( !m.toEuler( x, y, z ) ) { - pre = "("; - suf = ")"; - } - - return ( pre + QString( "Y %1 P %2 R %3" ) + suf ) - .arg( NumOrMinMax( x / PI * 180, 'f', 1 ) ) - .arg( NumOrMinMax( y / PI * 180, 'f', 1 ) ) - .arg( NumOrMinMax( z / PI * 180, 'f', 1 ) ); - } - case tMatrix4: - { - Matrix4 * m = static_cast( val.data ); - Matrix r; Vector3 t, s; - m->decompose( t, r, s ); - float xr, yr, zr; - r.toEuler( xr, yr, zr ); - xr *= 180 / PI; - yr *= 180 / PI; - zr *= 180 / PI; - return QString( "Trans( X %1 Y %2 Z %3 ) Rot( Y %4 P %5 R %6 ) Scale( X %7 Y %8 Z %9 )" ) - .arg( t[0], 0, 'f', 3 ) - .arg( t[1], 0, 'f', 3 ) - .arg( t[2], 0, 'f', 3 ) - .arg( xr, 0, 'f', 3 ) - .arg( yr, 0, 'f', 3 ) - .arg( zr, 0, 'f', 3 ) - .arg( s[0], 0, 'f', 3 ) - .arg( s[1], 0, 'f', 3 ) - .arg( s[2], 0, 'f', 3 ); - } - case tByteArray: - return QString( "%1 bytes" ) - .arg( static_cast( val.data )->count() ); - case tStringPalette: - { - QByteArray * array = static_cast( val.data ); - QString s; - while ( s.length() < array->count() ) - { - s += & array->data()[s.length()]; - s += QChar( '|' ); - } - return s; - } - case tByteMatrix: - { - ByteMatrix * array = static_cast( val.data ); - return QString( "%1 bytes [%2 x %3]" ) - .arg( array->count() ) - .arg( array->count(0) ) - .arg( array->count(1) ) - ; - } - case tFileVersion: - return NifModel::version2string( val.u32 ); - case tTriangle: - { - Triangle * tri = static_cast( val.data ); - return QString( "%1 %2 %3" ) - .arg( tri->v1() ) - .arg( tri->v2() ) - .arg( tri->v3() ); - } - case tFilePath: - { - return *static_cast( val.data ); - } - default: - return QString(); - } -} - -QColor NifValue::toColor() const -{ - if ( type() == tColor3 ) - return static_cast( val.data )->toQColor(); - else if ( type() == tColor4 ) - return static_cast( val.data )->toQColor(); - else - return QColor(); -} - -void NifOStream::init() -{ - bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); - linkAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D ); - stringAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003 ); -} - -bool NifIStream::read( NifValue & val ) -{ - switch ( val.type() ) - { - case NifValue::tBool: - val.val.u32 = 0; - if ( bool32bit ) - return device->read( (char *) &val.val.u32, 4 ) == 4; - else - return device->read( (char *) &val.val.u08, 1 ) == 1; - case NifValue::tByte: - val.val.u32 = 0; - return device->read( (char *) &val.val.u08, 1 ) == 1; - case NifValue::tWord: - case NifValue::tShort: - case NifValue::tFlags: - case NifValue::tBlockTypeIndex: - val.val.u32 = 0; - return device->read( (char *) &val.val.u16, 2 ) == 2; - case NifValue::tStringOffset: - case NifValue::tInt: - case NifValue::tUInt: - return device->read( (char *) &val.val.u32, 4 ) == 4; - case NifValue::tStringIndex: - return device->read( (char *) &val.val.u32, 4 ) == 4; - case NifValue::tLink: - case NifValue::tUpLink: - { - if ( device->read( (char *) &val.val.i32, 4 ) ) - { - if ( linkAdjust ) - val.val.i32--; - return true; - } - else - return false; - } - case NifValue::tFloat: - return device->read( (char *) &val.val.f32, 4 ) == 4; - case NifValue::tVector3: - return device->read( (char *) static_cast( val.val.data )->xyz, 12 ) == 12; - case NifValue::tVector4: - return device->read( (char *) static_cast( val.val.data )->xyzw, 16 ) == 16; - case NifValue::tTriangle: - return device->read( (char *) static_cast( val.val.data )->v, 6 ) == 6; - case NifValue::tQuat: - return device->read( (char *) static_cast( val.val.data )->wxyz, 16 ) == 16; - case NifValue::tQuatXYZW: - { - Quat * q = static_cast( val.val.data ); - return device->read( (char *) &q->wxyz[1], 12 ) == 12 && device->read( (char *) q->wxyz, 4 ) == 4; - } - case NifValue::tMatrix: - return device->read( (char *) static_cast( val.val.data )->m, 36 ) == 36; - case NifValue::tMatrix4: - return device->read( (char *) static_cast( val.val.data )->m, 64 ) == 64; - case NifValue::tVector2: - return device->read( (char *) static_cast( val.val.data )->xy, 8 ) == 8; - case NifValue::tColor3: - return device->read( (char *) static_cast( val.val.data )->rgb, 12 ) == 12; - case NifValue::tColor4: - return device->read( (char *) static_cast( val.val.data )->rgba, 16 ) == 16; - case NifValue::tSizedString: - { - int len; - device->read( (char *) &len, 4 ); - if ( len > 4096 || len < 0 ) { *static_cast( val.val.data ) = ""; return false; } - QByteArray string = device->read( len ); - if ( string.size() != len ) return false; - //string.replace( "\r", "\\r" ); - //string.replace( "\n", "\\n" ); - *static_cast( val.val.data ) = QString( string ); - } return true; - case NifValue::tShortString: - { - unsigned char len; - device->read( (char *) &len, 1 ); - QByteArray string = device->read( len ); - if ( string.size() != len ) return false; - //string.replace( "\r", "\\r" ); - //string.replace( "\n", "\\n" ); - *static_cast( val.val.data ) = QString( string ); - } return true; - case NifValue::tText: - { - int len; - device->read( (char *) &len, 4 ); - if ( len > 4096 || len < 0 ) { *static_cast( val.val.data ) = ""; return false; } - QByteArray string = device->read( len ); - if ( string.size() != len ) return false; - *static_cast( val.val.data ) = QString( string ); - } return true; - case NifValue::tByteArray: - { - int len; - device->read( (char *) &len, 4 ); - if ( len < 0 ) return false; - *static_cast( val.val.data ) = device->read( len ); - return static_cast( val.val.data )->count() == len; - } - case NifValue::tStringPalette: - { - int len; - device->read( (char *) &len, 4 ); - if ( len > 0xffff || len < 0 ) return false; - *static_cast( val.val.data ) = device->read( len ); - device->read( (char *) &len, 4 ); - return true; - } - case NifValue::tByteMatrix: - { - int len1, len2; - device->read( (char *) &len1, 4 ); - device->read( (char *) &len2, 4 ); - if ( len1 < 0 || len2 < 0) return false; - int len = len1 * len2; - ByteMatrix tmp(len1, len2); - qint64 rlen = device->read( tmp.data(), len ); - tmp.swap( *static_cast( val.val.data ) ); - return (rlen == len); - } - case NifValue::tHeaderString: - { - QByteArray string; - int c = 0; - char chr = 0; - while ( c++ < 80 && device->getChar( &chr ) && chr != '\n' ) - string.append( chr ); - if ( c >= 80 ) return false; - *static_cast( val.val.data ) = QString( string ); - bool x = model->setHeaderString( QString( string ) ); - init(); - return x; - } - case NifValue::tLineString: - { - QByteArray string; - int c = 0; - char chr = 0; - while ( c++ < 255 && device->getChar( &chr ) && chr != '\n' ) - string.append( chr ); - if ( c >= 255 ) return false; - *static_cast( val.val.data ) = QString( string ); - return true; - } - case NifValue::tChar8String: - { - QByteArray string; - int c = 0; - char chr = 0; - while ( c++ < 8 && device->getChar( &chr ) ) - string.append( chr ); - if ( c > 9 ) return false; - *static_cast( val.val.data ) = QString( string ); - return true; - } - case NifValue::tFileVersion: - { - if ( device->read( (char *) &val.val.u32, 4 ) != 4 ) return false; - //bool x = model->setVersion( val.val.u32 ); - //init(); - return true; - } - case NifValue::tString: - { - if (stringAdjust) - { - val.changeType(NifValue::tStringIndex); - return device->read( (char *) &val.val.i32, 4 ) == 4; - } - else - { - val.changeType(NifValue::tSizedString); - - int len; - device->read( (char *) &len, 4 ); - if ( len > 4096 || len < 0 ) { *static_cast( val.val.data ) = ""; return false; } - QByteArray string = device->read( len ); - if ( string.size() != len ) return false; - //string.replace( "\r", "\\r" ); - //string.replace( "\n", "\\n" ); - *static_cast( val.val.data ) = QString( string ); - return true; - } - } - case NifValue::tFilePath: - { - if (stringAdjust) - { - val.changeType(NifValue::tStringIndex); - return device->read( (char *) &val.val.i32, 4 ) == 4; - } - else - { - val.changeType(NifValue::tSizedString); - - int len; - device->read( (char *) &len, 4 ); - if ( len > 4096 || len < 0 ) { *static_cast( val.val.data ) = ""; return false; } - QByteArray string = device->read( len ); - if ( string.size() != len ) return false; - *static_cast( val.val.data ) = QString( string ); - return true; - } - } - - case NifValue::tNone: - return true; - } - return false; -} - -void NifIStream::init() -{ - bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); - linkAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D ); - stringAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003 ); -} - -bool NifOStream::write( const NifValue & val ) -{ - switch ( val.type() ) - { - case NifValue::tBool: - if ( bool32bit ) - return device->write( (char *) &val.val.u32, 4 ) == 4; - else - return device->write( (char *) &val.val.u08, 1 ) == 1; - case NifValue::tByte: - return device->write( (char *) &val.val.u08, 1 ) == 1; - case NifValue::tWord: - case NifValue::tShort: - case NifValue::tFlags: - case NifValue::tBlockTypeIndex: - return device->write( (char *) &val.val.u16, 2 ) == 2; - case NifValue::tStringOffset: - case NifValue::tInt: - case NifValue::tUInt: - case NifValue::tFileVersion: - case NifValue::tStringIndex: - return device->write( (char *) &val.val.u32, 4 ) == 4; - case NifValue::tLink: - case NifValue::tUpLink: - if ( ! linkAdjust ) - { - return device->write( (char *) &val.val.i32, 4 ) == 4; - } - else - { - qint32 l = val.val.i32 + 1; - return device->write( (char *) &l, 4 ) == 4; - } - case NifValue::tFloat: - return device->write( (char *) &val.val.f32, 4 ) == 4; - case NifValue::tVector3: - return device->write( (char *) static_cast( val.val.data )->xyz, 12 ) == 12; - case NifValue::tVector4: - return device->write( (char *) static_cast( val.val.data )->xyzw, 16 ) == 16; - case NifValue::tTriangle: - return device->write( (char *) static_cast( val.val.data )->v, 6 ) == 6; - case NifValue::tQuat: - return device->write( (char *) static_cast( val.val.data )->wxyz, 16 ) == 16; - case NifValue::tQuatXYZW: - { - Quat * q = static_cast( val.val.data ); - return device->write( (char *) &q->wxyz[1], 12 ) == 12 && device->write( (char *) q->wxyz, 4 ) == 4; - } - case NifValue::tMatrix: - return device->write( (char *) static_cast( val.val.data )->m, 36 ) == 36; - case NifValue::tMatrix4: - return device->write( (char *) static_cast( val.val.data )->m, 64 ) == 64; - case NifValue::tVector2: - return device->write( (char *) static_cast( val.val.data )->xy, 8 ) == 8; - case NifValue::tColor3: - return device->write( (char *) static_cast( val.val.data )->rgb, 12 ) == 12; - case NifValue::tColor4: - return device->write( (char *) static_cast( val.val.data )->rgba, 16 ) == 16; - case NifValue::tSizedString: - { - QByteArray string = static_cast( val.val.data )->toAscii(); - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - int len = string.size(); - if ( device->write( (char *) &len, 4 ) != 4 ) - return false; - return device->write( (const char *) string, string.size() ) == string.size(); - } - case NifValue::tShortString: - { - QByteArray string = static_cast( val.val.data )->toAscii(); - string.replace( "\\r", "\r" ); - string.replace( "\\n", "\n" ); - if ( string.size() > 254 ) string.resize( 254 ); - unsigned char len = string.size() + 1; - if ( device->write( (char *) &len, 1 ) != 1 ) - return false; - return device->write( (const char *) string, len ) == len; - } - case NifValue::tText: - { - QByteArray string = static_cast( val.val.data )->toAscii(); - int len = string.size(); - if ( device->write( (char *) &len, 4 ) != 4 ) - return false; - return device->write( (const char *) string, string.size() ) == string.size(); - } - case NifValue::tHeaderString: - case NifValue::tLineString: - { - QByteArray string = static_cast( val.val.data )->toAscii(); - if ( device->write( (const char *) string, string.length() ) != string.length() ) - return false; - return ( device->write( "\n", 1 ) == 1 ); - } - case NifValue::tChar8String: - { - QByteArray string = static_cast( val.val.data )->toAscii(); - quint32 n = std::min(8, string.length()); - if ( device->write( (const char *) string, n ) != n ) - return false; - for ( quint32 i = n; i < 8; ++i) - if ( device->write( "\0", 1 ) != 1 ) return false; - return true; - } - case NifValue::tByteArray: - { - QByteArray * array = static_cast( val.val.data ); - int len = array->count(); - if ( device->write( (char *) &len, 4 ) != 4 ) - return false; - return device->write( *array ) == len; - } - case NifValue::tStringPalette: - { - QByteArray * array = static_cast( val.val.data ); - int len = array->count(); - if ( device->write( (char *) &len, 4 ) != 4 ) - return false; - if ( device->write( *array ) != len ) - return false; - return device->write( (char *) &len, 4 ) == 4; - } - case NifValue::tByteMatrix: - { - ByteMatrix * array = static_cast( val.val.data ); - int len = array->count(0); - if ( device->write( (char *) &len, 4 ) != 4 ) - return false; - len = array->count(1); - if ( device->write( (char *) &len, 4 ) != 4 ) - return false; - len = array->count(); - return device->write(array->data(), len) == len; - } - case NifValue::tString: - case NifValue::tFilePath: - { - if (stringAdjust) - { - return device->write( (char *) &val.val.u32, 4 ) == 4; - } - else - { - QByteArray string; - if( val.val.data != 0 ) - { - string = static_cast( val.val.data )->toAscii(); - } - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - int len = string.size(); - if ( device->write( (char *) &len, 4 ) != 4 ) - return false; - return device->write( (const char *) string, string.size() ) == string.size(); - } - } - case NifValue::tNone: - return true; - } - return false; -} - -void NifSStream::init() -{ - bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); - stringAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003 ); -} - -int NifSStream::size( const NifValue & val ) -{ - switch ( val.type() ) - { - case NifValue::tBool: - if ( bool32bit ) - return 4; - else - return 1; - case NifValue::tByte: - return 1; - case NifValue::tWord: - case NifValue::tShort: - case NifValue::tFlags: - case NifValue::tBlockTypeIndex: - return 2; - case NifValue::tStringOffset: - case NifValue::tInt: - case NifValue::tUInt: - case NifValue::tStringIndex: - case NifValue::tFileVersion: - case NifValue::tLink: - case NifValue::tUpLink: - case NifValue::tFloat: - return 4; - case NifValue::tVector3: - return 12; - case NifValue::tVector4: - return 16; - case NifValue::tTriangle: - return 6; - case NifValue::tQuat: - case NifValue::tQuatXYZW: - return 16; - case NifValue::tMatrix: - return 36; - case NifValue::tMatrix4: - return 64; - case NifValue::tVector2: - return 8; - case NifValue::tColor3: - return 12; - case NifValue::tColor4: - return 16; - case NifValue::tSizedString: - { - QByteArray string = static_cast( val.val.data )->toAscii(); - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - return 4 + string.size(); - } - case NifValue::tShortString: - { - QByteArray string = static_cast( val.val.data )->toAscii(); - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - if ( string.size() > 254 ) string.resize( 254 ); - return 1 + string.size() + 1; - } - case NifValue::tText: - { - QByteArray string = static_cast( val.val.data )->toAscii(); - return 4 + string.size(); - } - case NifValue::tHeaderString: - case NifValue::tLineString: - { - QByteArray string = static_cast( val.val.data )->toAscii(); - return string.length() + 1; - } - case NifValue::tChar8String: - { - return 8; - } - case NifValue::tByteArray: - { - QByteArray * array = static_cast( val.val.data ); - return 4 + array->count(); - } - case NifValue::tStringPalette: - { - QByteArray * array = static_cast( val.val.data ); - return 4 + array->count() + 4; - } - case NifValue::tByteMatrix: - { - ByteMatrix * array = static_cast( val.val.data ); - return 4 + 4 + array->count(); - } - case NifValue::tString: - case NifValue::tFilePath: - { - if (stringAdjust) - { - return 4; - } - else - { - QByteArray string = static_cast( val.val.data )->toAscii(); - //string.replace( "\\r", "\r" ); - //string.replace( "\\n", "\n" ); - return 4 + string.size(); - } - } - case NifValue::tNone: - return 0; - } - return 0; -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "nifvalue.h" +#include "nifmodel.h" + +#include + +/* + * NifValue + */ + +QHash NifValue::typeMap; +QHash NifValue::typeTxt; +QHash > > NifValue::enumMap; + +void NifValue::initialize() +{ + typeMap.clear(); + typeTxt.clear(); + + typeMap.insert( "bool", NifValue::tBool ); + typeMap.insert( "byte", NifValue::tByte ); + typeMap.insert( "word", NifValue::tWord ); + typeMap.insert( "short", NifValue::tShort ); + typeMap.insert( "int", NifValue::tInt ); + typeMap.insert( "flags", NifValue::tFlags ); + typeMap.insert( "ushort", NifValue::tWord ); + typeMap.insert( "uint", NifValue::tUInt ); + typeMap.insert( "link", NifValue::tLink ); + typeMap.insert( "uplink", NifValue::tUpLink ); + typeMap.insert( "float", NifValue::tFloat ); + typeMap.insert( "sizedstring", NifValue::tSizedString ); + typeMap.insert( "text", NifValue::tText ); + typeMap.insert( "shortstring", NifValue::tShortString ); + typeMap.insert( "color3", NifValue::tColor3 ); + typeMap.insert( "color4", NifValue::tColor4 ); + typeMap.insert( "vector4", NifValue::tVector4 ); + typeMap.insert( "vector3", NifValue::tVector3 ); + typeMap.insert( "quat", NifValue::tQuat ); + typeMap.insert( "quaternion", NifValue::tQuat ); + typeMap.insert( "quaternion_wxyz", NifValue::tQuat ); + typeMap.insert( "quaternion_xyzw", NifValue::tQuatXYZW ); + typeMap.insert( "matrix33", NifValue::tMatrix ); + typeMap.insert( "matrix44", NifValue::tMatrix4 ); + typeMap.insert( "vector2", NifValue::tVector2 ); + typeMap.insert( "triangle", NifValue::tTriangle ); + typeMap.insert( "bytearray", NifValue::tByteArray ); + typeMap.insert( "bytematrix", NifValue::tByteMatrix); + typeMap.insert( "fileversion", NifValue::tFileVersion ); + typeMap.insert( "headerstring", NifValue::tHeaderString ); + typeMap.insert( "linestring", NifValue::tLineString ); + typeMap.insert( "stringpalette", NifValue::tStringPalette ); + typeMap.insert( "stringoffset", NifValue::tStringOffset ); + typeMap.insert( "stringindex", NifValue::tStringIndex ); + typeMap.insert( "blocktypeindex", NifValue::tBlockTypeIndex ); + typeMap.insert( "char8string", NifValue::tChar8String ); + typeMap.insert( "string", NifValue::tString ); + typeMap.insert( "filepath", NifValue::tFilePath); + + enumMap.clear(); +} + +NifValue::Type NifValue::type( const QString & id ) +{ + if ( typeMap.isEmpty() ) + initialize(); + + if ( typeMap.contains( id ) ) + return typeMap[id]; + + return tNone; +} + +void NifValue::setTypeDescription( const QString & typId, const QString & txt ) +{ + typeTxt[typId] = QString( txt ).replace( "\n", "
" ); +} + +QString NifValue::typeDescription( const QString & typId ) +{ + QString txt = QString( "

%1

%2

" ).arg( typId ).arg( typeTxt.value( typId ) ); + + if ( enumMap.contains( typId ) ) + { + txt += "

"; + QHashIterator< quint32, QPair< QString, QString > > it( enumMap[ typId ] ); + int cnt = 0; + while ( it.hasNext() ) + { + if ( cnt++ > 30 ) + { + cnt = 0; + txt += "
"; + } + it.next(); + txt += QString( "" ).arg( it.value().first ).arg( it.key() ).arg( it.value().second ); + } + txt += "
%2%1%3

"; + } + + return txt; +} + +bool NifValue::registerAlias( const QString & alias, const QString & original ) +{ + if ( typeMap.isEmpty() ) + initialize(); + + if ( typeMap.contains( original ) && ! typeMap.contains( alias ) ) + { + typeMap.insert( alias, typeMap[original] ); + return true; + } + + return false; +} + +bool NifValue::registerEnumOption( const QString & eid, const QString & oid, quint32 oval, const QString & otxt ) +{ + QHash< quint32, QPair< QString, QString > > & e = enumMap[eid]; + + if ( e.contains( oval ) ) + return false; + + e[oval] = QPair( oid, otxt ); + return true; +} + +QStringList NifValue::enumOptions( const QString & eid ) +{ + QStringList opts; + if ( enumMap.contains( eid ) ) + { + QHashIterator< quint32, QPair< QString, QString > > it( enumMap[ eid ] ); + while ( it.hasNext() ) + { + it.next(); + opts << it.value().first; + } + } + return opts; +} + +QString NifValue::enumOptionName( const QString & eid, quint32 val ) +{ + return enumMap.value( eid ).value( val ).first; +} + +QString NifValue::enumOptionText( const QString & eid, quint32 val ) +{ + return enumMap.value( eid ).value( val ).second; +} + +quint32 NifValue::enumOptionValue( const QString & eid, const QString & oid, bool * ok ) +{ + if ( enumMap.contains( eid ) ) + { + QHashIterator< quint32, QPair< QString, QString > > it( enumMap[ eid ] ); + while ( it.hasNext() ) + { + it.next(); + if ( it.value().first == oid ) + { + if ( ok ) *ok = true; + return it.key(); + } + } + } + if ( ok ) *ok = false; + return 0; +} + + +NifValue::NifValue( Type t ) : typ( tNone ) +{ + changeType( t ); +} + +NifValue::NifValue( const NifValue & other ) : typ( tNone ) +{ + operator=( other ); +} + +NifValue::~NifValue() +{ + clear(); +} + +void NifValue::clear() +{ + switch ( typ ) + { + case tVector4: + delete static_cast( val.data ); + break; + case tVector3: + delete static_cast( val.data ); + break; + case tVector2: + delete static_cast( val.data ); + break; + case tMatrix: + delete static_cast( val.data ); + break; + case tMatrix4: + delete static_cast( val.data ); + break; + case tQuat: + case tQuatXYZW: + delete static_cast( val.data ); + break; + case tByteMatrix: + delete static_cast( val.data ); + break; + case tByteArray: + case tStringPalette: + delete static_cast( val.data ); + break; + case tTriangle: + delete static_cast( val.data ); + break; + case tString: + case tSizedString: + case tText: + case tShortString: + case tHeaderString: + case tLineString: + case tChar8String: + delete static_cast( val.data ); + break; + case tColor3: + delete static_cast( val.data ); + break; + case tColor4: + delete static_cast( val.data ); + break; + default: + break; + } + typ = tNone; + val.u32 = 0; +} + +void NifValue::changeType( Type t ) +{ + if ( typ == t ) + return; + + if ( typ != tNone ) + clear(); + + switch ( ( typ = t ) ) + { + case tLink: + case tUpLink: + val.i32 = -1; + return; + case tVector3: + val.data = new Vector3(); + break; + case tVector4: + val.data = new Vector4(); + return; + case tMatrix: + val.data = new Matrix(); + return; + case tMatrix4: + val.data = new Matrix4(); + return; + case tQuat: + case tQuatXYZW: + val.data = new Quat(); + return; + case tVector2: + val.data = new Vector2(); + return; + case tTriangle: + val.data = new Triangle(); + return; + case tString: + case tSizedString: + case tText: + case tShortString: + case tHeaderString: + case tLineString: + case tChar8String: + val.data = new QString(); + return; + case tColor3: + val.data = new Color3(); + return; + case tColor4: + val.data = new Color4(); + return; + case tByteArray: + case tStringPalette: + val.data = new QByteArray(); + return; + case tByteMatrix: + val.data = new ByteMatrix(); + return; + case tStringOffset: + case tStringIndex: + val.u32 = 0xffffffff; + return; + default: + val.u32 = 0; + return; + } +} + +void NifValue::operator=( const NifValue & other ) +{ + if ( typ != other.typ ) + changeType( other.typ ); + + switch ( typ ) + { + case tVector3: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tVector4: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tMatrix: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tMatrix4: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tQuat: + case tQuatXYZW: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tVector2: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tString: + case tSizedString: + case tText: + case tShortString: + case tHeaderString: + case tLineString: + case tChar8String: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tColor3: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tColor4: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tByteArray: + case tStringPalette: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tByteMatrix: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + case tTriangle: + *static_cast( val.data ) = *static_cast( other.val.data ); + return; + default: + val = other.val; + return; + } +} + +QVariant NifValue::toVariant() const +{ + QVariant v; + v.setValue( *this ); + return v; +} + +bool NifValue::fromVariant( const QVariant & var ) +{ + if ( var.canConvert() ) + { + operator=( var.value() ); + return true; + } + else if ( var.type() == QVariant::String ) + { + return set( var.toString() ); + } + return false; +} + +bool NifValue::fromString( const QString & s ) +{ + bool ok; + switch ( typ ) + { + case tBool: + if ( s == "yes" || s == "true" ) + { + val.u32 = 1; + return true; + } + else if ( s == "no" || s == "false" ) + { + val.u32 = 0; + return true; + } + case tByte: + val.u32 = 0; + val.u08 = s.toUInt( &ok ); + return ok; + case tWord: + case tFlags: + case tStringOffset: + case tBlockTypeIndex: + case tShort: + val.u32 = 0; + val.u16 = s.toUInt( &ok ); + return ok; + case tInt: + case tUInt: + val.u32 = s.toUInt( &ok ); + return ok; + case tStringIndex: + val.u32 = s.toUInt( &ok ); + return ok; + case tLink: + case tUpLink: + val.i32 = s.toInt( &ok ); + return ok; + case tFloat: + val.f32 = s.toDouble( &ok ); + return ok; + case tString: + case tSizedString: + case tText: + case tShortString: + case tHeaderString: + case tLineString: + case tChar8String: + *static_cast( val.data ) = s; + return true; + case tColor3: + static_cast( val.data )->fromQColor( QColor( s ) ); + return true; + case tColor4: + static_cast( val.data )->fromQColor( QColor( s ) ); + return true; + case tFileVersion: + val.u32 = NifModel::version2number( s ); + return val.u32 != 0; + case tVector2: + static_cast( val.data )->fromString( s ); + return true; + case tVector3: + static_cast( val.data )->fromString( s ); + return true; + case tVector4: + static_cast( val.data )->fromString( s ); + return true; + case tQuat: + case tQuatXYZW: + static_cast( val.data )->fromString( s ); + return true; + case tByteArray: + case tByteMatrix: + case tStringPalette: + case tMatrix: + case tMatrix4: + case tTriangle: + case tNone: + return false; + } + return false; +} + +QString NifValue::toString() const +{ + switch ( typ ) + { + case tBool: + return ( val.u32 ? "yes" : "no" ); + case tByte: + case tWord: + case tFlags: + case tStringOffset: + case tBlockTypeIndex: + case tUInt: + return QString::number( val.u32 ); + case tStringIndex: + return QString::number( val.u32 ); + case tShort: + return QString::number( (short)val.u16 ); + case tInt: + return QString::number( (int)val.u32 ); + case tLink: + case tUpLink: + return QString::number( val.i32 ); + case tFloat: + return NumOrMinMax( val.f32, 'f', 4 ); + case tString: + case tSizedString: + case tText: + case tShortString: + case tHeaderString: + case tLineString: + case tChar8String: + return *static_cast( val.data ); + case tColor3: + { + Color3 * col = static_cast( val.data ); + return QString( "#%1%2%3" ) + .arg( (int) ( col->red() * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int) ( col->green() * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int) ( col->blue() * 0xff ), 2, 16, QChar( '0' ) ); + } + case tColor4: + { + Color4 * col = static_cast( val.data ); + return QString( "#%1%2%3%4" ) + .arg( (int) ( col->red() * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int) ( col->green() * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int) ( col->blue() * 0xff ), 2, 16, QChar( '0' ) ) + .arg( (int) ( col->alpha() * 0xff ), 2, 16, QChar( '0' ) ); + } + case tVector2: + { + Vector2 * v = static_cast( val.data ); + + return QString( "X %1 Y %2" ) + .arg( NumOrMinMax( (*v)[0], 'f', 4 ) ) + .arg( NumOrMinMax( (*v)[1], 'f', 4 ) ); + } + case tVector3: + { + Vector3 * v = static_cast( val.data ); + + return QString( "X %1 Y %2 Z %3" ) + .arg( NumOrMinMax( (*v)[0], 'f', 4 ) ) + .arg( NumOrMinMax( (*v)[1], 'f', 4 ) ) + .arg( NumOrMinMax( (*v)[2], 'f', 4 ) ); + } + case tVector4: + { + Vector4 * v = static_cast( val.data ); + + return QString( "X %1 Y %2 Z %3 W %4" ) + .arg( NumOrMinMax( (*v)[0], 'f', 4 ) ) + .arg( NumOrMinMax( (*v)[1], 'f', 4 ) ) + .arg( NumOrMinMax( (*v)[2], 'f', 4 ) ) + .arg( NumOrMinMax( (*v)[3], 'f', 4 ) ); + } + case tMatrix: + case tQuat: + case tQuatXYZW: + { + Matrix m; + if( typ == tMatrix ) + m = *(static_cast( val.data )); + else + m.fromQuat( *(static_cast( val.data )) ); + float x, y, z; + QString pre, suf; + if( !m.toEuler( x, y, z ) ) { + pre = "("; + suf = ")"; + } + + return ( pre + QString( "Y %1 P %2 R %3" ) + suf ) + .arg( NumOrMinMax( x / PI * 180, 'f', 1 ) ) + .arg( NumOrMinMax( y / PI * 180, 'f', 1 ) ) + .arg( NumOrMinMax( z / PI * 180, 'f', 1 ) ); + } + case tMatrix4: + { + Matrix4 * m = static_cast( val.data ); + Matrix r; Vector3 t, s; + m->decompose( t, r, s ); + float xr, yr, zr; + r.toEuler( xr, yr, zr ); + xr *= 180 / PI; + yr *= 180 / PI; + zr *= 180 / PI; + return QString( "Trans( X %1 Y %2 Z %3 ) Rot( Y %4 P %5 R %6 ) Scale( X %7 Y %8 Z %9 )" ) + .arg( t[0], 0, 'f', 3 ) + .arg( t[1], 0, 'f', 3 ) + .arg( t[2], 0, 'f', 3 ) + .arg( xr, 0, 'f', 3 ) + .arg( yr, 0, 'f', 3 ) + .arg( zr, 0, 'f', 3 ) + .arg( s[0], 0, 'f', 3 ) + .arg( s[1], 0, 'f', 3 ) + .arg( s[2], 0, 'f', 3 ); + } + case tByteArray: + return QString( "%1 bytes" ) + .arg( static_cast( val.data )->count() ); + case tStringPalette: + { + QByteArray * array = static_cast( val.data ); + QString s; + while ( s.length() < array->count() ) + { + s += & array->data()[s.length()]; + s += QChar( '|' ); + } + return s; + } + case tByteMatrix: + { + ByteMatrix * array = static_cast( val.data ); + return QString( "%1 bytes [%2 x %3]" ) + .arg( array->count() ) + .arg( array->count(0) ) + .arg( array->count(1) ) + ; + } + case tFileVersion: + return NifModel::version2string( val.u32 ); + case tTriangle: + { + Triangle * tri = static_cast( val.data ); + return QString( "%1 %2 %3" ) + .arg( tri->v1() ) + .arg( tri->v2() ) + .arg( tri->v3() ); + } + case tFilePath: + { + return *static_cast( val.data ); + } + default: + return QString(); + } +} + +QColor NifValue::toColor() const +{ + if ( type() == tColor3 ) + return static_cast( val.data )->toQColor(); + else if ( type() == tColor4 ) + return static_cast( val.data )->toQColor(); + else + return QColor(); +} + +void NifOStream::init() +{ + bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); + linkAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D ); + stringAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003 ); +} + +bool NifIStream::read( NifValue & val ) +{ + switch ( val.type() ) + { + case NifValue::tBool: + val.val.u32 = 0; + if ( bool32bit ) + return device->read( (char *) &val.val.u32, 4 ) == 4; + else + return device->read( (char *) &val.val.u08, 1 ) == 1; + case NifValue::tByte: + val.val.u32 = 0; + return device->read( (char *) &val.val.u08, 1 ) == 1; + case NifValue::tWord: + case NifValue::tShort: + case NifValue::tFlags: + case NifValue::tBlockTypeIndex: + val.val.u32 = 0; + return device->read( (char *) &val.val.u16, 2 ) == 2; + case NifValue::tStringOffset: + case NifValue::tInt: + case NifValue::tUInt: + return device->read( (char *) &val.val.u32, 4 ) == 4; + case NifValue::tStringIndex: + return device->read( (char *) &val.val.u32, 4 ) == 4; + case NifValue::tLink: + case NifValue::tUpLink: + { + if ( device->read( (char *) &val.val.i32, 4 ) ) + { + if ( linkAdjust ) + val.val.i32--; + return true; + } + else + return false; + } + case NifValue::tFloat: + return device->read( (char *) &val.val.f32, 4 ) == 4; + case NifValue::tVector3: + return device->read( (char *) static_cast( val.val.data )->xyz, 12 ) == 12; + case NifValue::tVector4: + return device->read( (char *) static_cast( val.val.data )->xyzw, 16 ) == 16; + case NifValue::tTriangle: + return device->read( (char *) static_cast( val.val.data )->v, 6 ) == 6; + case NifValue::tQuat: + return device->read( (char *) static_cast( val.val.data )->wxyz, 16 ) == 16; + case NifValue::tQuatXYZW: + { + Quat * q = static_cast( val.val.data ); + return device->read( (char *) &q->wxyz[1], 12 ) == 12 && device->read( (char *) q->wxyz, 4 ) == 4; + } + case NifValue::tMatrix: + return device->read( (char *) static_cast( val.val.data )->m, 36 ) == 36; + case NifValue::tMatrix4: + return device->read( (char *) static_cast( val.val.data )->m, 64 ) == 64; + case NifValue::tVector2: + return device->read( (char *) static_cast( val.val.data )->xy, 8 ) == 8; + case NifValue::tColor3: + return device->read( (char *) static_cast( val.val.data )->rgb, 12 ) == 12; + case NifValue::tColor4: + return device->read( (char *) static_cast( val.val.data )->rgba, 16 ) == 16; + case NifValue::tSizedString: + { + int len; + device->read( (char *) &len, 4 ); + if ( len > 4096 || len < 0 ) { *static_cast( val.val.data ) = ""; return false; } + QByteArray string = device->read( len ); + if ( string.size() != len ) return false; + //string.replace( "\r", "\\r" ); + //string.replace( "\n", "\\n" ); + *static_cast( val.val.data ) = QString( string ); + } return true; + case NifValue::tShortString: + { + unsigned char len; + device->read( (char *) &len, 1 ); + QByteArray string = device->read( len ); + if ( string.size() != len ) return false; + //string.replace( "\r", "\\r" ); + //string.replace( "\n", "\\n" ); + *static_cast( val.val.data ) = QString( string ); + } return true; + case NifValue::tText: + { + int len; + device->read( (char *) &len, 4 ); + if ( len > 4096 || len < 0 ) { *static_cast( val.val.data ) = ""; return false; } + QByteArray string = device->read( len ); + if ( string.size() != len ) return false; + *static_cast( val.val.data ) = QString( string ); + } return true; + case NifValue::tByteArray: + { + int len; + device->read( (char *) &len, 4 ); + if ( len < 0 ) return false; + *static_cast( val.val.data ) = device->read( len ); + return static_cast( val.val.data )->count() == len; + } + case NifValue::tStringPalette: + { + int len; + device->read( (char *) &len, 4 ); + if ( len > 0xffff || len < 0 ) return false; + *static_cast( val.val.data ) = device->read( len ); + device->read( (char *) &len, 4 ); + return true; + } + case NifValue::tByteMatrix: + { + int len1, len2; + device->read( (char *) &len1, 4 ); + device->read( (char *) &len2, 4 ); + if ( len1 < 0 || len2 < 0) return false; + int len = len1 * len2; + ByteMatrix tmp(len1, len2); + qint64 rlen = device->read( tmp.data(), len ); + tmp.swap( *static_cast( val.val.data ) ); + return (rlen == len); + } + case NifValue::tHeaderString: + { + QByteArray string; + int c = 0; + char chr = 0; + while ( c++ < 80 && device->getChar( &chr ) && chr != '\n' ) + string.append( chr ); + if ( c >= 80 ) return false; + *static_cast( val.val.data ) = QString( string ); + bool x = model->setHeaderString( QString( string ) ); + init(); + return x; + } + case NifValue::tLineString: + { + QByteArray string; + int c = 0; + char chr = 0; + while ( c++ < 255 && device->getChar( &chr ) && chr != '\n' ) + string.append( chr ); + if ( c >= 255 ) return false; + *static_cast( val.val.data ) = QString( string ); + return true; + } + case NifValue::tChar8String: + { + QByteArray string; + int c = 0; + char chr = 0; + while ( c++ < 8 && device->getChar( &chr ) ) + string.append( chr ); + if ( c > 9 ) return false; + *static_cast( val.val.data ) = QString( string ); + return true; + } + case NifValue::tFileVersion: + { + if ( device->read( (char *) &val.val.u32, 4 ) != 4 ) return false; + //bool x = model->setVersion( val.val.u32 ); + //init(); + return true; + } + case NifValue::tString: + { + if (stringAdjust) + { + val.changeType(NifValue::tStringIndex); + return device->read( (char *) &val.val.i32, 4 ) == 4; + } + else + { + val.changeType(NifValue::tSizedString); + + int len; + device->read( (char *) &len, 4 ); + if ( len > 4096 || len < 0 ) { *static_cast( val.val.data ) = ""; return false; } + QByteArray string = device->read( len ); + if ( string.size() != len ) return false; + //string.replace( "\r", "\\r" ); + //string.replace( "\n", "\\n" ); + *static_cast( val.val.data ) = QString( string ); + return true; + } + } + case NifValue::tFilePath: + { + if (stringAdjust) + { + val.changeType(NifValue::tStringIndex); + return device->read( (char *) &val.val.i32, 4 ) == 4; + } + else + { + val.changeType(NifValue::tSizedString); + + int len; + device->read( (char *) &len, 4 ); + if ( len > 4096 || len < 0 ) { *static_cast( val.val.data ) = ""; return false; } + QByteArray string = device->read( len ); + if ( string.size() != len ) return false; + *static_cast( val.val.data ) = QString( string ); + return true; + } + } + + case NifValue::tNone: + return true; + } + return false; +} + +void NifIStream::init() +{ + bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); + linkAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D ); + stringAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003 ); +} + +bool NifOStream::write( const NifValue & val ) +{ + switch ( val.type() ) + { + case NifValue::tBool: + if ( bool32bit ) + return device->write( (char *) &val.val.u32, 4 ) == 4; + else + return device->write( (char *) &val.val.u08, 1 ) == 1; + case NifValue::tByte: + return device->write( (char *) &val.val.u08, 1 ) == 1; + case NifValue::tWord: + case NifValue::tShort: + case NifValue::tFlags: + case NifValue::tBlockTypeIndex: + return device->write( (char *) &val.val.u16, 2 ) == 2; + case NifValue::tStringOffset: + case NifValue::tInt: + case NifValue::tUInt: + case NifValue::tFileVersion: + case NifValue::tStringIndex: + return device->write( (char *) &val.val.u32, 4 ) == 4; + case NifValue::tLink: + case NifValue::tUpLink: + if ( ! linkAdjust ) + { + return device->write( (char *) &val.val.i32, 4 ) == 4; + } + else + { + qint32 l = val.val.i32 + 1; + return device->write( (char *) &l, 4 ) == 4; + } + case NifValue::tFloat: + return device->write( (char *) &val.val.f32, 4 ) == 4; + case NifValue::tVector3: + return device->write( (char *) static_cast( val.val.data )->xyz, 12 ) == 12; + case NifValue::tVector4: + return device->write( (char *) static_cast( val.val.data )->xyzw, 16 ) == 16; + case NifValue::tTriangle: + return device->write( (char *) static_cast( val.val.data )->v, 6 ) == 6; + case NifValue::tQuat: + return device->write( (char *) static_cast( val.val.data )->wxyz, 16 ) == 16; + case NifValue::tQuatXYZW: + { + Quat * q = static_cast( val.val.data ); + return device->write( (char *) &q->wxyz[1], 12 ) == 12 && device->write( (char *) q->wxyz, 4 ) == 4; + } + case NifValue::tMatrix: + return device->write( (char *) static_cast( val.val.data )->m, 36 ) == 36; + case NifValue::tMatrix4: + return device->write( (char *) static_cast( val.val.data )->m, 64 ) == 64; + case NifValue::tVector2: + return device->write( (char *) static_cast( val.val.data )->xy, 8 ) == 8; + case NifValue::tColor3: + return device->write( (char *) static_cast( val.val.data )->rgb, 12 ) == 12; + case NifValue::tColor4: + return device->write( (char *) static_cast( val.val.data )->rgba, 16 ) == 16; + case NifValue::tSizedString: + { + QByteArray string = static_cast( val.val.data )->toAscii(); + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + int len = string.size(); + if ( device->write( (char *) &len, 4 ) != 4 ) + return false; + return device->write( (const char *) string, string.size() ) == string.size(); + } + case NifValue::tShortString: + { + QByteArray string = static_cast( val.val.data )->toAscii(); + string.replace( "\\r", "\r" ); + string.replace( "\\n", "\n" ); + if ( string.size() > 254 ) string.resize( 254 ); + unsigned char len = string.size() + 1; + if ( device->write( (char *) &len, 1 ) != 1 ) + return false; + return device->write( (const char *) string, len ) == len; + } + case NifValue::tText: + { + QByteArray string = static_cast( val.val.data )->toAscii(); + int len = string.size(); + if ( device->write( (char *) &len, 4 ) != 4 ) + return false; + return device->write( (const char *) string, string.size() ) == string.size(); + } + case NifValue::tHeaderString: + case NifValue::tLineString: + { + QByteArray string = static_cast( val.val.data )->toAscii(); + if ( device->write( (const char *) string, string.length() ) != string.length() ) + return false; + return ( device->write( "\n", 1 ) == 1 ); + } + case NifValue::tChar8String: + { + QByteArray string = static_cast( val.val.data )->toAscii(); + quint32 n = std::min(8, string.length()); + if ( device->write( (const char *) string, n ) != n ) + return false; + for ( quint32 i = n; i < 8; ++i) + if ( device->write( "\0", 1 ) != 1 ) return false; + return true; + } + case NifValue::tByteArray: + { + QByteArray * array = static_cast( val.val.data ); + int len = array->count(); + if ( device->write( (char *) &len, 4 ) != 4 ) + return false; + return device->write( *array ) == len; + } + case NifValue::tStringPalette: + { + QByteArray * array = static_cast( val.val.data ); + int len = array->count(); + if ( device->write( (char *) &len, 4 ) != 4 ) + return false; + if ( device->write( *array ) != len ) + return false; + return device->write( (char *) &len, 4 ) == 4; + } + case NifValue::tByteMatrix: + { + ByteMatrix * array = static_cast( val.val.data ); + int len = array->count(0); + if ( device->write( (char *) &len, 4 ) != 4 ) + return false; + len = array->count(1); + if ( device->write( (char *) &len, 4 ) != 4 ) + return false; + len = array->count(); + return device->write(array->data(), len) == len; + } + case NifValue::tString: + case NifValue::tFilePath: + { + if (stringAdjust) + { + return device->write( (char *) &val.val.u32, 4 ) == 4; + } + else + { + QByteArray string; + if( val.val.data != 0 ) + { + string = static_cast( val.val.data )->toAscii(); + } + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + int len = string.size(); + if ( device->write( (char *) &len, 4 ) != 4 ) + return false; + return device->write( (const char *) string, string.size() ) == string.size(); + } + } + case NifValue::tNone: + return true; + } + return false; +} + +void NifSStream::init() +{ + bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); + stringAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003 ); +} + +int NifSStream::size( const NifValue & val ) +{ + switch ( val.type() ) + { + case NifValue::tBool: + if ( bool32bit ) + return 4; + else + return 1; + case NifValue::tByte: + return 1; + case NifValue::tWord: + case NifValue::tShort: + case NifValue::tFlags: + case NifValue::tBlockTypeIndex: + return 2; + case NifValue::tStringOffset: + case NifValue::tInt: + case NifValue::tUInt: + case NifValue::tStringIndex: + case NifValue::tFileVersion: + case NifValue::tLink: + case NifValue::tUpLink: + case NifValue::tFloat: + return 4; + case NifValue::tVector3: + return 12; + case NifValue::tVector4: + return 16; + case NifValue::tTriangle: + return 6; + case NifValue::tQuat: + case NifValue::tQuatXYZW: + return 16; + case NifValue::tMatrix: + return 36; + case NifValue::tMatrix4: + return 64; + case NifValue::tVector2: + return 8; + case NifValue::tColor3: + return 12; + case NifValue::tColor4: + return 16; + case NifValue::tSizedString: + { + QByteArray string = static_cast( val.val.data )->toAscii(); + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + return 4 + string.size(); + } + case NifValue::tShortString: + { + QByteArray string = static_cast( val.val.data )->toAscii(); + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + if ( string.size() > 254 ) string.resize( 254 ); + return 1 + string.size() + 1; + } + case NifValue::tText: + { + QByteArray string = static_cast( val.val.data )->toAscii(); + return 4 + string.size(); + } + case NifValue::tHeaderString: + case NifValue::tLineString: + { + QByteArray string = static_cast( val.val.data )->toAscii(); + return string.length() + 1; + } + case NifValue::tChar8String: + { + return 8; + } + case NifValue::tByteArray: + { + QByteArray * array = static_cast( val.val.data ); + return 4 + array->count(); + } + case NifValue::tStringPalette: + { + QByteArray * array = static_cast( val.val.data ); + return 4 + array->count() + 4; + } + case NifValue::tByteMatrix: + { + ByteMatrix * array = static_cast( val.val.data ); + return 4 + 4 + array->count(); + } + case NifValue::tString: + case NifValue::tFilePath: + { + if (stringAdjust) + { + return 4; + } + else + { + QByteArray string = static_cast( val.val.data )->toAscii(); + //string.replace( "\\r", "\r" ); + //string.replace( "\\n", "\n" ); + return 4 + string.size(); + } + } + case NifValue::tNone: + return 0; + } + return 0; +} + diff --git a/nifvalue.h b/nifvalue.h index 0f69775f3..e1205ccbf 100644 --- a/nifvalue.h +++ b/nifvalue.h @@ -1,478 +1,478 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef NIFVALUE_H -#define NIFVALUE_H - - -#include -#include -#include - - -#include "niftypes.h" - - -//! A generic class used for storing a value of any type. -/*! - * The NifValue::Type enum lists all supported types. - */ -class NifValue -{ -public: - //! List of all types implemented internally by NifSkope. - /*! - * To add a new type, add a new enumerant to Type, and update NifValue::initialize() - * to reflect the name of the type as used in the xml. - */ - enum Type - { - // all count types should come between tBool and tUInt - tBool = 0, - tByte = 1, - tWord = 2, - tFlags = 3, - tStringOffset = 4, - tStringIndex = 5, - tBlockTypeIndex = 6, - tInt = 7, - tShort = 8, - tUInt = 9, - // - tLink = 10, - tUpLink = 11, - tFloat = 12, - // all string types should come between tSizedString and tChar8String - tSizedString = 13, - tText = 15, - tShortString = 16, - tHeaderString = 18, - tLineString = 19, - tChar8String = 20, - // - tColor3 = 21, - tColor4 = 22, - tVector3 = 23, - tQuat = 24, - tQuatXYZW = 25, - tMatrix = 26, - tMatrix4 = 27, - tVector2 = 28, - tVector4 = 29, - tTriangle = 30, - tFileVersion = 31, - tByteArray = 32, - tStringPalette = 33, - tString = 34, // not a regular string: an integer for nif versions 20.1.0.3 and up - tFilePath = 35, // not a string: requires special handling for slash/backslash etc. - tByteMatrix = 36, - - tNone = 0xff - }; - - // *** apparently not used *** - //template static Type typeId(); - - //! Initialize the class data - /*! - * Sets typeMap. Clears typeTxt and enumMap (which will be filled later during xml parsing). - */ - static void initialize(); - - //! Get the Type corresponding to a string typId, as stored in the typeMap. - /*! - * \param typId The type string (as used in the xml). - * \return The Type corresponding to the string, or tNone if the type is not found. - */ - static Type type( const QString & typId ); - //! Get a html formatted description of the type. - static QString typeDescription( const QString & typId ); - //! Update the typeTxt map with the type description. Newline characters are replaced by html line break tags. - static void setTypeDescription( const QString & typId, const QString & txt ); - //! Register an alias for a type. - /*! - * This is done by updating the typeMap and maps the alias string to the type - * corresponding to the internal string. - */ - static bool registerAlias( const QString & alias, const QString & internal ); - - //! Register an option for an enum type. - /*! - * \param eid The name of the enum type. - * \param oid The name of the option of that type to add. - * \param oval The value of that option. - * \param otxt The documentation string for the option. - * \return true if successful, false if the option value was already registered. - */ - static bool registerEnumOption( const QString & eid, const QString & oid, quint32 oval, const QString & otxt ); - //! Get the name of an option from its value. - static QString enumOptionName( const QString & eid, quint32 oval ); - //! Get the documentation string of an option from its value. - static QString enumOptionText( const QString & eid, quint32 oval ); - //! Get the an option from an option string. - /*! - * \param eid The name of the enum type. - * \param oid The name of the option. - * \param ok Is set to true if succesfull, is set to false if the option string was not found. - */ - static quint32 enumOptionValue( const QString & eid, const QString & oid, bool * ok = 0 ); - //! Get list of all options that have been registered for the given enum type. - static QStringList enumOptions( const QString & eid ); - - //! Initialize the value to nothing, type tNone. - NifValue() { typ = tNone; } - //! Initialize the value to a default value of the specified type. - NifValue( Type t ); - //! Copy constructor. - NifValue( const NifValue & other ); - - ~NifValue(); - - //! Clear the data, setting its type to tNone. - void clear(); - //! Change the type of data stored. - /*! - * Clears existing data, changes its type, and then reinitializes the data to its default. - * Note that if Type is the same as originally, then the data is not cleared. - */ - void changeType( Type ); - - //! Assignment. Performs a deep copy of the data. - void operator=( const NifValue & other ); - - //! Get the type. - Type type() const { return typ; } - - //! Check if the type is not tNone. - static bool isValid( Type t ) { return t != tNone; } - //! Check if a type is of a link type (Ref or Ptr in xml). - static bool isLink( Type t ) { return t == tLink || t == tUpLink; } - - //! Check if the type of the data is not tNone. - bool isValid() const { return typ != tNone; } - //! Check if the type of the data is a color type (Color3 or Color4 in xml). - bool isColor() const { return typ == tColor3 || typ == tColor4; } - //! Check if the type of the data is a count. - bool isCount() const { return (typ >= tBool && typ <= tUInt); } - //! Check if the type of the data is a flag type (Flags in xml). - bool isFlags() const { return typ == tFlags; } - //! Check if the type of the data is a float type (Float in xml). - bool isFloat() const { return typ == tFloat; } - //! Check if the type of the data is of a link type (Ref or Ptr in xml). - bool isLink() const { return typ == tLink || typ == tUpLink; } - bool isMatrix() const { return typ == tMatrix; } - bool isMatrix4() const { return typ == tMatrix4; } - bool isQuat() const { return typ == tQuat || typ == tQuatXYZW; } - bool isString() const { return (typ >= tSizedString && typ <= tChar8String) || typ == tString; } - bool isVector4() const { return typ == tVector4; } - bool isVector3() const { return typ == tVector3; } - bool isVector2() const { return typ == tVector2; } - bool isTriangle() const { return typ == tTriangle; } - bool isByteArray() const { return typ == tByteArray || typ == tStringPalette ; } - bool isFileVersion() const { return typ == tFileVersion; } - bool isByteMatrix() const { return typ == tByteMatrix; } - - QColor toColor() const; - quint32 toCount() const; - float toFloat() const; - qint32 toLink() const; - quint32 toFileVersion() const; - - QString toString() const; - QVariant toVariant() const; - - bool setCount( quint32 ); - bool setFloat( float ); - bool setLink( int ); - bool setFileVersion( quint32 ); - - bool fromString( const QString & ); - bool fromVariant( const QVariant & ); - - //! Check whether the data can be converted to something of type T. - template bool ask( T * t = 0 ) const; - //! Get the data in the form of something of type T. - template T get() const; - //! Set the data from an instance of type T. Return true if successful. - template bool set( const T & x ); - -protected: - //! The type of this data. - Type typ; - - //! The structure containing the data. - union Value - { - quint8 u08; - quint16 u16; - quint32 u32; - qint32 i32; - float f32; - void * data; - }; - - //! The data value. - Value val; - - //! Get the data as an object of type T. - /*! - * If the type t is not equal to the actual type of the data, then return T(). Serves - * as a helper function for get, intended for internal use only. - */ - template T getType( Type t ) const; - //! Set the data from an object of type T. - /*! - * If the type t is not equal to the actual type of the data, then return false, else - * return true. Helper function for set, intended for internal use only. - */ - template bool setType( Type t, T v ); - - //! A dictionary yielding the Type from a type string. - static QHash typeMap; - //! A dictionary yielding the enumaration dictionary from a string. - /*! - * Enums are stored as mappings from quint32 to pairs of strings, where - * the first string in the pair is the enumerant string, and the second - * is the enumerant documentation string. For example, - * enumMap["AlphaFormat"][1] = QPair<"ALPHA_BINARY", "Texture is either fully transparent or fully opaque."> - */ - static QHash > > enumMap; - //! A dictionary yielding the documentation string of a type string. - static QHash typeTxt; - - friend class NifIStream; - friend class NifOStream; - friend class NifSStream; -}; - -inline quint32 NifValue::toCount() const { -#ifdef WIN32 - if ( isCount() ) - return val.u32; - else if( isFloat() ) - return *(quint32*)&val.f32; -#else - if ( isCount() || isFloat() ) - return val.u32; // GCC only allows type punning via union (http://gcc.gnu.org/onlinedocs/gcc-4.2.1/gcc/Optimize-Options.html#index-fstrict_002daliasing-550) -#endif - return 0; -} -inline float NifValue::toFloat() const { if ( isFloat() ) return val.f32; else return 0.0; } -inline qint32 NifValue::toLink() const { if ( isLink() ) return val.i32; else return -1; } -inline quint32 NifValue::toFileVersion() const { if ( isFileVersion() ) return val.u32; else return 0; } - -inline bool NifValue::setCount( quint32 c ) { if ( isCount() ) { val.u32 = c; return true; } else return false; } -inline bool NifValue::setFloat( float f ) { if ( isFloat() ) { val.f32 = f; return true; } else return false; } -inline bool NifValue::setLink( int l ) { if ( isLink() ) { val.i32 = l; return true; } else return false; } -inline bool NifValue::setFileVersion( quint32 v ) { if ( isFileVersion() ) { val.u32 = v; return true; } else return false; } - -template inline T NifValue::getType( Type t ) const -{ - if ( typ == t ) - return *static_cast( val.data ); // WARNING: this throws an exception if the type of v is not the original type by which val.data was initialized; the programmer must make sure that T matches t. - else - return T(); -} - -template inline bool NifValue::setType( Type t, T v ) -{ - if ( typ == t ) - { - *static_cast( val.data ) = v; // WARNING: this throws an exception if the type of v is not the original type by which val.data was initialized; the programmer must make sure that T matches t. - return true; - } - return false; -} - -template <> inline bool NifValue::get() const { return toCount(); } -template <> inline qint32 NifValue::get() const { return toCount(); } -template <> inline quint32 NifValue::get() const { return toCount(); } -template <> inline qint16 NifValue::get() const { return toCount(); } -template <> inline quint16 NifValue::get() const { return toCount(); } -template <> inline quint8 NifValue::get() const { return toCount(); } -template <> inline float NifValue::get() const { return toFloat(); } -template <> inline QColor NifValue::get() const { return toColor(); } -template <> inline QVariant NifValue::get() const { return toVariant(); } - -template <> inline Matrix NifValue::get() const { return getType( tMatrix ); } -template <> inline Matrix4 NifValue::get() const { return getType( tMatrix4 ); } -template <> inline Vector4 NifValue::get() const { return getType( tVector4 ); } -template <> inline Vector3 NifValue::get() const { return getType( tVector3 ); } -template <> inline Vector2 NifValue::get() const { return getType( tVector2 ); } -template <> inline Color3 NifValue::get() const { return getType( tColor3 ); } -template <> inline Color4 NifValue::get() const { return getType( tColor4 ); } -template <> inline Triangle NifValue::get() const { return getType( tTriangle ); } -template <> inline QString NifValue::get() const -{ - if ( isString() ) - return *static_cast( val.data ); - else - return QString(); -} -template <> inline QByteArray NifValue::get() const -{ - if ( isByteArray() ) - return *static_cast( val.data ); - else - return QByteArray(); -} -template <> inline Quat NifValue::get() const -{ - if ( isQuat() ) - return *static_cast( val.data ); - else - return Quat(); -} - -template <> inline bool NifValue::set( const bool & b ) { return setCount( b ); } -template <> inline bool NifValue::set( const int & i ) { return setCount( i ); } -template <> inline bool NifValue::set( const quint32 & i ) { return setCount( i ); } -template <> inline bool NifValue::set( const qint16 & i ) { return setCount( i ); } -template <> inline bool NifValue::set( const quint16 & i ) { return setCount( i ); } -template <> inline bool NifValue::set( const quint8 & i ) { return setCount( i ); } -template <> inline bool NifValue::set( const float & f ) { return setFloat( f ); } -template <> inline bool NifValue::set( const Matrix & x ) { return setType( tMatrix, x ); } -template <> inline bool NifValue::set( const Matrix4 & x ) { return setType( tMatrix4, x ); } -template <> inline bool NifValue::set( const Vector4 & x ) { return setType( tVector4, x ); } -template <> inline bool NifValue::set( const Vector3 & x ) { return setType( tVector3, x ); } -template <> inline bool NifValue::set( const Vector2 & x ) { return setType( tVector2, x ); } -template <> inline bool NifValue::set( const Color3 & x ) { return setType( tColor3, x ); } -template <> inline bool NifValue::set( const Color4 & x ) { return setType( tColor4, x ); } -template <> inline bool NifValue::set( const Triangle & x ) { return setType( tTriangle, x ); } -template <> inline bool NifValue::set( const QString & x ) -{ - if ( isString() ) - { - if ( val.data == NULL ) - { - val.data = new QString; - } - - *static_cast( val.data ) = x; - return true; - } - return false; -} -template <> inline bool NifValue::set( const QByteArray & x ) -{ - if ( isByteArray() ) - { - *static_cast( val.data ) = x; - return true; - } - return false; -} -template <> inline bool NifValue::set( const Quat & x ) -{ - if ( isQuat() ) - { - *static_cast( val.data ) = x; - return true; - } - return false; -} - -template <> inline bool NifValue::ask( bool * ) const { return isCount(); } -template <> inline bool NifValue::ask( int * ) const { return isCount(); } -template <> inline bool NifValue::ask( short * ) const { return isCount(); } -template <> inline bool NifValue::ask( float * ) const { return isFloat(); } -template <> inline bool NifValue::ask( Matrix * ) const { return type() == tMatrix; } -template <> inline bool NifValue::ask( Matrix4 * ) const { return type() == tMatrix4; } -template <> inline bool NifValue::ask( Quat * ) const { return isQuat(); } -template <> inline bool NifValue::ask( Vector4 * ) const { return type() == tVector4; } -template <> inline bool NifValue::ask( Vector3 * ) const { return type() == tVector3; } -template <> inline bool NifValue::ask( Vector2 * ) const { return type() == tVector2; } -template <> inline bool NifValue::ask( Color3 * ) const { return type() == tColor3; } -template <> inline bool NifValue::ask( Color4 * ) const { return type() == tColor4; } -template <> inline bool NifValue::ask( Triangle * ) const { return type() == tTriangle; } -template <> inline bool NifValue::ask( QString * ) const { return isString(); } -template <> inline bool NifValue::ask( QByteArray * ) const { return isByteArray(); } - -class BaseModel; -class NifItem; - -class NifIStream -{ -public: - NifIStream( BaseModel * m, QIODevice * d ) : model( m ), device( d ) { init(); } - - bool read( NifValue & ); - -private: - BaseModel * model; - QIODevice * device; - - void init(); - - bool bool32bit; - bool linkAdjust; - bool stringAdjust; -}; - -class NifOStream -{ -public: - NifOStream( const BaseModel * n, QIODevice * d ) : model( n ), device( d ) { init(); } - - bool write( const NifValue & ); - -private: - const BaseModel * model; - QIODevice * device; - - void init(); - - bool bool32bit; - bool linkAdjust; - bool stringAdjust; -}; - -class NifSStream -{ -public: - NifSStream( const BaseModel * n ) : model( n ) { init(); } - - int size( const NifValue & ); - -private: - const BaseModel * model; - - void init(); - - bool bool32bit; - bool stringAdjust; -}; - - -Q_DECLARE_METATYPE( NifValue ) - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef NIFVALUE_H +#define NIFVALUE_H + + +#include +#include +#include + + +#include "niftypes.h" + + +//! A generic class used for storing a value of any type. +/*! + * The NifValue::Type enum lists all supported types. + */ +class NifValue +{ +public: + //! List of all types implemented internally by NifSkope. + /*! + * To add a new type, add a new enumerant to Type, and update NifValue::initialize() + * to reflect the name of the type as used in the xml. + */ + enum Type + { + // all count types should come between tBool and tUInt + tBool = 0, + tByte = 1, + tWord = 2, + tFlags = 3, + tStringOffset = 4, + tStringIndex = 5, + tBlockTypeIndex = 6, + tInt = 7, + tShort = 8, + tUInt = 9, + // + tLink = 10, + tUpLink = 11, + tFloat = 12, + // all string types should come between tSizedString and tChar8String + tSizedString = 13, + tText = 15, + tShortString = 16, + tHeaderString = 18, + tLineString = 19, + tChar8String = 20, + // + tColor3 = 21, + tColor4 = 22, + tVector3 = 23, + tQuat = 24, + tQuatXYZW = 25, + tMatrix = 26, + tMatrix4 = 27, + tVector2 = 28, + tVector4 = 29, + tTriangle = 30, + tFileVersion = 31, + tByteArray = 32, + tStringPalette = 33, + tString = 34, // not a regular string: an integer for nif versions 20.1.0.3 and up + tFilePath = 35, // not a string: requires special handling for slash/backslash etc. + tByteMatrix = 36, + + tNone = 0xff + }; + + // *** apparently not used *** + //template static Type typeId(); + + //! Initialize the class data + /*! + * Sets typeMap. Clears typeTxt and enumMap (which will be filled later during xml parsing). + */ + static void initialize(); + + //! Get the Type corresponding to a string typId, as stored in the typeMap. + /*! + * \param typId The type string (as used in the xml). + * \return The Type corresponding to the string, or tNone if the type is not found. + */ + static Type type( const QString & typId ); + //! Get a html formatted description of the type. + static QString typeDescription( const QString & typId ); + //! Update the typeTxt map with the type description. Newline characters are replaced by html line break tags. + static void setTypeDescription( const QString & typId, const QString & txt ); + //! Register an alias for a type. + /*! + * This is done by updating the typeMap and maps the alias string to the type + * corresponding to the internal string. + */ + static bool registerAlias( const QString & alias, const QString & internal ); + + //! Register an option for an enum type. + /*! + * \param eid The name of the enum type. + * \param oid The name of the option of that type to add. + * \param oval The value of that option. + * \param otxt The documentation string for the option. + * \return true if successful, false if the option value was already registered. + */ + static bool registerEnumOption( const QString & eid, const QString & oid, quint32 oval, const QString & otxt ); + //! Get the name of an option from its value. + static QString enumOptionName( const QString & eid, quint32 oval ); + //! Get the documentation string of an option from its value. + static QString enumOptionText( const QString & eid, quint32 oval ); + //! Get the an option from an option string. + /*! + * \param eid The name of the enum type. + * \param oid The name of the option. + * \param ok Is set to true if succesfull, is set to false if the option string was not found. + */ + static quint32 enumOptionValue( const QString & eid, const QString & oid, bool * ok = 0 ); + //! Get list of all options that have been registered for the given enum type. + static QStringList enumOptions( const QString & eid ); + + //! Initialize the value to nothing, type tNone. + NifValue() { typ = tNone; } + //! Initialize the value to a default value of the specified type. + NifValue( Type t ); + //! Copy constructor. + NifValue( const NifValue & other ); + + ~NifValue(); + + //! Clear the data, setting its type to tNone. + void clear(); + //! Change the type of data stored. + /*! + * Clears existing data, changes its type, and then reinitializes the data to its default. + * Note that if Type is the same as originally, then the data is not cleared. + */ + void changeType( Type ); + + //! Assignment. Performs a deep copy of the data. + void operator=( const NifValue & other ); + + //! Get the type. + Type type() const { return typ; } + + //! Check if the type is not tNone. + static bool isValid( Type t ) { return t != tNone; } + //! Check if a type is of a link type (Ref or Ptr in xml). + static bool isLink( Type t ) { return t == tLink || t == tUpLink; } + + //! Check if the type of the data is not tNone. + bool isValid() const { return typ != tNone; } + //! Check if the type of the data is a color type (Color3 or Color4 in xml). + bool isColor() const { return typ == tColor3 || typ == tColor4; } + //! Check if the type of the data is a count. + bool isCount() const { return (typ >= tBool && typ <= tUInt); } + //! Check if the type of the data is a flag type (Flags in xml). + bool isFlags() const { return typ == tFlags; } + //! Check if the type of the data is a float type (Float in xml). + bool isFloat() const { return typ == tFloat; } + //! Check if the type of the data is of a link type (Ref or Ptr in xml). + bool isLink() const { return typ == tLink || typ == tUpLink; } + bool isMatrix() const { return typ == tMatrix; } + bool isMatrix4() const { return typ == tMatrix4; } + bool isQuat() const { return typ == tQuat || typ == tQuatXYZW; } + bool isString() const { return (typ >= tSizedString && typ <= tChar8String) || typ == tString; } + bool isVector4() const { return typ == tVector4; } + bool isVector3() const { return typ == tVector3; } + bool isVector2() const { return typ == tVector2; } + bool isTriangle() const { return typ == tTriangle; } + bool isByteArray() const { return typ == tByteArray || typ == tStringPalette ; } + bool isFileVersion() const { return typ == tFileVersion; } + bool isByteMatrix() const { return typ == tByteMatrix; } + + QColor toColor() const; + quint32 toCount() const; + float toFloat() const; + qint32 toLink() const; + quint32 toFileVersion() const; + + QString toString() const; + QVariant toVariant() const; + + bool setCount( quint32 ); + bool setFloat( float ); + bool setLink( int ); + bool setFileVersion( quint32 ); + + bool fromString( const QString & ); + bool fromVariant( const QVariant & ); + + //! Check whether the data can be converted to something of type T. + template bool ask( T * t = 0 ) const; + //! Get the data in the form of something of type T. + template T get() const; + //! Set the data from an instance of type T. Return true if successful. + template bool set( const T & x ); + +protected: + //! The type of this data. + Type typ; + + //! The structure containing the data. + union Value + { + quint8 u08; + quint16 u16; + quint32 u32; + qint32 i32; + float f32; + void * data; + }; + + //! The data value. + Value val; + + //! Get the data as an object of type T. + /*! + * If the type t is not equal to the actual type of the data, then return T(). Serves + * as a helper function for get, intended for internal use only. + */ + template T getType( Type t ) const; + //! Set the data from an object of type T. + /*! + * If the type t is not equal to the actual type of the data, then return false, else + * return true. Helper function for set, intended for internal use only. + */ + template bool setType( Type t, T v ); + + //! A dictionary yielding the Type from a type string. + static QHash typeMap; + //! A dictionary yielding the enumaration dictionary from a string. + /*! + * Enums are stored as mappings from quint32 to pairs of strings, where + * the first string in the pair is the enumerant string, and the second + * is the enumerant documentation string. For example, + * enumMap["AlphaFormat"][1] = QPair<"ALPHA_BINARY", "Texture is either fully transparent or fully opaque."> + */ + static QHash > > enumMap; + //! A dictionary yielding the documentation string of a type string. + static QHash typeTxt; + + friend class NifIStream; + friend class NifOStream; + friend class NifSStream; +}; + +inline quint32 NifValue::toCount() const { +#ifdef WIN32 + if ( isCount() ) + return val.u32; + else if( isFloat() ) + return *(quint32*)&val.f32; +#else + if ( isCount() || isFloat() ) + return val.u32; // GCC only allows type punning via union (http://gcc.gnu.org/onlinedocs/gcc-4.2.1/gcc/Optimize-Options.html#index-fstrict_002daliasing-550) +#endif + return 0; +} +inline float NifValue::toFloat() const { if ( isFloat() ) return val.f32; else return 0.0; } +inline qint32 NifValue::toLink() const { if ( isLink() ) return val.i32; else return -1; } +inline quint32 NifValue::toFileVersion() const { if ( isFileVersion() ) return val.u32; else return 0; } + +inline bool NifValue::setCount( quint32 c ) { if ( isCount() ) { val.u32 = c; return true; } else return false; } +inline bool NifValue::setFloat( float f ) { if ( isFloat() ) { val.f32 = f; return true; } else return false; } +inline bool NifValue::setLink( int l ) { if ( isLink() ) { val.i32 = l; return true; } else return false; } +inline bool NifValue::setFileVersion( quint32 v ) { if ( isFileVersion() ) { val.u32 = v; return true; } else return false; } + +template inline T NifValue::getType( Type t ) const +{ + if ( typ == t ) + return *static_cast( val.data ); // WARNING: this throws an exception if the type of v is not the original type by which val.data was initialized; the programmer must make sure that T matches t. + else + return T(); +} + +template inline bool NifValue::setType( Type t, T v ) +{ + if ( typ == t ) + { + *static_cast( val.data ) = v; // WARNING: this throws an exception if the type of v is not the original type by which val.data was initialized; the programmer must make sure that T matches t. + return true; + } + return false; +} + +template <> inline bool NifValue::get() const { return toCount(); } +template <> inline qint32 NifValue::get() const { return toCount(); } +template <> inline quint32 NifValue::get() const { return toCount(); } +template <> inline qint16 NifValue::get() const { return toCount(); } +template <> inline quint16 NifValue::get() const { return toCount(); } +template <> inline quint8 NifValue::get() const { return toCount(); } +template <> inline float NifValue::get() const { return toFloat(); } +template <> inline QColor NifValue::get() const { return toColor(); } +template <> inline QVariant NifValue::get() const { return toVariant(); } + +template <> inline Matrix NifValue::get() const { return getType( tMatrix ); } +template <> inline Matrix4 NifValue::get() const { return getType( tMatrix4 ); } +template <> inline Vector4 NifValue::get() const { return getType( tVector4 ); } +template <> inline Vector3 NifValue::get() const { return getType( tVector3 ); } +template <> inline Vector2 NifValue::get() const { return getType( tVector2 ); } +template <> inline Color3 NifValue::get() const { return getType( tColor3 ); } +template <> inline Color4 NifValue::get() const { return getType( tColor4 ); } +template <> inline Triangle NifValue::get() const { return getType( tTriangle ); } +template <> inline QString NifValue::get() const +{ + if ( isString() ) + return *static_cast( val.data ); + else + return QString(); +} +template <> inline QByteArray NifValue::get() const +{ + if ( isByteArray() ) + return *static_cast( val.data ); + else + return QByteArray(); +} +template <> inline Quat NifValue::get() const +{ + if ( isQuat() ) + return *static_cast( val.data ); + else + return Quat(); +} + +template <> inline bool NifValue::set( const bool & b ) { return setCount( b ); } +template <> inline bool NifValue::set( const int & i ) { return setCount( i ); } +template <> inline bool NifValue::set( const quint32 & i ) { return setCount( i ); } +template <> inline bool NifValue::set( const qint16 & i ) { return setCount( i ); } +template <> inline bool NifValue::set( const quint16 & i ) { return setCount( i ); } +template <> inline bool NifValue::set( const quint8 & i ) { return setCount( i ); } +template <> inline bool NifValue::set( const float & f ) { return setFloat( f ); } +template <> inline bool NifValue::set( const Matrix & x ) { return setType( tMatrix, x ); } +template <> inline bool NifValue::set( const Matrix4 & x ) { return setType( tMatrix4, x ); } +template <> inline bool NifValue::set( const Vector4 & x ) { return setType( tVector4, x ); } +template <> inline bool NifValue::set( const Vector3 & x ) { return setType( tVector3, x ); } +template <> inline bool NifValue::set( const Vector2 & x ) { return setType( tVector2, x ); } +template <> inline bool NifValue::set( const Color3 & x ) { return setType( tColor3, x ); } +template <> inline bool NifValue::set( const Color4 & x ) { return setType( tColor4, x ); } +template <> inline bool NifValue::set( const Triangle & x ) { return setType( tTriangle, x ); } +template <> inline bool NifValue::set( const QString & x ) +{ + if ( isString() ) + { + if ( val.data == NULL ) + { + val.data = new QString; + } + + *static_cast( val.data ) = x; + return true; + } + return false; +} +template <> inline bool NifValue::set( const QByteArray & x ) +{ + if ( isByteArray() ) + { + *static_cast( val.data ) = x; + return true; + } + return false; +} +template <> inline bool NifValue::set( const Quat & x ) +{ + if ( isQuat() ) + { + *static_cast( val.data ) = x; + return true; + } + return false; +} + +template <> inline bool NifValue::ask( bool * ) const { return isCount(); } +template <> inline bool NifValue::ask( int * ) const { return isCount(); } +template <> inline bool NifValue::ask( short * ) const { return isCount(); } +template <> inline bool NifValue::ask( float * ) const { return isFloat(); } +template <> inline bool NifValue::ask( Matrix * ) const { return type() == tMatrix; } +template <> inline bool NifValue::ask( Matrix4 * ) const { return type() == tMatrix4; } +template <> inline bool NifValue::ask( Quat * ) const { return isQuat(); } +template <> inline bool NifValue::ask( Vector4 * ) const { return type() == tVector4; } +template <> inline bool NifValue::ask( Vector3 * ) const { return type() == tVector3; } +template <> inline bool NifValue::ask( Vector2 * ) const { return type() == tVector2; } +template <> inline bool NifValue::ask( Color3 * ) const { return type() == tColor3; } +template <> inline bool NifValue::ask( Color4 * ) const { return type() == tColor4; } +template <> inline bool NifValue::ask( Triangle * ) const { return type() == tTriangle; } +template <> inline bool NifValue::ask( QString * ) const { return isString(); } +template <> inline bool NifValue::ask( QByteArray * ) const { return isByteArray(); } + +class BaseModel; +class NifItem; + +class NifIStream +{ +public: + NifIStream( BaseModel * m, QIODevice * d ) : model( m ), device( d ) { init(); } + + bool read( NifValue & ); + +private: + BaseModel * model; + QIODevice * device; + + void init(); + + bool bool32bit; + bool linkAdjust; + bool stringAdjust; +}; + +class NifOStream +{ +public: + NifOStream( const BaseModel * n, QIODevice * d ) : model( n ), device( d ) { init(); } + + bool write( const NifValue & ); + +private: + const BaseModel * model; + QIODevice * device; + + void init(); + + bool bool32bit; + bool linkAdjust; + bool stringAdjust; +}; + +class NifSStream +{ +public: + NifSStream( const BaseModel * n ) : model( n ) { init(); } + + int size( const NifValue & ); + +private: + const BaseModel * model; + + void init(); + + bool bool32bit; + bool stringAdjust; +}; + + +Q_DECLARE_METATYPE( NifValue ) + +#endif diff --git a/nifxml.cpp b/nifxml.cpp index f1fe906d2..ff1e45845 100644 --- a/nifxml.cpp +++ b/nifxml.cpp @@ -1,476 +1,476 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "nifmodel.h" -#include "niftypes.h" - -#include -#include -#include - -#define err( X ) { errorStr = X; return false; } - -QReadWriteLock NifModel::XMLlock; - -QList NifModel::supportedVersions; - -QHash NifModel::compounds; -QHash NifModel::blocks; - -class NifXmlHandler : public QXmlDefaultHandler -{ -public: - enum Tag - { - tagNone = 0, - tagFile, - tagVersion, - tagCompound, - tagBlock, - tagAdd, - tagBasic, - tagEnum, - tagOption - }; - - NifXmlHandler() - { - depth = 0; - tags.insert( "niftoolsxml", tagFile ); - tags.insert( "version", tagVersion ); - tags.insert( "compound", tagCompound ); - tags.insert( "niobject", tagBlock ); - tags.insert( "add", tagAdd ); - tags.insert( "basic", tagBasic ); - tags.insert( "enum", tagEnum ); - tags.insert( "option", tagOption ); - blk = 0; - } - - int depth; - Tag stack[10]; - QHash tags; - QString errorStr; - - QString typId; - QString typTxt; - - QString optId; - QString optVal; - QString optTxt; - - NifBlock * blk; - NifData data; - - Tag current() const - { - return stack[depth-1]; - } - void push( Tag x ) - { - stack[depth++] = x; - } - Tag pop() - { - return stack[--depth]; - } - - bool startElement( const QString &, const QString &, const QString & tagid, const QXmlAttributes & list ) - { - if ( depth >= 8 ) err( "error maximum nesting level exceeded" ); - - Tag x = tags.value( tagid ); - if ( x == tagNone ) err( "error unknown element '" + tagid + "'" ); - - if ( depth == 0 ) - { - if ( x != tagFile ) err( "this is not a niftoolsxml file" ); - push( x ); - return true; - } - - switch ( current() ) - { - case tagFile: - push( x ); - switch ( x ) - { - case tagCompound: - case tagBlock: - { - if ( ! list.value("nifskopetype").isEmpty() ) - { - QString alias = list.value( "name" ); - QString type = list.value( "nifskopetype" ); - if ( alias != type ) - if ( ! NifValue::registerAlias( alias, type ) ) - err( "failed to register alias " + alias + " for type " + type ); - typId = alias; - typTxt = QString(); - } - else - { - if ( x == tagCompound && NifValue::isValid( NifValue::type( list.value( "name" ) ) ) ) - err( "compound " + list.value( "name" ) + " is already registered as internal type" ); - - QString id = list.value( "name" ); - if ( id.isEmpty() ) - err( "compound and niblocks must have a name" ); - - if ( NifModel::compounds.contains( id ) || NifModel::blocks.contains( id ) ) - err( "multiple declarations of " + id ); - - if ( ! blk ) blk = new NifBlock; - blk->id = list.value( "name" ); - blk->abstract = ( list.value( "abstract" ) == "1" ); - - if ( x == tagBlock ) - { - blk->ancestor = list.value( "inherit" ); - if ( ! blk->ancestor.isEmpty() ) - { - if ( ! NifModel::blocks.contains( blk->ancestor ) ) - err( "forward declaration of block id " + blk->ancestor ); - } - } - }; - } break; - case tagBasic: - { - QString alias = list.value( "name" ); - QString type = list.value( "nifskopetype" ); - if ( alias.isEmpty() || type.isEmpty() ) - err( "basic definition must have a name and a nifskopetype" ); - if ( alias != type ) - if ( ! NifValue::registerAlias( alias, type ) ) - err( "failed to register alias " + alias + " for type " + type ); - typId = alias; - typTxt = QString(); - } break; - case tagEnum: - { - typId = list.value( "name" ); - typTxt = QString(); - QString storage = list.value( "storage" ); - if ( typId.isEmpty() || storage.isEmpty() ) - err( "enum definition must have a name and a known storage type" ); - if ( ! NifValue::registerAlias( typId, storage ) ) - err( "failed to register alias " + storage + " for enum type " + typId ); - } break; - case tagVersion: - { - int v = NifModel::version2number( list.value( "num" ).trimmed() ); - if ( v != 0 && ! list.value( "num" ).isEmpty() ) - NifModel::supportedVersions.append( v ); - else - err( "invalid version tag" ); - } break; - default: - err( "expected basic, enum, compound, niobject or version got " + tagid + " instead" ); - } break; - case tagVersion: - //err( "version tag must not contain any sub tags" ); - break; - case tagCompound: - if ( x != tagAdd ) err( "only add tags allowed in compound type declaration" ); - case tagBlock: - push( x ); - switch ( x ) - { - case tagAdd: - { - // ns type optimizers come here - // we really shouldn't be doing this - // but it will work for now until we find a better solution - QString type = list.value( "type" ); - - if ( type == "KeyArray" ) type = "ns keyarray"; - else if ( type == "VectorKeyArray" ) type = "ns keyvecarray"; - else if ( type == "TypedVectorKeyArray" ) type = "ns keyvecarraytyp"; - else if ( type == "RotationKeyArray" ) type = "ns keyrotarray"; - - // now allocate - data = NifData( - list.value( "name" ), - type, - list.value( "template" ), - NifValue( NifValue::type( type ) ), - list.value( "arg" ), - list.value( "arr1" ), - list.value( "arr2" ), - list.value( "cond" ), - NifModel::version2number( list.value( "ver1" ) ), - NifModel::version2number( list.value( "ver2" ) ) - ); - QString defval = list.value( "default" ); - if ( ! defval.isEmpty() ) - data.value.fromString( defval ); - QString userver = list.value( "userver" ); - if ( ! userver.isEmpty() ) - { - QString cond = data.cond(); - if ( ! cond.isEmpty() ) - cond += " && "; - cond += "HEADER/User Version == " + userver; - data.setCond( cond ); - } - QString userver2 = list.value( "userver2" ); - if ( ! userver2.isEmpty() ) - { - QString cond = data.cond(); - if ( ! cond.isEmpty() ) - cond += " && "; - cond += "HEADER/User Version 2 == " + userver2; - data.setCond( cond ); - } - if ( data.name().isEmpty() || data.type().isEmpty() ) err( "add needs at least name and type attributes" ); - } break; - default: - err( "only add tags allowed in block declaration" ); - } break; - case tagEnum: - push( x ); - switch ( x ) - { - case tagOption: - optId = list.value( "name" ); - optVal = list.value( "value" ); - optTxt = QString(); - - if ( optId.isEmpty() || optVal.isEmpty() ) - err( "option defintion must have a name and a value" ); - bool ok; - optVal.toInt( &ok ); - if ( ! ok ) - err( "option value error (only integers please)" ); - break; - default: - err( "only option tags allowed in enum declaration" ); - } break; - default: - err( "error unhandled tag " + tagid ); - break; - } - return true; - } - - bool endElement( const QString &, const QString &, const QString & tagid ) - { - if ( depth <= 0 ) err( "mismatching end element tag for element " + tagid ); - Tag x = tags.value( tagid ); - if ( pop() != x ) err( "mismatching end element tag for element " + tagid ); - switch ( x ) - { - case tagCompound: - if ( blk && ! blk->id.isEmpty() && ! blk->text.isEmpty() ) - NifValue::setTypeDescription( blk->id, blk->text ); - else if ( !typId.isEmpty() && ! typTxt.isEmpty() ) - NifValue::setTypeDescription( typId, typTxt ); - case tagBlock: - if ( blk ) - { - if ( blk->id.isEmpty() ) - { - delete blk; - blk = 0; - err( "invalid " + tagid + " declaration: name is empty" ); - } - switch ( x ) - { - case tagCompound: - NifModel::compounds.insert( blk->id, blk ); - break; - case tagBlock: - NifModel::blocks.insert( blk->id, blk ); - break; - default: - break; - } - blk = 0; - } - break; - case tagAdd: - if ( blk ) blk->types.append( data ); - break; - case tagOption: - if ( ! NifValue::registerEnumOption( typId, optId, optVal.toInt(), optTxt ) ) - err( "failed to register enum option" ); - break; - case tagBasic: - case tagEnum: - NifValue::setTypeDescription( typId, typTxt ); - default: - break; - } - return true; - } - - bool characters( const QString & s ) - { - switch ( current() ) - { - case tagVersion: - break; - case tagCompound: - case tagBlock: - if ( blk ) - blk->text += s.trimmed(); - else - typTxt += s.trimmed(); - break; - case tagAdd: - data.setText( data.text() + s.trimmed() ); - break; - case tagBasic: - case tagEnum: - typTxt += s.trimmed(); - break; - case tagOption: - optTxt += s.trimmed(); - break; - default: - break; - } - return true; - } - - bool checkType( const NifData & data ) - { - return NifModel::compounds.contains( data.type() ) || NifValue::type( data.type() ) != NifValue::tNone || data.type() == "TEMPLATE"; - } - - bool checkTemp( const NifData & data ) - { - return data.temp().isEmpty() || NifValue::type( data.temp() ) != NifValue::tNone || data.temp() == "TEMPLATE" || NifModel::blocks.contains( data.temp() ); - } - - bool endDocument() - { // make a rough check of the maps - foreach ( QString key, NifModel::compounds.keys() ) - { - NifBlock * c = NifModel::compounds.value( key ); - foreach ( NifData data, c->types ) - { - if ( ! checkType( data ) ) - err( "compound type " + key + " referes to unknown type " + data.type() ); - if ( ! checkTemp( data ) ) - err( "compound type " + key + " referes to unknown template type " + data.temp() ); - if ( data.type() == key ) - err( "compound type " + key + " contains itself" ); - } - } - - foreach ( QString key, NifModel::blocks.keys() ) - { - NifBlock * blk = NifModel::blocks.value( key ); - if ( ! blk->ancestor.isEmpty() && ! NifModel::blocks.contains( blk->ancestor ) ) - err( "niobject " + key + " inherits unknown ancestor " + blk->ancestor ); - if ( blk->ancestor == key ) - err( "niobject " + key + " inherits itself" ); - foreach ( NifData data, blk->types ) - { - if ( ! checkType( data ) ) - err( "niobject " + key + " referres to unknown type " + data.type() ); - if ( ! checkTemp( data ) ) - err( "niobject " + key + " referes to unknown template type " + data.temp() ); - } - } - - return true; - } - - QString errorString() const - { - return errorStr; - } - bool fatalError( const QXmlParseException & exception ) - { - if ( errorStr.isEmpty() ) errorStr = "Syntax error"; - errorStr.prepend( QString( "XML parse error (line %1):
" ).arg( exception.lineNumber() ) ); - return false; - } -}; - -bool NifModel::loadXML() -{ - QDir dir( QApplication::applicationDirPath() ); - QString fname; - if ( dir.exists( "../docsys/nif.xml" ) ) - fname = dir.filePath( "../docsys/nif.xml" ); - else if ( dir.exists( "../../docsys/nif.xml" ) ) - fname = dir.filePath( "../../docsys/nif.xml" ); - else if ( dir.exists( "/usr/share/nifskope/nif.xml" ) ) - fname = dir.filePath( "/usr/share/nifskope/nif.xml" ); - else - fname = dir.filePath( "nif.xml" ); - QString result = NifModel::parseXmlDescription( fname ); - if ( ! result.isEmpty() ) - { - QMessageBox::critical( 0, "NifSkope", result ); - return false; - } - return true; -} - -QString NifModel::parseXmlDescription( const QString & filename ) -{ - QWriteLocker lck( &XMLlock ); - - qDeleteAll( compounds ); compounds.clear(); - qDeleteAll( blocks ); blocks.clear(); - - supportedVersions.clear(); - - NifValue::initialize(); - - QFile f( filename ); - if ( ! f.open( QIODevice::ReadOnly | QIODevice::Text ) ) - return QString( "error: couldn't open xml description file: " + filename ); - - NifXmlHandler handler; - QXmlSimpleReader reader; - reader.setContentHandler( &handler ); - reader.setErrorHandler( &handler ); - QXmlInputSource source( &f ); - reader.parse( source ); - - if ( ! handler.errorString().isEmpty() ) - { - qDeleteAll( compounds ); compounds.clear(); - qDeleteAll( blocks ); blocks.clear(); - - supportedVersions.clear(); - } - - return handler.errorString(); -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "nifmodel.h" +#include "niftypes.h" + +#include +#include +#include + +#define err( X ) { errorStr = X; return false; } + +QReadWriteLock NifModel::XMLlock; + +QList NifModel::supportedVersions; + +QHash NifModel::compounds; +QHash NifModel::blocks; + +class NifXmlHandler : public QXmlDefaultHandler +{ +public: + enum Tag + { + tagNone = 0, + tagFile, + tagVersion, + tagCompound, + tagBlock, + tagAdd, + tagBasic, + tagEnum, + tagOption + }; + + NifXmlHandler() + { + depth = 0; + tags.insert( "niftoolsxml", tagFile ); + tags.insert( "version", tagVersion ); + tags.insert( "compound", tagCompound ); + tags.insert( "niobject", tagBlock ); + tags.insert( "add", tagAdd ); + tags.insert( "basic", tagBasic ); + tags.insert( "enum", tagEnum ); + tags.insert( "option", tagOption ); + blk = 0; + } + + int depth; + Tag stack[10]; + QHash tags; + QString errorStr; + + QString typId; + QString typTxt; + + QString optId; + QString optVal; + QString optTxt; + + NifBlock * blk; + NifData data; + + Tag current() const + { + return stack[depth-1]; + } + void push( Tag x ) + { + stack[depth++] = x; + } + Tag pop() + { + return stack[--depth]; + } + + bool startElement( const QString &, const QString &, const QString & tagid, const QXmlAttributes & list ) + { + if ( depth >= 8 ) err( "error maximum nesting level exceeded" ); + + Tag x = tags.value( tagid ); + if ( x == tagNone ) err( "error unknown element '" + tagid + "'" ); + + if ( depth == 0 ) + { + if ( x != tagFile ) err( "this is not a niftoolsxml file" ); + push( x ); + return true; + } + + switch ( current() ) + { + case tagFile: + push( x ); + switch ( x ) + { + case tagCompound: + case tagBlock: + { + if ( ! list.value("nifskopetype").isEmpty() ) + { + QString alias = list.value( "name" ); + QString type = list.value( "nifskopetype" ); + if ( alias != type ) + if ( ! NifValue::registerAlias( alias, type ) ) + err( "failed to register alias " + alias + " for type " + type ); + typId = alias; + typTxt = QString(); + } + else + { + if ( x == tagCompound && NifValue::isValid( NifValue::type( list.value( "name" ) ) ) ) + err( "compound " + list.value( "name" ) + " is already registered as internal type" ); + + QString id = list.value( "name" ); + if ( id.isEmpty() ) + err( "compound and niblocks must have a name" ); + + if ( NifModel::compounds.contains( id ) || NifModel::blocks.contains( id ) ) + err( "multiple declarations of " + id ); + + if ( ! blk ) blk = new NifBlock; + blk->id = list.value( "name" ); + blk->abstract = ( list.value( "abstract" ) == "1" ); + + if ( x == tagBlock ) + { + blk->ancestor = list.value( "inherit" ); + if ( ! blk->ancestor.isEmpty() ) + { + if ( ! NifModel::blocks.contains( blk->ancestor ) ) + err( "forward declaration of block id " + blk->ancestor ); + } + } + }; + } break; + case tagBasic: + { + QString alias = list.value( "name" ); + QString type = list.value( "nifskopetype" ); + if ( alias.isEmpty() || type.isEmpty() ) + err( "basic definition must have a name and a nifskopetype" ); + if ( alias != type ) + if ( ! NifValue::registerAlias( alias, type ) ) + err( "failed to register alias " + alias + " for type " + type ); + typId = alias; + typTxt = QString(); + } break; + case tagEnum: + { + typId = list.value( "name" ); + typTxt = QString(); + QString storage = list.value( "storage" ); + if ( typId.isEmpty() || storage.isEmpty() ) + err( "enum definition must have a name and a known storage type" ); + if ( ! NifValue::registerAlias( typId, storage ) ) + err( "failed to register alias " + storage + " for enum type " + typId ); + } break; + case tagVersion: + { + int v = NifModel::version2number( list.value( "num" ).trimmed() ); + if ( v != 0 && ! list.value( "num" ).isEmpty() ) + NifModel::supportedVersions.append( v ); + else + err( "invalid version tag" ); + } break; + default: + err( "expected basic, enum, compound, niobject or version got " + tagid + " instead" ); + } break; + case tagVersion: + //err( "version tag must not contain any sub tags" ); + break; + case tagCompound: + if ( x != tagAdd ) err( "only add tags allowed in compound type declaration" ); + case tagBlock: + push( x ); + switch ( x ) + { + case tagAdd: + { + // ns type optimizers come here + // we really shouldn't be doing this + // but it will work for now until we find a better solution + QString type = list.value( "type" ); + + if ( type == "KeyArray" ) type = "ns keyarray"; + else if ( type == "VectorKeyArray" ) type = "ns keyvecarray"; + else if ( type == "TypedVectorKeyArray" ) type = "ns keyvecarraytyp"; + else if ( type == "RotationKeyArray" ) type = "ns keyrotarray"; + + // now allocate + data = NifData( + list.value( "name" ), + type, + list.value( "template" ), + NifValue( NifValue::type( type ) ), + list.value( "arg" ), + list.value( "arr1" ), + list.value( "arr2" ), + list.value( "cond" ), + NifModel::version2number( list.value( "ver1" ) ), + NifModel::version2number( list.value( "ver2" ) ) + ); + QString defval = list.value( "default" ); + if ( ! defval.isEmpty() ) + data.value.fromString( defval ); + QString userver = list.value( "userver" ); + if ( ! userver.isEmpty() ) + { + QString cond = data.cond(); + if ( ! cond.isEmpty() ) + cond += " && "; + cond += "HEADER/User Version == " + userver; + data.setCond( cond ); + } + QString userver2 = list.value( "userver2" ); + if ( ! userver2.isEmpty() ) + { + QString cond = data.cond(); + if ( ! cond.isEmpty() ) + cond += " && "; + cond += "HEADER/User Version 2 == " + userver2; + data.setCond( cond ); + } + if ( data.name().isEmpty() || data.type().isEmpty() ) err( "add needs at least name and type attributes" ); + } break; + default: + err( "only add tags allowed in block declaration" ); + } break; + case tagEnum: + push( x ); + switch ( x ) + { + case tagOption: + optId = list.value( "name" ); + optVal = list.value( "value" ); + optTxt = QString(); + + if ( optId.isEmpty() || optVal.isEmpty() ) + err( "option defintion must have a name and a value" ); + bool ok; + optVal.toInt( &ok ); + if ( ! ok ) + err( "option value error (only integers please)" ); + break; + default: + err( "only option tags allowed in enum declaration" ); + } break; + default: + err( "error unhandled tag " + tagid ); + break; + } + return true; + } + + bool endElement( const QString &, const QString &, const QString & tagid ) + { + if ( depth <= 0 ) err( "mismatching end element tag for element " + tagid ); + Tag x = tags.value( tagid ); + if ( pop() != x ) err( "mismatching end element tag for element " + tagid ); + switch ( x ) + { + case tagCompound: + if ( blk && ! blk->id.isEmpty() && ! blk->text.isEmpty() ) + NifValue::setTypeDescription( blk->id, blk->text ); + else if ( !typId.isEmpty() && ! typTxt.isEmpty() ) + NifValue::setTypeDescription( typId, typTxt ); + case tagBlock: + if ( blk ) + { + if ( blk->id.isEmpty() ) + { + delete blk; + blk = 0; + err( "invalid " + tagid + " declaration: name is empty" ); + } + switch ( x ) + { + case tagCompound: + NifModel::compounds.insert( blk->id, blk ); + break; + case tagBlock: + NifModel::blocks.insert( blk->id, blk ); + break; + default: + break; + } + blk = 0; + } + break; + case tagAdd: + if ( blk ) blk->types.append( data ); + break; + case tagOption: + if ( ! NifValue::registerEnumOption( typId, optId, optVal.toInt(), optTxt ) ) + err( "failed to register enum option" ); + break; + case tagBasic: + case tagEnum: + NifValue::setTypeDescription( typId, typTxt ); + default: + break; + } + return true; + } + + bool characters( const QString & s ) + { + switch ( current() ) + { + case tagVersion: + break; + case tagCompound: + case tagBlock: + if ( blk ) + blk->text += s.trimmed(); + else + typTxt += s.trimmed(); + break; + case tagAdd: + data.setText( data.text() + s.trimmed() ); + break; + case tagBasic: + case tagEnum: + typTxt += s.trimmed(); + break; + case tagOption: + optTxt += s.trimmed(); + break; + default: + break; + } + return true; + } + + bool checkType( const NifData & data ) + { + return NifModel::compounds.contains( data.type() ) || NifValue::type( data.type() ) != NifValue::tNone || data.type() == "TEMPLATE"; + } + + bool checkTemp( const NifData & data ) + { + return data.temp().isEmpty() || NifValue::type( data.temp() ) != NifValue::tNone || data.temp() == "TEMPLATE" || NifModel::blocks.contains( data.temp() ); + } + + bool endDocument() + { // make a rough check of the maps + foreach ( QString key, NifModel::compounds.keys() ) + { + NifBlock * c = NifModel::compounds.value( key ); + foreach ( NifData data, c->types ) + { + if ( ! checkType( data ) ) + err( "compound type " + key + " referes to unknown type " + data.type() ); + if ( ! checkTemp( data ) ) + err( "compound type " + key + " referes to unknown template type " + data.temp() ); + if ( data.type() == key ) + err( "compound type " + key + " contains itself" ); + } + } + + foreach ( QString key, NifModel::blocks.keys() ) + { + NifBlock * blk = NifModel::blocks.value( key ); + if ( ! blk->ancestor.isEmpty() && ! NifModel::blocks.contains( blk->ancestor ) ) + err( "niobject " + key + " inherits unknown ancestor " + blk->ancestor ); + if ( blk->ancestor == key ) + err( "niobject " + key + " inherits itself" ); + foreach ( NifData data, blk->types ) + { + if ( ! checkType( data ) ) + err( "niobject " + key + " referres to unknown type " + data.type() ); + if ( ! checkTemp( data ) ) + err( "niobject " + key + " referes to unknown template type " + data.temp() ); + } + } + + return true; + } + + QString errorString() const + { + return errorStr; + } + bool fatalError( const QXmlParseException & exception ) + { + if ( errorStr.isEmpty() ) errorStr = "Syntax error"; + errorStr.prepend( QString( "XML parse error (line %1):
" ).arg( exception.lineNumber() ) ); + return false; + } +}; + +bool NifModel::loadXML() +{ + QDir dir( QApplication::applicationDirPath() ); + QString fname; + if ( dir.exists( "../docsys/nif.xml" ) ) + fname = dir.filePath( "../docsys/nif.xml" ); + else if ( dir.exists( "../../docsys/nif.xml" ) ) + fname = dir.filePath( "../../docsys/nif.xml" ); + else if ( dir.exists( "/usr/share/nifskope/nif.xml" ) ) + fname = dir.filePath( "/usr/share/nifskope/nif.xml" ); + else + fname = dir.filePath( "nif.xml" ); + QString result = NifModel::parseXmlDescription( fname ); + if ( ! result.isEmpty() ) + { + QMessageBox::critical( 0, "NifSkope", result ); + return false; + } + return true; +} + +QString NifModel::parseXmlDescription( const QString & filename ) +{ + QWriteLocker lck( &XMLlock ); + + qDeleteAll( compounds ); compounds.clear(); + qDeleteAll( blocks ); blocks.clear(); + + supportedVersions.clear(); + + NifValue::initialize(); + + QFile f( filename ); + if ( ! f.open( QIODevice::ReadOnly | QIODevice::Text ) ) + return QString( "error: couldn't open xml description file: " + filename ); + + NifXmlHandler handler; + QXmlSimpleReader reader; + reader.setContentHandler( &handler ); + reader.setErrorHandler( &handler ); + QXmlInputSource source( &f ); + reader.parse( source ); + + if ( ! handler.errorString().isEmpty() ) + { + qDeleteAll( compounds ); compounds.clear(); + qDeleteAll( blocks ); blocks.clear(); + + supportedVersions.clear(); + } + + return handler.errorString(); +} + diff --git a/options.cpp b/options.cpp index 19efa78b3..ade65c7f0 100644 --- a/options.cpp +++ b/options.cpp @@ -1,822 +1,822 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "options.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include "widgets/colorwheel.h" -#include "widgets/fileselect.h" -#include "widgets/floatslider.h" -#include "widgets/groupbox.h" - -class SmallListView : public QListView -{ -public: - QSize sizeHint() const { return minimumSizeHint(); } -}; - -Options::Options() -{ - tSave = new QTimer( this ); - tSave->setInterval( 5000 ); - tSave->setSingleShot( true ); - connect( this, SIGNAL( sigChanged() ), tSave, SLOT( start() ) ); - connect( tSave, SIGNAL( timeout() ), this, SLOT( save() ) ); - - tEmit = new QTimer( this ); - tEmit->setInterval( 500 ); - tEmit->setSingleShot( true ); - connect( tEmit, SIGNAL( timeout() ), this, SIGNAL( sigChanged() ) ); - connect( this, SIGNAL( sigChanged() ), tEmit, SLOT( stop() ) ); - - - NIFSKOPE_QSETTINGS(cfg); - cfg.beginGroup( "Render Settings" ); - - aDrawAxes = new QAction( "Draw &Axes", this ); - aDrawAxes->setToolTip( "draw xyz-Axes" ); - aDrawAxes->setCheckable( true ); - aDrawAxes->setChecked( cfg.value( "Draw AXes", true ).toBool() ); - connect( aDrawAxes, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - aDrawNodes = new QAction( "Draw &Nodes", this ); - aDrawNodes->setToolTip( "draw bones/nodes" ); - aDrawNodes->setCheckable( true ); - aDrawNodes->setChecked( cfg.value( "Draw Nodes", true ).toBool() ); - connect( aDrawNodes, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - aDrawHavok = new QAction( "Draw &Havok", this ); - aDrawHavok->setToolTip( "draw the havok shapes" ); - aDrawHavok->setCheckable( true ); - aDrawHavok->setChecked( cfg.value( "Draw Collision Geometry", true ).toBool() ); - connect( aDrawHavok, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - aDrawConstraints = new QAction( "Draw &Constraints", this ); - aDrawConstraints->setToolTip( "draw the havok constraints" ); - aDrawConstraints->setCheckable( true ); - aDrawConstraints->setChecked( cfg.value( "Draw Constraints", true ).toBool() ); - connect( aDrawConstraints, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - aDrawFurn = new QAction( "Draw &Furniture", this ); - aDrawFurn->setToolTip( "draw the furniture markers" ); - aDrawFurn->setCheckable( true ); - aDrawFurn->setChecked( cfg.value( "Draw Furniture Markers", true ).toBool() ); - connect( aDrawFurn, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - aDrawHidden = new QAction( "Show Hid&den", this ); - aDrawHidden->setToolTip( "always draw nodes and meshes" ); - aDrawHidden->setCheckable( true ); - aDrawHidden->setChecked( cfg.value( "Show Hidden Objects", false ).toBool() ); - connect( aDrawHidden, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - aDrawStats = new QAction( "Show S&tats", this ); - aDrawStats->setToolTip( "display some statistics about the selected node" ); - aDrawStats->setCheckable( true ); - aDrawStats->setChecked( cfg.value( "Show Stats", false ).toBool() ); - connect( aDrawStats, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - - dialog = new GroupBox( "", Qt::Vertical ); - dialog->setWindowTitle( "Render Settings" ); - dialog->setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); - dialog->installEventFilter( this ); - - aSettings = new QAction( "&Settings...", this ); - aSettings->setToolTip( "show the settings dialog" ); - connect( aSettings, SIGNAL( triggered() ), dialog, SLOT( show() ) ); - - - dialog->pushLayout( "Texture Folders", Qt::Vertical ); - dialog->pushLayout( Qt::Horizontal ); - -#ifdef Q_OS_WIN32 - dialog->pushLayout( "Auto Detect", Qt::Vertical ); - QButtonGroup * tfgamegrp = new QButtonGroup( this ); - connect( tfgamegrp, SIGNAL( buttonClicked( int ) ), this, SLOT( textureFolderAutoDetect() ) ); - int gameid = 0; - QPushButton * bt = new QPushButton( "Auto Detect\nGame Paths" ); - tfgamegrp->addButton( bt); - dialog->addWidget( bt ); - - dialog->popLayout(); -#endif - - dialog->pushLayout( "Custom", Qt::Vertical ); - dialog->pushLayout( Qt::Horizontal ); - - QButtonGroup * tfactgrp = new QButtonGroup( this ); - connect( tfactgrp, SIGNAL( buttonClicked( int ) ), this, SLOT( textureFolderAction( int ) ) ); - int tfaid = 0; - foreach ( QString tfaname, QStringList() << "Add Folder" << "Remove Folder" << "Move Up" ) - { - QPushButton * bt = new QPushButton( tfaname ); - TexFolderButtons[tfaid] = bt; - tfactgrp->addButton( bt, tfaid++ ); - dialog->addWidget( bt ); - } - - dialog->popLayout(); - - TexFolderModel = new QStringListModel( this ); - TexFolderModel->setStringList( cfg.value( "Texture Folders" ).toStringList() ); - connect( TexFolderModel, SIGNAL( rowsInserted( const QModelIndex &, int, int ) ), tEmit, SLOT( start() ) ); - connect( TexFolderModel, SIGNAL( rowsRemoved( const QModelIndex &, int, int ) ), tEmit, SLOT( start() ) ); - connect( TexFolderModel, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), tEmit, SLOT( start() ) ); - connect( TexFolderModel, SIGNAL( modelReset() ), tEmit, SLOT( start() ) ); - - TexFolderView = new SmallListView; - dialog->addWidget( TexFolderView, 0 ); - TexFolderView->setModel( TexFolderModel ); - TexFolderView->setCurrentIndex( TexFolderModel->index( 0, 0, QModelIndex() ) ); - - dialog->pushLayout( Qt::Horizontal ); - - TexFolderSelect = new FileSelector( FileSelector::Folder, "Folder", QBoxLayout::RightToLeft ); - dialog->addWidget( TexFolderSelect ); - - QDataWidgetMapper * TexFolderMapper = new QDataWidgetMapper( this ); - TexFolderMapper->setModel( TexFolderModel ); - TexFolderMapper->addMapping( TexFolderSelect, 0 ); - TexFolderMapper->setCurrentModelIndex( TexFolderView->currentIndex() ); - connect( TexFolderView->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ), - TexFolderMapper, SLOT( setCurrentModelIndex( const QModelIndex & ) ) ); - connect( TexFolderSelect, SIGNAL( sigActivated( const QString & ) ), TexFolderMapper, SLOT( submit() ) ); - - connect( TexFolderView->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ), - this, SLOT( textureFolderIndex( const QModelIndex & ) ) ); - textureFolderIndex( TexFolderView->currentIndex() ); - - dialog->addWidget( TexAlternatives = new QCheckBox( "&Look for alternatives" ) ); - TexAlternatives->setToolTip( "If a texture was nowhere to be found
NifSkope will start looking for alternatives.

texture.dds does not exist -> use texture.bmp instead

" ); - TexAlternatives->setChecked( cfg.value( "Texture Alternatives", true ).toBool() ); - connect( TexAlternatives, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - dialog->popLayout(); - dialog->popLayout(); - dialog->popLayout(); - dialog->popLayout(); - dialog->pushLayout( Qt::Horizontal ); - dialog->pushLayout( "Render", Qt::Vertical ); - - - dialog->addWidget( AntiAlias = new QCheckBox( "&Anti Aliasing" ) ); - AntiAlias->setToolTip( "Enable anti aliasing and anisotropic texture filtering if available.
You'll need to restart NifSkope for this setting to take effect.
" ); - AntiAlias->setChecked( cfg.value( "Anti Aliasing", true ).toBool() ); - connect( AntiAlias, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - dialog->addWidget( Textures = new QCheckBox( "&Textures" ) ); - Textures->setToolTip( "Enable textures" ); - Textures->setChecked( cfg.value( "Texturing", true ).toBool() ); - connect( Textures, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - dialog->addWidget( Shaders = new QCheckBox( "&Shaders" ) ); - Shaders->setToolTip( "Enable Shaders" ); - Shaders->setChecked( cfg.value( "Enable Shaders", true ).toBool() ); - connect( Shaders, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - - dialog->popLayout(); - dialog->pushLayout( "Up Axis", Qt::Vertical ); - - - dialog->addWidget( AxisX = new QRadioButton( "X" ) ); - dialog->addWidget( AxisY = new QRadioButton( "Y" ) ); - dialog->addWidget( AxisZ = new QRadioButton( "Z" ) ); - - QButtonGroup * btgrp = new QButtonGroup( this ); - btgrp->addButton( AxisX ); - btgrp->addButton( AxisY ); - btgrp->addButton( AxisZ ); - btgrp->setExclusive( true ); - - QString upax = cfg.value( "Up Axis", "Z" ).toString(); - if ( upax == "X" ) - AxisX->setChecked( true ); - else if ( upax == "Y" ) - AxisY->setChecked( true ); - else - AxisZ->setChecked( true ); - - connect( AxisX, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - connect( AxisY, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - connect( AxisZ, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - - dialog->popLayout(); - dialog->pushLayout( "Culling", Qt::Vertical, 1 ); - - dialog->addWidget( CullNoTex = new QCheckBox( "Cull &Non Textured" ) ); - CullNoTex->setToolTip( "Hide all meshes without textures" ); - CullNoTex->setChecked( cfg.value( "Cull Non Textured", false ).toBool() ); - connect( CullNoTex, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - dialog->addWidget( CullByID = new QCheckBox( "&Cull Nodes by Name" ) ); - CullByID->setToolTip( "Enabling this option hides some special nodes and meshes" ); - CullByID->setChecked( cfg.value( "Cull Nodes By Name", false ).toBool() ); - connect( CullByID, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - dialog->addWidget( CullExpr = new QLineEdit( cfg.value( "Cull Expression", "^collidee|^shadowcaster|^\\!LoD_cullme|^footprint" ).toString() ) ); - CullExpr->setToolTip( "Enter a regular expression. Nodes which names match the expression will be hidden" ); - CullExpr->setEnabled( CullByID->isChecked() ); - connect( CullExpr, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( sigChanged() ) ); - connect( CullByID, SIGNAL( toggled( bool ) ), CullExpr, SLOT( setEnabled( bool ) ) ); - - - dialog->popLayout(); - dialog->popLayout(); - dialog->pushLayout( "Light", Qt::Vertical ); - dialog->pushLayout( Qt::Horizontal ); - - - cfg.beginGroup( "Light0" ); - - QStringList lightNames( QStringList() << "Ambient" << "Diffuse" << "Specular" ); - QList lightDefaults( QList() << QColor::fromRgbF( .4, .4, .4 ) << QColor::fromRgbF( .8, .8, .8 ) << QColor::fromRgbF( 1, 1, 1 ) ); - for ( int l = 0; l < 3; l++ ) - { - ColorWheel * wheel = new ColorWheel( cfg.value( lightNames[l], lightDefaults[l] ).value() ); - wheel->setSizeHint( QSize( 105, 105 ) ); - connect( wheel, SIGNAL( sigColorEdited( const QColor & ) ), this, SIGNAL( sigChanged() ) ); - LightColor[l] = wheel; - - dialog->pushLayout( lightNames[l], Qt::Vertical ); - dialog->addWidget( wheel ); - dialog->popLayout(); - } - - dialog->popLayout(); - dialog->pushLayout( Qt::Horizontal ); - - dialog->addWidget( LightFrontal = new QCheckBox( "Frontal" ), 0 ); - LightFrontal->setToolTip( "Lock light to camera position" ); - LightFrontal->setChecked( cfg.value( "Frontal", true ).toBool() ); - connect( LightFrontal, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); - - QWidget * pos = dialog->pushLayout( "Position", Qt::Horizontal, 1 ); - pos->setDisabled( LightFrontal->isChecked() ); - connect( LightFrontal, SIGNAL( toggled( bool ) ), pos, SLOT( setDisabled( bool ) ) ); - - dialog->addWidget( new QLabel( "Declination" ) ); - dialog->addWidget( LightDeclination = new QSpinBox, 1 ); - LightDeclination->setMinimum( -180 ); - LightDeclination->setMaximum( +180 ); - LightDeclination->setSingleStep( 5 ); - LightDeclination->setWrapping( true ); - LightDeclination->setValue( cfg.value( "Declination", 0 ).toInt() ); - connect( LightDeclination, SIGNAL( valueChanged( int ) ), this, SIGNAL( sigChanged() ) ); - - dialog->addWidget( new QLabel( "Planar Angle" ) ); - dialog->addWidget( LightPlanarAngle = new QSpinBox, 1 ); - LightPlanarAngle->setMinimum( -180 ); - LightPlanarAngle->setMaximum( +180 ); - LightPlanarAngle->setSingleStep( 5 ); - LightPlanarAngle->setWrapping( true ); - LightPlanarAngle->setValue( cfg.value( "Planar Angle", 0 ).toInt() ); - connect( LightPlanarAngle, SIGNAL( valueChanged( int ) ), this, SIGNAL( sigChanged() ) ); - - cfg.endGroup(); - - dialog->popLayout(); - dialog->pushLayout( "Presets", Qt::Horizontal ); - - QButtonGroup * grp = new QButtonGroup( this ); - connect( grp, SIGNAL( buttonClicked( int ) ), this, SLOT( activateLightPreset( int ) ) ); - int psid = 0; - foreach ( QString psname, QStringList() << "Sunny Day" << "Dark Night" ) - { - QPushButton * bt = new QPushButton( psname ); - grp->addButton( bt, psid++ ); - dialog->addWidget( bt ); - } - - dialog->popLayout(); - dialog->popLayout(); - dialog->popLayout(); - dialog->pushLayout( "Colors", Qt::Horizontal ); - - - QStringList colorNames( QStringList() << "Background" << "Foreground" << "Highlight" ); - QList colorDefaults( QList() << QColor::fromRgb( 0, 0, 0 ) << QColor::fromRgb( 255, 255, 255 ) << QColor::fromRgb( 255, 255, 0 ) ); - for ( int c = 0; c < 3; c++ ) - { - dialog->pushLayout( colorNames[c], Qt::Horizontal ); - - ColorWheel * wheel = new ColorWheel( cfg.value( colorNames[c], colorDefaults[c] ).value() ); - wheel->setSizeHint( QSize( 105, 105 ) ); - connect( wheel, SIGNAL( sigColorEdited( const QColor & ) ), this, SIGNAL( sigChanged() ) ); - colors[ c ] = wheel; - dialog->addWidget( wheel ); - - if ( c != 0 ) - { - alpha[ c ] = new AlphaSlider( Qt::Vertical ); - alpha[ c ]->setValue( cfg.value( colorNames[c], colorDefaults[c] ).value().alphaF() ); - alpha[ c ]->setColor( wheel->getColor() ); - connect( alpha[ c ], SIGNAL( valueChanged( float ) ), this, SIGNAL( sigChanged() ) ); - connect( wheel, SIGNAL( sigColor( const QColor & ) ), alpha[ c ], SLOT( setColor( const QColor & ) ) ); - dialog->addWidget( alpha[ c ] ); - } - else - alpha[ c ] = 0; - - dialog->popLayout(); - } - dialog->popLayout(); - - //Misc Options - cfg.beginGroup( "Misc Settings" ); - - dialog->pushLayout( "Misc. Settings", Qt::Vertical, 1 ); - dialog->pushLayout( Qt::Horizontal ); - - - dialog->addWidget( new QLabel( "Startup Version" ) ); - dialog->addWidget( StartVer = new QLineEdit( cfg.value( "Startup Version", "20.0.0.5" ).toString() ) ); - StartVer->setToolTip( "This is the version that the initial 'blank' NIF file that is created when NifSkope opens will be." ); - connect( StartVer, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( sigChanged() ) ); - - - dialog->popLayout(); - - //More Misc options can be added here. - dialog->popLayout(); - cfg.endGroup(); -} - -Options::~Options() -{ - if ( tSave->isActive() ) - save(); -} - -Options * Options::get() -{ - static Options * options = new Options(); - return options; -} - -QList Options::actions() -{ - Options * opts = get(); - return QList() - << opts->aDrawAxes - << opts->aDrawNodes - << opts->aDrawHavok - << opts->aDrawConstraints - << opts->aDrawFurn - << opts->aDrawHidden -#ifdef USE_GL_QPAINTER - << opts->aDrawStats -#endif - << opts->aSettings; -} - -bool Options::eventFilter( QObject * o, QEvent * e ) -{ - if ( o == dialog && e->type() == QEvent::Close && tSave->isActive() ) - { - save(); - } - return false; -} - -void Options::save() -{ - tSave->stop(); - - NIFSKOPE_QSETTINGS(cfg); - - // remove obsolete keys - foreach ( QString key, QStringList() - << "texture folder" << "bg color" << "cull nodes by name" << "draw axis" << "draw furniture" - << "draw havok" << "draw hidden" << "draw nodes" << "draw only textured meshes" << "draw stats" - << "enable blending" << "enable shading" << "enable textures" << "highlight meshes" << "hl color" - << "rotate" ) - { - if ( cfg.contains( key ) ) - cfg.remove( key ); - } - - cfg.beginGroup( "Render Settings" ); - - cfg.setValue( "Texture Folders", textureFolders() ); - cfg.setValue( "Texture Alternatives", textureAlternatives() ); - - cfg.setValue( "Draw Axes", drawAxes() ); - cfg.setValue( "Draw Nodes", drawNodes() ); - cfg.setValue( "Draw Collision Geometry", drawHavok() ); - cfg.setValue( "Draw Constraints", drawConstraints() ); - cfg.setValue( "Draw Furniture Markers", drawFurn() ); - cfg.setValue( "Show Hidden Objects", drawHidden() ); - cfg.setValue( "Show Stats", drawStats() ); - - cfg.setValue( "Background", bgColor() ); - cfg.setValue( "Foreground", nlColor() ); - cfg.setValue( "Highlight", hlColor() ); - - cfg.setValue( "Anti Aliasing", antialias() ); - cfg.setValue( "Texturing", texturing() ); - cfg.setValue( "Enable Shaders", shaders() ); - - cfg.setValue( "Cull Nodes By Name", CullByID->isChecked() ); - cfg.setValue( "Cull Expression", CullExpr->text() ); - cfg.setValue( "Cull Non Textured", onlyTextured() ); - - cfg.setValue( "Up Axis", AxisX->isChecked() ? "X" : AxisY->isChecked() ? "Y" : "Z" ); - - cfg.beginGroup( "Light0" ); - cfg.setValue( "Ambient", ambient() ); - cfg.setValue( "Diffuse", diffuse() ); - cfg.setValue( "Specular", specular() ); - cfg.setValue( "Frontal", lightFrontal() ); - cfg.setValue( "Declination", lightDeclination() ); - cfg.setValue( "Planar Angle", lightPlanarAngle() ); - - cfg.endGroup(); - - cfg.beginGroup( "Misc Settings" ); - - cfg.setValue( "Startup Version", startupVersion() ); - - cfg.endGroup(); - - -} - -void Options::textureFolderAutoDetect() -{ - //List to hold all games paths that were detected - QStringList list; - - //Generic "same directory" path should always be added - list.append( "./" ); - - //String to hold the message box message - QString game_list; - -#ifdef Q_OS_WIN32 - - // Oblivion - { - QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Oblivion", QSettings::NativeFormat ); - QDir dir( reg.value( "Installed Path" ).toString() ); - if ( dir.exists() && dir.cd( "Data" ) ) - { - game_list.append( "TES4: Oblivion\n" ); - - list.append( dir.path() ); - - dir.setNameFilters( QStringList() << "*.bsa" ); - dir.setFilter( QDir::Dirs ); - foreach ( QString dn, dir.entryList() ) - list << dir.filePath( dn ); - -#ifndef FSENGINE - if ( ! dir.cd( "Textures" ) ) - { - QMessageBox::information( dialog, "NifSkope", - "

The texture folder was not found.

" - "

This may be because you haven't extracted the archive files yet.
" - "Here, it is explained how to do that.

" ); - } -#endif - } - } - - // Morrowind - { - QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Morrowind", QSettings::NativeFormat ); - QDir dir( reg.value( "Installed Path" ).toString() ); - if ( dir.exists() && dir.cd( "Data Files" ) ) - { - game_list.append( "TES3: Morrowind\n" ); - - list.append( dir.path() ); - list.append( dir.path() + "/Textures" ); - - dir.setNameFilters( QStringList() << "*.bsa" ); - dir.setFilter( QDir::Dirs ); - foreach ( QString dn, dir.entryList() ) - list << dir.filePath( dn ) << dir.filePath( dn ) + "/Textures"; - } - } - - // CIV IV - { - QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Firaxis Games\\Sid Meier's Civilization 4", QSettings::NativeFormat ); - QDir dir( reg.value( "INSTALLDIR" ).toString() ); - if ( dir.exists() && dir.cd( "Assets/Art/shared" ) ) - { - game_list.append( "Sid Meier's Civilization IV\n" ); - - list.append( dir.path() ); - } - } - - // Freedom Force - list.append( "./textures" ); - list.append( "./skins/standard" ); - { - QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\FFVTTR", QSettings::NativeFormat ); - QDir dir( reg.value( "InstallDir" ).toString() ); - if ( dir.exists() && dir.cd( "/Data/Art/library/area_specific/_textures" ) ) - { - game_list.append( "Freedom Force vs. the Third Reich\n" ); - list.append( dir.path() ); - } - } - { - QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\Freedom Force", QSettings::NativeFormat ); - QDir dir( reg.value( "InstallDir" ).toString() ); - if ( dir.exists() && dir.cd( "Data/Art/library/area_specific/_textures" ) ) - { - game_list.append( "Freedom Force\n" ); - - list.append( dir.path() ); - } - } - { - QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\Freedom Force Demo", QSettings::NativeFormat ); - QDir dir( reg.value( "InstallDir" ).toString() ); - if ( dir.exists() && dir.cd( "Data/Art/library/area_specific/_textures" ) ) { - game_list.append( "Freedom Force Demo\n" ); - - list.append( dir.path() ); - } - } -#endif - //Set folder list box to contain the newly detected textures, along with the ones the user has already defined, ignoring any duplicates - - //Remove duplicates - QStringList finalList = TexFolderModel->stringList(); - for( QStringList::ConstIterator it = list.begin(); it != list.end(); ++it ) { - if ( finalList.contains( *it ) == false ) - finalList << *it; - } - - TexFolderModel->setStringList( finalList ); - TexAlternatives->setChecked( false ); - TexFolderView->setCurrentIndex( TexFolderModel->index( 0, 0, QModelIndex() ) ); - - //Announce result to user - if ( game_list.size() == 0 ) - { - game_list = QString("No supported games were detected.\nYour game may still work, you will just have to set the folders manually until an auto-detect routine is created."); - } - else - { - game_list = QString("Successfully detected the following games:\n") + game_list; - } - QMessageBox::information( dialog, "NifSkope", game_list ); - - -} - -void Options::textureFolderAction( int id ) -{ - QModelIndex idx = TexFolderView->currentIndex(); - switch ( id ) - { - case 0: - // add folder - TexFolderModel->insertRow( 0, QModelIndex() ); - TexFolderModel->setData( TexFolderModel->index( 0, 0, QModelIndex() ), "Choose a folder", Qt::EditRole ); - TexFolderView->setCurrentIndex( TexFolderModel->index( 0, 0, QModelIndex() ) ); - break; - case 1: - if ( idx.isValid() ) - { // remove folder - TexFolderModel->removeRow( idx.row(), QModelIndex() ); - } - break; - case 2: - if ( idx.isValid() && idx.row() > 0 ) - { // move up - QModelIndex xdi = idx.sibling( idx.row() - 1, 0 ); - QVariant v = TexFolderModel->data( idx, Qt::EditRole ); - TexFolderModel->setData( idx, TexFolderModel->data( xdi, Qt::EditRole ), Qt::EditRole ); - TexFolderModel->setData( xdi, v, Qt::EditRole ); - TexFolderView->setCurrentIndex( xdi ); - } - break; - } -} - -void Options::textureFolderIndex( const QModelIndex & idx ) -{ - TexFolderSelect->setEnabled( idx.isValid() ); - TexFolderButtons[0]->setEnabled( true ); - TexFolderButtons[1]->setEnabled( idx.isValid() ); - TexFolderButtons[2]->setEnabled( idx.isValid() && ( idx.row() > 0 ) ); -} - -void Options::activateLightPreset( int id ) -{ - switch ( id ) - { - case 0: // sunny day - LightColor[0]->setColor( QColor::fromRgbF( 0.4, 0.4, 0.4 ) ); - LightColor[1]->setColor( QColor::fromRgbF( 0.8, 0.8, 0.8 ) ); - LightColor[2]->setColor( QColor::fromRgbF( 1.0, 1.0, 1.0 ) ); - break; - case 1: // dark night - LightColor[0]->setColor( QColor::fromRgbF( 0.2, 0.2, 0.25 ) ); - LightColor[1]->setColor( QColor::fromRgbF( 0.1, 0.1, 0.1 ) ); - LightColor[2]->setColor( QColor::fromRgbF( 0.1, 0.1, 0.1 ) ); - break; - default: - return; - } - emit sigChanged(); -} - - -QStringList Options::textureFolders() -{ - return get()->TexFolderModel->stringList(); -} - -bool Options::textureAlternatives() -{ - return get()->TexAlternatives->isChecked(); -} - -Options::Axis Options::upAxis() -{ - return get()->AxisX->isChecked() ? XAxis : get()->AxisY->isChecked() ? YAxis : ZAxis; -} - -bool Options::antialias() -{ - return get()->AntiAlias->isChecked(); -} - -bool Options::texturing() -{ - return get()->Textures->isChecked(); -} - -bool Options::shaders() -{ - return get()->Shaders->isChecked(); -} - - -bool Options::drawAxes() -{ - return get()->aDrawAxes->isChecked(); -} - -bool Options::drawNodes() -{ - return get()->aDrawNodes->isChecked(); -} - -bool Options::drawHavok() -{ - return get()->aDrawHavok->isChecked(); -} - -bool Options::drawConstraints() -{ - return get()->aDrawConstraints->isChecked(); -} - -bool Options::drawFurn() -{ - return get()->aDrawFurn->isChecked(); -} - -bool Options::drawHidden() -{ - return get()->aDrawHidden->isChecked(); -} - -bool Options::drawStats() -{ - return get()->aDrawStats->isChecked(); -} - -bool Options::benchmark() -{ - return false; -} - - -QColor Options::bgColor() -{ - return get()->colors[ 0 ]->getColor(); -} - -QColor Options::nlColor() -{ - QColor c = get()->colors[ 1 ]->getColor(); - c.setAlphaF( get()->alpha[ 1 ]->value() ); - return c; -} - -QColor Options::hlColor() -{ - QColor c = get()->colors[ 2 ]->getColor(); - c.setAlphaF( get()->alpha[ 2 ]->value() ); - return c; -} - - -QRegExp Options::cullExpression() -{ - return get()->CullByID->isChecked() ? QRegExp( get()->CullExpr->text() ) : QRegExp(); -} - -bool Options::onlyTextured() -{ - return get()->CullNoTex->isChecked(); -} - - -QColor Options::ambient() -{ - return get()->LightColor[ 0 ]->getColor(); -} - -QColor Options::diffuse() -{ - return get()->LightColor[ 1 ]->getColor(); -} - -QColor Options::specular() -{ - return get()->LightColor[ 2 ]->getColor(); -} - -bool Options::lightFrontal() -{ - return get()->LightFrontal->isChecked(); -} - -int Options::lightDeclination() -{ - return get()->LightDeclination->value(); -} - -int Options::lightPlanarAngle() -{ - return get()->LightPlanarAngle->value(); -} - -QString Options::startupVersion() -{ - return get()->StartVer->text(); -}; - - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "options.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "widgets/colorwheel.h" +#include "widgets/fileselect.h" +#include "widgets/floatslider.h" +#include "widgets/groupbox.h" + +class SmallListView : public QListView +{ +public: + QSize sizeHint() const { return minimumSizeHint(); } +}; + +Options::Options() +{ + tSave = new QTimer( this ); + tSave->setInterval( 5000 ); + tSave->setSingleShot( true ); + connect( this, SIGNAL( sigChanged() ), tSave, SLOT( start() ) ); + connect( tSave, SIGNAL( timeout() ), this, SLOT( save() ) ); + + tEmit = new QTimer( this ); + tEmit->setInterval( 500 ); + tEmit->setSingleShot( true ); + connect( tEmit, SIGNAL( timeout() ), this, SIGNAL( sigChanged() ) ); + connect( this, SIGNAL( sigChanged() ), tEmit, SLOT( stop() ) ); + + + NIFSKOPE_QSETTINGS(cfg); + cfg.beginGroup( "Render Settings" ); + + aDrawAxes = new QAction( "Draw &Axes", this ); + aDrawAxes->setToolTip( "draw xyz-Axes" ); + aDrawAxes->setCheckable( true ); + aDrawAxes->setChecked( cfg.value( "Draw AXes", true ).toBool() ); + connect( aDrawAxes, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + aDrawNodes = new QAction( "Draw &Nodes", this ); + aDrawNodes->setToolTip( "draw bones/nodes" ); + aDrawNodes->setCheckable( true ); + aDrawNodes->setChecked( cfg.value( "Draw Nodes", true ).toBool() ); + connect( aDrawNodes, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + aDrawHavok = new QAction( "Draw &Havok", this ); + aDrawHavok->setToolTip( "draw the havok shapes" ); + aDrawHavok->setCheckable( true ); + aDrawHavok->setChecked( cfg.value( "Draw Collision Geometry", true ).toBool() ); + connect( aDrawHavok, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + aDrawConstraints = new QAction( "Draw &Constraints", this ); + aDrawConstraints->setToolTip( "draw the havok constraints" ); + aDrawConstraints->setCheckable( true ); + aDrawConstraints->setChecked( cfg.value( "Draw Constraints", true ).toBool() ); + connect( aDrawConstraints, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + aDrawFurn = new QAction( "Draw &Furniture", this ); + aDrawFurn->setToolTip( "draw the furniture markers" ); + aDrawFurn->setCheckable( true ); + aDrawFurn->setChecked( cfg.value( "Draw Furniture Markers", true ).toBool() ); + connect( aDrawFurn, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + aDrawHidden = new QAction( "Show Hid&den", this ); + aDrawHidden->setToolTip( "always draw nodes and meshes" ); + aDrawHidden->setCheckable( true ); + aDrawHidden->setChecked( cfg.value( "Show Hidden Objects", false ).toBool() ); + connect( aDrawHidden, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + aDrawStats = new QAction( "Show S&tats", this ); + aDrawStats->setToolTip( "display some statistics about the selected node" ); + aDrawStats->setCheckable( true ); + aDrawStats->setChecked( cfg.value( "Show Stats", false ).toBool() ); + connect( aDrawStats, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + + dialog = new GroupBox( "", Qt::Vertical ); + dialog->setWindowTitle( "Render Settings" ); + dialog->setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); + dialog->installEventFilter( this ); + + aSettings = new QAction( "&Settings...", this ); + aSettings->setToolTip( "show the settings dialog" ); + connect( aSettings, SIGNAL( triggered() ), dialog, SLOT( show() ) ); + + + dialog->pushLayout( "Texture Folders", Qt::Vertical ); + dialog->pushLayout( Qt::Horizontal ); + +#ifdef Q_OS_WIN32 + dialog->pushLayout( "Auto Detect", Qt::Vertical ); + QButtonGroup * tfgamegrp = new QButtonGroup( this ); + connect( tfgamegrp, SIGNAL( buttonClicked( int ) ), this, SLOT( textureFolderAutoDetect() ) ); + int gameid = 0; + QPushButton * bt = new QPushButton( "Auto Detect\nGame Paths" ); + tfgamegrp->addButton( bt); + dialog->addWidget( bt ); + + dialog->popLayout(); +#endif + + dialog->pushLayout( "Custom", Qt::Vertical ); + dialog->pushLayout( Qt::Horizontal ); + + QButtonGroup * tfactgrp = new QButtonGroup( this ); + connect( tfactgrp, SIGNAL( buttonClicked( int ) ), this, SLOT( textureFolderAction( int ) ) ); + int tfaid = 0; + foreach ( QString tfaname, QStringList() << "Add Folder" << "Remove Folder" << "Move Up" ) + { + QPushButton * bt = new QPushButton( tfaname ); + TexFolderButtons[tfaid] = bt; + tfactgrp->addButton( bt, tfaid++ ); + dialog->addWidget( bt ); + } + + dialog->popLayout(); + + TexFolderModel = new QStringListModel( this ); + TexFolderModel->setStringList( cfg.value( "Texture Folders" ).toStringList() ); + connect( TexFolderModel, SIGNAL( rowsInserted( const QModelIndex &, int, int ) ), tEmit, SLOT( start() ) ); + connect( TexFolderModel, SIGNAL( rowsRemoved( const QModelIndex &, int, int ) ), tEmit, SLOT( start() ) ); + connect( TexFolderModel, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), tEmit, SLOT( start() ) ); + connect( TexFolderModel, SIGNAL( modelReset() ), tEmit, SLOT( start() ) ); + + TexFolderView = new SmallListView; + dialog->addWidget( TexFolderView, 0 ); + TexFolderView->setModel( TexFolderModel ); + TexFolderView->setCurrentIndex( TexFolderModel->index( 0, 0, QModelIndex() ) ); + + dialog->pushLayout( Qt::Horizontal ); + + TexFolderSelect = new FileSelector( FileSelector::Folder, "Folder", QBoxLayout::RightToLeft ); + dialog->addWidget( TexFolderSelect ); + + QDataWidgetMapper * TexFolderMapper = new QDataWidgetMapper( this ); + TexFolderMapper->setModel( TexFolderModel ); + TexFolderMapper->addMapping( TexFolderSelect, 0 ); + TexFolderMapper->setCurrentModelIndex( TexFolderView->currentIndex() ); + connect( TexFolderView->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ), + TexFolderMapper, SLOT( setCurrentModelIndex( const QModelIndex & ) ) ); + connect( TexFolderSelect, SIGNAL( sigActivated( const QString & ) ), TexFolderMapper, SLOT( submit() ) ); + + connect( TexFolderView->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ), + this, SLOT( textureFolderIndex( const QModelIndex & ) ) ); + textureFolderIndex( TexFolderView->currentIndex() ); + + dialog->addWidget( TexAlternatives = new QCheckBox( "&Look for alternatives" ) ); + TexAlternatives->setToolTip( "If a texture was nowhere to be found
NifSkope will start looking for alternatives.

texture.dds does not exist -> use texture.bmp instead

" ); + TexAlternatives->setChecked( cfg.value( "Texture Alternatives", true ).toBool() ); + connect( TexAlternatives, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + dialog->popLayout(); + dialog->popLayout(); + dialog->popLayout(); + dialog->popLayout(); + dialog->pushLayout( Qt::Horizontal ); + dialog->pushLayout( "Render", Qt::Vertical ); + + + dialog->addWidget( AntiAlias = new QCheckBox( "&Anti Aliasing" ) ); + AntiAlias->setToolTip( "Enable anti aliasing and anisotropic texture filtering if available.
You'll need to restart NifSkope for this setting to take effect.
" ); + AntiAlias->setChecked( cfg.value( "Anti Aliasing", true ).toBool() ); + connect( AntiAlias, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + dialog->addWidget( Textures = new QCheckBox( "&Textures" ) ); + Textures->setToolTip( "Enable textures" ); + Textures->setChecked( cfg.value( "Texturing", true ).toBool() ); + connect( Textures, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + dialog->addWidget( Shaders = new QCheckBox( "&Shaders" ) ); + Shaders->setToolTip( "Enable Shaders" ); + Shaders->setChecked( cfg.value( "Enable Shaders", true ).toBool() ); + connect( Shaders, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + + dialog->popLayout(); + dialog->pushLayout( "Up Axis", Qt::Vertical ); + + + dialog->addWidget( AxisX = new QRadioButton( "X" ) ); + dialog->addWidget( AxisY = new QRadioButton( "Y" ) ); + dialog->addWidget( AxisZ = new QRadioButton( "Z" ) ); + + QButtonGroup * btgrp = new QButtonGroup( this ); + btgrp->addButton( AxisX ); + btgrp->addButton( AxisY ); + btgrp->addButton( AxisZ ); + btgrp->setExclusive( true ); + + QString upax = cfg.value( "Up Axis", "Z" ).toString(); + if ( upax == "X" ) + AxisX->setChecked( true ); + else if ( upax == "Y" ) + AxisY->setChecked( true ); + else + AxisZ->setChecked( true ); + + connect( AxisX, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + connect( AxisY, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + connect( AxisZ, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + + dialog->popLayout(); + dialog->pushLayout( "Culling", Qt::Vertical, 1 ); + + dialog->addWidget( CullNoTex = new QCheckBox( "Cull &Non Textured" ) ); + CullNoTex->setToolTip( "Hide all meshes without textures" ); + CullNoTex->setChecked( cfg.value( "Cull Non Textured", false ).toBool() ); + connect( CullNoTex, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + dialog->addWidget( CullByID = new QCheckBox( "&Cull Nodes by Name" ) ); + CullByID->setToolTip( "Enabling this option hides some special nodes and meshes" ); + CullByID->setChecked( cfg.value( "Cull Nodes By Name", false ).toBool() ); + connect( CullByID, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + dialog->addWidget( CullExpr = new QLineEdit( cfg.value( "Cull Expression", "^collidee|^shadowcaster|^\\!LoD_cullme|^footprint" ).toString() ) ); + CullExpr->setToolTip( "Enter a regular expression. Nodes which names match the expression will be hidden" ); + CullExpr->setEnabled( CullByID->isChecked() ); + connect( CullExpr, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( sigChanged() ) ); + connect( CullByID, SIGNAL( toggled( bool ) ), CullExpr, SLOT( setEnabled( bool ) ) ); + + + dialog->popLayout(); + dialog->popLayout(); + dialog->pushLayout( "Light", Qt::Vertical ); + dialog->pushLayout( Qt::Horizontal ); + + + cfg.beginGroup( "Light0" ); + + QStringList lightNames( QStringList() << "Ambient" << "Diffuse" << "Specular" ); + QList lightDefaults( QList() << QColor::fromRgbF( .4, .4, .4 ) << QColor::fromRgbF( .8, .8, .8 ) << QColor::fromRgbF( 1, 1, 1 ) ); + for ( int l = 0; l < 3; l++ ) + { + ColorWheel * wheel = new ColorWheel( cfg.value( lightNames[l], lightDefaults[l] ).value() ); + wheel->setSizeHint( QSize( 105, 105 ) ); + connect( wheel, SIGNAL( sigColorEdited( const QColor & ) ), this, SIGNAL( sigChanged() ) ); + LightColor[l] = wheel; + + dialog->pushLayout( lightNames[l], Qt::Vertical ); + dialog->addWidget( wheel ); + dialog->popLayout(); + } + + dialog->popLayout(); + dialog->pushLayout( Qt::Horizontal ); + + dialog->addWidget( LightFrontal = new QCheckBox( "Frontal" ), 0 ); + LightFrontal->setToolTip( "Lock light to camera position" ); + LightFrontal->setChecked( cfg.value( "Frontal", true ).toBool() ); + connect( LightFrontal, SIGNAL( toggled( bool ) ), this, SIGNAL( sigChanged() ) ); + + QWidget * pos = dialog->pushLayout( "Position", Qt::Horizontal, 1 ); + pos->setDisabled( LightFrontal->isChecked() ); + connect( LightFrontal, SIGNAL( toggled( bool ) ), pos, SLOT( setDisabled( bool ) ) ); + + dialog->addWidget( new QLabel( "Declination" ) ); + dialog->addWidget( LightDeclination = new QSpinBox, 1 ); + LightDeclination->setMinimum( -180 ); + LightDeclination->setMaximum( +180 ); + LightDeclination->setSingleStep( 5 ); + LightDeclination->setWrapping( true ); + LightDeclination->setValue( cfg.value( "Declination", 0 ).toInt() ); + connect( LightDeclination, SIGNAL( valueChanged( int ) ), this, SIGNAL( sigChanged() ) ); + + dialog->addWidget( new QLabel( "Planar Angle" ) ); + dialog->addWidget( LightPlanarAngle = new QSpinBox, 1 ); + LightPlanarAngle->setMinimum( -180 ); + LightPlanarAngle->setMaximum( +180 ); + LightPlanarAngle->setSingleStep( 5 ); + LightPlanarAngle->setWrapping( true ); + LightPlanarAngle->setValue( cfg.value( "Planar Angle", 0 ).toInt() ); + connect( LightPlanarAngle, SIGNAL( valueChanged( int ) ), this, SIGNAL( sigChanged() ) ); + + cfg.endGroup(); + + dialog->popLayout(); + dialog->pushLayout( "Presets", Qt::Horizontal ); + + QButtonGroup * grp = new QButtonGroup( this ); + connect( grp, SIGNAL( buttonClicked( int ) ), this, SLOT( activateLightPreset( int ) ) ); + int psid = 0; + foreach ( QString psname, QStringList() << "Sunny Day" << "Dark Night" ) + { + QPushButton * bt = new QPushButton( psname ); + grp->addButton( bt, psid++ ); + dialog->addWidget( bt ); + } + + dialog->popLayout(); + dialog->popLayout(); + dialog->popLayout(); + dialog->pushLayout( "Colors", Qt::Horizontal ); + + + QStringList colorNames( QStringList() << "Background" << "Foreground" << "Highlight" ); + QList colorDefaults( QList() << QColor::fromRgb( 0, 0, 0 ) << QColor::fromRgb( 255, 255, 255 ) << QColor::fromRgb( 255, 255, 0 ) ); + for ( int c = 0; c < 3; c++ ) + { + dialog->pushLayout( colorNames[c], Qt::Horizontal ); + + ColorWheel * wheel = new ColorWheel( cfg.value( colorNames[c], colorDefaults[c] ).value() ); + wheel->setSizeHint( QSize( 105, 105 ) ); + connect( wheel, SIGNAL( sigColorEdited( const QColor & ) ), this, SIGNAL( sigChanged() ) ); + colors[ c ] = wheel; + dialog->addWidget( wheel ); + + if ( c != 0 ) + { + alpha[ c ] = new AlphaSlider( Qt::Vertical ); + alpha[ c ]->setValue( cfg.value( colorNames[c], colorDefaults[c] ).value().alphaF() ); + alpha[ c ]->setColor( wheel->getColor() ); + connect( alpha[ c ], SIGNAL( valueChanged( float ) ), this, SIGNAL( sigChanged() ) ); + connect( wheel, SIGNAL( sigColor( const QColor & ) ), alpha[ c ], SLOT( setColor( const QColor & ) ) ); + dialog->addWidget( alpha[ c ] ); + } + else + alpha[ c ] = 0; + + dialog->popLayout(); + } + dialog->popLayout(); + + //Misc Options + cfg.beginGroup( "Misc Settings" ); + + dialog->pushLayout( "Misc. Settings", Qt::Vertical, 1 ); + dialog->pushLayout( Qt::Horizontal ); + + + dialog->addWidget( new QLabel( "Startup Version" ) ); + dialog->addWidget( StartVer = new QLineEdit( cfg.value( "Startup Version", "20.0.0.5" ).toString() ) ); + StartVer->setToolTip( "This is the version that the initial 'blank' NIF file that is created when NifSkope opens will be." ); + connect( StartVer, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( sigChanged() ) ); + + + dialog->popLayout(); + + //More Misc options can be added here. + dialog->popLayout(); + cfg.endGroup(); +} + +Options::~Options() +{ + if ( tSave->isActive() ) + save(); +} + +Options * Options::get() +{ + static Options * options = new Options(); + return options; +} + +QList Options::actions() +{ + Options * opts = get(); + return QList() + << opts->aDrawAxes + << opts->aDrawNodes + << opts->aDrawHavok + << opts->aDrawConstraints + << opts->aDrawFurn + << opts->aDrawHidden +#ifdef USE_GL_QPAINTER + << opts->aDrawStats +#endif + << opts->aSettings; +} + +bool Options::eventFilter( QObject * o, QEvent * e ) +{ + if ( o == dialog && e->type() == QEvent::Close && tSave->isActive() ) + { + save(); + } + return false; +} + +void Options::save() +{ + tSave->stop(); + + NIFSKOPE_QSETTINGS(cfg); + + // remove obsolete keys + foreach ( QString key, QStringList() + << "texture folder" << "bg color" << "cull nodes by name" << "draw axis" << "draw furniture" + << "draw havok" << "draw hidden" << "draw nodes" << "draw only textured meshes" << "draw stats" + << "enable blending" << "enable shading" << "enable textures" << "highlight meshes" << "hl color" + << "rotate" ) + { + if ( cfg.contains( key ) ) + cfg.remove( key ); + } + + cfg.beginGroup( "Render Settings" ); + + cfg.setValue( "Texture Folders", textureFolders() ); + cfg.setValue( "Texture Alternatives", textureAlternatives() ); + + cfg.setValue( "Draw Axes", drawAxes() ); + cfg.setValue( "Draw Nodes", drawNodes() ); + cfg.setValue( "Draw Collision Geometry", drawHavok() ); + cfg.setValue( "Draw Constraints", drawConstraints() ); + cfg.setValue( "Draw Furniture Markers", drawFurn() ); + cfg.setValue( "Show Hidden Objects", drawHidden() ); + cfg.setValue( "Show Stats", drawStats() ); + + cfg.setValue( "Background", bgColor() ); + cfg.setValue( "Foreground", nlColor() ); + cfg.setValue( "Highlight", hlColor() ); + + cfg.setValue( "Anti Aliasing", antialias() ); + cfg.setValue( "Texturing", texturing() ); + cfg.setValue( "Enable Shaders", shaders() ); + + cfg.setValue( "Cull Nodes By Name", CullByID->isChecked() ); + cfg.setValue( "Cull Expression", CullExpr->text() ); + cfg.setValue( "Cull Non Textured", onlyTextured() ); + + cfg.setValue( "Up Axis", AxisX->isChecked() ? "X" : AxisY->isChecked() ? "Y" : "Z" ); + + cfg.beginGroup( "Light0" ); + cfg.setValue( "Ambient", ambient() ); + cfg.setValue( "Diffuse", diffuse() ); + cfg.setValue( "Specular", specular() ); + cfg.setValue( "Frontal", lightFrontal() ); + cfg.setValue( "Declination", lightDeclination() ); + cfg.setValue( "Planar Angle", lightPlanarAngle() ); + + cfg.endGroup(); + + cfg.beginGroup( "Misc Settings" ); + + cfg.setValue( "Startup Version", startupVersion() ); + + cfg.endGroup(); + + +} + +void Options::textureFolderAutoDetect() +{ + //List to hold all games paths that were detected + QStringList list; + + //Generic "same directory" path should always be added + list.append( "./" ); + + //String to hold the message box message + QString game_list; + +#ifdef Q_OS_WIN32 + + // Oblivion + { + QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Oblivion", QSettings::NativeFormat ); + QDir dir( reg.value( "Installed Path" ).toString() ); + if ( dir.exists() && dir.cd( "Data" ) ) + { + game_list.append( "TES4: Oblivion\n" ); + + list.append( dir.path() ); + + dir.setNameFilters( QStringList() << "*.bsa" ); + dir.setFilter( QDir::Dirs ); + foreach ( QString dn, dir.entryList() ) + list << dir.filePath( dn ); + +#ifndef FSENGINE + if ( ! dir.cd( "Textures" ) ) + { + QMessageBox::information( dialog, "NifSkope", + "

The texture folder was not found.

" + "

This may be because you haven't extracted the archive files yet.
" + "Here, it is explained how to do that.

" ); + } +#endif + } + } + + // Morrowind + { + QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Morrowind", QSettings::NativeFormat ); + QDir dir( reg.value( "Installed Path" ).toString() ); + if ( dir.exists() && dir.cd( "Data Files" ) ) + { + game_list.append( "TES3: Morrowind\n" ); + + list.append( dir.path() ); + list.append( dir.path() + "/Textures" ); + + dir.setNameFilters( QStringList() << "*.bsa" ); + dir.setFilter( QDir::Dirs ); + foreach ( QString dn, dir.entryList() ) + list << dir.filePath( dn ) << dir.filePath( dn ) + "/Textures"; + } + } + + // CIV IV + { + QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Firaxis Games\\Sid Meier's Civilization 4", QSettings::NativeFormat ); + QDir dir( reg.value( "INSTALLDIR" ).toString() ); + if ( dir.exists() && dir.cd( "Assets/Art/shared" ) ) + { + game_list.append( "Sid Meier's Civilization IV\n" ); + + list.append( dir.path() ); + } + } + + // Freedom Force + list.append( "./textures" ); + list.append( "./skins/standard" ); + { + QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\FFVTTR", QSettings::NativeFormat ); + QDir dir( reg.value( "InstallDir" ).toString() ); + if ( dir.exists() && dir.cd( "/Data/Art/library/area_specific/_textures" ) ) + { + game_list.append( "Freedom Force vs. the Third Reich\n" ); + list.append( dir.path() ); + } + } + { + QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\Freedom Force", QSettings::NativeFormat ); + QDir dir( reg.value( "InstallDir" ).toString() ); + if ( dir.exists() && dir.cd( "Data/Art/library/area_specific/_textures" ) ) + { + game_list.append( "Freedom Force\n" ); + + list.append( dir.path() ); + } + } + { + QSettings reg( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\Freedom Force Demo", QSettings::NativeFormat ); + QDir dir( reg.value( "InstallDir" ).toString() ); + if ( dir.exists() && dir.cd( "Data/Art/library/area_specific/_textures" ) ) { + game_list.append( "Freedom Force Demo\n" ); + + list.append( dir.path() ); + } + } +#endif + //Set folder list box to contain the newly detected textures, along with the ones the user has already defined, ignoring any duplicates + + //Remove duplicates + QStringList finalList = TexFolderModel->stringList(); + for( QStringList::ConstIterator it = list.begin(); it != list.end(); ++it ) { + if ( finalList.contains( *it ) == false ) + finalList << *it; + } + + TexFolderModel->setStringList( finalList ); + TexAlternatives->setChecked( false ); + TexFolderView->setCurrentIndex( TexFolderModel->index( 0, 0, QModelIndex() ) ); + + //Announce result to user + if ( game_list.size() == 0 ) + { + game_list = QString("No supported games were detected.\nYour game may still work, you will just have to set the folders manually until an auto-detect routine is created."); + } + else + { + game_list = QString("Successfully detected the following games:\n") + game_list; + } + QMessageBox::information( dialog, "NifSkope", game_list ); + + +} + +void Options::textureFolderAction( int id ) +{ + QModelIndex idx = TexFolderView->currentIndex(); + switch ( id ) + { + case 0: + // add folder + TexFolderModel->insertRow( 0, QModelIndex() ); + TexFolderModel->setData( TexFolderModel->index( 0, 0, QModelIndex() ), "Choose a folder", Qt::EditRole ); + TexFolderView->setCurrentIndex( TexFolderModel->index( 0, 0, QModelIndex() ) ); + break; + case 1: + if ( idx.isValid() ) + { // remove folder + TexFolderModel->removeRow( idx.row(), QModelIndex() ); + } + break; + case 2: + if ( idx.isValid() && idx.row() > 0 ) + { // move up + QModelIndex xdi = idx.sibling( idx.row() - 1, 0 ); + QVariant v = TexFolderModel->data( idx, Qt::EditRole ); + TexFolderModel->setData( idx, TexFolderModel->data( xdi, Qt::EditRole ), Qt::EditRole ); + TexFolderModel->setData( xdi, v, Qt::EditRole ); + TexFolderView->setCurrentIndex( xdi ); + } + break; + } +} + +void Options::textureFolderIndex( const QModelIndex & idx ) +{ + TexFolderSelect->setEnabled( idx.isValid() ); + TexFolderButtons[0]->setEnabled( true ); + TexFolderButtons[1]->setEnabled( idx.isValid() ); + TexFolderButtons[2]->setEnabled( idx.isValid() && ( idx.row() > 0 ) ); +} + +void Options::activateLightPreset( int id ) +{ + switch ( id ) + { + case 0: // sunny day + LightColor[0]->setColor( QColor::fromRgbF( 0.4, 0.4, 0.4 ) ); + LightColor[1]->setColor( QColor::fromRgbF( 0.8, 0.8, 0.8 ) ); + LightColor[2]->setColor( QColor::fromRgbF( 1.0, 1.0, 1.0 ) ); + break; + case 1: // dark night + LightColor[0]->setColor( QColor::fromRgbF( 0.2, 0.2, 0.25 ) ); + LightColor[1]->setColor( QColor::fromRgbF( 0.1, 0.1, 0.1 ) ); + LightColor[2]->setColor( QColor::fromRgbF( 0.1, 0.1, 0.1 ) ); + break; + default: + return; + } + emit sigChanged(); +} + + +QStringList Options::textureFolders() +{ + return get()->TexFolderModel->stringList(); +} + +bool Options::textureAlternatives() +{ + return get()->TexAlternatives->isChecked(); +} + +Options::Axis Options::upAxis() +{ + return get()->AxisX->isChecked() ? XAxis : get()->AxisY->isChecked() ? YAxis : ZAxis; +} + +bool Options::antialias() +{ + return get()->AntiAlias->isChecked(); +} + +bool Options::texturing() +{ + return get()->Textures->isChecked(); +} + +bool Options::shaders() +{ + return get()->Shaders->isChecked(); +} + + +bool Options::drawAxes() +{ + return get()->aDrawAxes->isChecked(); +} + +bool Options::drawNodes() +{ + return get()->aDrawNodes->isChecked(); +} + +bool Options::drawHavok() +{ + return get()->aDrawHavok->isChecked(); +} + +bool Options::drawConstraints() +{ + return get()->aDrawConstraints->isChecked(); +} + +bool Options::drawFurn() +{ + return get()->aDrawFurn->isChecked(); +} + +bool Options::drawHidden() +{ + return get()->aDrawHidden->isChecked(); +} + +bool Options::drawStats() +{ + return get()->aDrawStats->isChecked(); +} + +bool Options::benchmark() +{ + return false; +} + + +QColor Options::bgColor() +{ + return get()->colors[ 0 ]->getColor(); +} + +QColor Options::nlColor() +{ + QColor c = get()->colors[ 1 ]->getColor(); + c.setAlphaF( get()->alpha[ 1 ]->value() ); + return c; +} + +QColor Options::hlColor() +{ + QColor c = get()->colors[ 2 ]->getColor(); + c.setAlphaF( get()->alpha[ 2 ]->value() ); + return c; +} + + +QRegExp Options::cullExpression() +{ + return get()->CullByID->isChecked() ? QRegExp( get()->CullExpr->text() ) : QRegExp(); +} + +bool Options::onlyTextured() +{ + return get()->CullNoTex->isChecked(); +} + + +QColor Options::ambient() +{ + return get()->LightColor[ 0 ]->getColor(); +} + +QColor Options::diffuse() +{ + return get()->LightColor[ 1 ]->getColor(); +} + +QColor Options::specular() +{ + return get()->LightColor[ 2 ]->getColor(); +} + +bool Options::lightFrontal() +{ + return get()->LightFrontal->isChecked(); +} + +int Options::lightDeclination() +{ + return get()->LightDeclination->value(); +} + +int Options::lightPlanarAngle() +{ + return get()->LightPlanarAngle->value(); +} + +QString Options::startupVersion() +{ + return get()->StartVer->text(); +}; + + diff --git a/options.h b/options.h index b54c0234e..515e6cb82 100644 --- a/options.h +++ b/options.h @@ -1,174 +1,174 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef OPTIONS_H -#define OPTIONS_H - -#include - -class QAbstractButton; -class QAction; -class QColor; -class QCheckBox; -class QDialog; -class QLineEdit; -class QListView; -class QModelIndex; -class QRadioButton; -class QSpinBox; -class QStringListModel; -class QTimer; - -class AlphaSlider; -class ColorWheel; -class FileSelector; -class GroupBox; - -class Options : public QObject -{ - Q_OBJECT -public: - static Options * get(); - static QList actions(); - - static QStringList textureFolders(); - static bool textureAlternatives(); - - static bool antialias(); - static bool texturing(); - static bool shaders(); - - static bool blending() { return true; } - - static QColor bgColor(); - static QColor nlColor(); - static QColor hlColor(); - - static QRegExp cullExpression(); - static bool onlyTextured(); - - static bool drawAxes(); - static bool drawNodes(); - static bool drawHavok(); - static bool drawConstraints(); - static bool drawFurn(); - static bool drawHidden(); - static bool drawStats(); - - static bool benchmark(); - - typedef enum { - ZAxis, YAxis, XAxis - } Axis; - - static Axis upAxis(); - - static QColor ambient(); - static QColor diffuse(); - static QColor specular(); - - static bool lightFrontal(); - static int lightDeclination(); - static int lightPlanarAngle(); - - static QString startupVersion(); - -signals: - void sigChanged(); - -public slots: - void save(); - -protected slots: - void textureFolderAction( int ); - void textureFolderIndex( const QModelIndex & ); - void textureFolderAutoDetect(); - void activateLightPreset( int ); - -protected: - Options(); - ~Options(); - - bool eventFilter( QObject * o, QEvent * e ); - - QAction * aDrawAxes; - QAction * aDrawNodes; - QAction * aDrawHavok; - QAction * aDrawConstraints; - QAction * aDrawFurn; - QAction * aDrawHidden; - QAction * aDrawStats; - - QAction * aSettings; - - GroupBox * dialog; - - QStringListModel * TexFolderModel; - QListView * TexFolderView; - FileSelector * TexFolderSelect; - QCheckBox * TexAlternatives; - QAbstractButton * TexFolderButtons[3]; - - ColorWheel * colors[3]; - AlphaSlider * alpha[3]; - - QCheckBox * AntiAlias; - QCheckBox * Textures; - QCheckBox * Shaders; - - QCheckBox * CullNoTex; - QCheckBox * CullByID; - QLineEdit * CullExpr; - - QRadioButton * AxisX; - QRadioButton * AxisY; - QRadioButton * AxisZ; - - ColorWheel * LightColor[3]; - - QCheckBox * LightFrontal; - QSpinBox * LightDeclination; - QSpinBox * LightPlanarAngle; - - - QTimer * tSave, * tEmit; - - //Misc Optoins - QLineEdit * StartVer; - -}; - -#define glNormalColor() glColor( Color4( Options::nlColor() ) ) -#define glHighlightColor() glColor( Color4( Options::hlColor() ) ) - - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef OPTIONS_H +#define OPTIONS_H + +#include + +class QAbstractButton; +class QAction; +class QColor; +class QCheckBox; +class QDialog; +class QLineEdit; +class QListView; +class QModelIndex; +class QRadioButton; +class QSpinBox; +class QStringListModel; +class QTimer; + +class AlphaSlider; +class ColorWheel; +class FileSelector; +class GroupBox; + +class Options : public QObject +{ + Q_OBJECT +public: + static Options * get(); + static QList actions(); + + static QStringList textureFolders(); + static bool textureAlternatives(); + + static bool antialias(); + static bool texturing(); + static bool shaders(); + + static bool blending() { return true; } + + static QColor bgColor(); + static QColor nlColor(); + static QColor hlColor(); + + static QRegExp cullExpression(); + static bool onlyTextured(); + + static bool drawAxes(); + static bool drawNodes(); + static bool drawHavok(); + static bool drawConstraints(); + static bool drawFurn(); + static bool drawHidden(); + static bool drawStats(); + + static bool benchmark(); + + typedef enum { + ZAxis, YAxis, XAxis + } Axis; + + static Axis upAxis(); + + static QColor ambient(); + static QColor diffuse(); + static QColor specular(); + + static bool lightFrontal(); + static int lightDeclination(); + static int lightPlanarAngle(); + + static QString startupVersion(); + +signals: + void sigChanged(); + +public slots: + void save(); + +protected slots: + void textureFolderAction( int ); + void textureFolderIndex( const QModelIndex & ); + void textureFolderAutoDetect(); + void activateLightPreset( int ); + +protected: + Options(); + ~Options(); + + bool eventFilter( QObject * o, QEvent * e ); + + QAction * aDrawAxes; + QAction * aDrawNodes; + QAction * aDrawHavok; + QAction * aDrawConstraints; + QAction * aDrawFurn; + QAction * aDrawHidden; + QAction * aDrawStats; + + QAction * aSettings; + + GroupBox * dialog; + + QStringListModel * TexFolderModel; + QListView * TexFolderView; + FileSelector * TexFolderSelect; + QCheckBox * TexAlternatives; + QAbstractButton * TexFolderButtons[3]; + + ColorWheel * colors[3]; + AlphaSlider * alpha[3]; + + QCheckBox * AntiAlias; + QCheckBox * Textures; + QCheckBox * Shaders; + + QCheckBox * CullNoTex; + QCheckBox * CullByID; + QLineEdit * CullExpr; + + QRadioButton * AxisX; + QRadioButton * AxisY; + QRadioButton * AxisZ; + + ColorWheel * LightColor[3]; + + QCheckBox * LightFrontal; + QSpinBox * LightDeclination; + QSpinBox * LightPlanarAngle; + + + QTimer * tSave, * tEmit; + + //Misc Optoins + QLineEdit * StartVer; + +}; + +#define glNormalColor() glColor( Color4( Options::nlColor() ) ) +#define glHighlightColor() glColor( Color4( Options::hlColor() ) ) + + +#endif diff --git a/spellbook.cpp b/spellbook.cpp index a0d7a6c5d..1a97e3d71 100644 --- a/spellbook.cpp +++ b/spellbook.cpp @@ -1,271 +1,271 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "spellbook.h" - -#include -#include - -QList & SpellBook::spells() -{ // construct-on-first-use wrapper - static QList * _spells = new QList(); - return *_spells; -} - -QList & SpellBook::books() -{ - static QList * _books = new QList(); - return *_books; -} - -QMultiHash & SpellBook::hash() -{ - static QMultiHash * _hash = new QMultiHash(); - return *_hash; -} - -QList & SpellBook::instants() -{ - static QList * _instants = new QList(); - return *_instants; -} - -QList & SpellBook::sanitizers() -{ - static QList * _sanitizers = new QList(); - return *_sanitizers; -} - -SpellBook::SpellBook( NifModel * nif, const QModelIndex & index, QObject * receiver, const char * member ) : QMenu(), Nif( 0 ) -{ - setTitle( "Spells" ); - - // register this book in the library - books().append( this ); - - // attach this book to the specified nif - sltNif( nif ); - - // fill in the known spells - foreach ( Spell * spell, spells() ) - newSpellRegistered( spell ); - - // set the current index - sltIndex( index ); - - connect( this, SIGNAL( triggered( QAction * ) ), this, SLOT( sltSpellTriggered( QAction * ) ) ); - - if ( receiver && member ) - connect( this, SIGNAL( sigIndex( const QModelIndex & ) ), receiver, member ); -} - -SpellBook::~SpellBook() -{ - books().removeAll( this ); -} - -void SpellBook::cast( NifModel * nif, const QModelIndex & index, Spell * spell ) -{ - if ( spell && spell->isApplicable( nif, index ) ) - emit sigIndex( spell->cast( nif, index ) ); -} - -void SpellBook::sltSpellTriggered( QAction * action ) -{ - Spell * spell = Map.value( action ); - cast( Nif, Index, spell ); -} - -void SpellBook::sltNif( NifModel * nif ) -{ - if ( Nif ) - disconnect( Nif, SIGNAL( modelReset() ), this, SLOT( checkActions() ) ); - Nif = nif; - Index = QModelIndex(); - if ( Nif ) - connect( Nif, SIGNAL( modelReset() ), this, SLOT( checkActions() ) ); -} - -void SpellBook::sltIndex( const QModelIndex & index ) -{ - if ( index.model() == Nif ) - Index = index; - else - Index = QModelIndex(); - checkActions(); -} - -void SpellBook::checkActions() -{ - checkActions( this, QString() ); -} - -void SpellBook::checkActions( QMenu * menu, const QString & page ) -{ - bool menuEnable = false; - foreach ( QAction * action, menu->actions() ) - { - if ( action->menu() ) - { - checkActions( action->menu(), action->menu()->title() ); - menuEnable |= action->menu()->isEnabled(); - action->setVisible( action->menu()->isEnabled() ); - } - else - { - foreach ( Spell * spell, spells() ) - { - if ( action->text() == spell->name() && page == spell->page() ) - { - bool actionEnable = Nif && spell->isApplicable( Nif, Index ); - action->setVisible( actionEnable ); - action->setEnabled( actionEnable ); - menuEnable |= actionEnable; - } - } - } - } - menu->setEnabled( menuEnable ); -} - -void SpellBook::newSpellRegistered( Spell * spell ) -{ - if ( spell->page().isEmpty() ) - { - Map.insert( addAction( spell->icon(), spell->name() ), spell ); - } - else - { - QMenu * menu = 0; - foreach ( QAction * action, actions() ) - { - if ( action->menu() && action->menu()->title() == spell->page() ) - { - menu = action->menu(); - break; - } - } - if ( ! menu ) - { - menu = new QMenu( spell->page() ); - addMenu( menu ); - } - QAction * act = menu->addAction( spell->icon(), spell->name() ); - act->setShortcut( spell->hotkey() ); - Map.insert( act, spell ); - } -} - -void SpellBook::registerSpell( Spell * spell ) -{ - spells().append( spell ); - hash().insertMulti( spell->name(), spell ); - - if ( spell->instant() ) - instants().append( spell ); - if ( spell->sanity() ) - sanitizers().append( spell ); - - foreach ( SpellBook * book, books() ) - { - book->newSpellRegistered( spell ); - } -} - -Spell * SpellBook::lookup( const QString & id ) -{ - if ( id.isEmpty() ) - return 0; - - QString page; - QString name = id; - - if ( id.contains( "/" ) ) - { - QStringList split = id.split( "/" ); - page = split.value( 0 ); - name = split.value( 1 ); - } - - foreach ( Spell * spell, hash().values( name ) ) - { - if ( spell->page() == page ) - return spell; - } - - return 0; -} - -Spell * SpellBook::lookup( const QKeySequence & hotkey ) -{ - if ( hotkey.isEmpty() ) - return 0; - foreach ( Spell * spell, spells() ) - if ( spell->hotkey() == hotkey ) - return spell; - return 0; -} - -Spell * SpellBook::instant( const NifModel * nif, const QModelIndex & index ) -{ - foreach ( Spell * spell, instants() ) - { - if ( spell->isApplicable( nif, index ) ) - return spell; - } - return 0; -} - -QModelIndex SpellBook::sanitize( NifModel * nif ) -{ - QPersistentModelIndex ridx; - - foreach ( Spell * spell, sanitizers() ) - { - if ( spell->isApplicable( nif, QModelIndex() ) ) - { - QModelIndex idx = spell->cast( nif, QModelIndex() ); - if ( idx.isValid() && ! ridx.isValid() ) - ridx = idx; - } - } - - return ridx; -} - -QAction * SpellBook::exec( const QPoint & pos, QAction * act ) -{ - if ( isEnabled() ) - return QMenu::exec( pos, act ); - else - return 0; -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "spellbook.h" + +#include +#include + +QList & SpellBook::spells() +{ // construct-on-first-use wrapper + static QList * _spells = new QList(); + return *_spells; +} + +QList & SpellBook::books() +{ + static QList * _books = new QList(); + return *_books; +} + +QMultiHash & SpellBook::hash() +{ + static QMultiHash * _hash = new QMultiHash(); + return *_hash; +} + +QList & SpellBook::instants() +{ + static QList * _instants = new QList(); + return *_instants; +} + +QList & SpellBook::sanitizers() +{ + static QList * _sanitizers = new QList(); + return *_sanitizers; +} + +SpellBook::SpellBook( NifModel * nif, const QModelIndex & index, QObject * receiver, const char * member ) : QMenu(), Nif( 0 ) +{ + setTitle( "Spells" ); + + // register this book in the library + books().append( this ); + + // attach this book to the specified nif + sltNif( nif ); + + // fill in the known spells + foreach ( Spell * spell, spells() ) + newSpellRegistered( spell ); + + // set the current index + sltIndex( index ); + + connect( this, SIGNAL( triggered( QAction * ) ), this, SLOT( sltSpellTriggered( QAction * ) ) ); + + if ( receiver && member ) + connect( this, SIGNAL( sigIndex( const QModelIndex & ) ), receiver, member ); +} + +SpellBook::~SpellBook() +{ + books().removeAll( this ); +} + +void SpellBook::cast( NifModel * nif, const QModelIndex & index, Spell * spell ) +{ + if ( spell && spell->isApplicable( nif, index ) ) + emit sigIndex( spell->cast( nif, index ) ); +} + +void SpellBook::sltSpellTriggered( QAction * action ) +{ + Spell * spell = Map.value( action ); + cast( Nif, Index, spell ); +} + +void SpellBook::sltNif( NifModel * nif ) +{ + if ( Nif ) + disconnect( Nif, SIGNAL( modelReset() ), this, SLOT( checkActions() ) ); + Nif = nif; + Index = QModelIndex(); + if ( Nif ) + connect( Nif, SIGNAL( modelReset() ), this, SLOT( checkActions() ) ); +} + +void SpellBook::sltIndex( const QModelIndex & index ) +{ + if ( index.model() == Nif ) + Index = index; + else + Index = QModelIndex(); + checkActions(); +} + +void SpellBook::checkActions() +{ + checkActions( this, QString() ); +} + +void SpellBook::checkActions( QMenu * menu, const QString & page ) +{ + bool menuEnable = false; + foreach ( QAction * action, menu->actions() ) + { + if ( action->menu() ) + { + checkActions( action->menu(), action->menu()->title() ); + menuEnable |= action->menu()->isEnabled(); + action->setVisible( action->menu()->isEnabled() ); + } + else + { + foreach ( Spell * spell, spells() ) + { + if ( action->text() == spell->name() && page == spell->page() ) + { + bool actionEnable = Nif && spell->isApplicable( Nif, Index ); + action->setVisible( actionEnable ); + action->setEnabled( actionEnable ); + menuEnable |= actionEnable; + } + } + } + } + menu->setEnabled( menuEnable ); +} + +void SpellBook::newSpellRegistered( Spell * spell ) +{ + if ( spell->page().isEmpty() ) + { + Map.insert( addAction( spell->icon(), spell->name() ), spell ); + } + else + { + QMenu * menu = 0; + foreach ( QAction * action, actions() ) + { + if ( action->menu() && action->menu()->title() == spell->page() ) + { + menu = action->menu(); + break; + } + } + if ( ! menu ) + { + menu = new QMenu( spell->page() ); + addMenu( menu ); + } + QAction * act = menu->addAction( spell->icon(), spell->name() ); + act->setShortcut( spell->hotkey() ); + Map.insert( act, spell ); + } +} + +void SpellBook::registerSpell( Spell * spell ) +{ + spells().append( spell ); + hash().insertMulti( spell->name(), spell ); + + if ( spell->instant() ) + instants().append( spell ); + if ( spell->sanity() ) + sanitizers().append( spell ); + + foreach ( SpellBook * book, books() ) + { + book->newSpellRegistered( spell ); + } +} + +Spell * SpellBook::lookup( const QString & id ) +{ + if ( id.isEmpty() ) + return 0; + + QString page; + QString name = id; + + if ( id.contains( "/" ) ) + { + QStringList split = id.split( "/" ); + page = split.value( 0 ); + name = split.value( 1 ); + } + + foreach ( Spell * spell, hash().values( name ) ) + { + if ( spell->page() == page ) + return spell; + } + + return 0; +} + +Spell * SpellBook::lookup( const QKeySequence & hotkey ) +{ + if ( hotkey.isEmpty() ) + return 0; + foreach ( Spell * spell, spells() ) + if ( spell->hotkey() == hotkey ) + return spell; + return 0; +} + +Spell * SpellBook::instant( const NifModel * nif, const QModelIndex & index ) +{ + foreach ( Spell * spell, instants() ) + { + if ( spell->isApplicable( nif, index ) ) + return spell; + } + return 0; +} + +QModelIndex SpellBook::sanitize( NifModel * nif ) +{ + QPersistentModelIndex ridx; + + foreach ( Spell * spell, sanitizers() ) + { + if ( spell->isApplicable( nif, QModelIndex() ) ) + { + QModelIndex idx = spell->cast( nif, QModelIndex() ); + if ( idx.isValid() && ! ridx.isValid() ) + ridx = idx; + } + } + + return ridx; +} + +QAction * SpellBook::exec( const QPoint & pos, QAction * act ) +{ + if ( isEnabled() ) + return QMenu::exec( pos, act ); + else + return 0; +} diff --git a/spellbook.h b/spellbook.h index f56c9b7db..61bc8e84b 100644 --- a/spellbook.h +++ b/spellbook.h @@ -1,127 +1,127 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef SPELLBOOK_H -#define SPELLBOOK_H - -#include "nifmodel.h" - -#include -#include - -#define REGISTER_SPELL( SPELL ) static Librarian __##SPELL##__ ( new SPELL ); - -class Spell -{ -public: - Spell() {} - virtual ~Spell() {} - - virtual QString name() const = 0; - virtual QString page() const { return QString(); } - virtual QString hint() const { return QString(); } - virtual QIcon icon() const { return QIcon(); } - virtual bool instant() const { return false; } - virtual bool sanity() const { return false; } - virtual QKeySequence hotkey() const { return QKeySequence(); } - - virtual bool isApplicable( const NifModel * nif, const QModelIndex & index ) = 0; - - virtual QModelIndex cast( NifModel * nif, const QModelIndex & index ) = 0; - - void castIfApplicable( NifModel * nif, const QModelIndex & index ) - { - if ( isApplicable( nif, index ) ) - cast( nif, index ); - } - - static QString tr( const char * key ) { return QCoreApplication::translate( "Spell", key ); } -}; - -class SpellBook : public QMenu -{ - Q_OBJECT -public: - SpellBook( NifModel * nif, const QModelIndex & index = QModelIndex(), QObject * receiver = 0, const char * member = 0 ); - ~SpellBook(); - - QAction * exec( const QPoint & pos, QAction * act = 0 ); - - static void registerSpell( Spell * spell ); - - static Spell * lookup( const QString & id ); - static Spell * lookup( const QKeySequence & hotkey ); - static Spell * instant( const NifModel * nif, const QModelIndex & index ); - - static QModelIndex sanitize( NifModel * nif ); - -public slots: - void sltNif( NifModel * nif ); - - void sltIndex( const QModelIndex & index ); - - void cast( NifModel * nif, const QModelIndex & index, Spell * spell ); - - void checkActions(); - -signals: - void sigIndex( const QModelIndex & index ); - -protected slots: - void sltSpellTriggered( QAction * action ); - -protected: - NifModel * Nif; - QPersistentModelIndex Index; - QMap Map; - - void newSpellRegistered( Spell * spell ); - void checkActions( QMenu * menu, const QString & page ); - -private: - static QList & spells(); - static QList & books(); - static QMultiHash & hash(); - static QList & instants(); - static QList & sanitizers(); -}; - -class Librarian -{ -public: - Librarian( Spell * spell ) - { - SpellBook::registerSpell( spell ); - } -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef SPELLBOOK_H +#define SPELLBOOK_H + +#include "nifmodel.h" + +#include +#include + +#define REGISTER_SPELL( SPELL ) static Librarian __##SPELL##__ ( new SPELL ); + +class Spell +{ +public: + Spell() {} + virtual ~Spell() {} + + virtual QString name() const = 0; + virtual QString page() const { return QString(); } + virtual QString hint() const { return QString(); } + virtual QIcon icon() const { return QIcon(); } + virtual bool instant() const { return false; } + virtual bool sanity() const { return false; } + virtual QKeySequence hotkey() const { return QKeySequence(); } + + virtual bool isApplicable( const NifModel * nif, const QModelIndex & index ) = 0; + + virtual QModelIndex cast( NifModel * nif, const QModelIndex & index ) = 0; + + void castIfApplicable( NifModel * nif, const QModelIndex & index ) + { + if ( isApplicable( nif, index ) ) + cast( nif, index ); + } + + static QString tr( const char * key ) { return QCoreApplication::translate( "Spell", key ); } +}; + +class SpellBook : public QMenu +{ + Q_OBJECT +public: + SpellBook( NifModel * nif, const QModelIndex & index = QModelIndex(), QObject * receiver = 0, const char * member = 0 ); + ~SpellBook(); + + QAction * exec( const QPoint & pos, QAction * act = 0 ); + + static void registerSpell( Spell * spell ); + + static Spell * lookup( const QString & id ); + static Spell * lookup( const QKeySequence & hotkey ); + static Spell * instant( const NifModel * nif, const QModelIndex & index ); + + static QModelIndex sanitize( NifModel * nif ); + +public slots: + void sltNif( NifModel * nif ); + + void sltIndex( const QModelIndex & index ); + + void cast( NifModel * nif, const QModelIndex & index, Spell * spell ); + + void checkActions(); + +signals: + void sigIndex( const QModelIndex & index ); + +protected slots: + void sltSpellTriggered( QAction * action ); + +protected: + NifModel * Nif; + QPersistentModelIndex Index; + QMap Map; + + void newSpellRegistered( Spell * spell ); + void checkActions( QMenu * menu, const QString & page ); + +private: + static QList & spells(); + static QList & books(); + static QMultiHash & hash(); + static QList & instants(); + static QList & sanitizers(); +}; + +class Librarian +{ +public: + Librarian( Spell * spell ) + { + SpellBook::registerSpell( spell ); + } +}; + +#endif diff --git a/spells/animation.cpp b/spells/animation.cpp index 697594406..f70e445fa 100644 --- a/spells/animation.cpp +++ b/spells/animation.cpp @@ -1,268 +1,268 @@ -#include "../spellbook.h" - -#include -#include - -class spAttachKf : public Spell -{ -public: - QString name() const { return Spell::tr("Attach .KF"); } - QString page() const { return Spell::tr("Animation"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif && ! index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - try - { - QString kfname = QFileDialog::getOpenFileName( 0, Spell::tr("Choose a .kf file"), nif->getFolder(), "*.kf" ); - - if ( kfname.isEmpty() ) - return index; - - NifModel kf; - - QFile kffile( kfname ); - if ( ! kffile.open( QFile::ReadOnly ) ) - throw QString( Spell::tr("failed to open .kf %1") ).arg( kfname ); - - if ( ! kf.load( kffile ) ) - throw QString( Spell::tr("failed to load .kf from file %1") ).arg( kfname ); - - QPersistentModelIndex iRoot; - - foreach ( qint32 l, kf.getRootLinks() ) - { - QModelIndex iSeq = kf.getBlock( l, "NiControllerSequence" ); - if ( ! iSeq.isValid() ) - throw QString( Spell::tr("this is not a normal .kf file; there should be only NiControllerSequences as root blocks") ); - - QString rootName = kf.get( iSeq, "Target Name" ); - QModelIndex ir = findRootTarget( nif, rootName ); - - if ( ! ir.isValid() ) - throw QString( Spell::tr("couldn't find the animation's root node (%1)") ).arg( rootName ); - - if ( ! iRoot.isValid() ) - iRoot = ir; - else if ( iRoot != ir ) - throw QString( Spell::tr("the animation root nodes differ; bailing out...") ); - } - - QPersistentModelIndex iMultiTransformer = findController( nif, iRoot, "NiMultiTargetTransformController" ); - QPersistentModelIndex iCtrlManager = findController( nif, iRoot, "NiControllerManager" ); - - QList seqLinks = kf.getRootLinks(); - QStringList missingNodes; - - foreach ( qint32 lSeq, kf.getRootLinks() ) - { - QModelIndex iSeq = kf.getBlock( lSeq, "NiControllerSequence" ); - - QList< QPersistentModelIndex > controlledNodes; - - QModelIndex iCtrlBlcks = kf.getIndex( iSeq, "Controlled Blocks" ); - for ( int r = 0; r < kf.rowCount( iCtrlBlcks ); r++ ) - { - QString nodeName = kf.string( iCtrlBlcks.child( r, 0 ), "Node Name" ); - if (nodeName.isEmpty()) { - QModelIndex iNodeName = kf.getIndex( iCtrlBlcks.child( r, 0 ), "Node Name Offset" ); - nodeName = iNodeName.sibling( iNodeName.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); - } - QModelIndex iCtrlNode = findChildNode( nif, iRoot, nodeName ); - if ( iCtrlNode.isValid() ) - { - if ( ! controlledNodes.contains( iCtrlNode ) ) - controlledNodes.append( iCtrlNode ); - } - else - { - if ( ! missingNodes.contains( nodeName ) ) - missingNodes << nodeName; - } - } - - if ( ! iMultiTransformer.isValid() ) - iMultiTransformer = attachController( nif, iRoot, "NiMultiTargetTransformController" ); - if ( ! iCtrlManager.isValid() ) - iCtrlManager = attachController( nif, iRoot, "NiControllerManager" ); - - setLinkArray( nif, iMultiTransformer, "Extra Targets", controlledNodes ); - - QPersistentModelIndex iObjPalette = nif->getBlock( nif->getLink( iCtrlManager, "Object Palette" ), "NiDefaultAVObjectPalette" ); - if ( ! iObjPalette.isValid() ) - { - iObjPalette = nif->insertNiBlock( "NiDefaultAVObjectPalette", nif->getBlockNumber( iCtrlManager ) + 1 ); - nif->setLink( iCtrlManager, "Object Palette", nif->getBlockNumber( iObjPalette ) ); - } - - setNameLinkArray( nif, iObjPalette, "Objs", controlledNodes ); - } - - QMap map = kf.moveAllNiBlocks( nif ); - - foreach ( qint32 lSeq, seqLinks ) - { - qint32 nSeq = map.value( lSeq ); - int numSeq = nif->get( iCtrlManager, "Num Controller Sequences" ); - nif->set( iCtrlManager, "Num Controller Sequences", numSeq+1 ); - nif->updateArray( iCtrlManager, "Controller Sequences" ); - nif->setLink( nif->getIndex( iCtrlManager, "Controller Sequences" ).child( numSeq, 0 ), nSeq ); - QModelIndex iSeq = nif->getBlock( nSeq, "NiControllerSequence" ); - nif->setLink( iSeq, "Manager", nif->getBlockNumber( iCtrlManager ) ); - } - - if ( ! missingNodes.isEmpty() ) - { - qWarning() << Spell::tr("The following controlled nodes were not found in the nif:"); - foreach ( QString nn, missingNodes ) - qWarning() << nn; - } - - return iRoot; - } - catch ( QString e ) - { - qWarning( e.toAscii() ); - } - return index; - } - - static QModelIndex findChildNode( const NifModel * nif, const QModelIndex & parent, const QString & name ) - { - if ( ! nif->inherits( parent, "NiAVObject" ) ) - return QModelIndex(); - - if ( nif->get( parent, "Name" ) == name ) - return parent; - - foreach ( qint32 l, nif->getChildLinks( nif->getBlockNumber( parent ) ) ) - { - QModelIndex child = findChildNode( nif, nif->getBlock( l ), name ); - if ( child.isValid() ) - return child; - } - - return QModelIndex(); - } - - static QModelIndex findRootTarget( const NifModel * nif, const QString & name ) - { - foreach ( qint32 l, nif->getRootLinks() ) - { - QModelIndex root = findChildNode( nif, nif->getBlock( l ), name ); - if ( root.isValid() ) - return root; - } - - return QModelIndex(); - } - - static QModelIndex findController( const NifModel * nif, const QModelIndex & node, const QString & ctrltype ) - { - foreach ( qint32 l, nif->getChildLinks( nif->getBlockNumber( node ) ) ) - { - QModelIndex iCtrl = nif->getBlock( l, "NiTimeController" ); - if ( iCtrl.isValid() ) - { - if ( nif->inherits( iCtrl, ctrltype ) ) - return iCtrl; - else - { - iCtrl = findController( nif, iCtrl, ctrltype ); - if ( iCtrl.isValid() ) - return iCtrl; - } - } - } - return QModelIndex(); - } - - static QModelIndex attachController( NifModel * nif, const QPersistentModelIndex & iNode, const QString & ctrltype ) - { - QModelIndex iCtrl = nif->insertNiBlock( ctrltype, nif->getBlockNumber( iNode ) + 1 ); - if ( ! iCtrl.isValid() ) - return QModelIndex(); - - qint32 oldctrl = nif->getLink( iNode, "Controller" ); - nif->setLink( iNode, "Controller", nif->getBlockNumber( iCtrl ) ); - nif->setLink( iCtrl, "Next Controller", oldctrl ); - nif->setLink( iCtrl, "Target", nif->getBlockNumber( iNode ) ); - nif->set( iCtrl, "Flags", 8 ); - - return iCtrl; - } - - static void setLinkArray( NifModel * nif, const QModelIndex & iParent, const QString & array, const QList< QPersistentModelIndex > & iBlocks ) - { - QModelIndex iNum = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); - QModelIndex iArray = nif->getIndex( iParent, array ); - - if ( ! iNum.isValid() || ! iArray.isValid() ) - throw QString( Spell::tr("array %1 not found") ).arg( array ); - - QVector links = nif->getLinkArray( iArray ); - - foreach ( QModelIndex iBlock, iBlocks ) - if ( ! links.contains( nif->getBlockNumber( iBlock ) ) ) - links.append( nif->getBlockNumber( iBlock ) ); - - nif->set( iNum, links.count() ); - nif->updateArray( iArray ); - nif->setLinkArray( iArray, links ); - } - - static void setNameLinkArray( NifModel * nif, const QModelIndex & iParent, const QString & array, const QList< QPersistentModelIndex > & iBlocks ) - { - QModelIndex iNum = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); - QModelIndex iArray = nif->getIndex( iParent, array ); - - if ( ! iNum.isValid() || ! iArray.isValid() ) - throw QString( Spell::tr("array %1 not found") ).arg( array ); - - QList< QPersistentModelIndex > blocksToAdd; - - foreach ( QPersistentModelIndex idx, iBlocks ) - { - QString name = nif->get( idx, "Name" ); - int r; - for ( r = 0; r < nif->rowCount( iArray ); r++ ) - { - if ( nif->get( iArray.child( r, 0 ), "Name" ) == name ) - break; - } - if ( r == nif->rowCount( iArray ) ) - blocksToAdd << idx; - } - - int r = nif->get( iNum ); - nif->set( iNum, r + blocksToAdd.count() ); - nif->updateArray( iArray ); - foreach ( QPersistentModelIndex idx, blocksToAdd ) - { - nif->set( iArray.child( r, 0 ), "Name", nif->get( idx, "Name" ) ); - nif->setLink( iArray.child( r, 0 ), "AV Object", nif->getBlockNumber( idx ) ); - r++; - } - } -}; - -REGISTER_SPELL( spAttachKf ) - -class spConvertQuatsToEulers : public Spell -{ -public: - QString name() const { return Spell::tr("Convert Quat- to ZYX-Rotations"); } - QString page() const { return Spell::tr("Animation"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iBlock = nif->getBlock( index, "NiKeyframeData" ); - return iBlock.isValid() && nif->get( iBlock, "Rotation Type" ) != 4; - } - - -}; +#include "../spellbook.h" + +#include +#include + +class spAttachKf : public Spell +{ +public: + QString name() const { return Spell::tr("Attach .KF"); } + QString page() const { return Spell::tr("Animation"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif && ! index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + try + { + QString kfname = QFileDialog::getOpenFileName( 0, Spell::tr("Choose a .kf file"), nif->getFolder(), "*.kf" ); + + if ( kfname.isEmpty() ) + return index; + + NifModel kf; + + QFile kffile( kfname ); + if ( ! kffile.open( QFile::ReadOnly ) ) + throw QString( Spell::tr("failed to open .kf %1") ).arg( kfname ); + + if ( ! kf.load( kffile ) ) + throw QString( Spell::tr("failed to load .kf from file %1") ).arg( kfname ); + + QPersistentModelIndex iRoot; + + foreach ( qint32 l, kf.getRootLinks() ) + { + QModelIndex iSeq = kf.getBlock( l, "NiControllerSequence" ); + if ( ! iSeq.isValid() ) + throw QString( Spell::tr("this is not a normal .kf file; there should be only NiControllerSequences as root blocks") ); + + QString rootName = kf.get( iSeq, "Target Name" ); + QModelIndex ir = findRootTarget( nif, rootName ); + + if ( ! ir.isValid() ) + throw QString( Spell::tr("couldn't find the animation's root node (%1)") ).arg( rootName ); + + if ( ! iRoot.isValid() ) + iRoot = ir; + else if ( iRoot != ir ) + throw QString( Spell::tr("the animation root nodes differ; bailing out...") ); + } + + QPersistentModelIndex iMultiTransformer = findController( nif, iRoot, "NiMultiTargetTransformController" ); + QPersistentModelIndex iCtrlManager = findController( nif, iRoot, "NiControllerManager" ); + + QList seqLinks = kf.getRootLinks(); + QStringList missingNodes; + + foreach ( qint32 lSeq, kf.getRootLinks() ) + { + QModelIndex iSeq = kf.getBlock( lSeq, "NiControllerSequence" ); + + QList< QPersistentModelIndex > controlledNodes; + + QModelIndex iCtrlBlcks = kf.getIndex( iSeq, "Controlled Blocks" ); + for ( int r = 0; r < kf.rowCount( iCtrlBlcks ); r++ ) + { + QString nodeName = kf.string( iCtrlBlcks.child( r, 0 ), "Node Name" ); + if (nodeName.isEmpty()) { + QModelIndex iNodeName = kf.getIndex( iCtrlBlcks.child( r, 0 ), "Node Name Offset" ); + nodeName = iNodeName.sibling( iNodeName.row(), NifModel::ValueCol ).data( Qt::DisplayRole ).toString(); + } + QModelIndex iCtrlNode = findChildNode( nif, iRoot, nodeName ); + if ( iCtrlNode.isValid() ) + { + if ( ! controlledNodes.contains( iCtrlNode ) ) + controlledNodes.append( iCtrlNode ); + } + else + { + if ( ! missingNodes.contains( nodeName ) ) + missingNodes << nodeName; + } + } + + if ( ! iMultiTransformer.isValid() ) + iMultiTransformer = attachController( nif, iRoot, "NiMultiTargetTransformController" ); + if ( ! iCtrlManager.isValid() ) + iCtrlManager = attachController( nif, iRoot, "NiControllerManager" ); + + setLinkArray( nif, iMultiTransformer, "Extra Targets", controlledNodes ); + + QPersistentModelIndex iObjPalette = nif->getBlock( nif->getLink( iCtrlManager, "Object Palette" ), "NiDefaultAVObjectPalette" ); + if ( ! iObjPalette.isValid() ) + { + iObjPalette = nif->insertNiBlock( "NiDefaultAVObjectPalette", nif->getBlockNumber( iCtrlManager ) + 1 ); + nif->setLink( iCtrlManager, "Object Palette", nif->getBlockNumber( iObjPalette ) ); + } + + setNameLinkArray( nif, iObjPalette, "Objs", controlledNodes ); + } + + QMap map = kf.moveAllNiBlocks( nif ); + + foreach ( qint32 lSeq, seqLinks ) + { + qint32 nSeq = map.value( lSeq ); + int numSeq = nif->get( iCtrlManager, "Num Controller Sequences" ); + nif->set( iCtrlManager, "Num Controller Sequences", numSeq+1 ); + nif->updateArray( iCtrlManager, "Controller Sequences" ); + nif->setLink( nif->getIndex( iCtrlManager, "Controller Sequences" ).child( numSeq, 0 ), nSeq ); + QModelIndex iSeq = nif->getBlock( nSeq, "NiControllerSequence" ); + nif->setLink( iSeq, "Manager", nif->getBlockNumber( iCtrlManager ) ); + } + + if ( ! missingNodes.isEmpty() ) + { + qWarning() << Spell::tr("The following controlled nodes were not found in the nif:"); + foreach ( QString nn, missingNodes ) + qWarning() << nn; + } + + return iRoot; + } + catch ( QString e ) + { + qWarning( e.toAscii() ); + } + return index; + } + + static QModelIndex findChildNode( const NifModel * nif, const QModelIndex & parent, const QString & name ) + { + if ( ! nif->inherits( parent, "NiAVObject" ) ) + return QModelIndex(); + + if ( nif->get( parent, "Name" ) == name ) + return parent; + + foreach ( qint32 l, nif->getChildLinks( nif->getBlockNumber( parent ) ) ) + { + QModelIndex child = findChildNode( nif, nif->getBlock( l ), name ); + if ( child.isValid() ) + return child; + } + + return QModelIndex(); + } + + static QModelIndex findRootTarget( const NifModel * nif, const QString & name ) + { + foreach ( qint32 l, nif->getRootLinks() ) + { + QModelIndex root = findChildNode( nif, nif->getBlock( l ), name ); + if ( root.isValid() ) + return root; + } + + return QModelIndex(); + } + + static QModelIndex findController( const NifModel * nif, const QModelIndex & node, const QString & ctrltype ) + { + foreach ( qint32 l, nif->getChildLinks( nif->getBlockNumber( node ) ) ) + { + QModelIndex iCtrl = nif->getBlock( l, "NiTimeController" ); + if ( iCtrl.isValid() ) + { + if ( nif->inherits( iCtrl, ctrltype ) ) + return iCtrl; + else + { + iCtrl = findController( nif, iCtrl, ctrltype ); + if ( iCtrl.isValid() ) + return iCtrl; + } + } + } + return QModelIndex(); + } + + static QModelIndex attachController( NifModel * nif, const QPersistentModelIndex & iNode, const QString & ctrltype ) + { + QModelIndex iCtrl = nif->insertNiBlock( ctrltype, nif->getBlockNumber( iNode ) + 1 ); + if ( ! iCtrl.isValid() ) + return QModelIndex(); + + qint32 oldctrl = nif->getLink( iNode, "Controller" ); + nif->setLink( iNode, "Controller", nif->getBlockNumber( iCtrl ) ); + nif->setLink( iCtrl, "Next Controller", oldctrl ); + nif->setLink( iCtrl, "Target", nif->getBlockNumber( iNode ) ); + nif->set( iCtrl, "Flags", 8 ); + + return iCtrl; + } + + static void setLinkArray( NifModel * nif, const QModelIndex & iParent, const QString & array, const QList< QPersistentModelIndex > & iBlocks ) + { + QModelIndex iNum = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); + QModelIndex iArray = nif->getIndex( iParent, array ); + + if ( ! iNum.isValid() || ! iArray.isValid() ) + throw QString( Spell::tr("array %1 not found") ).arg( array ); + + QVector links = nif->getLinkArray( iArray ); + + foreach ( QModelIndex iBlock, iBlocks ) + if ( ! links.contains( nif->getBlockNumber( iBlock ) ) ) + links.append( nif->getBlockNumber( iBlock ) ); + + nif->set( iNum, links.count() ); + nif->updateArray( iArray ); + nif->setLinkArray( iArray, links ); + } + + static void setNameLinkArray( NifModel * nif, const QModelIndex & iParent, const QString & array, const QList< QPersistentModelIndex > & iBlocks ) + { + QModelIndex iNum = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); + QModelIndex iArray = nif->getIndex( iParent, array ); + + if ( ! iNum.isValid() || ! iArray.isValid() ) + throw QString( Spell::tr("array %1 not found") ).arg( array ); + + QList< QPersistentModelIndex > blocksToAdd; + + foreach ( QPersistentModelIndex idx, iBlocks ) + { + QString name = nif->get( idx, "Name" ); + int r; + for ( r = 0; r < nif->rowCount( iArray ); r++ ) + { + if ( nif->get( iArray.child( r, 0 ), "Name" ) == name ) + break; + } + if ( r == nif->rowCount( iArray ) ) + blocksToAdd << idx; + } + + int r = nif->get( iNum ); + nif->set( iNum, r + blocksToAdd.count() ); + nif->updateArray( iArray ); + foreach ( QPersistentModelIndex idx, blocksToAdd ) + { + nif->set( iArray.child( r, 0 ), "Name", nif->get( idx, "Name" ) ); + nif->setLink( iArray.child( r, 0 ), "AV Object", nif->getBlockNumber( idx ) ); + r++; + } + } +}; + +REGISTER_SPELL( spAttachKf ) + +class spConvertQuatsToEulers : public Spell +{ +public: + QString name() const { return Spell::tr("Convert Quat- to ZYX-Rotations"); } + QString page() const { return Spell::tr("Animation"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iBlock = nif->getBlock( index, "NiKeyframeData" ); + return iBlock.isValid() && nif->get( iBlock, "Rotation Type" ) != 4; + } + + +}; diff --git a/spells/blocks.cpp b/spells/blocks.cpp index 73030c62c..f95c6dc8f 100644 --- a/spells/blocks.cpp +++ b/spells/blocks.cpp @@ -1,784 +1,784 @@ -#include "blocks.h" -#include "../config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -static void addLink( NifModel * nif, QModelIndex iParent, QString array, int link ) -{ - QModelIndex iSize = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); - QModelIndex iArray = nif->getIndex( iParent, array ); - if ( iSize.isValid() && iArray.isValid() ) - { - int numlinks = nif->get( iSize ); - nif->set( iSize, numlinks + 1 ); - nif->updateArray( iArray ); - nif->setLink( iArray.child( numlinks, 0 ), link ); - } -} - -static void delLink( NifModel * nif, QModelIndex iParent, QString array, int link ) -{ - QModelIndex iSize = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); - QModelIndex iArray = nif->getIndex( iParent, array ); - QList links = nif->getLinkArray( iArray ).toList(); - if ( iSize.isValid() && iArray.isValid() && links.contains( link ) ) - { - links.removeAll( link ); - nif->set( iSize, links.count() ); - nif->updateArray( iArray ); - nif->setLinkArray( iArray, links.toVector() ); - } -} - -static void blockLink( NifModel * nif, const QModelIndex & index, const QModelIndex & iBlock ) -{ - if ( nif->isLink( index ) && nif->inherits( iBlock, nif->itemTmplt( index ) ) ) - { - nif->setLink( index, nif->getBlockNumber( iBlock ) ); - } - if ( nif->inherits( index, "NiNode" ) && nif->inherits( iBlock, "NiAVObject" ) ) - { - addLink( nif, index, "Children", nif->getBlockNumber( iBlock ) ); - } - else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiProperty" ) ) - { - addLink( nif, index, "Properties", nif->getBlockNumber( iBlock ) ); - } - else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiExtraData" ) ) - { - addLink( nif, index, "Extra Data List", nif->getBlockNumber( iBlock ) ); - } -} - -class spInsertBlock : public Spell -{ -public: - QString name() const { return "Insert"; } - QString page() const { return "Block"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ( ! index.isValid() || ! index.parent().isValid() ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QStringList ids = nif->allNiBlocks(); - ids.sort(); - - QMap< QString, QMenu *> map; - foreach ( QString id, ids ) - { - QString x( "Other" ); - - if ( id.startsWith( "Ni" ) ) - x = QString("Ni&") + id.mid( 2, 1 ) + "..."; - if ( id.startsWith( "bhk" ) || id.startsWith( "hk" ) ) - x = "Havok"; - if ( id.startsWith( "BS" ) || id == "AvoidNode" || id == "RootCollisionNode" ) - x = "Bethesda"; - if ( id.startsWith( "Fx" ) ) - x = "Firaxis"; - - if ( ! map.contains( x ) ) - map[ x ] = new QMenu( x ); - map[ x ]->addAction( id ); - } - - QMenu menu; - foreach ( QMenu * m, map ) - menu.addMenu( m ); - - QAction * act = menu.exec( QCursor::pos() ); - if ( act ) - return nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); - else - return index; - } -}; - -REGISTER_SPELL( spInsertBlock ) - - -class spAttachProperty : public Spell -{ -public: - QString name() const { return "Attach Property"; } - QString page() const { return "Node"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->itemType( index ) == "NiBlock" && nif->inherits( index, "NiAVObject" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QMenu menu; - QStringList ids = nif->allNiBlocks(); - ids.sort(); - foreach ( QString id, ids ) - if ( nif->inherits( id, "NiProperty" ) ) - menu.addAction( id ); - if ( menu.actions().isEmpty() ) - return index; - QAction * act = menu.exec( QCursor::pos() ); - if ( act ) - { - QPersistentModelIndex iParent = index; - QModelIndex iProperty = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); - addLink( nif, iParent, "Properties", nif->getBlockNumber( iProperty ) ); - return iProperty; - } - else - return index; - } -}; - -REGISTER_SPELL( spAttachProperty ) - - -class spAttachNode : public Spell -{ -public: - QString name() const { return "Attach Node"; } - QString page() const { return "Node"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index ) && nif->inherits( index, "NiNode" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QMenu menu; - QStringList ids = nif->allNiBlocks(); - ids.sort(); - foreach ( QString id, ids ) - if ( nif->inherits( id, "NiAVObject" ) && ! nif->inherits( id, "NiLight" ) ) - menu.addAction( id ); - - QAction * act = menu.exec( QCursor::pos() ); - if ( act ) - { - QPersistentModelIndex iParent = index; - QModelIndex iNode = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); - addLink( nif, iParent, "Children", nif->getBlockNumber( iNode ) ); - return iNode; - } - else - return index; - } -}; - -REGISTER_SPELL( spAttachNode ) - - -class spAttachLight : public Spell -{ -public: - QString name() const { return "Attach Light"; } - QString page() const { return "Node"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index ) && nif->inherits( index, "NiNode" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QMenu menu; - QStringList ids = nif->allNiBlocks(); - ids.sort(); - foreach ( QString id, ids ) - if ( nif->inherits( id, "NiLight" ) ) - menu.addAction( id ); - - QAction * act = menu.exec( QCursor::pos() ); - if ( act ) - { - QPersistentModelIndex iParent = index; - QModelIndex iLight = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); - addLink( nif, iParent, "Children", nif->getBlockNumber( iLight ) ); - addLink( nif, iParent, "Effects", nif->getBlockNumber( iLight ) ); - return iLight; - } - else - return index; - } -}; - -REGISTER_SPELL( spAttachLight ) - - -class spAttachExtraData : public Spell -{ -public: - QString name() const { return "Attach Extra Data"; } - QString page() const { return "Node"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index ) && nif->inherits( index, "NiObjectNET" ) && nif->checkVersion( 0x0a000100, 0 ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QMenu menu; - QStringList ids = nif->allNiBlocks(); - ids.sort(); - foreach ( QString id, ids ) - if ( nif->inherits( id, "NiExtraData" ) ) - menu.addAction( id ); - QAction * act = menu.exec( QCursor::pos() ); - if ( act ) - { - QPersistentModelIndex iParent = index; - QModelIndex iExtra = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); - addLink( nif, iParent, "Extra Data List", nif->getBlockNumber( iExtra ) ); - return iExtra; - } - else - return index; - } -}; - -REGISTER_SPELL( spAttachExtraData ) - - -class spRemoveBlock : public Spell -{ -public: - QString name() const { return "Remove"; } - QString page() const { return "Block"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index ) && nif->getBlockNumber( index ) >= 0; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - nif->removeNiBlock( nif->getBlockNumber( index ) ); - return QModelIndex(); - } -}; - -REGISTER_SPELL( spRemoveBlock ) - - -class spCopyBlock : public Spell -{ -public: - QString name() const { return "Copy"; } - QString page() const { return "Block"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QByteArray data; - QBuffer buffer( & data ); - if ( buffer.open( QIODevice::WriteOnly ) && nif->save( buffer, index ) ) - { - QMimeData * mime = new QMimeData; - mime->setData( QString( "nifskope/niblock/%1/%2" ).arg( nif->itemName( index ) ).arg( nif->getVersion() ), data ); - QApplication::clipboard()->setMimeData( mime ); - } - return index; - } -}; - -REGISTER_SPELL( spCopyBlock ) - - -class spPasteBlock : public Spell -{ -public: - QString name() const { return "Paste"; } - QString page() const { return "Block"; } - - QString acceptFormat( const QString & format, const NifModel * nif ) - { - QStringList split = format.split( "/" ); - if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "niblock" && nif->isNiBlock( split.value( 2 ) ) ) - return split.value( 3 ); - return QString(); - } - - QString blockType( const QString & format ) - { - return format.split( "/" ).value( 2 ); - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - const QMimeData * mime = QApplication::clipboard()->mimeData(); - if ( mime ) - foreach ( QString form, mime->formats() ) - if ( ! acceptFormat( form, nif ).isEmpty() ) - return true; - return false; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - const QMimeData * mime = QApplication::clipboard()->mimeData(); - if ( mime ) - { - foreach ( QString form, mime->formats() ) - { - QString version = acceptFormat( form, nif ); - if ( ! version.isEmpty() && ( version == nif->getVersion() || QMessageBox::question( 0, "Paste Block", QString( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ).arg( nif->getVersion() ).arg( version ), "Continue", "Cancel" ) == 0 )) - { - QByteArray data = mime->data( form ); - QBuffer buffer( & data ); - if ( buffer.open( QIODevice::ReadOnly ) ) - { - QModelIndex block = nif->insertNiBlock( blockType( form ), nif->getBlockCount() ); - nif->load( buffer, block ); - blockLink( nif, index, block ); - return block; - } - } - } - } - return QModelIndex(); - } -}; - -REGISTER_SPELL( spPasteBlock ) - - -class spPasteOverBlock : public Spell -{ -public: - QString name() const { return "Paste Over"; } - QString page() const { return "Block"; } - - QString acceptFormat( const QString & format, const NifModel * nif, const QModelIndex & block ) - { - QStringList split = format.split( "/" ); - if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "niblock" && nif->isNiBlock( block, split.value( 2 ) ) ) - return split.value( 3 ); - return QString(); - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - const QMimeData * mime = QApplication::clipboard()->mimeData(); - if ( mime ) - foreach ( QString form, mime->formats() ) - if ( ! acceptFormat( form, nif, index ).isEmpty() ) - return true; - return false; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - const QMimeData * mime = QApplication::clipboard()->mimeData(); - if ( mime ) - { - foreach ( QString form, mime->formats() ) - { - QString version = acceptFormat( form, nif, index ); - if ( ! version.isEmpty() && ( version == nif->getVersion() || QMessageBox::question( 0, "Paste Over", QString( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ).arg( nif->getVersion() ).arg( version ), "Continue", "Cancel" ) == 0 )) - { - QByteArray data = mime->data( form ); - QBuffer buffer( & data ); - if ( buffer.open( QIODevice::ReadOnly ) ) - { - nif->load( buffer, index ); - return index; - } - } - } - } - return QModelIndex(); - } -}; - -REGISTER_SPELL( spPasteOverBlock ) - - -class spCopyBranch : public Spell -{ -public: - QString name() const { return "Copy Branch"; } - QString page() const { return "Block"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QList blocks; - populateBlocks( blocks, nif, nif->getBlockNumber( index ) ); - - QMap blockMap; - for ( int b = 0; b < blocks.count(); b++ ) - blockMap.insert( blocks[b], b ); - - QMap parentMap; - foreach ( qint32 block, blocks ) - { - foreach ( qint32 link, nif->getParentLinks( block ) ) - { - if ( ! blocks.contains( link ) && ! parentMap.contains( link ) ) - { - QModelIndex iParent = nif->getBlock( link ); - if ( iParent.isValid() ) - { - QString name = nif->get( iParent, "Name" ); - if ( ! name.isEmpty() ) - { - parentMap.insert( link, nif->itemName( iParent ) + "|" + name ); - continue; - } - } - qWarning() << "failed to map parent link" << link << "for block" << block << nif->itemName( nif->getBlock( block ) ); - return index; - } - } - } - - QByteArray data; - QBuffer buffer( & data ); - if ( buffer.open( QIODevice::WriteOnly ) ) - { - QDataStream ds( & buffer ); - ds << blocks.count(); - ds << blockMap; - ds << parentMap; - foreach ( qint32 block, blocks ) - { - ds << nif->itemName( nif->getBlock( block ) ); - if ( ! nif->save( buffer, nif->getBlock( block ) ) ) - { - qWarning() << "failed to save block" << block << nif->itemName( nif->getBlock( block ) ); - return index; - } - } - QMimeData * mime = new QMimeData; - mime->setData( QString( "nifskope/nibranch/%1" ).arg( nif->getVersion() ), data ); - QApplication::clipboard()->setMimeData( mime ); - } - return index; - } - - void populateBlocks( QList & blocks, NifModel * nif, qint32 block ) - { - if ( ! blocks.contains( block ) ) blocks.append( block ); - foreach ( qint32 link, nif->getChildLinks( block ) ) - populateBlocks( blocks, nif, link ); - } -}; - -REGISTER_SPELL( spCopyBranch ) - - -class spPasteBranch : public Spell -{ -public: - QString name() const { return "Paste Branch"; } - QString page() const { return "Block"; } - - QString acceptFormat( const QString & format, const NifModel * nif ) - { - QStringList split = format.split( "/" ); - if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "nibranch" ) - return split.value( 2 ); - else - return QString(); - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - if ( index.isValid() && ! nif->isNiBlock( index ) && ! nif->isLink( index ) ) - return false; - const QMimeData * mime = QApplication::clipboard()->mimeData(); - if ( mime ) - foreach ( QString form, mime->formats() ) - if ( nif->isVersionSupported( nif->version2number( acceptFormat( form, nif ) ) ) ) - return true; - return false; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - const QMimeData * mime = QApplication::clipboard()->mimeData(); - if ( mime ) - { - foreach ( QString form, mime->formats() ) - { - QString v = acceptFormat( form, nif ); - if ( ! v.isEmpty() && ( v == nif->getVersion() || QMessageBox::question( 0, "Paste Branch", QString( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ).arg( nif->getVersion() ).arg( v ), "Continue", "Cancel" ) == 0 ) ) - { - QByteArray data = mime->data( form ); - QBuffer buffer( & data ); - if ( buffer.open( QIODevice::ReadOnly ) ) - { - QDataStream ds( & buffer ); - - int count; - ds >> count; - - QMap blockMap; - ds >> blockMap; - QMutableMapIterator ibm( blockMap ); - while ( ibm.hasNext() ) - { - ibm.next(); - ibm.value() += nif->getBlockCount(); - } - - QMap parentMap; - ds >> parentMap; - - QMapIterator ipm( parentMap ); - while ( ipm.hasNext() ) - { - ipm.next(); - qint32 block = getBlockByName( nif, ipm.value() ); - if ( block >= 0 ) - { - blockMap.insert( ipm.key(), block ); - } - else - { - qWarning() << "failed to map parent link" << ipm.value(); - return index; - } - } - - QModelIndex iRoot; - - for ( int c = 0; c < count; c++ ) - { - QString type; - ds >> type; - - QModelIndex block = nif->insertNiBlock( type, -1 ); - if ( ! nif->loadAndMapLinks( buffer, block, blockMap ) ) - return index; - if ( c == 0 ) - iRoot = block; - } - - blockLink( nif, index, iRoot ); - - return iRoot; - } - } - } - } - return QModelIndex(); - } - - qint32 getBlockByName( NifModel * nif, const QString & tn ) - { - QStringList ls = tn.split( "|" ); - QString type = ls.value( 0 ); - QString name = ls.value( 1 ); - if ( type.isEmpty() || name.isEmpty() ) - return -1; - for ( int b = 0; b < nif->getBlockCount(); b++ ) - { - QModelIndex iBlock = nif->getBlock( b ); - if ( nif->itemName( iBlock ) == type && nif->get( iBlock, "Name" ) == name ) - return b; - } - return -1; - } -}; - -REGISTER_SPELL( spPasteBranch ) - - - static void removeChildren( NifModel * nif, const QPersistentModelIndex & iBlock ) - { - QList iChildren; - foreach ( quint32 link, nif->getChildLinks( nif->getBlockNumber( iBlock ) ) ) - iChildren.append( nif->getBlock( link ) ); - - foreach ( QPersistentModelIndex iChild, iChildren ) - if ( iChild.isValid() && nif->getBlockNumber( iBlock ) == nif->getParent( nif->getBlockNumber( iChild ) ) ) - removeChildren( nif, iChild ); - - foreach ( QPersistentModelIndex iChild, iChildren ) - if ( iChild.isValid() && nif->getBlockNumber( iBlock ) == nif->getParent( nif->getBlockNumber( iChild ) ) ) - nif->removeNiBlock( nif->getBlockNumber( iChild ) ); - } - -bool spRemoveBranch::isApplicable( const NifModel * nif, const QModelIndex & iBlock ) -{ - int ix = nif->getBlockNumber( iBlock ); - return ( nif->isNiBlock( iBlock ) && ix >= 0 && ( nif->getRootLinks().contains( ix ) || nif->getParent( ix ) >= 0 ) ); -} - -QModelIndex spRemoveBranch::cast( NifModel * nif, const QModelIndex & index ) -{ - QPersistentModelIndex iBlock = index; - removeChildren( nif, iBlock ); - nif->removeNiBlock( nif->getBlockNumber( iBlock ) ); - return QModelIndex(); -} - -REGISTER_SPELL( spRemoveBranch ) - - -class spFlattenBranch : public Spell -{ -public: - QString name() const { return "Flatten Branch"; } - QString page() const { return "Block"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ), "NiNode" ); - return nif->inherits( index, "NiNode" ) && iParent.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & iNode ) - { - QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( iNode ) ), "NiNode" ); - doNode( nif, iNode, iParent, Transform() ); - return iNode; - } - - void doNode( NifModel * nif, const QModelIndex & iNode, const QModelIndex & iParent, Transform tp ) - { - if ( ! nif->inherits( iNode, "NiNode" ) ) - return; - - Transform t = tp * Transform( nif, iNode ); - - QList links; - - foreach ( qint32 l, nif->getLinkArray( iNode, "Children" ) ) - { - QModelIndex iChild = nif->getBlock( l ); - if ( nif->getParent( nif->getBlockNumber( iChild ) ) == nif->getBlockNumber( iNode ) ) - { - Transform tc = t * Transform( nif, iChild ); - tc.writeBack( nif, iChild ); - addLink( nif, iParent, "Children", l ); - delLink( nif, iNode, "Children", l ); - links.append( l ); - } - } - - foreach ( qint32 l, links ) - { - doNode( nif, nif->getBlock( l, "NiNode" ), iParent, tp ); - } - } -}; - -REGISTER_SPELL( spFlattenBranch ) - - -class spMoveBlockUp : public Spell -{ -public: - QString name() const { return "Move Up"; } - QString page() const { return "Block"; } - QKeySequence hotkey() const { return QKeySequence( Qt::CTRL + Qt::Key_Up ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index ) && nif->getBlockNumber( index ) > 0; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) - { - int ix = nif->getBlockNumber( iBlock ); - nif->moveNiBlock( ix, ix - 1 ); - return nif->getBlock( ix - 1 ); - } -}; - -REGISTER_SPELL( spMoveBlockUp ) - - -class spMoveBlockDown : public Spell -{ -public: - QString name() const { return "Move Down"; } - QString page() const { return "Block"; } - QKeySequence hotkey() const { return QKeySequence( Qt::CTRL + Qt::Key_Down ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index ) && nif->getBlockNumber( index ) < nif->getBlockCount() - 1; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) - { - int ix = nif->getBlockNumber( iBlock ); - nif->moveNiBlock( ix, ix + 1 ); - return nif->getBlock( ix + 1 ); - } -}; - -REGISTER_SPELL( spMoveBlockDown ) - - -class spRemoveBlocksById : public Spell -{ -public: - QString name() const { return "Remove By Id"; } - QString page() const { return "Block"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ! index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & ) - { - NIFSKOPE_QSETTINGS(settings); - settings.beginGroup( "spells" ); - settings.beginGroup( page() ); - settings.beginGroup( name() ); - - bool ok = true; - QString match = QInputDialog::getText( 0, "Remove Blocks by Id", "Enter a regular expression:", QLineEdit::Normal, - settings.value( "match expression", "^BS|^NiBS|^bhk|^hk" ).toString(), & ok ); - - if ( ! ok ) - return QModelIndex(); - - settings.setValue( "match expression", match ); - - QRegExp exp( match ); - - int n = 0; - while ( n < nif->getBlockCount() ) - { - QModelIndex iBlock = nif->getBlock( n ); - if ( nif->itemName( iBlock ).indexOf( exp ) >= 0 ) - nif->removeNiBlock( n ); - else - n++; - } - - return QModelIndex(); - } -}; - -REGISTER_SPELL( spRemoveBlocksById ) - +#include "blocks.h" +#include "../config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static void addLink( NifModel * nif, QModelIndex iParent, QString array, int link ) +{ + QModelIndex iSize = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); + QModelIndex iArray = nif->getIndex( iParent, array ); + if ( iSize.isValid() && iArray.isValid() ) + { + int numlinks = nif->get( iSize ); + nif->set( iSize, numlinks + 1 ); + nif->updateArray( iArray ); + nif->setLink( iArray.child( numlinks, 0 ), link ); + } +} + +static void delLink( NifModel * nif, QModelIndex iParent, QString array, int link ) +{ + QModelIndex iSize = nif->getIndex( iParent, QString( "Num %1" ).arg( array ) ); + QModelIndex iArray = nif->getIndex( iParent, array ); + QList links = nif->getLinkArray( iArray ).toList(); + if ( iSize.isValid() && iArray.isValid() && links.contains( link ) ) + { + links.removeAll( link ); + nif->set( iSize, links.count() ); + nif->updateArray( iArray ); + nif->setLinkArray( iArray, links.toVector() ); + } +} + +static void blockLink( NifModel * nif, const QModelIndex & index, const QModelIndex & iBlock ) +{ + if ( nif->isLink( index ) && nif->inherits( iBlock, nif->itemTmplt( index ) ) ) + { + nif->setLink( index, nif->getBlockNumber( iBlock ) ); + } + if ( nif->inherits( index, "NiNode" ) && nif->inherits( iBlock, "NiAVObject" ) ) + { + addLink( nif, index, "Children", nif->getBlockNumber( iBlock ) ); + } + else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiProperty" ) ) + { + addLink( nif, index, "Properties", nif->getBlockNumber( iBlock ) ); + } + else if ( nif->inherits( index, "NiAVObject" ) && nif->inherits( iBlock, "NiExtraData" ) ) + { + addLink( nif, index, "Extra Data List", nif->getBlockNumber( iBlock ) ); + } +} + +class spInsertBlock : public Spell +{ +public: + QString name() const { return "Insert"; } + QString page() const { return "Block"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ( ! index.isValid() || ! index.parent().isValid() ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QStringList ids = nif->allNiBlocks(); + ids.sort(); + + QMap< QString, QMenu *> map; + foreach ( QString id, ids ) + { + QString x( "Other" ); + + if ( id.startsWith( "Ni" ) ) + x = QString("Ni&") + id.mid( 2, 1 ) + "..."; + if ( id.startsWith( "bhk" ) || id.startsWith( "hk" ) ) + x = "Havok"; + if ( id.startsWith( "BS" ) || id == "AvoidNode" || id == "RootCollisionNode" ) + x = "Bethesda"; + if ( id.startsWith( "Fx" ) ) + x = "Firaxis"; + + if ( ! map.contains( x ) ) + map[ x ] = new QMenu( x ); + map[ x ]->addAction( id ); + } + + QMenu menu; + foreach ( QMenu * m, map ) + menu.addMenu( m ); + + QAction * act = menu.exec( QCursor::pos() ); + if ( act ) + return nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); + else + return index; + } +}; + +REGISTER_SPELL( spInsertBlock ) + + +class spAttachProperty : public Spell +{ +public: + QString name() const { return "Attach Property"; } + QString page() const { return "Node"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->itemType( index ) == "NiBlock" && nif->inherits( index, "NiAVObject" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QMenu menu; + QStringList ids = nif->allNiBlocks(); + ids.sort(); + foreach ( QString id, ids ) + if ( nif->inherits( id, "NiProperty" ) ) + menu.addAction( id ); + if ( menu.actions().isEmpty() ) + return index; + QAction * act = menu.exec( QCursor::pos() ); + if ( act ) + { + QPersistentModelIndex iParent = index; + QModelIndex iProperty = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); + addLink( nif, iParent, "Properties", nif->getBlockNumber( iProperty ) ); + return iProperty; + } + else + return index; + } +}; + +REGISTER_SPELL( spAttachProperty ) + + +class spAttachNode : public Spell +{ +public: + QString name() const { return "Attach Node"; } + QString page() const { return "Node"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index ) && nif->inherits( index, "NiNode" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QMenu menu; + QStringList ids = nif->allNiBlocks(); + ids.sort(); + foreach ( QString id, ids ) + if ( nif->inherits( id, "NiAVObject" ) && ! nif->inherits( id, "NiLight" ) ) + menu.addAction( id ); + + QAction * act = menu.exec( QCursor::pos() ); + if ( act ) + { + QPersistentModelIndex iParent = index; + QModelIndex iNode = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); + addLink( nif, iParent, "Children", nif->getBlockNumber( iNode ) ); + return iNode; + } + else + return index; + } +}; + +REGISTER_SPELL( spAttachNode ) + + +class spAttachLight : public Spell +{ +public: + QString name() const { return "Attach Light"; } + QString page() const { return "Node"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index ) && nif->inherits( index, "NiNode" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QMenu menu; + QStringList ids = nif->allNiBlocks(); + ids.sort(); + foreach ( QString id, ids ) + if ( nif->inherits( id, "NiLight" ) ) + menu.addAction( id ); + + QAction * act = menu.exec( QCursor::pos() ); + if ( act ) + { + QPersistentModelIndex iParent = index; + QModelIndex iLight = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); + addLink( nif, iParent, "Children", nif->getBlockNumber( iLight ) ); + addLink( nif, iParent, "Effects", nif->getBlockNumber( iLight ) ); + return iLight; + } + else + return index; + } +}; + +REGISTER_SPELL( spAttachLight ) + + +class spAttachExtraData : public Spell +{ +public: + QString name() const { return "Attach Extra Data"; } + QString page() const { return "Node"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index ) && nif->inherits( index, "NiObjectNET" ) && nif->checkVersion( 0x0a000100, 0 ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QMenu menu; + QStringList ids = nif->allNiBlocks(); + ids.sort(); + foreach ( QString id, ids ) + if ( nif->inherits( id, "NiExtraData" ) ) + menu.addAction( id ); + QAction * act = menu.exec( QCursor::pos() ); + if ( act ) + { + QPersistentModelIndex iParent = index; + QModelIndex iExtra = nif->insertNiBlock( act->text(), nif->getBlockNumber( index ) + 1 ); + addLink( nif, iParent, "Extra Data List", nif->getBlockNumber( iExtra ) ); + return iExtra; + } + else + return index; + } +}; + +REGISTER_SPELL( spAttachExtraData ) + + +class spRemoveBlock : public Spell +{ +public: + QString name() const { return "Remove"; } + QString page() const { return "Block"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index ) && nif->getBlockNumber( index ) >= 0; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + nif->removeNiBlock( nif->getBlockNumber( index ) ); + return QModelIndex(); + } +}; + +REGISTER_SPELL( spRemoveBlock ) + + +class spCopyBlock : public Spell +{ +public: + QString name() const { return "Copy"; } + QString page() const { return "Block"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QByteArray data; + QBuffer buffer( & data ); + if ( buffer.open( QIODevice::WriteOnly ) && nif->save( buffer, index ) ) + { + QMimeData * mime = new QMimeData; + mime->setData( QString( "nifskope/niblock/%1/%2" ).arg( nif->itemName( index ) ).arg( nif->getVersion() ), data ); + QApplication::clipboard()->setMimeData( mime ); + } + return index; + } +}; + +REGISTER_SPELL( spCopyBlock ) + + +class spPasteBlock : public Spell +{ +public: + QString name() const { return "Paste"; } + QString page() const { return "Block"; } + + QString acceptFormat( const QString & format, const NifModel * nif ) + { + QStringList split = format.split( "/" ); + if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "niblock" && nif->isNiBlock( split.value( 2 ) ) ) + return split.value( 3 ); + return QString(); + } + + QString blockType( const QString & format ) + { + return format.split( "/" ).value( 2 ); + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + const QMimeData * mime = QApplication::clipboard()->mimeData(); + if ( mime ) + foreach ( QString form, mime->formats() ) + if ( ! acceptFormat( form, nif ).isEmpty() ) + return true; + return false; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + const QMimeData * mime = QApplication::clipboard()->mimeData(); + if ( mime ) + { + foreach ( QString form, mime->formats() ) + { + QString version = acceptFormat( form, nif ); + if ( ! version.isEmpty() && ( version == nif->getVersion() || QMessageBox::question( 0, "Paste Block", QString( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ).arg( nif->getVersion() ).arg( version ), "Continue", "Cancel" ) == 0 )) + { + QByteArray data = mime->data( form ); + QBuffer buffer( & data ); + if ( buffer.open( QIODevice::ReadOnly ) ) + { + QModelIndex block = nif->insertNiBlock( blockType( form ), nif->getBlockCount() ); + nif->load( buffer, block ); + blockLink( nif, index, block ); + return block; + } + } + } + } + return QModelIndex(); + } +}; + +REGISTER_SPELL( spPasteBlock ) + + +class spPasteOverBlock : public Spell +{ +public: + QString name() const { return "Paste Over"; } + QString page() const { return "Block"; } + + QString acceptFormat( const QString & format, const NifModel * nif, const QModelIndex & block ) + { + QStringList split = format.split( "/" ); + if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "niblock" && nif->isNiBlock( block, split.value( 2 ) ) ) + return split.value( 3 ); + return QString(); + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + const QMimeData * mime = QApplication::clipboard()->mimeData(); + if ( mime ) + foreach ( QString form, mime->formats() ) + if ( ! acceptFormat( form, nif, index ).isEmpty() ) + return true; + return false; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + const QMimeData * mime = QApplication::clipboard()->mimeData(); + if ( mime ) + { + foreach ( QString form, mime->formats() ) + { + QString version = acceptFormat( form, nif, index ); + if ( ! version.isEmpty() && ( version == nif->getVersion() || QMessageBox::question( 0, "Paste Over", QString( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ).arg( nif->getVersion() ).arg( version ), "Continue", "Cancel" ) == 0 )) + { + QByteArray data = mime->data( form ); + QBuffer buffer( & data ); + if ( buffer.open( QIODevice::ReadOnly ) ) + { + nif->load( buffer, index ); + return index; + } + } + } + } + return QModelIndex(); + } +}; + +REGISTER_SPELL( spPasteOverBlock ) + + +class spCopyBranch : public Spell +{ +public: + QString name() const { return "Copy Branch"; } + QString page() const { return "Block"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QList blocks; + populateBlocks( blocks, nif, nif->getBlockNumber( index ) ); + + QMap blockMap; + for ( int b = 0; b < blocks.count(); b++ ) + blockMap.insert( blocks[b], b ); + + QMap parentMap; + foreach ( qint32 block, blocks ) + { + foreach ( qint32 link, nif->getParentLinks( block ) ) + { + if ( ! blocks.contains( link ) && ! parentMap.contains( link ) ) + { + QModelIndex iParent = nif->getBlock( link ); + if ( iParent.isValid() ) + { + QString name = nif->get( iParent, "Name" ); + if ( ! name.isEmpty() ) + { + parentMap.insert( link, nif->itemName( iParent ) + "|" + name ); + continue; + } + } + qWarning() << "failed to map parent link" << link << "for block" << block << nif->itemName( nif->getBlock( block ) ); + return index; + } + } + } + + QByteArray data; + QBuffer buffer( & data ); + if ( buffer.open( QIODevice::WriteOnly ) ) + { + QDataStream ds( & buffer ); + ds << blocks.count(); + ds << blockMap; + ds << parentMap; + foreach ( qint32 block, blocks ) + { + ds << nif->itemName( nif->getBlock( block ) ); + if ( ! nif->save( buffer, nif->getBlock( block ) ) ) + { + qWarning() << "failed to save block" << block << nif->itemName( nif->getBlock( block ) ); + return index; + } + } + QMimeData * mime = new QMimeData; + mime->setData( QString( "nifskope/nibranch/%1" ).arg( nif->getVersion() ), data ); + QApplication::clipboard()->setMimeData( mime ); + } + return index; + } + + void populateBlocks( QList & blocks, NifModel * nif, qint32 block ) + { + if ( ! blocks.contains( block ) ) blocks.append( block ); + foreach ( qint32 link, nif->getChildLinks( block ) ) + populateBlocks( blocks, nif, link ); + } +}; + +REGISTER_SPELL( spCopyBranch ) + + +class spPasteBranch : public Spell +{ +public: + QString name() const { return "Paste Branch"; } + QString page() const { return "Block"; } + + QString acceptFormat( const QString & format, const NifModel * nif ) + { + QStringList split = format.split( "/" ); + if ( split.value( 0 ) == "nifskope" && split.value( 1 ) == "nibranch" ) + return split.value( 2 ); + else + return QString(); + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + if ( index.isValid() && ! nif->isNiBlock( index ) && ! nif->isLink( index ) ) + return false; + const QMimeData * mime = QApplication::clipboard()->mimeData(); + if ( mime ) + foreach ( QString form, mime->formats() ) + if ( nif->isVersionSupported( nif->version2number( acceptFormat( form, nif ) ) ) ) + return true; + return false; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + const QMimeData * mime = QApplication::clipboard()->mimeData(); + if ( mime ) + { + foreach ( QString form, mime->formats() ) + { + QString v = acceptFormat( form, nif ); + if ( ! v.isEmpty() && ( v == nif->getVersion() || QMessageBox::question( 0, "Paste Branch", QString( "Nif versions differ!

Current File Version: %1
Clipboard Data Version: %2

The results will be unpredictable..." ).arg( nif->getVersion() ).arg( v ), "Continue", "Cancel" ) == 0 ) ) + { + QByteArray data = mime->data( form ); + QBuffer buffer( & data ); + if ( buffer.open( QIODevice::ReadOnly ) ) + { + QDataStream ds( & buffer ); + + int count; + ds >> count; + + QMap blockMap; + ds >> blockMap; + QMutableMapIterator ibm( blockMap ); + while ( ibm.hasNext() ) + { + ibm.next(); + ibm.value() += nif->getBlockCount(); + } + + QMap parentMap; + ds >> parentMap; + + QMapIterator ipm( parentMap ); + while ( ipm.hasNext() ) + { + ipm.next(); + qint32 block = getBlockByName( nif, ipm.value() ); + if ( block >= 0 ) + { + blockMap.insert( ipm.key(), block ); + } + else + { + qWarning() << "failed to map parent link" << ipm.value(); + return index; + } + } + + QModelIndex iRoot; + + for ( int c = 0; c < count; c++ ) + { + QString type; + ds >> type; + + QModelIndex block = nif->insertNiBlock( type, -1 ); + if ( ! nif->loadAndMapLinks( buffer, block, blockMap ) ) + return index; + if ( c == 0 ) + iRoot = block; + } + + blockLink( nif, index, iRoot ); + + return iRoot; + } + } + } + } + return QModelIndex(); + } + + qint32 getBlockByName( NifModel * nif, const QString & tn ) + { + QStringList ls = tn.split( "|" ); + QString type = ls.value( 0 ); + QString name = ls.value( 1 ); + if ( type.isEmpty() || name.isEmpty() ) + return -1; + for ( int b = 0; b < nif->getBlockCount(); b++ ) + { + QModelIndex iBlock = nif->getBlock( b ); + if ( nif->itemName( iBlock ) == type && nif->get( iBlock, "Name" ) == name ) + return b; + } + return -1; + } +}; + +REGISTER_SPELL( spPasteBranch ) + + + static void removeChildren( NifModel * nif, const QPersistentModelIndex & iBlock ) + { + QList iChildren; + foreach ( quint32 link, nif->getChildLinks( nif->getBlockNumber( iBlock ) ) ) + iChildren.append( nif->getBlock( link ) ); + + foreach ( QPersistentModelIndex iChild, iChildren ) + if ( iChild.isValid() && nif->getBlockNumber( iBlock ) == nif->getParent( nif->getBlockNumber( iChild ) ) ) + removeChildren( nif, iChild ); + + foreach ( QPersistentModelIndex iChild, iChildren ) + if ( iChild.isValid() && nif->getBlockNumber( iBlock ) == nif->getParent( nif->getBlockNumber( iChild ) ) ) + nif->removeNiBlock( nif->getBlockNumber( iChild ) ); + } + +bool spRemoveBranch::isApplicable( const NifModel * nif, const QModelIndex & iBlock ) +{ + int ix = nif->getBlockNumber( iBlock ); + return ( nif->isNiBlock( iBlock ) && ix >= 0 && ( nif->getRootLinks().contains( ix ) || nif->getParent( ix ) >= 0 ) ); +} + +QModelIndex spRemoveBranch::cast( NifModel * nif, const QModelIndex & index ) +{ + QPersistentModelIndex iBlock = index; + removeChildren( nif, iBlock ); + nif->removeNiBlock( nif->getBlockNumber( iBlock ) ); + return QModelIndex(); +} + +REGISTER_SPELL( spRemoveBranch ) + + +class spFlattenBranch : public Spell +{ +public: + QString name() const { return "Flatten Branch"; } + QString page() const { return "Block"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ), "NiNode" ); + return nif->inherits( index, "NiNode" ) && iParent.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & iNode ) + { + QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( iNode ) ), "NiNode" ); + doNode( nif, iNode, iParent, Transform() ); + return iNode; + } + + void doNode( NifModel * nif, const QModelIndex & iNode, const QModelIndex & iParent, Transform tp ) + { + if ( ! nif->inherits( iNode, "NiNode" ) ) + return; + + Transform t = tp * Transform( nif, iNode ); + + QList links; + + foreach ( qint32 l, nif->getLinkArray( iNode, "Children" ) ) + { + QModelIndex iChild = nif->getBlock( l ); + if ( nif->getParent( nif->getBlockNumber( iChild ) ) == nif->getBlockNumber( iNode ) ) + { + Transform tc = t * Transform( nif, iChild ); + tc.writeBack( nif, iChild ); + addLink( nif, iParent, "Children", l ); + delLink( nif, iNode, "Children", l ); + links.append( l ); + } + } + + foreach ( qint32 l, links ) + { + doNode( nif, nif->getBlock( l, "NiNode" ), iParent, tp ); + } + } +}; + +REGISTER_SPELL( spFlattenBranch ) + + +class spMoveBlockUp : public Spell +{ +public: + QString name() const { return "Move Up"; } + QString page() const { return "Block"; } + QKeySequence hotkey() const { return QKeySequence( Qt::CTRL + Qt::Key_Up ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index ) && nif->getBlockNumber( index ) > 0; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) + { + int ix = nif->getBlockNumber( iBlock ); + nif->moveNiBlock( ix, ix - 1 ); + return nif->getBlock( ix - 1 ); + } +}; + +REGISTER_SPELL( spMoveBlockUp ) + + +class spMoveBlockDown : public Spell +{ +public: + QString name() const { return "Move Down"; } + QString page() const { return "Block"; } + QKeySequence hotkey() const { return QKeySequence( Qt::CTRL + Qt::Key_Down ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index ) && nif->getBlockNumber( index ) < nif->getBlockCount() - 1; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) + { + int ix = nif->getBlockNumber( iBlock ); + nif->moveNiBlock( ix, ix + 1 ); + return nif->getBlock( ix + 1 ); + } +}; + +REGISTER_SPELL( spMoveBlockDown ) + + +class spRemoveBlocksById : public Spell +{ +public: + QString name() const { return "Remove By Id"; } + QString page() const { return "Block"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ! index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) + { + NIFSKOPE_QSETTINGS(settings); + settings.beginGroup( "spells" ); + settings.beginGroup( page() ); + settings.beginGroup( name() ); + + bool ok = true; + QString match = QInputDialog::getText( 0, "Remove Blocks by Id", "Enter a regular expression:", QLineEdit::Normal, + settings.value( "match expression", "^BS|^NiBS|^bhk|^hk" ).toString(), & ok ); + + if ( ! ok ) + return QModelIndex(); + + settings.setValue( "match expression", match ); + + QRegExp exp( match ); + + int n = 0; + while ( n < nif->getBlockCount() ) + { + QModelIndex iBlock = nif->getBlock( n ); + if ( nif->itemName( iBlock ).indexOf( exp ) >= 0 ) + nif->removeNiBlock( n ); + else + n++; + } + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spRemoveBlocksById ) + diff --git a/spells/blocks.h b/spells/blocks.h index 52b858c74..815a55845 100644 --- a/spells/blocks.h +++ b/spells/blocks.h @@ -1,17 +1,17 @@ -#ifndef SP_BLOCKS_H -#define SP_BLOCKS_H - -#include "../spellbook.h" - -class spRemoveBranch : public Spell -{ -public: - QString name() const { return Spell::tr("Remove Branch"); } - QString page() const { return Spell::tr("Block"); } - QKeySequence hotkey() const { return QKeySequence( Qt::CTRL + Qt::Key_Delete ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ); - QModelIndex cast( NifModel * nif, const QModelIndex & index ); -}; - -#endif +#ifndef SP_BLOCKS_H +#define SP_BLOCKS_H + +#include "../spellbook.h" + +class spRemoveBranch : public Spell +{ +public: + QString name() const { return Spell::tr("Remove Branch"); } + QString page() const { return Spell::tr("Block"); } + QKeySequence hotkey() const { return QKeySequence( Qt::CTRL + Qt::Key_Delete ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ); + QModelIndex cast( NifModel * nif, const QModelIndex & index ); +}; + +#endif diff --git a/spells/color.cpp b/spells/color.cpp index 950651417..a95e366f2 100644 --- a/spells/color.cpp +++ b/spells/color.cpp @@ -1,30 +1,30 @@ -#include "../spellbook.h" - -#include "../widgets/colorwheel.h" - -class spChooseColor : public Spell -{ -public: - QString name() const { return "Choose"; } - QString page() const { return "Color"; } - QIcon icon() const { return ColorWheel::getIcon(); } - bool instant() const { return true; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->getValue( index ).isColor(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - if ( nif->getValue( index ).type() == NifValue::tColor3 ) - nif->set( index, ColorWheel::choose( nif->get( index ) ) ); - else if ( nif->getValue( index ).type() == NifValue::tColor4 ) - nif->set( index, ColorWheel::choose( nif->get( index ) ) ); - return index; - } -}; - -REGISTER_SPELL( spChooseColor ) - - +#include "../spellbook.h" + +#include "../widgets/colorwheel.h" + +class spChooseColor : public Spell +{ +public: + QString name() const { return "Choose"; } + QString page() const { return "Color"; } + QIcon icon() const { return ColorWheel::getIcon(); } + bool instant() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->getValue( index ).isColor(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + if ( nif->getValue( index ).type() == NifValue::tColor3 ) + nif->set( index, ColorWheel::choose( nif->get( index ) ) ); + else if ( nif->getValue( index ).type() == NifValue::tColor4 ) + nif->set( index, ColorWheel::choose( nif->get( index ) ) ); + return index; + } +}; + +REGISTER_SPELL( spChooseColor ) + + diff --git a/spells/flags.cpp b/spells/flags.cpp index 12055aa07..dd62193d3 100644 --- a/spells/flags.cpp +++ b/spells/flags.cpp @@ -1,412 +1,412 @@ -#include "../spellbook.h" - -#include -#include -#include -#include -#include -#include -#include - -class spEditFlags : public Spell -{ -public: - QString name() const { return Spell::tr( "Flags" ); } - bool instant() const { return true; } - QIcon icon() const { return QIcon( ":/img/flag" ); } - - enum FlagType - { - Alpha, Controller, Node, RigidBody, Shape, ZBuffer, BSX, None - }; - - QModelIndex getFlagIndex( const NifModel * nif, const QModelIndex & index ) const - { - if ( nif->itemName( index ) == "Flags" && nif->isNiBlock( index.parent() ) ) - return index; - if ( nif->isNiBlock( index ) ) - return nif->getIndex( index, "Flags" ); - if ( nif->inherits( nif->getBlock( index ), "bhkRigidBody" ) ) - { - QModelIndex iFlags = nif->getIndex( nif->getBlock( index ), "Col Filter" ); - iFlags = iFlags.sibling( iFlags.row(), NifModel::ValueCol ); - if ( index == iFlags ) - return iFlags; - } - else if ( nif->inherits( nif->getBlock( index ), "BSXFlags" ) ) - { - QModelIndex iFlags = nif->getIndex( nif->getBlock( index ), "Integer Data" ); - iFlags = iFlags.sibling( iFlags.row(), NifModel::ValueCol ); - if ( index == iFlags ) - return iFlags; - } - return QModelIndex(); - } - - FlagType queryType( const NifModel * nif, const QModelIndex & index ) const - { - if ( nif->getValue( index ).isCount() ) - { - QString name = nif->itemName( index.parent() ); - if ( name == "NiAlphaProperty" ) - return Alpha; - else if ( nif->inherits( name, "NiTimeController" ) ) - return Controller; - else if ( name == "NiNode" ) - return Node; - else if ( name == "bhkRigidBody" || name == "bhkRigidBodyT" ) - return RigidBody; - else if ( name == "NiTriShape" || name == "NiTriStrips" ) - return Shape; - else if ( name == "NiZBufferProperty" ) - return ZBuffer; - else if ( name == "BSXFlags" ) - return BSX; - } - return None; - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return queryType( nif, getFlagIndex( nif, index ) ) != None; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iFlags = getFlagIndex( nif, index ); - - switch ( queryType( nif, iFlags ) ) - { - case Alpha: - alphaFlags( nif, iFlags ); - break; - case Controller: - controllerFlags( nif, iFlags ); - break; - case Node: - nodeFlags( nif, iFlags ); - break; - case RigidBody: - bodyFlags( nif, iFlags ); - break; - case Shape: - shapeFlags( nif, iFlags ); - break; - case ZBuffer: - zbufferFlags( nif, iFlags ); - break; - case BSX: - bsxFlags( nif, iFlags ); - break; - default: - break; - } - return index; - } - - void alphaFlags( NifModel * nif, const QModelIndex & index ) - { - quint16 flags = nif->get( index ); - if ( ! flags ) flags = 0xed; - - QDialog dlg; - QVBoxLayout * vbox = new QVBoxLayout; - dlg.setLayout( vbox ); - - QStringList blendModes = QStringList() << - "One" << "Zero" << "Src Color" << "Inv Src Color" << "Dst Color" << "Inv Dst Color" << "Src Alpha" << "Inv Src Alpha" << - "Dst Alpha" << "Inv Dst Alpha" << "Src Alpha Saturate"; - QStringList testModes = QStringList() << - "Always" << "Less" << "Equal" << "Less or Equal" << "Greater" << "Not Equal" << "Greater or Equal" << "Never"; - - QCheckBox * chkBlend = dlgCheck( vbox, "Enable Blending" ); - chkBlend->setChecked( flags & 1 ); - - QComboBox * cmbSrc = dlgCombo( vbox, "Source Blend Mode", blendModes, chkBlend ); - cmbSrc->setCurrentIndex( flags >> 1 & 0x0f ); - - QComboBox * cmbDst = dlgCombo( vbox, "Destination Blend Mode", blendModes, chkBlend ); - cmbDst->setCurrentIndex( flags >> 5 & 0x0f ); - - QCheckBox * chkTest = dlgCheck( vbox, "Enable Testing" ); - chkTest->setChecked( flags & ( 1 << 9 ) ); - - QComboBox * cmbTest = dlgCombo( vbox, "Alpha Test Function", testModes, chkTest ); - cmbTest->setCurrentIndex( flags >> 10 & 0x07 ); - - QSpinBox * spnTest = dlgSpin( vbox, "Alpha Test Threshold", 0x00, 0xff, chkTest ); - spnTest->setValue( nif->get( nif->getBlock( index ), "Threshold" ) ); - - QCheckBox * chkSort = dlgCheck( vbox, "No Sorter" ); - chkSort->setChecked( ( flags & 0x2000 ) != 0 ); - - dlgButtons( &dlg, vbox ); - - if ( dlg.exec() == QDialog::Accepted ) - { - flags = flags & 0xfffe; - if ( chkBlend->isChecked() ) - { - flags |= 1; - flags = flags & 0xffe1 | cmbSrc->currentIndex() << 1; - flags = flags & 0xfe1f | cmbDst->currentIndex() << 5; - } - - flags = flags & 0xe1ff; - if ( chkTest->isChecked() ) - { - flags |= 0x0200; - flags = flags & 0xe3ff | ( cmbTest->currentIndex() << 10 ); - nif->set( nif->getBlock( index ), "Threshold", spnTest->value() ); - } - - flags = flags & 0xdfff | ( chkSort->isChecked() ? 0x2000 : 0 ); - - nif->set( index, flags ); - } - } - - void nodeFlags( NifModel * nif, const QModelIndex & index ) - { - quint16 flags = nif->get( index ); - - QDialog dlg; - QVBoxLayout * vbox = new QVBoxLayout; - dlg.setLayout( vbox ); - - QCheckBox * chkHidden = dlgCheck( vbox, "Hidden" ); - chkHidden->setChecked( flags & 1 ); - - QComboBox * cmbCollision = dlgCombo( vbox, "Collision Detection", QStringList() << "None" << "Triangles" << "Bounding Box" << "Continue" ); - cmbCollision->setCurrentIndex( flags >> 1 & 3 ); - - QCheckBox * chkSkin = dlgCheck( vbox, "Skin Influence" ); - chkSkin->setChecked( ! ( flags & 8 ) ); - - dlgButtons( &dlg, vbox ); - - if ( dlg.exec() == QDialog::Accepted ) - { - flags = flags & 0xfffe | ( chkHidden->isChecked() ? 1 : 0 ); - flags = flags & 0xfff9 | ( cmbCollision->currentIndex() << 1 ); - flags = flags & 0xfff7 | ( chkSkin->isChecked() ? 0 : 8 ); - nif->set( index, flags ); - } - } - - void controllerFlags( NifModel * nif, const QModelIndex & index ) - { - quint16 flags = nif->get( index ); - - QDialog dlg; - QVBoxLayout * vbox = new QVBoxLayout; - dlg.setLayout( vbox ); - - QCheckBox * chkActive = dlgCheck( vbox, "Active" ); - chkActive->setChecked( flags & 8 ); - - QComboBox * cmbLoop = dlgCombo( vbox, "Loop Mode", QStringList() << "Cycle" << "Reverse" << "Clamp" ); - cmbLoop->setCurrentIndex( flags >> 1 & 3 ); - - dlgButtons( &dlg, vbox ); - - if ( dlg.exec() == QDialog::Accepted ) - { - flags = flags & 0xfff7 | ( chkActive->isChecked() ? 8 : 0 ); - flags = flags & 0xfff9 | cmbLoop->currentIndex() << 1; - nif->set( index, flags ); - } - } - - void bodyFlags( NifModel * nif, const QModelIndex & index ) - { - quint16 flags = nif->get( index ); - - QDialog dlg; - QVBoxLayout * vbox = new QVBoxLayout( &dlg ); - - QCheckBox * chkLinked = dlgCheck( vbox, "Linked" ); - chkLinked->setChecked( flags & 0x80 ); - QCheckBox * chkNoCol = dlgCheck( vbox, "No Collision" ); - chkNoCol->setChecked( flags & 0x40 ); - QCheckBox * chkScaled = dlgCheck( vbox, "Scaled" ); - chkScaled->setChecked( flags & 0x20 ); - - QSpinBox * spnPartNo = dlgSpin( vbox, "Part Number", 0, 0x1f, chkLinked ); - spnPartNo->setValue( flags & 0x1f ); - - dlgButtons( &dlg, vbox ); - - if ( dlg.exec() == QDialog::Accepted ) - { - flags = flags & 0x7f | ( chkLinked->isChecked() ? 0x80 : 0 ); - flags = flags & 0xbf | ( chkNoCol->isChecked() ? 0x40 : 0 ); - flags = flags & 0xdf | ( chkScaled->isChecked() ? 0x20 : 0 ); - flags = flags & 0xe0 | ( chkLinked->isChecked() ? spnPartNo->value() : 0 ); - nif->set( index, flags ); - nif->set( index.parent(), "Col Filter Copy", flags ); - } - } - - void shapeFlags( NifModel * nif, const QModelIndex & index ) - { - quint16 flags = nif->get( index ); - - QDialog dlg; - QVBoxLayout * vbox = new QVBoxLayout; - dlg.setLayout( vbox ); - - QCheckBox * chkHidden = dlgCheck( vbox, "Hidden" ); - chkHidden->setChecked( flags & 0x01 ); - - QComboBox * cmbCollision = dlgCombo( vbox, "Collision Detection", QStringList() << "None" << "Triangles" << "Bounding Box" << "Continue" ); - cmbCollision->setCurrentIndex( flags >> 1 & 3 ); - - QCheckBox * chkShadow = 0; - if ( nif->checkVersion( 0x04000002, 0x04000002 ) ) - { - chkShadow = dlgCheck( vbox, "Shadow" ); - chkShadow->setChecked( flags & 0x40 ); - } - - dlgButtons( &dlg, vbox ); - - if ( dlg.exec() == QDialog::Accepted ) - { - flags = flags & 0xfffe | ( chkHidden->isChecked() ? 0x01 : 0 ); - flags = flags & 0xfff9 | ( cmbCollision->currentIndex() << 1 ); - if ( chkShadow ) - flags = flags & 0xffbf | ( chkShadow->isChecked() ? 0x40 : 0 ); - nif->set( index, flags ); - } - } - - void zbufferFlags( NifModel * nif, const QModelIndex & index ) - { - quint16 flags = nif->get( index ); - - QDialog dlg; - QVBoxLayout * vbox = new QVBoxLayout; - dlg.setLayout( vbox ); - - QCheckBox * chkEnable = dlgCheck( vbox, "Enable Z Buffer" ); - chkEnable->setChecked( flags & 1 ); - - QCheckBox * chkROnly = dlgCheck( vbox, "Z Buffer Read Only" ); - chkROnly->setChecked( ( flags & 2 ) == 0 ); - - QComboBox * cmbFunc = dlgCombo( vbox, "Z Buffer Test Function", QStringList() << "Always" << "Less" << "Equal" << "Less or Equal" << "Greater" << "Not Equal" << "Greater or Equal" << "Never", chkEnable ); - if ( nif->checkVersion( 0x04010012, 0 ) ) - cmbFunc->setCurrentIndex( nif->get( nif->getBlock( index ), "Function" ) ); - else - cmbFunc->setCurrentIndex( ( flags >> 2 ) & 0x07 ); - - dlgButtons( &dlg, vbox ); - - if ( dlg.exec() == QDialog::Accepted ) - { - flags = flags & 0xfffe | ( chkEnable->isChecked() ? 1 : 0 ); - flags = flags & 0xfffd | ( chkROnly->isChecked() ? 0 : 2 ); - if ( nif->checkVersion( 0x04010012, 0 ) ) - nif->set( nif->getBlock( index ), "Function", cmbFunc->currentIndex() ); - else - flags = flags & 0xffe3 | ( cmbFunc->currentIndex() << 2 ); - nif->set( index, flags ); - } - } - - void bsxFlags( NifModel * nif, const QModelIndex & index ) - { - quint32 flags = nif->get( index ); - - QDialog dlg; - QVBoxLayout * vbox = new QVBoxLayout( & dlg ); - - QStringList flagNames( QStringList() - << Spell::tr( "Enable Animation" ) // 1 - << Spell::tr( "Enable Collision" ) // 2 - << Spell::tr( "Is Skeleton Nif (?)" ) // 4 - << Spell::tr( "Unidentified Flag (?)" ) // 8 - << Spell::tr( "FlameNodes Present" ) // 16 - << Spell::tr( "EditorMarkers Present" ) // 32 - ); - - QList chkBoxes; - int x = 0; - foreach ( QString flagName, flagNames ) - { - chkBoxes << dlgCheck( vbox, QString( "%1 (%2)" ).arg( flagName ).arg( 1 << x ) ); - chkBoxes.last()->setChecked( flags & ( 1 << x ) ); - x++; - } - - dlgButtons( &dlg, vbox ); - - if ( dlg.exec() == QDialog::Accepted ) - { - x = 0; - foreach ( QCheckBox * chk, chkBoxes ) - { - flags = flags & ( ~ ( 1 << x ) ) | ( chk->isChecked() ? 1 << x : 0 ); - x++; - } - nif->set( index, flags ); - } - } - - QCheckBox * dlgCheck( QVBoxLayout * vbox, const QString & name, QCheckBox * chk = 0 ) - { - QCheckBox * box = new QCheckBox( name ); - vbox->addWidget( box ); - if ( chk ) - { - QObject::connect( chk, SIGNAL( toggled( bool ) ), box, SLOT( setEnabled( bool ) ) ); - box->setEnabled( chk->isChecked() ); - } - return box; - } - - QComboBox * dlgCombo( QVBoxLayout * vbox, const QString & name, QStringList items, QCheckBox * chk = 0 ) - { - vbox->addWidget( new QLabel( name ) ); - QComboBox * cmb = new QComboBox; - vbox->addWidget( cmb ); - cmb->addItems( items ); - if ( chk ) - { - QObject::connect( chk, SIGNAL( toggled( bool ) ), cmb, SLOT( setEnabled( bool ) ) ); - cmb->setEnabled( chk->isChecked() ); - } - return cmb; - } - - QSpinBox * dlgSpin( QVBoxLayout * vbox, const QString & name, int min, int max, QCheckBox * chk = 0 ) - { - vbox->addWidget( new QLabel( name ) ); - QSpinBox * spn = new QSpinBox; - vbox->addWidget( spn ); - spn->setRange( min, max ); - if ( chk ) - { - QObject::connect( chk, SIGNAL( toggled( bool ) ), spn, SLOT( setEnabled( bool ) ) ); - spn->setEnabled( chk->isChecked() ); - } - return spn; - } - - void dlgButtons( QDialog * dlg, QVBoxLayout * vbox ) - { - QHBoxLayout * hbox = new QHBoxLayout; - vbox->addLayout( hbox ); - - QPushButton * btAccept = new QPushButton( "Accept" ); - hbox->addWidget( btAccept ); - QObject::connect( btAccept, SIGNAL( clicked() ), dlg, SLOT( accept() ) ); - - QPushButton * btReject = new QPushButton( "Cancel" ); - hbox->addWidget( btReject ); - QObject::connect( btReject, SIGNAL( clicked() ), dlg, SLOT( reject() ) ); - } -}; - -REGISTER_SPELL( spEditFlags ) +#include "../spellbook.h" + +#include +#include +#include +#include +#include +#include +#include + +class spEditFlags : public Spell +{ +public: + QString name() const { return Spell::tr( "Flags" ); } + bool instant() const { return true; } + QIcon icon() const { return QIcon( ":/img/flag" ); } + + enum FlagType + { + Alpha, Controller, Node, RigidBody, Shape, ZBuffer, BSX, None + }; + + QModelIndex getFlagIndex( const NifModel * nif, const QModelIndex & index ) const + { + if ( nif->itemName( index ) == "Flags" && nif->isNiBlock( index.parent() ) ) + return index; + if ( nif->isNiBlock( index ) ) + return nif->getIndex( index, "Flags" ); + if ( nif->inherits( nif->getBlock( index ), "bhkRigidBody" ) ) + { + QModelIndex iFlags = nif->getIndex( nif->getBlock( index ), "Col Filter" ); + iFlags = iFlags.sibling( iFlags.row(), NifModel::ValueCol ); + if ( index == iFlags ) + return iFlags; + } + else if ( nif->inherits( nif->getBlock( index ), "BSXFlags" ) ) + { + QModelIndex iFlags = nif->getIndex( nif->getBlock( index ), "Integer Data" ); + iFlags = iFlags.sibling( iFlags.row(), NifModel::ValueCol ); + if ( index == iFlags ) + return iFlags; + } + return QModelIndex(); + } + + FlagType queryType( const NifModel * nif, const QModelIndex & index ) const + { + if ( nif->getValue( index ).isCount() ) + { + QString name = nif->itemName( index.parent() ); + if ( name == "NiAlphaProperty" ) + return Alpha; + else if ( nif->inherits( name, "NiTimeController" ) ) + return Controller; + else if ( name == "NiNode" ) + return Node; + else if ( name == "bhkRigidBody" || name == "bhkRigidBodyT" ) + return RigidBody; + else if ( name == "NiTriShape" || name == "NiTriStrips" ) + return Shape; + else if ( name == "NiZBufferProperty" ) + return ZBuffer; + else if ( name == "BSXFlags" ) + return BSX; + } + return None; + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return queryType( nif, getFlagIndex( nif, index ) ) != None; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iFlags = getFlagIndex( nif, index ); + + switch ( queryType( nif, iFlags ) ) + { + case Alpha: + alphaFlags( nif, iFlags ); + break; + case Controller: + controllerFlags( nif, iFlags ); + break; + case Node: + nodeFlags( nif, iFlags ); + break; + case RigidBody: + bodyFlags( nif, iFlags ); + break; + case Shape: + shapeFlags( nif, iFlags ); + break; + case ZBuffer: + zbufferFlags( nif, iFlags ); + break; + case BSX: + bsxFlags( nif, iFlags ); + break; + default: + break; + } + return index; + } + + void alphaFlags( NifModel * nif, const QModelIndex & index ) + { + quint16 flags = nif->get( index ); + if ( ! flags ) flags = 0xed; + + QDialog dlg; + QVBoxLayout * vbox = new QVBoxLayout; + dlg.setLayout( vbox ); + + QStringList blendModes = QStringList() << + "One" << "Zero" << "Src Color" << "Inv Src Color" << "Dst Color" << "Inv Dst Color" << "Src Alpha" << "Inv Src Alpha" << + "Dst Alpha" << "Inv Dst Alpha" << "Src Alpha Saturate"; + QStringList testModes = QStringList() << + "Always" << "Less" << "Equal" << "Less or Equal" << "Greater" << "Not Equal" << "Greater or Equal" << "Never"; + + QCheckBox * chkBlend = dlgCheck( vbox, "Enable Blending" ); + chkBlend->setChecked( flags & 1 ); + + QComboBox * cmbSrc = dlgCombo( vbox, "Source Blend Mode", blendModes, chkBlend ); + cmbSrc->setCurrentIndex( flags >> 1 & 0x0f ); + + QComboBox * cmbDst = dlgCombo( vbox, "Destination Blend Mode", blendModes, chkBlend ); + cmbDst->setCurrentIndex( flags >> 5 & 0x0f ); + + QCheckBox * chkTest = dlgCheck( vbox, "Enable Testing" ); + chkTest->setChecked( flags & ( 1 << 9 ) ); + + QComboBox * cmbTest = dlgCombo( vbox, "Alpha Test Function", testModes, chkTest ); + cmbTest->setCurrentIndex( flags >> 10 & 0x07 ); + + QSpinBox * spnTest = dlgSpin( vbox, "Alpha Test Threshold", 0x00, 0xff, chkTest ); + spnTest->setValue( nif->get( nif->getBlock( index ), "Threshold" ) ); + + QCheckBox * chkSort = dlgCheck( vbox, "No Sorter" ); + chkSort->setChecked( ( flags & 0x2000 ) != 0 ); + + dlgButtons( &dlg, vbox ); + + if ( dlg.exec() == QDialog::Accepted ) + { + flags = flags & 0xfffe; + if ( chkBlend->isChecked() ) + { + flags |= 1; + flags = flags & 0xffe1 | cmbSrc->currentIndex() << 1; + flags = flags & 0xfe1f | cmbDst->currentIndex() << 5; + } + + flags = flags & 0xe1ff; + if ( chkTest->isChecked() ) + { + flags |= 0x0200; + flags = flags & 0xe3ff | ( cmbTest->currentIndex() << 10 ); + nif->set( nif->getBlock( index ), "Threshold", spnTest->value() ); + } + + flags = flags & 0xdfff | ( chkSort->isChecked() ? 0x2000 : 0 ); + + nif->set( index, flags ); + } + } + + void nodeFlags( NifModel * nif, const QModelIndex & index ) + { + quint16 flags = nif->get( index ); + + QDialog dlg; + QVBoxLayout * vbox = new QVBoxLayout; + dlg.setLayout( vbox ); + + QCheckBox * chkHidden = dlgCheck( vbox, "Hidden" ); + chkHidden->setChecked( flags & 1 ); + + QComboBox * cmbCollision = dlgCombo( vbox, "Collision Detection", QStringList() << "None" << "Triangles" << "Bounding Box" << "Continue" ); + cmbCollision->setCurrentIndex( flags >> 1 & 3 ); + + QCheckBox * chkSkin = dlgCheck( vbox, "Skin Influence" ); + chkSkin->setChecked( ! ( flags & 8 ) ); + + dlgButtons( &dlg, vbox ); + + if ( dlg.exec() == QDialog::Accepted ) + { + flags = flags & 0xfffe | ( chkHidden->isChecked() ? 1 : 0 ); + flags = flags & 0xfff9 | ( cmbCollision->currentIndex() << 1 ); + flags = flags & 0xfff7 | ( chkSkin->isChecked() ? 0 : 8 ); + nif->set( index, flags ); + } + } + + void controllerFlags( NifModel * nif, const QModelIndex & index ) + { + quint16 flags = nif->get( index ); + + QDialog dlg; + QVBoxLayout * vbox = new QVBoxLayout; + dlg.setLayout( vbox ); + + QCheckBox * chkActive = dlgCheck( vbox, "Active" ); + chkActive->setChecked( flags & 8 ); + + QComboBox * cmbLoop = dlgCombo( vbox, "Loop Mode", QStringList() << "Cycle" << "Reverse" << "Clamp" ); + cmbLoop->setCurrentIndex( flags >> 1 & 3 ); + + dlgButtons( &dlg, vbox ); + + if ( dlg.exec() == QDialog::Accepted ) + { + flags = flags & 0xfff7 | ( chkActive->isChecked() ? 8 : 0 ); + flags = flags & 0xfff9 | cmbLoop->currentIndex() << 1; + nif->set( index, flags ); + } + } + + void bodyFlags( NifModel * nif, const QModelIndex & index ) + { + quint16 flags = nif->get( index ); + + QDialog dlg; + QVBoxLayout * vbox = new QVBoxLayout( &dlg ); + + QCheckBox * chkLinked = dlgCheck( vbox, "Linked" ); + chkLinked->setChecked( flags & 0x80 ); + QCheckBox * chkNoCol = dlgCheck( vbox, "No Collision" ); + chkNoCol->setChecked( flags & 0x40 ); + QCheckBox * chkScaled = dlgCheck( vbox, "Scaled" ); + chkScaled->setChecked( flags & 0x20 ); + + QSpinBox * spnPartNo = dlgSpin( vbox, "Part Number", 0, 0x1f, chkLinked ); + spnPartNo->setValue( flags & 0x1f ); + + dlgButtons( &dlg, vbox ); + + if ( dlg.exec() == QDialog::Accepted ) + { + flags = flags & 0x7f | ( chkLinked->isChecked() ? 0x80 : 0 ); + flags = flags & 0xbf | ( chkNoCol->isChecked() ? 0x40 : 0 ); + flags = flags & 0xdf | ( chkScaled->isChecked() ? 0x20 : 0 ); + flags = flags & 0xe0 | ( chkLinked->isChecked() ? spnPartNo->value() : 0 ); + nif->set( index, flags ); + nif->set( index.parent(), "Col Filter Copy", flags ); + } + } + + void shapeFlags( NifModel * nif, const QModelIndex & index ) + { + quint16 flags = nif->get( index ); + + QDialog dlg; + QVBoxLayout * vbox = new QVBoxLayout; + dlg.setLayout( vbox ); + + QCheckBox * chkHidden = dlgCheck( vbox, "Hidden" ); + chkHidden->setChecked( flags & 0x01 ); + + QComboBox * cmbCollision = dlgCombo( vbox, "Collision Detection", QStringList() << "None" << "Triangles" << "Bounding Box" << "Continue" ); + cmbCollision->setCurrentIndex( flags >> 1 & 3 ); + + QCheckBox * chkShadow = 0; + if ( nif->checkVersion( 0x04000002, 0x04000002 ) ) + { + chkShadow = dlgCheck( vbox, "Shadow" ); + chkShadow->setChecked( flags & 0x40 ); + } + + dlgButtons( &dlg, vbox ); + + if ( dlg.exec() == QDialog::Accepted ) + { + flags = flags & 0xfffe | ( chkHidden->isChecked() ? 0x01 : 0 ); + flags = flags & 0xfff9 | ( cmbCollision->currentIndex() << 1 ); + if ( chkShadow ) + flags = flags & 0xffbf | ( chkShadow->isChecked() ? 0x40 : 0 ); + nif->set( index, flags ); + } + } + + void zbufferFlags( NifModel * nif, const QModelIndex & index ) + { + quint16 flags = nif->get( index ); + + QDialog dlg; + QVBoxLayout * vbox = new QVBoxLayout; + dlg.setLayout( vbox ); + + QCheckBox * chkEnable = dlgCheck( vbox, "Enable Z Buffer" ); + chkEnable->setChecked( flags & 1 ); + + QCheckBox * chkROnly = dlgCheck( vbox, "Z Buffer Read Only" ); + chkROnly->setChecked( ( flags & 2 ) == 0 ); + + QComboBox * cmbFunc = dlgCombo( vbox, "Z Buffer Test Function", QStringList() << "Always" << "Less" << "Equal" << "Less or Equal" << "Greater" << "Not Equal" << "Greater or Equal" << "Never", chkEnable ); + if ( nif->checkVersion( 0x04010012, 0 ) ) + cmbFunc->setCurrentIndex( nif->get( nif->getBlock( index ), "Function" ) ); + else + cmbFunc->setCurrentIndex( ( flags >> 2 ) & 0x07 ); + + dlgButtons( &dlg, vbox ); + + if ( dlg.exec() == QDialog::Accepted ) + { + flags = flags & 0xfffe | ( chkEnable->isChecked() ? 1 : 0 ); + flags = flags & 0xfffd | ( chkROnly->isChecked() ? 0 : 2 ); + if ( nif->checkVersion( 0x04010012, 0 ) ) + nif->set( nif->getBlock( index ), "Function", cmbFunc->currentIndex() ); + else + flags = flags & 0xffe3 | ( cmbFunc->currentIndex() << 2 ); + nif->set( index, flags ); + } + } + + void bsxFlags( NifModel * nif, const QModelIndex & index ) + { + quint32 flags = nif->get( index ); + + QDialog dlg; + QVBoxLayout * vbox = new QVBoxLayout( & dlg ); + + QStringList flagNames( QStringList() + << Spell::tr( "Enable Animation" ) // 1 + << Spell::tr( "Enable Collision" ) // 2 + << Spell::tr( "Is Skeleton Nif (?)" ) // 4 + << Spell::tr( "Unidentified Flag (?)" ) // 8 + << Spell::tr( "FlameNodes Present" ) // 16 + << Spell::tr( "EditorMarkers Present" ) // 32 + ); + + QList chkBoxes; + int x = 0; + foreach ( QString flagName, flagNames ) + { + chkBoxes << dlgCheck( vbox, QString( "%1 (%2)" ).arg( flagName ).arg( 1 << x ) ); + chkBoxes.last()->setChecked( flags & ( 1 << x ) ); + x++; + } + + dlgButtons( &dlg, vbox ); + + if ( dlg.exec() == QDialog::Accepted ) + { + x = 0; + foreach ( QCheckBox * chk, chkBoxes ) + { + flags = flags & ( ~ ( 1 << x ) ) | ( chk->isChecked() ? 1 << x : 0 ); + x++; + } + nif->set( index, flags ); + } + } + + QCheckBox * dlgCheck( QVBoxLayout * vbox, const QString & name, QCheckBox * chk = 0 ) + { + QCheckBox * box = new QCheckBox( name ); + vbox->addWidget( box ); + if ( chk ) + { + QObject::connect( chk, SIGNAL( toggled( bool ) ), box, SLOT( setEnabled( bool ) ) ); + box->setEnabled( chk->isChecked() ); + } + return box; + } + + QComboBox * dlgCombo( QVBoxLayout * vbox, const QString & name, QStringList items, QCheckBox * chk = 0 ) + { + vbox->addWidget( new QLabel( name ) ); + QComboBox * cmb = new QComboBox; + vbox->addWidget( cmb ); + cmb->addItems( items ); + if ( chk ) + { + QObject::connect( chk, SIGNAL( toggled( bool ) ), cmb, SLOT( setEnabled( bool ) ) ); + cmb->setEnabled( chk->isChecked() ); + } + return cmb; + } + + QSpinBox * dlgSpin( QVBoxLayout * vbox, const QString & name, int min, int max, QCheckBox * chk = 0 ) + { + vbox->addWidget( new QLabel( name ) ); + QSpinBox * spn = new QSpinBox; + vbox->addWidget( spn ); + spn->setRange( min, max ); + if ( chk ) + { + QObject::connect( chk, SIGNAL( toggled( bool ) ), spn, SLOT( setEnabled( bool ) ) ); + spn->setEnabled( chk->isChecked() ); + } + return spn; + } + + void dlgButtons( QDialog * dlg, QVBoxLayout * vbox ) + { + QHBoxLayout * hbox = new QHBoxLayout; + vbox->addLayout( hbox ); + + QPushButton * btAccept = new QPushButton( "Accept" ); + hbox->addWidget( btAccept ); + QObject::connect( btAccept, SIGNAL( clicked() ), dlg, SLOT( accept() ) ); + + QPushButton * btReject = new QPushButton( "Cancel" ); + hbox->addWidget( btReject ); + QObject::connect( btReject, SIGNAL( clicked() ), dlg, SLOT( reject() ) ); + } +}; + +REGISTER_SPELL( spEditFlags ) diff --git a/spells/havok.cpp b/spells/havok.cpp index 8c63305fb..121b4a099 100644 --- a/spells/havok.cpp +++ b/spells/havok.cpp @@ -1,330 +1,330 @@ -#include "../spellbook.h" - -#include "../NvTriStrip/qtwrapper.h" - -#include "blocks.h" - -#include - -//Wz didn't provide enough code for this to work. I don't see any reason QHull can't be put on SVN, but he seems to have changed -//the parameters of the example compute_convex_hull function, so I'll just leave it defined out until someone has a chance to dig -//into it or provide an alternative implementation without QHull. -#ifdef USE_QHULL - -class spCreateCVS : public Spell -{ -public: - QString name() const { return "Create Convex Shape"; } - QString page() const { return "Havok"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - - if( !nif->inherits( index, "NiTriBasedGeom" ) ) - return false; - - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); - return iData.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); - if( !iData.isValid() ) - return index; - - /* those will be filled with the CVS data */ - QVector convex_verts, convex_norms; - - /* get the verts of our mesh */ - QVector verts = nif->getArray( iData, "Vertices" ); - - /* make a convex hull from it */ - compute_convex_hull( verts, convex_verts, convex_norms ); - - /* create the CVS block */ - QModelIndex iCVS = nif->insertNiBlock( "bhkConvexVerticesShape" ); - - /* set CVS verts */ - nif->set( iCVS, "Num Vertices", convex_verts.count() ); - nif->updateArray( iCVS, "Vertices" ); - nif->setArray( iCVS, "Vertices", convex_verts ); - - /* set CVS norms */ - nif->set( iCVS, "Num Half-Spaces", convex_norms.count() ); - nif->updateArray( iCVS, "Half-Spaces" ); - nif->setArray( iCVS, "Half-Spaces", convex_norms ); - - return iCVS; - } -}; - -REGISTER_SPELL( spCreateCVS ); -#endif - - -class spConstraintHelper : public Spell -{ -public: - QString name() const { return "A -> B"; } - QString page() const { return "Havok"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif && ( - nif->isNiBlock( nif->getBlock( index ), "bhkMalleableConstraint" ) - || nif->isNiBlock( nif->getBlock( index ), "bhkRagdollConstraint" ) - || nif->isNiBlock( nif->getBlock( index ), "bhkLimitedHingeConstraint" ) - || nif->isNiBlock( nif->getBlock( index ), "bhkHingeConstraint" ) - || nif->isNiBlock( nif->getBlock( index ), "bhkPrismaticConstraint" ) ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iConstraint = nif->getBlock( index ); - QString name = nif->itemName( iConstraint ); - if ( name == "bhkMalleableConstraint" ) - { - if ( nif->getIndex( iConstraint, "Ragdoll" ).isValid() ) - { - name = "bhkRagdollConstraint"; - } - else if ( nif->getIndex( iConstraint, "Limited Hinge" ).isValid() ) - { - name = "bhkLimitedHingeConstraint"; - } - } - - QModelIndex iBodyA = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 0, 0 ) ), "bhkRigidBody" ); - QModelIndex iBodyB = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 1, 0 ) ), "bhkRigidBody" ); - - if ( ! iBodyA.isValid() || ! iBodyB.isValid() ) - { - qWarning() << "coudn't find the bodies for this constraint"; - return index; - } - - Transform transA = bodyTrans( nif, iBodyA ); - Transform transB = bodyTrans( nif, iBodyB ); - - if ( name == "bhkLimitedHingeConstraint" ) - { - iConstraint = nif->getIndex( iConstraint, "Limited Hinge" ); - if ( ! iConstraint.isValid() ) - return index; - } - - if ( name == "bhkRagdollConstraint" ) - { - iConstraint = nif->getIndex( iConstraint, "Ragdoll" ); - if ( ! iConstraint.isValid() ) - return index; - } - - Vector3 pivot = Vector3( nif->get( iConstraint, "Pivot A" ) ) * 7.0; - pivot = transA * pivot; - pivot = transB.rotation.inverted() * ( pivot - transB.translation ) / transB.scale / 7.0; - nif->set( iConstraint, "Pivot B", Vector4( pivot[0], pivot[1], pivot[2], 0 ) ); - - if ( name == "bhkLimitedHingeConstraint" ) - { - Vector3 axle = Vector3( nif->get( iConstraint, "Axle A" ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, "Axle B", Vector4( axle[0], axle[1], axle[2], 0 ) ); - - axle = Vector3( nif->get( iConstraint, "Perp2AxleInA2" ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, "Perp2AxleInB2", Vector4( axle[0], axle[1], axle[2], 0 ) ); - } - - if ( name == "bhkRagdollConstraint" ) - { - Vector3 axle = Vector3( nif->get( iConstraint, "Plane A" ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, "Plane B", Vector4( axle[0], axle[1], axle[2], 0 ) ); - - axle = Vector3( nif->get( iConstraint, "Twist A" ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, "Twist B", Vector4( axle[0], axle[1], axle[2], 0 ) ); - } - - return index; - } - - static Transform bodyTrans( const NifModel * nif, const QModelIndex & index ) - { - Transform t; - if ( nif->isNiBlock( index, "bhkRigidBodyT" ) ) - { - t.translation = nif->get( index, "Translation" ) * 7; - t.rotation.fromQuat( nif->get( index, "Rotation" ) ); - } - - qint32 l = nif->getBlockNumber( index ); - - while ( ( l = nif->getParent( l ) ) >= 0 ) - { - QModelIndex iAV = nif->getBlock( l, "NiAVObject" ); - if ( iAV.isValid() ) - t = Transform( nif, iAV ) * t; - } - - return t; - } -}; - -REGISTER_SPELL( spConstraintHelper ) - - -class spStiffSpringHelper : public Spell -{ -public: - QString name() const { return tr( "Calculate Spring Length" ); } - QString page() const { return tr( "Havok" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & idx ) - { - return nif && nif->isNiBlock( nif->getBlock( idx ), "bhkStiffSpringConstraint" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & idx ) - { - QModelIndex iConstraint = nif->getBlock( idx ); - - QModelIndex iBodyA = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 0, 0 ) ), "bhkRigidBody" ); - QModelIndex iBodyB = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 1, 0 ) ), "bhkRigidBody" ); - - if ( ! iBodyA.isValid() || ! iBodyB.isValid() ) - { - qWarning() << "coudn't find the bodies for this constraint"; - return idx; - } - - Transform transA = spConstraintHelper::bodyTrans( nif, iBodyA ); - Transform transB = spConstraintHelper::bodyTrans( nif, iBodyB ); - - Vector3 pivotA( nif->get( iConstraint, "Pivot A" ) * 7 ); - Vector3 pivotB( nif->get( iConstraint, "Pivot B" ) * 7 ); - - float length = ( transA * pivotA - transB * pivotB ).length() / 7; - - nif->set( iConstraint, "Length", length ); - - return nif->getIndex( iConstraint, "Length" ); - } -}; - -REGISTER_SPELL( spStiffSpringHelper ) - - -class spPackHavokStrips : public Spell -{ -public: - QString name() const { return Spell::tr( "Pack Strips" ); } - QString page() const { return Spell::tr( "Havok" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & idx ) - { - return nif->isNiBlock( idx, "bhkNiTriStripsShape" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) - { - QPersistentModelIndex iShape( iBlock ); - - QVector vertices; - QVector triangles; - QVector normals; - - foreach ( qint32 lData, nif->getLinkArray( iShape, "Strips Data" ) ) - { - QModelIndex iData = nif->getBlock( lData, "NiTriStripsData" ); - - if ( iData.isValid() ) - { - QVector vrts = nif->getArray( iData, "Vertices" ); - QVector tris; - QVector nrms; - - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - for ( int x = 0; x < nif->rowCount( iPoints ); x++ ) - { - tris += triangulate( nif->getArray( iPoints.child( x, 0 ) ) ); - } - - QMutableVectorIterator it( tris ); - while ( it.hasNext() ) - { - Triangle & tri = it.next(); - - Vector3 a = vrts.value( tri[0] ); - Vector3 b = vrts.value( tri[1] ); - Vector3 c = vrts.value( tri[2] ); - - nrms << Vector3::crossproduct( b - a, c - a ).normalize(); - - tri[0] += vertices.count(); - tri[1] += vertices.count(); - tri[2] += vertices.count(); - } - - foreach ( Vector3 v, vrts ) - vertices += v / 7; - triangles += tris; - normals += nrms; - } - } - - if ( vertices.isEmpty() || triangles.isEmpty() ) - { - qWarning() << Spell::tr( "no mesh data was found" ); - return iShape; - } - - QPersistentModelIndex iPackedShape = nif->insertNiBlock( "bhkPackedNiTriStripsShape", nif->getBlockNumber( iShape ) ); - - nif->set( iPackedShape, "Num Sub Shapes", 1 ); - QModelIndex iSubShapes = nif->getIndex( iPackedShape, "Sub Shapes" ); - nif->updateArray( iSubShapes ); - nif->set( iSubShapes.child( 0, 0 ), "Layer", 1 ); - nif->set( iSubShapes.child( 0, 0 ), "Num Vertices", vertices.count() ); - nif->setArray( iPackedShape, "Unknown Floats", QVector() << 0.0f << 0.0f << 0.1f << 0.0f << 1.0f << 1.0f << 1.0f << 1.0f << 0.1f ); - nif->set( iPackedShape, "Scale", 1.0f ); - nif->setArray( iPackedShape, "Unknown Floats 2", QVector() << 1.0f << 1.0f << 1.0f ); - - QModelIndex iPackedData = nif->insertNiBlock( "hkPackedNiTriStripsData", nif->getBlockNumber( iPackedShape ) ); - nif->setLink( iPackedShape, "Data", nif->getBlockNumber( iPackedData ) ); - - nif->set( iPackedData, "Num Triangles", triangles.count() ); - QModelIndex iTriangles = nif->getIndex( iPackedData, "Triangles" ); - nif->updateArray( iTriangles ); - for ( int t = 0; t < triangles.size(); t++ ) - { - nif->set( iTriangles.child( t, 0 ), "Triangle", triangles[ t ] ); - nif->set( iTriangles.child( t, 0 ), "Normal", normals.value( t ) ); - } - - nif->set( iPackedData, "Num Vertices", vertices.count() ); - QModelIndex iVertices = nif->getIndex( iPackedData, "Vertices" ); - nif->updateArray( iVertices ); - nif->setArray( iVertices, vertices ); - - QMap lnkmap; - lnkmap.insert( nif->getBlockNumber( iShape ), nif->getBlockNumber( iPackedShape ) ); - nif->mapLinks( lnkmap ); - - // *** THIS SOMETIMES CRASHES NIFSKOPE *** - // *** UNCOMMENT WHEN BRANCH REMOVER IS FIXED *** - //spRemoveBranch BranchRemover; - //BranchRemover.castIfApplicable( nif, iShape ); - - return iPackedShape; - } -}; - -REGISTER_SPELL( spPackHavokStrips ) - +#include "../spellbook.h" + +#include "../NvTriStrip/qtwrapper.h" + +#include "blocks.h" + +#include + +//Wz didn't provide enough code for this to work. I don't see any reason QHull can't be put on SVN, but he seems to have changed +//the parameters of the example compute_convex_hull function, so I'll just leave it defined out until someone has a chance to dig +//into it or provide an alternative implementation without QHull. +#ifdef USE_QHULL + +class spCreateCVS : public Spell +{ +public: + QString name() const { return "Create Convex Shape"; } + QString page() const { return "Havok"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + + if( !nif->inherits( index, "NiTriBasedGeom" ) ) + return false; + + QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + return iData.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + if( !iData.isValid() ) + return index; + + /* those will be filled with the CVS data */ + QVector convex_verts, convex_norms; + + /* get the verts of our mesh */ + QVector verts = nif->getArray( iData, "Vertices" ); + + /* make a convex hull from it */ + compute_convex_hull( verts, convex_verts, convex_norms ); + + /* create the CVS block */ + QModelIndex iCVS = nif->insertNiBlock( "bhkConvexVerticesShape" ); + + /* set CVS verts */ + nif->set( iCVS, "Num Vertices", convex_verts.count() ); + nif->updateArray( iCVS, "Vertices" ); + nif->setArray( iCVS, "Vertices", convex_verts ); + + /* set CVS norms */ + nif->set( iCVS, "Num Half-Spaces", convex_norms.count() ); + nif->updateArray( iCVS, "Half-Spaces" ); + nif->setArray( iCVS, "Half-Spaces", convex_norms ); + + return iCVS; + } +}; + +REGISTER_SPELL( spCreateCVS ); +#endif + + +class spConstraintHelper : public Spell +{ +public: + QString name() const { return "A -> B"; } + QString page() const { return "Havok"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif && ( + nif->isNiBlock( nif->getBlock( index ), "bhkMalleableConstraint" ) + || nif->isNiBlock( nif->getBlock( index ), "bhkRagdollConstraint" ) + || nif->isNiBlock( nif->getBlock( index ), "bhkLimitedHingeConstraint" ) + || nif->isNiBlock( nif->getBlock( index ), "bhkHingeConstraint" ) + || nif->isNiBlock( nif->getBlock( index ), "bhkPrismaticConstraint" ) ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iConstraint = nif->getBlock( index ); + QString name = nif->itemName( iConstraint ); + if ( name == "bhkMalleableConstraint" ) + { + if ( nif->getIndex( iConstraint, "Ragdoll" ).isValid() ) + { + name = "bhkRagdollConstraint"; + } + else if ( nif->getIndex( iConstraint, "Limited Hinge" ).isValid() ) + { + name = "bhkLimitedHingeConstraint"; + } + } + + QModelIndex iBodyA = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 0, 0 ) ), "bhkRigidBody" ); + QModelIndex iBodyB = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 1, 0 ) ), "bhkRigidBody" ); + + if ( ! iBodyA.isValid() || ! iBodyB.isValid() ) + { + qWarning() << "coudn't find the bodies for this constraint"; + return index; + } + + Transform transA = bodyTrans( nif, iBodyA ); + Transform transB = bodyTrans( nif, iBodyB ); + + if ( name == "bhkLimitedHingeConstraint" ) + { + iConstraint = nif->getIndex( iConstraint, "Limited Hinge" ); + if ( ! iConstraint.isValid() ) + return index; + } + + if ( name == "bhkRagdollConstraint" ) + { + iConstraint = nif->getIndex( iConstraint, "Ragdoll" ); + if ( ! iConstraint.isValid() ) + return index; + } + + Vector3 pivot = Vector3( nif->get( iConstraint, "Pivot A" ) ) * 7.0; + pivot = transA * pivot; + pivot = transB.rotation.inverted() * ( pivot - transB.translation ) / transB.scale / 7.0; + nif->set( iConstraint, "Pivot B", Vector4( pivot[0], pivot[1], pivot[2], 0 ) ); + + if ( name == "bhkLimitedHingeConstraint" ) + { + Vector3 axle = Vector3( nif->get( iConstraint, "Axle A" ) ); + axle = transA.rotation * axle; + axle = transB.rotation.inverted() * axle; + nif->set( iConstraint, "Axle B", Vector4( axle[0], axle[1], axle[2], 0 ) ); + + axle = Vector3( nif->get( iConstraint, "Perp2AxleInA2" ) ); + axle = transA.rotation * axle; + axle = transB.rotation.inverted() * axle; + nif->set( iConstraint, "Perp2AxleInB2", Vector4( axle[0], axle[1], axle[2], 0 ) ); + } + + if ( name == "bhkRagdollConstraint" ) + { + Vector3 axle = Vector3( nif->get( iConstraint, "Plane A" ) ); + axle = transA.rotation * axle; + axle = transB.rotation.inverted() * axle; + nif->set( iConstraint, "Plane B", Vector4( axle[0], axle[1], axle[2], 0 ) ); + + axle = Vector3( nif->get( iConstraint, "Twist A" ) ); + axle = transA.rotation * axle; + axle = transB.rotation.inverted() * axle; + nif->set( iConstraint, "Twist B", Vector4( axle[0], axle[1], axle[2], 0 ) ); + } + + return index; + } + + static Transform bodyTrans( const NifModel * nif, const QModelIndex & index ) + { + Transform t; + if ( nif->isNiBlock( index, "bhkRigidBodyT" ) ) + { + t.translation = nif->get( index, "Translation" ) * 7; + t.rotation.fromQuat( nif->get( index, "Rotation" ) ); + } + + qint32 l = nif->getBlockNumber( index ); + + while ( ( l = nif->getParent( l ) ) >= 0 ) + { + QModelIndex iAV = nif->getBlock( l, "NiAVObject" ); + if ( iAV.isValid() ) + t = Transform( nif, iAV ) * t; + } + + return t; + } +}; + +REGISTER_SPELL( spConstraintHelper ) + + +class spStiffSpringHelper : public Spell +{ +public: + QString name() const { return tr( "Calculate Spring Length" ); } + QString page() const { return tr( "Havok" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & idx ) + { + return nif && nif->isNiBlock( nif->getBlock( idx ), "bhkStiffSpringConstraint" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & idx ) + { + QModelIndex iConstraint = nif->getBlock( idx ); + + QModelIndex iBodyA = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 0, 0 ) ), "bhkRigidBody" ); + QModelIndex iBodyB = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 1, 0 ) ), "bhkRigidBody" ); + + if ( ! iBodyA.isValid() || ! iBodyB.isValid() ) + { + qWarning() << "coudn't find the bodies for this constraint"; + return idx; + } + + Transform transA = spConstraintHelper::bodyTrans( nif, iBodyA ); + Transform transB = spConstraintHelper::bodyTrans( nif, iBodyB ); + + Vector3 pivotA( nif->get( iConstraint, "Pivot A" ) * 7 ); + Vector3 pivotB( nif->get( iConstraint, "Pivot B" ) * 7 ); + + float length = ( transA * pivotA - transB * pivotB ).length() / 7; + + nif->set( iConstraint, "Length", length ); + + return nif->getIndex( iConstraint, "Length" ); + } +}; + +REGISTER_SPELL( spStiffSpringHelper ) + + +class spPackHavokStrips : public Spell +{ +public: + QString name() const { return Spell::tr( "Pack Strips" ); } + QString page() const { return Spell::tr( "Havok" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & idx ) + { + return nif->isNiBlock( idx, "bhkNiTriStripsShape" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) + { + QPersistentModelIndex iShape( iBlock ); + + QVector vertices; + QVector triangles; + QVector normals; + + foreach ( qint32 lData, nif->getLinkArray( iShape, "Strips Data" ) ) + { + QModelIndex iData = nif->getBlock( lData, "NiTriStripsData" ); + + if ( iData.isValid() ) + { + QVector vrts = nif->getArray( iData, "Vertices" ); + QVector tris; + QVector nrms; + + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + for ( int x = 0; x < nif->rowCount( iPoints ); x++ ) + { + tris += triangulate( nif->getArray( iPoints.child( x, 0 ) ) ); + } + + QMutableVectorIterator it( tris ); + while ( it.hasNext() ) + { + Triangle & tri = it.next(); + + Vector3 a = vrts.value( tri[0] ); + Vector3 b = vrts.value( tri[1] ); + Vector3 c = vrts.value( tri[2] ); + + nrms << Vector3::crossproduct( b - a, c - a ).normalize(); + + tri[0] += vertices.count(); + tri[1] += vertices.count(); + tri[2] += vertices.count(); + } + + foreach ( Vector3 v, vrts ) + vertices += v / 7; + triangles += tris; + normals += nrms; + } + } + + if ( vertices.isEmpty() || triangles.isEmpty() ) + { + qWarning() << Spell::tr( "no mesh data was found" ); + return iShape; + } + + QPersistentModelIndex iPackedShape = nif->insertNiBlock( "bhkPackedNiTriStripsShape", nif->getBlockNumber( iShape ) ); + + nif->set( iPackedShape, "Num Sub Shapes", 1 ); + QModelIndex iSubShapes = nif->getIndex( iPackedShape, "Sub Shapes" ); + nif->updateArray( iSubShapes ); + nif->set( iSubShapes.child( 0, 0 ), "Layer", 1 ); + nif->set( iSubShapes.child( 0, 0 ), "Num Vertices", vertices.count() ); + nif->setArray( iPackedShape, "Unknown Floats", QVector() << 0.0f << 0.0f << 0.1f << 0.0f << 1.0f << 1.0f << 1.0f << 1.0f << 0.1f ); + nif->set( iPackedShape, "Scale", 1.0f ); + nif->setArray( iPackedShape, "Unknown Floats 2", QVector() << 1.0f << 1.0f << 1.0f ); + + QModelIndex iPackedData = nif->insertNiBlock( "hkPackedNiTriStripsData", nif->getBlockNumber( iPackedShape ) ); + nif->setLink( iPackedShape, "Data", nif->getBlockNumber( iPackedData ) ); + + nif->set( iPackedData, "Num Triangles", triangles.count() ); + QModelIndex iTriangles = nif->getIndex( iPackedData, "Triangles" ); + nif->updateArray( iTriangles ); + for ( int t = 0; t < triangles.size(); t++ ) + { + nif->set( iTriangles.child( t, 0 ), "Triangle", triangles[ t ] ); + nif->set( iTriangles.child( t, 0 ), "Normal", normals.value( t ) ); + } + + nif->set( iPackedData, "Num Vertices", vertices.count() ); + QModelIndex iVertices = nif->getIndex( iPackedData, "Vertices" ); + nif->updateArray( iVertices ); + nif->setArray( iVertices, vertices ); + + QMap lnkmap; + lnkmap.insert( nif->getBlockNumber( iShape ), nif->getBlockNumber( iPackedShape ) ); + nif->mapLinks( lnkmap ); + + // *** THIS SOMETIMES CRASHES NIFSKOPE *** + // *** UNCOMMENT WHEN BRANCH REMOVER IS FIXED *** + //spRemoveBranch BranchRemover; + //BranchRemover.castIfApplicable( nif, iShape ); + + return iPackedShape; + } +}; + +REGISTER_SPELL( spPackHavokStrips ) + diff --git a/spells/headerstring.cpp b/spells/headerstring.cpp index dea29db6e..cacd0cb8d 100644 --- a/spells/headerstring.cpp +++ b/spells/headerstring.cpp @@ -1,138 +1,138 @@ -#include "../spellbook.h" - -#include -#include -#include -#include -#include -#include -#include - -/* XPM */ -static char * txt_xpm[] = { -"32 32 36 1", -" c None", -". c #FFFFFF","+ c #000000","@ c #BDBDBD","# c #717171","$ c #252525", -"% c #4F4F4F","& c #A9A9A9","* c #A8A8A8","= c #555555","- c #EAEAEA", -"; c #151515","> c #131313",", c #D0D0D0","' c #AAAAAA",") c #080808", -"! c #ABABAB","~ c #565656","{ c #D1D1D1","] c #4D4D4D","^ c #4E4E4E", -"/ c #FDFDFD","( c #A4A4A4","_ c #0A0A0A",": c #A5A5A5","< c #050505", -"[ c #C4C4C4","} c #E9E9E9","| c #D5D5D5","1 c #141414","2 c #3E3E3E", -"3 c #DDDDDD","4 c #424242","5 c #070707","6 c #040404","7 c #202020", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ........... .... ", -" .+++++++++. .@#$. ", -" .+++++++++. .+++. ", -" ....+++..............+++... ", -" .+++. %++&.*++=++++++. ", -" .+++. .-;+>,>+;-++++++. ", -" .+++. .'++)++!..+++... ", -" .+++. .=+++~. .+++. ", -" .+++. .{+++{. .+++. ", -" .+++. .]+++^. .+++/ ", -" .+++. .(++_++:..<++[.. ", -" .+++. .}>+;|;+1}.2++++. ", -" .+++. ^++'.'++%.34567. ", -" ..... ................. ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" "}; - -static QIcon * txt_xpm_icon = 0; - -class spEditStringIndex : public Spell -{ -public: - QString name() const { return "Edit String Index"; } - QString page() const { return ""; } - QIcon icon() const - { - if ( ! txt_xpm_icon ) - txt_xpm_icon = new QIcon( txt_xpm ); - return *txt_xpm_icon; - } - bool instant() const { return true; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - NifValue::Type type = nif->getValue( index ).type() ; - if (type == NifValue::tStringIndex) - return true; - if ((type == NifValue::tString || type == NifValue::tFilePath) && nif->checkVersion( 0x14010003, 0 ) ) - return true; - return false; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - int offset = nif->get( index ); - QStringList strings; - QString string; - if (nif->getValue( index ).type() == NifValue::tStringIndex && nif->checkVersion( 0x14010003, 0 ) ) - { - QModelIndex header = nif->getHeader(); - QVector stringVector = nif->getArray( header, "Strings" ); - strings = stringVector.toList(); - if (offset >= 0 && offset < stringVector.size()) - string = stringVector.at(offset); - } - else - { - return index; - } - - QDialog dlg; - - QLabel * lb = new QLabel( & dlg ); - lb->setText( "Select a string or enter a new one" ); - - QListWidget * lw = new QListWidget( & dlg ); - lw->addItems( strings ); - - QLineEdit * le = new QLineEdit( & dlg ); - le->setText( string ); - le->setFocus(); - - QObject::connect( lw, SIGNAL( currentTextChanged( const QString & ) ), le, SLOT( setText( const QString & ) ) ); - QObject::connect( lw, SIGNAL( itemActivated( QListWidgetItem * ) ), & dlg, SLOT( accept() ) ); - QObject::connect( le, SIGNAL( returnPressed() ), & dlg, SLOT( accept() ) ); - - QPushButton * bo = new QPushButton( "Ok", & dlg ); - QObject::connect( bo, SIGNAL( clicked() ), & dlg, SLOT( accept() ) ); - - QPushButton * bc = new QPushButton( "Cancel", & dlg ); - QObject::connect( bc, SIGNAL( clicked() ), & dlg, SLOT( reject() ) ); - - QGridLayout * grid = new QGridLayout; - dlg.setLayout( grid ); - grid->addWidget( lb, 0, 0, 1, 2 ); - grid->addWidget( lw, 1, 0, 1, 2 ); - grid->addWidget( le, 2, 0, 1, 2 ); - grid->addWidget( bo, 3, 0, 1, 1 ); - grid->addWidget( bc, 3, 1, 1, 1 ); - - if ( dlg.exec() != QDialog::Accepted ) - return index; - - nif->set( index, le->text() ); - - return index; - } -}; - -REGISTER_SPELL( spEditStringIndex ) - +#include "../spellbook.h" + +#include +#include +#include +#include +#include +#include +#include + +/* XPM */ +static char * txt_xpm[] = { +"32 32 36 1", +" c None", +". c #FFFFFF","+ c #000000","@ c #BDBDBD","# c #717171","$ c #252525", +"% c #4F4F4F","& c #A9A9A9","* c #A8A8A8","= c #555555","- c #EAEAEA", +"; c #151515","> c #131313",", c #D0D0D0","' c #AAAAAA",") c #080808", +"! c #ABABAB","~ c #565656","{ c #D1D1D1","] c #4D4D4D","^ c #4E4E4E", +"/ c #FDFDFD","( c #A4A4A4","_ c #0A0A0A",": c #A5A5A5","< c #050505", +"[ c #C4C4C4","} c #E9E9E9","| c #D5D5D5","1 c #141414","2 c #3E3E3E", +"3 c #DDDDDD","4 c #424242","5 c #070707","6 c #040404","7 c #202020", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ........... .... ", +" .+++++++++. .@#$. ", +" .+++++++++. .+++. ", +" ....+++..............+++... ", +" .+++. %++&.*++=++++++. ", +" .+++. .-;+>,>+;-++++++. ", +" .+++. .'++)++!..+++... ", +" .+++. .=+++~. .+++. ", +" .+++. .{+++{. .+++. ", +" .+++. .]+++^. .+++/ ", +" .+++. .(++_++:..<++[.. ", +" .+++. .}>+;|;+1}.2++++. ", +" .+++. ^++'.'++%.34567. ", +" ..... ................. ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; + +static QIcon * txt_xpm_icon = 0; + +class spEditStringIndex : public Spell +{ +public: + QString name() const { return "Edit String Index"; } + QString page() const { return ""; } + QIcon icon() const + { + if ( ! txt_xpm_icon ) + txt_xpm_icon = new QIcon( txt_xpm ); + return *txt_xpm_icon; + } + bool instant() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + NifValue::Type type = nif->getValue( index ).type() ; + if (type == NifValue::tStringIndex) + return true; + if ((type == NifValue::tString || type == NifValue::tFilePath) && nif->checkVersion( 0x14010003, 0 ) ) + return true; + return false; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + int offset = nif->get( index ); + QStringList strings; + QString string; + if (nif->getValue( index ).type() == NifValue::tStringIndex && nif->checkVersion( 0x14010003, 0 ) ) + { + QModelIndex header = nif->getHeader(); + QVector stringVector = nif->getArray( header, "Strings" ); + strings = stringVector.toList(); + if (offset >= 0 && offset < stringVector.size()) + string = stringVector.at(offset); + } + else + { + return index; + } + + QDialog dlg; + + QLabel * lb = new QLabel( & dlg ); + lb->setText( "Select a string or enter a new one" ); + + QListWidget * lw = new QListWidget( & dlg ); + lw->addItems( strings ); + + QLineEdit * le = new QLineEdit( & dlg ); + le->setText( string ); + le->setFocus(); + + QObject::connect( lw, SIGNAL( currentTextChanged( const QString & ) ), le, SLOT( setText( const QString & ) ) ); + QObject::connect( lw, SIGNAL( itemActivated( QListWidgetItem * ) ), & dlg, SLOT( accept() ) ); + QObject::connect( le, SIGNAL( returnPressed() ), & dlg, SLOT( accept() ) ); + + QPushButton * bo = new QPushButton( "Ok", & dlg ); + QObject::connect( bo, SIGNAL( clicked() ), & dlg, SLOT( accept() ) ); + + QPushButton * bc = new QPushButton( "Cancel", & dlg ); + QObject::connect( bc, SIGNAL( clicked() ), & dlg, SLOT( reject() ) ); + + QGridLayout * grid = new QGridLayout; + dlg.setLayout( grid ); + grid->addWidget( lb, 0, 0, 1, 2 ); + grid->addWidget( lw, 1, 0, 1, 2 ); + grid->addWidget( le, 2, 0, 1, 2 ); + grid->addWidget( bo, 3, 0, 1, 1 ); + grid->addWidget( bc, 3, 1, 1, 1 ); + + if ( dlg.exec() != QDialog::Accepted ) + return index; + + nif->set( index, le->text() ); + + return index; + } +}; + +REGISTER_SPELL( spEditStringIndex ) + diff --git a/spells/light.cpp b/spells/light.cpp index 917034a5d..f39e0f46e 100644 --- a/spells/light.cpp +++ b/spells/light.cpp @@ -1,94 +1,94 @@ -#include "../spellbook.h" - -#include "../widgets/nifeditors.h" - -/* XPM */ -static char * light42_xpm[] = { -"24 24 43 1", -" c None", -". c #000100","+ c #0E0D02","@ c #111401","# c #151500","$ c #191903", -"% c #1E1D02","& c #201E00","* c #2A2C01","= c #2D2D00","- c #2E2F00", -"; c #2F3000","> c #3B3A00",", c #3D3C00","' c #3E3D00",") c #454300", -"! c #464800","~ c #494B00","{ c #525200","] c #565700","^ c #6B6900", -"/ c #6B6D00","( c #797A00","_ c #7E7F02",": c #848300","< c #9D9E03", -"[ c #A6A600","} c #B3B202","| c #B8B500","1 c #CFD000","2 c #D7D600", -"3 c #DDDB00","4 c #E4E200","5 c #E9E600","6 c #E8EB00","7 c #ECEF00", -"8 c #F1F300","9 c #F3F504","0 c #F6F800","a c #F9FA00","b c #FBFC00", -"c c #FEFE00","d c #FFFF01", -" -,'~'* ", -" $[8bbdb5_ ", -" }bddddddb[ ", -" :bddddddddb< ", -" -2dddddddddda; ", -" 'addddddddddb{ ", -" ~bddddddddddd^ ", -" ]dddddddddddd/ ", -" ~bddddddddddd{ ", -" ,addddddddddb; ", -" 3dddddddddd7 ", -" :adddddddd9: ", -" (8bddddb8) ", -" ,}4741|] ", -" +@##%& ", -" ...... ", -" ...... ", -" ...... ", -" ...... ", -" ...... ", -" ...... ", -" ...... ", -" ...... ", -" .... "}; - -QIcon * light42_xpm_icon = 0; - -class spLightEdit : public Spell -{ -public: - QString name() const { return "Light"; } - QString page() const { return ""; } - bool instant() const { return true; } - QIcon icon() const - { - if ( ! light42_xpm_icon ) light42_xpm_icon = new QIcon( light42_xpm ); - return *light42_xpm_icon; - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iBlock = nif->getBlock( index ); - QModelIndex sibling = index.sibling( index.row(), 0 ); - return index.isValid() && nif->inherits( iBlock, "NiLight" ) && ( iBlock == sibling || nif->getIndex( iBlock, "Name" ) == sibling ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iLight = nif->getBlock( index ); - - NifBlockEditor * le = new NifBlockEditor( nif, iLight ); - le->pushLayout( new QHBoxLayout() ); - le->add( new NifVectorEdit( nif, nif->getIndex( iLight, "Translation" ) ) ); - le->add( new NifRotationEdit( nif, nif->getIndex( iLight, "Rotation" ) ) ); - le->popLayout(); - le->add( new NifFloatSlider( nif, nif->getIndex( iLight, "Dimmer" ), 0, 1.0 ) ); - le->pushLayout( new QHBoxLayout() ); - le->add( new NifColorEdit( nif, nif->getIndex( iLight, "Ambient Color" ) ) ); - le->add( new NifColorEdit( nif, nif->getIndex( iLight, "Diffuse Color" ) ) ); - le->add( new NifColorEdit( nif, nif->getIndex( iLight, "Specular Color" ) ) ); - le->popLayout(); - le->pushLayout( new QHBoxLayout(), "Point Light Parameter" ); - le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Constant Attenuation" ) ) ); - le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Linear Attenuation" ) ) ); - le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Quadratic Attenuation" ) ) ); - le->popLayout(); - le->pushLayout( new QHBoxLayout(), "Spot Light Parameters" ); - le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Cutoff Angle" ), 0, 90 ) ); - le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Exponent" ), 0, 128 ) ); - le->popLayout(); - le->show(); - - return index; - } -}; - -REGISTER_SPELL( spLightEdit ) +#include "../spellbook.h" + +#include "../widgets/nifeditors.h" + +/* XPM */ +static char * light42_xpm[] = { +"24 24 43 1", +" c None", +". c #000100","+ c #0E0D02","@ c #111401","# c #151500","$ c #191903", +"% c #1E1D02","& c #201E00","* c #2A2C01","= c #2D2D00","- c #2E2F00", +"; c #2F3000","> c #3B3A00",", c #3D3C00","' c #3E3D00",") c #454300", +"! c #464800","~ c #494B00","{ c #525200","] c #565700","^ c #6B6900", +"/ c #6B6D00","( c #797A00","_ c #7E7F02",": c #848300","< c #9D9E03", +"[ c #A6A600","} c #B3B202","| c #B8B500","1 c #CFD000","2 c #D7D600", +"3 c #DDDB00","4 c #E4E200","5 c #E9E600","6 c #E8EB00","7 c #ECEF00", +"8 c #F1F300","9 c #F3F504","0 c #F6F800","a c #F9FA00","b c #FBFC00", +"c c #FEFE00","d c #FFFF01", +" -,'~'* ", +" $[8bbdb5_ ", +" }bddddddb[ ", +" :bddddddddb< ", +" -2dddddddddda; ", +" 'addddddddddb{ ", +" ~bddddddddddd^ ", +" ]dddddddddddd/ ", +" ~bddddddddddd{ ", +" ,addddddddddb; ", +" 3dddddddddd7 ", +" :adddddddd9: ", +" (8bddddb8) ", +" ,}4741|] ", +" +@##%& ", +" ...... ", +" ...... ", +" ...... ", +" ...... ", +" ...... ", +" ...... ", +" ...... ", +" ...... ", +" .... "}; + +QIcon * light42_xpm_icon = 0; + +class spLightEdit : public Spell +{ +public: + QString name() const { return "Light"; } + QString page() const { return ""; } + bool instant() const { return true; } + QIcon icon() const + { + if ( ! light42_xpm_icon ) light42_xpm_icon = new QIcon( light42_xpm ); + return *light42_xpm_icon; + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iBlock = nif->getBlock( index ); + QModelIndex sibling = index.sibling( index.row(), 0 ); + return index.isValid() && nif->inherits( iBlock, "NiLight" ) && ( iBlock == sibling || nif->getIndex( iBlock, "Name" ) == sibling ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iLight = nif->getBlock( index ); + + NifBlockEditor * le = new NifBlockEditor( nif, iLight ); + le->pushLayout( new QHBoxLayout() ); + le->add( new NifVectorEdit( nif, nif->getIndex( iLight, "Translation" ) ) ); + le->add( new NifRotationEdit( nif, nif->getIndex( iLight, "Rotation" ) ) ); + le->popLayout(); + le->add( new NifFloatSlider( nif, nif->getIndex( iLight, "Dimmer" ), 0, 1.0 ) ); + le->pushLayout( new QHBoxLayout() ); + le->add( new NifColorEdit( nif, nif->getIndex( iLight, "Ambient Color" ) ) ); + le->add( new NifColorEdit( nif, nif->getIndex( iLight, "Diffuse Color" ) ) ); + le->add( new NifColorEdit( nif, nif->getIndex( iLight, "Specular Color" ) ) ); + le->popLayout(); + le->pushLayout( new QHBoxLayout(), "Point Light Parameter" ); + le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Constant Attenuation" ) ) ); + le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Linear Attenuation" ) ) ); + le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Quadratic Attenuation" ) ) ); + le->popLayout(); + le->pushLayout( new QHBoxLayout(), "Spot Light Parameters" ); + le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Cutoff Angle" ), 0, 90 ) ); + le->add( new NifFloatEdit( nif, nif->getIndex( iLight, "Exponent" ), 0, 128 ) ); + le->popLayout(); + le->show(); + + return index; + } +}; + +REGISTER_SPELL( spLightEdit ) diff --git a/spells/material.cpp b/spells/material.cpp index 523ac78ac..2a31c8093 100644 --- a/spells/material.cpp +++ b/spells/material.cpp @@ -1,125 +1,125 @@ -#include "../spellbook.h" - -#include "../widgets/nifeditors.h" - -/* XPM */ -static char * mat42_xpm[] = { -"64 64 43 1", -" c None", -". c #866A36","+ c #856E33","@ c #817333","# c #836F45","$ c #8B6D3E", -"% c #8C6F34","& c #917231","* c #77812C","= c #7D765D","- c #987147", -"; c #6C8D29","> c #93792F",", c #7A7B71","' c #A0734E",") c #71828B", -"! c #5DA11E","~ c #9B822B","{ c #56AD16","] c #6E889D","^ c #AD795F", -"/ c #698DB0","( c #48BE13","_ c #6691BD",": c #43C311","< c #A58D28", -"[ c #6396CE","} c #1BE30C","| c #38D700","1 c #BD7F71","2 c #11F003", -"3 c #C78581","4 c #0CFF00","5 c #B7A51C","6 c #DA8C95","7 c #C3B618", -"8 c #ED93AA","9 c #F898B9","0 c #D6CD0A","a c #FF9BC4","b c #E6DF00", -"c c #F6F500","d c #FFFF00", -" ", -" ", -" ....... ", -" ................ ", -" ....................... ", -" ........................... ", -" ............................... ", -" ................................. ", -" ................................... ", -" ...................................... ", -" ........................................ ", -" ...........................@***+......... ", -" ................%~5<%......;2444}:*........ ", -" ................&0ddd0&....@}4444442;........ ", -" ................%7ddddd7....;44444444|+....... ", -" .................5ddddddb>...{444444442@....... ", -" .................%0ddddddc<..+|444444442@....... ", -" ..................%bddddddc<..@|444444442@....... ", -" ...................%bddddddc<..@|44444444|........ ", -" ............'^1^-...%bddddddc>..@|44444444!........ ", -" ............39aaa8'..%bddddddb%..+:4444442!......... ", -" ............^9aaaaa6$.%7dddddd0....*}4444};.......... ", -" .............6aaaaaa9^....................... ", -" .............-9aaaaaaa1...%7cdb~...................... ", -" ..............-9aaaaaaa1.....>~&...................... ", -" ..............-8aaaaaaa1.............................. ", -" ........#)]],..$8aaaaaa9^.............................. ", -" .......=_[[[[,..^9aaaaa6.............................. ", -" .......$/[[[[[]$..^9aaa8'............................. ", -" .......)[[[[[[_#...'363'.............................. ", -" ......#_[[[[[[_#.................................... ", -" ......=[[[[[[[/#.................................. ", -" ......,[[[[[[[_#................................. ", -" ......,[[[[[[[_#............................... ", -" ......,[[[[[[[]$............................. ", -" ......=_[[[[[_#............................ ", -" .......][[[[_,............................ ", -" .......#][[_,........................... ", -" .........==#.......................... ", -" ..................................... ", -" .................................... ", -" ................................... ", -" .................................. ", -" ................................. ", -" ................................ ", -" ............................... ", -" .................. ........... ", -" ................ ......... ", -" .............. ........ ", -" ............. ........ ", -" ............ ....... ", -" ........... ....... ", -" ............ ....... ", -" ........... ........ ", -" ........... ......... ", -" ........................ ", -" ....................... ", -" ..................... ", -" ................. ", -" ............. ", -" ........ ", -" ", -" "}; - -QIcon * mat42_xpm_icon = 0; - -class spMaterialEdit : public Spell -{ -public: - QString name() const { return "Material"; } - QString page() const { return ""; } - bool instant() const { return true; } - QIcon icon() const - { - if ( ! mat42_xpm_icon ) mat42_xpm_icon = new QIcon( mat42_xpm ); - return *mat42_xpm_icon; - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iBlock = nif->getBlock( index, "NiMaterialProperty" ); - QModelIndex sibling = index.sibling( index.row(), 0 ); - return index.isValid() && ( iBlock == sibling || nif->getIndex( iBlock, "Name" ) == sibling ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iMaterial = nif->getBlock( index ); - NifBlockEditor * me = new NifBlockEditor( nif, iMaterial ); - - me->pushLayout( new QHBoxLayout ); - me->add( new NifColorEdit( nif, nif->getIndex( iMaterial, "Ambient Color" ) ) ); - me->add( new NifColorEdit( nif, nif->getIndex( iMaterial, "Diffuse Color" ) ) ); - me->popLayout(); - me->pushLayout( new QHBoxLayout ); - me->add( new NifColorEdit( nif, nif->getIndex( iMaterial, "Specular Color" ) ) ); - me->add( new NifColorEdit( nif, nif->getIndex( iMaterial, "Emissive Color" ) ) ); - me->popLayout(); - me->add( new NifFloatSlider( nif, nif->getIndex( iMaterial, "Alpha" ), 0.0, 1.0 ) ); - me->add( new NifFloatSlider( nif, nif->getIndex( iMaterial, "Glossiness" ), 0.0, 100.0 ) ); - me->show(); - - return index; - } -}; - -REGISTER_SPELL( spMaterialEdit ) +#include "../spellbook.h" + +#include "../widgets/nifeditors.h" + +/* XPM */ +static char * mat42_xpm[] = { +"64 64 43 1", +" c None", +". c #866A36","+ c #856E33","@ c #817333","# c #836F45","$ c #8B6D3E", +"% c #8C6F34","& c #917231","* c #77812C","= c #7D765D","- c #987147", +"; c #6C8D29","> c #93792F",", c #7A7B71","' c #A0734E",") c #71828B", +"! c #5DA11E","~ c #9B822B","{ c #56AD16","] c #6E889D","^ c #AD795F", +"/ c #698DB0","( c #48BE13","_ c #6691BD",": c #43C311","< c #A58D28", +"[ c #6396CE","} c #1BE30C","| c #38D700","1 c #BD7F71","2 c #11F003", +"3 c #C78581","4 c #0CFF00","5 c #B7A51C","6 c #DA8C95","7 c #C3B618", +"8 c #ED93AA","9 c #F898B9","0 c #D6CD0A","a c #FF9BC4","b c #E6DF00", +"c c #F6F500","d c #FFFF00", +" ", +" ", +" ....... ", +" ................ ", +" ....................... ", +" ........................... ", +" ............................... ", +" ................................. ", +" ................................... ", +" ...................................... ", +" ........................................ ", +" ...........................@***+......... ", +" ................%~5<%......;2444}:*........ ", +" ................&0ddd0&....@}4444442;........ ", +" ................%7ddddd7....;44444444|+....... ", +" .................5ddddddb>...{444444442@....... ", +" .................%0ddddddc<..+|444444442@....... ", +" ..................%bddddddc<..@|444444442@....... ", +" ...................%bddddddc<..@|44444444|........ ", +" ............'^1^-...%bddddddc>..@|44444444!........ ", +" ............39aaa8'..%bddddddb%..+:4444442!......... ", +" ............^9aaaaa6$.%7dddddd0....*}4444};.......... ", +" .............6aaaaaa9^....................... ", +" .............-9aaaaaaa1...%7cdb~...................... ", +" ..............-9aaaaaaa1.....>~&...................... ", +" ..............-8aaaaaaa1.............................. ", +" ........#)]],..$8aaaaaa9^.............................. ", +" .......=_[[[[,..^9aaaaa6.............................. ", +" .......$/[[[[[]$..^9aaa8'............................. ", +" .......)[[[[[[_#...'363'.............................. ", +" ......#_[[[[[[_#.................................... ", +" ......=[[[[[[[/#.................................. ", +" ......,[[[[[[[_#................................. ", +" ......,[[[[[[[_#............................... ", +" ......,[[[[[[[]$............................. ", +" ......=_[[[[[_#............................ ", +" .......][[[[_,............................ ", +" .......#][[_,........................... ", +" .........==#.......................... ", +" ..................................... ", +" .................................... ", +" ................................... ", +" .................................. ", +" ................................. ", +" ................................ ", +" ............................... ", +" .................. ........... ", +" ................ ......... ", +" .............. ........ ", +" ............. ........ ", +" ............ ....... ", +" ........... ....... ", +" ............ ....... ", +" ........... ........ ", +" ........... ......... ", +" ........................ ", +" ....................... ", +" ..................... ", +" ................. ", +" ............. ", +" ........ ", +" ", +" "}; + +QIcon * mat42_xpm_icon = 0; + +class spMaterialEdit : public Spell +{ +public: + QString name() const { return "Material"; } + QString page() const { return ""; } + bool instant() const { return true; } + QIcon icon() const + { + if ( ! mat42_xpm_icon ) mat42_xpm_icon = new QIcon( mat42_xpm ); + return *mat42_xpm_icon; + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iBlock = nif->getBlock( index, "NiMaterialProperty" ); + QModelIndex sibling = index.sibling( index.row(), 0 ); + return index.isValid() && ( iBlock == sibling || nif->getIndex( iBlock, "Name" ) == sibling ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iMaterial = nif->getBlock( index ); + NifBlockEditor * me = new NifBlockEditor( nif, iMaterial ); + + me->pushLayout( new QHBoxLayout ); + me->add( new NifColorEdit( nif, nif->getIndex( iMaterial, "Ambient Color" ) ) ); + me->add( new NifColorEdit( nif, nif->getIndex( iMaterial, "Diffuse Color" ) ) ); + me->popLayout(); + me->pushLayout( new QHBoxLayout ); + me->add( new NifColorEdit( nif, nif->getIndex( iMaterial, "Specular Color" ) ) ); + me->add( new NifColorEdit( nif, nif->getIndex( iMaterial, "Emissive Color" ) ) ); + me->popLayout(); + me->add( new NifFloatSlider( nif, nif->getIndex( iMaterial, "Alpha" ), 0.0, 1.0 ) ); + me->add( new NifFloatSlider( nif, nif->getIndex( iMaterial, "Glossiness" ), 0.0, 100.0 ) ); + me->show(); + + return index; + } +}; + +REGISTER_SPELL( spMaterialEdit ) diff --git a/spells/mesh.cpp b/spells/mesh.cpp index 15030f9a1..7d430b382 100644 --- a/spells/mesh.cpp +++ b/spells/mesh.cpp @@ -1,599 +1,599 @@ -#include "mesh.h" - -#include -#include -#include - -#include - -class spFlipTexCoords : public Spell -{ -public: - QString name() const { return Spell::tr("Flip UV"); } - QString page() const { return Spell::tr("Mesh"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->itemType( index ).toLower() == "texcoord" || nif->inherits( index, "NiTriBasedGeomData" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex idx = index; - if ( nif->itemType( index ).toLower() != "texcoord" ) - { - idx = nif->getIndex( nif->getBlock( index ), "UV Sets" ); - } - QMenu menu; - static const char * const flipCmds[3] = { "S = 1.0 - S", "T = 1.0 - T", "S <=> T" }; - for ( int c = 0; c < 3; c++ ) - menu.addAction( flipCmds[c] ); - - QAction * act = menu.exec( QCursor::pos() ); - if ( act ) { - for ( int c = 0; c < 3; c++ ) - if ( act->text() == flipCmds[c] ) - flip( nif, idx, c ); - } - - return index; - } - - void flip( NifModel * nif, const QModelIndex & index, int f ) - { - if ( nif->isArray( index ) ) - { - QModelIndex idx = index.child( 0, 0 ); - if ( idx.isValid() ) - { - if ( nif->isArray( idx ) ) - flip( nif, idx, f ); - else - { - QVector tc = nif->getArray( index ); - for ( int c = 0; c < tc.count(); c++ ) - flip( tc[c], f ); - nif->setArray( index, tc ); - } - } - } - else - { - Vector2 v = nif->get( index ); - flip( v, f ); - nif->set( index, v ); - } - } - - void flip( Vector2 & v, int f ) - { - switch ( f ) - { - case 0: - v[0] = 1.0 - v[0]; - break; - case 1: - v[1] = 1.0 - v[1]; - break; - default: - { - float x = v[0]; - v[0] = v[1]; - v[1] = x; - } break; - } - } -}; - -REGISTER_SPELL( spFlipTexCoords ) - - -class spFlipFace : public Spell -{ -public: - QString name() const { return Spell::tr("Flip Face"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ( nif->getValue( index ).type() == NifValue::tTriangle ) - || ( nif->isArray( index ) && nif->getValue( index.child( 0, 0 ) ).type() == NifValue::tTriangle ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - if ( nif->isArray( index ) ) - { - QVector tris = nif->getArray( index ); - for ( int t = 0; t < tris.count(); t++ ) - tris[t].flip(); - nif->setArray( index, tris ); - } - else - { - Triangle t = nif->get( index ); - t.flip(); - nif->set( index, t ); - } - return index; - } -}; - -REGISTER_SPELL( spFlipFace ) - - -class spPruneRedundantTriangles : public Spell -{ -public: - QString name() const { return Spell::tr("Prune Triangles"); } - QString page() const { return Spell::tr("Mesh"); } - - static QModelIndex getTriShapeData( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = nif->getBlock( index ); - if ( nif->isNiBlock( index, "NiTriShape" ) ) - iData = nif->getBlock( nif->getLink( index, "Data" ) ); - if ( nif->isNiBlock( iData, "NiTriShapeData" ) ) - return iData; - else return QModelIndex(); - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return getTriShapeData( nif, index ).isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = getTriShapeData( nif, index ); - - QList tris = nif->getArray( iData, "Triangles" ).toList(); - int cnt = 0; - - int i = 0; - while ( i < tris.count() ) - { - const Triangle & t = tris[i]; - if ( t[0] == t[1] || t[1] == t[2] || t[2] == t[0] ) - { - tris.removeAt( i ); - cnt++; - } - else - i++; - } - - i = 0; - while ( i < tris.count() ) - { - const Triangle & t = tris[i]; - - int j = i + 1; - while ( j < tris.count() ) - { - const Triangle & r = tris[j]; - - if ( ( t[0] == r[0] && t[1] == r[1] && t[2] == r[2] ) - || ( t[0] == r[1] && t[1] == r[2] && t[2] == r[0] ) - || ( t[0] == r[2] && t[1] == r[0] && t[2] == r[1] ) ) - { - tris.removeAt( j ); - cnt++; - } - else - j++; - } - i++; - } - - if ( cnt > 0 ) - { - qWarning() << QString( Spell::tr("%1 triangles removed") ).arg( cnt ); - nif->set( iData, "Num Triangles", tris.count() ); - nif->set( iData, "Num Triangle Points", tris.count() * 3 ); - nif->updateArray( iData, "Triangles" ); - nif->setArray( iData, "Triangles", tris.toVector() ); - } - return index; - } -}; - -REGISTER_SPELL( spPruneRedundantTriangles ) - - -template static void removeFromArray( QVector & array, QMap map ) -{ - for ( int x = array.count() - 1; x >= 0; x-- ) - { - if ( ! map.contains( x ) ) - array.remove( x ); - } -} - -static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, const QModelIndex & iShape ) -{ - try - { - // read the data - - QVector verts = nif->getArray( iData, "Vertices" ); - if ( ! verts.count() ) { - throw QString( Spell::tr("no vertices?") ); - } - QVector norms = nif->getArray( iData, "Normals" ); - QVector colors = nif->getArray( iData, "Vertex Colors" ); - QList< QVector > texco; - QModelIndex iUVSets = nif->getIndex( iData, "UV Sets" ); - for ( int r = 0; r < nif->rowCount( iUVSets ); r++ ) - { - texco << nif->getArray( iUVSets.child( r, 0 ) ); - if ( texco.last().count() != verts.count() ) - throw QString( "uv array size differs" ); - } - - int numVerts = verts.count(); - - if ( numVerts != nif->get( iData, "Num Vertices" ) || - ( norms.count() && norms.count() != numVerts ) || - ( colors.count() && colors.count() != numVerts ) ) - { - throw QString( "vertex array size differs" ); - } - - // detect unused vertices - - QMap used; - - QVector tris = nif->getArray( iData, "Triangles" ); - foreach ( Triangle tri, tris ) - { - for ( int t = 0; t < 3; t++ ) - used.insert( tri[t], true ); - } - - QList< QVector< quint16 > > strips; - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - { - strips << nif->getArray( iPoints.child( r, 0 ) ); - foreach ( quint16 p, strips.last() ) - used.insert( p, true ); - } - - // remove them - - qWarning() << "removing" << verts.count() - used.count() << "vertices"; - - if ( verts.count() == used.count() ) - return; - - removeFromArray( verts, used ); - removeFromArray( norms, used ); - removeFromArray( colors, used ); - for ( int c = 0; c < texco.count(); c++ ) - removeFromArray( texco[c], used ); - - // adjust the faces - - QMap map; - quint16 y = 0; - for ( quint16 x = 0; x < numVerts; x++ ) - { - if ( used.contains( x ) ) - map.insert( x, y++ ); - } - - QMutableVectorIterator itri( tris ); - while ( itri.hasNext() ) - { - Triangle & tri = itri.next(); - for ( int t = 0; t < 3; t++ ) - if ( map.contains( tri[t] ) ) - tri[t] = map[ tri[t] ]; - } - - QMutableListIterator< QVector > istrip( strips ); - while ( istrip.hasNext() ) - { - QVector & strip = istrip.next(); - for ( int s = 0; s < strip.size(); s++ ) - { - if ( map.contains( strip[s] ) ) - strip[s] = map[ strip[s] ]; - } - } - - // write back the data - - nif->setArray( iData, "Triangles", tris ); - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - nif->setArray( iPoints.child( r, 0 ), strips[r] ); - nif->set( iData, "Num Vertices", verts.count() ); - nif->updateArray( iData, "Vertices" ); - nif->setArray( iData, "Vertices", verts ); - nif->updateArray( iData, "Normals" ); - nif->setArray( iData, "Normals", norms ); - nif->updateArray( iData, "Vertex Colors" ); - nif->setArray( iData, "Vertex Colors", colors ); - for ( int r = 0; r < nif->rowCount( iUVSets ); r++ ) - { - nif->updateArray( iUVSets.child( r, 0 ) ); - nif->setArray( iUVSets.child( r, 0 ), texco[r] ); - } - - // process NiSkinData - - QModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); - - QModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ); - QModelIndex iBones = nif->getIndex( iSkinData, "Bone List" ); - for ( int b = 0; b < nif->rowCount( iBones ); b++ ) - { - QVector< QPair > weights; - QModelIndex iWeights = nif->getIndex( iBones.child( b, 0 ), "Vertex Weights" ); - for ( int w = 0; w < nif->rowCount( iWeights ); w++ ) - { - weights.append( QPair( nif->get( iWeights.child( w, 0 ), "Index" ), nif->get( iWeights.child( w, 0 ), "Weight" ) ) ); - } - - for ( int x = weights.count() - 1; x >= 0; x-- ) - { - if ( ! used.contains( weights[x].first ) ) - weights.remove( x ); - } - - QMutableVectorIterator< QPair< int, float > > it( weights ); - while ( it.hasNext() ) - { - QPair & w = it.next(); - if ( map.contains( w.first ) ) - w.first = map[ w.first ]; - } - - nif->set( iBones.child( b, 0 ), "Num Vertices", weights.count() ); - nif->updateArray( iWeights ); - for ( int w = 0; w < weights.count(); w++ ) - { - nif->set( iWeights.child( w, 0 ), "Index", weights[w].first ); - nif->set( iWeights.child( w, 0 ), "Weight", weights[w].second ); - } - } - - // process NiSkinPartition - - QModelIndex iSkinPart = nif->getBlock( nif->getLink( iSkinInst, "Skin Partition" ), "NiSkinPartition" ); - if ( ! iSkinPart.isValid() ) - iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); - if ( iSkinPart.isValid() ) - { - nif->removeNiBlock( nif->getBlockNumber( iSkinPart ) ); - qWarning() << "the skin partition was removed, please add it again with the skin partition spell"; - } - } - catch ( QString e ) - { - qWarning() << e.toAscii().data(); - } -} - - -class spRemoveDuplicateVertices : public Spell -{ -public: - QString name() const { return Spell::tr("Remove Duplicate Vertices"); } - QString page() const { return Spell::tr("Mesh"); } - - static QModelIndex getShape( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iShape = nif->getBlock( index ); - if ( nif->isNiBlock( iShape, "NiTriBasedGeomData" ) ) - iShape = nif->getBlock( nif->getParent( nif->getBlockNumber( iShape ) ) ); - if ( nif->isNiBlock( iShape, "NiTriShape" ) || nif->isNiBlock( index, "NiTriStrips" ) ) - if ( nif->getBlock( nif->getLink( iShape, "Data" ), "NiTriBasedGeomData" ).isValid() ) - return iShape; - return QModelIndex(); - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return getShape( nif, index ).isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - try - { - QModelIndex iShape = getShape( nif, index ); - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); - - // read the data - - QVector verts = nif->getArray( iData, "Vertices" ); - if ( ! verts.count() ) - throw QString( "no vertices?" ); - QVector norms = nif->getArray( iData, "Normals" ); - QVector colors = nif->getArray( iData, "Vertex Colors" ); - QList< QVector > texco; - QModelIndex iUVSets = nif->getIndex( iData, "UV Sets" ); - for ( int r = 0; r < nif->rowCount( iUVSets ); r++ ) - { - texco << nif->getArray( iUVSets.child( r, 0 ) ); - if ( texco.last().count() != verts.count() ) - throw QString( Spell::tr("uv array size differs") ); - } - - int numVerts = verts.count(); - - if ( numVerts != nif->get( iData, "Num Vertices" ) || - ( norms.count() && norms.count() != numVerts ) || - ( colors.count() && colors.count() != numVerts ) ) - { - throw QString( Spell::tr("vertex array size differs") ); - } - - // detect the dublicates - - QMap map; - - for ( int a = 0; a < numVerts; a++ ) - { - Vector3 v = verts[a]; - for ( int b = 0; b < a; b++ ) - { - if ( ! ( v == verts[b] ) ) - continue; - if ( norms.count() && ! ( norms[a] == norms[b] ) ) - continue; - if ( colors.count() && ! ( colors[a] == colors[b] ) ) - continue; - int t = 0; - for ( t = 0; t < texco.count(); t++ ) - { - if ( ! ( texco[t][a] == texco[t][b] ) ) - break; - } - if ( t < texco.count() ) - continue; - - map.insert( b, a ); - } - } - - //qWarning() << QString( Spell::tr("detected % duplicates") ).arg( map.count() ); - - // adjust the faces - - QVector< Triangle > tris = nif->getArray< Triangle >( iData, "Triangles" ); - QMutableVectorIterator itri( tris ); - while ( itri.hasNext() ) - { - Triangle & t = itri.next(); - for ( int p = 0; p < 3; p++ ) - if ( map.contains( t[p] ) ) - t[p] = map.value( t[p] ); - } - nif->setArray( iData, "Triangles", tris ); - - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - { - QVector strip = nif->getArray( iPoints.child( r, 0 ) ); - QMutableVectorIterator istrp( strip ); - while ( istrp.hasNext() ) - { - quint16 & p = istrp.next(); - if ( map.contains( p ) ) - p = map.value( p ); - } - nif->setArray( iPoints.child( r, 0 ), strip ); - } - - // finally, remove the now unused vertices - - removeWasteVertices( nif, iData, iShape ); - } - catch ( QString e ) - { - qWarning() << e.toAscii().data(); - } - - return index; - } -}; - -REGISTER_SPELL( spRemoveDuplicateVertices ) - - -class spRemoveWasteVertices : public Spell -{ -public: - QString name() const { return Spell::tr("Remove Unused Vertices"); } - QString page() const { return Spell::tr("Mesh"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return spRemoveDuplicateVertices::getShape( nif, index ).isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iShape = spRemoveDuplicateVertices::getShape( nif, index ); - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); - - removeWasteVertices( nif, iData, iShape ); - - return index; - } -}; - -REGISTER_SPELL( spRemoveWasteVertices ) - - -bool spUpdateCenterRadius::isApplicable( const NifModel * nif, const QModelIndex & index ) -{ - return nif->getBlock( index, "NiGeometryData" ).isValid(); -} - -QModelIndex spUpdateCenterRadius::cast( NifModel * nif, const QModelIndex & index ) -{ - QModelIndex iData = nif->getBlock( index ); - - QVector verts = nif->getArray( iData, "Vertices" ); - if ( ! verts.count() ) - return index; - - Vector3 center; - float radius = 0.0f; - - /* - Oblivion and CT_volatile meshes require a - different center algorithm - */ - if( ( ( nif->getVersionNumber() & 0x14000000 ) && ( nif->getUserVersion() == 11 ) ) - || ( nif->get(iData, "Consistency Flags") & 0x8000 ) ) - { - /* is a Oblivion mesh! */ - float xMin(FLT_MAX), xMax(-FLT_MAX); - float yMin(FLT_MAX), yMax(-FLT_MAX); - float zMin(FLT_MAX), zMax(-FLT_MAX); - foreach( Vector3 v, verts ) - { - if( v[0] < xMin ) - xMin = v[0]; - else if ( v[0] > xMax ) - xMax = v[0]; - - if( v[1] < yMin ) - yMin = v[1]; - else if ( v[1] > yMax ) - yMax = v[1]; - - if( v[2] < zMin ) - zMin = v[2]; - else if ( v[2] > zMax ) - zMax = v[2]; - } - - center = Vector3( xMin + xMax, yMin + yMax, zMin + zMax ) / 2; - } - else { - foreach( Vector3 v, verts ) - { - center += v; - } - center /= verts.count(); - } - - float d; - foreach ( Vector3 v, verts ) - { - if ( ( d = ( center - v ).length() ) > radius ) - radius = d; - } - - nif->set( iData, "Center", center ); - nif->set( iData, "Radius", radius ); - - return index; -} - -REGISTER_SPELL( spUpdateCenterRadius ); +#include "mesh.h" + +#include +#include +#include + +#include + +class spFlipTexCoords : public Spell +{ +public: + QString name() const { return Spell::tr("Flip UV"); } + QString page() const { return Spell::tr("Mesh"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->itemType( index ).toLower() == "texcoord" || nif->inherits( index, "NiTriBasedGeomData" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex idx = index; + if ( nif->itemType( index ).toLower() != "texcoord" ) + { + idx = nif->getIndex( nif->getBlock( index ), "UV Sets" ); + } + QMenu menu; + static const char * const flipCmds[3] = { "S = 1.0 - S", "T = 1.0 - T", "S <=> T" }; + for ( int c = 0; c < 3; c++ ) + menu.addAction( flipCmds[c] ); + + QAction * act = menu.exec( QCursor::pos() ); + if ( act ) { + for ( int c = 0; c < 3; c++ ) + if ( act->text() == flipCmds[c] ) + flip( nif, idx, c ); + } + + return index; + } + + void flip( NifModel * nif, const QModelIndex & index, int f ) + { + if ( nif->isArray( index ) ) + { + QModelIndex idx = index.child( 0, 0 ); + if ( idx.isValid() ) + { + if ( nif->isArray( idx ) ) + flip( nif, idx, f ); + else + { + QVector tc = nif->getArray( index ); + for ( int c = 0; c < tc.count(); c++ ) + flip( tc[c], f ); + nif->setArray( index, tc ); + } + } + } + else + { + Vector2 v = nif->get( index ); + flip( v, f ); + nif->set( index, v ); + } + } + + void flip( Vector2 & v, int f ) + { + switch ( f ) + { + case 0: + v[0] = 1.0 - v[0]; + break; + case 1: + v[1] = 1.0 - v[1]; + break; + default: + { + float x = v[0]; + v[0] = v[1]; + v[1] = x; + } break; + } + } +}; + +REGISTER_SPELL( spFlipTexCoords ) + + +class spFlipFace : public Spell +{ +public: + QString name() const { return Spell::tr("Flip Face"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ( nif->getValue( index ).type() == NifValue::tTriangle ) + || ( nif->isArray( index ) && nif->getValue( index.child( 0, 0 ) ).type() == NifValue::tTriangle ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + if ( nif->isArray( index ) ) + { + QVector tris = nif->getArray( index ); + for ( int t = 0; t < tris.count(); t++ ) + tris[t].flip(); + nif->setArray( index, tris ); + } + else + { + Triangle t = nif->get( index ); + t.flip(); + nif->set( index, t ); + } + return index; + } +}; + +REGISTER_SPELL( spFlipFace ) + + +class spPruneRedundantTriangles : public Spell +{ +public: + QString name() const { return Spell::tr("Prune Triangles"); } + QString page() const { return Spell::tr("Mesh"); } + + static QModelIndex getTriShapeData( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = nif->getBlock( index ); + if ( nif->isNiBlock( index, "NiTriShape" ) ) + iData = nif->getBlock( nif->getLink( index, "Data" ) ); + if ( nif->isNiBlock( iData, "NiTriShapeData" ) ) + return iData; + else return QModelIndex(); + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return getTriShapeData( nif, index ).isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = getTriShapeData( nif, index ); + + QList tris = nif->getArray( iData, "Triangles" ).toList(); + int cnt = 0; + + int i = 0; + while ( i < tris.count() ) + { + const Triangle & t = tris[i]; + if ( t[0] == t[1] || t[1] == t[2] || t[2] == t[0] ) + { + tris.removeAt( i ); + cnt++; + } + else + i++; + } + + i = 0; + while ( i < tris.count() ) + { + const Triangle & t = tris[i]; + + int j = i + 1; + while ( j < tris.count() ) + { + const Triangle & r = tris[j]; + + if ( ( t[0] == r[0] && t[1] == r[1] && t[2] == r[2] ) + || ( t[0] == r[1] && t[1] == r[2] && t[2] == r[0] ) + || ( t[0] == r[2] && t[1] == r[0] && t[2] == r[1] ) ) + { + tris.removeAt( j ); + cnt++; + } + else + j++; + } + i++; + } + + if ( cnt > 0 ) + { + qWarning() << QString( Spell::tr("%1 triangles removed") ).arg( cnt ); + nif->set( iData, "Num Triangles", tris.count() ); + nif->set( iData, "Num Triangle Points", tris.count() * 3 ); + nif->updateArray( iData, "Triangles" ); + nif->setArray( iData, "Triangles", tris.toVector() ); + } + return index; + } +}; + +REGISTER_SPELL( spPruneRedundantTriangles ) + + +template static void removeFromArray( QVector & array, QMap map ) +{ + for ( int x = array.count() - 1; x >= 0; x-- ) + { + if ( ! map.contains( x ) ) + array.remove( x ); + } +} + +static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, const QModelIndex & iShape ) +{ + try + { + // read the data + + QVector verts = nif->getArray( iData, "Vertices" ); + if ( ! verts.count() ) { + throw QString( Spell::tr("no vertices?") ); + } + QVector norms = nif->getArray( iData, "Normals" ); + QVector colors = nif->getArray( iData, "Vertex Colors" ); + QList< QVector > texco; + QModelIndex iUVSets = nif->getIndex( iData, "UV Sets" ); + for ( int r = 0; r < nif->rowCount( iUVSets ); r++ ) + { + texco << nif->getArray( iUVSets.child( r, 0 ) ); + if ( texco.last().count() != verts.count() ) + throw QString( "uv array size differs" ); + } + + int numVerts = verts.count(); + + if ( numVerts != nif->get( iData, "Num Vertices" ) || + ( norms.count() && norms.count() != numVerts ) || + ( colors.count() && colors.count() != numVerts ) ) + { + throw QString( "vertex array size differs" ); + } + + // detect unused vertices + + QMap used; + + QVector tris = nif->getArray( iData, "Triangles" ); + foreach ( Triangle tri, tris ) + { + for ( int t = 0; t < 3; t++ ) + used.insert( tri[t], true ); + } + + QList< QVector< quint16 > > strips; + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + { + strips << nif->getArray( iPoints.child( r, 0 ) ); + foreach ( quint16 p, strips.last() ) + used.insert( p, true ); + } + + // remove them + + qWarning() << "removing" << verts.count() - used.count() << "vertices"; + + if ( verts.count() == used.count() ) + return; + + removeFromArray( verts, used ); + removeFromArray( norms, used ); + removeFromArray( colors, used ); + for ( int c = 0; c < texco.count(); c++ ) + removeFromArray( texco[c], used ); + + // adjust the faces + + QMap map; + quint16 y = 0; + for ( quint16 x = 0; x < numVerts; x++ ) + { + if ( used.contains( x ) ) + map.insert( x, y++ ); + } + + QMutableVectorIterator itri( tris ); + while ( itri.hasNext() ) + { + Triangle & tri = itri.next(); + for ( int t = 0; t < 3; t++ ) + if ( map.contains( tri[t] ) ) + tri[t] = map[ tri[t] ]; + } + + QMutableListIterator< QVector > istrip( strips ); + while ( istrip.hasNext() ) + { + QVector & strip = istrip.next(); + for ( int s = 0; s < strip.size(); s++ ) + { + if ( map.contains( strip[s] ) ) + strip[s] = map[ strip[s] ]; + } + } + + // write back the data + + nif->setArray( iData, "Triangles", tris ); + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + nif->setArray( iPoints.child( r, 0 ), strips[r] ); + nif->set( iData, "Num Vertices", verts.count() ); + nif->updateArray( iData, "Vertices" ); + nif->setArray( iData, "Vertices", verts ); + nif->updateArray( iData, "Normals" ); + nif->setArray( iData, "Normals", norms ); + nif->updateArray( iData, "Vertex Colors" ); + nif->setArray( iData, "Vertex Colors", colors ); + for ( int r = 0; r < nif->rowCount( iUVSets ); r++ ) + { + nif->updateArray( iUVSets.child( r, 0 ) ); + nif->setArray( iUVSets.child( r, 0 ), texco[r] ); + } + + // process NiSkinData + + QModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); + + QModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ); + QModelIndex iBones = nif->getIndex( iSkinData, "Bone List" ); + for ( int b = 0; b < nif->rowCount( iBones ); b++ ) + { + QVector< QPair > weights; + QModelIndex iWeights = nif->getIndex( iBones.child( b, 0 ), "Vertex Weights" ); + for ( int w = 0; w < nif->rowCount( iWeights ); w++ ) + { + weights.append( QPair( nif->get( iWeights.child( w, 0 ), "Index" ), nif->get( iWeights.child( w, 0 ), "Weight" ) ) ); + } + + for ( int x = weights.count() - 1; x >= 0; x-- ) + { + if ( ! used.contains( weights[x].first ) ) + weights.remove( x ); + } + + QMutableVectorIterator< QPair< int, float > > it( weights ); + while ( it.hasNext() ) + { + QPair & w = it.next(); + if ( map.contains( w.first ) ) + w.first = map[ w.first ]; + } + + nif->set( iBones.child( b, 0 ), "Num Vertices", weights.count() ); + nif->updateArray( iWeights ); + for ( int w = 0; w < weights.count(); w++ ) + { + nif->set( iWeights.child( w, 0 ), "Index", weights[w].first ); + nif->set( iWeights.child( w, 0 ), "Weight", weights[w].second ); + } + } + + // process NiSkinPartition + + QModelIndex iSkinPart = nif->getBlock( nif->getLink( iSkinInst, "Skin Partition" ), "NiSkinPartition" ); + if ( ! iSkinPart.isValid() ) + iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); + if ( iSkinPart.isValid() ) + { + nif->removeNiBlock( nif->getBlockNumber( iSkinPart ) ); + qWarning() << "the skin partition was removed, please add it again with the skin partition spell"; + } + } + catch ( QString e ) + { + qWarning() << e.toAscii().data(); + } +} + + +class spRemoveDuplicateVertices : public Spell +{ +public: + QString name() const { return Spell::tr("Remove Duplicate Vertices"); } + QString page() const { return Spell::tr("Mesh"); } + + static QModelIndex getShape( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iShape = nif->getBlock( index ); + if ( nif->isNiBlock( iShape, "NiTriBasedGeomData" ) ) + iShape = nif->getBlock( nif->getParent( nif->getBlockNumber( iShape ) ) ); + if ( nif->isNiBlock( iShape, "NiTriShape" ) || nif->isNiBlock( index, "NiTriStrips" ) ) + if ( nif->getBlock( nif->getLink( iShape, "Data" ), "NiTriBasedGeomData" ).isValid() ) + return iShape; + return QModelIndex(); + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return getShape( nif, index ).isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + try + { + QModelIndex iShape = getShape( nif, index ); + QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + + // read the data + + QVector verts = nif->getArray( iData, "Vertices" ); + if ( ! verts.count() ) + throw QString( "no vertices?" ); + QVector norms = nif->getArray( iData, "Normals" ); + QVector colors = nif->getArray( iData, "Vertex Colors" ); + QList< QVector > texco; + QModelIndex iUVSets = nif->getIndex( iData, "UV Sets" ); + for ( int r = 0; r < nif->rowCount( iUVSets ); r++ ) + { + texco << nif->getArray( iUVSets.child( r, 0 ) ); + if ( texco.last().count() != verts.count() ) + throw QString( Spell::tr("uv array size differs") ); + } + + int numVerts = verts.count(); + + if ( numVerts != nif->get( iData, "Num Vertices" ) || + ( norms.count() && norms.count() != numVerts ) || + ( colors.count() && colors.count() != numVerts ) ) + { + throw QString( Spell::tr("vertex array size differs") ); + } + + // detect the dublicates + + QMap map; + + for ( int a = 0; a < numVerts; a++ ) + { + Vector3 v = verts[a]; + for ( int b = 0; b < a; b++ ) + { + if ( ! ( v == verts[b] ) ) + continue; + if ( norms.count() && ! ( norms[a] == norms[b] ) ) + continue; + if ( colors.count() && ! ( colors[a] == colors[b] ) ) + continue; + int t = 0; + for ( t = 0; t < texco.count(); t++ ) + { + if ( ! ( texco[t][a] == texco[t][b] ) ) + break; + } + if ( t < texco.count() ) + continue; + + map.insert( b, a ); + } + } + + //qWarning() << QString( Spell::tr("detected % duplicates") ).arg( map.count() ); + + // adjust the faces + + QVector< Triangle > tris = nif->getArray< Triangle >( iData, "Triangles" ); + QMutableVectorIterator itri( tris ); + while ( itri.hasNext() ) + { + Triangle & t = itri.next(); + for ( int p = 0; p < 3; p++ ) + if ( map.contains( t[p] ) ) + t[p] = map.value( t[p] ); + } + nif->setArray( iData, "Triangles", tris ); + + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + { + QVector strip = nif->getArray( iPoints.child( r, 0 ) ); + QMutableVectorIterator istrp( strip ); + while ( istrp.hasNext() ) + { + quint16 & p = istrp.next(); + if ( map.contains( p ) ) + p = map.value( p ); + } + nif->setArray( iPoints.child( r, 0 ), strip ); + } + + // finally, remove the now unused vertices + + removeWasteVertices( nif, iData, iShape ); + } + catch ( QString e ) + { + qWarning() << e.toAscii().data(); + } + + return index; + } +}; + +REGISTER_SPELL( spRemoveDuplicateVertices ) + + +class spRemoveWasteVertices : public Spell +{ +public: + QString name() const { return Spell::tr("Remove Unused Vertices"); } + QString page() const { return Spell::tr("Mesh"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return spRemoveDuplicateVertices::getShape( nif, index ).isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iShape = spRemoveDuplicateVertices::getShape( nif, index ); + QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + + removeWasteVertices( nif, iData, iShape ); + + return index; + } +}; + +REGISTER_SPELL( spRemoveWasteVertices ) + + +bool spUpdateCenterRadius::isApplicable( const NifModel * nif, const QModelIndex & index ) +{ + return nif->getBlock( index, "NiGeometryData" ).isValid(); +} + +QModelIndex spUpdateCenterRadius::cast( NifModel * nif, const QModelIndex & index ) +{ + QModelIndex iData = nif->getBlock( index ); + + QVector verts = nif->getArray( iData, "Vertices" ); + if ( ! verts.count() ) + return index; + + Vector3 center; + float radius = 0.0f; + + /* + Oblivion and CT_volatile meshes require a + different center algorithm + */ + if( ( ( nif->getVersionNumber() & 0x14000000 ) && ( nif->getUserVersion() == 11 ) ) + || ( nif->get(iData, "Consistency Flags") & 0x8000 ) ) + { + /* is a Oblivion mesh! */ + float xMin(FLT_MAX), xMax(-FLT_MAX); + float yMin(FLT_MAX), yMax(-FLT_MAX); + float zMin(FLT_MAX), zMax(-FLT_MAX); + foreach( Vector3 v, verts ) + { + if( v[0] < xMin ) + xMin = v[0]; + else if ( v[0] > xMax ) + xMax = v[0]; + + if( v[1] < yMin ) + yMin = v[1]; + else if ( v[1] > yMax ) + yMax = v[1]; + + if( v[2] < zMin ) + zMin = v[2]; + else if ( v[2] > zMax ) + zMax = v[2]; + } + + center = Vector3( xMin + xMax, yMin + yMax, zMin + zMax ) / 2; + } + else { + foreach( Vector3 v, verts ) + { + center += v; + } + center /= verts.count(); + } + + float d; + foreach ( Vector3 v, verts ) + { + if ( ( d = ( center - v ).length() ) > radius ) + radius = d; + } + + nif->set( iData, "Center", center ); + nif->set( iData, "Radius", radius ); + + return index; +} + +REGISTER_SPELL( spUpdateCenterRadius ); diff --git a/spells/mesh.h b/spells/mesh.h index 73260fbdb..ea9dbcfd8 100644 --- a/spells/mesh.h +++ b/spells/mesh.h @@ -1,17 +1,17 @@ -#ifndef SP_MESH_H -#define SP_MESH_H - -#include "../spellbook.h" - -class spUpdateCenterRadius : public Spell -{ -public: - QString name() const { return Spell::tr("Update Center/Radius"); } - QString page() const { return Spell::tr("Mesh"); } - - - bool isApplicable( const NifModel * nif, const QModelIndex & index ); - QModelIndex cast( NifModel * nif, const QModelIndex & index ); -}; - -#endif +#ifndef SP_MESH_H +#define SP_MESH_H + +#include "../spellbook.h" + +class spUpdateCenterRadius : public Spell +{ +public: + QString name() const { return Spell::tr("Update Center/Radius"); } + QString page() const { return Spell::tr("Mesh"); } + + + bool isApplicable( const NifModel * nif, const QModelIndex & index ); + QModelIndex cast( NifModel * nif, const QModelIndex & index ); +}; + +#endif diff --git a/spells/misc.cpp b/spells/misc.cpp index 2e05c9a8c..c8cd033a5 100644 --- a/spells/misc.cpp +++ b/spells/misc.cpp @@ -1,136 +1,136 @@ -#include "../spellbook.h" - - -class spUpdateArray : public Spell -{ -public: - QString name() const { return Spell::tr( "Update" ); } - QString page() const { return Spell::tr( "Array" ); } - QIcon icon() const { return QIcon( ":/img/update" ); } - bool instant() const { return true; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - if ( nif->isArray(index) && nif->evalCondition( index ) ) - { - //Check if array is of fixed size - NifItem * item = static_cast( index.internalPointer() ); - bool static1 = true; - bool static2 = true; - - if ( item->arr1().isEmpty() == false ) - { - item->arr1().toInt( &static1 ); - } - - if ( item->arr2().isEmpty() == false ) - { - item->arr2().toInt( &static2 ); - } - - //Leave this commented out until a way for static arrays to be initialized to the right size is created. - //if ( static1 && static2 ) - //{ - // //Neither arr1 or arr2 is a variable name - // return false; - //} - - //One of arr1 or arr2 is a variable name so the array is dynamic - return true; - } - - return false; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - nif->updateArray( index ); - return index; - } -}; - -REGISTER_SPELL( spUpdateArray ) - -class spUpdateHeader : public Spell -{ -public: - QString name() const { return Spell::tr( "Update" ); } - QString page() const { return Spell::tr( "Header" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ( nif->getHeader() == nif->getBlockOrHeader( index ) ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - nif->updateHeader(); - return index; - } -}; - -REGISTER_SPELL( spUpdateHeader ) - -class spUpdateFooter : public Spell -{ -public: - QString name() const { return Spell::tr( "Update" ); } - QString page() const { return Spell::tr( "Footer" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ( nif->getFooter() == nif->getBlockOrHeader( index ) ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - nif->updateFooter(); - return index; - } -}; - -REGISTER_SPELL( spUpdateFooter ) - -class spFollowLink : public Spell -{ -public: - QString name() const { return Spell::tr( "Follow Link" ); } - bool instant() const { return true; } - QIcon icon() const { return QIcon( ":/img/link" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isLink( index ) && nif->getLink( index ) >= 0; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex idx = nif->getBlock( nif->getLink( index ) ); - if ( idx.isValid() ) - return idx; - else - return index; - } -}; - -REGISTER_SPELL( spFollowLink ) - -class spFileOffset : public Spell -{ -public: - QString name() const { return Spell::tr( "File Offset" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif && index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - int ofs = nif->fileOffset( index ); - qWarning( QString( "estimated file offset is %1 (0x%2)" ).arg( ofs ).arg( ofs, 0, 16 ).toAscii() ); - return index; - } -}; - -REGISTER_SPELL( spFileOffset ) +#include "../spellbook.h" + + +class spUpdateArray : public Spell +{ +public: + QString name() const { return Spell::tr( "Update" ); } + QString page() const { return Spell::tr( "Array" ); } + QIcon icon() const { return QIcon( ":/img/update" ); } + bool instant() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + if ( nif->isArray(index) && nif->evalCondition( index ) ) + { + //Check if array is of fixed size + NifItem * item = static_cast( index.internalPointer() ); + bool static1 = true; + bool static2 = true; + + if ( item->arr1().isEmpty() == false ) + { + item->arr1().toInt( &static1 ); + } + + if ( item->arr2().isEmpty() == false ) + { + item->arr2().toInt( &static2 ); + } + + //Leave this commented out until a way for static arrays to be initialized to the right size is created. + //if ( static1 && static2 ) + //{ + // //Neither arr1 or arr2 is a variable name + // return false; + //} + + //One of arr1 or arr2 is a variable name so the array is dynamic + return true; + } + + return false; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + nif->updateArray( index ); + return index; + } +}; + +REGISTER_SPELL( spUpdateArray ) + +class spUpdateHeader : public Spell +{ +public: + QString name() const { return Spell::tr( "Update" ); } + QString page() const { return Spell::tr( "Header" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ( nif->getHeader() == nif->getBlockOrHeader( index ) ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + nif->updateHeader(); + return index; + } +}; + +REGISTER_SPELL( spUpdateHeader ) + +class spUpdateFooter : public Spell +{ +public: + QString name() const { return Spell::tr( "Update" ); } + QString page() const { return Spell::tr( "Footer" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ( nif->getFooter() == nif->getBlockOrHeader( index ) ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + nif->updateFooter(); + return index; + } +}; + +REGISTER_SPELL( spUpdateFooter ) + +class spFollowLink : public Spell +{ +public: + QString name() const { return Spell::tr( "Follow Link" ); } + bool instant() const { return true; } + QIcon icon() const { return QIcon( ":/img/link" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isLink( index ) && nif->getLink( index ) >= 0; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex idx = nif->getBlock( nif->getLink( index ) ); + if ( idx.isValid() ) + return idx; + else + return index; + } +}; + +REGISTER_SPELL( spFollowLink ) + +class spFileOffset : public Spell +{ +public: + QString name() const { return Spell::tr( "File Offset" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif && index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + int ofs = nif->fileOffset( index ); + qWarning( QString( "estimated file offset is %1 (0x%2)" ).arg( ofs ).arg( ofs, 0, 16 ).toAscii() ); + return index; + } +}; + +REGISTER_SPELL( spFileOffset ) diff --git a/spells/morphctrl.cpp b/spells/morphctrl.cpp index 16d7f3c4e..b2385b0f0 100644 --- a/spells/morphctrl.cpp +++ b/spells/morphctrl.cpp @@ -1,111 +1,111 @@ -#include "../spellbook.h" - -#include - -class spMorphFrameSave : public Spell -{ -public: - QString name() const { return "Save Vertices To Frame"; } - QString page() const { return "Morph"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return checkMorpher( nif, index ) && listFrames( nif, index ).count() > 0; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iMeshData = getMeshData( nif, index ); - QModelIndex iMorphData = getMorphData( nif, index ); - - QMenu menu; - QStringList frameList = listFrames( nif, index ); - - if ( nif->get( iMeshData, "Num Vertices" ) != nif->get( iMorphData, "Num Vertices" ) ) - menu.addAction( frameList.first() ); - else - foreach ( QString f, frameList ) - menu.addAction( f ); - - QAction * act = menu.exec( QCursor::pos() ); - if ( act ) - { - QModelIndex iFrames = getFrameArray( nif, index ); - int selFrame = frameList.indexOf( act->text() ); - if ( selFrame == 0 ) - { - qWarning() << "overriding base key frame, all other frames will be cleared"; - nif->set( iMorphData, "Num Vertices", nif->get( iMeshData, "Num Vertices" ) ); - QVector verts = nif->getArray( iMeshData, "Vertices" ); - nif->updateArray( iFrames.child( 0, 0 ), "Vectors" ); - nif->setArray( iFrames.child( 0, 0 ), "Vectors", verts ); - verts.fill( Vector3() ); - for ( int f = 1; f < nif->rowCount( iFrames ); f++ ) - { - nif->updateArray( iFrames.child( f, 0 ), "Vectors" ); - nif->setArray( iFrames.child( f, 0 ), "Vectors", verts ); - } - } - else - { - QVector verts = nif->getArray( iMeshData, "Vertices" ); - QVector base = nif->getArray( iFrames.child( 0, 0 ), "Vectors" ); - QVector frame( base.count(), Vector3() ); - for ( int n = 0; n < base.count(); n++ ) - frame[ n ] = verts.value( n ) - base[ n ]; - nif->setArray( iFrames.child( selFrame, 0 ), "Vectors", frame ); - } - } - - return index; - } - - bool checkMorpher( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index, "NiGeomMorpherController" ) && nif->checkVersion( 0x0a010000, 0 ) && getMeshData( nif, index ).isValid(); - } - - QModelIndex getMeshData( const NifModel * nif, const QModelIndex & iMorpher ) - { - QModelIndex iMesh = nif->getBlock( nif->getParent( nif->getBlockNumber( iMorpher ) ) ); - if ( nif->inherits( iMesh, "NiTriBasedGeom" ) ) - { - QModelIndex iData = nif->getBlock( nif->getLink( iMesh, "Data" ) ); - if ( nif->inherits( iData, "NiTriBasedGeomData" ) ) - return iData; - else - return QModelIndex(); - } - else - return QModelIndex(); - } - - QModelIndex getMorphData( const NifModel * nif, const QModelIndex & iMorpher ) - { - return nif->getBlock( nif->getLink( iMorpher, "Data" ), "NiMorphData" ); - } - - QModelIndex getFrameArray( const NifModel * nif, const QModelIndex & iMorpher ) - { - return nif->getIndex( getMorphData( nif, iMorpher ), "Morphs" ); - } - - QStringList listFrames( const NifModel * nif, const QModelIndex & iMorpher ) - { - QModelIndex iFrames = getFrameArray( nif, iMorpher ); - if ( iFrames.isValid() ) - { - QStringList list; - for ( int i = 0; i < nif->rowCount( iFrames ); i++ ) - { - list << nif->get( iFrames.child( i, 0 ), "Frame Name" ); - } - return list; - } - else - return QStringList(); - } - -}; - -REGISTER_SPELL( spMorphFrameSave ) +#include "../spellbook.h" + +#include + +class spMorphFrameSave : public Spell +{ +public: + QString name() const { return "Save Vertices To Frame"; } + QString page() const { return "Morph"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return checkMorpher( nif, index ) && listFrames( nif, index ).count() > 0; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iMeshData = getMeshData( nif, index ); + QModelIndex iMorphData = getMorphData( nif, index ); + + QMenu menu; + QStringList frameList = listFrames( nif, index ); + + if ( nif->get( iMeshData, "Num Vertices" ) != nif->get( iMorphData, "Num Vertices" ) ) + menu.addAction( frameList.first() ); + else + foreach ( QString f, frameList ) + menu.addAction( f ); + + QAction * act = menu.exec( QCursor::pos() ); + if ( act ) + { + QModelIndex iFrames = getFrameArray( nif, index ); + int selFrame = frameList.indexOf( act->text() ); + if ( selFrame == 0 ) + { + qWarning() << "overriding base key frame, all other frames will be cleared"; + nif->set( iMorphData, "Num Vertices", nif->get( iMeshData, "Num Vertices" ) ); + QVector verts = nif->getArray( iMeshData, "Vertices" ); + nif->updateArray( iFrames.child( 0, 0 ), "Vectors" ); + nif->setArray( iFrames.child( 0, 0 ), "Vectors", verts ); + verts.fill( Vector3() ); + for ( int f = 1; f < nif->rowCount( iFrames ); f++ ) + { + nif->updateArray( iFrames.child( f, 0 ), "Vectors" ); + nif->setArray( iFrames.child( f, 0 ), "Vectors", verts ); + } + } + else + { + QVector verts = nif->getArray( iMeshData, "Vertices" ); + QVector base = nif->getArray( iFrames.child( 0, 0 ), "Vectors" ); + QVector frame( base.count(), Vector3() ); + for ( int n = 0; n < base.count(); n++ ) + frame[ n ] = verts.value( n ) - base[ n ]; + nif->setArray( iFrames.child( selFrame, 0 ), "Vectors", frame ); + } + } + + return index; + } + + bool checkMorpher( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index, "NiGeomMorpherController" ) && nif->checkVersion( 0x0a010000, 0 ) && getMeshData( nif, index ).isValid(); + } + + QModelIndex getMeshData( const NifModel * nif, const QModelIndex & iMorpher ) + { + QModelIndex iMesh = nif->getBlock( nif->getParent( nif->getBlockNumber( iMorpher ) ) ); + if ( nif->inherits( iMesh, "NiTriBasedGeom" ) ) + { + QModelIndex iData = nif->getBlock( nif->getLink( iMesh, "Data" ) ); + if ( nif->inherits( iData, "NiTriBasedGeomData" ) ) + return iData; + else + return QModelIndex(); + } + else + return QModelIndex(); + } + + QModelIndex getMorphData( const NifModel * nif, const QModelIndex & iMorpher ) + { + return nif->getBlock( nif->getLink( iMorpher, "Data" ), "NiMorphData" ); + } + + QModelIndex getFrameArray( const NifModel * nif, const QModelIndex & iMorpher ) + { + return nif->getIndex( getMorphData( nif, iMorpher ), "Morphs" ); + } + + QStringList listFrames( const NifModel * nif, const QModelIndex & iMorpher ) + { + QModelIndex iFrames = getFrameArray( nif, iMorpher ); + if ( iFrames.isValid() ) + { + QStringList list; + for ( int i = 0; i < nif->rowCount( iFrames ); i++ ) + { + list << nif->get( iFrames.child( i, 0 ), "Frame Name" ); + } + return list; + } + else + return QStringList(); + } + +}; + +REGISTER_SPELL( spMorphFrameSave ) diff --git a/spells/normals.cpp b/spells/normals.cpp index ee156c537..075770610 100644 --- a/spells/normals.cpp +++ b/spells/normals.cpp @@ -1,204 +1,204 @@ -#include "../spellbook.h" - -#include "../NvTriStrip/qtwrapper.h" - -#include -#include -#include -#include -#include - -class spFaceNormals : public Spell -{ -public: - QString name() const { return Spell::tr("Face Normals"); } - QString page() const { return Spell::tr("Mesh"); } - - static QModelIndex getShapeData( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = nif->getBlock( index ); - if ( nif->isNiBlock( index, "NiTriShape" ) || nif->isNiBlock( index, "NiTriStrips" ) ) - iData = nif->getBlock( nif->getLink( index, "Data" ) ); - if ( nif->isNiBlock( iData, "NiTriShapeData" ) || nif->isNiBlock( iData, "NiTriStripsData" ) ) - return iData; - else return QModelIndex(); - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return getShapeData( nif, index ).isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = getShapeData( nif, index ); - - QVector verts = nif->getArray( iData, "Vertices" ); - QVector triangles; - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - if ( iPoints.isValid() ) - { - QList< QVector< quint16 > > strips; - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); - triangles = triangulate( strips ); - } - else - { - triangles = nif->getArray( iData, "Triangles" ); - } - - - QVector norms( verts.count() ); - foreach ( Triangle tri, triangles ) - { - Vector3 a = verts[ tri[0] ]; - Vector3 b = verts[ tri[1] ]; - Vector3 c = verts[ tri[2] ]; - - Vector3 fn = Vector3::crossproduct( b - a, c - a ); - norms[ tri[0] ] += fn; - norms[ tri[1] ] += fn; - norms[ tri[2] ] += fn; - } - - for ( int n = 0; n < norms.count(); n++ ) - { - norms[ n ].normalize(); - } - - nif->set( iData, "Has Normals", 1 ); - nif->updateArray( iData, "Normals" ); - nif->setArray( iData, "Normals", norms ); - - return index; - } -}; - -REGISTER_SPELL( spFaceNormals ) - -class spSmoothNormals : public Spell -{ -public: - QString name() const { return Spell::tr("Smooth Normals"); } - QString page() const { return Spell::tr("Mesh"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return spFaceNormals::getShapeData( nif, index ).isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = spFaceNormals::getShapeData( nif, index ); - - QVector verts = nif->getArray( iData, "Vertices" ); - QVector norms = nif->getArray( iData, "Normals" ); - - if ( verts.isEmpty() || verts.count() != norms.count() ) - return index; - - QDialog dlg; - dlg.setWindowTitle( Spell::tr("Smooth Normals") ); - - QGridLayout * grid = new QGridLayout; - dlg.setLayout( grid ); - - QDoubleSpinBox * angle = new QDoubleSpinBox; - angle->setRange( 0, 180 ); - angle->setValue( 60 ); - angle->setSingleStep( 5 ); - - grid->addWidget( new QLabel( Spell::tr("Max Smooth Angle") ), 0, 0 ); - grid->addWidget( angle, 0, 1 ); - - QDoubleSpinBox * dist = new QDoubleSpinBox; - dist->setRange( 0, 1 ); - dist->setDecimals( 4 ); - dist->setSingleStep( 0.01 ); - dist->setValue( 0.001 ); - - grid->addWidget( new QLabel( Spell::tr("Max Vertex Distance") ), 1, 0 ); - grid->addWidget( dist, 1, 1 ); - - QPushButton * btOk = new QPushButton; - btOk->setText( Spell::tr("Smooth") ); - QObject::connect( btOk, SIGNAL( clicked() ), & dlg, SLOT( accept() ) ); - - QPushButton * btCancel = new QPushButton; - btCancel->setText( Spell::tr("Cancel") ); - QObject::connect( btCancel, SIGNAL( clicked() ), & dlg, SLOT( reject() ) ); - - grid->addWidget( btOk, 2, 0 ); - grid->addWidget( btCancel, 2, 1 ); - - if ( dlg.exec() != QDialog::Accepted ) - return index; - - - float maxa = angle->value() / 180 * PI; - float maxd = dist->value(); - - QVector snorms( norms ); - - for ( int i = 0; i < verts.count(); i++ ) - { - const Vector3 & a = verts[i]; - Vector3 an = norms[i]; - for ( int j = i+1; j < verts.count(); j++ ) - { - const Vector3 & b = verts[j]; - if ( ( a - b ).squaredLength() < maxd ) - { - Vector3 bn = norms[j]; - if ( Vector3::angle( an, bn ) < maxa ) - { - snorms[i] += bn; - snorms[j] += an; - } - } - } - } - - for ( int i = 0; i < verts.count(); i++ ) - snorms[i].normalize(); - - nif->setArray( iData, "Normals", snorms ); - - return index; - } -}; - -REGISTER_SPELL( spSmoothNormals ) - -class spNormalize : public Spell -{ -public: - QString name() const { return Spell::tr("Normalize"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ( nif->getValue( index ).type() == NifValue::tVector3 ) - || ( nif->isArray( index ) && nif->getValue( index.child( 0, 0 ) ).type() == NifValue::tVector3 ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - if ( nif->isArray( index ) ) - { - QVector norms = nif->getArray( index ); - for ( int n = 0; n < norms.count(); n++ ) - norms[n].normalize(); - nif->setArray( index, norms ); - } - else - { - Vector3 n = nif->get( index ); - n.normalize(); - nif->set( index, n ); - } - return index; - } -}; - -REGISTER_SPELL( spNormalize ) +#include "../spellbook.h" + +#include "../NvTriStrip/qtwrapper.h" + +#include +#include +#include +#include +#include + +class spFaceNormals : public Spell +{ +public: + QString name() const { return Spell::tr("Face Normals"); } + QString page() const { return Spell::tr("Mesh"); } + + static QModelIndex getShapeData( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = nif->getBlock( index ); + if ( nif->isNiBlock( index, "NiTriShape" ) || nif->isNiBlock( index, "NiTriStrips" ) ) + iData = nif->getBlock( nif->getLink( index, "Data" ) ); + if ( nif->isNiBlock( iData, "NiTriShapeData" ) || nif->isNiBlock( iData, "NiTriStripsData" ) ) + return iData; + else return QModelIndex(); + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return getShapeData( nif, index ).isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = getShapeData( nif, index ); + + QVector verts = nif->getArray( iData, "Vertices" ); + QVector triangles; + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + if ( iPoints.isValid() ) + { + QList< QVector< quint16 > > strips; + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); + triangles = triangulate( strips ); + } + else + { + triangles = nif->getArray( iData, "Triangles" ); + } + + + QVector norms( verts.count() ); + foreach ( Triangle tri, triangles ) + { + Vector3 a = verts[ tri[0] ]; + Vector3 b = verts[ tri[1] ]; + Vector3 c = verts[ tri[2] ]; + + Vector3 fn = Vector3::crossproduct( b - a, c - a ); + norms[ tri[0] ] += fn; + norms[ tri[1] ] += fn; + norms[ tri[2] ] += fn; + } + + for ( int n = 0; n < norms.count(); n++ ) + { + norms[ n ].normalize(); + } + + nif->set( iData, "Has Normals", 1 ); + nif->updateArray( iData, "Normals" ); + nif->setArray( iData, "Normals", norms ); + + return index; + } +}; + +REGISTER_SPELL( spFaceNormals ) + +class spSmoothNormals : public Spell +{ +public: + QString name() const { return Spell::tr("Smooth Normals"); } + QString page() const { return Spell::tr("Mesh"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return spFaceNormals::getShapeData( nif, index ).isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = spFaceNormals::getShapeData( nif, index ); + + QVector verts = nif->getArray( iData, "Vertices" ); + QVector norms = nif->getArray( iData, "Normals" ); + + if ( verts.isEmpty() || verts.count() != norms.count() ) + return index; + + QDialog dlg; + dlg.setWindowTitle( Spell::tr("Smooth Normals") ); + + QGridLayout * grid = new QGridLayout; + dlg.setLayout( grid ); + + QDoubleSpinBox * angle = new QDoubleSpinBox; + angle->setRange( 0, 180 ); + angle->setValue( 60 ); + angle->setSingleStep( 5 ); + + grid->addWidget( new QLabel( Spell::tr("Max Smooth Angle") ), 0, 0 ); + grid->addWidget( angle, 0, 1 ); + + QDoubleSpinBox * dist = new QDoubleSpinBox; + dist->setRange( 0, 1 ); + dist->setDecimals( 4 ); + dist->setSingleStep( 0.01 ); + dist->setValue( 0.001 ); + + grid->addWidget( new QLabel( Spell::tr("Max Vertex Distance") ), 1, 0 ); + grid->addWidget( dist, 1, 1 ); + + QPushButton * btOk = new QPushButton; + btOk->setText( Spell::tr("Smooth") ); + QObject::connect( btOk, SIGNAL( clicked() ), & dlg, SLOT( accept() ) ); + + QPushButton * btCancel = new QPushButton; + btCancel->setText( Spell::tr("Cancel") ); + QObject::connect( btCancel, SIGNAL( clicked() ), & dlg, SLOT( reject() ) ); + + grid->addWidget( btOk, 2, 0 ); + grid->addWidget( btCancel, 2, 1 ); + + if ( dlg.exec() != QDialog::Accepted ) + return index; + + + float maxa = angle->value() / 180 * PI; + float maxd = dist->value(); + + QVector snorms( norms ); + + for ( int i = 0; i < verts.count(); i++ ) + { + const Vector3 & a = verts[i]; + Vector3 an = norms[i]; + for ( int j = i+1; j < verts.count(); j++ ) + { + const Vector3 & b = verts[j]; + if ( ( a - b ).squaredLength() < maxd ) + { + Vector3 bn = norms[j]; + if ( Vector3::angle( an, bn ) < maxa ) + { + snorms[i] += bn; + snorms[j] += an; + } + } + } + } + + for ( int i = 0; i < verts.count(); i++ ) + snorms[i].normalize(); + + nif->setArray( iData, "Normals", snorms ); + + return index; + } +}; + +REGISTER_SPELL( spSmoothNormals ) + +class spNormalize : public Spell +{ +public: + QString name() const { return Spell::tr("Normalize"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ( nif->getValue( index ).type() == NifValue::tVector3 ) + || ( nif->isArray( index ) && nif->getValue( index.child( 0, 0 ) ).type() == NifValue::tVector3 ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + if ( nif->isArray( index ) ) + { + QVector norms = nif->getArray( index ); + for ( int n = 0; n < norms.count(); n++ ) + norms[n].normalize(); + nif->setArray( index, norms ); + } + else + { + Vector3 n = nif->get( index ); + n.normalize(); + nif->set( index, n ); + } + return index; + } +}; + +REGISTER_SPELL( spNormalize ) diff --git a/spells/optimize.cpp b/spells/optimize.cpp index a41f566f7..9334a323c 100644 --- a/spells/optimize.cpp +++ b/spells/optimize.cpp @@ -1,435 +1,435 @@ -#include "../spellbook.h" - -#include -#include - -#include "blocks.h" -#include "mesh.h" -#include "tangentspace.h" -#include "transform.h" - -class spCombiProps : public Spell -{ -public: - QString name() const { return Spell::tr( "Combine Properties" ); } - QString page() const { return Spell::tr( "Optimize" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif && ! index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & ) - { - QMap props; - QMap map; - do - { - props.clear(); - map.clear(); - - for ( qint32 b = 0; b < nif->getBlockCount(); b++ ) - { - QModelIndex iBlock = nif->getBlock( b ); - if ( nif->isNiBlock( iBlock, "NiMaterialProperty" ) ) - { - if ( nif->get( iBlock, "Name" ).contains( "Material" ) ) - nif->set( iBlock, "Name", "Material" ); - else if ( nif->get( iBlock, "Name" ).contains( "Default" ) ) - nif->set( iBlock, "Name", "Default" ); - } - if ( nif->inherits( iBlock, "NiProperty" ) || nif->inherits( iBlock, "NiSourceTexture" ) ) - { - QBuffer data; - data.open( QBuffer::WriteOnly ); - data.write( nif->itemName( iBlock ).toAscii() ); - nif->save( data, iBlock ); - props.insert( b, data.buffer() ); - } - } - - foreach ( qint32 x, props.keys() ) - { - foreach ( qint32 y, props.keys() ) - { - if ( x < y && ( ! map.contains( y ) ) && props[x].size() == props[y].size() ) - { - int c = 0; - while ( c < props[x].size() ) - if ( props[x][c] == props[y][c] ) - c++; - else - break; - if ( c == props[x].size() ) - map.insert( y, x ); - } - } - } - - if ( ! map.isEmpty() ) - { - qWarning() << "removing" << map.count() << "properties"; - nif->mapLinks( map ); - QList l = map.keys(); - qSort( l.begin(), l.end(), qGreater() ); - foreach ( qint32 b, l ) - nif->removeNiBlock( b ); - } - - } while ( ! map.isEmpty() ); - - return QModelIndex(); - } -}; - -REGISTER_SPELL( spCombiProps ) - - -class spUniqueProps : public Spell -{ -public: - QString name() const { return Spell::tr( "Split Properties" ); } - QString page() const { return Spell::tr( "Optimize" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif && ! index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - for ( int b = 0; b < nif->getBlockCount(); b++ ) - { - QModelIndex iAVObj = nif->getBlock( b, "NiAVObject" ); - if ( iAVObj.isValid() ) - { - QVector props = nif->getLinkArray( iAVObj, "Properties" ); - QMutableVectorIterator it( props ); - while ( it.hasNext() ) - { - qint32 & l = it.next(); - QModelIndex iProp = nif->getBlock( l, "NiProperty" ); - if ( iProp.isValid() && nif->getParent( l ) != b ) - { - QMap map; - if ( nif->isNiBlock( iProp, "NiTexturingProperty" ) ) - { - foreach ( qint32 sl, nif->getChildLinks( nif->getBlockNumber( iProp ) ) ) - { - QModelIndex iSrc = nif->getBlock( sl, "NiSourceTexture" ); - if ( iSrc.isValid() && ! map.contains( sl ) ) - { - QModelIndex iSrc2 = nif->insertNiBlock( "NiSourceTexture", nif->getBlockCount() + 1 ); - QBuffer buffer; - buffer.open( QBuffer::WriteOnly ); - nif->save( buffer, iSrc ); - buffer.close(); - buffer.open( QBuffer::ReadOnly ); - nif->load( buffer, iSrc2 ); - map[ sl ] = nif->getBlockNumber( iSrc2 ); - } - } - } - - QModelIndex iProp2 = nif->insertNiBlock( nif->itemName( iProp ), nif->getBlockCount() + 1 ); - QBuffer buffer; - buffer.open( QBuffer::WriteOnly ); - nif->save( buffer, iProp ); - buffer.close(); - buffer.open( QBuffer::ReadOnly ); - nif->loadAndMapLinks( buffer, iProp2, map ); - l = nif->getBlockNumber( iProp2 ); - } - } - nif->setLinkArray( iAVObj, "Properties", props ); - } - } - return index; - } -}; - -REGISTER_SPELL( spUniqueProps ) - - -class spRemoveBogusNodes : public Spell -{ -public: - QString name() const { return Spell::tr( "Remove Bogus Nodes" ); } - QString page() const { return Spell::tr( "Optimize" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif && ! index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - bool removed; - int cnt = 0; - do - { - removed = false; - for ( int b = 0; b < nif->getBlockCount(); b++ ) - { - QModelIndex iNode = nif->getBlock( b, "NiNode" ); - if ( iNode.isValid() ) - { - if ( nif->getChildLinks( b ).isEmpty() && nif->getParentLinks( b ).isEmpty() ) - { - int x = 0; - for ( int c = 0; c < nif->getBlockCount(); c++ ) - { - if ( c != b ) - { - if ( nif->getChildLinks( c ).contains( b ) ) - x++; - if ( nif->getParentLinks( c ).contains( b ) ) - x = 2; - if ( x >= 2 ) - break; - } - } - if ( x < 2 ) - { - removed = true; - cnt++; - nif->removeNiBlock( b ); - break; - } - } - } - } - } while ( removed ); - - if ( cnt > 0 ) - qWarning() << "removed" << cnt << "ninodes"; - - return QModelIndex(); - } -}; - -REGISTER_SPELL( spRemoveBogusNodes ) - - -class spCombiTris : public Spell -{ -public: - QString name() const { return Spell::tr( "Combine Shapes" ); } - QString page() const { return Spell::tr( "Optimize" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif && nif->isNiBlock( index, "NiNode" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - // join meshes which share properties and parent - // ( animated ones are left untouched ) - - QPersistentModelIndex iParent( index ); - - // populate a list of possible candidates - - QList lTris; - - foreach ( qint32 lChild, nif->getLinkArray( iParent, "Children" ) ) - { - if ( nif->getParent( lChild ) == nif->getBlockNumber( iParent ) ) - { - QModelIndex iChild = nif->getBlock( lChild ); - if ( nif->isNiBlock( iChild, "NiTriShape" ) || nif->isNiBlock( iChild, "NiTriStrips" ) ) - lTris << lChild; - } - } - - // detect matches - - QMap< qint32, QList< qint32 > > match; - QList found; - - foreach ( qint32 lTriA, lTris ) - { - if ( found.contains( lTriA ) ) - continue; - foreach ( qint32 lTriB, lTris ) - { - if ( matches( nif, nif->getBlock( lTriA ), nif->getBlock( lTriB ) ) ) - { - match[ lTriA ] << lTriB; - found << lTriB; - } - } - } - - // combine the matches - - spApplyTransformation ApplyTransform; - spTangentSpace TSpace; - - QList remove; - - foreach ( qint32 lTriA, match.keys() ) - { - ApplyTransform.cast( nif, nif->getBlock( lTriA ) ); - - foreach ( qint32 lTriB, match[ lTriA ] ) - { - ApplyTransform.cast( nif, nif->getBlock( lTriB ) ); - combine( nif, nif->getBlock( lTriA ), nif->getBlock( lTriB ) ); - remove << nif->getBlock( lTriB ); - } - - TSpace.castIfApplicable( nif, nif->getBlock( lTriA ) ); - } - - // remove the now obsolete shapes - - spRemoveBranch BranchRemover; - - foreach ( QModelIndex rem, remove ) - { - BranchRemover.cast( nif, rem ); - } - - return iParent; - } - - bool matches( const NifModel * nif, QModelIndex iTriA, QModelIndex iTriB ) - { - if ( iTriA == iTriB || nif->itemName( iTriA ) != nif->itemName( iTriB ) - || nif->get( iTriA, "Flags" ) != nif->get( iTriB, "Flags" ) ) - return false; - - QVector lPrpsA = nif->getLinkArray( iTriA, "Properties" ); - QVector lPrpsB = nif->getLinkArray( iTriB, "Properties" ); - - qSort( lPrpsA ); - qSort( lPrpsB ); - - if ( lPrpsA != lPrpsB ) - return false; - - foreach ( qint32 l, nif->getChildLinks( nif->getBlockNumber( iTriA ) ) ) - { - if ( lPrpsA.contains( l ) ) continue; - QModelIndex iBlock = nif->getBlock( l ); - if ( nif->isNiBlock( iBlock, "NiTriShapeData" ) ) - continue; - if ( nif->isNiBlock( iBlock, "NiTriStripsData" ) ) - continue; - if ( nif->isNiBlock( iBlock, "NiBinaryExtraData" ) && nif->get( iBlock, "Name" ) == "Tangent space (binormal & tangent vectors)" ) - continue; - qWarning() << "Attached " << nif->itemName( iBlock ) << " prevents " << nif->get( iTriA, "Name" ) << " and " << nif->get( iTriB, "Name" ) << " from matching."; - return false; - } - - foreach ( qint32 l, nif->getChildLinks( nif->getBlockNumber( iTriB ) ) ) - { - if ( lPrpsB.contains( l ) ) - continue; - QModelIndex iBlock = nif->getBlock( l ); - if ( nif->isNiBlock( iBlock, "NiTriShapeData" ) ) - continue; - if ( nif->isNiBlock( iBlock, "NiTriStripsData" ) ) - continue; - if ( nif->isNiBlock( iBlock, "NiBinaryExtraData" ) && nif->get( iBlock, "Name" ) == "Tangent space (binormal & tangent vectors)" ) - continue; - qWarning() << "Attached " << nif->itemName( iBlock ) << " prevents " << nif->get( iTriA, "Name" ) << " and " << nif->get( iTriB, "Name" ) << " from matching."; - return false; - } - - QModelIndex iDataA = nif->getBlock( nif->getLink( iTriA, "Data" ), "NiTriBasedGeomData" ); - QModelIndex iDataB = nif->getBlock( nif->getLink( iTriB, "Data" ), "NiTriBasedGeomData" ); - - return dataMatches( nif, iDataA, iDataB ); - } - - bool dataMatches( const NifModel * nif, QModelIndex iDataA, QModelIndex iDataB ) - { - if ( iDataA == iDataB ) - return true; - - foreach ( QString id, QStringList() << "Vertices" << "Normals" << "Vertex Colors" << "UV Sets" ) - { - QModelIndex iA = nif->getIndex( iDataA, id ); - QModelIndex iB = nif->getIndex( iDataB, id ); - - if ( iA.isValid() != iB.isValid() ) - return false; - - if ( id == "UV Sets" && nif->rowCount( iA ) != nif->rowCount( iB ) ) - return false; - } - return true; - } - - void combine( NifModel * nif, QModelIndex iTriA, QModelIndex iTriB ) - { // combine meshes a and b ( a += b ) - nif->set( iTriB, "Flags", nif->get( iTriB, "Flags" ) | 1 ); - - QModelIndex iDataA = nif->getBlock( nif->getLink( iTriA, "Data" ), "NiTriBasedGeomData" ); - QModelIndex iDataB = nif->getBlock( nif->getLink( iTriB, "Data" ), "NiTriBasedGeomData" ); - - int numA = nif->get( iDataA, "Num Vertices" ); - int numB = nif->get( iDataB, "Num Vertices" ); - nif->set( iDataA, "Num Vertices", numA + numB ); - - nif->updateArray( iDataA, "Vertices" ); - nif->setArray( iDataA, "Vertices", nif->getArray( iDataA, "Vertices" ).mid( 0, numA ) + nif->getArray( iDataB, "Vertices" ) ); - - nif->updateArray( iDataA, "Normals" ); - nif->setArray( iDataA, "Normals", nif->getArray( iDataA, "Normals" ).mid( 0, numA ) + nif->getArray( iDataB, "Normals" ) ); - - nif->updateArray( iDataA, "Vertex Colors" ); - nif->setArray( iDataA, "Vertex Colors", nif->getArray( iDataA, "Vertex Colors" ).mid( 0, numA ) + nif->getArray( iDataB, "Vertex Colors" ) ); - - QModelIndex iUVa = nif->getIndex( iDataA, "UV Sets" ); - QModelIndex iUVb = nif->getIndex( iDataB, "UV Sets" ); - - for ( int r = 0; r < nif->rowCount( iUVa ); r++ ) - { - nif->updateArray( iUVa.child( r, 0 ) ); - nif->setArray( iUVa.child( r, 0 ), nif->getArray( iUVa.child( r, 0 ) ).mid( 0, numA ) + nif->getArray( iUVb.child( r, 0 ) ) ); - } - - int triCntA = nif->get( iDataA, "Num Triangles" ); - int triCntB = nif->get( iDataB, "Num Triangles" ); - nif->set( iDataA, "Num Triangles", triCntA + triCntB ); - nif->set( iDataA, "Num Triangle Points", ( triCntA + triCntB ) * 3 ); - - QVector triangles = nif->getArray( iDataB, "Triangles" ); - QMutableVectorIterator itTri( triangles ); - while ( itTri.hasNext() ) - { - Triangle & tri = itTri.next(); - tri[0] += numA; - tri[1] += numA; - tri[2] += numA; - } - nif->updateArray( iDataA, "Triangles" ); - nif->setArray( iDataA, "Triangles", triangles + nif->getArray( iDataA, "Triangles" ) ); - - int stripCntA = nif->get( iDataA, "Num Strips" ); - int stripCntB = nif->get( iDataB, "Num Strips" ); - nif->set( iDataA, "Num Strips", stripCntA + stripCntB ); - - nif->updateArray( iDataA, "Strip Lengths" ); - nif->updateArray( iDataA, "Points" ); - for ( int r = 0; r < stripCntB; r++ ) - { - QVector strip = nif->getArray( nif->getIndex( iDataB, "Points" ).child( r, 0 ) ); - QMutableVectorIterator it( strip ); - while ( it.hasNext() ) - it.next() += numA; - nif->set( nif->getIndex( iDataA, "Strip Lengths" ).child( r + stripCntA, 0 ), strip.size() ); - nif->updateArray( nif->getIndex( iDataA, "Points" ).child( r + stripCntA, 0 ) ); - nif->setArray( nif->getIndex( iDataA, "Points" ).child( r + stripCntA, 0 ), strip ); - } - - spUpdateCenterRadius CenterRadius; - CenterRadius.castIfApplicable( nif, iDataA ); - } -}; - -REGISTER_SPELL( spCombiTris ) +#include "../spellbook.h" + +#include +#include + +#include "blocks.h" +#include "mesh.h" +#include "tangentspace.h" +#include "transform.h" + +class spCombiProps : public Spell +{ +public: + QString name() const { return Spell::tr( "Combine Properties" ); } + QString page() const { return Spell::tr( "Optimize" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif && ! index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) + { + QMap props; + QMap map; + do + { + props.clear(); + map.clear(); + + for ( qint32 b = 0; b < nif->getBlockCount(); b++ ) + { + QModelIndex iBlock = nif->getBlock( b ); + if ( nif->isNiBlock( iBlock, "NiMaterialProperty" ) ) + { + if ( nif->get( iBlock, "Name" ).contains( "Material" ) ) + nif->set( iBlock, "Name", "Material" ); + else if ( nif->get( iBlock, "Name" ).contains( "Default" ) ) + nif->set( iBlock, "Name", "Default" ); + } + if ( nif->inherits( iBlock, "NiProperty" ) || nif->inherits( iBlock, "NiSourceTexture" ) ) + { + QBuffer data; + data.open( QBuffer::WriteOnly ); + data.write( nif->itemName( iBlock ).toAscii() ); + nif->save( data, iBlock ); + props.insert( b, data.buffer() ); + } + } + + foreach ( qint32 x, props.keys() ) + { + foreach ( qint32 y, props.keys() ) + { + if ( x < y && ( ! map.contains( y ) ) && props[x].size() == props[y].size() ) + { + int c = 0; + while ( c < props[x].size() ) + if ( props[x][c] == props[y][c] ) + c++; + else + break; + if ( c == props[x].size() ) + map.insert( y, x ); + } + } + } + + if ( ! map.isEmpty() ) + { + qWarning() << "removing" << map.count() << "properties"; + nif->mapLinks( map ); + QList l = map.keys(); + qSort( l.begin(), l.end(), qGreater() ); + foreach ( qint32 b, l ) + nif->removeNiBlock( b ); + } + + } while ( ! map.isEmpty() ); + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spCombiProps ) + + +class spUniqueProps : public Spell +{ +public: + QString name() const { return Spell::tr( "Split Properties" ); } + QString page() const { return Spell::tr( "Optimize" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif && ! index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + for ( int b = 0; b < nif->getBlockCount(); b++ ) + { + QModelIndex iAVObj = nif->getBlock( b, "NiAVObject" ); + if ( iAVObj.isValid() ) + { + QVector props = nif->getLinkArray( iAVObj, "Properties" ); + QMutableVectorIterator it( props ); + while ( it.hasNext() ) + { + qint32 & l = it.next(); + QModelIndex iProp = nif->getBlock( l, "NiProperty" ); + if ( iProp.isValid() && nif->getParent( l ) != b ) + { + QMap map; + if ( nif->isNiBlock( iProp, "NiTexturingProperty" ) ) + { + foreach ( qint32 sl, nif->getChildLinks( nif->getBlockNumber( iProp ) ) ) + { + QModelIndex iSrc = nif->getBlock( sl, "NiSourceTexture" ); + if ( iSrc.isValid() && ! map.contains( sl ) ) + { + QModelIndex iSrc2 = nif->insertNiBlock( "NiSourceTexture", nif->getBlockCount() + 1 ); + QBuffer buffer; + buffer.open( QBuffer::WriteOnly ); + nif->save( buffer, iSrc ); + buffer.close(); + buffer.open( QBuffer::ReadOnly ); + nif->load( buffer, iSrc2 ); + map[ sl ] = nif->getBlockNumber( iSrc2 ); + } + } + } + + QModelIndex iProp2 = nif->insertNiBlock( nif->itemName( iProp ), nif->getBlockCount() + 1 ); + QBuffer buffer; + buffer.open( QBuffer::WriteOnly ); + nif->save( buffer, iProp ); + buffer.close(); + buffer.open( QBuffer::ReadOnly ); + nif->loadAndMapLinks( buffer, iProp2, map ); + l = nif->getBlockNumber( iProp2 ); + } + } + nif->setLinkArray( iAVObj, "Properties", props ); + } + } + return index; + } +}; + +REGISTER_SPELL( spUniqueProps ) + + +class spRemoveBogusNodes : public Spell +{ +public: + QString name() const { return Spell::tr( "Remove Bogus Nodes" ); } + QString page() const { return Spell::tr( "Optimize" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif && ! index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + bool removed; + int cnt = 0; + do + { + removed = false; + for ( int b = 0; b < nif->getBlockCount(); b++ ) + { + QModelIndex iNode = nif->getBlock( b, "NiNode" ); + if ( iNode.isValid() ) + { + if ( nif->getChildLinks( b ).isEmpty() && nif->getParentLinks( b ).isEmpty() ) + { + int x = 0; + for ( int c = 0; c < nif->getBlockCount(); c++ ) + { + if ( c != b ) + { + if ( nif->getChildLinks( c ).contains( b ) ) + x++; + if ( nif->getParentLinks( c ).contains( b ) ) + x = 2; + if ( x >= 2 ) + break; + } + } + if ( x < 2 ) + { + removed = true; + cnt++; + nif->removeNiBlock( b ); + break; + } + } + } + } + } while ( removed ); + + if ( cnt > 0 ) + qWarning() << "removed" << cnt << "ninodes"; + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spRemoveBogusNodes ) + + +class spCombiTris : public Spell +{ +public: + QString name() const { return Spell::tr( "Combine Shapes" ); } + QString page() const { return Spell::tr( "Optimize" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif && nif->isNiBlock( index, "NiNode" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + // join meshes which share properties and parent + // ( animated ones are left untouched ) + + QPersistentModelIndex iParent( index ); + + // populate a list of possible candidates + + QList lTris; + + foreach ( qint32 lChild, nif->getLinkArray( iParent, "Children" ) ) + { + if ( nif->getParent( lChild ) == nif->getBlockNumber( iParent ) ) + { + QModelIndex iChild = nif->getBlock( lChild ); + if ( nif->isNiBlock( iChild, "NiTriShape" ) || nif->isNiBlock( iChild, "NiTriStrips" ) ) + lTris << lChild; + } + } + + // detect matches + + QMap< qint32, QList< qint32 > > match; + QList found; + + foreach ( qint32 lTriA, lTris ) + { + if ( found.contains( lTriA ) ) + continue; + foreach ( qint32 lTriB, lTris ) + { + if ( matches( nif, nif->getBlock( lTriA ), nif->getBlock( lTriB ) ) ) + { + match[ lTriA ] << lTriB; + found << lTriB; + } + } + } + + // combine the matches + + spApplyTransformation ApplyTransform; + spTangentSpace TSpace; + + QList remove; + + foreach ( qint32 lTriA, match.keys() ) + { + ApplyTransform.cast( nif, nif->getBlock( lTriA ) ); + + foreach ( qint32 lTriB, match[ lTriA ] ) + { + ApplyTransform.cast( nif, nif->getBlock( lTriB ) ); + combine( nif, nif->getBlock( lTriA ), nif->getBlock( lTriB ) ); + remove << nif->getBlock( lTriB ); + } + + TSpace.castIfApplicable( nif, nif->getBlock( lTriA ) ); + } + + // remove the now obsolete shapes + + spRemoveBranch BranchRemover; + + foreach ( QModelIndex rem, remove ) + { + BranchRemover.cast( nif, rem ); + } + + return iParent; + } + + bool matches( const NifModel * nif, QModelIndex iTriA, QModelIndex iTriB ) + { + if ( iTriA == iTriB || nif->itemName( iTriA ) != nif->itemName( iTriB ) + || nif->get( iTriA, "Flags" ) != nif->get( iTriB, "Flags" ) ) + return false; + + QVector lPrpsA = nif->getLinkArray( iTriA, "Properties" ); + QVector lPrpsB = nif->getLinkArray( iTriB, "Properties" ); + + qSort( lPrpsA ); + qSort( lPrpsB ); + + if ( lPrpsA != lPrpsB ) + return false; + + foreach ( qint32 l, nif->getChildLinks( nif->getBlockNumber( iTriA ) ) ) + { + if ( lPrpsA.contains( l ) ) continue; + QModelIndex iBlock = nif->getBlock( l ); + if ( nif->isNiBlock( iBlock, "NiTriShapeData" ) ) + continue; + if ( nif->isNiBlock( iBlock, "NiTriStripsData" ) ) + continue; + if ( nif->isNiBlock( iBlock, "NiBinaryExtraData" ) && nif->get( iBlock, "Name" ) == "Tangent space (binormal & tangent vectors)" ) + continue; + qWarning() << "Attached " << nif->itemName( iBlock ) << " prevents " << nif->get( iTriA, "Name" ) << " and " << nif->get( iTriB, "Name" ) << " from matching."; + return false; + } + + foreach ( qint32 l, nif->getChildLinks( nif->getBlockNumber( iTriB ) ) ) + { + if ( lPrpsB.contains( l ) ) + continue; + QModelIndex iBlock = nif->getBlock( l ); + if ( nif->isNiBlock( iBlock, "NiTriShapeData" ) ) + continue; + if ( nif->isNiBlock( iBlock, "NiTriStripsData" ) ) + continue; + if ( nif->isNiBlock( iBlock, "NiBinaryExtraData" ) && nif->get( iBlock, "Name" ) == "Tangent space (binormal & tangent vectors)" ) + continue; + qWarning() << "Attached " << nif->itemName( iBlock ) << " prevents " << nif->get( iTriA, "Name" ) << " and " << nif->get( iTriB, "Name" ) << " from matching."; + return false; + } + + QModelIndex iDataA = nif->getBlock( nif->getLink( iTriA, "Data" ), "NiTriBasedGeomData" ); + QModelIndex iDataB = nif->getBlock( nif->getLink( iTriB, "Data" ), "NiTriBasedGeomData" ); + + return dataMatches( nif, iDataA, iDataB ); + } + + bool dataMatches( const NifModel * nif, QModelIndex iDataA, QModelIndex iDataB ) + { + if ( iDataA == iDataB ) + return true; + + foreach ( QString id, QStringList() << "Vertices" << "Normals" << "Vertex Colors" << "UV Sets" ) + { + QModelIndex iA = nif->getIndex( iDataA, id ); + QModelIndex iB = nif->getIndex( iDataB, id ); + + if ( iA.isValid() != iB.isValid() ) + return false; + + if ( id == "UV Sets" && nif->rowCount( iA ) != nif->rowCount( iB ) ) + return false; + } + return true; + } + + void combine( NifModel * nif, QModelIndex iTriA, QModelIndex iTriB ) + { // combine meshes a and b ( a += b ) + nif->set( iTriB, "Flags", nif->get( iTriB, "Flags" ) | 1 ); + + QModelIndex iDataA = nif->getBlock( nif->getLink( iTriA, "Data" ), "NiTriBasedGeomData" ); + QModelIndex iDataB = nif->getBlock( nif->getLink( iTriB, "Data" ), "NiTriBasedGeomData" ); + + int numA = nif->get( iDataA, "Num Vertices" ); + int numB = nif->get( iDataB, "Num Vertices" ); + nif->set( iDataA, "Num Vertices", numA + numB ); + + nif->updateArray( iDataA, "Vertices" ); + nif->setArray( iDataA, "Vertices", nif->getArray( iDataA, "Vertices" ).mid( 0, numA ) + nif->getArray( iDataB, "Vertices" ) ); + + nif->updateArray( iDataA, "Normals" ); + nif->setArray( iDataA, "Normals", nif->getArray( iDataA, "Normals" ).mid( 0, numA ) + nif->getArray( iDataB, "Normals" ) ); + + nif->updateArray( iDataA, "Vertex Colors" ); + nif->setArray( iDataA, "Vertex Colors", nif->getArray( iDataA, "Vertex Colors" ).mid( 0, numA ) + nif->getArray( iDataB, "Vertex Colors" ) ); + + QModelIndex iUVa = nif->getIndex( iDataA, "UV Sets" ); + QModelIndex iUVb = nif->getIndex( iDataB, "UV Sets" ); + + for ( int r = 0; r < nif->rowCount( iUVa ); r++ ) + { + nif->updateArray( iUVa.child( r, 0 ) ); + nif->setArray( iUVa.child( r, 0 ), nif->getArray( iUVa.child( r, 0 ) ).mid( 0, numA ) + nif->getArray( iUVb.child( r, 0 ) ) ); + } + + int triCntA = nif->get( iDataA, "Num Triangles" ); + int triCntB = nif->get( iDataB, "Num Triangles" ); + nif->set( iDataA, "Num Triangles", triCntA + triCntB ); + nif->set( iDataA, "Num Triangle Points", ( triCntA + triCntB ) * 3 ); + + QVector triangles = nif->getArray( iDataB, "Triangles" ); + QMutableVectorIterator itTri( triangles ); + while ( itTri.hasNext() ) + { + Triangle & tri = itTri.next(); + tri[0] += numA; + tri[1] += numA; + tri[2] += numA; + } + nif->updateArray( iDataA, "Triangles" ); + nif->setArray( iDataA, "Triangles", triangles + nif->getArray( iDataA, "Triangles" ) ); + + int stripCntA = nif->get( iDataA, "Num Strips" ); + int stripCntB = nif->get( iDataB, "Num Strips" ); + nif->set( iDataA, "Num Strips", stripCntA + stripCntB ); + + nif->updateArray( iDataA, "Strip Lengths" ); + nif->updateArray( iDataA, "Points" ); + for ( int r = 0; r < stripCntB; r++ ) + { + QVector strip = nif->getArray( nif->getIndex( iDataB, "Points" ).child( r, 0 ) ); + QMutableVectorIterator it( strip ); + while ( it.hasNext() ) + it.next() += numA; + nif->set( nif->getIndex( iDataA, "Strip Lengths" ).child( r + stripCntA, 0 ), strip.size() ); + nif->updateArray( nif->getIndex( iDataA, "Points" ).child( r + stripCntA, 0 ) ); + nif->setArray( nif->getIndex( iDataA, "Points" ).child( r + stripCntA, 0 ), strip ); + } + + spUpdateCenterRadius CenterRadius; + CenterRadius.castIfApplicable( nif, iDataA ); + } +}; + +REGISTER_SPELL( spCombiTris ) diff --git a/spells/sanitize.cpp b/spells/sanitize.cpp index 5d3d000de..7ff2547ea 100644 --- a/spells/sanitize.cpp +++ b/spells/sanitize.cpp @@ -1,313 +1,313 @@ -#include "../spellbook.h" - -#include - -class spSanitizeLinkArrays20000005 : public Spell -{ -public: - QString name() const { return "Adjust Link Arrays"; } - QString page() const { return "Sanitize"; } - bool sanity() const { return true; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->checkVersion( 0x14000005, 0x14000005 ) && ! index.isValid(); - } - - static bool compareChildLinks( QPair a, QPair b ) - { - return a.second != b.second ? a.second : a.first < b.first; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & ) - { - for ( int n = 0; n < nif->getBlockCount(); n++ ) - { - QModelIndex iBlock = nif->getBlock( n ); - - // reorder children (tribasedgeom first) - QModelIndex iNumChildren = nif->getIndex( iBlock, "Num Children" ); - QModelIndex iChildren = nif->getIndex( iBlock, "Children" ); - if ( iNumChildren.isValid() && iChildren.isValid() ) - { - QList< QPair > links; - for ( int r = 0; r < nif->rowCount( iChildren ); r++ ) - { - qint32 l = nif->getLink( iChildren.child( r, 0 ) ); - if ( l >= 0 ) - links.append( QPair( l, nif->inherits( nif->getBlock( l ), "NiTriBasedGeom" ) ) ); - } - - qStableSort( links.begin(), links.end(), compareChildLinks ); - - for ( int r = 0; r < links.count(); r++ ) - { - if ( links[r].first != nif->getLink( iChildren.child( r, 0 ) ) ) - nif->setLink( iChildren.child( r, 0 ), links[r].first ); - nif->set( iNumChildren, links.count() ); - nif->updateArray( iChildren ); - } - } - - // remove empty property links - QModelIndex iNumProperties = nif->getIndex( iBlock, "Num Properties" ); - QModelIndex iProperties = nif->getIndex( iBlock, "Properties" ); - if ( iNumProperties.isValid() && iProperties.isValid() ) - { - QVector links; - for ( int r = 0; r < nif->rowCount( iProperties ); r++ ) - { - qint32 l = nif->getLink( iProperties.child( r, 0 ) ); - if ( l >= 0 ) links.append( l ); - } - if ( links.count() < nif->rowCount( iProperties ) ) - { - nif->set( iNumProperties, links.count() ); - nif->updateArray( iProperties ); - nif->setLinkArray( iProperties, links ); - } - } - } - return QModelIndex(); - } -}; - -REGISTER_SPELL( spSanitizeLinkArrays20000005 ) - - -class spAdjustTextureSources : public Spell -{ -public: - QString name() const { return "Adjust Texture Sources"; } - QString page() const { return "Sanitize"; } - bool sanity() const { return true; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif && ! index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & ) - { - for ( int i = 0; i < nif->getBlockCount(); i++ ) - { - QModelIndex iTexSrc = nif->getBlock( i, "NiSourceTexture" ); - if ( iTexSrc.isValid() ) - { - QModelIndex iFileName = nif->getIndex( iTexSrc, "File Name" ); - if ( iFileName.isValid() ) // adjust file path - nif->set( iFileName, nif->get( iFileName ).replace( "/", "\\" ) ); - - if ( nif->checkVersion( 0x14000005, 0x14000005 ) ) - { // adjust format options (oblivion only) - nif->set( iTexSrc, "Pixel Layout", 6 ); - nif->set( iTexSrc, "Use Mipmaps", 1 ); - nif->set( iTexSrc, "Alpha Format", 3 ); - nif->set( iTexSrc, "Unknown Byte", 1 ); - nif->set( iTexSrc, "Unknown Byte 2", 1 ); - } - } - } - return QModelIndex(); - } -}; - -REGISTER_SPELL( spAdjustTextureSources ) - - -class spSanitizeHavokBlockOrder : public Spell -{ -public: - QString name() const { return "Reorder Havok Blocks"; } - QString page() const { return "Sanitize"; } - bool sanity() const { return true; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->checkVersion( 0x14000005, 0x14000005 ) && ! index.isValid(); - } - - struct wrap - { - qint32 blocknr; - QList parents; - QList children; - }; - - QModelIndex cast( NifModel * nif, const QModelIndex & ) - { - // wrap blocks for sorting - QVector sortwrapper( nif->getBlockCount() ); - for ( qint32 n = 0; n < nif->getBlockCount(); n++ ) - { - sortwrapper[n].blocknr = n; - if ( isHvkBlock( nif, nif->getBlock( n ) ) ) - { - QStack stack; - stack.push( n ); - setup( nif, stack, sortwrapper ); - } - else if ( nif->inherits( nif->getBlock( n ), "bhkConstraint" ) ) - { - QStack stack; - stack.push( n ); - setupConstraint( nif, stack, sortwrapper ); - } - } - - // brute force sorting - QVector::iterator i, j; - i = sortwrapper.begin(); - while ( i != sortwrapper.end() ) - { - j = i + 1; - while ( j != sortwrapper.end() ) - { - if ( compareBlocks( *j, *i ) ) - { - qSwap( *j, *i ); - break; - } - ++j; - } - if ( j == sortwrapper.end() ) - ++i; - } - - // extract the new block order - QVector order( sortwrapper.count() ); - for ( qint32 n = 0; n < sortwrapper.count(); n++ ) - order[ sortwrapper[ n ].blocknr ] = n; - - // reorder the blocks - nif->reorderBlocks( order ); - - return QModelIndex(); - } - - static bool isHvkBlock( const NifModel * nif, const QModelIndex & iBlock ) - { - return nif->inherits( iBlock, "bhkShape" ) || nif->inherits( iBlock, "NiCollisionObject" ) || nif->inherits( iBlock, "NiTriBasedGeomData" ); - } - - static void setup( const NifModel * nif, QStack & stack, QVector & sortwrapper ) - { - foreach ( qint32 link, nif->getChildLinks( stack.top() ) ) - { - if ( isHvkBlock( nif, nif->getBlock( link ) ) ) - { - foreach ( qint32 x, stack ) - { - if ( ! sortwrapper[ link ].parents.contains( x ) ) - sortwrapper[ link ].parents.append( x ); - if ( ! sortwrapper[ x ].children.contains( link ) ) - sortwrapper[ x ].children.append( link ); - } - - stack.push( link ); - setup( nif, stack, sortwrapper ); - stack.pop(); - } - } - } - - static void setupConstraint( const NifModel * nif, QStack & stack, QVector & sortwrapper ) - { - foreach ( qint32 link, nif->getParentLinks( stack.top() ) ) - { - if ( isHvkBlock( nif, nif->getBlock( link ) ) ) - { - foreach ( qint32 x, stack ) - { - if ( ! sortwrapper[ link ].parents.contains( x ) ) - sortwrapper[ link ].parents.append( x ); - if ( ! sortwrapper[ x ].children.contains( link ) ) - sortwrapper[ x ].children.append( link ); - } - } - } - } - - static bool compareBlocks( const wrap & a, const wrap & b ) - { - return a.parents.contains( b.blocknr ) || b.children.contains( a.blocknr ); - } -}; - -REGISTER_SPELL( spSanitizeHavokBlockOrder ) - - -class spSanityCheckLinks : public Spell -{ -public: - QString name() const { return "Check Links"; } - QString page() const { return "Sanitize"; } - bool sanity() const { return true; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ! index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & ) - { - for ( int b = 0; b < nif->getBlockCount(); b++ ) - { - QModelIndex iBlock = nif->getBlock( b ); - QModelIndex idx = check( nif, iBlock ); - if ( idx.isValid() ) - return idx; - } - return QModelIndex(); - } - - QModelIndex check( NifModel * nif, const QModelIndex & iParent ) - { - for ( int r = 0; r < nif->rowCount( iParent ); r++ ) - { - QModelIndex idx = iParent.child( r, 0 ); - bool child; - if ( nif->isLink( idx, &child ) ) - { - qint32 l = nif->getLink( idx ); - if ( l < 0 ) - { - /* - if ( ! child ) - { - qWarning() << "unassigned parent link"; - return idx; - } - */ - } - else if ( l >= nif->getBlockCount() ) - { - qWarning() << "invalid link"; - return idx; - } - else - { - QString tmplt = nif->itemTmplt( idx ); - if ( ! tmplt.isEmpty() ) - { - QModelIndex iBlock = nif->getBlock( l ); - if ( ! nif->inherits( iBlock, tmplt ) ) - { - qWarning() << "link points to wrong block type"; - return idx; - } - } - } - } - if ( nif->rowCount( idx ) > 0 ) - { - QModelIndex x = check( nif, idx ); - if ( x.isValid() ) - return x; - } - } - return QModelIndex(); - } -}; - -REGISTER_SPELL( spSanityCheckLinks ) +#include "../spellbook.h" + +#include + +class spSanitizeLinkArrays20000005 : public Spell +{ +public: + QString name() const { return "Adjust Link Arrays"; } + QString page() const { return "Sanitize"; } + bool sanity() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->checkVersion( 0x14000005, 0x14000005 ) && ! index.isValid(); + } + + static bool compareChildLinks( QPair a, QPair b ) + { + return a.second != b.second ? a.second : a.first < b.first; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) + { + for ( int n = 0; n < nif->getBlockCount(); n++ ) + { + QModelIndex iBlock = nif->getBlock( n ); + + // reorder children (tribasedgeom first) + QModelIndex iNumChildren = nif->getIndex( iBlock, "Num Children" ); + QModelIndex iChildren = nif->getIndex( iBlock, "Children" ); + if ( iNumChildren.isValid() && iChildren.isValid() ) + { + QList< QPair > links; + for ( int r = 0; r < nif->rowCount( iChildren ); r++ ) + { + qint32 l = nif->getLink( iChildren.child( r, 0 ) ); + if ( l >= 0 ) + links.append( QPair( l, nif->inherits( nif->getBlock( l ), "NiTriBasedGeom" ) ) ); + } + + qStableSort( links.begin(), links.end(), compareChildLinks ); + + for ( int r = 0; r < links.count(); r++ ) + { + if ( links[r].first != nif->getLink( iChildren.child( r, 0 ) ) ) + nif->setLink( iChildren.child( r, 0 ), links[r].first ); + nif->set( iNumChildren, links.count() ); + nif->updateArray( iChildren ); + } + } + + // remove empty property links + QModelIndex iNumProperties = nif->getIndex( iBlock, "Num Properties" ); + QModelIndex iProperties = nif->getIndex( iBlock, "Properties" ); + if ( iNumProperties.isValid() && iProperties.isValid() ) + { + QVector links; + for ( int r = 0; r < nif->rowCount( iProperties ); r++ ) + { + qint32 l = nif->getLink( iProperties.child( r, 0 ) ); + if ( l >= 0 ) links.append( l ); + } + if ( links.count() < nif->rowCount( iProperties ) ) + { + nif->set( iNumProperties, links.count() ); + nif->updateArray( iProperties ); + nif->setLinkArray( iProperties, links ); + } + } + } + return QModelIndex(); + } +}; + +REGISTER_SPELL( spSanitizeLinkArrays20000005 ) + + +class spAdjustTextureSources : public Spell +{ +public: + QString name() const { return "Adjust Texture Sources"; } + QString page() const { return "Sanitize"; } + bool sanity() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif && ! index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) + { + for ( int i = 0; i < nif->getBlockCount(); i++ ) + { + QModelIndex iTexSrc = nif->getBlock( i, "NiSourceTexture" ); + if ( iTexSrc.isValid() ) + { + QModelIndex iFileName = nif->getIndex( iTexSrc, "File Name" ); + if ( iFileName.isValid() ) // adjust file path + nif->set( iFileName, nif->get( iFileName ).replace( "/", "\\" ) ); + + if ( nif->checkVersion( 0x14000005, 0x14000005 ) ) + { // adjust format options (oblivion only) + nif->set( iTexSrc, "Pixel Layout", 6 ); + nif->set( iTexSrc, "Use Mipmaps", 1 ); + nif->set( iTexSrc, "Alpha Format", 3 ); + nif->set( iTexSrc, "Unknown Byte", 1 ); + nif->set( iTexSrc, "Unknown Byte 2", 1 ); + } + } + } + return QModelIndex(); + } +}; + +REGISTER_SPELL( spAdjustTextureSources ) + + +class spSanitizeHavokBlockOrder : public Spell +{ +public: + QString name() const { return "Reorder Havok Blocks"; } + QString page() const { return "Sanitize"; } + bool sanity() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->checkVersion( 0x14000005, 0x14000005 ) && ! index.isValid(); + } + + struct wrap + { + qint32 blocknr; + QList parents; + QList children; + }; + + QModelIndex cast( NifModel * nif, const QModelIndex & ) + { + // wrap blocks for sorting + QVector sortwrapper( nif->getBlockCount() ); + for ( qint32 n = 0; n < nif->getBlockCount(); n++ ) + { + sortwrapper[n].blocknr = n; + if ( isHvkBlock( nif, nif->getBlock( n ) ) ) + { + QStack stack; + stack.push( n ); + setup( nif, stack, sortwrapper ); + } + else if ( nif->inherits( nif->getBlock( n ), "bhkConstraint" ) ) + { + QStack stack; + stack.push( n ); + setupConstraint( nif, stack, sortwrapper ); + } + } + + // brute force sorting + QVector::iterator i, j; + i = sortwrapper.begin(); + while ( i != sortwrapper.end() ) + { + j = i + 1; + while ( j != sortwrapper.end() ) + { + if ( compareBlocks( *j, *i ) ) + { + qSwap( *j, *i ); + break; + } + ++j; + } + if ( j == sortwrapper.end() ) + ++i; + } + + // extract the new block order + QVector order( sortwrapper.count() ); + for ( qint32 n = 0; n < sortwrapper.count(); n++ ) + order[ sortwrapper[ n ].blocknr ] = n; + + // reorder the blocks + nif->reorderBlocks( order ); + + return QModelIndex(); + } + + static bool isHvkBlock( const NifModel * nif, const QModelIndex & iBlock ) + { + return nif->inherits( iBlock, "bhkShape" ) || nif->inherits( iBlock, "NiCollisionObject" ) || nif->inherits( iBlock, "NiTriBasedGeomData" ); + } + + static void setup( const NifModel * nif, QStack & stack, QVector & sortwrapper ) + { + foreach ( qint32 link, nif->getChildLinks( stack.top() ) ) + { + if ( isHvkBlock( nif, nif->getBlock( link ) ) ) + { + foreach ( qint32 x, stack ) + { + if ( ! sortwrapper[ link ].parents.contains( x ) ) + sortwrapper[ link ].parents.append( x ); + if ( ! sortwrapper[ x ].children.contains( link ) ) + sortwrapper[ x ].children.append( link ); + } + + stack.push( link ); + setup( nif, stack, sortwrapper ); + stack.pop(); + } + } + } + + static void setupConstraint( const NifModel * nif, QStack & stack, QVector & sortwrapper ) + { + foreach ( qint32 link, nif->getParentLinks( stack.top() ) ) + { + if ( isHvkBlock( nif, nif->getBlock( link ) ) ) + { + foreach ( qint32 x, stack ) + { + if ( ! sortwrapper[ link ].parents.contains( x ) ) + sortwrapper[ link ].parents.append( x ); + if ( ! sortwrapper[ x ].children.contains( link ) ) + sortwrapper[ x ].children.append( link ); + } + } + } + } + + static bool compareBlocks( const wrap & a, const wrap & b ) + { + return a.parents.contains( b.blocknr ) || b.children.contains( a.blocknr ); + } +}; + +REGISTER_SPELL( spSanitizeHavokBlockOrder ) + + +class spSanityCheckLinks : public Spell +{ +public: + QString name() const { return "Check Links"; } + QString page() const { return "Sanitize"; } + bool sanity() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ! index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) + { + for ( int b = 0; b < nif->getBlockCount(); b++ ) + { + QModelIndex iBlock = nif->getBlock( b ); + QModelIndex idx = check( nif, iBlock ); + if ( idx.isValid() ) + return idx; + } + return QModelIndex(); + } + + QModelIndex check( NifModel * nif, const QModelIndex & iParent ) + { + for ( int r = 0; r < nif->rowCount( iParent ); r++ ) + { + QModelIndex idx = iParent.child( r, 0 ); + bool child; + if ( nif->isLink( idx, &child ) ) + { + qint32 l = nif->getLink( idx ); + if ( l < 0 ) + { + /* + if ( ! child ) + { + qWarning() << "unassigned parent link"; + return idx; + } + */ + } + else if ( l >= nif->getBlockCount() ) + { + qWarning() << "invalid link"; + return idx; + } + else + { + QString tmplt = nif->itemTmplt( idx ); + if ( ! tmplt.isEmpty() ) + { + QModelIndex iBlock = nif->getBlock( l ); + if ( ! nif->inherits( iBlock, tmplt ) ) + { + qWarning() << "link points to wrong block type"; + return idx; + } + } + } + } + if ( nif->rowCount( idx ) > 0 ) + { + QModelIndex x = check( nif, idx ); + if ( x.isValid() ) + return x; + } + } + return QModelIndex(); + } +}; + +REGISTER_SPELL( spSanityCheckLinks ) diff --git a/spells/skeleton.cpp b/spells/skeleton.cpp index a63321fd3..49eea28f0 100644 --- a/spells/skeleton.cpp +++ b/spells/skeleton.cpp @@ -1,1006 +1,1006 @@ -#include "../spellbook.h" - -#include "skeleton.h" - -#include "../gl/gltools.h" - -#include "../NvTriStrip/qtwrapper.h" - -#include -#include -#include -#include -#include -#include -#include - -#define SKEL_DAT ":/res/spells/skel.dat" - -QDebug operator<<( QDebug dbg, const Vector3 & v ) -{ - return dbg << v[0] << v[1] << v[2]; -} - -typedef QMap TransMap; - -class spFixSkeleton : public Spell -{ -public: - QString name() const { return "Fix Bip01"; } - QString page() const { return "Skeleton"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ( nif->getVersion() == "4.0.0.2" && nif->itemType( index ) == "NiBlock" && nif->get( index, "Name" ) == "Bip01" ); //&& QFile::exists( SKEL_DAT ) ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QFile file( SKEL_DAT ); - if ( file.open( QIODevice::ReadOnly ) ) - { - QDataStream stream( &file ); - - TransMap local; - TransMap world; - QString name; - do - { - stream >> name; - if ( !name.isEmpty() ) - { - Transform t; - stream >> t; - local.insert( name, t ); - stream >> t; - world.insert( name, t ); - } - } - while ( ! name.isEmpty() ); - - TransMap bones; - doBones( nif, index, Transform(), local, bones ); - - foreach ( int link, nif->getChildLinks( nif->getBlockNumber( index ) ) ) - { - QModelIndex iChild = nif->getBlock( link ); - if ( iChild.isValid() ) - { - if ( nif->itemName( iChild ) == "NiNode" ) - { - doNodes( nif, iChild, Transform(), world, bones ); - } - else if ( nif->inherits( iChild, "NiTriBasedGeom" ) ) - { - doShape( nif, iChild, Transform(), world, bones ); - } - } - } - - nif->reset(); - } - - return index; - } - - void doBones( NifModel * nif, const QModelIndex & index, const Transform & tparent, const TransMap & local, TransMap & bones ) - { - QString name = nif->get( index, "Name" ); - if ( name.startsWith( "Bip01" ) ) - { - Transform tlocal( nif, index ); - bones.insert( name, tparent * tlocal ); - - local.value( name ).writeBack( nif, index ); - - foreach ( int link, nif->getChildLinks( nif->getBlockNumber( index ) ) ) - { - QModelIndex iChild = nif->getBlock( link, "NiNode" ); - if ( iChild.isValid() ) - doBones( nif, iChild, tparent * tlocal, local, bones ); - } - } - } - - bool doNodes( NifModel * nif, const QModelIndex & index, const Transform & tparent, const TransMap & world, const TransMap & bones ) - { - bool hasSkinnedChildren = false; - - QString name = nif->get( index, "Name" ); - if ( ! name.startsWith( "Bip01" ) ) - { - Transform tlocal( nif, index ); - - foreach ( int link, nif->getChildLinks( nif->getBlockNumber( index ) ) ) - { - QModelIndex iChild = nif->getBlock( link ); - if ( iChild.isValid() ) - { - if ( nif->itemName( iChild ) == "NiNode" ) - { - hasSkinnedChildren |= doNodes( nif, iChild, tparent * tlocal, world, bones ); - } - else if ( nif->inherits( iChild, "NiTriBasedGeom" ) ) - { - hasSkinnedChildren |= doShape( nif, iChild, tparent * tlocal, world, bones ); - } - } - } - } - - if ( hasSkinnedChildren ) - { - Transform().writeBack( nif, index ); - } - - return hasSkinnedChildren; - } - bool doShape( NifModel * nif, const QModelIndex & index, const Transform & tparent, const TransMap & world, const TransMap & bones ) - { - QModelIndex iShapeData = nif->getBlock( nif->getLink( index, "Data" ) ); - QModelIndex iSkinInstance = nif->getBlock( nif->getLink( index, "Skin Instance" ), "NiSkinInstance" ); - if ( ! iSkinInstance.isValid() || ! iShapeData.isValid() ) - return false; - QStringList names; - QModelIndex iNames = nif->getIndex( iSkinInstance, "Bones" ); - if ( iNames.isValid() ) - iNames = nif->getIndex( iNames, "Bones" ); - if ( iNames.isValid() ) - for ( int n = 0; n < nif->rowCount( iNames ); n++ ) - { - QModelIndex iBone = nif->getBlock( nif->getLink( iNames.child( n, 0 ) ), "NiNode" ); - if ( iBone.isValid() ) - names.append( nif->get( iBone, "Name" ) ); - else - names.append(""); - } - QModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInstance, "Data" ), "NiSkinData" ); - if ( !iSkinData.isValid() ) - return false; - QModelIndex iBones = nif->getIndex( iSkinData, "Bone List" ); - if ( ! iBones.isValid() ) - return false; - - Transform t( nif, iSkinData ); - t = tparent * t; - t.writeBack( nif, iSkinData ); - - for ( int b = 0; b < nif->rowCount( iBones ) && b < names.count(); b++ ) - { - QModelIndex iBone = iBones.child( b, 0 ); - - t = Transform( nif, iBone ); - - t.rotation = world.value( names[ b ] ).rotation.inverted() * bones.value( names[ b ] ).rotation * t.rotation; - t.translation = world.value( names[ b ] ).rotation.inverted() * bones.value( names[ b ] ).rotation * t.translation; - - t.writeBack( nif, iBone ); - } - - Vector3 center = nif->get( iShapeData, "Center" ); - nif->set( iShapeData, "Center", tparent * center ); - return true; - } -}; - -REGISTER_SPELL( spFixSkeleton ) - - -class spScanSkeleton : public Spell -{ -public: - QString name() const { return "Scan Bip01"; } - QString page() const { return "Skeleton"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ( nif->getVersion() == "4.0.0.2" && nif->itemType( index ) == "NiBlock" && nif->get( index, "Name" ) == "Bip01" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QFile file( SKEL_DAT ); - if ( file.open( QIODevice::WriteOnly ) ) - { - QDataStream stream( &file ); - scan( nif, index, Transform(), stream ); - stream << QString(); - } - return index; - } - - void scan( NifModel * nif, const QModelIndex & index, const Transform & tparent, QDataStream & stream ) - { - QString name = nif->get( index, "Name" ); - if ( name.startsWith( "Bip01" ) ) - { - Transform local( nif, index ); - stream << name << local << tparent * local; - qWarning() << name; - foreach ( int link, nif->getChildLinks( nif->getBlockNumber( index ) ) ) - { - QModelIndex iChild = nif->getBlock( link, "NiNode" ); - if ( iChild.isValid() ) - scan( nif, iChild, tparent * local, stream ); - } - } - } -}; - -//REGISTER_SPELL( spScanSkeleton ) - - -class spSkinPartition : public Spell -{ -public: - QString name() const { return Spell::tr("Make Skin Partition"); } - QString page() const { return Spell::tr("Mesh"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & iShape ) - { - if ( nif->isNiBlock( iShape, "NiTriShape" ) ) - { - QModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); - if ( iSkinInst.isValid() ) - { - return nif->getBlock( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ).isValid(); - } - } - return false; - } - - typedef QPair boneweight; - - typedef struct { - QList bones; - QVector triangles; - } Partition; - - QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) - { - int mbpp = 0, mbpv = 0; - bool make_strips = false; - return cast( nif, iBlock, mbpp, mbpv, make_strips ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & iBlock, int & maxBonesPerPartition, int & maxBonesPerVertex, bool make_strips, bool pad = false ) - { - QPersistentModelIndex iShape = iBlock; - try - { - QPersistentModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ), "NiTriShapeData" ); - QPersistentModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); - QPersistentModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ); - QModelIndex iSkinPart = nif->getBlock( nif->getLink( iSkinInst, "Skin Partition" ), "NiSkinPartition" ); - if ( ! iSkinPart.isValid() ) - iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); - - // read in the weights from NiSkinData - - QVector< QList< boneweight > > weights( nif->get( iData, "Num Vertices" ) ); - - QModelIndex iBoneList = nif->getIndex( iSkinData, "Bone List" ); - int numBones = nif->rowCount( iBoneList ); - for ( int bone = 0; bone < numBones; bone++ ) - { - QModelIndex iVertexWeights = nif->getIndex( iBoneList.child( bone, 0 ), "Vertex Weights" ); - for ( int r = 0; r < nif->rowCount( iVertexWeights ); r++ ) - { - int vertex = nif->get( iVertexWeights.child( r, 0 ), "Index" ); - float weight = nif->get( iVertexWeights.child( r, 0 ), "Weight" ); - if ( vertex >= weights.count() ) - throw QString( Spell::tr("bad NiSkinData - vertex count does not match") ); - weights[vertex].append( boneweight( bone, weight ) ); - } - } - - // count min and max bones per vertex - - int minBones, maxBones; - minBones = maxBones = weights.value( 0 ).count(); - foreach ( QList< boneweight > list, weights ) - { - if ( list.count() < minBones ) - minBones = list.count(); - if ( list.count() > maxBones ) - maxBones = list.count(); - } - - if ( minBones <= 0 ) - throw QString( Spell::tr("bad NiSkinData - some vertices have no weights at all") ); - - // query max bones per vertex/partition - - if ( maxBonesPerPartition <= 0 || maxBonesPerVertex <= 0 ) - { - SkinPartitionDialog dlg( maxBones ); - - if ( dlg.exec() != QDialog::Accepted ) - return iBlock; - - maxBonesPerPartition = dlg.maxBonesPerPartition(); - maxBonesPerVertex = dlg.maxBonesPerVertex(); - make_strips = dlg.makeStrips(); - pad = dlg.padPartitions(); - } - - // reduce vertex influences if necessary - - if ( maxBones > maxBonesPerVertex ) - { - QMutableVectorIterator< QList< boneweight > > it( weights ); - int c = 0; - while ( it.hasNext() ) - { - QList< boneweight > & lst = it.next(); - - if ( lst.count() > maxBonesPerVertex ) - c++; - - while ( lst.count() > maxBonesPerVertex ) - { - int j = 0; - float weight = lst.first().second; - for ( int i = 0; i < lst.count(); i++ ) - { - if ( lst[i].second < weight ) - j = i; - } - lst.removeAt( j ); - } - - float totalWeight = 0; - foreach ( boneweight bw, lst ) - { - totalWeight += bw.second; - } - - for ( int b = 0; b < lst.count(); b++ ) - { // normalize - lst[b].second /= totalWeight; - } - } - qWarning() << QString( Spell::tr( "reduced %1 vertices to %2 bone influences (maximum number of bones per vertex was %3)" ) ).arg( c ).arg( maxBonesPerVertex ).arg( maxBones ); - } - - maxBones = maxBonesPerVertex; - - // reduces bone weights so that the triangles fit into the partitions - - QList triangles = nif->getArray( iData, "Triangles" ).toList(); - - QMap< int, int > match; - bool doMatch = true; - - QList tribones; - - int cnt = 0; - - foreach ( Triangle tri, triangles ) - { - do - { - tribones.clear(); - for ( int c = 0; c < 3; c++ ) - { - foreach ( boneweight bw, weights[tri[c]] ) - { - if ( ! tribones.contains( bw.first ) ) - tribones.append( bw.first ); - } - } - - if ( tribones.count() > maxBonesPerPartition ) - { - // sum up the weights for each bone - // bones with weight == 1 can't be removed - - QMap sum; - QList nono; - - for ( int t = 0; t < 3; t++ ) - { - if ( weights[tri[t]].count() == 1 ) - nono.append( weights[tri[t]].first().first ); - - foreach ( boneweight bw, weights[ tri[t] ] ) - sum[ bw.first ] += bw.second; - } - - // select the bone to remove - - float minWeight = 5.0; - int minBone = -1; - - foreach ( int b, sum.keys() ) - { - if ( ! nono.contains( b ) && sum[b] < minWeight ) - { - minWeight = sum[b]; - minBone = b; - } - } - - if ( minBone < 0 ) // this shouldn't never happen - throw QString( "internal error 0x01" ); - - // do a vertex match detect - - if ( doMatch ) - { - QVector verts = nif->getArray( iData, "Vertices" ); - for ( int a = 0; a < verts.count(); a++ ) - { - match.insertMulti( a, a ); - for ( int b = a + 1; b < verts.count(); b++ ) - { - if ( verts[a] == verts[b] && weights[a] == weights[b] ) - { - match.insertMulti( a, b ); - match.insertMulti( b, a ); - } - } - } - } - - // now remove that bone from all vertices of this triangle and from all matching vertices too - - for ( int t = 0; t < 3; t++ ) - { - bool rem = false; - foreach ( int v, match.values( tri[t] ) ) - { - QList< boneweight > & bws = weights[ v ]; - QMutableListIterator< boneweight > it( bws ); - while ( it.hasNext() ) - { - boneweight & bw = it.next(); - if ( bw.first == minBone ) - { - it.remove(); - rem = true; - } - } - - float totalWeight = 0; - foreach ( boneweight bw, bws ) - { - totalWeight += bw.second; - } - - if ( totalWeight == 0 ) - throw QString( "internal error 0x02" ); - - for ( int b = 0; b < bws.count(); b++ ) - { // normalize - bws[b].second /= totalWeight; - } - } - if ( rem ) - cnt++; - } - } - } while ( tribones.count() > maxBonesPerPartition ); - } - - if ( cnt > 0 ) - qWarning() << QString( Spell::tr( "removed %1 bone influences" ) ).arg( cnt ); - - // split the triangles into partitions - - QList parts; - - while ( ! triangles.isEmpty() ) - { - Partition part; - - QHash usedVerts; - - bool addtriangles; - do - { - QMutableListIterator it( triangles ); - while ( it.hasNext() ) - { - Triangle & tri = it.next(); - - QList tribones; - for ( int c = 0; c < 3; c++ ) - { - foreach ( boneweight bw, weights[tri[c]] ) - { - if ( ! tribones.contains( bw.first ) ) - tribones.append( bw.first ); - } - } - - if ( part.bones.isEmpty() || containsBones( part.bones, tribones ) ) - { - part.bones = mergeBones( part.bones, tribones ); - part.triangles.append( tri ); - usedVerts[ tri[0] ] = true; - usedVerts[ tri[1] ] = true; - usedVerts[ tri[2] ] = true; - it.remove(); - } - } - - addtriangles = false; - - if ( part.bones.count() < maxBonesPerPartition ) - { // if we have room left in the partition then add an adjacent triangle - it.toFront(); - while ( it.hasNext() ) - { - Triangle & tri = it.next(); - - if ( usedVerts.contains( tri[0] ) || usedVerts.contains( tri[1] ) || usedVerts.contains( tri[2] ) ) - { - QList tribones; - for ( int c = 0; c < 3; c++ ) - { - foreach ( boneweight bw, weights[tri[c]] ) - { - if ( ! tribones.contains( bw.first ) ) - tribones.append( bw.first ); - } - } - - tribones = mergeBones( part.bones, tribones ); - if ( tribones.count() <= maxBonesPerPartition ) - { - part.bones = tribones; - part.triangles.append( tri ); - usedVerts[ tri[0] ] = true; - usedVerts[ tri[1] ] = true; - usedVerts[ tri[2] ] = true; - it.remove(); - addtriangles = true; - //break; - } - } - } - } - } - while ( addtriangles ); - - parts.append( part ); - } - - //qWarning() << parts.count() << "small partitions"; - - // merge partitions - - bool merged; - do - { - merged = false; - for ( int p1 = 0; p1 < parts.count() && ! merged; p1++ ) - { - if ( parts[p1].bones.count() < maxBonesPerPartition ) - { - for ( int p2 = p1+1; p2 < parts.count() && ! merged; p2++ ) - { - QList mergedBones = mergeBones( parts[p1].bones, parts[p2].bones ); - if ( mergedBones.count() <= maxBonesPerPartition ) - { - parts[p1].bones = mergedBones; - parts[p1].triangles << parts[p2].triangles; - parts.removeAt( p2 ); - merged = true; - } - } - } - } - } - while ( merged ); - - //qWarning() << parts.count() << "partitions"; - - // create the NiSkinPartition if it doesn't exist yet - - if ( ! iSkinPart.isValid() ) - { - iSkinPart = nif->insertNiBlock( "NiSkinPartition", nif->getBlockNumber( iSkinData ) + 1 ); - nif->setLink( iSkinInst, "Skin Partition", nif->getBlockNumber( iSkinPart ) ); - nif->setLink( iSkinData, "Skin Partition", nif->getBlockNumber( iSkinPart ) ); - } - - // start writing NiSkinPartition - - nif->set( iSkinPart, "Num Skin Partition Blocks", parts.count() ); - nif->updateArray( iSkinPart, "Skin Partition Blocks" ); - - for ( int p = 0; p < parts.count(); p++ ) - { - QModelIndex iPart = nif->getIndex( iSkinPart, "Skin Partition Blocks" ).child( p, 0 ); - - QList bones = parts[p].bones; - qSort( bones ); - - QVector triangles = parts[p].triangles; - - QVector vertices; - foreach ( Triangle tri, triangles ) - { - for ( int t = 0; t < 3; t++ ) - { - if ( ! vertices.contains( tri[t] ) ) - vertices.append( tri[t] ); - } - } - qSort( vertices ); - - // map the vertices - - for ( int tri = 0; tri < triangles.count(); tri++ ) - { - for ( int t = 0; t < 3; t++ ) - { - triangles[tri][t] = vertices.indexOf( triangles[tri][t] ); - } - } - - // stripify the triangles - QList< QVector > strips; - int numTriangles = 0; - if ( make_strips == true ) - { - strips = stripify( triangles ); - - foreach ( QVector strip, strips ) - { - numTriangles += strip.count() - 2; - } - } - else - { - numTriangles = triangles.count(); - } - - // fill in counts - if ( pad ) - { - while ( bones.size() < maxBonesPerPartition ) { - bones.append(0); - } - } - - nif->set( iPart, "Num Vertices", vertices.count() ); - nif->set( iPart, "Num Triangles", numTriangles ); - nif->set( iPart, "Num Bones", bones.count() ); - nif->set( iPart, "Num Strips", strips.count() ); - nif->set( iPart, "Num Weights Per Vertex", maxBones ); - - // fill in bone map - - QModelIndex iBoneMap = nif->getIndex( iPart, "Bones" ); - nif->updateArray( iBoneMap ); - nif->setArray( iBoneMap, bones.toVector() ); - - // fill in vertex map - - nif->set( iPart, "Has Vertex Map", 1 ); - QModelIndex iVertexMap = nif->getIndex( iPart, "Vertex Map" ); - nif->updateArray( iVertexMap ); - nif->setArray( iVertexMap, vertices ); - - // fill in vertex weights - - nif->set( iPart, "Has Vertex Weights", 1 ); - QModelIndex iVWeights = nif->getIndex( iPart, "Vertex Weights" ); - nif->updateArray( iVWeights ); - for ( int v = 0; v < nif->rowCount( iVWeights ); v++ ) - { - QModelIndex iVertex = iVWeights.child( v, 0 ); - nif->updateArray( iVertex ); - QList< boneweight > list = weights.value( vertices[v] ); - for ( int b = 0; b < maxBones; b++ ) - nif->set( iVertex.child( b, 0 ), list.count() > b ? list[ b ].second : 0.0 ); - } - - nif->set( iPart, "Has Faces", 1 ); - - if ( make_strips == true ) - { - //Clear out any existing triangle data that might be left over from an existing Skin Partition - QModelIndex iTriangles = nif->getIndex( iPart, "Triangles" ); - nif->updateArray( iTriangles ); - - // write the strips - QModelIndex iStripLengths = nif->getIndex( iPart, "Strip Lengths" ); - nif->updateArray( iStripLengths ); - for ( int s = 0; s < nif->rowCount( iStripLengths ); s++ ) - nif->set( iStripLengths.child( s, 0 ), strips.value( s ).count() ); - - QModelIndex iStrips = nif->getIndex( iPart, "Strips" ); - nif->updateArray( iStrips ); - for ( int s = 0; s < nif->rowCount( iStrips ); s++ ) - { - nif->updateArray( iStrips.child( s, 0 ) ); - nif->setArray( iStrips.child( s, 0 ), strips.value( s ) ); - } - } - else - { - //Clear out any existing strip data that might be left over from an existing Skin Partition - QModelIndex iStripLengths = nif->getIndex( iPart, "Strip Lengths" ); - nif->updateArray( iStripLengths ); - QModelIndex iStrips = nif->getIndex( iPart, "Strips" ); - nif->updateArray( iStrips ); - - QModelIndex iTriangles = nif->getIndex( iPart, "Triangles" ); - nif->updateArray( iTriangles ); - nif->setArray( iTriangles, triangles ); - } - - // fill in vertex bones - - nif->set( iPart, "Has Bone Indices", 1 ); - QModelIndex iVBones = nif->getIndex( iPart, "Bone Indices" ); - nif->updateArray( iVBones ); - for ( int v = 0; v < nif->rowCount( iVBones ); v++ ) - { - QModelIndex iVertex = iVBones.child( v, 0 ); - nif->updateArray( iVertex ); - QList< boneweight > list = weights.value( vertices[v] ); - for ( int b = 0; b < maxBones; b++ ) - nif->set( iVertex.child( b, 0 ), list.count() > b ? bones.indexOf( list[ b ].first ) : 0 ); - } - } - - // done - - return iShape; - } - catch ( QString err ) - { - if ( ! err.isEmpty() ) - QMessageBox::warning( 0, "NifSkope", err ); - return iShape; - } - } - - static QList mergeBones( QList a, QList b ) - { - foreach ( int c, b ) - { - if ( ! a.contains( c ) ) - { - a.append( c ); - } - } - return a; - } - - static bool containsBones( QList a, QList b ) - { - foreach ( int c, b ) - { - if ( ! a.contains( c ) ) - return false; - } - return true; - } -}; - -REGISTER_SPELL( spSkinPartition ) - - -class spAllSkinPartitions : public Spell -{ -public: - QString name() const { return Spell::tr( "Make All Skin Partitions" ); } - QString page() const { return Spell::tr( "Batch" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif && ! index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QList< QPersistentModelIndex > indices; - - spSkinPartition Partitioner; - - for ( int n = 0; n < nif->getBlockCount(); n++ ) - { - QModelIndex idx = nif->getBlock( n ); - if ( Partitioner.isApplicable( nif, idx ) ) - indices.append( idx ); - } - - int mbpp = 0, mbpv = 0; - bool make_strips = false; - - foreach ( QModelIndex idx, indices ) - { - Partitioner.cast( nif, idx, mbpp, mbpv, make_strips ); - } - - qWarning() << QString( Spell::tr( "did %1 partitions" ) ).arg( indices.count() ); - - return QModelIndex(); - } -}; - -REGISTER_SPELL( spAllSkinPartitions ) - - -SkinPartitionDialog::SkinPartitionDialog( int ) : QDialog() -{ - spnVert = new QSpinBox( this ); - spnVert->setMinimum( 1 ); - spnVert->setMaximum( 8 ); - spnVert->setValue( 4 ); - connect( spnVert, SIGNAL( valueChanged( int ) ), this, SLOT( changed() ) ); - - spnPart = new QSpinBox( this ); - spnPart->setMinimum( 4 ); - spnPart->setMaximum( 40 ); - spnPart->setValue( 18 ); - - ckTStrip = new QCheckBox( "&Stripify Triangles" ); - ckTStrip->setChecked( true ); - ckTStrip->setToolTip( "Determines whether the triangles in each partition will be arranged into strips or as a list of individual triangles. Different gaems work best with one or the other." ); - connect( ckTStrip, SIGNAL( clicked() ), this, SLOT( changed() ) ); - - ckPad = new QCheckBox( "&Pad Small Partitions" ); - ckPad->setChecked( false ); - ckPad->setToolTip( "Determines whether partitions that will have fewer than the selected maximum number of bones will have extra bones added to bring them up to that number." ); - connect( ckPad, SIGNAL( clicked() ), this, SLOT( changed() ) ); - - - QLabel * labVert = new QLabel( this ); - labVert->setText( Spell::tr( - "Number of Bones per Vertex
" - "Hint: Most games use 4 bones per vertex
" - "Note: If the mesh contains vertices which are
" - "influenced by more than x bones the number of
" - "influences will be reduced for these vertices
" - ) ); - - QLabel * labPart = new QLabel( this ); - labPart->setText( Spell::tr( - "Number of Bones per Partition
" - "Hint: Oblivion uses 18 bones pp
" - "CivIV (non shader meshes) 4 bones pp
" - "CivIV (shader enabled meshes) 18 bones pp
" - "Note: To fit the triangles into the partitions
" - "some bone influences may be removed again." - ) ); - - QLabel * labTStrip = new QLabel( this ); - labTStrip->setText( Spell::tr( - "Whether or not to stripify the triangles in each partition.
" - "Hint: Morrowind and Freedom force do not support strips.
" - "Strips generally perform faster, if the game supports them." - ) ); - - QLabel * labPad = new QLabel( this ); - labPad->setText( Spell::tr( - "Whether or not to pad partitions that will have fewer bones than specified above.
" - "Hint: Freedom Force seems to require this, but it doesn't seem to affect other games." - ) ); - - QPushButton * btOk = new QPushButton( this ); - btOk->setText( Spell::tr( "Ok" ) ); - connect( btOk, SIGNAL( clicked() ), this, SLOT( accept() ) ); - - QPushButton * btCancel = new QPushButton( this ); - btCancel->setText( Spell::tr( "Cancel" ) ); - connect( btCancel, SIGNAL( clicked() ), this, SLOT( reject() ) ); - - QGridLayout * grid = new QGridLayout( this ); - grid->addWidget( labVert, 0, 0 ); grid->addWidget( spnVert, 0, 1 ); - grid->addWidget( labPart, 1, 0 ); grid->addWidget( spnPart, 1, 1 ); - grid->addWidget( labTStrip, 2, 0); grid->addWidget( ckTStrip, 2, 1 ); - grid->addWidget( labPad, 3, 0); grid->addWidget( ckPad, 3, 1 ); - grid->addWidget( btOk, 4, 0 ); grid->addWidget( btCancel, 4, 1 ); -} - -void SkinPartitionDialog::changed() -{ - spnPart->setMinimum( spnVert->value() ); -} - -int SkinPartitionDialog::maxBonesPerVertex() -{ - return spnVert->value(); -} - -int SkinPartitionDialog::maxBonesPerPartition() -{ - return spnPart->value(); -} - -bool SkinPartitionDialog::makeStrips() -{ - return ckTStrip->isChecked(); -} - -bool SkinPartitionDialog::padPartitions() -{ - return ckPad->isChecked(); -} - -class spFixBoneBounds : public Spell -{ -public: - QString name() const { return Spell::tr( "Fix Bone Bounds" ); } - QString page() const { return Spell::tr( "Skeleton" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index, "NiSkinData" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & iSkinData ) - { - QModelIndex iSkinInstance = nif->getBlock( nif->getParent( nif->getBlockNumber( iSkinData ) ), "NiSkinInstance" ); - QModelIndex iMesh = nif->getBlock( nif->getParent( nif->getBlockNumber( iSkinInstance ) ) ); - QModelIndex iMeshData = nif->getBlock( nif->getLink( iMesh, "Data" ) ); - int skelRoot = nif->getLink( iSkinInstance, "Skeleton Root" ); - if ( ! nif->inherits( iMeshData, "NiTriBasedGeomData" ) || skelRoot < 0 || skelRoot != nif->getParent( nif->getBlockNumber( iMesh ) ) ) - return iSkinData; - - Transform meshTrans( nif, iMesh ); - - QVector boneTrans; - QModelIndex iBoneMap = nif->getIndex( iSkinInstance, "Bones" ); - for ( int n = 0; n < nif->rowCount( iBoneMap ); n++ ) - { - QModelIndex iBone = nif->getBlock( nif->getLink( iBoneMap.child( n, 0 ) ), "NiNode" ); - if ( skelRoot != nif->getParent( nif->getBlockNumber( iBone ) ) ) - return iSkinData; - boneTrans.append( Transform( nif, iBone ) ); - } - - QVector verts = nif->getArray( iMeshData, "Vertices" ); - - QModelIndex iBoneDataList = nif->getIndex( iSkinData, "Bone List" ); - for ( int b = 0; b < nif->rowCount( iBoneDataList ); b++ ) - { - Vector3 mn; - Vector3 mx; - - Vector3 center; - float radius = 0; - - QModelIndex iWeightList = nif->getIndex( iBoneDataList.child( b, 0 ), "Vertex Weights" ); - for ( int w = 0; w < nif->rowCount( iWeightList ); w++ ) - { - int v = nif->get( iWeightList.child( w, 0 ), "Index" ); - if ( w == 0 ) - { - mn = verts.value( v ); - mx = verts.value( v ); - } - else - { - mn.boundMin( verts.value( v ) ); - mx.boundMax( verts.value( v ) ); - } - } - - mn = meshTrans * mn; - mx = meshTrans * mx; - - Transform bt( boneTrans[b] ); - mn = bt.rotation.inverted() * ( mn - bt.translation ) / bt.scale; - mx = bt.rotation.inverted() * ( mx - bt.translation ) / bt.scale; - - center = ( mn + mx ) / 2; - radius = qMax( ( mn - center ).length(), ( mx - center ).length() ); - - nif->set( iBoneDataList.child( b, 0 ), "Bounding Sphere Offset", center ); - nif->set( iBoneDataList.child( b, 0 ), "Bounding Sphere Radius", radius ); - } - - return iSkinData; - } - -}; - -REGISTER_SPELL( spFixBoneBounds ) +#include "../spellbook.h" + +#include "skeleton.h" + +#include "../gl/gltools.h" + +#include "../NvTriStrip/qtwrapper.h" + +#include +#include +#include +#include +#include +#include +#include + +#define SKEL_DAT ":/res/spells/skel.dat" + +QDebug operator<<( QDebug dbg, const Vector3 & v ) +{ + return dbg << v[0] << v[1] << v[2]; +} + +typedef QMap TransMap; + +class spFixSkeleton : public Spell +{ +public: + QString name() const { return "Fix Bip01"; } + QString page() const { return "Skeleton"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ( nif->getVersion() == "4.0.0.2" && nif->itemType( index ) == "NiBlock" && nif->get( index, "Name" ) == "Bip01" ); //&& QFile::exists( SKEL_DAT ) ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QFile file( SKEL_DAT ); + if ( file.open( QIODevice::ReadOnly ) ) + { + QDataStream stream( &file ); + + TransMap local; + TransMap world; + QString name; + do + { + stream >> name; + if ( !name.isEmpty() ) + { + Transform t; + stream >> t; + local.insert( name, t ); + stream >> t; + world.insert( name, t ); + } + } + while ( ! name.isEmpty() ); + + TransMap bones; + doBones( nif, index, Transform(), local, bones ); + + foreach ( int link, nif->getChildLinks( nif->getBlockNumber( index ) ) ) + { + QModelIndex iChild = nif->getBlock( link ); + if ( iChild.isValid() ) + { + if ( nif->itemName( iChild ) == "NiNode" ) + { + doNodes( nif, iChild, Transform(), world, bones ); + } + else if ( nif->inherits( iChild, "NiTriBasedGeom" ) ) + { + doShape( nif, iChild, Transform(), world, bones ); + } + } + } + + nif->reset(); + } + + return index; + } + + void doBones( NifModel * nif, const QModelIndex & index, const Transform & tparent, const TransMap & local, TransMap & bones ) + { + QString name = nif->get( index, "Name" ); + if ( name.startsWith( "Bip01" ) ) + { + Transform tlocal( nif, index ); + bones.insert( name, tparent * tlocal ); + + local.value( name ).writeBack( nif, index ); + + foreach ( int link, nif->getChildLinks( nif->getBlockNumber( index ) ) ) + { + QModelIndex iChild = nif->getBlock( link, "NiNode" ); + if ( iChild.isValid() ) + doBones( nif, iChild, tparent * tlocal, local, bones ); + } + } + } + + bool doNodes( NifModel * nif, const QModelIndex & index, const Transform & tparent, const TransMap & world, const TransMap & bones ) + { + bool hasSkinnedChildren = false; + + QString name = nif->get( index, "Name" ); + if ( ! name.startsWith( "Bip01" ) ) + { + Transform tlocal( nif, index ); + + foreach ( int link, nif->getChildLinks( nif->getBlockNumber( index ) ) ) + { + QModelIndex iChild = nif->getBlock( link ); + if ( iChild.isValid() ) + { + if ( nif->itemName( iChild ) == "NiNode" ) + { + hasSkinnedChildren |= doNodes( nif, iChild, tparent * tlocal, world, bones ); + } + else if ( nif->inherits( iChild, "NiTriBasedGeom" ) ) + { + hasSkinnedChildren |= doShape( nif, iChild, tparent * tlocal, world, bones ); + } + } + } + } + + if ( hasSkinnedChildren ) + { + Transform().writeBack( nif, index ); + } + + return hasSkinnedChildren; + } + bool doShape( NifModel * nif, const QModelIndex & index, const Transform & tparent, const TransMap & world, const TransMap & bones ) + { + QModelIndex iShapeData = nif->getBlock( nif->getLink( index, "Data" ) ); + QModelIndex iSkinInstance = nif->getBlock( nif->getLink( index, "Skin Instance" ), "NiSkinInstance" ); + if ( ! iSkinInstance.isValid() || ! iShapeData.isValid() ) + return false; + QStringList names; + QModelIndex iNames = nif->getIndex( iSkinInstance, "Bones" ); + if ( iNames.isValid() ) + iNames = nif->getIndex( iNames, "Bones" ); + if ( iNames.isValid() ) + for ( int n = 0; n < nif->rowCount( iNames ); n++ ) + { + QModelIndex iBone = nif->getBlock( nif->getLink( iNames.child( n, 0 ) ), "NiNode" ); + if ( iBone.isValid() ) + names.append( nif->get( iBone, "Name" ) ); + else + names.append(""); + } + QModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInstance, "Data" ), "NiSkinData" ); + if ( !iSkinData.isValid() ) + return false; + QModelIndex iBones = nif->getIndex( iSkinData, "Bone List" ); + if ( ! iBones.isValid() ) + return false; + + Transform t( nif, iSkinData ); + t = tparent * t; + t.writeBack( nif, iSkinData ); + + for ( int b = 0; b < nif->rowCount( iBones ) && b < names.count(); b++ ) + { + QModelIndex iBone = iBones.child( b, 0 ); + + t = Transform( nif, iBone ); + + t.rotation = world.value( names[ b ] ).rotation.inverted() * bones.value( names[ b ] ).rotation * t.rotation; + t.translation = world.value( names[ b ] ).rotation.inverted() * bones.value( names[ b ] ).rotation * t.translation; + + t.writeBack( nif, iBone ); + } + + Vector3 center = nif->get( iShapeData, "Center" ); + nif->set( iShapeData, "Center", tparent * center ); + return true; + } +}; + +REGISTER_SPELL( spFixSkeleton ) + + +class spScanSkeleton : public Spell +{ +public: + QString name() const { return "Scan Bip01"; } + QString page() const { return "Skeleton"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ( nif->getVersion() == "4.0.0.2" && nif->itemType( index ) == "NiBlock" && nif->get( index, "Name" ) == "Bip01" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QFile file( SKEL_DAT ); + if ( file.open( QIODevice::WriteOnly ) ) + { + QDataStream stream( &file ); + scan( nif, index, Transform(), stream ); + stream << QString(); + } + return index; + } + + void scan( NifModel * nif, const QModelIndex & index, const Transform & tparent, QDataStream & stream ) + { + QString name = nif->get( index, "Name" ); + if ( name.startsWith( "Bip01" ) ) + { + Transform local( nif, index ); + stream << name << local << tparent * local; + qWarning() << name; + foreach ( int link, nif->getChildLinks( nif->getBlockNumber( index ) ) ) + { + QModelIndex iChild = nif->getBlock( link, "NiNode" ); + if ( iChild.isValid() ) + scan( nif, iChild, tparent * local, stream ); + } + } + } +}; + +//REGISTER_SPELL( spScanSkeleton ) + + +class spSkinPartition : public Spell +{ +public: + QString name() const { return Spell::tr("Make Skin Partition"); } + QString page() const { return Spell::tr("Mesh"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & iShape ) + { + if ( nif->isNiBlock( iShape, "NiTriShape" ) ) + { + QModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); + if ( iSkinInst.isValid() ) + { + return nif->getBlock( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ).isValid(); + } + } + return false; + } + + typedef QPair boneweight; + + typedef struct { + QList bones; + QVector triangles; + } Partition; + + QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) + { + int mbpp = 0, mbpv = 0; + bool make_strips = false; + return cast( nif, iBlock, mbpp, mbpv, make_strips ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & iBlock, int & maxBonesPerPartition, int & maxBonesPerVertex, bool make_strips, bool pad = false ) + { + QPersistentModelIndex iShape = iBlock; + try + { + QPersistentModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ), "NiTriShapeData" ); + QPersistentModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); + QPersistentModelIndex iSkinData = nif->getBlock( nif->getLink( iSkinInst, "Data" ), "NiSkinData" ); + QModelIndex iSkinPart = nif->getBlock( nif->getLink( iSkinInst, "Skin Partition" ), "NiSkinPartition" ); + if ( ! iSkinPart.isValid() ) + iSkinPart = nif->getBlock( nif->getLink( iSkinData, "Skin Partition" ), "NiSkinPartition" ); + + // read in the weights from NiSkinData + + QVector< QList< boneweight > > weights( nif->get( iData, "Num Vertices" ) ); + + QModelIndex iBoneList = nif->getIndex( iSkinData, "Bone List" ); + int numBones = nif->rowCount( iBoneList ); + for ( int bone = 0; bone < numBones; bone++ ) + { + QModelIndex iVertexWeights = nif->getIndex( iBoneList.child( bone, 0 ), "Vertex Weights" ); + for ( int r = 0; r < nif->rowCount( iVertexWeights ); r++ ) + { + int vertex = nif->get( iVertexWeights.child( r, 0 ), "Index" ); + float weight = nif->get( iVertexWeights.child( r, 0 ), "Weight" ); + if ( vertex >= weights.count() ) + throw QString( Spell::tr("bad NiSkinData - vertex count does not match") ); + weights[vertex].append( boneweight( bone, weight ) ); + } + } + + // count min and max bones per vertex + + int minBones, maxBones; + minBones = maxBones = weights.value( 0 ).count(); + foreach ( QList< boneweight > list, weights ) + { + if ( list.count() < minBones ) + minBones = list.count(); + if ( list.count() > maxBones ) + maxBones = list.count(); + } + + if ( minBones <= 0 ) + throw QString( Spell::tr("bad NiSkinData - some vertices have no weights at all") ); + + // query max bones per vertex/partition + + if ( maxBonesPerPartition <= 0 || maxBonesPerVertex <= 0 ) + { + SkinPartitionDialog dlg( maxBones ); + + if ( dlg.exec() != QDialog::Accepted ) + return iBlock; + + maxBonesPerPartition = dlg.maxBonesPerPartition(); + maxBonesPerVertex = dlg.maxBonesPerVertex(); + make_strips = dlg.makeStrips(); + pad = dlg.padPartitions(); + } + + // reduce vertex influences if necessary + + if ( maxBones > maxBonesPerVertex ) + { + QMutableVectorIterator< QList< boneweight > > it( weights ); + int c = 0; + while ( it.hasNext() ) + { + QList< boneweight > & lst = it.next(); + + if ( lst.count() > maxBonesPerVertex ) + c++; + + while ( lst.count() > maxBonesPerVertex ) + { + int j = 0; + float weight = lst.first().second; + for ( int i = 0; i < lst.count(); i++ ) + { + if ( lst[i].second < weight ) + j = i; + } + lst.removeAt( j ); + } + + float totalWeight = 0; + foreach ( boneweight bw, lst ) + { + totalWeight += bw.second; + } + + for ( int b = 0; b < lst.count(); b++ ) + { // normalize + lst[b].second /= totalWeight; + } + } + qWarning() << QString( Spell::tr( "reduced %1 vertices to %2 bone influences (maximum number of bones per vertex was %3)" ) ).arg( c ).arg( maxBonesPerVertex ).arg( maxBones ); + } + + maxBones = maxBonesPerVertex; + + // reduces bone weights so that the triangles fit into the partitions + + QList triangles = nif->getArray( iData, "Triangles" ).toList(); + + QMap< int, int > match; + bool doMatch = true; + + QList tribones; + + int cnt = 0; + + foreach ( Triangle tri, triangles ) + { + do + { + tribones.clear(); + for ( int c = 0; c < 3; c++ ) + { + foreach ( boneweight bw, weights[tri[c]] ) + { + if ( ! tribones.contains( bw.first ) ) + tribones.append( bw.first ); + } + } + + if ( tribones.count() > maxBonesPerPartition ) + { + // sum up the weights for each bone + // bones with weight == 1 can't be removed + + QMap sum; + QList nono; + + for ( int t = 0; t < 3; t++ ) + { + if ( weights[tri[t]].count() == 1 ) + nono.append( weights[tri[t]].first().first ); + + foreach ( boneweight bw, weights[ tri[t] ] ) + sum[ bw.first ] += bw.second; + } + + // select the bone to remove + + float minWeight = 5.0; + int minBone = -1; + + foreach ( int b, sum.keys() ) + { + if ( ! nono.contains( b ) && sum[b] < minWeight ) + { + minWeight = sum[b]; + minBone = b; + } + } + + if ( minBone < 0 ) // this shouldn't never happen + throw QString( "internal error 0x01" ); + + // do a vertex match detect + + if ( doMatch ) + { + QVector verts = nif->getArray( iData, "Vertices" ); + for ( int a = 0; a < verts.count(); a++ ) + { + match.insertMulti( a, a ); + for ( int b = a + 1; b < verts.count(); b++ ) + { + if ( verts[a] == verts[b] && weights[a] == weights[b] ) + { + match.insertMulti( a, b ); + match.insertMulti( b, a ); + } + } + } + } + + // now remove that bone from all vertices of this triangle and from all matching vertices too + + for ( int t = 0; t < 3; t++ ) + { + bool rem = false; + foreach ( int v, match.values( tri[t] ) ) + { + QList< boneweight > & bws = weights[ v ]; + QMutableListIterator< boneweight > it( bws ); + while ( it.hasNext() ) + { + boneweight & bw = it.next(); + if ( bw.first == minBone ) + { + it.remove(); + rem = true; + } + } + + float totalWeight = 0; + foreach ( boneweight bw, bws ) + { + totalWeight += bw.second; + } + + if ( totalWeight == 0 ) + throw QString( "internal error 0x02" ); + + for ( int b = 0; b < bws.count(); b++ ) + { // normalize + bws[b].second /= totalWeight; + } + } + if ( rem ) + cnt++; + } + } + } while ( tribones.count() > maxBonesPerPartition ); + } + + if ( cnt > 0 ) + qWarning() << QString( Spell::tr( "removed %1 bone influences" ) ).arg( cnt ); + + // split the triangles into partitions + + QList parts; + + while ( ! triangles.isEmpty() ) + { + Partition part; + + QHash usedVerts; + + bool addtriangles; + do + { + QMutableListIterator it( triangles ); + while ( it.hasNext() ) + { + Triangle & tri = it.next(); + + QList tribones; + for ( int c = 0; c < 3; c++ ) + { + foreach ( boneweight bw, weights[tri[c]] ) + { + if ( ! tribones.contains( bw.first ) ) + tribones.append( bw.first ); + } + } + + if ( part.bones.isEmpty() || containsBones( part.bones, tribones ) ) + { + part.bones = mergeBones( part.bones, tribones ); + part.triangles.append( tri ); + usedVerts[ tri[0] ] = true; + usedVerts[ tri[1] ] = true; + usedVerts[ tri[2] ] = true; + it.remove(); + } + } + + addtriangles = false; + + if ( part.bones.count() < maxBonesPerPartition ) + { // if we have room left in the partition then add an adjacent triangle + it.toFront(); + while ( it.hasNext() ) + { + Triangle & tri = it.next(); + + if ( usedVerts.contains( tri[0] ) || usedVerts.contains( tri[1] ) || usedVerts.contains( tri[2] ) ) + { + QList tribones; + for ( int c = 0; c < 3; c++ ) + { + foreach ( boneweight bw, weights[tri[c]] ) + { + if ( ! tribones.contains( bw.first ) ) + tribones.append( bw.first ); + } + } + + tribones = mergeBones( part.bones, tribones ); + if ( tribones.count() <= maxBonesPerPartition ) + { + part.bones = tribones; + part.triangles.append( tri ); + usedVerts[ tri[0] ] = true; + usedVerts[ tri[1] ] = true; + usedVerts[ tri[2] ] = true; + it.remove(); + addtriangles = true; + //break; + } + } + } + } + } + while ( addtriangles ); + + parts.append( part ); + } + + //qWarning() << parts.count() << "small partitions"; + + // merge partitions + + bool merged; + do + { + merged = false; + for ( int p1 = 0; p1 < parts.count() && ! merged; p1++ ) + { + if ( parts[p1].bones.count() < maxBonesPerPartition ) + { + for ( int p2 = p1+1; p2 < parts.count() && ! merged; p2++ ) + { + QList mergedBones = mergeBones( parts[p1].bones, parts[p2].bones ); + if ( mergedBones.count() <= maxBonesPerPartition ) + { + parts[p1].bones = mergedBones; + parts[p1].triangles << parts[p2].triangles; + parts.removeAt( p2 ); + merged = true; + } + } + } + } + } + while ( merged ); + + //qWarning() << parts.count() << "partitions"; + + // create the NiSkinPartition if it doesn't exist yet + + if ( ! iSkinPart.isValid() ) + { + iSkinPart = nif->insertNiBlock( "NiSkinPartition", nif->getBlockNumber( iSkinData ) + 1 ); + nif->setLink( iSkinInst, "Skin Partition", nif->getBlockNumber( iSkinPart ) ); + nif->setLink( iSkinData, "Skin Partition", nif->getBlockNumber( iSkinPart ) ); + } + + // start writing NiSkinPartition + + nif->set( iSkinPart, "Num Skin Partition Blocks", parts.count() ); + nif->updateArray( iSkinPart, "Skin Partition Blocks" ); + + for ( int p = 0; p < parts.count(); p++ ) + { + QModelIndex iPart = nif->getIndex( iSkinPart, "Skin Partition Blocks" ).child( p, 0 ); + + QList bones = parts[p].bones; + qSort( bones ); + + QVector triangles = parts[p].triangles; + + QVector vertices; + foreach ( Triangle tri, triangles ) + { + for ( int t = 0; t < 3; t++ ) + { + if ( ! vertices.contains( tri[t] ) ) + vertices.append( tri[t] ); + } + } + qSort( vertices ); + + // map the vertices + + for ( int tri = 0; tri < triangles.count(); tri++ ) + { + for ( int t = 0; t < 3; t++ ) + { + triangles[tri][t] = vertices.indexOf( triangles[tri][t] ); + } + } + + // stripify the triangles + QList< QVector > strips; + int numTriangles = 0; + if ( make_strips == true ) + { + strips = stripify( triangles ); + + foreach ( QVector strip, strips ) + { + numTriangles += strip.count() - 2; + } + } + else + { + numTriangles = triangles.count(); + } + + // fill in counts + if ( pad ) + { + while ( bones.size() < maxBonesPerPartition ) { + bones.append(0); + } + } + + nif->set( iPart, "Num Vertices", vertices.count() ); + nif->set( iPart, "Num Triangles", numTriangles ); + nif->set( iPart, "Num Bones", bones.count() ); + nif->set( iPart, "Num Strips", strips.count() ); + nif->set( iPart, "Num Weights Per Vertex", maxBones ); + + // fill in bone map + + QModelIndex iBoneMap = nif->getIndex( iPart, "Bones" ); + nif->updateArray( iBoneMap ); + nif->setArray( iBoneMap, bones.toVector() ); + + // fill in vertex map + + nif->set( iPart, "Has Vertex Map", 1 ); + QModelIndex iVertexMap = nif->getIndex( iPart, "Vertex Map" ); + nif->updateArray( iVertexMap ); + nif->setArray( iVertexMap, vertices ); + + // fill in vertex weights + + nif->set( iPart, "Has Vertex Weights", 1 ); + QModelIndex iVWeights = nif->getIndex( iPart, "Vertex Weights" ); + nif->updateArray( iVWeights ); + for ( int v = 0; v < nif->rowCount( iVWeights ); v++ ) + { + QModelIndex iVertex = iVWeights.child( v, 0 ); + nif->updateArray( iVertex ); + QList< boneweight > list = weights.value( vertices[v] ); + for ( int b = 0; b < maxBones; b++ ) + nif->set( iVertex.child( b, 0 ), list.count() > b ? list[ b ].second : 0.0 ); + } + + nif->set( iPart, "Has Faces", 1 ); + + if ( make_strips == true ) + { + //Clear out any existing triangle data that might be left over from an existing Skin Partition + QModelIndex iTriangles = nif->getIndex( iPart, "Triangles" ); + nif->updateArray( iTriangles ); + + // write the strips + QModelIndex iStripLengths = nif->getIndex( iPart, "Strip Lengths" ); + nif->updateArray( iStripLengths ); + for ( int s = 0; s < nif->rowCount( iStripLengths ); s++ ) + nif->set( iStripLengths.child( s, 0 ), strips.value( s ).count() ); + + QModelIndex iStrips = nif->getIndex( iPart, "Strips" ); + nif->updateArray( iStrips ); + for ( int s = 0; s < nif->rowCount( iStrips ); s++ ) + { + nif->updateArray( iStrips.child( s, 0 ) ); + nif->setArray( iStrips.child( s, 0 ), strips.value( s ) ); + } + } + else + { + //Clear out any existing strip data that might be left over from an existing Skin Partition + QModelIndex iStripLengths = nif->getIndex( iPart, "Strip Lengths" ); + nif->updateArray( iStripLengths ); + QModelIndex iStrips = nif->getIndex( iPart, "Strips" ); + nif->updateArray( iStrips ); + + QModelIndex iTriangles = nif->getIndex( iPart, "Triangles" ); + nif->updateArray( iTriangles ); + nif->setArray( iTriangles, triangles ); + } + + // fill in vertex bones + + nif->set( iPart, "Has Bone Indices", 1 ); + QModelIndex iVBones = nif->getIndex( iPart, "Bone Indices" ); + nif->updateArray( iVBones ); + for ( int v = 0; v < nif->rowCount( iVBones ); v++ ) + { + QModelIndex iVertex = iVBones.child( v, 0 ); + nif->updateArray( iVertex ); + QList< boneweight > list = weights.value( vertices[v] ); + for ( int b = 0; b < maxBones; b++ ) + nif->set( iVertex.child( b, 0 ), list.count() > b ? bones.indexOf( list[ b ].first ) : 0 ); + } + } + + // done + + return iShape; + } + catch ( QString err ) + { + if ( ! err.isEmpty() ) + QMessageBox::warning( 0, "NifSkope", err ); + return iShape; + } + } + + static QList mergeBones( QList a, QList b ) + { + foreach ( int c, b ) + { + if ( ! a.contains( c ) ) + { + a.append( c ); + } + } + return a; + } + + static bool containsBones( QList a, QList b ) + { + foreach ( int c, b ) + { + if ( ! a.contains( c ) ) + return false; + } + return true; + } +}; + +REGISTER_SPELL( spSkinPartition ) + + +class spAllSkinPartitions : public Spell +{ +public: + QString name() const { return Spell::tr( "Make All Skin Partitions" ); } + QString page() const { return Spell::tr( "Batch" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif && ! index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QList< QPersistentModelIndex > indices; + + spSkinPartition Partitioner; + + for ( int n = 0; n < nif->getBlockCount(); n++ ) + { + QModelIndex idx = nif->getBlock( n ); + if ( Partitioner.isApplicable( nif, idx ) ) + indices.append( idx ); + } + + int mbpp = 0, mbpv = 0; + bool make_strips = false; + + foreach ( QModelIndex idx, indices ) + { + Partitioner.cast( nif, idx, mbpp, mbpv, make_strips ); + } + + qWarning() << QString( Spell::tr( "did %1 partitions" ) ).arg( indices.count() ); + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spAllSkinPartitions ) + + +SkinPartitionDialog::SkinPartitionDialog( int ) : QDialog() +{ + spnVert = new QSpinBox( this ); + spnVert->setMinimum( 1 ); + spnVert->setMaximum( 8 ); + spnVert->setValue( 4 ); + connect( spnVert, SIGNAL( valueChanged( int ) ), this, SLOT( changed() ) ); + + spnPart = new QSpinBox( this ); + spnPart->setMinimum( 4 ); + spnPart->setMaximum( 40 ); + spnPart->setValue( 18 ); + + ckTStrip = new QCheckBox( "&Stripify Triangles" ); + ckTStrip->setChecked( true ); + ckTStrip->setToolTip( "Determines whether the triangles in each partition will be arranged into strips or as a list of individual triangles. Different gaems work best with one or the other." ); + connect( ckTStrip, SIGNAL( clicked() ), this, SLOT( changed() ) ); + + ckPad = new QCheckBox( "&Pad Small Partitions" ); + ckPad->setChecked( false ); + ckPad->setToolTip( "Determines whether partitions that will have fewer than the selected maximum number of bones will have extra bones added to bring them up to that number." ); + connect( ckPad, SIGNAL( clicked() ), this, SLOT( changed() ) ); + + + QLabel * labVert = new QLabel( this ); + labVert->setText( Spell::tr( + "Number of Bones per Vertex
" + "Hint: Most games use 4 bones per vertex
" + "Note: If the mesh contains vertices which are
" + "influenced by more than x bones the number of
" + "influences will be reduced for these vertices
" + ) ); + + QLabel * labPart = new QLabel( this ); + labPart->setText( Spell::tr( + "Number of Bones per Partition
" + "Hint: Oblivion uses 18 bones pp
" + "CivIV (non shader meshes) 4 bones pp
" + "CivIV (shader enabled meshes) 18 bones pp
" + "Note: To fit the triangles into the partitions
" + "some bone influences may be removed again." + ) ); + + QLabel * labTStrip = new QLabel( this ); + labTStrip->setText( Spell::tr( + "Whether or not to stripify the triangles in each partition.
" + "Hint: Morrowind and Freedom force do not support strips.
" + "Strips generally perform faster, if the game supports them." + ) ); + + QLabel * labPad = new QLabel( this ); + labPad->setText( Spell::tr( + "Whether or not to pad partitions that will have fewer bones than specified above.
" + "Hint: Freedom Force seems to require this, but it doesn't seem to affect other games." + ) ); + + QPushButton * btOk = new QPushButton( this ); + btOk->setText( Spell::tr( "Ok" ) ); + connect( btOk, SIGNAL( clicked() ), this, SLOT( accept() ) ); + + QPushButton * btCancel = new QPushButton( this ); + btCancel->setText( Spell::tr( "Cancel" ) ); + connect( btCancel, SIGNAL( clicked() ), this, SLOT( reject() ) ); + + QGridLayout * grid = new QGridLayout( this ); + grid->addWidget( labVert, 0, 0 ); grid->addWidget( spnVert, 0, 1 ); + grid->addWidget( labPart, 1, 0 ); grid->addWidget( spnPart, 1, 1 ); + grid->addWidget( labTStrip, 2, 0); grid->addWidget( ckTStrip, 2, 1 ); + grid->addWidget( labPad, 3, 0); grid->addWidget( ckPad, 3, 1 ); + grid->addWidget( btOk, 4, 0 ); grid->addWidget( btCancel, 4, 1 ); +} + +void SkinPartitionDialog::changed() +{ + spnPart->setMinimum( spnVert->value() ); +} + +int SkinPartitionDialog::maxBonesPerVertex() +{ + return spnVert->value(); +} + +int SkinPartitionDialog::maxBonesPerPartition() +{ + return spnPart->value(); +} + +bool SkinPartitionDialog::makeStrips() +{ + return ckTStrip->isChecked(); +} + +bool SkinPartitionDialog::padPartitions() +{ + return ckPad->isChecked(); +} + +class spFixBoneBounds : public Spell +{ +public: + QString name() const { return Spell::tr( "Fix Bone Bounds" ); } + QString page() const { return Spell::tr( "Skeleton" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index, "NiSkinData" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & iSkinData ) + { + QModelIndex iSkinInstance = nif->getBlock( nif->getParent( nif->getBlockNumber( iSkinData ) ), "NiSkinInstance" ); + QModelIndex iMesh = nif->getBlock( nif->getParent( nif->getBlockNumber( iSkinInstance ) ) ); + QModelIndex iMeshData = nif->getBlock( nif->getLink( iMesh, "Data" ) ); + int skelRoot = nif->getLink( iSkinInstance, "Skeleton Root" ); + if ( ! nif->inherits( iMeshData, "NiTriBasedGeomData" ) || skelRoot < 0 || skelRoot != nif->getParent( nif->getBlockNumber( iMesh ) ) ) + return iSkinData; + + Transform meshTrans( nif, iMesh ); + + QVector boneTrans; + QModelIndex iBoneMap = nif->getIndex( iSkinInstance, "Bones" ); + for ( int n = 0; n < nif->rowCount( iBoneMap ); n++ ) + { + QModelIndex iBone = nif->getBlock( nif->getLink( iBoneMap.child( n, 0 ) ), "NiNode" ); + if ( skelRoot != nif->getParent( nif->getBlockNumber( iBone ) ) ) + return iSkinData; + boneTrans.append( Transform( nif, iBone ) ); + } + + QVector verts = nif->getArray( iMeshData, "Vertices" ); + + QModelIndex iBoneDataList = nif->getIndex( iSkinData, "Bone List" ); + for ( int b = 0; b < nif->rowCount( iBoneDataList ); b++ ) + { + Vector3 mn; + Vector3 mx; + + Vector3 center; + float radius = 0; + + QModelIndex iWeightList = nif->getIndex( iBoneDataList.child( b, 0 ), "Vertex Weights" ); + for ( int w = 0; w < nif->rowCount( iWeightList ); w++ ) + { + int v = nif->get( iWeightList.child( w, 0 ), "Index" ); + if ( w == 0 ) + { + mn = verts.value( v ); + mx = verts.value( v ); + } + else + { + mn.boundMin( verts.value( v ) ); + mx.boundMax( verts.value( v ) ); + } + } + + mn = meshTrans * mn; + mx = meshTrans * mx; + + Transform bt( boneTrans[b] ); + mn = bt.rotation.inverted() * ( mn - bt.translation ) / bt.scale; + mx = bt.rotation.inverted() * ( mx - bt.translation ) / bt.scale; + + center = ( mn + mx ) / 2; + radius = qMax( ( mn - center ).length(), ( mx - center ).length() ); + + nif->set( iBoneDataList.child( b, 0 ), "Bounding Sphere Offset", center ); + nif->set( iBoneDataList.child( b, 0 ), "Bounding Sphere Radius", radius ); + } + + return iSkinData; + } + +}; + +REGISTER_SPELL( spFixBoneBounds ) diff --git a/spells/skeleton.h b/spells/skeleton.h index 08878da75..504546ca9 100644 --- a/spells/skeleton.h +++ b/spells/skeleton.h @@ -1,32 +1,32 @@ -#ifndef SPELL_SKELETON_H -#define SPELL_SKELETON_H - -#include -#include - -class QSpinBox; - -class SkinPartitionDialog : public QDialog -{ - Q_OBJECT -public: - SkinPartitionDialog( int maxInfluences ); - - int maxBonesPerPartition(); - int maxBonesPerVertex(); - bool makeStrips(); - bool padPartitions(); - -protected slots: - void changed(); - -protected: - QSpinBox * spnPart; - QSpinBox * spnVert; - QCheckBox * ckTStrip; - QCheckBox * ckPad; - - int maxInfluences; -}; - -#endif +#ifndef SPELL_SKELETON_H +#define SPELL_SKELETON_H + +#include +#include + +class QSpinBox; + +class SkinPartitionDialog : public QDialog +{ + Q_OBJECT +public: + SkinPartitionDialog( int maxInfluences ); + + int maxBonesPerPartition(); + int maxBonesPerVertex(); + bool makeStrips(); + bool padPartitions(); + +protected slots: + void changed(); + +protected: + QSpinBox * spnPart; + QSpinBox * spnVert; + QCheckBox * ckTStrip; + QCheckBox * ckPad; + + int maxInfluences; +}; + +#endif diff --git a/spells/stringpalette.cpp b/spells/stringpalette.cpp index 06f2050b0..4332081eb 100644 --- a/spells/stringpalette.cpp +++ b/spells/stringpalette.cpp @@ -1,185 +1,185 @@ -#include "../spellbook.h" - -#include -#include -#include -#include -#include -#include -#include - -/* XPM */ -static char * txt_xpm[] = { -"32 32 36 1", -" c None", -". c #FFFFFF","+ c #000000","@ c #BDBDBD","# c #717171","$ c #252525", -"% c #4F4F4F","& c #A9A9A9","* c #A8A8A8","= c #555555","- c #EAEAEA", -"; c #151515","> c #131313",", c #D0D0D0","' c #AAAAAA",") c #080808", -"! c #ABABAB","~ c #565656","{ c #D1D1D1","] c #4D4D4D","^ c #4E4E4E", -"/ c #FDFDFD","( c #A4A4A4","_ c #0A0A0A",": c #A5A5A5","< c #050505", -"[ c #C4C4C4","} c #E9E9E9","| c #D5D5D5","1 c #141414","2 c #3E3E3E", -"3 c #DDDDDD","4 c #424242","5 c #070707","6 c #040404","7 c #202020", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ........... .... ", -" .+++++++++. .@#$. ", -" .+++++++++. .+++. ", -" ....+++..............+++... ", -" .+++. %++&.*++=++++++. ", -" .+++. .-;+>,>+;-++++++. ", -" .+++. .'++)++!..+++... ", -" .+++. .=+++~. .+++. ", -" .+++. .{+++{. .+++. ", -" .+++. .]+++^. .+++/ ", -" .+++. .(++_++:..<++[.. ", -" .+++. .}>+;|;+1}.2++++. ", -" .+++. ^++'.'++%.34567. ", -" ..... ................. ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" "}; - -QIcon * txt_xpm_icon = 0; - -class spEditStringOffset : public Spell -{ -public: - QString name() const { return "Edit String Offset"; } - QString page() const { return ""; } - QIcon icon() const - { - if ( ! txt_xpm_icon ) - txt_xpm_icon = new QIcon( txt_xpm ); - return *txt_xpm_icon; - } - bool instant() const { return true; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->getValue( index ).type() == NifValue::tStringOffset && getStringPalette( nif ).isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iPalette = getStringPalette( nif ); - - QMap strings = readStringPalette( nif, iPalette ); - QString string = getString( strings, nif->get( index ) ); - - QDialog dlg; - - QLabel * lb = new QLabel( & dlg ); - lb->setText( "Select a string or enter a new one" ); - - QListWidget * lw = new QListWidget( & dlg ); - lw->addItems( strings.keys() ); - - QLineEdit * le = new QLineEdit( & dlg ); - le->setText( string ); - le->setFocus(); - - QObject::connect( lw, SIGNAL( currentTextChanged( const QString & ) ), le, SLOT( setText( const QString & ) ) ); - QObject::connect( lw, SIGNAL( itemActivated( QListWidgetItem * ) ), & dlg, SLOT( accept() ) ); - QObject::connect( le, SIGNAL( returnPressed() ), & dlg, SLOT( accept() ) ); - - QPushButton * bo = new QPushButton( "Ok", & dlg ); - QObject::connect( bo, SIGNAL( clicked() ), & dlg, SLOT( accept() ) ); - - QPushButton * bc = new QPushButton( "Cancel", & dlg ); - QObject::connect( bc, SIGNAL( clicked() ), & dlg, SLOT( reject() ) ); - - QGridLayout * grid = new QGridLayout; - dlg.setLayout( grid ); - grid->addWidget( lb, 0, 0, 1, 2 ); - grid->addWidget( lw, 1, 0, 1, 2 ); - grid->addWidget( le, 2, 0, 1, 2 ); - grid->addWidget( bo, 3, 0, 1, 1 ); - grid->addWidget( bc, 3, 1, 1, 1 ); - - if ( dlg.exec() != QDialog::Accepted ) - return index; - - nif->set( index, addString( nif, iPalette, le->text() ) ); - - return index; - } - - static QModelIndex getStringPalette( const NifModel * nif ) - { - QModelIndex iPalette; - for ( int n = 0; n < nif->getBlockCount(); n++ ) - { - QModelIndex idx = nif->getBlock( n, "NiStringPalette" ); - if ( idx.isValid() ) - { - if ( ! iPalette.isValid() ) - iPalette = idx; - else - return QModelIndex(); - } - } - return iPalette; - } - - static QMap readStringPalette( const NifModel * nif, const QModelIndex & iPalette ) - { - QByteArray bytes = nif->get( iPalette, "Palette" ); - QMap strings; - int x = 0; - while ( x < bytes.count() ) - { - QString s( & bytes.data()[x] ); - strings.insert( s, x ); - x += s.length() + 1; - } - return strings; - } - - static QString getString( const QMap strings, int ofs ) - { - if ( ofs >= 0 ) - { - QMapIterator it( strings ); - while ( it.hasNext() ) - { - it.next(); - if ( ofs == it.value() ) - return it.key(); - } - } - return QString(); - } - - static int addString( NifModel * nif, const QModelIndex & iPalette, const QString & string ) - { - if ( string.isEmpty() ) - return 0xffffffff; - - QMap strings = readStringPalette( nif, iPalette ); - if ( strings.contains( string ) ) - return strings[ string ]; - - QByteArray bytes = nif->get( iPalette, "Palette" ); - int ofs = bytes.count(); - bytes += string.toAscii(); - bytes.append( '\0' ); - nif->set( iPalette, "Palette", bytes ); - return ofs; - } -}; - -REGISTER_SPELL( spEditStringOffset ) - +#include "../spellbook.h" + +#include +#include +#include +#include +#include +#include +#include + +/* XPM */ +static char * txt_xpm[] = { +"32 32 36 1", +" c None", +". c #FFFFFF","+ c #000000","@ c #BDBDBD","# c #717171","$ c #252525", +"% c #4F4F4F","& c #A9A9A9","* c #A8A8A8","= c #555555","- c #EAEAEA", +"; c #151515","> c #131313",", c #D0D0D0","' c #AAAAAA",") c #080808", +"! c #ABABAB","~ c #565656","{ c #D1D1D1","] c #4D4D4D","^ c #4E4E4E", +"/ c #FDFDFD","( c #A4A4A4","_ c #0A0A0A",": c #A5A5A5","< c #050505", +"[ c #C4C4C4","} c #E9E9E9","| c #D5D5D5","1 c #141414","2 c #3E3E3E", +"3 c #DDDDDD","4 c #424242","5 c #070707","6 c #040404","7 c #202020", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ........... .... ", +" .+++++++++. .@#$. ", +" .+++++++++. .+++. ", +" ....+++..............+++... ", +" .+++. %++&.*++=++++++. ", +" .+++. .-;+>,>+;-++++++. ", +" .+++. .'++)++!..+++... ", +" .+++. .=+++~. .+++. ", +" .+++. .{+++{. .+++. ", +" .+++. .]+++^. .+++/ ", +" .+++. .(++_++:..<++[.. ", +" .+++. .}>+;|;+1}.2++++. ", +" .+++. ^++'.'++%.34567. ", +" ..... ................. ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; + +QIcon * txt_xpm_icon = 0; + +class spEditStringOffset : public Spell +{ +public: + QString name() const { return "Edit String Offset"; } + QString page() const { return ""; } + QIcon icon() const + { + if ( ! txt_xpm_icon ) + txt_xpm_icon = new QIcon( txt_xpm ); + return *txt_xpm_icon; + } + bool instant() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->getValue( index ).type() == NifValue::tStringOffset && getStringPalette( nif ).isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iPalette = getStringPalette( nif ); + + QMap strings = readStringPalette( nif, iPalette ); + QString string = getString( strings, nif->get( index ) ); + + QDialog dlg; + + QLabel * lb = new QLabel( & dlg ); + lb->setText( "Select a string or enter a new one" ); + + QListWidget * lw = new QListWidget( & dlg ); + lw->addItems( strings.keys() ); + + QLineEdit * le = new QLineEdit( & dlg ); + le->setText( string ); + le->setFocus(); + + QObject::connect( lw, SIGNAL( currentTextChanged( const QString & ) ), le, SLOT( setText( const QString & ) ) ); + QObject::connect( lw, SIGNAL( itemActivated( QListWidgetItem * ) ), & dlg, SLOT( accept() ) ); + QObject::connect( le, SIGNAL( returnPressed() ), & dlg, SLOT( accept() ) ); + + QPushButton * bo = new QPushButton( "Ok", & dlg ); + QObject::connect( bo, SIGNAL( clicked() ), & dlg, SLOT( accept() ) ); + + QPushButton * bc = new QPushButton( "Cancel", & dlg ); + QObject::connect( bc, SIGNAL( clicked() ), & dlg, SLOT( reject() ) ); + + QGridLayout * grid = new QGridLayout; + dlg.setLayout( grid ); + grid->addWidget( lb, 0, 0, 1, 2 ); + grid->addWidget( lw, 1, 0, 1, 2 ); + grid->addWidget( le, 2, 0, 1, 2 ); + grid->addWidget( bo, 3, 0, 1, 1 ); + grid->addWidget( bc, 3, 1, 1, 1 ); + + if ( dlg.exec() != QDialog::Accepted ) + return index; + + nif->set( index, addString( nif, iPalette, le->text() ) ); + + return index; + } + + static QModelIndex getStringPalette( const NifModel * nif ) + { + QModelIndex iPalette; + for ( int n = 0; n < nif->getBlockCount(); n++ ) + { + QModelIndex idx = nif->getBlock( n, "NiStringPalette" ); + if ( idx.isValid() ) + { + if ( ! iPalette.isValid() ) + iPalette = idx; + else + return QModelIndex(); + } + } + return iPalette; + } + + static QMap readStringPalette( const NifModel * nif, const QModelIndex & iPalette ) + { + QByteArray bytes = nif->get( iPalette, "Palette" ); + QMap strings; + int x = 0; + while ( x < bytes.count() ) + { + QString s( & bytes.data()[x] ); + strings.insert( s, x ); + x += s.length() + 1; + } + return strings; + } + + static QString getString( const QMap strings, int ofs ) + { + if ( ofs >= 0 ) + { + QMapIterator it( strings ); + while ( it.hasNext() ) + { + it.next(); + if ( ofs == it.value() ) + return it.key(); + } + } + return QString(); + } + + static int addString( NifModel * nif, const QModelIndex & iPalette, const QString & string ) + { + if ( string.isEmpty() ) + return 0xffffffff; + + QMap strings = readStringPalette( nif, iPalette ); + if ( strings.contains( string ) ) + return strings[ string ]; + + QByteArray bytes = nif->get( iPalette, "Palette" ); + int ofs = bytes.count(); + bytes += string.toAscii(); + bytes.append( '\0' ); + nif->set( iPalette, "Palette", bytes ); + return ofs; + } +}; + +REGISTER_SPELL( spEditStringOffset ) + diff --git a/spells/strippify.cpp b/spells/strippify.cpp index c3c7952c4..e785d9d06 100644 --- a/spells/strippify.cpp +++ b/spells/strippify.cpp @@ -1,409 +1,409 @@ -#include "spellbook.h" - -#include "../NvTriStrip/qtwrapper.h" - - -template void copyArray( NifModel * nif, const QModelIndex & iDst, const QModelIndex & iSrc ) -{ - if ( iDst.isValid() && iSrc.isValid() ) - { - nif->updateArray( iDst ); - nif->setArray( iDst, nif->getArray( iSrc ) ); - } -} - -template void copyArray( NifModel * nif, const QModelIndex & iDst, const QModelIndex & iSrc, const QString & name ) -{ - copyArray( nif, nif->getIndex( iDst, name ), nif->getIndex( iSrc, name ) ); -} - -template void copyValue( NifModel * nif, const QModelIndex & iDst, const QModelIndex & iSrc, const QString & name ) -{ - nif->set( iDst, name, nif->get( iSrc, name ) ); -} - - -class spStrippify : public Spell -{ - QString name() const { return "Stripify"; } - QString page() const { return "Mesh"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->checkVersion( 0x0a000000, 0 ) && nif->isNiBlock( index, "NiTriShape" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QPersistentModelIndex idx = index; - QPersistentModelIndex iData = nif->getBlock( nif->getLink( idx, "Data" ), "NiTriShapeData" ); - - if ( ! iData.isValid() ) return idx; - - QVector triangles; - QModelIndex iTriangles = nif->getIndex( iData, "Triangles" ); - if ( iTriangles.isValid() ) - { - int skip = 0; - for ( int t = 0; t < nif->rowCount( iTriangles ); t++ ) - { - Triangle tri = nif->get( iTriangles.child( t, 0 ) ); - if ( tri[0] != tri[1] && tri[1] != tri[2] && tri[2] != tri[0] ) - triangles.append( tri ); - else - skip++; - } - //qWarning() << "num triangles" << triangles.count() << "skipped" << skip; - } - else - return idx; - - QList< QVector > strips = stripify ( triangles ); - - if ( strips.count() <= 0 ) - return idx; - - nif->insertNiBlock( "NiTriStripsData", nif->getBlockNumber( idx )+1 ); - QModelIndex iStripData = nif->getBlock( nif->getBlockNumber( idx ) + 1, "NiTriStripsData" ); - if ( iStripData.isValid() ) - { - copyValue( nif, iStripData, iData, "Num Vertices" ); - - nif->set( iStripData, "Has Vertices", 1 ); - copyArray( nif, iStripData, iData, "Vertices" ); - - copyValue( nif, iStripData, iData, "Has Normals" ); - copyArray( nif, iStripData, iData, "Normals" ); - - copyValue( nif, iStripData, iData, "Has Vertex Colors" ); - copyArray( nif, iStripData, iData, "Vertex Colors" ); - - copyValue( nif, iStripData, iData, "Has UV" ); - copyValue( nif, iStripData, iData, "Num UV Sets" ); - copyValue( nif, iStripData, iData, "Num UV Sets 2" ); - QModelIndex iDstUV = nif->getIndex( iStripData, "UV Sets" ); - QModelIndex iSrcUV = nif->getIndex( iData, "UV Sets" ); - if ( iDstUV.isValid() && iSrcUV.isValid() ) - { - nif->updateArray( iDstUV ); - for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) - { - copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); - } - } - iDstUV = nif->getIndex( iStripData, "UV Sets 2" ); - iSrcUV = nif->getIndex( iData, "UV Sets 2" ); - if ( iDstUV.isValid() && iSrcUV.isValid() ) - { - nif->updateArray( iDstUV ); - for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) - { - copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); - } - } - - copyValue( nif, iStripData, iData, "Center" ); - copyValue( nif, iStripData, iData, "Radius" ); - - nif->set( iStripData, "Num Strips", strips.count() ); - nif->set( iStripData, "Has Points", 1 ); - - QModelIndex iLengths = nif->getIndex( iStripData, "Strip Lengths" ); - QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); - - if ( iLengths.isValid() && iPoints.isValid() ) - { - nif->updateArray( iLengths ); - nif->updateArray( iPoints ); - int x = 0; - int z = 0; - foreach ( QVector strip, strips ) - { - nif->set( iLengths.child( x, 0 ), strip.count() ); - QModelIndex iStrip = iPoints.child( x, 0 ); - nif->updateArray( iStrip ); - nif->setArray( iStrip, strip ); - x++; - z += strip.count() - 2; - } - nif->set( iStripData, "Num Triangles", z ); - - nif->setData( idx.sibling( idx.row(), NifModel::NameCol ), "NiTriStrips" ); - int lnk = nif->getLink( idx, "Data" ); - nif->setLink( idx, "Data", nif->getBlockNumber( iStripData ) ); - nif->removeNiBlock( lnk ); - } - } - return idx; - } -}; - -REGISTER_SPELL( spStrippify ) - - -class spStrippifyAll : public Spell -{ -public: - QString name() const { return "Stripify all TriShapes"; } - QString page() const { return "Optimize"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->checkVersion( 0x0a000000, 0 ) && ! index.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & ) - { - QList iTriShapes; - - for ( int l = 0; l < nif->getBlockCount(); l++ ) - { - QModelIndex idx = nif->getBlock( l, "NiTriShape" ); - if ( idx.isValid() ) - iTriShapes << idx; - } - - spStrippify Stripper; - - foreach ( QModelIndex idx, iTriShapes ) - Stripper.castIfApplicable( nif, idx ); - - return QModelIndex(); - } -}; - -REGISTER_SPELL( spStrippifyAll ) - - -class spTriangulate : public Spell -{ - QString name() const { return "Triangulate"; } - QString page() const { return "Mesh"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->isNiBlock( index, "NiTriStrips" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QPersistentModelIndex idx = index; - QPersistentModelIndex iStripData = nif->getBlock( nif->getLink( idx, "Data" ), "NiTriStripsData" ); - - if ( ! iStripData.isValid() ) return idx; - - QList< QVector > strips; - - QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); - - if ( ! iPoints.isValid() ) return idx; - - for ( int s = 0; s < nif->rowCount( iPoints ); s++ ) - { - QVector strip; - QModelIndex iStrip = iPoints.child( s, 0 ); - for ( int p = 0; p < nif->rowCount( iStrip ); p++ ) - strip.append( nif->get( iStrip.child( p, 0 ) ) ); - strips.append( strip ); - } - - QVector triangles = triangulate ( strips ); - - nif->insertNiBlock( "NiTriShapeData", nif->getBlockNumber( idx ) + 1 ); - QModelIndex iTriData = nif->getBlock( nif->getBlockNumber( idx ) + 1, "NiTriShapeData" ); - if ( iTriData.isValid() ) - { - copyValue( nif, iTriData, iStripData, "Num Vertices" ); - - nif->set( iTriData, "Has Vertices", 1 ); - copyArray( nif, iTriData, iStripData, "Vertices" ); - - copyValue( nif, iTriData, iStripData, "Has Normals" ); - copyArray( nif, iTriData, iStripData, "Normals" ); - - copyValue( nif, iTriData, iStripData, "Has Vertex Colors" ); - copyArray( nif, iTriData, iStripData, "Vertex Colors" ); - - copyValue( nif, iTriData, iStripData, "Has UV" ); - copyValue( nif, iTriData, iStripData, "Num UV Sets" ); - copyValue( nif, iTriData, iStripData, "Num UV Sets 2" ); - QModelIndex iDstUV = nif->getIndex( iTriData, "UV Sets" ); - QModelIndex iSrcUV = nif->getIndex( iStripData, "UV Sets" ); - if ( iDstUV.isValid() && iSrcUV.isValid() ) - { - nif->updateArray( iDstUV ); - for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) - { - copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); - } - } - iDstUV = nif->getIndex( iTriData, "UV Sets 2" ); - iSrcUV = nif->getIndex( iStripData, "UV Sets 2" ); - if ( iDstUV.isValid() && iSrcUV.isValid() ) - { - nif->updateArray( iDstUV ); - for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) - { - copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); - } - } - - copyValue( nif, iTriData, iStripData, "Center" ); - copyValue( nif, iTriData, iStripData, "Radius" ); - - nif->set( iTriData, "Num Triangles", triangles.count() ); - nif->set( iTriData, "Num Triangle Points", triangles.count() * 3 ); - nif->set( iTriData, "Has Triangles", 1 ); - - QModelIndex iTriangles = nif->getIndex( iTriData, "Triangles" ); - if ( iTriangles.isValid() ) - { - nif->updateArray( iTriangles ); - nif->setArray( iTriangles, triangles ); - } - - nif->setData( idx.sibling( idx.row(), NifModel::NameCol ), "NiTriShape" ); - int lnk = nif->getLink( idx, "Data" ); - nif->setLink( idx, "Data", nif->getBlockNumber( iTriData ) ); - nif->removeNiBlock( lnk ); - } - return idx; - } -}; - -REGISTER_SPELL( spTriangulate ) - - -class spStichStrips : public Spell -{ -public: - QString name() const { return "Stich Strips"; } - QString page() const { return "Mesh"; } - - static QModelIndex getStripsData( const NifModel * nif, const QModelIndex & index ) - { - if ( nif->isNiBlock( index, "NiTriStrips" ) ) - return nif->getBlock( nif->getLink( index, "Data" ), "NiTriStripsData" ); - else - return nif->getBlock( index, "NiTriStripsData" ); - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = getStripsData( nif, index ); - return iData.isValid() && nif->get( iData, "Num Strips" ) > 1; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = getStripsData( nif, index ); - QModelIndex iLength = nif->getIndex( iData, "Strip Lengths" ); - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - if ( ! ( iLength.isValid() && iPoints.isValid() ) ) - return index; - - QList< QVector > strips; - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - strips += nif->getArray( iPoints.child( r, 0 ) ); - - if ( strips.isEmpty() ) - return index; - - QVector strip = strips.first(); - strips.pop_front(); - - foreach ( QVector s, strips ) - { // TODO: optimize this - if ( strip.count() & 1 ) - strip << strip.last() << s.first() << s.first() << s; - else - strip << strip.last() << s.first() << s; - } - - nif->set( iData, "Num Strips", 1 ); - nif->updateArray( iLength ); - nif->set( iLength.child( 0, 0 ), strip.size() ); - nif->updateArray( iPoints ); - nif->updateArray( iPoints.child( 0, 0 ) ); - nif->setArray( iPoints.child( 0, 0 ), strip ); - - return index; - } -}; - -REGISTER_SPELL( spStichStrips ) - - -class spUnstichStrips : public Spell -{ -public: - QString name() const { return "Unstich Strips"; } - QString page() const { return "Mesh"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = spStichStrips::getStripsData( nif, index ); - return iData.isValid() && nif->get( iData, "Num Strips" ) == 1; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iData = spStichStrips::getStripsData( nif, index ); - QModelIndex iLength = nif->getIndex( iData, "Strip Lengths" ); - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - if ( ! ( iLength.isValid() && iPoints.isValid() ) ) - return index; - - QVector< quint16 > strip = nif->getArray( iPoints.child( 0, 0 ) ); - if ( strip.size() <= 3 ) return index; - - QList< QVector< quint16 > > strips; - QVector< quint16 > scratch; - - quint16 a = strip[0]; - quint16 b = strip[1]; - bool flip = false; - for ( int s = 2; s < strip.size(); s++ ) - { - quint16 c = strip[s]; - - if ( a != b && b != c && c != a ) - { - if ( scratch.isEmpty() ) - { - if ( flip ) - scratch << a << a << b; - else - scratch << a << b; - } - scratch << c; - } - else if ( ! scratch.isEmpty() ) - { - strips << scratch; - scratch.clear(); - } - - a = b; - b = c; - flip = ! flip; - } - if ( ! scratch.isEmpty() ) - strips << scratch; - - nif->set( iData, "Num Strips", strips.size() ); - nif->updateArray( iLength ); - nif->updateArray( iPoints ); - for ( int r = 0; r < strips.count(); r++ ) - { - nif->set( iLength.child( r, 0 ), strips[r].size() ); - nif->updateArray( iPoints.child( r, 0 ) ); - nif->setArray( iPoints.child( r, 0 ), strips[r] ); - } - - return index; - } -}; - -REGISTER_SPELL( spUnstichStrips ) - +#include "spellbook.h" + +#include "../NvTriStrip/qtwrapper.h" + + +template void copyArray( NifModel * nif, const QModelIndex & iDst, const QModelIndex & iSrc ) +{ + if ( iDst.isValid() && iSrc.isValid() ) + { + nif->updateArray( iDst ); + nif->setArray( iDst, nif->getArray( iSrc ) ); + } +} + +template void copyArray( NifModel * nif, const QModelIndex & iDst, const QModelIndex & iSrc, const QString & name ) +{ + copyArray( nif, nif->getIndex( iDst, name ), nif->getIndex( iSrc, name ) ); +} + +template void copyValue( NifModel * nif, const QModelIndex & iDst, const QModelIndex & iSrc, const QString & name ) +{ + nif->set( iDst, name, nif->get( iSrc, name ) ); +} + + +class spStrippify : public Spell +{ + QString name() const { return "Stripify"; } + QString page() const { return "Mesh"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->checkVersion( 0x0a000000, 0 ) && nif->isNiBlock( index, "NiTriShape" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QPersistentModelIndex idx = index; + QPersistentModelIndex iData = nif->getBlock( nif->getLink( idx, "Data" ), "NiTriShapeData" ); + + if ( ! iData.isValid() ) return idx; + + QVector triangles; + QModelIndex iTriangles = nif->getIndex( iData, "Triangles" ); + if ( iTriangles.isValid() ) + { + int skip = 0; + for ( int t = 0; t < nif->rowCount( iTriangles ); t++ ) + { + Triangle tri = nif->get( iTriangles.child( t, 0 ) ); + if ( tri[0] != tri[1] && tri[1] != tri[2] && tri[2] != tri[0] ) + triangles.append( tri ); + else + skip++; + } + //qWarning() << "num triangles" << triangles.count() << "skipped" << skip; + } + else + return idx; + + QList< QVector > strips = stripify ( triangles ); + + if ( strips.count() <= 0 ) + return idx; + + nif->insertNiBlock( "NiTriStripsData", nif->getBlockNumber( idx )+1 ); + QModelIndex iStripData = nif->getBlock( nif->getBlockNumber( idx ) + 1, "NiTriStripsData" ); + if ( iStripData.isValid() ) + { + copyValue( nif, iStripData, iData, "Num Vertices" ); + + nif->set( iStripData, "Has Vertices", 1 ); + copyArray( nif, iStripData, iData, "Vertices" ); + + copyValue( nif, iStripData, iData, "Has Normals" ); + copyArray( nif, iStripData, iData, "Normals" ); + + copyValue( nif, iStripData, iData, "Has Vertex Colors" ); + copyArray( nif, iStripData, iData, "Vertex Colors" ); + + copyValue( nif, iStripData, iData, "Has UV" ); + copyValue( nif, iStripData, iData, "Num UV Sets" ); + copyValue( nif, iStripData, iData, "Num UV Sets 2" ); + QModelIndex iDstUV = nif->getIndex( iStripData, "UV Sets" ); + QModelIndex iSrcUV = nif->getIndex( iData, "UV Sets" ); + if ( iDstUV.isValid() && iSrcUV.isValid() ) + { + nif->updateArray( iDstUV ); + for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) + { + copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); + } + } + iDstUV = nif->getIndex( iStripData, "UV Sets 2" ); + iSrcUV = nif->getIndex( iData, "UV Sets 2" ); + if ( iDstUV.isValid() && iSrcUV.isValid() ) + { + nif->updateArray( iDstUV ); + for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) + { + copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); + } + } + + copyValue( nif, iStripData, iData, "Center" ); + copyValue( nif, iStripData, iData, "Radius" ); + + nif->set( iStripData, "Num Strips", strips.count() ); + nif->set( iStripData, "Has Points", 1 ); + + QModelIndex iLengths = nif->getIndex( iStripData, "Strip Lengths" ); + QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); + + if ( iLengths.isValid() && iPoints.isValid() ) + { + nif->updateArray( iLengths ); + nif->updateArray( iPoints ); + int x = 0; + int z = 0; + foreach ( QVector strip, strips ) + { + nif->set( iLengths.child( x, 0 ), strip.count() ); + QModelIndex iStrip = iPoints.child( x, 0 ); + nif->updateArray( iStrip ); + nif->setArray( iStrip, strip ); + x++; + z += strip.count() - 2; + } + nif->set( iStripData, "Num Triangles", z ); + + nif->setData( idx.sibling( idx.row(), NifModel::NameCol ), "NiTriStrips" ); + int lnk = nif->getLink( idx, "Data" ); + nif->setLink( idx, "Data", nif->getBlockNumber( iStripData ) ); + nif->removeNiBlock( lnk ); + } + } + return idx; + } +}; + +REGISTER_SPELL( spStrippify ) + + +class spStrippifyAll : public Spell +{ +public: + QString name() const { return "Stripify all TriShapes"; } + QString page() const { return "Optimize"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->checkVersion( 0x0a000000, 0 ) && ! index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) + { + QList iTriShapes; + + for ( int l = 0; l < nif->getBlockCount(); l++ ) + { + QModelIndex idx = nif->getBlock( l, "NiTriShape" ); + if ( idx.isValid() ) + iTriShapes << idx; + } + + spStrippify Stripper; + + foreach ( QModelIndex idx, iTriShapes ) + Stripper.castIfApplicable( nif, idx ); + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spStrippifyAll ) + + +class spTriangulate : public Spell +{ + QString name() const { return "Triangulate"; } + QString page() const { return "Mesh"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->isNiBlock( index, "NiTriStrips" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QPersistentModelIndex idx = index; + QPersistentModelIndex iStripData = nif->getBlock( nif->getLink( idx, "Data" ), "NiTriStripsData" ); + + if ( ! iStripData.isValid() ) return idx; + + QList< QVector > strips; + + QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); + + if ( ! iPoints.isValid() ) return idx; + + for ( int s = 0; s < nif->rowCount( iPoints ); s++ ) + { + QVector strip; + QModelIndex iStrip = iPoints.child( s, 0 ); + for ( int p = 0; p < nif->rowCount( iStrip ); p++ ) + strip.append( nif->get( iStrip.child( p, 0 ) ) ); + strips.append( strip ); + } + + QVector triangles = triangulate ( strips ); + + nif->insertNiBlock( "NiTriShapeData", nif->getBlockNumber( idx ) + 1 ); + QModelIndex iTriData = nif->getBlock( nif->getBlockNumber( idx ) + 1, "NiTriShapeData" ); + if ( iTriData.isValid() ) + { + copyValue( nif, iTriData, iStripData, "Num Vertices" ); + + nif->set( iTriData, "Has Vertices", 1 ); + copyArray( nif, iTriData, iStripData, "Vertices" ); + + copyValue( nif, iTriData, iStripData, "Has Normals" ); + copyArray( nif, iTriData, iStripData, "Normals" ); + + copyValue( nif, iTriData, iStripData, "Has Vertex Colors" ); + copyArray( nif, iTriData, iStripData, "Vertex Colors" ); + + copyValue( nif, iTriData, iStripData, "Has UV" ); + copyValue( nif, iTriData, iStripData, "Num UV Sets" ); + copyValue( nif, iTriData, iStripData, "Num UV Sets 2" ); + QModelIndex iDstUV = nif->getIndex( iTriData, "UV Sets" ); + QModelIndex iSrcUV = nif->getIndex( iStripData, "UV Sets" ); + if ( iDstUV.isValid() && iSrcUV.isValid() ) + { + nif->updateArray( iDstUV ); + for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) + { + copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); + } + } + iDstUV = nif->getIndex( iTriData, "UV Sets 2" ); + iSrcUV = nif->getIndex( iStripData, "UV Sets 2" ); + if ( iDstUV.isValid() && iSrcUV.isValid() ) + { + nif->updateArray( iDstUV ); + for ( int r = 0; r < nif->rowCount( iDstUV ); r++ ) + { + copyArray( nif, iDstUV.child( r, 0 ), iSrcUV.child( r, 0 ) ); + } + } + + copyValue( nif, iTriData, iStripData, "Center" ); + copyValue( nif, iTriData, iStripData, "Radius" ); + + nif->set( iTriData, "Num Triangles", triangles.count() ); + nif->set( iTriData, "Num Triangle Points", triangles.count() * 3 ); + nif->set( iTriData, "Has Triangles", 1 ); + + QModelIndex iTriangles = nif->getIndex( iTriData, "Triangles" ); + if ( iTriangles.isValid() ) + { + nif->updateArray( iTriangles ); + nif->setArray( iTriangles, triangles ); + } + + nif->setData( idx.sibling( idx.row(), NifModel::NameCol ), "NiTriShape" ); + int lnk = nif->getLink( idx, "Data" ); + nif->setLink( idx, "Data", nif->getBlockNumber( iTriData ) ); + nif->removeNiBlock( lnk ); + } + return idx; + } +}; + +REGISTER_SPELL( spTriangulate ) + + +class spStichStrips : public Spell +{ +public: + QString name() const { return "Stich Strips"; } + QString page() const { return "Mesh"; } + + static QModelIndex getStripsData( const NifModel * nif, const QModelIndex & index ) + { + if ( nif->isNiBlock( index, "NiTriStrips" ) ) + return nif->getBlock( nif->getLink( index, "Data" ), "NiTriStripsData" ); + else + return nif->getBlock( index, "NiTriStripsData" ); + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = getStripsData( nif, index ); + return iData.isValid() && nif->get( iData, "Num Strips" ) > 1; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = getStripsData( nif, index ); + QModelIndex iLength = nif->getIndex( iData, "Strip Lengths" ); + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + if ( ! ( iLength.isValid() && iPoints.isValid() ) ) + return index; + + QList< QVector > strips; + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + strips += nif->getArray( iPoints.child( r, 0 ) ); + + if ( strips.isEmpty() ) + return index; + + QVector strip = strips.first(); + strips.pop_front(); + + foreach ( QVector s, strips ) + { // TODO: optimize this + if ( strip.count() & 1 ) + strip << strip.last() << s.first() << s.first() << s; + else + strip << strip.last() << s.first() << s; + } + + nif->set( iData, "Num Strips", 1 ); + nif->updateArray( iLength ); + nif->set( iLength.child( 0, 0 ), strip.size() ); + nif->updateArray( iPoints ); + nif->updateArray( iPoints.child( 0, 0 ) ); + nif->setArray( iPoints.child( 0, 0 ), strip ); + + return index; + } +}; + +REGISTER_SPELL( spStichStrips ) + + +class spUnstichStrips : public Spell +{ +public: + QString name() const { return "Unstich Strips"; } + QString page() const { return "Mesh"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = spStichStrips::getStripsData( nif, index ); + return iData.isValid() && nif->get( iData, "Num Strips" ) == 1; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iData = spStichStrips::getStripsData( nif, index ); + QModelIndex iLength = nif->getIndex( iData, "Strip Lengths" ); + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + if ( ! ( iLength.isValid() && iPoints.isValid() ) ) + return index; + + QVector< quint16 > strip = nif->getArray( iPoints.child( 0, 0 ) ); + if ( strip.size() <= 3 ) return index; + + QList< QVector< quint16 > > strips; + QVector< quint16 > scratch; + + quint16 a = strip[0]; + quint16 b = strip[1]; + bool flip = false; + for ( int s = 2; s < strip.size(); s++ ) + { + quint16 c = strip[s]; + + if ( a != b && b != c && c != a ) + { + if ( scratch.isEmpty() ) + { + if ( flip ) + scratch << a << a << b; + else + scratch << a << b; + } + scratch << c; + } + else if ( ! scratch.isEmpty() ) + { + strips << scratch; + scratch.clear(); + } + + a = b; + b = c; + flip = ! flip; + } + if ( ! scratch.isEmpty() ) + strips << scratch; + + nif->set( iData, "Num Strips", strips.size() ); + nif->updateArray( iLength ); + nif->updateArray( iPoints ); + for ( int r = 0; r < strips.count(); r++ ) + { + nif->set( iLength.child( r, 0 ), strips[r].size() ); + nif->updateArray( iPoints.child( r, 0 ) ); + nif->setArray( iPoints.child( r, 0 ), strips[r] ); + } + + return index; + } +}; + +REGISTER_SPELL( spUnstichStrips ) + diff --git a/spells/tangentspace.cpp b/spells/tangentspace.cpp index 31b78f71b..9e5e484d6 100644 --- a/spells/tangentspace.cpp +++ b/spells/tangentspace.cpp @@ -1,228 +1,228 @@ -#include "tangentspace.h" - -#include "../NvTriStrip/qtwrapper.h" - -#include - -bool spTangentSpace::isApplicable( const NifModel * nif, const QModelIndex & index ) -{ - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); - return nif->checkVersion( 0x14000004, 0x14000005 ) && - ( ( nif->isNiBlock( index, "NiTriShape" ) && nif->isNiBlock( iData, "NiTriShapeData" ) ) - || ( nif->isNiBlock( index, "NiTriStrips" ) && nif->isNiBlock( iData, "NiTriStripsData" ) ) ); -} - -QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) -{ - QPersistentModelIndex iShape = iBlock; - - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); - - QVector verts = nif->getArray( iData, "Vertices" ); - QVector norms = nif->getArray( iData, "Normals" ); - QVector vxcol = nif->getArray( iData, "Vertex Colors" ); - QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); - if ( ! iTexCo.isValid() ) iTexCo = nif->getIndex( iData, "UV Sets 2" ); - iTexCo = iTexCo.child( 0, 0 ); - QVector texco = nif->getArray( iTexCo ); - - QVector triangles; - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - if ( iPoints.isValid() ) - { - QList< QVector< quint16 > > strips; - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); - triangles = triangulate( strips ); - } - else - { - triangles = nif->getArray( iData, "Triangles" ); - } - - if ( verts.isEmpty() || norms.count() != verts.count() || texco.count() != verts.count() || triangles.isEmpty() ) - { - qWarning() << Spell::tr( "need vertices, normals, texture coordinates and faces to calculate tangents and binormals" ); - return iBlock; - } - - QVector tan( verts.count() ); - QVector bin( verts.count() ); - - QMultiHash vmap; - - //int skptricnt = 0; - - for ( int t = 0; t < triangles.count(); t++ ) - { // for each triangle caculate the texture flow direction - //qDebug() << "triangle" << t; - - Triangle & tri = triangles[t]; - - int i1 = tri[0]; - int i2 = tri[1]; - int i3 = tri[2]; - - const Vector3 & v1 = verts[i1]; - const Vector3 & v2 = verts[i2]; - const Vector3 & v3 = verts[i3]; - - const Vector2 & w1 = texco[i1]; - const Vector2 & w2 = texco[i2]; - const Vector2 & w3 = texco[i3]; - - Vector3 v2v1 = v2 - v1; - Vector3 v3v1 = v3 - v1; - - Vector2 w2w1 = w2 - w1; - Vector2 w3w1 = w3 - w1; - - float r = w2w1[0] * w3w1[1] - w3w1[0] * w2w1[1]; - - /* - if ( fabs( r ) <= 10e-10 ) - { - //if ( skptricnt++ < 3 ) - // qWarning() << t; - continue; - } - - r = 1.0 / r; - */ - // this seems to produces better results - r = ( r >= 0 ? +1 : -1 ); - - Vector3 sdir( - ( w3w1[1] * v2v1[0] - w2w1[1] * v3v1[0] ) * r, - ( w3w1[1] * v2v1[1] - w2w1[1] * v3v1[1] ) * r, - ( w3w1[1] * v2v1[2] - w2w1[1] * v3v1[2] ) * r - ); - - Vector3 tdir( - ( w2w1[0] * v3v1[0] - w3w1[0] * v2v1[0] ) * r, - ( w2w1[0] * v3v1[1] - w3w1[0] * v2v1[1] ) * r, - ( w2w1[0] * v3v1[2] - w3w1[0] * v2v1[2] ) * r - ); - - sdir.normalize(); - tdir.normalize(); - - //qDebug() << sdir << tdir; - - for ( int j = 0; j < 3; j++ ) - { - int i = tri[j]; - - tan[i] += tdir; - bin[i] += sdir; - } - } - - //qWarning() << "skipped triangles" << skptricnt; - - //int cnt = 0; - - for ( int i = 0; i < verts.count(); i++ ) - { // for each vertex calculate tangent and binormal - const Vector3 & n = norms[i]; - - Vector3 & t = tan[i]; - Vector3 & b = bin[i]; - - //qDebug() << n << t << b; - - if ( t == Vector3() || b == Vector3() ) - { - t[0] = n[1]; t[1] = n[2]; t[2] = n[0]; - b = Vector3::crossproduct( n, t ); - //if ( cnt++ < 3 ) - // qWarning() << i; - } - else - { - t.normalize(); - t = ( t - n * Vector3::dotproduct( n, t ) ); - t.normalize(); - - //b = Vector3::crossproduct( n, t ); - - b.normalize(); - b = ( b - n * Vector3::dotproduct( n, b ) ); - b = ( b - t * Vector3::dotproduct( t, b ) ); - b.normalize(); - } - - //qDebug() << n << t << b; - //qDebug() << ""; - } - - //qWarning() << "unassigned vertices" << cnt; - - // update or create the tangent space extra data - - QModelIndex iTSpace; - foreach ( qint32 link, nif->getChildLinks( nif->getBlockNumber( iShape ) ) ) - { - iTSpace = nif->getBlock( link, "NiBinaryExtraData" ); - if ( iTSpace.isValid() && nif->get( iTSpace, "Name" ) == "Tangent space (binormal & tangent vectors)" ) - break; - else - iTSpace = QModelIndex(); - } - - if ( ! iTSpace.isValid() ) - { - iTSpace = nif->insertNiBlock( "NiBinaryExtraData", nif->getBlockNumber( iShape ) + 1 ); - nif->set( iTSpace, "Name", "Tangent space (binormal & tangent vectors)" ); - QModelIndex iNumExtras = nif->getIndex( iShape, "Num Extra Data List" ); - QModelIndex iExtras = nif->getIndex( iShape, "Extra Data List" ); - if ( iNumExtras.isValid() && iExtras.isValid() ) - { - int numlinks = nif->get( iNumExtras ); - nif->set( iNumExtras, numlinks + 1 ); - nif->updateArray( iExtras ); - nif->setLink( iExtras.child( numlinks, 0 ), nif->getBlockNumber( iTSpace ) ); - } - } - - nif->set( iTSpace, "Binary Data", QByteArray( (const char *) tan.data(), tan.count() * sizeof( Vector3 ) ) + QByteArray( (const char *) bin.data(), bin.count() * sizeof( Vector3 ) ) ); - return iShape; -} - -REGISTER_SPELL( spTangentSpace ) - -class spAllTangentSpaces : public Spell -{ -public: - QString name() const { return Spell::tr( "Update All Tangent Spaces" ); } - QString page() const { return Spell::tr( "Batch" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & idx ) - { - return nif && nif->checkVersion( 0x14000004, 0x14000005 ) && ! idx.isValid(); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & ) - { - QList< QPersistentModelIndex > indices; - - spTangentSpace TSpacer; - - for ( int n = 0; n < nif->getBlockCount(); n++ ) - { - QModelIndex idx = nif->getBlock( n ); - if ( TSpacer.isApplicable( nif, idx ) ) - indices << idx; - } - - foreach ( QModelIndex idx, indices ) - { - TSpacer.castIfApplicable( nif, idx ); - } - - return QModelIndex(); - } -}; - -REGISTER_SPELL( spAllTangentSpaces ) - +#include "tangentspace.h" + +#include "../NvTriStrip/qtwrapper.h" + +#include + +bool spTangentSpace::isApplicable( const NifModel * nif, const QModelIndex & index ) +{ + QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + return nif->checkVersion( 0x14000004, 0x14000005 ) && + ( ( nif->isNiBlock( index, "NiTriShape" ) && nif->isNiBlock( iData, "NiTriShapeData" ) ) + || ( nif->isNiBlock( index, "NiTriStrips" ) && nif->isNiBlock( iData, "NiTriStripsData" ) ) ); +} + +QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) +{ + QPersistentModelIndex iShape = iBlock; + + QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + + QVector verts = nif->getArray( iData, "Vertices" ); + QVector norms = nif->getArray( iData, "Normals" ); + QVector vxcol = nif->getArray( iData, "Vertex Colors" ); + QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); + if ( ! iTexCo.isValid() ) iTexCo = nif->getIndex( iData, "UV Sets 2" ); + iTexCo = iTexCo.child( 0, 0 ); + QVector texco = nif->getArray( iTexCo ); + + QVector triangles; + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + if ( iPoints.isValid() ) + { + QList< QVector< quint16 > > strips; + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); + triangles = triangulate( strips ); + } + else + { + triangles = nif->getArray( iData, "Triangles" ); + } + + if ( verts.isEmpty() || norms.count() != verts.count() || texco.count() != verts.count() || triangles.isEmpty() ) + { + qWarning() << Spell::tr( "need vertices, normals, texture coordinates and faces to calculate tangents and binormals" ); + return iBlock; + } + + QVector tan( verts.count() ); + QVector bin( verts.count() ); + + QMultiHash vmap; + + //int skptricnt = 0; + + for ( int t = 0; t < triangles.count(); t++ ) + { // for each triangle caculate the texture flow direction + //qDebug() << "triangle" << t; + + Triangle & tri = triangles[t]; + + int i1 = tri[0]; + int i2 = tri[1]; + int i3 = tri[2]; + + const Vector3 & v1 = verts[i1]; + const Vector3 & v2 = verts[i2]; + const Vector3 & v3 = verts[i3]; + + const Vector2 & w1 = texco[i1]; + const Vector2 & w2 = texco[i2]; + const Vector2 & w3 = texco[i3]; + + Vector3 v2v1 = v2 - v1; + Vector3 v3v1 = v3 - v1; + + Vector2 w2w1 = w2 - w1; + Vector2 w3w1 = w3 - w1; + + float r = w2w1[0] * w3w1[1] - w3w1[0] * w2w1[1]; + + /* + if ( fabs( r ) <= 10e-10 ) + { + //if ( skptricnt++ < 3 ) + // qWarning() << t; + continue; + } + + r = 1.0 / r; + */ + // this seems to produces better results + r = ( r >= 0 ? +1 : -1 ); + + Vector3 sdir( + ( w3w1[1] * v2v1[0] - w2w1[1] * v3v1[0] ) * r, + ( w3w1[1] * v2v1[1] - w2w1[1] * v3v1[1] ) * r, + ( w3w1[1] * v2v1[2] - w2w1[1] * v3v1[2] ) * r + ); + + Vector3 tdir( + ( w2w1[0] * v3v1[0] - w3w1[0] * v2v1[0] ) * r, + ( w2w1[0] * v3v1[1] - w3w1[0] * v2v1[1] ) * r, + ( w2w1[0] * v3v1[2] - w3w1[0] * v2v1[2] ) * r + ); + + sdir.normalize(); + tdir.normalize(); + + //qDebug() << sdir << tdir; + + for ( int j = 0; j < 3; j++ ) + { + int i = tri[j]; + + tan[i] += tdir; + bin[i] += sdir; + } + } + + //qWarning() << "skipped triangles" << skptricnt; + + //int cnt = 0; + + for ( int i = 0; i < verts.count(); i++ ) + { // for each vertex calculate tangent and binormal + const Vector3 & n = norms[i]; + + Vector3 & t = tan[i]; + Vector3 & b = bin[i]; + + //qDebug() << n << t << b; + + if ( t == Vector3() || b == Vector3() ) + { + t[0] = n[1]; t[1] = n[2]; t[2] = n[0]; + b = Vector3::crossproduct( n, t ); + //if ( cnt++ < 3 ) + // qWarning() << i; + } + else + { + t.normalize(); + t = ( t - n * Vector3::dotproduct( n, t ) ); + t.normalize(); + + //b = Vector3::crossproduct( n, t ); + + b.normalize(); + b = ( b - n * Vector3::dotproduct( n, b ) ); + b = ( b - t * Vector3::dotproduct( t, b ) ); + b.normalize(); + } + + //qDebug() << n << t << b; + //qDebug() << ""; + } + + //qWarning() << "unassigned vertices" << cnt; + + // update or create the tangent space extra data + + QModelIndex iTSpace; + foreach ( qint32 link, nif->getChildLinks( nif->getBlockNumber( iShape ) ) ) + { + iTSpace = nif->getBlock( link, "NiBinaryExtraData" ); + if ( iTSpace.isValid() && nif->get( iTSpace, "Name" ) == "Tangent space (binormal & tangent vectors)" ) + break; + else + iTSpace = QModelIndex(); + } + + if ( ! iTSpace.isValid() ) + { + iTSpace = nif->insertNiBlock( "NiBinaryExtraData", nif->getBlockNumber( iShape ) + 1 ); + nif->set( iTSpace, "Name", "Tangent space (binormal & tangent vectors)" ); + QModelIndex iNumExtras = nif->getIndex( iShape, "Num Extra Data List" ); + QModelIndex iExtras = nif->getIndex( iShape, "Extra Data List" ); + if ( iNumExtras.isValid() && iExtras.isValid() ) + { + int numlinks = nif->get( iNumExtras ); + nif->set( iNumExtras, numlinks + 1 ); + nif->updateArray( iExtras ); + nif->setLink( iExtras.child( numlinks, 0 ), nif->getBlockNumber( iTSpace ) ); + } + } + + nif->set( iTSpace, "Binary Data", QByteArray( (const char *) tan.data(), tan.count() * sizeof( Vector3 ) ) + QByteArray( (const char *) bin.data(), bin.count() * sizeof( Vector3 ) ) ); + return iShape; +} + +REGISTER_SPELL( spTangentSpace ) + +class spAllTangentSpaces : public Spell +{ +public: + QString name() const { return Spell::tr( "Update All Tangent Spaces" ); } + QString page() const { return Spell::tr( "Batch" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & idx ) + { + return nif && nif->checkVersion( 0x14000004, 0x14000005 ) && ! idx.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) + { + QList< QPersistentModelIndex > indices; + + spTangentSpace TSpacer; + + for ( int n = 0; n < nif->getBlockCount(); n++ ) + { + QModelIndex idx = nif->getBlock( n ); + if ( TSpacer.isApplicable( nif, idx ) ) + indices << idx; + } + + foreach ( QModelIndex idx, indices ) + { + TSpacer.castIfApplicable( nif, idx ); + } + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spAllTangentSpaces ) + diff --git a/spells/tangentspace.h b/spells/tangentspace.h index 6019cc60b..a7f81b6b9 100644 --- a/spells/tangentspace.h +++ b/spells/tangentspace.h @@ -1,17 +1,17 @@ -#ifndef TANGENTSPACE_H -#define TANGENTSPACE_H - -#include "../spellbook.h" - -class spTangentSpace : public Spell -{ -public: - QString name() const { return Spell::tr("Update Tangent Space"); } - QString page() const { return Spell::tr("Mesh"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ); - QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ); -}; - - -#endif +#ifndef TANGENTSPACE_H +#define TANGENTSPACE_H + +#include "../spellbook.h" + +class spTangentSpace : public Spell +{ +public: + QString name() const { return Spell::tr("Update Tangent Space"); } + QString page() const { return Spell::tr("Mesh"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ); + QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ); +}; + + +#endif diff --git a/spells/texture.cpp b/spells/texture.cpp index 3a9ca1d01..14376ac2a 100644 --- a/spells/texture.cpp +++ b/spells/texture.cpp @@ -1,560 +1,560 @@ - -#include "../spellbook.h" -#include "../gl/gltex.h" - -#include "../config.h" - -#include "../widgets/fileselect.h" -#include "../widgets/uvedit.h" - -#include "../NvTriStrip/qtwrapper.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* XPM */ -static char * tex42_xpm[] = { -"80 80 43 1", -" c None", -". c #29194A","+ c #2D1D37","@ c #2F2528","# c #333018", -"$ c #3C2D66","% c #50521A","& c #5B4E3C","* c #5B4286", -"= c #544885","- c #584B70","; c #64589D","> c #67710F", -", c #7558A4","' c #6A619A",") c #68658B","! c #7264AA", -"~ c #7E78A5","{ c #8173B8","] c #8274B1","^ c #8276AD", -"/ c #8A72C0","( c #998F0F","_ c #83819C",": c #908F97", -"< c #9F80D1","[ c #9589BD","} c #9885CE","| c #9986C7", -"1 c #9D9E9D","2 c #9F9AB4","3 c #A992DC","4 c #B38FDD", -"5 c #AC9AD0","6 c #AF97DA","7 c #C2BD04","8 c #C1A1DC", -"9 c #BEA9DD","0 c #BABBBB","a c #C1B8D2","b c #CDB7DC", -"c c #D5CAE0","d c #D6D4D7", -" ", -" ", -" ", -" ", -" ", -" d 2 ", -" aaa ~c d ", -" dd b_~2a!)~0 ", -" cdcc a6{'~[]'')0 ", -" cdada9c dc5^'^]];''~0 ", -" 53bba05[1 dc5{]|{|'']|20 ", -" ca5}aaab}~0d cdc5]{|||''|6~10 ", -" bb2<50ab|{_0 cdc}}|]|['{[}[10 ", -" b5b|#-**<44884488434<<<333699[11110 ", -" 96}{||{]'';===$$$.+@##%>%>&@**,,4bb88444}}}}366352:11110 ", -" [|33363}{{{!'====$$..+#>>>>>>((@-,<4b888444<<3436|_211110 ", -" [{{/}34464}}{!;'===$$$$.+#%>>>(((7-*,4888888643}}]!')_1110d ", -" a]{{}}}3368633{/''==$$$...+#%>>((7(%%@'<8884/||<]!*====-=):d ", -" [}}/}/}366633>>7777(&&&--]]]=*====;!;;;==='_d ", -" |3}333336334<<{!!;=$$$$....+@#%(777((&---$-*-==;*;=*;{;;;';;;)2 ", -" 63633333433<}{/!;;'=='==$$...+@%>(77(7&*/4]**,**=*=;!///{!!;';;;)c ", -" 6966333<3<<}{{/{{!!!=====*$$.+@##&((&&@-,<48/,]/!!;;,/{/{{{{{{{]!'2 ", -" 93|}}}3}}//}<{}{/!'!;';;;*$$..++@@((-***,<484<<}<isNiBlock( index, "NiTriShape" ) || nif->isNiBlock( index, "NiTriStrips" ) ) - return nif->getBlock( nif->getLink( index, "Data" ) ); - else if ( nif->isNiBlock( index, "NiTriShapeData" ) || nif->isNiBlock( index, "NiTriStripsData" ) ) - return index; - return QModelIndex(); -} - -QModelIndex getUV( const NifModel * nif, const QModelIndex & index ) -{ - QModelIndex iData = getData( nif, index ); - - if ( iData.isValid() ) - { - QModelIndex iUVs = nif->getIndex( iData, "UV Sets" ); - return iUVs; - } - return QModelIndex(); -} - -class spChooseTexture : public Spell -{ -public: - QString name() const { return "Choose"; } - QString page() const { return "Texture"; } - bool instant() const { return true; } - QIcon icon() const - { - if ( ! tex42_xpm_icon ) tex42_xpm_icon = new QIcon( tex42_xpm ); - return *tex42_xpm_icon; - } - - bool isApplicable( const NifModel * nif, const QModelIndex & idx ) - { - QModelIndex iBlock = nif->getBlock( idx ); - return ( nif->isNiBlock( iBlock, "NiSourceTexture" ) || nif->isNiBlock( iBlock, "NiImage" ) ) - && ( iBlock == idx.sibling( idx.row(), 0 ) || nif->itemName( idx ) == "File Name" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & idx ) - { - QModelIndex iSource = nif->getBlock( idx ); - QModelIndex iFile = nif->getIndex( iSource, "File Name" ); - QString file = TexCache::find( nif->get( iFile ), nif->getFolder() ); - - if ( ! QFile::exists(file) ) - { - // if file not found in cache, use last texture path - NIFSKOPE_QSETTINGS(cfg); - QString defaulttexpath(cfg.value("last texture path", QVariant(QDir::homePath())).toString()); - file = QDir(defaulttexpath).filePath(file); - } - - - // to avoid shortcut resolve bug take *.* as text filter - file = QFileDialog::getOpenFileName( 0, "Select a texture file", file, "*.*" ); - - if ( ! file.isEmpty() ) - { - // save path for future - NIFSKOPE_QSETTINGS(cfg); - cfg.setValue("last texture path", QVariant(QDir(file).absolutePath())); - - file = TexCache::stripPath( file, nif->getFolder() ); - nif->set( iSource, "Use External", 1 ); - nif->set( iFile, file.replace( "/", "\\" ) ); - } - return idx; - } -}; - -REGISTER_SPELL( spChooseTexture ) - - -class spEditTexCoords : public Spell -{ -public: - QString name() const { return Spell::tr("Edit UV"); } - QString page() const { return Spell::tr("Texture"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return ( nif->itemName(index) == "NiTriShape" || nif->itemName(index) == "NiTriStrips" ); - - //QModelIndex iUVs = getUV( nif, index ); - //return iUVs.isValid() && nif->rowCount( iUVs ) >= 1; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - UVWidget::createEditor( nif, index ); - return index; - } -}; - -REGISTER_SPELL( spEditTexCoords ) - - -QModelIndex addTexture( NifModel * nif, const QModelIndex & index, const QString & name ) -{ - QModelIndex iTexProp = nif->getBlock( index, "NiTexturingProperty" ); - if ( ! iTexProp.isValid() ) return index; - if ( nif->get( iTexProp, "Texture Count" ) < 7 ) - nif->set( iTexProp, "Texture Count", 7 ); - - nif->set( iTexProp, QString( "Has %1" ).arg( name ), 1 ); - QPersistentModelIndex iTex = nif->getIndex( iTexProp, name ); - if ( ! iTex.isValid() ) return index; - - nif->set( iTex, "Clamp Mode", 3 ); - nif->set( iTex, "Filter Mode", 3 ); - nif->set( iTex, "PS2 K", -75 ); - nif->set( iTex, "Unknown1", 257 ); - - QModelIndex iSrcTex = nif->insertNiBlock( "NiSourceTexture", nif->getBlockNumber( iTexProp ) + 1 ); - nif->setLink( iTex, "Source", nif->getBlockNumber( iSrcTex ) ); - - nif->set( iSrcTex, "Pixel Layout", ( nif->getVersion() == "20.0.0.5" && name == "Base Texture" ? 6 : 5 ) ); - nif->set( iSrcTex, "Use Mipmaps", 2 ); - nif->set( iSrcTex, "Alpha Format", 3 ); - nif->set( iSrcTex, "Unknown Byte", 1 ); - nif->set( iSrcTex, "Unknown Byte 2", 1 ); - nif->set( iSrcTex, "Use External", 1 ); - - return iSrcTex; -} - -class spAddBaseMap : public Spell -{ -public: - QString name() const { return "Add Base Texture"; } - QString page() const { return "Texture"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); - return ( block.isValid() && nif->get( block, "Has Base Texture" ) == 0 ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - return addTexture( nif, index, "Base Texture" ); - } -}; - -REGISTER_SPELL( spAddBaseMap ) - -class spAddDarkMap : public Spell -{ -public: - QString name() const { return "Add Dark Map"; } - QString page() const { return "Texture"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); - return ( block.isValid() && nif->get( block, "Has Dark Texture" ) == 0 ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - return addTexture( nif, index, "Dark Texture" ); - } -}; - -REGISTER_SPELL( spAddDarkMap ) - -class spAddDetailMap : public Spell -{ -public: - QString name() const { return "Add Detail Map"; } - QString page() const { return "Texture"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); - return ( block.isValid() && nif->get( block, "Has Detail Texture" ) == 0 ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - return addTexture( nif, index, "Detail Texture" ); - } -}; - -REGISTER_SPELL( spAddDetailMap ) - -class spAddGlowMap : public Spell -{ -public: - QString name() const { return "Add Glow Map"; } - QString page() const { return "Texture"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); - return ( block.isValid() && nif->get( block, "Has Glow Texture" ) == 0 ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - return addTexture( nif, index, "Glow Texture" ); - } -}; - -REGISTER_SPELL( spAddGlowMap ) - -#define wrap01f( X ) ( X > 1 ? X - floor( X ) : X < 0 ? X - floor( X ) : X ) - -class spTextureTemplate : public Spell -{ - QString name() const { return "Export Template"; } - QString page() const { return "Texture"; } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - QModelIndex iUVs = getUV( nif, index ); - return iUVs.isValid() && nif->rowCount( iUVs ) >= 1; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QModelIndex iUVs = getUV( nif, index ); - if ( nif->rowCount( iUVs ) <= 0 ) - return index; - - // fire up a dialog to set the user parameters - QDialog dlg; - QGridLayout * lay = new QGridLayout; - dlg.setLayout( lay ); - - FileSelector * file = new FileSelector( FileSelector::SaveFile, "File", QBoxLayout::RightToLeft ); - file->setFilter( QStringList() << "*.tga" ); - lay->addWidget( file, 0, 0, 1, 2 ); - - lay->addWidget( new QLabel( "Size" ), 1, 0 ); - QComboBox * size = new QComboBox; - lay->addWidget( size, 1, 1 ); - for ( int i = 6; i < 12; i++ ) - size->addItem( QString::number( 2 << i ) ); - - lay->addWidget( new QLabel( "Coord Set" ), 2, 0 ); - QComboBox * set = new QComboBox; - lay->addWidget( set, 2, 1 ); - for ( int i = 0; i < nif->rowCount( iUVs ); i++ ) - set->addItem( QString( "set %1" ).arg( i ) ); - - lay->addWidget( new QLabel( "Wrap Mode" ), 3, 0 ); - QComboBox * wrap = new QComboBox; - lay->addWidget( wrap, 3, 1 ); - wrap->addItem( "wrap" ); - wrap->addItem( "clamp" ); - - QPushButton * ok = new QPushButton( "Ok" ); - QObject::connect( ok, SIGNAL( clicked() ), &dlg, SLOT( accept() ) ); - lay->addWidget( ok, 4, 0, 1, 2 ); - - NIFSKOPE_QSETTINGS(settings); - settings.beginGroup( "spells" ); - settings.beginGroup( page() ); - settings.beginGroup( name() ); - - wrap->setCurrentIndex( settings.value( "Wrap Mode", 0 ).toInt() ); - size->setCurrentIndex( settings.value( "Image Size", 2 ).toInt() ); - file->setText( settings.value( "File Name", "" ).toString() ); - - if ( dlg.exec() != QDialog::Accepted ) - return index; - - settings.setValue( "Wrap Mode", wrap->currentIndex() ); - settings.setValue( "Image Size", size->currentIndex() ); - settings.setValue( "File Name", file->text() ); - - // get the selected coord set - QModelIndex iSet = iUVs.child( set->currentIndex(), 0 ); - - QVector uv = nif->getArray( iSet ); - QVector tri; - - // get the triangles - QModelIndex iData = getData( nif, index ); - QModelIndex iPoints = nif->getIndex( iData, "Points" ); - if ( iPoints.isValid() ) - { - QList< QVector< quint16 > > strips; - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); - tri = triangulate( strips ); - } - else - { - tri = nif->getArray( nif->getIndex( getData( nif, index ), "Triangles" ) ); - } - - // render the template image - quint16 s = size->currentText().toInt(); - - QImage img( s, s, QImage::Format_RGB32 ); - QPainter pntr( &img ); - - pntr.fillRect( img.rect(), QColor( 0xff, 0xff, 0xff ) ); - pntr.scale( s, s ); - pntr.setPen( QColor( 0x10, 0x20, 0x30 ) ); - - bool wrp = wrap->currentIndex() == 0; - - foreach ( Triangle t, tri ) - { - Vector2 v2[3]; - for ( int i = 0; i < 3; i++ ) - { - v2[i] = uv.value( t[i] ); - if ( wrp ) - { - v2[i][0] = wrap01f( v2[i][0] ); - v2[i][1] = wrap01f( v2[i][1] ); - } - } - - pntr.drawLine( QPointF( v2[0][0], v2[0][1] ), QPointF( v2[1][0], v2[1][1] ) ); - pntr.drawLine( QPointF( v2[1][0], v2[1][1] ), QPointF( v2[2][0], v2[2][1] ) ); - pntr.drawLine( QPointF( v2[2][0], v2[2][1] ), QPointF( v2[0][0], v2[0][1] ) ); - } - - // write the file - QString filename = file->text(); - if ( ! filename.endsWith( ".tga", Qt::CaseInsensitive ) ) - filename.append( ".tga" ); - - quint8 hdr[18]; - for ( int o = 0; o < 18; o++ ) hdr[o] = 0; - hdr[02] = 2; - hdr[12] = s % 256; - hdr[13] = s / 256; - hdr[14] = s % 256; - hdr[15] = s / 256; - hdr[16] = 32; - hdr[17] = 32; // flipV - - QFile f( filename ); - if ( ! f.open( QIODevice::WriteOnly ) || f.write( (char *) hdr, 18 ) != 18 || f.write( (char *) img.bits(), s * s * 4 ) != s * s * 4 ) - qWarning() << "exportTemplate(" << filename << ") : could not write file"; - - return index; - } -}; - -REGISTER_SPELL( spTextureTemplate ) - -class spMultiApplyMode : public Spell -{ - -public: - - QString name() const { return "Multi Apply Mode"; } - QString page() const { return "Batch"; } - - bool isApplicable( const NifModel * nif, const QModelIndex &index ) - { - return nif->checkVersion( 0x14000005, 0x14000005 ) && ! index.isValid(); - } - - QModelIndex cast( NifModel *nif, const QModelIndex &index ) - { - QStringList modes; - modes << "Replace" << "Decal" << "Modulate" << "Hilight" << "Hilight2"; - - QDialog dlg; - dlg.resize( 300, 60 ); - QComboBox *cbRep = new QComboBox( &dlg ); - QComboBox *cbBy = new QComboBox( &dlg ); - QPushButton *btnOk = new QPushButton( "OK", &dlg ); - QPushButton *btnCancel = new QPushButton( "Cancel", &dlg ); - cbRep->addItems( modes ); - cbRep->setCurrentIndex( 2 ); - cbBy->addItems( modes ); - cbBy->setCurrentIndex( 2 ); - - QGridLayout *layout; - layout = new QGridLayout; - layout->setSpacing( 20 ); - layout->addWidget( new QLabel( "Replace", &dlg ), 0, 0, Qt::AlignBottom ); - layout->addWidget( new QLabel( "By", &dlg ), 0, 1, Qt::AlignBottom ); - layout->addWidget( cbRep, 1, 0, Qt::AlignTop ); - layout->addWidget( cbBy, 1, 1, Qt::AlignTop ); - layout->addWidget( btnOk, 2, 0 ); - layout->addWidget( btnCancel, 2, 1 ); - dlg.setLayout( layout ); - - QObject::connect( btnOk, SIGNAL( clicked() ), &dlg, SLOT( accept() ) ); - QObject::connect( btnCancel, SIGNAL( clicked() ), &dlg, SLOT( reject() ) ); - - if ( dlg.exec() != QDialog::Accepted ) - return QModelIndex(); - - replaceApplyMode( nif, index, cbRep->currentIndex(), cbBy->currentIndex() ); - - return QModelIndex(); - } - - void replaceApplyMode( NifModel *nif, const QModelIndex &index, int rep, int by ) - { - if ( !index.isValid() ) - return; - - if ( nif->inherits( index, "NiTexturingProperty" ) && - nif->get( index, "Apply Mode" ) == rep ) - nif->set( index, "Apply Mode", by ); - - QModelIndex iChildren = nif->getIndex( index, "Children" ); - QList lChildren = nif->getChildLinks( nif->getBlockNumber( index ) ); - if ( iChildren.isValid() ) - { - for ( int c = 0; c < nif->rowCount( iChildren ); c++ ) - { - qint32 link = nif->getLink( iChildren.child( c, 0 ) ); - if ( lChildren.contains( link ) ) - { - QModelIndex iChild = nif->getBlock( link ); - replaceApplyMode( nif, iChild, rep, by ); - } - } - } - - QModelIndex iProperties = nif->getIndex( index, "Properties" ); - if ( iProperties.isValid() ) - { - for ( int p = 0; p < nif->rowCount( iProperties ); p++ ) - { - QModelIndex iProp = nif->getBlock( nif->getLink( iProperties.child( p, 0 ) ) ); - replaceApplyMode( nif, iProp, rep, by ); - } - } - } -}; - -REGISTER_SPELL( spMultiApplyMode ) + +#include "../spellbook.h" +#include "../gl/gltex.h" + +#include "../config.h" + +#include "../widgets/fileselect.h" +#include "../widgets/uvedit.h" + +#include "../NvTriStrip/qtwrapper.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* XPM */ +static char * tex42_xpm[] = { +"80 80 43 1", +" c None", +". c #29194A","+ c #2D1D37","@ c #2F2528","# c #333018", +"$ c #3C2D66","% c #50521A","& c #5B4E3C","* c #5B4286", +"= c #544885","- c #584B70","; c #64589D","> c #67710F", +", c #7558A4","' c #6A619A",") c #68658B","! c #7264AA", +"~ c #7E78A5","{ c #8173B8","] c #8274B1","^ c #8276AD", +"/ c #8A72C0","( c #998F0F","_ c #83819C",": c #908F97", +"< c #9F80D1","[ c #9589BD","} c #9885CE","| c #9986C7", +"1 c #9D9E9D","2 c #9F9AB4","3 c #A992DC","4 c #B38FDD", +"5 c #AC9AD0","6 c #AF97DA","7 c #C2BD04","8 c #C1A1DC", +"9 c #BEA9DD","0 c #BABBBB","a c #C1B8D2","b c #CDB7DC", +"c c #D5CAE0","d c #D6D4D7", +" ", +" ", +" ", +" ", +" ", +" d 2 ", +" aaa ~c d ", +" dd b_~2a!)~0 ", +" cdcc a6{'~[]'')0 ", +" cdada9c dc5^'^]];''~0 ", +" 53bba05[1 dc5{]|{|'']|20 ", +" ca5}aaab}~0d cdc5]{|||''|6~10 ", +" bb2<50ab|{_0 cdc}}|]|['{[}[10 ", +" b5b|#-**<44884488434<<<333699[11110 ", +" 96}{||{]'';===$$$.+@##%>%>&@**,,4bb88444}}}}366352:11110 ", +" [|33363}{{{!'====$$..+#>>>>>>((@-,<4b888444<<3436|_211110 ", +" [{{/}34464}}{!;'===$$$$.+#%>>>(((7-*,4888888643}}]!')_1110d ", +" a]{{}}}3368633{/''==$$$...+#%>>((7(%%@'<8884/||<]!*====-=):d ", +" [}}/}/}366633>>7777(&&&--]]]=*====;!;;;==='_d ", +" |3}333336334<<{!!;=$$$$....+@#%(777((&---$-*-==;*;=*;{;;;';;;)2 ", +" 63633333433<}{/!;;'=='==$$...+@%>(77(7&*/4]**,**=*=;!///{!!;';;;)c ", +" 6966333<3<<}{{/{{!!!=====*$$.+@##&((&&@-,<48/,]/!!;;,/{/{{{{{{{]!'2 ", +" 93|}}}3}}//}<{}{/!'!;';;;*$$..++@@((-***,<484<<}<isNiBlock( index, "NiTriShape" ) || nif->isNiBlock( index, "NiTriStrips" ) ) + return nif->getBlock( nif->getLink( index, "Data" ) ); + else if ( nif->isNiBlock( index, "NiTriShapeData" ) || nif->isNiBlock( index, "NiTriStripsData" ) ) + return index; + return QModelIndex(); +} + +QModelIndex getUV( const NifModel * nif, const QModelIndex & index ) +{ + QModelIndex iData = getData( nif, index ); + + if ( iData.isValid() ) + { + QModelIndex iUVs = nif->getIndex( iData, "UV Sets" ); + return iUVs; + } + return QModelIndex(); +} + +class spChooseTexture : public Spell +{ +public: + QString name() const { return "Choose"; } + QString page() const { return "Texture"; } + bool instant() const { return true; } + QIcon icon() const + { + if ( ! tex42_xpm_icon ) tex42_xpm_icon = new QIcon( tex42_xpm ); + return *tex42_xpm_icon; + } + + bool isApplicable( const NifModel * nif, const QModelIndex & idx ) + { + QModelIndex iBlock = nif->getBlock( idx ); + return ( nif->isNiBlock( iBlock, "NiSourceTexture" ) || nif->isNiBlock( iBlock, "NiImage" ) ) + && ( iBlock == idx.sibling( idx.row(), 0 ) || nif->itemName( idx ) == "File Name" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & idx ) + { + QModelIndex iSource = nif->getBlock( idx ); + QModelIndex iFile = nif->getIndex( iSource, "File Name" ); + QString file = TexCache::find( nif->get( iFile ), nif->getFolder() ); + + if ( ! QFile::exists(file) ) + { + // if file not found in cache, use last texture path + NIFSKOPE_QSETTINGS(cfg); + QString defaulttexpath(cfg.value("last texture path", QVariant(QDir::homePath())).toString()); + file = QDir(defaulttexpath).filePath(file); + } + + + // to avoid shortcut resolve bug take *.* as text filter + file = QFileDialog::getOpenFileName( 0, "Select a texture file", file, "*.*" ); + + if ( ! file.isEmpty() ) + { + // save path for future + NIFSKOPE_QSETTINGS(cfg); + cfg.setValue("last texture path", QVariant(QDir(file).absolutePath())); + + file = TexCache::stripPath( file, nif->getFolder() ); + nif->set( iSource, "Use External", 1 ); + nif->set( iFile, file.replace( "/", "\\" ) ); + } + return idx; + } +}; + +REGISTER_SPELL( spChooseTexture ) + + +class spEditTexCoords : public Spell +{ +public: + QString name() const { return Spell::tr("Edit UV"); } + QString page() const { return Spell::tr("Texture"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return ( nif->itemName(index) == "NiTriShape" || nif->itemName(index) == "NiTriStrips" ); + + //QModelIndex iUVs = getUV( nif, index ); + //return iUVs.isValid() && nif->rowCount( iUVs ) >= 1; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + UVWidget::createEditor( nif, index ); + return index; + } +}; + +REGISTER_SPELL( spEditTexCoords ) + + +QModelIndex addTexture( NifModel * nif, const QModelIndex & index, const QString & name ) +{ + QModelIndex iTexProp = nif->getBlock( index, "NiTexturingProperty" ); + if ( ! iTexProp.isValid() ) return index; + if ( nif->get( iTexProp, "Texture Count" ) < 7 ) + nif->set( iTexProp, "Texture Count", 7 ); + + nif->set( iTexProp, QString( "Has %1" ).arg( name ), 1 ); + QPersistentModelIndex iTex = nif->getIndex( iTexProp, name ); + if ( ! iTex.isValid() ) return index; + + nif->set( iTex, "Clamp Mode", 3 ); + nif->set( iTex, "Filter Mode", 3 ); + nif->set( iTex, "PS2 K", -75 ); + nif->set( iTex, "Unknown1", 257 ); + + QModelIndex iSrcTex = nif->insertNiBlock( "NiSourceTexture", nif->getBlockNumber( iTexProp ) + 1 ); + nif->setLink( iTex, "Source", nif->getBlockNumber( iSrcTex ) ); + + nif->set( iSrcTex, "Pixel Layout", ( nif->getVersion() == "20.0.0.5" && name == "Base Texture" ? 6 : 5 ) ); + nif->set( iSrcTex, "Use Mipmaps", 2 ); + nif->set( iSrcTex, "Alpha Format", 3 ); + nif->set( iSrcTex, "Unknown Byte", 1 ); + nif->set( iSrcTex, "Unknown Byte 2", 1 ); + nif->set( iSrcTex, "Use External", 1 ); + + return iSrcTex; +} + +class spAddBaseMap : public Spell +{ +public: + QString name() const { return "Add Base Texture"; } + QString page() const { return "Texture"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + return ( block.isValid() && nif->get( block, "Has Base Texture" ) == 0 ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + return addTexture( nif, index, "Base Texture" ); + } +}; + +REGISTER_SPELL( spAddBaseMap ) + +class spAddDarkMap : public Spell +{ +public: + QString name() const { return "Add Dark Map"; } + QString page() const { return "Texture"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + return ( block.isValid() && nif->get( block, "Has Dark Texture" ) == 0 ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + return addTexture( nif, index, "Dark Texture" ); + } +}; + +REGISTER_SPELL( spAddDarkMap ) + +class spAddDetailMap : public Spell +{ +public: + QString name() const { return "Add Detail Map"; } + QString page() const { return "Texture"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + return ( block.isValid() && nif->get( block, "Has Detail Texture" ) == 0 ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + return addTexture( nif, index, "Detail Texture" ); + } +}; + +REGISTER_SPELL( spAddDetailMap ) + +class spAddGlowMap : public Spell +{ +public: + QString name() const { return "Add Glow Map"; } + QString page() const { return "Texture"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex block = nif->getBlock( index, "NiTexturingProperty" ); + return ( block.isValid() && nif->get( block, "Has Glow Texture" ) == 0 ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + return addTexture( nif, index, "Glow Texture" ); + } +}; + +REGISTER_SPELL( spAddGlowMap ) + +#define wrap01f( X ) ( X > 1 ? X - floor( X ) : X < 0 ? X - floor( X ) : X ) + +class spTextureTemplate : public Spell +{ + QString name() const { return "Export Template"; } + QString page() const { return "Texture"; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + QModelIndex iUVs = getUV( nif, index ); + return iUVs.isValid() && nif->rowCount( iUVs ) >= 1; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QModelIndex iUVs = getUV( nif, index ); + if ( nif->rowCount( iUVs ) <= 0 ) + return index; + + // fire up a dialog to set the user parameters + QDialog dlg; + QGridLayout * lay = new QGridLayout; + dlg.setLayout( lay ); + + FileSelector * file = new FileSelector( FileSelector::SaveFile, "File", QBoxLayout::RightToLeft ); + file->setFilter( QStringList() << "*.tga" ); + lay->addWidget( file, 0, 0, 1, 2 ); + + lay->addWidget( new QLabel( "Size" ), 1, 0 ); + QComboBox * size = new QComboBox; + lay->addWidget( size, 1, 1 ); + for ( int i = 6; i < 12; i++ ) + size->addItem( QString::number( 2 << i ) ); + + lay->addWidget( new QLabel( "Coord Set" ), 2, 0 ); + QComboBox * set = new QComboBox; + lay->addWidget( set, 2, 1 ); + for ( int i = 0; i < nif->rowCount( iUVs ); i++ ) + set->addItem( QString( "set %1" ).arg( i ) ); + + lay->addWidget( new QLabel( "Wrap Mode" ), 3, 0 ); + QComboBox * wrap = new QComboBox; + lay->addWidget( wrap, 3, 1 ); + wrap->addItem( "wrap" ); + wrap->addItem( "clamp" ); + + QPushButton * ok = new QPushButton( "Ok" ); + QObject::connect( ok, SIGNAL( clicked() ), &dlg, SLOT( accept() ) ); + lay->addWidget( ok, 4, 0, 1, 2 ); + + NIFSKOPE_QSETTINGS(settings); + settings.beginGroup( "spells" ); + settings.beginGroup( page() ); + settings.beginGroup( name() ); + + wrap->setCurrentIndex( settings.value( "Wrap Mode", 0 ).toInt() ); + size->setCurrentIndex( settings.value( "Image Size", 2 ).toInt() ); + file->setText( settings.value( "File Name", "" ).toString() ); + + if ( dlg.exec() != QDialog::Accepted ) + return index; + + settings.setValue( "Wrap Mode", wrap->currentIndex() ); + settings.setValue( "Image Size", size->currentIndex() ); + settings.setValue( "File Name", file->text() ); + + // get the selected coord set + QModelIndex iSet = iUVs.child( set->currentIndex(), 0 ); + + QVector uv = nif->getArray( iSet ); + QVector tri; + + // get the triangles + QModelIndex iData = getData( nif, index ); + QModelIndex iPoints = nif->getIndex( iData, "Points" ); + if ( iPoints.isValid() ) + { + QList< QVector< quint16 > > strips; + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); + tri = triangulate( strips ); + } + else + { + tri = nif->getArray( nif->getIndex( getData( nif, index ), "Triangles" ) ); + } + + // render the template image + quint16 s = size->currentText().toInt(); + + QImage img( s, s, QImage::Format_RGB32 ); + QPainter pntr( &img ); + + pntr.fillRect( img.rect(), QColor( 0xff, 0xff, 0xff ) ); + pntr.scale( s, s ); + pntr.setPen( QColor( 0x10, 0x20, 0x30 ) ); + + bool wrp = wrap->currentIndex() == 0; + + foreach ( Triangle t, tri ) + { + Vector2 v2[3]; + for ( int i = 0; i < 3; i++ ) + { + v2[i] = uv.value( t[i] ); + if ( wrp ) + { + v2[i][0] = wrap01f( v2[i][0] ); + v2[i][1] = wrap01f( v2[i][1] ); + } + } + + pntr.drawLine( QPointF( v2[0][0], v2[0][1] ), QPointF( v2[1][0], v2[1][1] ) ); + pntr.drawLine( QPointF( v2[1][0], v2[1][1] ), QPointF( v2[2][0], v2[2][1] ) ); + pntr.drawLine( QPointF( v2[2][0], v2[2][1] ), QPointF( v2[0][0], v2[0][1] ) ); + } + + // write the file + QString filename = file->text(); + if ( ! filename.endsWith( ".tga", Qt::CaseInsensitive ) ) + filename.append( ".tga" ); + + quint8 hdr[18]; + for ( int o = 0; o < 18; o++ ) hdr[o] = 0; + hdr[02] = 2; + hdr[12] = s % 256; + hdr[13] = s / 256; + hdr[14] = s % 256; + hdr[15] = s / 256; + hdr[16] = 32; + hdr[17] = 32; // flipV + + QFile f( filename ); + if ( ! f.open( QIODevice::WriteOnly ) || f.write( (char *) hdr, 18 ) != 18 || f.write( (char *) img.bits(), s * s * 4 ) != s * s * 4 ) + qWarning() << "exportTemplate(" << filename << ") : could not write file"; + + return index; + } +}; + +REGISTER_SPELL( spTextureTemplate ) + +class spMultiApplyMode : public Spell +{ + +public: + + QString name() const { return "Multi Apply Mode"; } + QString page() const { return "Batch"; } + + bool isApplicable( const NifModel * nif, const QModelIndex &index ) + { + return nif->checkVersion( 0x14000005, 0x14000005 ) && ! index.isValid(); + } + + QModelIndex cast( NifModel *nif, const QModelIndex &index ) + { + QStringList modes; + modes << "Replace" << "Decal" << "Modulate" << "Hilight" << "Hilight2"; + + QDialog dlg; + dlg.resize( 300, 60 ); + QComboBox *cbRep = new QComboBox( &dlg ); + QComboBox *cbBy = new QComboBox( &dlg ); + QPushButton *btnOk = new QPushButton( "OK", &dlg ); + QPushButton *btnCancel = new QPushButton( "Cancel", &dlg ); + cbRep->addItems( modes ); + cbRep->setCurrentIndex( 2 ); + cbBy->addItems( modes ); + cbBy->setCurrentIndex( 2 ); + + QGridLayout *layout; + layout = new QGridLayout; + layout->setSpacing( 20 ); + layout->addWidget( new QLabel( "Replace", &dlg ), 0, 0, Qt::AlignBottom ); + layout->addWidget( new QLabel( "By", &dlg ), 0, 1, Qt::AlignBottom ); + layout->addWidget( cbRep, 1, 0, Qt::AlignTop ); + layout->addWidget( cbBy, 1, 1, Qt::AlignTop ); + layout->addWidget( btnOk, 2, 0 ); + layout->addWidget( btnCancel, 2, 1 ); + dlg.setLayout( layout ); + + QObject::connect( btnOk, SIGNAL( clicked() ), &dlg, SLOT( accept() ) ); + QObject::connect( btnCancel, SIGNAL( clicked() ), &dlg, SLOT( reject() ) ); + + if ( dlg.exec() != QDialog::Accepted ) + return QModelIndex(); + + replaceApplyMode( nif, index, cbRep->currentIndex(), cbBy->currentIndex() ); + + return QModelIndex(); + } + + void replaceApplyMode( NifModel *nif, const QModelIndex &index, int rep, int by ) + { + if ( !index.isValid() ) + return; + + if ( nif->inherits( index, "NiTexturingProperty" ) && + nif->get( index, "Apply Mode" ) == rep ) + nif->set( index, "Apply Mode", by ); + + QModelIndex iChildren = nif->getIndex( index, "Children" ); + QList lChildren = nif->getChildLinks( nif->getBlockNumber( index ) ); + if ( iChildren.isValid() ) + { + for ( int c = 0; c < nif->rowCount( iChildren ); c++ ) + { + qint32 link = nif->getLink( iChildren.child( c, 0 ) ); + if ( lChildren.contains( link ) ) + { + QModelIndex iChild = nif->getBlock( link ); + replaceApplyMode( nif, iChild, rep, by ); + } + } + } + + QModelIndex iProperties = nif->getIndex( index, "Properties" ); + if ( iProperties.isValid() ) + { + for ( int p = 0; p < nif->rowCount( iProperties ); p++ ) + { + QModelIndex iProp = nif->getBlock( nif->getLink( iProperties.child( p, 0 ) ) ); + replaceApplyMode( nif, iProp, rep, by ); + } + } + } +}; + +REGISTER_SPELL( spMultiApplyMode ) diff --git a/spells/transform.cpp b/spells/transform.cpp index 69ed6a8f7..5f475747d 100644 --- a/spells/transform.cpp +++ b/spells/transform.cpp @@ -1,369 +1,369 @@ -#include "transform.h" - -#include "../widgets/nifeditors.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* XPM */ -static char * transform_xpm[] = { -"64 64 6 1", -" c None", -". c #1800FF", -"+ c #FF0301", -"@ c #C46EBC", -"# c #0DFF00", -"$ c #2BFFAC", -" ", -" ", -" ", -" . ", -" ... ", -" ..... ", -" ....... ", -" ... ", -" ... ", -" ... ", -" ... ", -" ... ", -" ... ", -" ... ", -" ... ", -" ... ", -" ... ## ", -" + ... #### ", -" ++++ ... ##### ", -" +++++ ... ####### ", -" +++++ ... ####### ", -" +++++ ... ##### ", -" +++++ ... ##### ", -" +++++ ... ##### ", -" +++++ ... ##### ", -" +++++ ... ##### ", -" +++++ ... #### ", -" +++++ ... ##### ", -" +++++ ... #### ", -" ++++++ ... ##### ", -" +++++...##### ", -" +++...### ", -" ++...+# ", -" #...++ ", -" ###...++++ ", -" ####...++++++ ", -" ##### ... +++++ ", -" #### ... +++++ ", -" ##### ... ++++++ ", -" ##### ... +++++ ", -" ##### ... +++++ ", -" ##### ... ++++++ ", -" #### ... +++++ ", -" ##### ... +++++ ", -" #### ... ++++++ ", -" ##### ... +++++ + ", -" ##### ... +++++++ ", -" ##### ... +++++++ ", -" ##### ... +++++ ", -" #### ... ++++ ", -" ### ... + ", -" ... ", -" ... ", -" ... ", -" ... ", -" ... ", -" ... ", -" ... ", -" ", -" ", -" ", -" ", -" ", -" "}; - -bool spApplyTransformation::isApplicable( const NifModel * nif, const QModelIndex & index ) -{ - return nif->itemType( index ) == "NiBlock" && ( nif->inherits( nif->itemName( index ), "NiNode" ) - || nif->itemName( index ) == "NiTriShape" || nif->itemName( index ) == "NiTriStrips" ); -} - -QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & index ) -{ - if ( ( nif->getLink( index, "Controller" ) != -1 || nif->getLink( index, "Skin Instance" ) != -1 ) ) - if ( QMessageBox::question( 0, Spell::tr("Apply Transformation"), Spell::tr("On animated and or skinned nodes Apply Transformation most likely won't work the way you expected it."), Spell::tr("Try anyway"), Spell::tr("Cancel") ) != 0 ) - return index; - - if ( nif->inherits( nif->itemName( index ), "NiNode" ) ) - { - Transform tp( nif, index ); - bool ok = false; - foreach ( int l, nif->getChildLinks( nif->getBlockNumber( index ) ) ) - { - QModelIndex iChild = nif->getBlock( l ); - if ( iChild.isValid() && nif->inherits( nif->itemName( iChild ), "NiAVObject" ) ) - { - Transform tc( nif, iChild ); - tc = tp * tc; - tc.writeBack( nif, iChild ); - ok = true; - } - } - if ( ok ) - { - tp = Transform(); - tp.writeBack( nif, index ); - } - } - else - { - QModelIndex iData; - if ( nif->itemName( index ) == "NiTriShape") - iData = nif->getBlock( nif->getLink( index, "Data" ), "NiTriShapeData" ); - else if ( nif->itemName( index ) == "NiTriStrips" ) - iData = nif->getBlock( nif->getLink( index, "Data" ), "NiTriStripsData" ); - - if ( iData.isValid() ) - { - Transform t( nif, index ); - QModelIndex iVertices = nif->getIndex( iData, "Vertices" ); - if ( iVertices.isValid() ) - { - QVector a = nif->getArray( iVertices ); - for ( int v = 0; v < nif->rowCount( iVertices ); v++ ) - a[v] = t * a[v]; - nif->setArray( iVertices, a ); - - QModelIndex iNormals = nif->getIndex( iData, "Normals" ); - if ( iNormals.isValid() ) - { - a = nif->getArray( iNormals ); - for ( int n = 0; n < nif->rowCount( iNormals ); n++ ) - a[n] = t.rotation * a[n]; - nif->setArray( iNormals, a ); - } - } - QModelIndex iCenter = nif->getIndex( iData, "Center" ); - if ( iCenter.isValid() ) - nif->set( iCenter, t * nif->get( iCenter ) ); - QModelIndex iRadius = nif->getIndex( iData, "Radius" ); - if ( iRadius.isValid() ) - nif->set( iRadius, t.scale * nif->get( iRadius ) ); - t = Transform(); - t.writeBack( nif, index ); - } - } - return index; -} - -REGISTER_SPELL( spApplyTransformation ) - -class spClearTransformation : public Spell -{ -public: - QString name() const { return Spell::tr("Clear"); } - QString page() const { return Spell::tr("Transform"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return Transform::canConstruct( nif, index ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - Transform tp; - tp.writeBack( nif, index ); - return index; - } -}; - -REGISTER_SPELL( spClearTransformation ) - -class spCopyTransformation : public Spell -{ -public: - QString name() const { return Spell::tr("Copy"); } - QString page() const { return Spell::tr("Transform"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return Transform::canConstruct( nif, index ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QByteArray data; - QBuffer buffer( & data ); - if ( buffer.open( QIODevice::WriteOnly ) ) - { - QDataStream ds( &buffer ); - ds << Transform( nif, index ); - - QMimeData * mime = new QMimeData; - mime->setData( QString( "nifskope/transform" ), data ); - QApplication::clipboard()->setMimeData( mime ); - } - return index; - } -}; - -REGISTER_SPELL( spCopyTransformation ) - -class spPasteTransformation : public Spell -{ -public: - QString name() const { return Spell::tr("Paste"); } - QString page() const { return Spell::tr("Transform"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - const QMimeData * mime = QApplication::clipboard()->mimeData(); - if ( Transform::canConstruct( nif, index ) && mime ) - foreach ( QString form, mime->formats() ) - if ( form == "nifskope/transform" ) - return true; - - return false; - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - const QMimeData * mime = QApplication::clipboard()->mimeData(); - if ( mime ) - { - foreach ( QString form, mime->formats() ) - { - if ( form == "nifskope/transform" ) - { - QByteArray data = mime->data( form ); - QBuffer buffer( & data ); - if ( buffer.open( QIODevice::ReadOnly ) ) - { - QDataStream ds( &buffer ); - Transform t; - ds >> t; - t.writeBack( nif, index ); - return index; - } - } - } - } - return index; - } -}; - -REGISTER_SPELL( spPasteTransformation ) - -QIcon * transform_xpm_icon = 0; - -class spEditTransformation : public Spell -{ -public: - QString name() const { return Spell::tr("Edit"); } - QString page() const { return Spell::tr("Transform"); } - bool instant() const { return true; } - QIcon icon() const - { - if ( ! transform_xpm_icon ) - transform_xpm_icon = new QIcon( transform_xpm ); - return *transform_xpm_icon; - } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - if ( Transform::canConstruct( nif, index ) ) - return true; - - QModelIndex iTransform = nif->getIndex( index, "Transform" ); - if ( ! iTransform.isValid() ) - iTransform = index; - - return ( nif->getValue( iTransform ).type() == NifValue::tMatrix4 ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - NifBlockEditor * edit = new NifBlockEditor( nif, nif->getBlock( index ) ); - if ( Transform::canConstruct( nif, index ) ) - { - edit->add( new NifVectorEdit( nif, nif->getIndex( index, "Translation" ) ) ); - edit->add( new NifRotationEdit( nif, nif->getIndex( index, "Rotation" ) ) ); - edit->add( new NifFloatEdit( nif, nif->getIndex( index, "Scale" ) ) ); - } - else - { - QModelIndex iTransform = nif->getIndex( index, "Transform" ); - if ( ! iTransform.isValid() ) - iTransform = index; - edit->add( new NifMatrix4Edit( nif, iTransform ) ); - } - edit->show(); - return index; - } -}; - -REGISTER_SPELL( spEditTransformation ) - - -class spScaleVertices : public Spell -{ -public: - QString name() const { return Spell::tr( "Scale Vertices" ); } - QString page() const { return Spell::tr( "Transform" ); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ) - { - return nif->inherits( index, "NiGeometry" ); - } - - QModelIndex cast( NifModel * nif, const QModelIndex & index ) - { - QDialog dlg; - - QGridLayout * grid = new QGridLayout( &dlg ); - - QList scale; - - for ( int a = 0; a < 3; a++ ) - { - QDoubleSpinBox * spn = new QDoubleSpinBox; - scale << spn; - spn->setValue( 1.0 ); - spn->setDecimals( 4 ); - spn->setRange( -10e+4, 10e+4 ); - grid->addWidget( new QLabel( ( QStringList() << "X" << "Y" << "Z" ).value( a ) ), a, 0 ); - grid->addWidget( spn, a, 1 ); - } - - QPushButton * btScale = new QPushButton( Spell::tr( "Scale" ) ); - grid->addWidget( btScale, 3, 0, 1, 2 ); - QObject::connect( btScale, SIGNAL( clicked() ), &dlg, SLOT( accept() ) ); - - if ( dlg.exec() != QDialog::Accepted ) - return QModelIndex(); - - QModelIndex iData = nif->getBlock( nif->getLink( nif->getBlock( index ), "Data" ), "NiGeometryData" ); - - QVector vertices = nif->getArray( iData, "Vertices" ); - QMutableVectorIterator it( vertices ); - while ( it.hasNext() ) - { - Vector3 & v = it.next(); - - for ( int a = 0; a < 3; a++ ) - v[a] *= scale[a]->value(); - } - nif->setArray( iData, "Vertices", vertices ); - - return QModelIndex(); - } -}; - -REGISTER_SPELL( spScaleVertices ) - - +#include "transform.h" + +#include "../widgets/nifeditors.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* XPM */ +static char * transform_xpm[] = { +"64 64 6 1", +" c None", +". c #1800FF", +"+ c #FF0301", +"@ c #C46EBC", +"# c #0DFF00", +"$ c #2BFFAC", +" ", +" ", +" ", +" . ", +" ... ", +" ..... ", +" ....... ", +" ... ", +" ... ", +" ... ", +" ... ", +" ... ", +" ... ", +" ... ", +" ... ", +" ... ", +" ... ## ", +" + ... #### ", +" ++++ ... ##### ", +" +++++ ... ####### ", +" +++++ ... ####### ", +" +++++ ... ##### ", +" +++++ ... ##### ", +" +++++ ... ##### ", +" +++++ ... ##### ", +" +++++ ... ##### ", +" +++++ ... #### ", +" +++++ ... ##### ", +" +++++ ... #### ", +" ++++++ ... ##### ", +" +++++...##### ", +" +++...### ", +" ++...+# ", +" #...++ ", +" ###...++++ ", +" ####...++++++ ", +" ##### ... +++++ ", +" #### ... +++++ ", +" ##### ... ++++++ ", +" ##### ... +++++ ", +" ##### ... +++++ ", +" ##### ... ++++++ ", +" #### ... +++++ ", +" ##### ... +++++ ", +" #### ... ++++++ ", +" ##### ... +++++ + ", +" ##### ... +++++++ ", +" ##### ... +++++++ ", +" ##### ... +++++ ", +" #### ... ++++ ", +" ### ... + ", +" ... ", +" ... ", +" ... ", +" ... ", +" ... ", +" ... ", +" ... ", +" ", +" ", +" ", +" ", +" ", +" "}; + +bool spApplyTransformation::isApplicable( const NifModel * nif, const QModelIndex & index ) +{ + return nif->itemType( index ) == "NiBlock" && ( nif->inherits( nif->itemName( index ), "NiNode" ) + || nif->itemName( index ) == "NiTriShape" || nif->itemName( index ) == "NiTriStrips" ); +} + +QModelIndex spApplyTransformation::cast( NifModel * nif, const QModelIndex & index ) +{ + if ( ( nif->getLink( index, "Controller" ) != -1 || nif->getLink( index, "Skin Instance" ) != -1 ) ) + if ( QMessageBox::question( 0, Spell::tr("Apply Transformation"), Spell::tr("On animated and or skinned nodes Apply Transformation most likely won't work the way you expected it."), Spell::tr("Try anyway"), Spell::tr("Cancel") ) != 0 ) + return index; + + if ( nif->inherits( nif->itemName( index ), "NiNode" ) ) + { + Transform tp( nif, index ); + bool ok = false; + foreach ( int l, nif->getChildLinks( nif->getBlockNumber( index ) ) ) + { + QModelIndex iChild = nif->getBlock( l ); + if ( iChild.isValid() && nif->inherits( nif->itemName( iChild ), "NiAVObject" ) ) + { + Transform tc( nif, iChild ); + tc = tp * tc; + tc.writeBack( nif, iChild ); + ok = true; + } + } + if ( ok ) + { + tp = Transform(); + tp.writeBack( nif, index ); + } + } + else + { + QModelIndex iData; + if ( nif->itemName( index ) == "NiTriShape") + iData = nif->getBlock( nif->getLink( index, "Data" ), "NiTriShapeData" ); + else if ( nif->itemName( index ) == "NiTriStrips" ) + iData = nif->getBlock( nif->getLink( index, "Data" ), "NiTriStripsData" ); + + if ( iData.isValid() ) + { + Transform t( nif, index ); + QModelIndex iVertices = nif->getIndex( iData, "Vertices" ); + if ( iVertices.isValid() ) + { + QVector a = nif->getArray( iVertices ); + for ( int v = 0; v < nif->rowCount( iVertices ); v++ ) + a[v] = t * a[v]; + nif->setArray( iVertices, a ); + + QModelIndex iNormals = nif->getIndex( iData, "Normals" ); + if ( iNormals.isValid() ) + { + a = nif->getArray( iNormals ); + for ( int n = 0; n < nif->rowCount( iNormals ); n++ ) + a[n] = t.rotation * a[n]; + nif->setArray( iNormals, a ); + } + } + QModelIndex iCenter = nif->getIndex( iData, "Center" ); + if ( iCenter.isValid() ) + nif->set( iCenter, t * nif->get( iCenter ) ); + QModelIndex iRadius = nif->getIndex( iData, "Radius" ); + if ( iRadius.isValid() ) + nif->set( iRadius, t.scale * nif->get( iRadius ) ); + t = Transform(); + t.writeBack( nif, index ); + } + } + return index; +} + +REGISTER_SPELL( spApplyTransformation ) + +class spClearTransformation : public Spell +{ +public: + QString name() const { return Spell::tr("Clear"); } + QString page() const { return Spell::tr("Transform"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return Transform::canConstruct( nif, index ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + Transform tp; + tp.writeBack( nif, index ); + return index; + } +}; + +REGISTER_SPELL( spClearTransformation ) + +class spCopyTransformation : public Spell +{ +public: + QString name() const { return Spell::tr("Copy"); } + QString page() const { return Spell::tr("Transform"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return Transform::canConstruct( nif, index ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QByteArray data; + QBuffer buffer( & data ); + if ( buffer.open( QIODevice::WriteOnly ) ) + { + QDataStream ds( &buffer ); + ds << Transform( nif, index ); + + QMimeData * mime = new QMimeData; + mime->setData( QString( "nifskope/transform" ), data ); + QApplication::clipboard()->setMimeData( mime ); + } + return index; + } +}; + +REGISTER_SPELL( spCopyTransformation ) + +class spPasteTransformation : public Spell +{ +public: + QString name() const { return Spell::tr("Paste"); } + QString page() const { return Spell::tr("Transform"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + const QMimeData * mime = QApplication::clipboard()->mimeData(); + if ( Transform::canConstruct( nif, index ) && mime ) + foreach ( QString form, mime->formats() ) + if ( form == "nifskope/transform" ) + return true; + + return false; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + const QMimeData * mime = QApplication::clipboard()->mimeData(); + if ( mime ) + { + foreach ( QString form, mime->formats() ) + { + if ( form == "nifskope/transform" ) + { + QByteArray data = mime->data( form ); + QBuffer buffer( & data ); + if ( buffer.open( QIODevice::ReadOnly ) ) + { + QDataStream ds( &buffer ); + Transform t; + ds >> t; + t.writeBack( nif, index ); + return index; + } + } + } + } + return index; + } +}; + +REGISTER_SPELL( spPasteTransformation ) + +QIcon * transform_xpm_icon = 0; + +class spEditTransformation : public Spell +{ +public: + QString name() const { return Spell::tr("Edit"); } + QString page() const { return Spell::tr("Transform"); } + bool instant() const { return true; } + QIcon icon() const + { + if ( ! transform_xpm_icon ) + transform_xpm_icon = new QIcon( transform_xpm ); + return *transform_xpm_icon; + } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + if ( Transform::canConstruct( nif, index ) ) + return true; + + QModelIndex iTransform = nif->getIndex( index, "Transform" ); + if ( ! iTransform.isValid() ) + iTransform = index; + + return ( nif->getValue( iTransform ).type() == NifValue::tMatrix4 ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + NifBlockEditor * edit = new NifBlockEditor( nif, nif->getBlock( index ) ); + if ( Transform::canConstruct( nif, index ) ) + { + edit->add( new NifVectorEdit( nif, nif->getIndex( index, "Translation" ) ) ); + edit->add( new NifRotationEdit( nif, nif->getIndex( index, "Rotation" ) ) ); + edit->add( new NifFloatEdit( nif, nif->getIndex( index, "Scale" ) ) ); + } + else + { + QModelIndex iTransform = nif->getIndex( index, "Transform" ); + if ( ! iTransform.isValid() ) + iTransform = index; + edit->add( new NifMatrix4Edit( nif, iTransform ) ); + } + edit->show(); + return index; + } +}; + +REGISTER_SPELL( spEditTransformation ) + + +class spScaleVertices : public Spell +{ +public: + QString name() const { return Spell::tr( "Scale Vertices" ); } + QString page() const { return Spell::tr( "Transform" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) + { + return nif->inherits( index, "NiGeometry" ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) + { + QDialog dlg; + + QGridLayout * grid = new QGridLayout( &dlg ); + + QList scale; + + for ( int a = 0; a < 3; a++ ) + { + QDoubleSpinBox * spn = new QDoubleSpinBox; + scale << spn; + spn->setValue( 1.0 ); + spn->setDecimals( 4 ); + spn->setRange( -10e+4, 10e+4 ); + grid->addWidget( new QLabel( ( QStringList() << "X" << "Y" << "Z" ).value( a ) ), a, 0 ); + grid->addWidget( spn, a, 1 ); + } + + QPushButton * btScale = new QPushButton( Spell::tr( "Scale" ) ); + grid->addWidget( btScale, 3, 0, 1, 2 ); + QObject::connect( btScale, SIGNAL( clicked() ), &dlg, SLOT( accept() ) ); + + if ( dlg.exec() != QDialog::Accepted ) + return QModelIndex(); + + QModelIndex iData = nif->getBlock( nif->getLink( nif->getBlock( index ), "Data" ), "NiGeometryData" ); + + QVector vertices = nif->getArray( iData, "Vertices" ); + QMutableVectorIterator it( vertices ); + while ( it.hasNext() ) + { + Vector3 & v = it.next(); + + for ( int a = 0; a < 3; a++ ) + v[a] *= scale[a]->value(); + } + nif->setArray( iData, "Vertices", vertices ); + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spScaleVertices ) + + diff --git a/spells/transform.h b/spells/transform.h index 4ab504a26..ee9448ffa 100644 --- a/spells/transform.h +++ b/spells/transform.h @@ -1,16 +1,16 @@ -#ifndef SP_TRANSFORM_H -#define SP_TRANSFORM_H - -#include "../spellbook.h" - -class spApplyTransformation : public Spell -{ -public: - QString name() const { return Spell::tr("Apply"); } - QString page() const { return Spell::tr("Transform"); } - - bool isApplicable( const NifModel * nif, const QModelIndex & index ); - QModelIndex cast( NifModel * nif, const QModelIndex & index ); -}; - -#endif +#ifndef SP_TRANSFORM_H +#define SP_TRANSFORM_H + +#include "../spellbook.h" + +class spApplyTransformation : public Spell +{ +public: + QString name() const { return Spell::tr("Apply"); } + QString page() const { return Spell::tr("Transform"); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ); + QModelIndex cast( NifModel * nif, const QModelIndex & index ); +}; + +#endif diff --git a/widgets/colorwheel.cpp b/widgets/colorwheel.cpp index 6ac4325ed..837cac8ea 100644 --- a/widgets/colorwheel.cpp +++ b/widgets/colorwheel.cpp @@ -1,376 +1,376 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "colorwheel.h" -#include "floatslider.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../niftypes.h" - -/* XPM */ -static const char * const hsv42_xpm[] = { -"32 32 43 1", -" c None", -". c #3200FF","+ c #0024FF","@ c #7600FF","# c #0045FF","$ c #A000FF", -"% c #FF0704","& c #015AFF","* c #FF0031","= c #FF0058","- c #FF0074", -"; c #FF0086","> c #006DFF",", c #D700FF","' c #FF00A3",") c #FF00C5", -"! c #FC00DE","~ c #FC00FA","{ c #0086FF","] c #FF3700","^ c #00A7FF", -"/ c #FF6000","( c #00C3FF","_ c #00FF32",": c #00DDFF","< c #FF8D00", -"[ c #00FF55","} c #23FF00","| c #00FF72","1 c #00FF88","2 c #00FFA0", -"3 c #00FFBB","4 c #00FFD7","5 c #00F9F8","6 c #FFA900","7 c #60FF00", -"8 c #83FF00","9 c #FFC200","0 c #A3FF00","a c #FFD800","b c #C0FF00", -"c c #FBF100","d c #E6FF00", -" 7778800b ", -" }}}}778800bbdd ", -" }}}}}}777800bbddcc ", -" }}}}}7}778800bdddcca ", -" __}}}}}}77880bbddccca9 ", -" [___}}}}}}7780bbddcaa996 ", -" [[[___}}}}77880bddccaa9666 ", -" ||[[[___}}}}7780bddca9966<< ", -" |1|[[[__}}}}7880bdcca966<<&+.@~)';-===*******", -":(((((^^^{{>#+..@,~)';;-====*=* ", -" (^^^^^{{>>#+..@@,~!)'';;--==== ", -" ^^^^{{{>&##+...$$,~!)'';;---== ", -" ^^{{{>>&##....@@$,,~!))'';;-- ", -" {{>>>&#++....@$$,,~~!)'''';; ", -" {>>&&##++....@@$,,~~!!))''' ", -" >&&##++.....@@$$,,~~!)))'' ", -" &#+++......@@$$,,~~~!))) ", -" #+++.....@@@$$$,,~~!!! ", -" ++.......@$$$,,,~~~! ", -" ......@@@$$$$,,~ ", -" ......@@$$,,, ", -" .@@@@$$ "}; - -#include - -QIcon * ColorWheel::icon = 0; - -ColorWheel::ColorWheel( QWidget * parent ) : QWidget( parent ) -{ - H = 0.0; S = 0.0; V = 1.0; -} - -ColorWheel::ColorWheel( const QColor & c, QWidget * parent ) : QWidget( parent ) -{ - H = c.hueF(); - S = c.saturationF(); - V = c.valueF(); - if ( H >= 1.0 || H < 0.0 ) H = 0.0; - if ( S > 1.0 || S < 0.0 ) S = 1.0; - if ( V > 1.0 || S < 0.0 ) V = 1.0; -} - -QIcon ColorWheel::getIcon() -{ - if ( ! icon ) - icon = new QIcon( hsv42_xpm ); - return *icon; -} - -QColor ColorWheel::getColor() const -{ - return QColor::fromHsvF( H, S, V ); -} - -void ColorWheel::setColor( const QColor & c ) -{ - double h = c.hueF(); - S = c.saturationF(); - V = c.valueF(); - if ( h >= 1.0 || h < 0.0 ) h = 0.0; - if ( S > 1.0 || S < 0.0 ) S = 1.0; - if ( V > 1.0 || S < 0.0 ) V = 1.0; - if ( h != 0.0 ) H = h; - update(); - emit sigColor( c ); -} - -QSize ColorWheel::sizeHint() const -{ - if ( sHint.isValid() ) - return sHint; - else - return QSize( 250, 250 ); -} - -void ColorWheel::setSizeHint( const QSize & s ) -{ - sHint = s; -} - -QSize ColorWheel::minimumSizeHint() const -{ - return QSize( 50, 50 ); -} - -int ColorWheel::heightForWidth( int width ) const -{ - if ( width < minimumSizeHint().height() ) - return minimumSizeHint().height(); - return width; -} - -#ifndef pi -#define pi 3.1416 -#endif - -void ColorWheel::paintEvent( QPaintEvent * e ) -{ - double s = qMin( width(), height() ) / 2.0; - double c = s - s / 5; - - QPainter p( this ); - p.translate( width() / 2, height() / 2 ); - - p.setPen( Qt::NoPen ); - - QConicalGradient cgrad( QPointF( 0, 0 ), 90 ); - cgrad.setColorAt( 0.00, QColor::fromHsvF( 0.0, 1.0, 1.0 ) ); - for ( double d = 0.01; d < 1.00; d += 0.01 ) - cgrad.setColorAt( d, QColor::fromHsvF( d, 1.0, 1.0 ) ); - cgrad.setColorAt( 1.00, QColor::fromHsvF( 0.0, 1.0, 1.0 ) ); - - p.setBrush( QBrush( cgrad ) ); - p.drawEllipse( QRectF( -s, -s, s*2, s*2 ) ); - p.setBrush( palette().color( QPalette::Background ) ); - p.drawEllipse( QRectF( -c, -c, c*2, c*2 ) ); - - double x = ( H - 0.5 ) * 2 * pi; - - QPointF points[3]; - points[0] = QPointF( sin( x ) * c, cos( x ) * c ); - points[1] = QPointF( sin( x + 2 * pi / 3 ) * c, cos( x + 2 * pi / 3 ) * c ); - points[2] = QPointF( sin( x + 4 * pi / 3 ) * c, cos( x + 4 * pi / 3 ) * c ); - - QColor colors[3][2]; - colors[0][0] = QColor::fromHsvF( H, 1.0, 1.0, 1.0 ); - colors[0][1] = QColor::fromHsvF( H, 0.0, 0.0, 0.0 ); - colors[1][0] = QColor::fromHsvF( H, 0.0, 0.0, 1.0 ); - colors[1][1] = QColor::fromHsvF( H, 0.0, 0.0, 0.0 ); - colors[2][0] = QColor::fromHsvF( H, 0.0, 1.0, 1.0 ); - colors[2][1] = QColor::fromHsvF( H, 0.0, 1.0, 0.0 ); - - - p.setBrush( QColor::fromHsvF( H, 0.0, .5 ) ); - p.drawPolygon( points, 3 ); - - QLinearGradient lgrad( points[0], ( points[1]+points[2] ) / 2 ); - lgrad.setColorAt( 0.0, colors[0][0] ); - lgrad.setColorAt( 1.0, colors[0][1] ); - p.setBrush( lgrad ); - p.drawPolygon( points, 3 ); - - lgrad = QLinearGradient( points[1], ( points[0]+points[2] ) / 2 ); - lgrad.setColorAt( 0.0, colors[1][0] ); - lgrad.setColorAt( 1.0, colors[1][1] ); - p.setBrush( lgrad ); - p.drawPolygon( points, 3 ); - - lgrad = QLinearGradient( points[2], ( points[0]+points[1] ) / 2 ); - lgrad.setColorAt( 0.0, colors[2][0] ); - lgrad.setColorAt( 1.0, colors[2][1] ); - p.setBrush( lgrad ); - p.drawPolygon( points, 3 ); - - p.setPen( QColor::fromHsvF( H, 0.0, 1.0, 0.8 ) ); - p.setBrush( QColor::fromHsvF( H, 1.0, 1.0, 1.0 ) ); - - double z = (s-c)/2; - QPointF pointH( sin( x ) * ( c + z ), cos( x ) * ( c + z ) ); - p.drawEllipse( QRectF( pointH - QPointF( z/2, z/2 ), QSizeF( z, z ) ) ); - - p.setBrush( QColor::fromHsvF( H, S, V, 1.0 ) ); - p.rotate( ( 1.0 - H ) * 360 - 120 ); - QPointF pointSV( ( S - 0.5 ) * sqrt( c*c - c*c/4 ) * 2 * V, V * ( c + c/2 ) - c ); - p.drawEllipse( QRectF( pointSV - QPointF( z/2, z/2 ), QSizeF( z, z ) ) ); -} - -void ColorWheel::mousePressEvent( QMouseEvent * e ) -{ - if ( e->button() != Qt::LeftButton ) - return; - - double dx = abs( e->x() - width() / 2 ); - double dy = abs( e->y() - height() / 2 ); - double d = sqrt( dx*dx + dy*dy ); - - double s = qMin( width(), height() ) / 2; - double c = s - s / 5; - - if ( d > s ) - pressed = Nope; - else if ( d > c ) - pressed = Circle; - else - pressed = Triangle; - - setColor( e->x(), e->y() ); -} - -void ColorWheel::mouseMoveEvent( QMouseEvent * e ) -{ - if ( e->buttons() & Qt::LeftButton ) - setColor( e->x(), e->y() ); -} - -void ColorWheel::contextMenuEvent( QContextMenuEvent * e ) -{ - QMenu * menu = new QMenu( this ); - - foreach ( QString name, QColor::colorNames() ) - { - QAction * act = new QAction( menu ); - act->setText( name ); - QPixmap pix( 16, 16 ); - QPainter paint( &pix ); - paint.setBrush( QColor( name ) ); - paint.setPen( Qt::black ); - paint.drawRect( pix.rect().adjusted( 0, 0, -1, -1 ) ); - act->setIcon( QIcon( pix ) ); - menu->addAction( act ); - } - - if ( QAction * act = menu->exec( e->globalPos() ) ) - { - setColor( QColor( act->text() ) ); - emit sigColorEdited( getColor() ); - } - - delete menu; -} - -void ColorWheel::setColor( int x, int y ) -{ - if ( pressed == Circle ) - { - QLineF l( QPointF( width() / 2.0, height() / 2.0 ), QPointF( x, y ) ); - H = l.angle( QLineF( 0, 1, 0, 0 ) ) / 360.0; - if ( l.dx() > 0 ) H = 1.0 - H; - update(); - emit sigColor( getColor() ); - emit sigColorEdited( getColor() ); - } - else if ( pressed == Triangle ) - { - QPointF mp( x - width() / 2, y - height() / 2 ); - - QMatrix m; - m.rotate( (H ) * 360.0 + 120 ); - QPointF p( m.map( mp ) ); - double c = qMin( width(), height() ) / 2; - c -= c / 5; - V = ( p.y() + c ) / ( c + c/2 ); - if ( V < 0.0 ) V = 0; - if ( V > 1.0 ) V = 1.0; - if ( V > 0 ) - { - double h = V * ( c + c/2 ) / ( 2.0 * sin( 60.0 / 180.0 * pi ) ); - S = ( p.x() + h ) / ( h * 2 ); - if ( S < 0.0 ) S = 0.0; - if ( S > 1.0 ) S = 1.0; - } - else - S = 1.0; - update(); - emit sigColor( getColor() ); - emit sigColorEdited( getColor() ); - } -} - -QColor ColorWheel::choose( const QColor & c, bool alphaEnable, QWidget * parent ) -{ - QDialog dlg( parent ); - dlg.setWindowTitle( "Choose a Color" ); - QGridLayout * grid = new QGridLayout; - dlg.setLayout( grid ); - - ColorWheel * hsv = new ColorWheel; - hsv->setColor( c ); - grid->addWidget( hsv, 0, 0, 1, 2 ); - - AlphaSlider * alpha = new AlphaSlider; - alpha->setColor( c ); - alpha->setValue( c.alphaF() ); - alpha->setOrientation( Qt::Vertical ); - alpha->setVisible( alphaEnable ); - grid->addWidget( alpha, 0, 2 ); - connect( hsv, SIGNAL( sigColor( const QColor & ) ), alpha, SLOT( setColor( const QColor & ) ) ); - - QHBoxLayout * hbox = new QHBoxLayout; - grid->addLayout( hbox, 1, 0, 1, 3 ); - QPushButton * ok = new QPushButton( "ok" ); - hbox->addWidget( ok ); - QPushButton * cancel = new QPushButton( "cancel" ); - hbox->addWidget( cancel ); - connect( ok, SIGNAL( clicked() ), &dlg, SLOT( accept() ) ); - connect( cancel, SIGNAL( clicked() ), &dlg, SLOT( reject() ) ); - - if ( dlg.exec() == QDialog::Accepted ) - { - QColor color = hsv->getColor(); - color.setAlphaF( alpha->value() ); - return color; - } - else - return c; -} - -Color3 ColorWheel::choose( const Color3 & c, QWidget * parent ) -{ - return Color3( choose( c.toQColor(), false, parent ) ); -} - -Color4 ColorWheel::choose( const Color4 & c, QWidget * parent ) -{ - return Color4( choose( c.toQColor(), true, parent ) ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "colorwheel.h" +#include "floatslider.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../niftypes.h" + +/* XPM */ +static const char * const hsv42_xpm[] = { +"32 32 43 1", +" c None", +". c #3200FF","+ c #0024FF","@ c #7600FF","# c #0045FF","$ c #A000FF", +"% c #FF0704","& c #015AFF","* c #FF0031","= c #FF0058","- c #FF0074", +"; c #FF0086","> c #006DFF",", c #D700FF","' c #FF00A3",") c #FF00C5", +"! c #FC00DE","~ c #FC00FA","{ c #0086FF","] c #FF3700","^ c #00A7FF", +"/ c #FF6000","( c #00C3FF","_ c #00FF32",": c #00DDFF","< c #FF8D00", +"[ c #00FF55","} c #23FF00","| c #00FF72","1 c #00FF88","2 c #00FFA0", +"3 c #00FFBB","4 c #00FFD7","5 c #00F9F8","6 c #FFA900","7 c #60FF00", +"8 c #83FF00","9 c #FFC200","0 c #A3FF00","a c #FFD800","b c #C0FF00", +"c c #FBF100","d c #E6FF00", +" 7778800b ", +" }}}}778800bbdd ", +" }}}}}}777800bbddcc ", +" }}}}}7}778800bdddcca ", +" __}}}}}}77880bbddccca9 ", +" [___}}}}}}7780bbddcaa996 ", +" [[[___}}}}77880bddccaa9666 ", +" ||[[[___}}}}7780bddca9966<< ", +" |1|[[[__}}}}7880bdcca966<<&+.@~)';-===*******", +":(((((^^^{{>#+..@,~)';;-====*=* ", +" (^^^^^{{>>#+..@@,~!)'';;--==== ", +" ^^^^{{{>&##+...$$,~!)'';;---== ", +" ^^{{{>>&##....@@$,,~!))'';;-- ", +" {{>>>&#++....@$$,,~~!)'''';; ", +" {>>&&##++....@@$,,~~!!))''' ", +" >&&##++.....@@$$,,~~!)))'' ", +" &#+++......@@$$,,~~~!))) ", +" #+++.....@@@$$$,,~~!!! ", +" ++.......@$$$,,,~~~! ", +" ......@@@$$$$,,~ ", +" ......@@$$,,, ", +" .@@@@$$ "}; + +#include + +QIcon * ColorWheel::icon = 0; + +ColorWheel::ColorWheel( QWidget * parent ) : QWidget( parent ) +{ + H = 0.0; S = 0.0; V = 1.0; +} + +ColorWheel::ColorWheel( const QColor & c, QWidget * parent ) : QWidget( parent ) +{ + H = c.hueF(); + S = c.saturationF(); + V = c.valueF(); + if ( H >= 1.0 || H < 0.0 ) H = 0.0; + if ( S > 1.0 || S < 0.0 ) S = 1.0; + if ( V > 1.0 || S < 0.0 ) V = 1.0; +} + +QIcon ColorWheel::getIcon() +{ + if ( ! icon ) + icon = new QIcon( hsv42_xpm ); + return *icon; +} + +QColor ColorWheel::getColor() const +{ + return QColor::fromHsvF( H, S, V ); +} + +void ColorWheel::setColor( const QColor & c ) +{ + double h = c.hueF(); + S = c.saturationF(); + V = c.valueF(); + if ( h >= 1.0 || h < 0.0 ) h = 0.0; + if ( S > 1.0 || S < 0.0 ) S = 1.0; + if ( V > 1.0 || S < 0.0 ) V = 1.0; + if ( h != 0.0 ) H = h; + update(); + emit sigColor( c ); +} + +QSize ColorWheel::sizeHint() const +{ + if ( sHint.isValid() ) + return sHint; + else + return QSize( 250, 250 ); +} + +void ColorWheel::setSizeHint( const QSize & s ) +{ + sHint = s; +} + +QSize ColorWheel::minimumSizeHint() const +{ + return QSize( 50, 50 ); +} + +int ColorWheel::heightForWidth( int width ) const +{ + if ( width < minimumSizeHint().height() ) + return minimumSizeHint().height(); + return width; +} + +#ifndef pi +#define pi 3.1416 +#endif + +void ColorWheel::paintEvent( QPaintEvent * e ) +{ + double s = qMin( width(), height() ) / 2.0; + double c = s - s / 5; + + QPainter p( this ); + p.translate( width() / 2, height() / 2 ); + + p.setPen( Qt::NoPen ); + + QConicalGradient cgrad( QPointF( 0, 0 ), 90 ); + cgrad.setColorAt( 0.00, QColor::fromHsvF( 0.0, 1.0, 1.0 ) ); + for ( double d = 0.01; d < 1.00; d += 0.01 ) + cgrad.setColorAt( d, QColor::fromHsvF( d, 1.0, 1.0 ) ); + cgrad.setColorAt( 1.00, QColor::fromHsvF( 0.0, 1.0, 1.0 ) ); + + p.setBrush( QBrush( cgrad ) ); + p.drawEllipse( QRectF( -s, -s, s*2, s*2 ) ); + p.setBrush( palette().color( QPalette::Background ) ); + p.drawEllipse( QRectF( -c, -c, c*2, c*2 ) ); + + double x = ( H - 0.5 ) * 2 * pi; + + QPointF points[3]; + points[0] = QPointF( sin( x ) * c, cos( x ) * c ); + points[1] = QPointF( sin( x + 2 * pi / 3 ) * c, cos( x + 2 * pi / 3 ) * c ); + points[2] = QPointF( sin( x + 4 * pi / 3 ) * c, cos( x + 4 * pi / 3 ) * c ); + + QColor colors[3][2]; + colors[0][0] = QColor::fromHsvF( H, 1.0, 1.0, 1.0 ); + colors[0][1] = QColor::fromHsvF( H, 0.0, 0.0, 0.0 ); + colors[1][0] = QColor::fromHsvF( H, 0.0, 0.0, 1.0 ); + colors[1][1] = QColor::fromHsvF( H, 0.0, 0.0, 0.0 ); + colors[2][0] = QColor::fromHsvF( H, 0.0, 1.0, 1.0 ); + colors[2][1] = QColor::fromHsvF( H, 0.0, 1.0, 0.0 ); + + + p.setBrush( QColor::fromHsvF( H, 0.0, .5 ) ); + p.drawPolygon( points, 3 ); + + QLinearGradient lgrad( points[0], ( points[1]+points[2] ) / 2 ); + lgrad.setColorAt( 0.0, colors[0][0] ); + lgrad.setColorAt( 1.0, colors[0][1] ); + p.setBrush( lgrad ); + p.drawPolygon( points, 3 ); + + lgrad = QLinearGradient( points[1], ( points[0]+points[2] ) / 2 ); + lgrad.setColorAt( 0.0, colors[1][0] ); + lgrad.setColorAt( 1.0, colors[1][1] ); + p.setBrush( lgrad ); + p.drawPolygon( points, 3 ); + + lgrad = QLinearGradient( points[2], ( points[0]+points[1] ) / 2 ); + lgrad.setColorAt( 0.0, colors[2][0] ); + lgrad.setColorAt( 1.0, colors[2][1] ); + p.setBrush( lgrad ); + p.drawPolygon( points, 3 ); + + p.setPen( QColor::fromHsvF( H, 0.0, 1.0, 0.8 ) ); + p.setBrush( QColor::fromHsvF( H, 1.0, 1.0, 1.0 ) ); + + double z = (s-c)/2; + QPointF pointH( sin( x ) * ( c + z ), cos( x ) * ( c + z ) ); + p.drawEllipse( QRectF( pointH - QPointF( z/2, z/2 ), QSizeF( z, z ) ) ); + + p.setBrush( QColor::fromHsvF( H, S, V, 1.0 ) ); + p.rotate( ( 1.0 - H ) * 360 - 120 ); + QPointF pointSV( ( S - 0.5 ) * sqrt( c*c - c*c/4 ) * 2 * V, V * ( c + c/2 ) - c ); + p.drawEllipse( QRectF( pointSV - QPointF( z/2, z/2 ), QSizeF( z, z ) ) ); +} + +void ColorWheel::mousePressEvent( QMouseEvent * e ) +{ + if ( e->button() != Qt::LeftButton ) + return; + + double dx = abs( e->x() - width() / 2 ); + double dy = abs( e->y() - height() / 2 ); + double d = sqrt( dx*dx + dy*dy ); + + double s = qMin( width(), height() ) / 2; + double c = s - s / 5; + + if ( d > s ) + pressed = Nope; + else if ( d > c ) + pressed = Circle; + else + pressed = Triangle; + + setColor( e->x(), e->y() ); +} + +void ColorWheel::mouseMoveEvent( QMouseEvent * e ) +{ + if ( e->buttons() & Qt::LeftButton ) + setColor( e->x(), e->y() ); +} + +void ColorWheel::contextMenuEvent( QContextMenuEvent * e ) +{ + QMenu * menu = new QMenu( this ); + + foreach ( QString name, QColor::colorNames() ) + { + QAction * act = new QAction( menu ); + act->setText( name ); + QPixmap pix( 16, 16 ); + QPainter paint( &pix ); + paint.setBrush( QColor( name ) ); + paint.setPen( Qt::black ); + paint.drawRect( pix.rect().adjusted( 0, 0, -1, -1 ) ); + act->setIcon( QIcon( pix ) ); + menu->addAction( act ); + } + + if ( QAction * act = menu->exec( e->globalPos() ) ) + { + setColor( QColor( act->text() ) ); + emit sigColorEdited( getColor() ); + } + + delete menu; +} + +void ColorWheel::setColor( int x, int y ) +{ + if ( pressed == Circle ) + { + QLineF l( QPointF( width() / 2.0, height() / 2.0 ), QPointF( x, y ) ); + H = l.angle( QLineF( 0, 1, 0, 0 ) ) / 360.0; + if ( l.dx() > 0 ) H = 1.0 - H; + update(); + emit sigColor( getColor() ); + emit sigColorEdited( getColor() ); + } + else if ( pressed == Triangle ) + { + QPointF mp( x - width() / 2, y - height() / 2 ); + + QMatrix m; + m.rotate( (H ) * 360.0 + 120 ); + QPointF p( m.map( mp ) ); + double c = qMin( width(), height() ) / 2; + c -= c / 5; + V = ( p.y() + c ) / ( c + c/2 ); + if ( V < 0.0 ) V = 0; + if ( V > 1.0 ) V = 1.0; + if ( V > 0 ) + { + double h = V * ( c + c/2 ) / ( 2.0 * sin( 60.0 / 180.0 * pi ) ); + S = ( p.x() + h ) / ( h * 2 ); + if ( S < 0.0 ) S = 0.0; + if ( S > 1.0 ) S = 1.0; + } + else + S = 1.0; + update(); + emit sigColor( getColor() ); + emit sigColorEdited( getColor() ); + } +} + +QColor ColorWheel::choose( const QColor & c, bool alphaEnable, QWidget * parent ) +{ + QDialog dlg( parent ); + dlg.setWindowTitle( "Choose a Color" ); + QGridLayout * grid = new QGridLayout; + dlg.setLayout( grid ); + + ColorWheel * hsv = new ColorWheel; + hsv->setColor( c ); + grid->addWidget( hsv, 0, 0, 1, 2 ); + + AlphaSlider * alpha = new AlphaSlider; + alpha->setColor( c ); + alpha->setValue( c.alphaF() ); + alpha->setOrientation( Qt::Vertical ); + alpha->setVisible( alphaEnable ); + grid->addWidget( alpha, 0, 2 ); + connect( hsv, SIGNAL( sigColor( const QColor & ) ), alpha, SLOT( setColor( const QColor & ) ) ); + + QHBoxLayout * hbox = new QHBoxLayout; + grid->addLayout( hbox, 1, 0, 1, 3 ); + QPushButton * ok = new QPushButton( "ok" ); + hbox->addWidget( ok ); + QPushButton * cancel = new QPushButton( "cancel" ); + hbox->addWidget( cancel ); + connect( ok, SIGNAL( clicked() ), &dlg, SLOT( accept() ) ); + connect( cancel, SIGNAL( clicked() ), &dlg, SLOT( reject() ) ); + + if ( dlg.exec() == QDialog::Accepted ) + { + QColor color = hsv->getColor(); + color.setAlphaF( alpha->value() ); + return color; + } + else + return c; +} + +Color3 ColorWheel::choose( const Color3 & c, QWidget * parent ) +{ + return Color3( choose( c.toQColor(), false, parent ) ); +} + +Color4 ColorWheel::choose( const Color4 & c, QWidget * parent ) +{ + return Color4( choose( c.toQColor(), true, parent ) ); +} diff --git a/widgets/colorwheel.h b/widgets/colorwheel.h index 7c96da666..e0808b9f2 100644 --- a/widgets/colorwheel.h +++ b/widgets/colorwheel.h @@ -1,94 +1,94 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef COLORWHEEL_H -#define COLORWHEEL_H - -#include -#include -#include - -class Color3; -class Color4; - -class ColorWheel : public QWidget -{ - Q_OBJECT -public: - static QColor choose( const QColor & color, bool alpha = true, QWidget * parent = 0 ); - static Color3 choose( const Color3 & color, QWidget * parent = 0 ); - static Color4 choose( const Color4 & color, QWidget * parent = 0 ); - - static QIcon getIcon(); - - ColorWheel( QWidget * parent = 0 ); - ColorWheel( const QColor & c, QWidget * parent = 0 ); - - Q_PROPERTY( QColor color READ getColor WRITE setColor NOTIFY sigColor USER true ) - - QColor getColor() const; - - QSize sizeHint() const; - QSize minimumSizeHint() const; - - void setSizeHint( const QSize & s ); - - int heightForWidth( int width ) const; - -signals: - void sigColor( const QColor & ); - void sigColorEdited( const QColor & ); - -public slots: - void setColor( const QColor & ); - -protected: - void paintEvent( QPaintEvent * e ); - void mousePressEvent( QMouseEvent * e ); - void mouseMoveEvent( QMouseEvent * e ); - void contextMenuEvent( QContextMenuEvent * e ); - - void setColor( int x, int y ); - -private: - double H, S, V; - - enum { - Nope, Circle, Triangle - } pressed; - - QSize sHint; - - static QIcon * icon; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef COLORWHEEL_H +#define COLORWHEEL_H + +#include +#include +#include + +class Color3; +class Color4; + +class ColorWheel : public QWidget +{ + Q_OBJECT +public: + static QColor choose( const QColor & color, bool alpha = true, QWidget * parent = 0 ); + static Color3 choose( const Color3 & color, QWidget * parent = 0 ); + static Color4 choose( const Color4 & color, QWidget * parent = 0 ); + + static QIcon getIcon(); + + ColorWheel( QWidget * parent = 0 ); + ColorWheel( const QColor & c, QWidget * parent = 0 ); + + Q_PROPERTY( QColor color READ getColor WRITE setColor NOTIFY sigColor USER true ) + + QColor getColor() const; + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void setSizeHint( const QSize & s ); + + int heightForWidth( int width ) const; + +signals: + void sigColor( const QColor & ); + void sigColorEdited( const QColor & ); + +public slots: + void setColor( const QColor & ); + +protected: + void paintEvent( QPaintEvent * e ); + void mousePressEvent( QMouseEvent * e ); + void mouseMoveEvent( QMouseEvent * e ); + void contextMenuEvent( QContextMenuEvent * e ); + + void setColor( int x, int y ); + +private: + double H, S, V; + + enum { + Nope, Circle, Triangle + } pressed; + + QSize sHint; + + static QIcon * icon; +}; + +#endif diff --git a/widgets/copyfnam.cpp b/widgets/copyfnam.cpp index 91d5fd0f5..b258f27fa 100644 --- a/widgets/copyfnam.cpp +++ b/widgets/copyfnam.cpp @@ -1,83 +1,83 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "copyfnam.h" - -#include -#include - -CopyFilename::CopyFilename( QWidget * parent ) - : QWidget( parent ) -{ - this->leftArrow.load( ":/img/ArrowLeft" ); - this->rightArrow.load( ":/img/ArrowRight" ); - - this->setMinimumWidth( qMax( leftArrow.width(), this->rightArrow.width() ) + 4 ); - this->setMinimumHeight( this->leftArrow.width() + this->rightArrow.width() + 4 ); -} - -void CopyFilename::paintEvent( QPaintEvent * e ) -{ - int w = this->width(); - int h = this->height() / 2; - - // right arrow position, top - this->rightPos = this->rightArrow.rect(); - this->rightPos.moveTo( - ( w - this->rightArrow.width() ) / 2, - ( h - this->rightArrow.height() ) / 2 - ); - - // left arrow position, bottom - this->leftPos = this->leftArrow.rect(); - this->leftPos.moveTo( - ( w - this->leftArrow.width() ) / 2, - ( h - this->leftArrow.height() ) / 2 + h - ); - - // now paint the widget - QPainter p( this ); - - // draw arrows - p.drawImage( this->rightPos, this->rightArrow, this->rightArrow.rect() ); - p.drawImage( this->leftPos, this->leftArrow, this->leftArrow.rect() ); -} - -void CopyFilename::mousePressEvent( QMouseEvent * e ) -{ - if( e->y() < ( this->height() / 2 ) ) { - emit( this->rightTriggered() ); - } - else { - emit( this->leftTriggered() ); - } -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "copyfnam.h" + +#include +#include + +CopyFilename::CopyFilename( QWidget * parent ) + : QWidget( parent ) +{ + this->leftArrow.load( ":/img/ArrowLeft" ); + this->rightArrow.load( ":/img/ArrowRight" ); + + this->setMinimumWidth( qMax( leftArrow.width(), this->rightArrow.width() ) + 4 ); + this->setMinimumHeight( this->leftArrow.width() + this->rightArrow.width() + 4 ); +} + +void CopyFilename::paintEvent( QPaintEvent * e ) +{ + int w = this->width(); + int h = this->height() / 2; + + // right arrow position, top + this->rightPos = this->rightArrow.rect(); + this->rightPos.moveTo( + ( w - this->rightArrow.width() ) / 2, + ( h - this->rightArrow.height() ) / 2 + ); + + // left arrow position, bottom + this->leftPos = this->leftArrow.rect(); + this->leftPos.moveTo( + ( w - this->leftArrow.width() ) / 2, + ( h - this->leftArrow.height() ) / 2 + h + ); + + // now paint the widget + QPainter p( this ); + + // draw arrows + p.drawImage( this->rightPos, this->rightArrow, this->rightArrow.rect() ); + p.drawImage( this->leftPos, this->leftArrow, this->leftArrow.rect() ); +} + +void CopyFilename::mousePressEvent( QMouseEvent * e ) +{ + if( e->y() < ( this->height() / 2 ) ) { + emit( this->rightTriggered() ); + } + else { + emit( this->leftTriggered() ); + } +} diff --git a/widgets/copyfnam.h b/widgets/copyfnam.h index 3c6b13d5f..087e057c3 100644 --- a/widgets/copyfnam.h +++ b/widgets/copyfnam.h @@ -1,64 +1,64 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef COPYFNAM_H -#define COPYFNAM_H - -#include -#include -#include - -class CopyFilename : public QWidget -{ - Q_OBJECT -public: - CopyFilename( QWidget * parent = NULL ); - -signals: - void leftTriggered(); - void rightTriggered(); - -protected: - void paintEvent( QPaintEvent * e ); - void mousePressEvent( QMouseEvent * e ); - - // QPushButton * leftBtnCopyFilename; - // QPushButton * rightBtnCopyFilename; - -private: - QImage leftArrow; - QImage rightArrow; - QRect leftPos; - QRect rightPos; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef COPYFNAM_H +#define COPYFNAM_H + +#include +#include +#include + +class CopyFilename : public QWidget +{ + Q_OBJECT +public: + CopyFilename( QWidget * parent = NULL ); + +signals: + void leftTriggered(); + void rightTriggered(); + +protected: + void paintEvent( QPaintEvent * e ); + void mousePressEvent( QMouseEvent * e ); + + // QPushButton * leftBtnCopyFilename; + // QPushButton * rightBtnCopyFilename; + +private: + QImage leftArrow; + QImage rightArrow; + QRect leftPos; + QRect rightPos; +}; + +#endif diff --git a/widgets/fileselect.cpp b/widgets/fileselect.cpp index c4bcf2631..3a06c19d5 100644 --- a/widgets/fileselect.cpp +++ b/widgets/fileselect.cpp @@ -1,270 +1,270 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -// how long the visual save/load feedback is visible -#define FEEDBACK_TIME 1200 - -#include "fileselect.h" -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -CompletionAction::CompletionAction( QObject * parent ) : QAction( "Completion of Filenames", parent ) -{ - NIFSKOPE_QSETTINGS(cfg); - setCheckable( true ); - setChecked( cfg.value( "completion of file names", false ).toBool() ); - - connect( this, SIGNAL( toggled( bool ) ), this, SLOT( sltToggled( bool ) ) ); -} - -CompletionAction::~CompletionAction() -{ -} - -void CompletionAction::sltToggled( bool ) -{ - NIFSKOPE_QSETTINGS(cfg); - cfg.setValue( "completion of file names", isChecked() ); -} - -FileSelector::FileSelector( Modes mode, const QString & buttonText, QBoxLayout::Direction dir ) - : QWidget(), Mode( mode ), dirmdl( 0 ), completer( 0 ) -{ - QBoxLayout * lay = new QBoxLayout( dir, this ); - lay->setMargin( 0 ); - setLayout( lay ); - - line = new QLineEdit( this ); - - connect( line, SIGNAL( textEdited( const QString & ) ), this, SIGNAL( sigEdited( const QString & ) ) ); - connect( line, SIGNAL( returnPressed() ), this, SLOT( activate() ) ); - - action = new QAction( this ); - action->setText( buttonText ); - connect( action, SIGNAL( triggered() ), this, SLOT( browse() ) ); - addAction( action ); - - QToolButton * button = new QToolButton( this ); - button->setDefaultAction( action ); - - lay->addWidget( line ); - lay->addWidget( button ); - - // setFocusProxy( line ); - - line->installEventFilter( this ); - - connect( completionAction(), SIGNAL( toggled( bool ) ), this, SLOT( setCompletionEnabled( bool ) ) ); - setCompletionEnabled( completionAction()->isChecked() ); - - timer = new QTimer( this ); - timer->setSingleShot( true ); - timer->setInterval( FEEDBACK_TIME ); - connect( timer, SIGNAL( timeout() ), this, SLOT( rstState() ) ); -} - -QAction * FileSelector::completionAction() -{ - static QAction * action = new CompletionAction; - return action; -} - -void FileSelector::setCompletionEnabled( bool x ) -{ - if ( x && ! dirmdl ) - { - QDir::Filters fm; - - switch ( Mode ) - { - case LoadFile: - fm = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot; - break; - case SaveFile: - fm = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot; - break; - case Folder: - fm = QDir::AllDirs | QDir::NoDotAndDotDot; - break; - } - - dirmdl = new QDirModel( fltr, fm, QDir::DirsFirst | QDir::Name, this ); - dirmdl->setLazyChildCount( true ); - line->setCompleter( completer = new QCompleter( dirmdl, this ) ); - } - else if ( ! x && dirmdl ) - { - line->setCompleter( 0 ); - delete completer; - completer = 0; - delete dirmdl; - dirmdl = 0; - } -} - -QString FileSelector::file() const -{ - return line->text(); -} - -void FileSelector::setFile( const QString & x ) -{ - line->setText( QDir::toNativeSeparators( x ) ); -} - -void FileSelector::setText( const QString & x ) -{ - setFile( x ); -} - -void FileSelector::setState( States s ) -{ - State = s; - - if ( State != stNeutral ) - timer->start(); - else - timer->stop(); - - // reload style sheet - QString styletmp = styleSheet(); - setStyleSheet( QString() ); - setStyleSheet( styletmp ); -} - -void FileSelector::rstState() -{ - setState( stNeutral ); -} - -void FileSelector::replaceText( const QString & x ) -{ - line->setCompleter( 0 ); - line->selectAll(); - line->del(); - line->insert( x ); - line->setCompleter( completer ); -} - -void FileSelector::setFilter( const QStringList & fltr ) -{ - this->fltr = fltr; - if ( dirmdl ) - dirmdl->setNameFilters( fltr ); -} - -QStringList FileSelector::filter() const -{ - return fltr; -} - -void FileSelector::browse() -{ - QString x; - - switch ( Mode ) - { - case Folder: - x = QFileDialog::getExistingDirectory( this, "Choose a folder", file() ); - break; - case LoadFile: - // Qt uses ;; as separator if multiple types are available - { QStringList allfltr = fltr; allfltr.insert(0, fltr.join( " " )); - x = QFileDialog::getOpenFileName( this, "Choose a file", file(), allfltr.join( ";;" ) ); - } break; - case SaveFile: - x = QFileDialog::getSaveFileName( this, "Choose a file", file(), fltr.join( ";;" ) ); - break; - } - - if ( ! x.isEmpty() ) - { - line->setText( x ); - activate(); - } -} - -void FileSelector::activate() -{ - QFileInfo inf( file() ); - - switch ( Mode ) - { - case LoadFile: - if ( ! inf.isFile() ) { - setState( stError ); - return; - } - break; - case SaveFile: - if ( inf.isDir() ) { - setState( stError ); - return; - } - break; - case Folder: - if ( ! inf.isDir() ) { - setState( stError ); - return; - } - break; - } - emit sigActivated( file() ); -} - -bool FileSelector::eventFilter( QObject * o, QEvent * e ) -{ - if ( o == line && e->type() == QEvent::ContextMenu ) - { - QContextMenuEvent * event = static_cast( e ); - - QMenu * menu = line->createStandardContextMenu(); - menu->addSeparator(); - menu->addAction( completionAction() ); - menu->exec(event->globalPos()); - delete menu; - return true; - } - - return QWidget::eventFilter( o, e ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +// how long the visual save/load feedback is visible +#define FEEDBACK_TIME 1200 + +#include "fileselect.h" +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CompletionAction::CompletionAction( QObject * parent ) : QAction( "Completion of Filenames", parent ) +{ + NIFSKOPE_QSETTINGS(cfg); + setCheckable( true ); + setChecked( cfg.value( "completion of file names", false ).toBool() ); + + connect( this, SIGNAL( toggled( bool ) ), this, SLOT( sltToggled( bool ) ) ); +} + +CompletionAction::~CompletionAction() +{ +} + +void CompletionAction::sltToggled( bool ) +{ + NIFSKOPE_QSETTINGS(cfg); + cfg.setValue( "completion of file names", isChecked() ); +} + +FileSelector::FileSelector( Modes mode, const QString & buttonText, QBoxLayout::Direction dir ) + : QWidget(), Mode( mode ), dirmdl( 0 ), completer( 0 ) +{ + QBoxLayout * lay = new QBoxLayout( dir, this ); + lay->setMargin( 0 ); + setLayout( lay ); + + line = new QLineEdit( this ); + + connect( line, SIGNAL( textEdited( const QString & ) ), this, SIGNAL( sigEdited( const QString & ) ) ); + connect( line, SIGNAL( returnPressed() ), this, SLOT( activate() ) ); + + action = new QAction( this ); + action->setText( buttonText ); + connect( action, SIGNAL( triggered() ), this, SLOT( browse() ) ); + addAction( action ); + + QToolButton * button = new QToolButton( this ); + button->setDefaultAction( action ); + + lay->addWidget( line ); + lay->addWidget( button ); + + // setFocusProxy( line ); + + line->installEventFilter( this ); + + connect( completionAction(), SIGNAL( toggled( bool ) ), this, SLOT( setCompletionEnabled( bool ) ) ); + setCompletionEnabled( completionAction()->isChecked() ); + + timer = new QTimer( this ); + timer->setSingleShot( true ); + timer->setInterval( FEEDBACK_TIME ); + connect( timer, SIGNAL( timeout() ), this, SLOT( rstState() ) ); +} + +QAction * FileSelector::completionAction() +{ + static QAction * action = new CompletionAction; + return action; +} + +void FileSelector::setCompletionEnabled( bool x ) +{ + if ( x && ! dirmdl ) + { + QDir::Filters fm; + + switch ( Mode ) + { + case LoadFile: + fm = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot; + break; + case SaveFile: + fm = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot; + break; + case Folder: + fm = QDir::AllDirs | QDir::NoDotAndDotDot; + break; + } + + dirmdl = new QDirModel( fltr, fm, QDir::DirsFirst | QDir::Name, this ); + dirmdl->setLazyChildCount( true ); + line->setCompleter( completer = new QCompleter( dirmdl, this ) ); + } + else if ( ! x && dirmdl ) + { + line->setCompleter( 0 ); + delete completer; + completer = 0; + delete dirmdl; + dirmdl = 0; + } +} + +QString FileSelector::file() const +{ + return line->text(); +} + +void FileSelector::setFile( const QString & x ) +{ + line->setText( QDir::toNativeSeparators( x ) ); +} + +void FileSelector::setText( const QString & x ) +{ + setFile( x ); +} + +void FileSelector::setState( States s ) +{ + State = s; + + if ( State != stNeutral ) + timer->start(); + else + timer->stop(); + + // reload style sheet + QString styletmp = styleSheet(); + setStyleSheet( QString() ); + setStyleSheet( styletmp ); +} + +void FileSelector::rstState() +{ + setState( stNeutral ); +} + +void FileSelector::replaceText( const QString & x ) +{ + line->setCompleter( 0 ); + line->selectAll(); + line->del(); + line->insert( x ); + line->setCompleter( completer ); +} + +void FileSelector::setFilter( const QStringList & fltr ) +{ + this->fltr = fltr; + if ( dirmdl ) + dirmdl->setNameFilters( fltr ); +} + +QStringList FileSelector::filter() const +{ + return fltr; +} + +void FileSelector::browse() +{ + QString x; + + switch ( Mode ) + { + case Folder: + x = QFileDialog::getExistingDirectory( this, "Choose a folder", file() ); + break; + case LoadFile: + // Qt uses ;; as separator if multiple types are available + { QStringList allfltr = fltr; allfltr.insert(0, fltr.join( " " )); + x = QFileDialog::getOpenFileName( this, "Choose a file", file(), allfltr.join( ";;" ) ); + } break; + case SaveFile: + x = QFileDialog::getSaveFileName( this, "Choose a file", file(), fltr.join( ";;" ) ); + break; + } + + if ( ! x.isEmpty() ) + { + line->setText( x ); + activate(); + } +} + +void FileSelector::activate() +{ + QFileInfo inf( file() ); + + switch ( Mode ) + { + case LoadFile: + if ( ! inf.isFile() ) { + setState( stError ); + return; + } + break; + case SaveFile: + if ( inf.isDir() ) { + setState( stError ); + return; + } + break; + case Folder: + if ( ! inf.isDir() ) { + setState( stError ); + return; + } + break; + } + emit sigActivated( file() ); +} + +bool FileSelector::eventFilter( QObject * o, QEvent * e ) +{ + if ( o == line && e->type() == QEvent::ContextMenu ) + { + QContextMenuEvent * event = static_cast( e ); + + QMenu * menu = line->createStandardContextMenu(); + menu->addSeparator(); + menu->addAction( completionAction() ); + menu->exec(event->globalPos()); + delete menu; + return true; + } + + return QWidget::eventFilter( o, e ); +} diff --git a/widgets/fileselect.h b/widgets/fileselect.h index 176137eb6..3e47b0d43 100644 --- a/widgets/fileselect.h +++ b/widgets/fileselect.h @@ -1,137 +1,137 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef FILESELECT_H -#define FILESELECT_H - -#include -#include -#include -#include - -class QCompleter; -class QDirModel; - -//! A widget for file selection, both via a browser window and via editing a string. -/*! - * This widget displays a button (typically Load or Save) and a filename string. If the button is - * pressed, then a file selection window is displayed. - * - * Emits sigActivated when a file has been chosen, or when the filename string is activated (that - * is, pressing enter after editing the string). This signal can be connected to the load/save - * slot of the application. - * - * Emits sigEdited when the string is edited. - */ -class FileSelector : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QString file READ file WRITE setFile NOTIFY sigEdited USER true) - Q_PROPERTY(QStringList filter READ filter WRITE setFilter) - Q_PROPERTY(Modes mode READ mode WRITE setMode) - Q_PROPERTY(States state READ state WRITE setState RESET rstState) - Q_ENUMS(Modes) - Q_ENUMS(States) - -public: - enum Modes - { - LoadFile, SaveFile, Folder - }; - - enum States - { - stNeutral = 0, stSuccess = 1, stError = 2 - }; - - FileSelector( Modes m, const QString & buttonText = "browse", QBoxLayout::Direction dir = QBoxLayout::LeftToRight ); - - QString text() const { return file(); } - QString file() const; - - void setFilter( const QStringList & f ); - QStringList filter() const; - - Modes mode() const { return Mode; } - void setMode( Modes m ) { Mode = m; } - - States state() const { return State; } - void setState( States ); - -signals: - void sigEdited( const QString & ); - void sigActivated( const QString & ); - -public slots: - void rstState(); - - void setText( const QString & ); - void setFile( const QString & ); - - void replaceText( const QString & ); - - void setCompletionEnabled( bool ); - -protected slots: - void browse(); - void activate(); - -protected: - bool eventFilter( QObject * o, QEvent * e ); - - QAction * completionAction(); - - Modes Mode; - States State; - - QLineEdit * line; - QAction * action; - - QDirModel * dirmdl; - QCompleter * completer; - QStringList fltr; - - QTimer * timer; -}; - -class CompletionAction : public QAction -{ - Q_OBJECT -public: - CompletionAction( QObject * parent = 0 ); - ~CompletionAction(); - -protected slots: - void sltToggled( bool ); -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef FILESELECT_H +#define FILESELECT_H + +#include +#include +#include +#include + +class QCompleter; +class QDirModel; + +//! A widget for file selection, both via a browser window and via editing a string. +/*! + * This widget displays a button (typically Load or Save) and a filename string. If the button is + * pressed, then a file selection window is displayed. + * + * Emits sigActivated when a file has been chosen, or when the filename string is activated (that + * is, pressing enter after editing the string). This signal can be connected to the load/save + * slot of the application. + * + * Emits sigEdited when the string is edited. + */ +class FileSelector : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString file READ file WRITE setFile NOTIFY sigEdited USER true) + Q_PROPERTY(QStringList filter READ filter WRITE setFilter) + Q_PROPERTY(Modes mode READ mode WRITE setMode) + Q_PROPERTY(States state READ state WRITE setState RESET rstState) + Q_ENUMS(Modes) + Q_ENUMS(States) + +public: + enum Modes + { + LoadFile, SaveFile, Folder + }; + + enum States + { + stNeutral = 0, stSuccess = 1, stError = 2 + }; + + FileSelector( Modes m, const QString & buttonText = "browse", QBoxLayout::Direction dir = QBoxLayout::LeftToRight ); + + QString text() const { return file(); } + QString file() const; + + void setFilter( const QStringList & f ); + QStringList filter() const; + + Modes mode() const { return Mode; } + void setMode( Modes m ) { Mode = m; } + + States state() const { return State; } + void setState( States ); + +signals: + void sigEdited( const QString & ); + void sigActivated( const QString & ); + +public slots: + void rstState(); + + void setText( const QString & ); + void setFile( const QString & ); + + void replaceText( const QString & ); + + void setCompletionEnabled( bool ); + +protected slots: + void browse(); + void activate(); + +protected: + bool eventFilter( QObject * o, QEvent * e ); + + QAction * completionAction(); + + Modes Mode; + States State; + + QLineEdit * line; + QAction * action; + + QDirModel * dirmdl; + QCompleter * completer; + QStringList fltr; + + QTimer * timer; +}; + +class CompletionAction : public QAction +{ + Q_OBJECT +public: + CompletionAction( QObject * parent = 0 ); + ~CompletionAction(); + +protected slots: + void sltToggled( bool ); +}; + +#endif diff --git a/widgets/floatedit.cpp b/widgets/floatedit.cpp index 4bb5cbde1..9333a9dc0 100644 --- a/widgets/floatedit.cpp +++ b/widgets/floatedit.cpp @@ -1,171 +1,171 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "floatedit.h" -#include "../nifvalue.h" - -#include -#include -#include - -#include - - -QValidator::State FloatValidator::validate ( QString & input, int & pos ) const -{ - if( !( bottom() > -FLT_MAX ) && QString( "" ).startsWith( input ) ) { - if( input == "" ) - return QValidator::Acceptable; - return QValidator::Intermediate; - } - - if( !( top() < FLT_MAX ) && QString( "" ).startsWith( input ) ) { - if( input == "" ) - return QValidator::Acceptable; - return QValidator::Intermediate; - } - - return QDoubleValidator::validate( input, pos ); -} - -bool FloatValidator::canMin() const -{ - return !( bottom() > -FLT_MAX ); -} - -bool FloatValidator::canMax() const -{ - return !( top() < FLT_MAX ); -} - -FloatEdit::FloatEdit( QWidget * parent ) - : QLineEdit( parent ) -{ - val = 0.0f; - - setValidator( validator = new FloatValidator( this ) ); - validator->setDecimals( 4 ); - - connect( this, SIGNAL( editingFinished() ), this, SLOT( edited() ) ); - - /* - context menu - */ - actMin = new QAction( "", this ); - connect( actMin, SIGNAL( triggered() ), this, SLOT( setMin() ) ); - addAction( actMin ); - - actMax = new QAction( "", this ); - connect( actMax, SIGNAL( triggered() ), this, SLOT( setMax() ) ); - addAction( actMax ); -} - -void FloatEdit::contextMenuEvent( QContextMenuEvent * event ) -{ - QMenu * mnuContext = new QMenu( this ); - mnuContext->setMouseTracking( true ); - - QMenu * mnuStd = createStandardContextMenu(); - - if( validator->canMin() ) - mnuContext->addAction( actMin ); - if( validator->canMax() ) - mnuContext->addAction( actMax ); - if( validator->canMin() || validator->canMax() ) - mnuContext->addSeparator(); - - mnuContext->addActions( mnuStd->actions() ); - - mnuContext->exec( event->globalPos() ); - - delete mnuStd; - delete mnuContext; -} - -void FloatEdit::edited() -{ - QString str = text(); - if( str == "" ) - val = -FLT_MAX; - else if( str == "" ) - val = FLT_MAX; - else - val = str.toFloat(); - - emit sigEdited( val ); - emit sigEdited( str ); -} - -void FloatEdit::setValue( float f ) -{ - val = f; - setText( NumOrMinMax( val, 'f', validator->decimals() ) ); - emit valueChanged( val ); - emit valueChanged( text() ); -} - -void FloatEdit::set( float f, float, float ) -{ - setValue( f ); -} - -void FloatEdit::setMin() -{ - if( validator->canMin() ) - setValue( -FLT_MAX ); -} - -void FloatEdit::setMax() -{ - if( validator->canMax() ) - setValue( FLT_MAX ); -} - -float FloatEdit::value() const -{ - return val; -} - -void FloatEdit::setRange( float minimum, float maximum ) -{ - validator->setRange( minimum, maximum ); -} - -bool FloatEdit::isMin() const -{ - return (text() == ""); -} - -bool FloatEdit::isMax() const -{ - return (text() == ""); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "floatedit.h" +#include "../nifvalue.h" + +#include +#include +#include + +#include + + +QValidator::State FloatValidator::validate ( QString & input, int & pos ) const +{ + if( !( bottom() > -FLT_MAX ) && QString( "" ).startsWith( input ) ) { + if( input == "" ) + return QValidator::Acceptable; + return QValidator::Intermediate; + } + + if( !( top() < FLT_MAX ) && QString( "" ).startsWith( input ) ) { + if( input == "" ) + return QValidator::Acceptable; + return QValidator::Intermediate; + } + + return QDoubleValidator::validate( input, pos ); +} + +bool FloatValidator::canMin() const +{ + return !( bottom() > -FLT_MAX ); +} + +bool FloatValidator::canMax() const +{ + return !( top() < FLT_MAX ); +} + +FloatEdit::FloatEdit( QWidget * parent ) + : QLineEdit( parent ) +{ + val = 0.0f; + + setValidator( validator = new FloatValidator( this ) ); + validator->setDecimals( 4 ); + + connect( this, SIGNAL( editingFinished() ), this, SLOT( edited() ) ); + + /* + context menu + */ + actMin = new QAction( "", this ); + connect( actMin, SIGNAL( triggered() ), this, SLOT( setMin() ) ); + addAction( actMin ); + + actMax = new QAction( "", this ); + connect( actMax, SIGNAL( triggered() ), this, SLOT( setMax() ) ); + addAction( actMax ); +} + +void FloatEdit::contextMenuEvent( QContextMenuEvent * event ) +{ + QMenu * mnuContext = new QMenu( this ); + mnuContext->setMouseTracking( true ); + + QMenu * mnuStd = createStandardContextMenu(); + + if( validator->canMin() ) + mnuContext->addAction( actMin ); + if( validator->canMax() ) + mnuContext->addAction( actMax ); + if( validator->canMin() || validator->canMax() ) + mnuContext->addSeparator(); + + mnuContext->addActions( mnuStd->actions() ); + + mnuContext->exec( event->globalPos() ); + + delete mnuStd; + delete mnuContext; +} + +void FloatEdit::edited() +{ + QString str = text(); + if( str == "" ) + val = -FLT_MAX; + else if( str == "" ) + val = FLT_MAX; + else + val = str.toFloat(); + + emit sigEdited( val ); + emit sigEdited( str ); +} + +void FloatEdit::setValue( float f ) +{ + val = f; + setText( NumOrMinMax( val, 'f', validator->decimals() ) ); + emit valueChanged( val ); + emit valueChanged( text() ); +} + +void FloatEdit::set( float f, float, float ) +{ + setValue( f ); +} + +void FloatEdit::setMin() +{ + if( validator->canMin() ) + setValue( -FLT_MAX ); +} + +void FloatEdit::setMax() +{ + if( validator->canMax() ) + setValue( FLT_MAX ); +} + +float FloatEdit::value() const +{ + return val; +} + +void FloatEdit::setRange( float minimum, float maximum ) +{ + validator->setRange( minimum, maximum ); +} + +bool FloatEdit::isMin() const +{ + return (text() == ""); +} + +bool FloatEdit::isMax() const +{ + return (text() == ""); +} diff --git a/widgets/floatedit.h b/widgets/floatedit.h index d4ec4af22..ffc65d4bc 100644 --- a/widgets/floatedit.h +++ b/widgets/floatedit.h @@ -1,94 +1,94 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef FLOATEDIT_H -#define FLOATEDIT_H - -#include -#include - -class FloatValidator : public QDoubleValidator -{ - Q_OBJECT -public: - FloatValidator( QObject * parent = NULL ) - : QDoubleValidator( parent ) - {} - FloatValidator( double bottom, double top, int decimals, QObject * parent = NULL ) - : QDoubleValidator( bottom, top, decimals, parent ) - {} - - QValidator::State validate ( QString & input, int & pos ) const; - - bool canMin() const; - bool canMax() const; -}; - -class FloatEdit : public QLineEdit -{ - Q_OBJECT -public: - FloatEdit( QWidget * = NULL ); - - float value() const; - - void setRange( float minimum, float maximum ); - - bool isMin() const; - bool isMax() const; - -protected: - void contextMenuEvent( class QContextMenuEvent * event ); - -signals: - void sigEdited( float ); - void sigEdited( const QString & ); - void valueChanged( float ); - void valueChanged( const QString & ); - -public slots: - void setValue( float val ); - void set( float val, float n, float x ); - void setMin(); - void setMax(); - -protected slots: - void edited(); - -protected: - float val; - FloatValidator * validator; - class QAction * actMin; - class QAction * actMax; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef FLOATEDIT_H +#define FLOATEDIT_H + +#include +#include + +class FloatValidator : public QDoubleValidator +{ + Q_OBJECT +public: + FloatValidator( QObject * parent = NULL ) + : QDoubleValidator( parent ) + {} + FloatValidator( double bottom, double top, int decimals, QObject * parent = NULL ) + : QDoubleValidator( bottom, top, decimals, parent ) + {} + + QValidator::State validate ( QString & input, int & pos ) const; + + bool canMin() const; + bool canMax() const; +}; + +class FloatEdit : public QLineEdit +{ + Q_OBJECT +public: + FloatEdit( QWidget * = NULL ); + + float value() const; + + void setRange( float minimum, float maximum ); + + bool isMin() const; + bool isMax() const; + +protected: + void contextMenuEvent( class QContextMenuEvent * event ); + +signals: + void sigEdited( float ); + void sigEdited( const QString & ); + void valueChanged( float ); + void valueChanged( const QString & ); + +public slots: + void setValue( float val ); + void set( float val, float n, float x ); + void setMin(); + void setMax(); + +protected slots: + void edited(); + +protected: + float val; + FloatValidator * validator; + class QAction * actMin; + class QAction * actMax; +}; + +#endif diff --git a/widgets/floatslider.cpp b/widgets/floatslider.cpp index 883c69838..332eb6eae 100644 --- a/widgets/floatslider.cpp +++ b/widgets/floatslider.cpp @@ -1,422 +1,422 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "floatslider.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "floatedit.h" - -#include - -#define VAL_HEIGHT 12 - -FloatSliderEditBox::FloatSliderEditBox( QWidget * parent ) - : QFrame( parent, Qt::Popup | Qt::FramelessWindowHint ) -{ - setVisible( false ); - setFrameShadow( QFrame::Raised ); - setFrameShape( QFrame::StyledPanel ); - setLineWidth( 0 ); - setMaximumWidth( 100 ); - - QVBoxLayout * layout = new QVBoxLayout(); - layout->setMargin( 4 ); - layout->setSpacing( 2 ); - setLayout( layout ); -} - -void FloatSliderEditBox::addWidget( QWidget * w ) -{ - layout()->addWidget( w ); -} - -void FloatSliderEditBox::show( const QPoint & pos ) -{ - if( isVisible() ) { - return; - } - - move( pos ); - QWidget::show(); - setFocus( Qt::PopupFocusReason ); - - connect( QApplication::instance(), SIGNAL( focusChanged( QWidget *, QWidget * ) ), this, SLOT( focusChanged( QWidget *, QWidget * ) ) ); -} - -void FloatSliderEditBox::hide() -{ - if( !isVisible() ) { - return; - } - - disconnect( QApplication::instance(), SIGNAL( focusChanged( QWidget *, QWidget * ) ), this, SLOT( focusChanged( QWidget *, QWidget * ) ) ); - - QWidget::hide(); -} - -void FloatSliderEditBox::focusChanged( QWidget * oldW, QWidget * newW ) -{ - if( newW == this ) { - return; - } - - if( layout()->indexOf( newW ) > -1 ) { - return; - } - - hide(); -} - -FloatSlider::FloatSlider( Qt::Orientation o, bool showValue, bool isEditor ) - : QWidget(), val( 0.5 ), min( 0 ), max( 1.0 ), ori( o ), pressed( false ) -{ - QSizePolicy sp( QSizePolicy::Expanding, QSizePolicy::Fixed ); - if ( ori == Qt::Vertical ) - sp.transpose(); - setSizePolicy( sp ); - - showVal = showValue; - editVal = showVal && isEditor; - - if( showVal ) { - QFont fnt( "Arial", -1, QFont::Normal ); - fnt.setStyleStrategy( QFont::PreferAntialias ); - fnt.setPixelSize( VAL_HEIGHT - 2 ); - setFont( fnt ); - } - - if( editVal ) { - setToolTip( tr("Click value to edit.") ); - } - - editBox = new FloatSliderEditBox( this ); -} - -void FloatSlider::setValue( float v ) -{ - if ( v < min ) - v = min; - if ( v > max ) - v = max; - - if ( val != v ) - { - val = v; - update(); - } -} - -void FloatSlider::setValueUser( float v ) -{ - if ( v < min ) - v = min; - if ( v > max ) - v = max; - - if ( val != v ) - { - val = v; - update(); - emit valueChanged( val ); - } -} - -void FloatSlider::setRange( float mn, float mx ) -{ - if ( mn > mx ) - mx = mn; - - if ( min != mn || max != mx ) - { - min = mn; - if ( val < min ) - setValue( min ); - - max = mx; - if ( val > max ) - setValue( max ); - - update(); - } -} - -void FloatSlider::set( float v, float mn, float mx ) -{ - setRange( mn, mx ); - setValue( v ); -} - -void FloatSlider::setOrientation( Qt::Orientation o ) -{ - if ( ori != o ) - { - ori = o; - QSizePolicy sp = sizePolicy(); - sp.transpose(); - setSizePolicy( sp ); - updateGeometry(); - update(); - } -} - -void FloatSlider::addEditor( QWidget * editWidget ) { - editBox->addWidget( editWidget ); -} - -QStyleOptionSlider FloatSlider::getStyleOption() const -{ - QStyleOptionSlider opt; - /* - opt.init(q); - opt.orientation = orientation; - opt.maximum = maximum; - opt.minimum = minimum; - opt.tickPosition = (QSlider::TickPosition)tickPosition; - opt.tickInterval = tickInterval; - opt.upsideDown = (orientation == Qt::Horizontal) ? - (invertedAppearance != (opt.direction == Qt::RightToLeft)) - : (!invertedAppearance); - opt.direction = Qt::LeftToRight; // we use the upsideDown option instead - opt.sliderPosition = position; - opt.sliderValue = value; - opt.singleStep = singleStep; - opt.pageStep = pageStep; - if (orientation == Qt::Horizontal) - opt.state |= QStyle::State_Horizontal; - */ - - opt.initFrom( this ); - - opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; - opt.activeSubControls = QStyle::SC_None; - - if( showVal ) { - int w = fontMetrics().width( "0.000" ); -#pragma message("NOTICE: Qt Bugfix is needed here, see http://pastebin.mozilla.org/101393") - opt.rect.adjust( (6*w)/10, VAL_HEIGHT, (-6*w)/10, 0 ); - } - - opt.maximum = INT_MAX - 1; - opt.minimum = 0; - opt.orientation = ori; - opt.pageStep = 10; - opt.singleStep = 1; - opt.sliderValue = ( max != min ) ? int( 1.0f * ( val - min ) / ( max - min ) * opt.maximum ) : 0; - opt.sliderPosition = opt.sliderValue; - opt.tickPosition = QSlider::NoTicks; - opt.direction = Qt::LeftToRight; - - /* upside down for vertical slider; zero at bottom position */ - opt.upsideDown = (ori == Qt::Vertical); - - return opt; - -} - -void FloatSlider::paintEvent( QPaintEvent * e ) -{ - QPainter p( this ); - QStyleOptionSlider opt = getStyleOption(); - - if ( pressed ) - { - opt.activeSubControls = QStyle::SC_SliderHandle; - opt.state |= QStyle::State_Sunken; - } - - if( showVal ) { - QString t = QString().number( val, 'f', 3 ); - QRect tr = style()->subControlRect( QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this ); - tr.adjust( -opt.rect.left()+2, -VAL_HEIGHT, opt.rect.left()-2, -tr.height()-1 ); - - p.drawText( tr, t, QTextOption( Qt::AlignCenter ) ); - } - - style()->drawComplexControl( QStyle::CC_Slider, &opt, &p, this ); -} - -void FloatSlider::mousePressEvent( QMouseEvent * ev ) -{ - if ( max <= min || ( ev->buttons() != Qt::LeftButton ) ) - { - ev->ignore(); - return; - } - ev->accept(); - - if( editVal && QRect( 0, 0, width(), VAL_HEIGHT ).contains( ev->pos() ) ) { - editBox->show( this->mapToGlobal( ev->pos() ) ); - } - else { - pressed = true; - - setValueUser( mapToValue( ev->pos() ) ); - update(); - } -} - -void FloatSlider::mouseMoveEvent( QMouseEvent * ev ) -{ - if ( !pressed || max <= min || ( ev->buttons() != Qt::LeftButton ) ) - { - ev->ignore(); - return; - } - ev->accept(); - - setValueUser( mapToValue( ev->pos() ) ); - update(); -} - -void FloatSlider::mouseReleaseEvent( QMouseEvent * ev ) -{ - if ( ev->button() != Qt::LeftButton ) - { - ev->ignore(); - return; - } - ev->accept(); - - pressed = false; - update(); -} - -float FloatSlider::mapToValue( const QPoint & p ) const -{ - QStyleOptionSlider opt = getStyleOption(); - QRect gr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); - QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); - int sliderMin, sliderLen, sliderPos; - - if ( ori == Qt::Horizontal ) - { - sliderMin = gr.x() + sr.width() / 2; - sliderLen = gr.width() - sr.width(); - sliderPos = p.x(); - } - else - { - sliderMin = gr.y() + sr.height() / 2; - sliderLen = gr.height() - sr.height(); - sliderPos = height() - p.y(); - } - if ( sliderPos <= sliderMin ) - return min; - if ( sliderPos >= sliderMin + sliderLen ) - return max; - return min + ( min != max ? ( float( sliderPos - sliderMin ) / float( sliderLen ) * ( max - min ) ) : 0 ); -} - -QSize FloatSlider::sizeHint() const -{ - QStyleOptionSlider opt = getStyleOption(); - int w = style()->pixelMetric( QStyle::PM_SliderThickness, &opt, this ); - int h = 84; - if ( ori == Qt::Horizontal ) - { - int x = h; - h = w; - w = x; - } - return style()->sizeFromContents( QStyle::CT_Slider, &opt, QSize( w, h ), this ).expandedTo( QApplication::globalStrut() ); -} - -QSize FloatSlider::minimumSizeHint() const -{ - QSize s = sizeHint(); - QStyleOptionSlider opt = getStyleOption(); - int length = style()->pixelMetric( QStyle::PM_SliderLength, &opt, this ); - if ( ori == Qt::Horizontal ) - s.setWidth( length ); - else - s.setHeight( length ); - return s; -} - -AlphaSlider::AlphaSlider( Qt::Orientation o ) - : FloatSlider( o ) -{ - setRange( 0, 1.0 ); - setValue( 1.0 ); -} - -QSize AlphaSlider::sizeHint() const -{ - return FloatSlider::sizeHint() * 1.0; -} - -void AlphaSlider::setColor( const QColor & c ) -{ - color0 = c; - color1 = c; - color0.setAlphaF( 0.0 ); - color1.setAlphaF( 1.0 ); - - update(); -} - -void AlphaSlider::paintEvent( QPaintEvent * e ) -{ - int w2 = width() / 2; - int h2 = height() / 2; - - QPoint points[2]; - if ( orientation() == Qt::Vertical ) - { - points[0] = QPoint( w2, height() ); - points[1] = QPoint( w2, 0 ); - } - else - { - points[0] = QPoint( 0, h2 ); - points[1] = QPoint( width(), h2 ); - } - - QLinearGradient agrad = QLinearGradient( points[0], points[1] ); - agrad.setColorAt( 0.0, color0 ); - agrad.setColorAt( 1.0, color1 ); - - QPainter p; - p.begin( this ); - p.fillRect( rect(), agrad ); - p.end(); - - FloatSlider::paintEvent( e ); -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "floatslider.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "floatedit.h" + +#include + +#define VAL_HEIGHT 12 + +FloatSliderEditBox::FloatSliderEditBox( QWidget * parent ) + : QFrame( parent, Qt::Popup | Qt::FramelessWindowHint ) +{ + setVisible( false ); + setFrameShadow( QFrame::Raised ); + setFrameShape( QFrame::StyledPanel ); + setLineWidth( 0 ); + setMaximumWidth( 100 ); + + QVBoxLayout * layout = new QVBoxLayout(); + layout->setMargin( 4 ); + layout->setSpacing( 2 ); + setLayout( layout ); +} + +void FloatSliderEditBox::addWidget( QWidget * w ) +{ + layout()->addWidget( w ); +} + +void FloatSliderEditBox::show( const QPoint & pos ) +{ + if( isVisible() ) { + return; + } + + move( pos ); + QWidget::show(); + setFocus( Qt::PopupFocusReason ); + + connect( QApplication::instance(), SIGNAL( focusChanged( QWidget *, QWidget * ) ), this, SLOT( focusChanged( QWidget *, QWidget * ) ) ); +} + +void FloatSliderEditBox::hide() +{ + if( !isVisible() ) { + return; + } + + disconnect( QApplication::instance(), SIGNAL( focusChanged( QWidget *, QWidget * ) ), this, SLOT( focusChanged( QWidget *, QWidget * ) ) ); + + QWidget::hide(); +} + +void FloatSliderEditBox::focusChanged( QWidget * oldW, QWidget * newW ) +{ + if( newW == this ) { + return; + } + + if( layout()->indexOf( newW ) > -1 ) { + return; + } + + hide(); +} + +FloatSlider::FloatSlider( Qt::Orientation o, bool showValue, bool isEditor ) + : QWidget(), val( 0.5 ), min( 0 ), max( 1.0 ), ori( o ), pressed( false ) +{ + QSizePolicy sp( QSizePolicy::Expanding, QSizePolicy::Fixed ); + if ( ori == Qt::Vertical ) + sp.transpose(); + setSizePolicy( sp ); + + showVal = showValue; + editVal = showVal && isEditor; + + if( showVal ) { + QFont fnt( "Arial", -1, QFont::Normal ); + fnt.setStyleStrategy( QFont::PreferAntialias ); + fnt.setPixelSize( VAL_HEIGHT - 2 ); + setFont( fnt ); + } + + if( editVal ) { + setToolTip( tr("Click value to edit.") ); + } + + editBox = new FloatSliderEditBox( this ); +} + +void FloatSlider::setValue( float v ) +{ + if ( v < min ) + v = min; + if ( v > max ) + v = max; + + if ( val != v ) + { + val = v; + update(); + } +} + +void FloatSlider::setValueUser( float v ) +{ + if ( v < min ) + v = min; + if ( v > max ) + v = max; + + if ( val != v ) + { + val = v; + update(); + emit valueChanged( val ); + } +} + +void FloatSlider::setRange( float mn, float mx ) +{ + if ( mn > mx ) + mx = mn; + + if ( min != mn || max != mx ) + { + min = mn; + if ( val < min ) + setValue( min ); + + max = mx; + if ( val > max ) + setValue( max ); + + update(); + } +} + +void FloatSlider::set( float v, float mn, float mx ) +{ + setRange( mn, mx ); + setValue( v ); +} + +void FloatSlider::setOrientation( Qt::Orientation o ) +{ + if ( ori != o ) + { + ori = o; + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy( sp ); + updateGeometry(); + update(); + } +} + +void FloatSlider::addEditor( QWidget * editWidget ) { + editBox->addWidget( editWidget ); +} + +QStyleOptionSlider FloatSlider::getStyleOption() const +{ + QStyleOptionSlider opt; + /* + opt.init(q); + opt.orientation = orientation; + opt.maximum = maximum; + opt.minimum = minimum; + opt.tickPosition = (QSlider::TickPosition)tickPosition; + opt.tickInterval = tickInterval; + opt.upsideDown = (orientation == Qt::Horizontal) ? + (invertedAppearance != (opt.direction == Qt::RightToLeft)) + : (!invertedAppearance); + opt.direction = Qt::LeftToRight; // we use the upsideDown option instead + opt.sliderPosition = position; + opt.sliderValue = value; + opt.singleStep = singleStep; + opt.pageStep = pageStep; + if (orientation == Qt::Horizontal) + opt.state |= QStyle::State_Horizontal; + */ + + opt.initFrom( this ); + + opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; + opt.activeSubControls = QStyle::SC_None; + + if( showVal ) { + int w = fontMetrics().width( "0.000" ); +#pragma message("NOTICE: Qt Bugfix is needed here, see http://pastebin.mozilla.org/101393") + opt.rect.adjust( (6*w)/10, VAL_HEIGHT, (-6*w)/10, 0 ); + } + + opt.maximum = INT_MAX - 1; + opt.minimum = 0; + opt.orientation = ori; + opt.pageStep = 10; + opt.singleStep = 1; + opt.sliderValue = ( max != min ) ? int( 1.0f * ( val - min ) / ( max - min ) * opt.maximum ) : 0; + opt.sliderPosition = opt.sliderValue; + opt.tickPosition = QSlider::NoTicks; + opt.direction = Qt::LeftToRight; + + /* upside down for vertical slider; zero at bottom position */ + opt.upsideDown = (ori == Qt::Vertical); + + return opt; + +} + +void FloatSlider::paintEvent( QPaintEvent * e ) +{ + QPainter p( this ); + QStyleOptionSlider opt = getStyleOption(); + + if ( pressed ) + { + opt.activeSubControls = QStyle::SC_SliderHandle; + opt.state |= QStyle::State_Sunken; + } + + if( showVal ) { + QString t = QString().number( val, 'f', 3 ); + QRect tr = style()->subControlRect( QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this ); + tr.adjust( -opt.rect.left()+2, -VAL_HEIGHT, opt.rect.left()-2, -tr.height()-1 ); + + p.drawText( tr, t, QTextOption( Qt::AlignCenter ) ); + } + + style()->drawComplexControl( QStyle::CC_Slider, &opt, &p, this ); +} + +void FloatSlider::mousePressEvent( QMouseEvent * ev ) +{ + if ( max <= min || ( ev->buttons() != Qt::LeftButton ) ) + { + ev->ignore(); + return; + } + ev->accept(); + + if( editVal && QRect( 0, 0, width(), VAL_HEIGHT ).contains( ev->pos() ) ) { + editBox->show( this->mapToGlobal( ev->pos() ) ); + } + else { + pressed = true; + + setValueUser( mapToValue( ev->pos() ) ); + update(); + } +} + +void FloatSlider::mouseMoveEvent( QMouseEvent * ev ) +{ + if ( !pressed || max <= min || ( ev->buttons() != Qt::LeftButton ) ) + { + ev->ignore(); + return; + } + ev->accept(); + + setValueUser( mapToValue( ev->pos() ) ); + update(); +} + +void FloatSlider::mouseReleaseEvent( QMouseEvent * ev ) +{ + if ( ev->button() != Qt::LeftButton ) + { + ev->ignore(); + return; + } + ev->accept(); + + pressed = false; + update(); +} + +float FloatSlider::mapToValue( const QPoint & p ) const +{ + QStyleOptionSlider opt = getStyleOption(); + QRect gr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); + QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); + int sliderMin, sliderLen, sliderPos; + + if ( ori == Qt::Horizontal ) + { + sliderMin = gr.x() + sr.width() / 2; + sliderLen = gr.width() - sr.width(); + sliderPos = p.x(); + } + else + { + sliderMin = gr.y() + sr.height() / 2; + sliderLen = gr.height() - sr.height(); + sliderPos = height() - p.y(); + } + if ( sliderPos <= sliderMin ) + return min; + if ( sliderPos >= sliderMin + sliderLen ) + return max; + return min + ( min != max ? ( float( sliderPos - sliderMin ) / float( sliderLen ) * ( max - min ) ) : 0 ); +} + +QSize FloatSlider::sizeHint() const +{ + QStyleOptionSlider opt = getStyleOption(); + int w = style()->pixelMetric( QStyle::PM_SliderThickness, &opt, this ); + int h = 84; + if ( ori == Qt::Horizontal ) + { + int x = h; + h = w; + w = x; + } + return style()->sizeFromContents( QStyle::CT_Slider, &opt, QSize( w, h ), this ).expandedTo( QApplication::globalStrut() ); +} + +QSize FloatSlider::minimumSizeHint() const +{ + QSize s = sizeHint(); + QStyleOptionSlider opt = getStyleOption(); + int length = style()->pixelMetric( QStyle::PM_SliderLength, &opt, this ); + if ( ori == Qt::Horizontal ) + s.setWidth( length ); + else + s.setHeight( length ); + return s; +} + +AlphaSlider::AlphaSlider( Qt::Orientation o ) + : FloatSlider( o ) +{ + setRange( 0, 1.0 ); + setValue( 1.0 ); +} + +QSize AlphaSlider::sizeHint() const +{ + return FloatSlider::sizeHint() * 1.0; +} + +void AlphaSlider::setColor( const QColor & c ) +{ + color0 = c; + color1 = c; + color0.setAlphaF( 0.0 ); + color1.setAlphaF( 1.0 ); + + update(); +} + +void AlphaSlider::paintEvent( QPaintEvent * e ) +{ + int w2 = width() / 2; + int h2 = height() / 2; + + QPoint points[2]; + if ( orientation() == Qt::Vertical ) + { + points[0] = QPoint( w2, height() ); + points[1] = QPoint( w2, 0 ); + } + else + { + points[0] = QPoint( 0, h2 ); + points[1] = QPoint( width(), h2 ); + } + + QLinearGradient agrad = QLinearGradient( points[0], points[1] ); + agrad.setColorAt( 0.0, color0 ); + agrad.setColorAt( 1.0, color1 ); + + QPainter p; + p.begin( this ); + p.fillRect( rect(), agrad ); + p.end(); + + FloatSlider::paintEvent( e ); +} + diff --git a/widgets/floatslider.h b/widgets/floatslider.h index 109f2214a..b0c1503cf 100644 --- a/widgets/floatslider.h +++ b/widgets/floatslider.h @@ -1,122 +1,122 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef FLOATSLIDER_H -#define FLOATSLIDER_H - -#include -#include - -class FloatEdit; - -class FloatSliderEditBox : public QFrame -{ - Q_OBJECT -public: - FloatSliderEditBox( QWidget * = NULL ); - - void addWidget( QWidget * ); - -public slots: - void show( const QPoint & ); - void hide(); - void focusChanged( QWidget *, QWidget * ); -}; - -class FloatSlider : public QWidget -{ - Q_OBJECT -public: - FloatSlider( Qt::Orientation = Qt::Horizontal, bool = false, bool = false ); - - float value() const { return val; } - - float minimum() const { return min; } - float maximum() const { return max; } - - Qt::Orientation orientation() const { return ori; } - void setOrientation( Qt::Orientation o ); - - void addEditor( QWidget * ); - - QSize sizeHint() const; - QSize minimumSizeHint() const; - -signals: - void valueChanged( float ); - -public slots: - void setValue( float val ); - void setRange( float min, float max ); - void set( float val, float min, float max ); - -protected: - void setValueUser( float val ); - - void paintEvent( QPaintEvent * ); - void mousePressEvent( QMouseEvent * ); - void mouseMoveEvent( QMouseEvent * ); - void mouseReleaseEvent( QMouseEvent * ); - - float mapToValue( const QPoint & p ) const; - - class QStyleOptionSlider getStyleOption() const; - - float val, min, max; - Qt::Orientation ori; - - bool pressed; - bool showVal; - bool editVal; - - FloatSliderEditBox * editBox; -}; - -class AlphaSlider : public FloatSlider -{ - Q_OBJECT -public: - AlphaSlider( Qt::Orientation o = Qt::Horizontal ); - - QSize sizeHint() const; - -public slots: - void setColor( const QColor & c ); - -protected: - void paintEvent( QPaintEvent * e ); - - QColor color0; - QColor color1; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef FLOATSLIDER_H +#define FLOATSLIDER_H + +#include +#include + +class FloatEdit; + +class FloatSliderEditBox : public QFrame +{ + Q_OBJECT +public: + FloatSliderEditBox( QWidget * = NULL ); + + void addWidget( QWidget * ); + +public slots: + void show( const QPoint & ); + void hide(); + void focusChanged( QWidget *, QWidget * ); +}; + +class FloatSlider : public QWidget +{ + Q_OBJECT +public: + FloatSlider( Qt::Orientation = Qt::Horizontal, bool = false, bool = false ); + + float value() const { return val; } + + float minimum() const { return min; } + float maximum() const { return max; } + + Qt::Orientation orientation() const { return ori; } + void setOrientation( Qt::Orientation o ); + + void addEditor( QWidget * ); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + +signals: + void valueChanged( float ); + +public slots: + void setValue( float val ); + void setRange( float min, float max ); + void set( float val, float min, float max ); + +protected: + void setValueUser( float val ); + + void paintEvent( QPaintEvent * ); + void mousePressEvent( QMouseEvent * ); + void mouseMoveEvent( QMouseEvent * ); + void mouseReleaseEvent( QMouseEvent * ); + + float mapToValue( const QPoint & p ) const; + + class QStyleOptionSlider getStyleOption() const; + + float val, min, max; + Qt::Orientation ori; + + bool pressed; + bool showVal; + bool editVal; + + FloatSliderEditBox * editBox; +}; + +class AlphaSlider : public FloatSlider +{ + Q_OBJECT +public: + AlphaSlider( Qt::Orientation o = Qt::Horizontal ); + + QSize sizeHint() const; + +public slots: + void setColor( const QColor & c ); + +protected: + void paintEvent( QPaintEvent * e ); + + QColor color0; + QColor color1; +}; + +#endif diff --git a/widgets/groupbox.cpp b/widgets/groupbox.cpp index ceb86f362..76fbab222 100644 --- a/widgets/groupbox.cpp +++ b/widgets/groupbox.cpp @@ -1,83 +1,83 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "groupbox.h" - -GroupBox::GroupBox( const QString & title, Qt::Orientation o ) : QGroupBox( title ) -{ - lay.push( new QBoxLayout( o2d( o ), this ) ); -} - -GroupBox::~GroupBox() -{ -} - -void GroupBox::addWidget( QWidget * widget, int stretch, Qt::Alignment alignment ) -{ - lay.top()->addWidget( widget, stretch, alignment ); -} - -QWidget * GroupBox::pushLayout( const QString & name, Qt::Orientation o, int stretch, Qt::Alignment alignment ) -{ - QGroupBox * grp = new QGroupBox( name ); - lay.top()->addWidget( grp, stretch, alignment ); - QBoxLayout * l = new QBoxLayout( o2d( o ) ); - grp->setLayout( l ); - lay.push( l ); - return grp; -} - -void GroupBox::pushLayout( Qt::Orientation o, int stretch ) -{ - QBoxLayout * l = new QBoxLayout( o2d( o ) ); - lay.top()->addLayout( l, stretch ); - lay.push( l ); -} - -void GroupBox::popLayout() -{ - if ( lay.count() > 1 ) - lay.pop(); -} - -QBoxLayout::Direction GroupBox::o2d( Qt::Orientation o ) -{ - switch ( o ) - { - case Qt::Vertical: - return QBoxLayout::TopToBottom; - case Qt::Horizontal: - default: - return QBoxLayout::LeftToRight; - } -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "groupbox.h" + +GroupBox::GroupBox( const QString & title, Qt::Orientation o ) : QGroupBox( title ) +{ + lay.push( new QBoxLayout( o2d( o ), this ) ); +} + +GroupBox::~GroupBox() +{ +} + +void GroupBox::addWidget( QWidget * widget, int stretch, Qt::Alignment alignment ) +{ + lay.top()->addWidget( widget, stretch, alignment ); +} + +QWidget * GroupBox::pushLayout( const QString & name, Qt::Orientation o, int stretch, Qt::Alignment alignment ) +{ + QGroupBox * grp = new QGroupBox( name ); + lay.top()->addWidget( grp, stretch, alignment ); + QBoxLayout * l = new QBoxLayout( o2d( o ) ); + grp->setLayout( l ); + lay.push( l ); + return grp; +} + +void GroupBox::pushLayout( Qt::Orientation o, int stretch ) +{ + QBoxLayout * l = new QBoxLayout( o2d( o ) ); + lay.top()->addLayout( l, stretch ); + lay.push( l ); +} + +void GroupBox::popLayout() +{ + if ( lay.count() > 1 ) + lay.pop(); +} + +QBoxLayout::Direction GroupBox::o2d( Qt::Orientation o ) +{ + switch ( o ) + { + case Qt::Vertical: + return QBoxLayout::TopToBottom; + case Qt::Horizontal: + default: + return QBoxLayout::LeftToRight; + } +} + diff --git a/widgets/groupbox.h b/widgets/groupbox.h index a0084cb9a..a17f883f3 100644 --- a/widgets/groupbox.h +++ b/widgets/groupbox.h @@ -1,57 +1,57 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef GROUPBOX_H -#define GROUPBOX_H - -#include -#include -#include - -class GroupBox : public QGroupBox -{ - QStack lay; -public: - GroupBox( const QString & title, Qt::Orientation o ); - ~GroupBox(); - - void addWidget( QWidget * widget, int stretch = 0, Qt::Alignment alignment = 0 ); - - QWidget * pushLayout( const QString & name, Qt::Orientation o, int stretch = 0, Qt::Alignment alignment = 0 ); - void pushLayout( Qt::Orientation o, int stretch = 0 ); - - void popLayout(); - - static QBoxLayout::Direction o2d( Qt::Orientation o ); -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef GROUPBOX_H +#define GROUPBOX_H + +#include +#include +#include + +class GroupBox : public QGroupBox +{ + QStack lay; +public: + GroupBox( const QString & title, Qt::Orientation o ); + ~GroupBox(); + + void addWidget( QWidget * widget, int stretch = 0, Qt::Alignment alignment = 0 ); + + QWidget * pushLayout( const QString & name, Qt::Orientation o, int stretch = 0, Qt::Alignment alignment = 0 ); + void pushLayout( Qt::Orientation o, int stretch = 0 ); + + void popLayout(); + + static QBoxLayout::Direction o2d( Qt::Orientation o ); +}; + +#endif diff --git a/widgets/nifeditors.cpp b/widgets/nifeditors.cpp index 303bd82e8..768de5958 100644 --- a/widgets/nifeditors.cpp +++ b/widgets/nifeditors.cpp @@ -1,399 +1,399 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "nifeditors.h" - -#include "../nifmodel.h" - -#include "colorwheel.h" -#include "floatslider.h" -#include "valueedit.h" - -#include -#include -#include -#include -#include - -NifBlockEditor::NifBlockEditor( NifModel * n, const QModelIndex & i, bool fireAndForget ) - : QWidget(), nif( n ), iBlock( i ) -{ - connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), - this, SLOT( nifDataChanged( const QModelIndex &, const QModelIndex & ) ) ); - connect( nif, SIGNAL( modelReset() ), this, SLOT( updateData() ) ); - connect( nif, SIGNAL( destroyed() ), this, SLOT( nifDestroyed() ) ); - - QVBoxLayout * layout = new QVBoxLayout(); - setLayout( layout ); - layouts.push( layout ); - - QModelIndex iName = nif->getIndex( iBlock, "Name" ); - if ( iName.isValid() ) - add( new NifLineEdit( nif, iName ) ); - - timer = new QTimer( this ); - connect( timer, SIGNAL( timeout() ), this, SLOT( updateData() ) ); - timer->setInterval( 0 ); - timer->setSingleShot( true ); - - if ( fireAndForget ) - { - setAttribute( Qt::WA_DeleteOnClose ); - - QPushButton * btAccept = new QPushButton( tr("Accept") ); - connect( btAccept, SIGNAL( clicked() ), this, SLOT( close() ) ); - layout->addWidget( btAccept ); - } - - setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); -} - -void NifBlockEditor::add( NifEditBox * box ) -{ - editors.append( box ); - if ( layouts.count() == 1 && testAttribute( Qt::WA_DeleteOnClose ) ) - layouts.top()->insertWidget( layouts.top()->count() - 1, box ); - else - layouts.top()->addWidget( box ); -} - -void NifBlockEditor::pushLayout( QBoxLayout * lay, const QString & name ) -{ - if ( name.isEmpty() ) - { - if ( layouts.count() == 1 && testAttribute( Qt::WA_DeleteOnClose ) ) - layouts.top()->insertLayout( layouts.top()->count() - 1, lay ); - else - layouts.top()->addLayout( lay ); - layouts.push( lay ); - } - else - { - QGroupBox * group = new QGroupBox; - group->setTitle( name ); - group->setLayout( lay ); - - if ( layouts.count() == 1 && testAttribute( Qt::WA_DeleteOnClose ) ) - layouts.top()->insertWidget( layouts.top()->count() - 1, group ); - else - layouts.top()->addWidget( group ); - - layouts.push( lay ); - } -} - -void NifBlockEditor::popLayout() -{ - if ( layouts.count() > 1 ) - layouts.pop(); -} - -void NifBlockEditor::showEvent( QShowEvent * ) -{ - updateData(); -} - -void NifBlockEditor::nifDestroyed() -{ - nif = 0; - setDisabled( true ); - if ( testAttribute( Qt::WA_DeleteOnClose ) ) - close(); -} - -void NifBlockEditor::nifDataChanged( const QModelIndex & begin, const QModelIndex & end ) -{ - if ( nif && iBlock.isValid() ) - { - if ( nif->getBlockOrHeader( begin ) == iBlock || nif->getBlockOrHeader( end ) == iBlock ) - { - if ( ! timer->isActive() ) - timer->start(); - } - } - else - nifDestroyed(); -} - -void NifBlockEditor::updateData() -{ - if ( nif && iBlock.isValid() ) - { - QString x = nif->itemName( iBlock ); - QModelIndex iName = nif->getIndex( iBlock, "Name" ); - if ( iName.isValid() ) - x += " - " + nif->get( iName ); - setWindowTitle( x ); - - foreach ( NifEditBox * box, editors ) - { - box->setEnabled( box->getIndex().isValid() ); - box->updateData( nif ); - } - } - else - nifDestroyed(); - - if ( timer->isActive() ) - timer->stop(); -} - - -NifEditBox::NifEditBox( NifModel * n, const QModelIndex & i ) - : QGroupBox(), nif( n ), index( i ) -{ - setTitle( nif->itemName( index ) ); -} - -QLayout * NifEditBox::getLayout() -{ - QLayout * lay = QGroupBox::layout(); - if ( ! lay ) - { - lay = new QHBoxLayout; - setLayout( lay ); - } - return lay; -} - -void NifEditBox::applyData() -{ - if ( nif && index.isValid() ) - { - applyData( nif ); - } -} - - -NifFloatSlider::NifFloatSlider( NifModel * nif, const QModelIndex & index, float min, float max ) - : NifEditBox( nif, index ) -{ - getLayout()->addWidget( slider = new FloatSlider() ); - slider->setRange( min, max ); - connect( slider, SIGNAL( valueChanged( float ) ), this, SLOT( applyData() ) ); -} - -void NifFloatSlider::updateData( NifModel * nif ) -{ - slider->setValue( nif->get( index ) ); -} - -void NifFloatSlider::applyData( NifModel * nif ) -{ - nif->set( index, slider->value() ); -} - -NifFloatEdit::NifFloatEdit( NifModel * nif, const QModelIndex & index, float min, float max ) - : NifEditBox( nif, index ) -{ - getLayout()->addWidget( spinbox = new QDoubleSpinBox() ); - spinbox->setRange( min, max ); - spinbox->setDecimals( 4 ); - connect( spinbox, SIGNAL( valueChanged( double ) ), this, SLOT( applyData() ) ); -} - -void NifFloatEdit::updateData( NifModel * nif ) -{ - spinbox->setValue( nif->get( index ) ); -} - -void NifFloatEdit::applyData( NifModel * nif ) -{ - nif->set( index, spinbox->value() ); -} - -NifLineEdit::NifLineEdit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ) -{ - getLayout()->addWidget( line = new QLineEdit() ); - connect( line, SIGNAL( textEdited( const QString & ) ), this, SLOT( applyData() ) ); -} - -void NifLineEdit::updateData( NifModel * nif ) -{ - line->setText( nif->get( index ) ); -} - -void NifLineEdit::applyData( NifModel * nif ) -{ - nif->set( index, line->text() ); -} - - -NifColorEdit::NifColorEdit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ) -{ - getLayout()->addWidget( color = new ColorWheel() ); - color->setSizeHint( QSize( 140, 140 ) ); - connect( color, SIGNAL( sigColor( const QColor & ) ), this, SLOT( applyData() ) ); - if ( nif->getValue( index ).type() == NifValue::tColor4 ) - { - getLayout()->addWidget( alpha = new AlphaSlider() ); - connect( alpha, SIGNAL( valueChanged( float ) ), this, SLOT( applyData() ) ); - } - else - alpha = 0; -} - -void NifColorEdit::updateData( NifModel * nif ) -{ - if ( alpha ) - { - color->setColor( nif->get( index ).toQColor() ); - alpha->setValue( nif->get( index )[3] ); - } - else - { - color->setColor( nif->get( index ).toQColor() ); - } -} - -void NifColorEdit::applyData( NifModel * nif ) -{ - if ( alpha ) - { - Color4 c4; - c4.fromQColor( color->getColor() ); - c4[3] = alpha->value(); - nif->set( index, c4 ); - } - else - { - Color3 c3; - c3.fromQColor( color->getColor() ); - nif->set( index, c3 ); - } -} - -NifVectorEdit::NifVectorEdit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ) -{ - getLayout()->addWidget( vector = new VectorEdit() ); - connect( vector, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); -} - -void NifVectorEdit::updateData( NifModel * nif ) -{ - NifValue val = nif->getValue( index ); - if ( val.type() == NifValue::tVector3 ) - vector->setVector3( val.get() ); - else if ( val.type() == NifValue::tVector2 ) - vector->setVector2( val.get() ); -} - -void NifVectorEdit::applyData( NifModel * nif ) -{ - NifValue::Type type = nif->getValue( index ).type(); - if ( type == NifValue::tVector3 ) - nif->set( index, vector->getVector3() ); - else if ( type == NifValue::tVector2 ) - nif->set( index, vector->getVector2() ); -} - -NifRotationEdit::NifRotationEdit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ) -{ - getLayout()->addWidget( rotation = new RotationEdit() ); - connect( rotation, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); -} - -void NifRotationEdit::updateData( NifModel * nif ) -{ - NifValue val = nif->getValue( index ); - if ( val.type() == NifValue::tMatrix ) - rotation->setMatrix( val.get() ); - else if ( val.type() == NifValue::tQuat || val.type() == NifValue::tQuatXYZW ) - rotation->setQuat( val.get() ); -} - -void NifRotationEdit::applyData( NifModel * nif ) -{ - NifValue::Type type = nif->getValue( index ).type(); - if ( type == NifValue::tMatrix ) - nif->set( index, rotation->getMatrix() ); - else if ( type == NifValue::tQuat || type == NifValue::tQuatXYZW ) - nif->set( index, rotation->getQuat() ); -} - -NifMatrix4Edit::NifMatrix4Edit( NifModel * nif, const QModelIndex & index ) - : NifEditBox( nif, index ), setting( false ) -{ - QBoxLayout * vbox = new QVBoxLayout; - setLayout( vbox ); - - QGroupBox * group = new QGroupBox; - vbox->addWidget( group ); - group->setTitle( tr("Translation") ); - group->setLayout( new QHBoxLayout ); - group->layout()->addWidget( translation = new VectorEdit() ); - connect( translation, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); - - group = new QGroupBox; - vbox->addWidget( group ); - group->setTitle( tr("Rotation") ); - group->setLayout( new QHBoxLayout ); - group->layout()->addWidget( rotation = new RotationEdit() ); - connect( rotation, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); - - group = new QGroupBox; - vbox->addWidget( group ); - group->setTitle( tr("Scale") ); - group->setLayout( new QHBoxLayout ); - group->layout()->addWidget( scale = new VectorEdit() ); - connect( scale, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); -} - -void NifMatrix4Edit::updateData( NifModel * nif ) -{ - if ( setting ) - return; - Matrix4 mtx = nif->get( index ); - - Vector3 t, s; - Matrix r; - - mtx.decompose( t, r, s ); - - translation->setVector3( t ); - rotation->setMatrix( r ); - scale->setVector3( s ); -} - -void NifMatrix4Edit::applyData( NifModel * nif ) -{ - setting = true; - Matrix4 mtx; - mtx.compose( translation->getVector3(), rotation->getMatrix(), scale->getVector3() ); - nif->set( index, mtx ); - setting = false; -} - +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "nifeditors.h" + +#include "../nifmodel.h" + +#include "colorwheel.h" +#include "floatslider.h" +#include "valueedit.h" + +#include +#include +#include +#include +#include + +NifBlockEditor::NifBlockEditor( NifModel * n, const QModelIndex & i, bool fireAndForget ) + : QWidget(), nif( n ), iBlock( i ) +{ + connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), + this, SLOT( nifDataChanged( const QModelIndex &, const QModelIndex & ) ) ); + connect( nif, SIGNAL( modelReset() ), this, SLOT( updateData() ) ); + connect( nif, SIGNAL( destroyed() ), this, SLOT( nifDestroyed() ) ); + + QVBoxLayout * layout = new QVBoxLayout(); + setLayout( layout ); + layouts.push( layout ); + + QModelIndex iName = nif->getIndex( iBlock, "Name" ); + if ( iName.isValid() ) + add( new NifLineEdit( nif, iName ) ); + + timer = new QTimer( this ); + connect( timer, SIGNAL( timeout() ), this, SLOT( updateData() ) ); + timer->setInterval( 0 ); + timer->setSingleShot( true ); + + if ( fireAndForget ) + { + setAttribute( Qt::WA_DeleteOnClose ); + + QPushButton * btAccept = new QPushButton( tr("Accept") ); + connect( btAccept, SIGNAL( clicked() ), this, SLOT( close() ) ); + layout->addWidget( btAccept ); + } + + setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); +} + +void NifBlockEditor::add( NifEditBox * box ) +{ + editors.append( box ); + if ( layouts.count() == 1 && testAttribute( Qt::WA_DeleteOnClose ) ) + layouts.top()->insertWidget( layouts.top()->count() - 1, box ); + else + layouts.top()->addWidget( box ); +} + +void NifBlockEditor::pushLayout( QBoxLayout * lay, const QString & name ) +{ + if ( name.isEmpty() ) + { + if ( layouts.count() == 1 && testAttribute( Qt::WA_DeleteOnClose ) ) + layouts.top()->insertLayout( layouts.top()->count() - 1, lay ); + else + layouts.top()->addLayout( lay ); + layouts.push( lay ); + } + else + { + QGroupBox * group = new QGroupBox; + group->setTitle( name ); + group->setLayout( lay ); + + if ( layouts.count() == 1 && testAttribute( Qt::WA_DeleteOnClose ) ) + layouts.top()->insertWidget( layouts.top()->count() - 1, group ); + else + layouts.top()->addWidget( group ); + + layouts.push( lay ); + } +} + +void NifBlockEditor::popLayout() +{ + if ( layouts.count() > 1 ) + layouts.pop(); +} + +void NifBlockEditor::showEvent( QShowEvent * ) +{ + updateData(); +} + +void NifBlockEditor::nifDestroyed() +{ + nif = 0; + setDisabled( true ); + if ( testAttribute( Qt::WA_DeleteOnClose ) ) + close(); +} + +void NifBlockEditor::nifDataChanged( const QModelIndex & begin, const QModelIndex & end ) +{ + if ( nif && iBlock.isValid() ) + { + if ( nif->getBlockOrHeader( begin ) == iBlock || nif->getBlockOrHeader( end ) == iBlock ) + { + if ( ! timer->isActive() ) + timer->start(); + } + } + else + nifDestroyed(); +} + +void NifBlockEditor::updateData() +{ + if ( nif && iBlock.isValid() ) + { + QString x = nif->itemName( iBlock ); + QModelIndex iName = nif->getIndex( iBlock, "Name" ); + if ( iName.isValid() ) + x += " - " + nif->get( iName ); + setWindowTitle( x ); + + foreach ( NifEditBox * box, editors ) + { + box->setEnabled( box->getIndex().isValid() ); + box->updateData( nif ); + } + } + else + nifDestroyed(); + + if ( timer->isActive() ) + timer->stop(); +} + + +NifEditBox::NifEditBox( NifModel * n, const QModelIndex & i ) + : QGroupBox(), nif( n ), index( i ) +{ + setTitle( nif->itemName( index ) ); +} + +QLayout * NifEditBox::getLayout() +{ + QLayout * lay = QGroupBox::layout(); + if ( ! lay ) + { + lay = new QHBoxLayout; + setLayout( lay ); + } + return lay; +} + +void NifEditBox::applyData() +{ + if ( nif && index.isValid() ) + { + applyData( nif ); + } +} + + +NifFloatSlider::NifFloatSlider( NifModel * nif, const QModelIndex & index, float min, float max ) + : NifEditBox( nif, index ) +{ + getLayout()->addWidget( slider = new FloatSlider() ); + slider->setRange( min, max ); + connect( slider, SIGNAL( valueChanged( float ) ), this, SLOT( applyData() ) ); +} + +void NifFloatSlider::updateData( NifModel * nif ) +{ + slider->setValue( nif->get( index ) ); +} + +void NifFloatSlider::applyData( NifModel * nif ) +{ + nif->set( index, slider->value() ); +} + +NifFloatEdit::NifFloatEdit( NifModel * nif, const QModelIndex & index, float min, float max ) + : NifEditBox( nif, index ) +{ + getLayout()->addWidget( spinbox = new QDoubleSpinBox() ); + spinbox->setRange( min, max ); + spinbox->setDecimals( 4 ); + connect( spinbox, SIGNAL( valueChanged( double ) ), this, SLOT( applyData() ) ); +} + +void NifFloatEdit::updateData( NifModel * nif ) +{ + spinbox->setValue( nif->get( index ) ); +} + +void NifFloatEdit::applyData( NifModel * nif ) +{ + nif->set( index, spinbox->value() ); +} + +NifLineEdit::NifLineEdit( NifModel * nif, const QModelIndex & index ) + : NifEditBox( nif, index ) +{ + getLayout()->addWidget( line = new QLineEdit() ); + connect( line, SIGNAL( textEdited( const QString & ) ), this, SLOT( applyData() ) ); +} + +void NifLineEdit::updateData( NifModel * nif ) +{ + line->setText( nif->get( index ) ); +} + +void NifLineEdit::applyData( NifModel * nif ) +{ + nif->set( index, line->text() ); +} + + +NifColorEdit::NifColorEdit( NifModel * nif, const QModelIndex & index ) + : NifEditBox( nif, index ) +{ + getLayout()->addWidget( color = new ColorWheel() ); + color->setSizeHint( QSize( 140, 140 ) ); + connect( color, SIGNAL( sigColor( const QColor & ) ), this, SLOT( applyData() ) ); + if ( nif->getValue( index ).type() == NifValue::tColor4 ) + { + getLayout()->addWidget( alpha = new AlphaSlider() ); + connect( alpha, SIGNAL( valueChanged( float ) ), this, SLOT( applyData() ) ); + } + else + alpha = 0; +} + +void NifColorEdit::updateData( NifModel * nif ) +{ + if ( alpha ) + { + color->setColor( nif->get( index ).toQColor() ); + alpha->setValue( nif->get( index )[3] ); + } + else + { + color->setColor( nif->get( index ).toQColor() ); + } +} + +void NifColorEdit::applyData( NifModel * nif ) +{ + if ( alpha ) + { + Color4 c4; + c4.fromQColor( color->getColor() ); + c4[3] = alpha->value(); + nif->set( index, c4 ); + } + else + { + Color3 c3; + c3.fromQColor( color->getColor() ); + nif->set( index, c3 ); + } +} + +NifVectorEdit::NifVectorEdit( NifModel * nif, const QModelIndex & index ) + : NifEditBox( nif, index ) +{ + getLayout()->addWidget( vector = new VectorEdit() ); + connect( vector, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); +} + +void NifVectorEdit::updateData( NifModel * nif ) +{ + NifValue val = nif->getValue( index ); + if ( val.type() == NifValue::tVector3 ) + vector->setVector3( val.get() ); + else if ( val.type() == NifValue::tVector2 ) + vector->setVector2( val.get() ); +} + +void NifVectorEdit::applyData( NifModel * nif ) +{ + NifValue::Type type = nif->getValue( index ).type(); + if ( type == NifValue::tVector3 ) + nif->set( index, vector->getVector3() ); + else if ( type == NifValue::tVector2 ) + nif->set( index, vector->getVector2() ); +} + +NifRotationEdit::NifRotationEdit( NifModel * nif, const QModelIndex & index ) + : NifEditBox( nif, index ) +{ + getLayout()->addWidget( rotation = new RotationEdit() ); + connect( rotation, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); +} + +void NifRotationEdit::updateData( NifModel * nif ) +{ + NifValue val = nif->getValue( index ); + if ( val.type() == NifValue::tMatrix ) + rotation->setMatrix( val.get() ); + else if ( val.type() == NifValue::tQuat || val.type() == NifValue::tQuatXYZW ) + rotation->setQuat( val.get() ); +} + +void NifRotationEdit::applyData( NifModel * nif ) +{ + NifValue::Type type = nif->getValue( index ).type(); + if ( type == NifValue::tMatrix ) + nif->set( index, rotation->getMatrix() ); + else if ( type == NifValue::tQuat || type == NifValue::tQuatXYZW ) + nif->set( index, rotation->getQuat() ); +} + +NifMatrix4Edit::NifMatrix4Edit( NifModel * nif, const QModelIndex & index ) + : NifEditBox( nif, index ), setting( false ) +{ + QBoxLayout * vbox = new QVBoxLayout; + setLayout( vbox ); + + QGroupBox * group = new QGroupBox; + vbox->addWidget( group ); + group->setTitle( tr("Translation") ); + group->setLayout( new QHBoxLayout ); + group->layout()->addWidget( translation = new VectorEdit() ); + connect( translation, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); + + group = new QGroupBox; + vbox->addWidget( group ); + group->setTitle( tr("Rotation") ); + group->setLayout( new QHBoxLayout ); + group->layout()->addWidget( rotation = new RotationEdit() ); + connect( rotation, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); + + group = new QGroupBox; + vbox->addWidget( group ); + group->setTitle( tr("Scale") ); + group->setLayout( new QHBoxLayout ); + group->layout()->addWidget( scale = new VectorEdit() ); + connect( scale, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); +} + +void NifMatrix4Edit::updateData( NifModel * nif ) +{ + if ( setting ) + return; + Matrix4 mtx = nif->get( index ); + + Vector3 t, s; + Matrix r; + + mtx.decompose( t, r, s ); + + translation->setVector3( t ); + rotation->setMatrix( r ); + scale->setVector3( s ); +} + +void NifMatrix4Edit::applyData( NifModel * nif ) +{ + setting = true; + Matrix4 mtx; + mtx.compose( translation->getVector3(), rotation->getMatrix(), scale->getVector3() ); + nif->set( index, mtx ); + setting = false; +} + diff --git a/widgets/nifeditors.h b/widgets/nifeditors.h index 178f47d17..90c5ff585 100644 --- a/widgets/nifeditors.h +++ b/widgets/nifeditors.h @@ -1,190 +1,190 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef NIFEDITORS_H -#define NIFEDITORS_H - -#include -#include -#include -#include - -#include "../nifmodel.h" - -class NifEditBox : public QGroupBox -{ - Q_OBJECT -public: - NifEditBox( NifModel * nif, const QModelIndex & index ); - - virtual void updateData( NifModel * nif ) = 0; - virtual void applyData( NifModel * nif ) = 0; - - QModelIndex getIndex() const { return index; } - -protected slots: - void applyData(); - -protected: - QLayout * getLayout(); - - QPointer nif; - QPersistentModelIndex index; -}; - -class NifBlockEditor : public QWidget -{ - Q_OBJECT -public: - NifBlockEditor( NifModel * nif, const QModelIndex & block, bool fireAndForget = true ); - - void add( NifEditBox * ); - - void pushLayout( QBoxLayout *, const QString & name = QString() ); - void popLayout(); - - NifModel * getNif() { return nif; } - -protected slots: - void nifDataChanged( const QModelIndex &, const QModelIndex & ); - void nifDestroyed(); - void updateData(); - -protected: - void showEvent( QShowEvent * ); - - NifModel * nif; - QPersistentModelIndex iBlock; - - QList editors; - QStack layouts; - - class QTimer * timer; -}; - -class NifFloatSlider : public NifEditBox -{ - Q_OBJECT -public: - NifFloatSlider( NifModel * nif, const QModelIndex & index, float min, float max ); - - void updateData( NifModel * ); - void applyData( NifModel * ); - -protected: - class FloatSlider * slider; -}; - -class NifFloatEdit : public NifEditBox -{ - Q_OBJECT -public: - NifFloatEdit( NifModel * nif, const QModelIndex & index, float min = -10e8, float max = +10e8 ); - - void updateData( NifModel * ); - void applyData( NifModel * ); - -protected: - class QDoubleSpinBox * spinbox; -}; - -class NifLineEdit : public NifEditBox -{ - Q_OBJECT -public: - NifLineEdit( NifModel * nif, const QModelIndex & index ); - - void updateData( NifModel * ); - void applyData( NifModel * ); - -protected: - class QLineEdit * line; -}; - -class NifColorEdit : public NifEditBox -{ - Q_OBJECT -public: - NifColorEdit( NifModel * nif, const QModelIndex & index ); - - void updateData( NifModel * ); - void applyData( NifModel * ); - -protected: - class ColorWheel * color; - class AlphaSlider * alpha; -}; - -class NifVectorEdit : public NifEditBox -{ - Q_OBJECT -public: - NifVectorEdit( NifModel * nif, const QModelIndex & index ); - - void updateData( NifModel * ); - void applyData( NifModel * ); - -protected: - class VectorEdit * vector; -}; - -class NifRotationEdit : public NifEditBox -{ - Q_OBJECT -public: - NifRotationEdit( NifModel * nif, const QModelIndex & index ); - - void updateData( NifModel * ); - void applyData( NifModel * ); - -protected: - class RotationEdit * rotation; -}; - -class NifMatrix4Edit : public NifEditBox -{ - Q_OBJECT -public: - NifMatrix4Edit( NifModel * nif, const QModelIndex & index ); - - void updateData( NifModel * ); - void applyData( NifModel * ); - -protected: - class VectorEdit * translation; - class RotationEdit * rotation; - class VectorEdit * scale; - - bool setting; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef NIFEDITORS_H +#define NIFEDITORS_H + +#include +#include +#include +#include + +#include "../nifmodel.h" + +class NifEditBox : public QGroupBox +{ + Q_OBJECT +public: + NifEditBox( NifModel * nif, const QModelIndex & index ); + + virtual void updateData( NifModel * nif ) = 0; + virtual void applyData( NifModel * nif ) = 0; + + QModelIndex getIndex() const { return index; } + +protected slots: + void applyData(); + +protected: + QLayout * getLayout(); + + QPointer nif; + QPersistentModelIndex index; +}; + +class NifBlockEditor : public QWidget +{ + Q_OBJECT +public: + NifBlockEditor( NifModel * nif, const QModelIndex & block, bool fireAndForget = true ); + + void add( NifEditBox * ); + + void pushLayout( QBoxLayout *, const QString & name = QString() ); + void popLayout(); + + NifModel * getNif() { return nif; } + +protected slots: + void nifDataChanged( const QModelIndex &, const QModelIndex & ); + void nifDestroyed(); + void updateData(); + +protected: + void showEvent( QShowEvent * ); + + NifModel * nif; + QPersistentModelIndex iBlock; + + QList editors; + QStack layouts; + + class QTimer * timer; +}; + +class NifFloatSlider : public NifEditBox +{ + Q_OBJECT +public: + NifFloatSlider( NifModel * nif, const QModelIndex & index, float min, float max ); + + void updateData( NifModel * ); + void applyData( NifModel * ); + +protected: + class FloatSlider * slider; +}; + +class NifFloatEdit : public NifEditBox +{ + Q_OBJECT +public: + NifFloatEdit( NifModel * nif, const QModelIndex & index, float min = -10e8, float max = +10e8 ); + + void updateData( NifModel * ); + void applyData( NifModel * ); + +protected: + class QDoubleSpinBox * spinbox; +}; + +class NifLineEdit : public NifEditBox +{ + Q_OBJECT +public: + NifLineEdit( NifModel * nif, const QModelIndex & index ); + + void updateData( NifModel * ); + void applyData( NifModel * ); + +protected: + class QLineEdit * line; +}; + +class NifColorEdit : public NifEditBox +{ + Q_OBJECT +public: + NifColorEdit( NifModel * nif, const QModelIndex & index ); + + void updateData( NifModel * ); + void applyData( NifModel * ); + +protected: + class ColorWheel * color; + class AlphaSlider * alpha; +}; + +class NifVectorEdit : public NifEditBox +{ + Q_OBJECT +public: + NifVectorEdit( NifModel * nif, const QModelIndex & index ); + + void updateData( NifModel * ); + void applyData( NifModel * ); + +protected: + class VectorEdit * vector; +}; + +class NifRotationEdit : public NifEditBox +{ + Q_OBJECT +public: + NifRotationEdit( NifModel * nif, const QModelIndex & index ); + + void updateData( NifModel * ); + void applyData( NifModel * ); + +protected: + class RotationEdit * rotation; +}; + +class NifMatrix4Edit : public NifEditBox +{ + Q_OBJECT +public: + NifMatrix4Edit( NifModel * nif, const QModelIndex & index ); + + void updateData( NifModel * ); + void applyData( NifModel * ); + +protected: + class VectorEdit * translation; + class RotationEdit * rotation; + class VectorEdit * scale; + + bool setting; +}; + +#endif diff --git a/widgets/nifview.cpp b/widgets/nifview.cpp index 6ef705709..41050658b 100644 --- a/widgets/nifview.cpp +++ b/widgets/nifview.cpp @@ -1,198 +1,198 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "nifview.h" - -#include "basemodel.h" -#include "nifproxy.h" -#include "spellbook.h" - -#include - -NifTreeView::NifTreeView() : QTreeView() -{ - nif = 0; - EvalConditions = false; - - setUniformRowHeights( true ); - setAlternatingRowColors( true ); - setContextMenuPolicy( Qt::CustomContextMenu ); - setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); -} - -NifTreeView::~NifTreeView() -{ -} - -void NifTreeView::setModel( QAbstractItemModel * model ) -{ - if ( nif && EvalConditions ) - disconnect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( updateConditions() ) ); - - nif = qobject_cast( model ); - - QTreeView::setModel( model ); - - if ( nif && EvalConditions ) - connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( updateConditions() ) ); -} - -void NifTreeView::setRootIndex( const QModelIndex & index ) -{ - QModelIndex root = index; - if ( root.isValid() && root.column() != 0 ) - root = root.sibling( root.row(), 0 ); - QTreeView::setRootIndex( root ); -} - -void NifTreeView::clearRootIndex() -{ - setRootIndex( QModelIndex() ); -} - -void NifTreeView::setEvalConditions( bool c ) -{ - if ( EvalConditions == c ) - return; - - EvalConditions = c; - - if ( nif ) - { - if ( EvalConditions ) - connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( updateConditions() ) ); - else - disconnect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( updateConditions() ) ); - } - - doItemsLayout(); -} - -bool NifTreeView::isRowHidden( int r, const QModelIndex & index ) const -{ - return ( EvalConditions && index.isValid() && nif && index.model() == nif && !nif->evalCondition( index.child( r, 0 ) ) ); -} - -void NifTreeView::setAllExpanded( const QModelIndex & index, bool e ) -{ - if ( ! model() ) return; - for ( int r = 0; r < model()->rowCount( index ); r++ ) - { - QModelIndex child = model()->index( r, 0, index ); - if ( model()->hasChildren( child ) ) - { - setExpanded( child, e ); - setAllExpanded( child, e ); - } - } -} - -QStyleOptionViewItem NifTreeView::viewOptions() const -{ - QStyleOptionViewItem opt = QTreeView::viewOptions(); - opt.showDecorationSelected = true; - return opt; -} - -void NifTreeView::drawBranches( QPainter * painter, const QRect & rect, const QModelIndex & index ) const -{ - if ( rootIsDecorated() ) - QTreeView::drawBranches( painter, rect, index ); -} - -void NifTreeView::updateConditions() -{ - if ( EvalConditions ) - { - if ( isIndexHidden( currentIndex() ) ) - setCurrentIndex( QModelIndex() ); - doItemsLayout(); - } -} - -void NifTreeView::keyPressEvent( QKeyEvent * e ) -{ - Spell * spell = SpellBook::lookup( QKeySequence( e->modifiers() + e->key() ) ); - - if ( spell ) - { - NifModel * nif = 0; - NifProxyModel * proxy = 0; - - QPersistentModelIndex oldidx; - - if ( model()->inherits( "NifModel" ) ) - { - nif = static_cast( model() ); - oldidx = currentIndex(); - } - else if ( model()->inherits( "NifProxyModel" ) ) - { - proxy = static_cast( model() ); - nif = static_cast( proxy->model() ); - oldidx = proxy->mapTo( currentIndex() ); - } - - if ( nif && spell->isApplicable( nif, oldidx ) ) - { - selectionModel()->setCurrentIndex( QModelIndex(), QItemSelectionModel::Clear | QItemSelectionModel::Rows ); - - QModelIndex newidx = spell->cast( nif, oldidx ); - if ( proxy ) - newidx = proxy->mapFrom( newidx, oldidx ); - - // grab selection from the selection model as it tends to be more accurate - if (!newidx.isValid()) - { - if (oldidx.isValid()) - newidx = ( proxy ) ? proxy->mapFrom( oldidx, oldidx ) : ((QModelIndex)oldidx); - else - newidx = selectionModel()->currentIndex(); - } - if (newidx.isValid()) - { - selectionModel()->setCurrentIndex(newidx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - scrollTo(newidx, EnsureVisible); - emit clicked(newidx); - } - return; - } - } - - QTreeView::keyPressEvent( e ); -} - -void NifTreeView::currentChanged( const QModelIndex & current, const QModelIndex & last ) -{ - QTreeView::currentChanged( current, last ); - emit sigCurrentIndexChanged( currentIndex() ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "nifview.h" + +#include "basemodel.h" +#include "nifproxy.h" +#include "spellbook.h" + +#include + +NifTreeView::NifTreeView() : QTreeView() +{ + nif = 0; + EvalConditions = false; + + setUniformRowHeights( true ); + setAlternatingRowColors( true ); + setContextMenuPolicy( Qt::CustomContextMenu ); + setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); +} + +NifTreeView::~NifTreeView() +{ +} + +void NifTreeView::setModel( QAbstractItemModel * model ) +{ + if ( nif && EvalConditions ) + disconnect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( updateConditions() ) ); + + nif = qobject_cast( model ); + + QTreeView::setModel( model ); + + if ( nif && EvalConditions ) + connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( updateConditions() ) ); +} + +void NifTreeView::setRootIndex( const QModelIndex & index ) +{ + QModelIndex root = index; + if ( root.isValid() && root.column() != 0 ) + root = root.sibling( root.row(), 0 ); + QTreeView::setRootIndex( root ); +} + +void NifTreeView::clearRootIndex() +{ + setRootIndex( QModelIndex() ); +} + +void NifTreeView::setEvalConditions( bool c ) +{ + if ( EvalConditions == c ) + return; + + EvalConditions = c; + + if ( nif ) + { + if ( EvalConditions ) + connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( updateConditions() ) ); + else + disconnect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( updateConditions() ) ); + } + + doItemsLayout(); +} + +bool NifTreeView::isRowHidden( int r, const QModelIndex & index ) const +{ + return ( EvalConditions && index.isValid() && nif && index.model() == nif && !nif->evalCondition( index.child( r, 0 ) ) ); +} + +void NifTreeView::setAllExpanded( const QModelIndex & index, bool e ) +{ + if ( ! model() ) return; + for ( int r = 0; r < model()->rowCount( index ); r++ ) + { + QModelIndex child = model()->index( r, 0, index ); + if ( model()->hasChildren( child ) ) + { + setExpanded( child, e ); + setAllExpanded( child, e ); + } + } +} + +QStyleOptionViewItem NifTreeView::viewOptions() const +{ + QStyleOptionViewItem opt = QTreeView::viewOptions(); + opt.showDecorationSelected = true; + return opt; +} + +void NifTreeView::drawBranches( QPainter * painter, const QRect & rect, const QModelIndex & index ) const +{ + if ( rootIsDecorated() ) + QTreeView::drawBranches( painter, rect, index ); +} + +void NifTreeView::updateConditions() +{ + if ( EvalConditions ) + { + if ( isIndexHidden( currentIndex() ) ) + setCurrentIndex( QModelIndex() ); + doItemsLayout(); + } +} + +void NifTreeView::keyPressEvent( QKeyEvent * e ) +{ + Spell * spell = SpellBook::lookup( QKeySequence( e->modifiers() + e->key() ) ); + + if ( spell ) + { + NifModel * nif = 0; + NifProxyModel * proxy = 0; + + QPersistentModelIndex oldidx; + + if ( model()->inherits( "NifModel" ) ) + { + nif = static_cast( model() ); + oldidx = currentIndex(); + } + else if ( model()->inherits( "NifProxyModel" ) ) + { + proxy = static_cast( model() ); + nif = static_cast( proxy->model() ); + oldidx = proxy->mapTo( currentIndex() ); + } + + if ( nif && spell->isApplicable( nif, oldidx ) ) + { + selectionModel()->setCurrentIndex( QModelIndex(), QItemSelectionModel::Clear | QItemSelectionModel::Rows ); + + QModelIndex newidx = spell->cast( nif, oldidx ); + if ( proxy ) + newidx = proxy->mapFrom( newidx, oldidx ); + + // grab selection from the selection model as it tends to be more accurate + if (!newidx.isValid()) + { + if (oldidx.isValid()) + newidx = ( proxy ) ? proxy->mapFrom( oldidx, oldidx ) : ((QModelIndex)oldidx); + else + newidx = selectionModel()->currentIndex(); + } + if (newidx.isValid()) + { + selectionModel()->setCurrentIndex(newidx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + scrollTo(newidx, EnsureVisible); + emit clicked(newidx); + } + return; + } + } + + QTreeView::keyPressEvent( e ); +} + +void NifTreeView::currentChanged( const QModelIndex & current, const QModelIndex & last ) +{ + QTreeView::currentChanged( current, last ); + emit sigCurrentIndexChanged( currentIndex() ); +} diff --git a/widgets/nifview.h b/widgets/nifview.h index 735e14db2..e2a7de5b4 100644 --- a/widgets/nifview.h +++ b/widgets/nifview.h @@ -1,80 +1,80 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef NIFTREEVIEW_H -#define NIFTREEVIEW_H - -#include - -//! Widget for showing a nif file as tree, list, or block details. -class NifTreeView : public QTreeView -{ - Q_OBJECT -public: - NifTreeView(); - ~NifTreeView(); - - void setModel( QAbstractItemModel * model ); - void setAllExpanded( const QModelIndex & index, bool e ); - - bool evalConditions() const { return EvalConditions; } - bool isRowHidden(int row, const QModelIndex &parent) const; - - QSize minimumSizeHint() const { return QSize( 50, 50 ); } - QSize sizeHint() const { return QSize( 400, 400 ); } - -signals: - void sigCurrentIndexChanged( const QModelIndex & ); - -public slots: - void setRootIndex( const QModelIndex & index ); - void clearRootIndex(); - - void setEvalConditions( bool ); - -protected slots: - void updateConditions(); - void currentChanged( const QModelIndex & current, const QModelIndex & previous ); - -protected: - void drawBranches( QPainter * painter, const QRect & rect, const QModelIndex & index ) const; - void keyPressEvent( QKeyEvent * e ); - - QStyleOptionViewItem viewOptions() const; - - bool EvalConditions; - - class BaseModel * nif; -}; - - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef NIFTREEVIEW_H +#define NIFTREEVIEW_H + +#include + +//! Widget for showing a nif file as tree, list, or block details. +class NifTreeView : public QTreeView +{ + Q_OBJECT +public: + NifTreeView(); + ~NifTreeView(); + + void setModel( QAbstractItemModel * model ); + void setAllExpanded( const QModelIndex & index, bool e ); + + bool evalConditions() const { return EvalConditions; } + bool isRowHidden(int row, const QModelIndex &parent) const; + + QSize minimumSizeHint() const { return QSize( 50, 50 ); } + QSize sizeHint() const { return QSize( 400, 400 ); } + +signals: + void sigCurrentIndexChanged( const QModelIndex & ); + +public slots: + void setRootIndex( const QModelIndex & index ); + void clearRootIndex(); + + void setEvalConditions( bool ); + +protected slots: + void updateConditions(); + void currentChanged( const QModelIndex & current, const QModelIndex & previous ); + +protected: + void drawBranches( QPainter * painter, const QRect & rect, const QModelIndex & index ) const; + void keyPressEvent( QKeyEvent * e ); + + QStyleOptionViewItem viewOptions() const; + + bool EvalConditions; + + class BaseModel * nif; +}; + + +#endif diff --git a/widgets/refrbrowser.cpp b/widgets/refrbrowser.cpp index eccbd5e68..7ddf3ae80 100644 --- a/widgets/refrbrowser.cpp +++ b/widgets/refrbrowser.cpp @@ -1,119 +1,119 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "refrbrowser.h" - -#include -#include - -#include "nifmodel.h" - -ReferenceBrowser::ReferenceBrowser( QWidget * parent ) - : QTextBrowser( parent ) -{ - nif = NULL; - - // Search for reference documentation in different locations. - - // First, try the application path, for windows install. - docFolder.setPath( qApp->applicationDirPath() ); - docFolderPresent = docFolder.exists( "doc" ); - - // Next, try the docsys path (if application is run from the nifskope - // repository directory, as in linux build). - if( ! docFolderPresent ) { - docFolder.setPath( qApp->applicationDirPath() ); - docFolder.cd( "../docsys" ); - docFolderPresent = docFolder.exists( "doc" ); - } - - // Again, try the docsys path (if application is run from the - // nifskope/release repository directory, as in windows build). - if( ! docFolderPresent ) { - docFolder.setPath( qApp->applicationDirPath() ); - docFolder.cd( "../../docsys" ); - docFolderPresent = docFolder.exists( "doc" ); - } - - // Try the /usr/share/nifskope path, for linux install. - if ( ! docFolderPresent ) { - docFolder.cd( "/usr/share/nifskope" ); - docFolderPresent = docFolder.exists( "doc" ); - } - - if( docFolderPresent ) { - docFolder.cd( "doc" ); - } - - if( ! docFolderPresent || ! QFileInfo( docFolder.filePath( "index.html" ) ).exists() ) - { - setText( tr("Please install the reference documentation into the 'doc' folder.") ); - return; - } - - setSearchPaths( QStringList() << docFolder.absolutePath() ); - setStyleSheet( "docsys.css" ); - setSourceFile( "index.html" ); -} - -void ReferenceBrowser::setNifModel( NifModel * nifModel ) -{ - nif = nifModel; -} - -// TODO: Read documentation from ZIP files - -void ReferenceBrowser::setSourceFile( const QString & source ) -{ - if( QFileInfo( docFolder.filePath( source ) ).exists() ) { - setSource( QUrl( source ) ); - } -} - -void ReferenceBrowser::browse( const QModelIndex & index ) -{ - if( !nif || !docFolderPresent ) { - return; - } - - QString blockType = nif->getBlockType( index ); - if( blockType == "NiBlock" ) { - blockType = nif->getBlockName( index ); - } - - if( ! QFileInfo( docFolder.filePath( "%1.html" ).arg( blockType ) ).exists() ) { - setText( tr("The reference file for '%1' could not be found.").arg( blockType ) ); - return; - } - - setSourceFile( QString( "%1.html" ).arg( blockType ) ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "refrbrowser.h" + +#include +#include + +#include "nifmodel.h" + +ReferenceBrowser::ReferenceBrowser( QWidget * parent ) + : QTextBrowser( parent ) +{ + nif = NULL; + + // Search for reference documentation in different locations. + + // First, try the application path, for windows install. + docFolder.setPath( qApp->applicationDirPath() ); + docFolderPresent = docFolder.exists( "doc" ); + + // Next, try the docsys path (if application is run from the nifskope + // repository directory, as in linux build). + if( ! docFolderPresent ) { + docFolder.setPath( qApp->applicationDirPath() ); + docFolder.cd( "../docsys" ); + docFolderPresent = docFolder.exists( "doc" ); + } + + // Again, try the docsys path (if application is run from the + // nifskope/release repository directory, as in windows build). + if( ! docFolderPresent ) { + docFolder.setPath( qApp->applicationDirPath() ); + docFolder.cd( "../../docsys" ); + docFolderPresent = docFolder.exists( "doc" ); + } + + // Try the /usr/share/nifskope path, for linux install. + if ( ! docFolderPresent ) { + docFolder.cd( "/usr/share/nifskope" ); + docFolderPresent = docFolder.exists( "doc" ); + } + + if( docFolderPresent ) { + docFolder.cd( "doc" ); + } + + if( ! docFolderPresent || ! QFileInfo( docFolder.filePath( "index.html" ) ).exists() ) + { + setText( tr("Please install the reference documentation into the 'doc' folder.") ); + return; + } + + setSearchPaths( QStringList() << docFolder.absolutePath() ); + setStyleSheet( "docsys.css" ); + setSourceFile( "index.html" ); +} + +void ReferenceBrowser::setNifModel( NifModel * nifModel ) +{ + nif = nifModel; +} + +// TODO: Read documentation from ZIP files + +void ReferenceBrowser::setSourceFile( const QString & source ) +{ + if( QFileInfo( docFolder.filePath( source ) ).exists() ) { + setSource( QUrl( source ) ); + } +} + +void ReferenceBrowser::browse( const QModelIndex & index ) +{ + if( !nif || !docFolderPresent ) { + return; + } + + QString blockType = nif->getBlockType( index ); + if( blockType == "NiBlock" ) { + blockType = nif->getBlockName( index ); + } + + if( ! QFileInfo( docFolder.filePath( "%1.html" ).arg( blockType ) ).exists() ) { + setText( tr("The reference file for '%1' could not be found.").arg( blockType ) ); + return; + } + + setSourceFile( QString( "%1.html" ).arg( blockType ) ); +} diff --git a/widgets/refrbrowser.h b/widgets/refrbrowser.h index b25e0eb1d..f6e7bdd5d 100644 --- a/widgets/refrbrowser.h +++ b/widgets/refrbrowser.h @@ -1,60 +1,60 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef REFRBROWSER_H -#define REFRBROWSER_H - -#include -#include - -class NifModel; -class QModelIndex; - -class ReferenceBrowser : public QTextBrowser -{ - Q_OBJECT - -public: - ReferenceBrowser( QWidget * parent = NULL ); - -public slots: - void setNifModel( NifModel * ); - void setSourceFile( const QString & ); - void browse( const QModelIndex & ); - -private: - NifModel * nif; - bool docFolderPresent; - QDir docFolder; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef REFRBROWSER_H +#define REFRBROWSER_H + +#include +#include + +class NifModel; +class QModelIndex; + +class ReferenceBrowser : public QTextBrowser +{ + Q_OBJECT + +public: + ReferenceBrowser( QWidget * parent = NULL ); + +public slots: + void setNifModel( NifModel * ); + void setSourceFile( const QString & ); + void browse( const QModelIndex & ); + +private: + NifModel * nif; + bool docFolderPresent; + QDir docFolder; +}; + +#endif diff --git a/widgets/uvedit.cpp b/widgets/uvedit.cpp index 01543c1c5..0af158807 100644 --- a/widgets/uvedit.cpp +++ b/widgets/uvedit.cpp @@ -1,1031 +1,1031 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "uvedit.h" - -#include "../nifmodel.h" -#include "../niftypes.h" -#include "../options.h" -#include "../gl/gltex.h" -#include "../gl/gltools.h" -#include "../NvTriStrip/qtwrapper.h" - -#include -#include - -#include -#include -#include - -#define BASESIZE 512.0 -#define GRIDSIZE 16.0 -#define GRIDSEGS 4 -#define ZOOMUNIT 64.0 - -UVWidget * UVWidget::createEditor( NifModel * nif, const QModelIndex & idx ) -{ - UVWidget * uvw = new UVWidget; - uvw->setAttribute( Qt::WA_DeleteOnClose ); - if ( ! uvw->setNifData( nif, idx ) ) - { - qWarning() << tr( "Could not load texture data for UV editor." ); - delete uvw; - return NULL; - } - uvw->show(); - return uvw; -} - -static GLshort vertArray[4][2] = { - { 0, 0 }, { 1, 0 }, - { 1, 1 }, { 0, 1 } -}; - -static GLshort texArray[4][2] = { - { 0, 0 }, { 1, 0 }, - { 1, 1 }, { 0, 1 } -}; - -static GLdouble glUnit = ( 1.0 / BASESIZE ); -static GLdouble glGridD = GRIDSIZE * glUnit; - -UVWidget::UVWidget( QWidget * parent ) - : QGLWidget( QGLFormat( QGL::SampleBuffers ), parent, 0, Qt::Tool | Qt::WindowStaysOnTopHint ), undoStack( new QUndoStack( this ) ) -{ - setWindowTitle( tr("UV Editor") ); - setFocusPolicy( Qt::StrongFocus ); - - textures = new TexCache( this ); - - zoom = 1.2; - - pos = QPoint( 0, 0 ); - - mousePos = QPoint( -1000, -1000 ); - - setCursor( QCursor( Qt::CrossCursor ) ); - setMouseTracking( true ); - - setContextMenuPolicy( Qt::ActionsContextMenu ); - - QAction * aUndo = undoStack->createUndoAction( this ); - QAction * aRedo = undoStack->createRedoAction( this ); - - aUndo->setShortcut( QKeySequence::Undo ); - aRedo->setShortcut( QKeySequence::Redo ); - - addAction( aUndo ); - addAction( aRedo ); - - QAction * aSep = new QAction( this ); - aSep->setSeparator( true ); - addAction( aSep ); - - QAction * aSelectAll = new QAction( tr( "Select &All" ), this ); - aSelectAll->setShortcut( QKeySequence::SelectAll ); - connect( aSelectAll, SIGNAL( triggered() ), this, SLOT( selectAll() ) ); - addAction( aSelectAll ); - - QAction * aSelectNone = new QAction( tr( "Select &None" ), this ); - connect( aSelectNone, SIGNAL( triggered() ), this, SLOT( selectNone() ) ); - addAction( aSelectNone ); - - QAction * aSelectFaces = new QAction( tr( "Select &Faces" ), this ); - connect( aSelectFaces, SIGNAL( triggered() ), this, SLOT( selectFaces() ) ); - addAction( aSelectFaces ); - - QAction * aSelectConnected = new QAction( tr( "Select &Connected" ), this ); - connect( aSelectConnected, SIGNAL( triggered() ), this, SLOT( selectConnected() ) ); - addAction( aSelectConnected ); - - aSep = new QAction( this ); - aSep->setSeparator( true ); - addAction( aSep ); - - aTextureBlend = new QAction( tr( "Texture Alpha Blending" ), this ); - aTextureBlend->setCheckable( true ); - aTextureBlend->setChecked( true ); - connect( aTextureBlend, SIGNAL( toggled( bool ) ), this, SLOT( updateGL() ) ); - addAction( aTextureBlend ); - - connect( Options::get(), SIGNAL( sigChanged() ), this, SLOT( updateGL() ) ); -} - -UVWidget::~UVWidget() -{ - delete textures; -} - -void UVWidget::initializeGL() -{ - glMatrixMode( GL_MODELVIEW ); - - initializeTextureUnits( context() ); - - glShadeModel( GL_SMOOTH ); - glShadeModel( GL_LINE_SMOOTH ); - - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - glEnable( GL_BLEND ); - - glDepthFunc( GL_LEQUAL ); - glEnable( GL_DEPTH_TEST ); - - glEnable( GL_MULTISAMPLE ); - glDisable( GL_LIGHTING ); - - qglClearColor( Options::bgColor() ); - - bindTexture( texfile ); - - glEnableClientState( GL_VERTEX_ARRAY ); - glVertexPointer( 2, GL_SHORT, 0, vertArray ); - - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 2, GL_SHORT, 0, texArray ); - - // check for errors - GLenum err; - while ( ( err = glGetError() ) != GL_NO_ERROR ) { - qDebug() << "GL ERROR (init) : " << (const char *) gluErrorString( err ); - } -} - -void UVWidget::resizeGL( int width, int height ) -{ - updateViewRect( width, height ); -} - -void UVWidget::paintGL() -{ - glPushAttrib( GL_ALL_ATTRIB_BITS ); - - glMatrixMode( GL_PROJECTION ); - glPushMatrix(); - glLoadIdentity(); - - setupViewport( width(), height() ); - - glMatrixMode( GL_MODELVIEW ); - glPushMatrix(); - glLoadIdentity(); - - qglClearColor( Options::bgColor() ); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); - - glDisable( GL_DEPTH_TEST ); - glDepthMask( GL_FALSE ); - - // draw texture - - glPushMatrix(); - glLoadIdentity(); - - glEnable( GL_TEXTURE_2D ); - - if ( aTextureBlend->isChecked() ) - glEnable( GL_BLEND ); - else - glDisable( GL_BLEND ); - - glTranslatef( -0.5f, -0.5f, 0.0f ); - - glTranslatef( -1.0f, -1.0f, 0.0f ); - for( int i = 0; i < 3; i++ ) - { - for( int j = 0; j < 3; j++ ) - { - if( i == 1 && j == 1 ) { - glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); - } - else { - glColor4f( 0.5f, 0.5f, 0.5f, 1.0f ); - } - - glDrawArrays( GL_QUADS, 0, 4 ); - - glTranslatef( 1.0f, 0.0f, 0.0f ); - } - - glTranslatef( -3.0f, 1.0f, 0.0f ); - } - glTranslatef( 1.0f, -2.0f, 0.0f ); - - glDisable( GL_TEXTURE_2D ); - - glPopMatrix(); - - - // draw grid - - glPushMatrix(); - glLoadIdentity(); - - glEnable( GL_BLEND ); - - glLineWidth( 0.8f ); - glBegin( GL_LINES ); - int glGridMinX = qRound( qMin( glViewRect[0], glViewRect[1] ) / glGridD ); - int glGridMaxX = qRound( qMax( glViewRect[0], glViewRect[1] ) / glGridD ); - int glGridMinY = qRound( qMin( glViewRect[2], glViewRect[3] ) / glGridD ); - int glGridMaxY = qRound( qMax( glViewRect[2], glViewRect[3] ) / glGridD ); - for( int i = glGridMinX; i < glGridMaxX; i++ ) - { - GLdouble glGridPos = glGridD * i; - - if( ( i % GRIDSEGS ) == 0 ) { - glLineWidth( 1.2f ); - glColor4f( 1.0f, 1.0f, 1.0f, 0.2f ); - } - else if( zoom > 2.0 ) { - continue; - } - - glVertex2d( glGridPos, glViewRect[2] ); - glVertex2d( glGridPos, glViewRect[3] ); - - if( ( i % GRIDSEGS ) == 0 ) { - glLineWidth( 0.8f ); - glColor4f( 1.0f, 1.0f, 1.0f, 0.1f ); - } - } - for( int i = glGridMinY; i < glGridMaxY; i++ ) - { - GLdouble glGridPos = glGridD * i; - - if( ( i % GRIDSEGS ) == 0 ) { - glLineWidth( 1.2f ); - glColor4f( 1.0f, 1.0f, 1.0f, 0.2f ); - } - else if( zoom > 2.0 ) { - continue; - } - - glVertex2d( glViewRect[0], glGridPos ); - glVertex2d( glViewRect[1], glGridPos ); - - if( ( i % GRIDSEGS ) == 0 ) { - glLineWidth( 0.8f ); - glColor4f( 1.0f, 1.0f, 1.0f, 0.1f ); - } - } - glEnd(); - - glPopMatrix(); - - - - drawTexCoords(); - - - glDisable( GL_DEPTH_TEST ); - glDepthMask( GL_FALSE ); - - if( ! selectRect.isNull() ) - { - glLoadIdentity(); - glHighlightColor(); - glBegin( GL_LINE_LOOP ); - glVertex( mapToContents( selectRect.topLeft() ) ); - glVertex( mapToContents( selectRect.topRight() ) ); - glVertex( mapToContents( selectRect.bottomRight() ) ); - glVertex( mapToContents( selectRect.bottomLeft() ) ); - glEnd(); - } - - if ( ! selectPoly.isEmpty() ) - { - glLoadIdentity(); - glHighlightColor(); - glBegin( GL_LINE_LOOP ); - foreach ( QPoint p, selectPoly ) - { - glVertex( mapToContents( p ) ); - } - glEnd(); - } - - - - glMatrixMode( GL_MODELVIEW ); - glPopMatrix(); - - glMatrixMode( GL_PROJECTION ); - glPopMatrix(); - - glPopAttrib(); -} - -void UVWidget::drawTexCoords() -{ - glMatrixMode( GL_MODELVIEW ); - - glPushMatrix(); - glLoadIdentity(); - - glScalef( 1.0f, 1.0f, 1.0f ); - glTranslatef( -0.5f, -0.5f, 0.0f ); - - Color4 nlColor( Options::nlColor() ); - nlColor.setAlpha( 0.5f ); - Color4 hlColor( Options::hlColor() ); - hlColor.setAlpha( 0.5f ); - - glLineWidth( 1.0f ); - glPointSize( 3.5f ); - - glEnable( GL_BLEND ); - glEnable( GL_DEPTH_TEST ); - glDepthFunc( GL_LEQUAL ); - glDepthMask( GL_TRUE ); - - float z; - - // draw triangle edges - - for ( int i = 0; i < faces.size(); i++ ) - { - glBegin( GL_LINE_LOOP ); - for ( int j = 0; j < 3; j++ ) - { - int x = faces[i].tc[j]; - - if ( selection.contains( x ) ) - { - glColor( Color3( hlColor ) ); - z = 1.0f; - } - else - { - glColor( Color3( nlColor ) ); - z = 0.0f; - } - - glVertex( Vector3( texcoords[x], z ) ); - } - glEnd(); - } - - // draw points - - glBegin( GL_POINTS ); - for ( int i = 0; i < texcoords.size(); i++ ) - { - if ( selection.contains( i ) ) - { - glColor( Color3( hlColor ) ); - z = 1.0f; - } - else - { - glColor( Color3( nlColor ) ); - z = 0.0f; - } - glVertex( Vector3( texcoords[i], z ) ); - } - glEnd(); - - glPopMatrix(); -} - -void UVWidget::setupViewport( int width, int height ) -{ - glMatrixMode( GL_PROJECTION ); - glLoadIdentity(); - - glViewport( 0, 0, width, height ); - - glOrtho( glViewRect[0], glViewRect[1], glViewRect[2], glViewRect[3], -10.0, +10.0 ); -} - -void UVWidget::updateViewRect( int width, int height ) -{ - GLdouble glOffX = glUnit * zoom * 0.5 * width; - GLdouble glOffY = glUnit * zoom * 0.5 * height; - GLdouble glPosX = glUnit * pos.x(); - GLdouble glPosY = glUnit * pos.y(); - - glViewRect[0] = - glOffX - glPosX; - glViewRect[1] = + glOffX - glPosX; - glViewRect[2] = + glOffY + glPosY; - glViewRect[3] = - glOffY + glPosY; -} - -QPoint UVWidget::mapFromContents( const Vector2 & v ) const -{ - float x = ( ( v[0] - 0.5 ) - glViewRect[ 0 ] ) / ( glViewRect[ 1 ] - glViewRect[ 0 ] ) * width(); - float y = ( ( v[1] - 0.5 ) - glViewRect[ 2 ] ) / ( glViewRect[ 3 ] - glViewRect[ 2 ] ) * height() * ( - 1 ) + height(); - - return QPointF( x, y ).toPoint(); -} - -Vector2 UVWidget::mapToContents( const QPoint & p ) const -{ - float x = ( (float) p.x() / (float) width() ) * ( glViewRect[ 1 ] - glViewRect[ 0 ] ) + glViewRect[ 0 ]; - float y = ( (float) p.y() / (float) height() ) * ( glViewRect[ 2 ] - glViewRect[ 3 ] ) + glViewRect[ 3 ]; - return Vector2( x, y ); -} - -QVector UVWidget::indices( const QPoint & p ) const -{ - return indices( QRect( p - QPoint( 2, 2 ), QSize( 5, 5 ) ) ); -} - -QVector UVWidget::indices( const QRegion & region ) const -{ - QList hits; - - for ( int i = 0; i < texcoords.count(); i++ ) - { - if ( region.contains( mapFromContents( texcoords[ i ] ) ) ) - hits << i; - } - - return hits.toVector(); -} - -bool UVWidget::bindTexture( const QString & filename ) -{ - GLuint mipmaps = 0; - GLfloat max_anisotropy = 0.0f; - - QString extensions( (const char *) glGetString( GL_EXTENSIONS ) ); - - if ( extensions.contains( "GL_EXT_texture_filter_anisotropic" ) ) - { - glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, & max_anisotropy ); - //qWarning() << "maximum anisotropy" << max_anisotropy; - } - - if ( mipmaps = textures->bind( filename ) ) - { - if ( max_anisotropy > 0.0f ) - { - if ( Options::antialias() ) - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy ); - else - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f ); - } - - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ); - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - - // TODO: Add support for non-square textures - - glMatrixMode( GL_TEXTURE ); - glLoadIdentity(); - - glMatrixMode( GL_MODELVIEW ); - return true; - } - - return false; - } - - - -QSize UVWidget::sizeHint() const -{ - if( sHint.isValid() ) { - return sHint; - } - - return QSizeF( BASESIZE, BASESIZE ).toSize(); -} - -void UVWidget::setSizeHint( const QSize & s ) -{ - sHint = s; -} - -QSize UVWidget::minimumSizeHint() const -{ - return QSizeF( BASESIZE, BASESIZE ).toSize(); -} - -int UVWidget::heightForWidth( int width ) const -{ - if ( width < minimumSizeHint().height() ) - return minimumSizeHint().height(); - return width; -} - -void UVWidget::mousePressEvent( QMouseEvent * e ) -{ - QPoint dPos( e->pos() - mousePos ); - mousePos = e->pos(); - - if ( e->button() == Qt::LeftButton ) - { - QVector hits = indices( mousePos ); - - if ( hits.isEmpty() ) - { - if ( ! e->modifiers().testFlag( Qt::ShiftModifier ) ) - selectNone(); - - if ( e->modifiers().testFlag( Qt::AltModifier ) ) - { - selectPoly << e->pos(); - } - else - { - selectRect.setTopLeft( mousePos ); - selectRect.setBottomRight( mousePos ); - } - } - else - { - if ( dPos.manhattanLength() > 4 ) - selectCycle = 0; - else - selectCycle++; - - int h = hits[ selectCycle % hits.count() ]; - - if ( ! e->modifiers().testFlag( Qt::ShiftModifier ) ) - { - if ( ! isSelected( h ) ) - selectNone(); - select( h ); - } - else - { - select( h, ! isSelected( h ) ); - } - - if ( selection.isEmpty() ) - { - setCursor( QCursor( Qt::CrossCursor ) ); - } - else - { - setCursor( QCursor( Qt::SizeAllCursor ) ); - } - } - } - - updateGL(); -} - -void UVWidget::mouseMoveEvent( QMouseEvent * e ) -{ - QPoint dPos( e->pos() - mousePos ); - - switch ( e->buttons() ) - { - case Qt::LeftButton: - if ( ! selectRect.isNull() ) - { - selectRect.setBottomRight( e->pos() ); - } - else if ( ! selectPoly.isEmpty() ) - { - selectPoly << e->pos(); - } - else - { - moveSelection( glUnit * zoom * dPos.x(), glUnit * zoom * dPos.y() ); - } - break; - - case Qt::MidButton: - pos += zoom * QPointF( dPos.x(), -dPos.y() ); - updateViewRect( width(), height() ); - - setCursor( QCursor( Qt::OpenHandCursor ) ); - - break; - - case Qt::RightButton: - zoom *= 1.0 + ( dPos.y() / ZOOMUNIT ); - - if ( zoom < 0.1 ) - { - zoom = 0.1; - } - else if ( zoom > 10.0 ) - { - zoom = 10.0; - } - - updateViewRect( width(), height() ); - - setCursor( QCursor( Qt::SizeVerCursor ) ); - - break; - - default: - if ( indices( e->pos() ).count() ) - { - setCursor( QCursor( Qt::ArrowCursor ) ); - } - else - { - setCursor( QCursor( Qt::CrossCursor ) ); - } - return; - } - - mousePos = e->pos(); - - updateGL(); -} - -void UVWidget::mouseReleaseEvent( QMouseEvent * e ) -{ - switch( e->button() ) - { - case Qt::LeftButton: - if ( ! selectRect.isNull() ) - { - select( QRegion( selectRect.normalized() ) ); - selectRect = QRect(); - } - else if ( ! selectPoly.isEmpty() ) - { - if ( selectPoly.size() > 2 ) - { - select( QRegion( QPolygon( selectPoly.toVector() ) ) ); - } - selectPoly.clear(); - } - break; - default: - break; - } - - if ( indices( e->pos() ).count() ) - { - setCursor( QCursor( Qt::ArrowCursor ) ); - } - else - { - setCursor( QCursor( Qt::CrossCursor ) ); - } - - updateGL(); -} - -void UVWidget::wheelEvent( QWheelEvent * e ) -{ - switch( e->modifiers()) { - case Qt::NoModifier: - zoom *= 1.0 + ( e->delta() / 8.0 ) / ZOOMUNIT; - - if( zoom < 0.1 ) { - zoom = 0.1; - } - else if( zoom > 10.0 ) { - zoom = 10.0; - } - - updateViewRect( width(), height() ); - - break; - } - - updateGL(); -} - - - -bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) -{ - if ( nif ) - { - disconnect( nif ); - } - - undoStack->clear(); - - nif = nifModel; - iShape = nifIndex; - - connect( nif, SIGNAL( modelReset() ), this, SLOT( close() ) ); - connect( nif, SIGNAL( destroyed() ), this, SLOT( close() ) ); - connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( nifDataChanged( const QModelIndex & ) ) ); - connect( nif, SIGNAL( rowsRemoved( const QModelIndex &, int, int ) ), this, SLOT( nifDataChanged( const QModelIndex ) ) ); - - textures->setNifFolder( nif->getFolder() ); - - iShapeData = nif->getBlock( nif->getLink( iShape, "Data" ) ); - if( nif->inherits( iShapeData, "NiTriBasedGeomData" ) ) - { - iTexCoords = nif->getIndex( iShapeData, "UV Sets" ).child( 0, 0 ); - if( ! iTexCoords.isValid() || ! nif->rowCount( iTexCoords ) ) - { - return false; - } - - texcoords = nif->getArray< Vector2 >( iTexCoords ); - - QVector< Triangle > tris; - - if( nif->isNiBlock( iShapeData, "NiTriShapeData" ) ) - { - tris = nif->getArray< Triangle >( iShapeData, "Triangles" ); - } - else if( nif->isNiBlock( iShapeData, "NiTriStripsData" ) ) - { - QModelIndex iPoints = nif->getIndex( iShapeData, "Points" ); - if( iPoints.isValid() ) - { - for( int r = 0; r < nif->rowCount( iPoints ); r++ ) - { - tris += triangulate( nif->getArray< quint16 >( iPoints.child( r, 0 ) ) ); - } - } - else - { - return false; - } - } - else - { - return false; - } - - QVectorIterator< Triangle > itri( tris ); - while ( itri.hasNext() ) - { - const Triangle & t = itri.next(); - - int fIdx = faces.size(); - faces.append( face( fIdx, t[0], t[1], t[2] ) ); - - for( int i = 0; i < 3; i++ ) - { - texcoords2faces.insertMulti( t[i], fIdx ); - } - } - } - - foreach( qint32 l, nif->getLinkArray( iShape, "Properties" ) ) - { - QModelIndex iTexProp = nif->getBlock( l, "NiTexturingProperty" ); - if( iTexProp.isValid() ) - { - QModelIndex iBaseTex = nif->getIndex( iTexProp, "Base Texture" ); - if( iBaseTex.isValid() ) - { - QModelIndex iTexSource = nif->getBlock( nif->getLink( iBaseTex, "Source" ) ); - if( iTexSource.isValid() ) - { - texfile = TexCache::find( nif->get( iTexSource, "File Name" ) , nif->getFolder() ); - return true; - } - } - } - else { - iTexProp = nif->getBlock( l, "NiTextureProperty" ); - if( iTexProp.isValid() ) - { - QModelIndex iTexSource = nif->getBlock( nif->getLink( iTexProp, "Image" ) ); - if( iTexSource.isValid() ) - { - texfile = TexCache::find( nif->get( iTexSource, "File Name" ) , nif->getFolder() ); - return true; - } - } - } - } - - return false; -} - -void UVWidget::updateNif() -{ - if ( nif && iTexCoords.isValid() ) - { - disconnect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( nifDataChanged( const QModelIndex & ) ) ); - nif->setArray< Vector2 >( iTexCoords, texcoords ); - connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( nifDataChanged( const QModelIndex & ) ) ); - } -} - -void UVWidget::nifDataChanged( const QModelIndex & idx ) -{ - if ( ! nif || ! iShape.isValid() || ! iShapeData.isValid() || ! iTexCoords.isValid() ) - { - close(); - return; - } - - if ( nif->getBlock( idx ) == iShapeData ) - { - //if ( ! setNifData( nif, iShape ) ) - { - close(); - return; - } - } -} - -bool UVWidget::isSelected( int index ) -{ - return selection.contains( index ); -} - -class UVWSelectCommand : public QUndoCommand -{ -public: - UVWSelectCommand( UVWidget * w, const QList & nSel ) : QUndoCommand(), uvw( w ), newSelection( nSel ) - { - setText( "Select" ); - } - - int id() const - { - return 0; - } - - bool mergeWith( const QUndoCommand * cmd ) - { - if ( cmd->id() == id() ) - { - newSelection = static_cast( cmd )->newSelection; - return true; - } - return false; - } - - void redo() - { - oldSelection = uvw->selection; - uvw->selection = newSelection; - uvw->updateGL(); - } - - void undo() - { - uvw->selection = oldSelection; - uvw->updateGL(); - } - -protected: - UVWidget * uvw; - QList oldSelection, newSelection; -}; - -void UVWidget::select( int index, bool yes ) -{ - QList selection = this->selection; - if ( yes ) - { - if ( ! selection.contains( index ) ) - selection.append( index ); - } - else - selection.removeAll( index ); - undoStack->push( new UVWSelectCommand( this, selection ) ); -} - -void UVWidget::select( const QRegion & r, bool add ) -{ - QList selection( add ? this->selection : QList() ); - foreach ( int s, indices( r ) ) - { - if ( ! selection.contains( s ) ) - selection.append( s ); - } - undoStack->push( new UVWSelectCommand( this, selection ) ); -} - -void UVWidget::selectNone() -{ - undoStack->push( new UVWSelectCommand( this, QList() ) ); -} - -void UVWidget::selectAll() -{ - QList selection; - for ( int s = 0; s < texcoords.count(); s++ ) - selection << s; - undoStack->push( new UVWSelectCommand( this, selection ) ); -} - -void UVWidget::selectFaces() -{ - QList selection = this->selection; - foreach ( int s, selection ) - { - foreach ( int f, texcoords2faces.values( s ) ) - { - for ( int i = 0; i < 3; i++ ) - { - if ( ! selection.contains( faces[f].tc[i] ) ) - selection.append( faces[f].tc[i] ); - } - } - } - undoStack->push( new UVWSelectCommand( this, selection ) ); -} - -void UVWidget::selectConnected() -{ - QList selection = this->selection; - bool more = true; - while ( more ) - { - more = false; - foreach ( int s, selection ) - { - foreach ( int f, texcoords2faces.values( s ) ) - { - for ( int i = 0; i < 3; i++ ) - { - if ( ! selection.contains( faces[f].tc[i] ) ) - { - selection.append( faces[f].tc[i] ); - more = true; - } - } - } - } - } - undoStack->push( new UVWSelectCommand( this, selection ) ); -} - -class UVWMoveCommand : public QUndoCommand -{ -public: - UVWMoveCommand( UVWidget * w, double dx, double dy ) : QUndoCommand(), uvw( w ), move( dx, dy ) - { - setText( "Move" ); - } - - int id() const - { - return 1; - } - - bool mergeWith( const QUndoCommand * cmd ) - { - if ( cmd->id() == id() ) - { - move += static_cast( cmd )->move; - return true; - } - return false; - } - - void redo() - { - foreach( int tc, uvw->selection ) - { - uvw->texcoords[tc] += move; - } - uvw->updateNif(); - uvw->updateGL(); - } - - void undo() - { - foreach( int tc, uvw->selection ) - { - uvw->texcoords[tc] -= move; - } - uvw->updateNif(); - uvw->updateGL(); - } - -protected: - UVWidget * uvw; - Vector2 move; -}; - -void UVWidget::moveSelection( double moveX, double moveY ) -{ - undoStack->push( new UVWMoveCommand( this, moveX, moveY ) ); -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "uvedit.h" + +#include "../nifmodel.h" +#include "../niftypes.h" +#include "../options.h" +#include "../gl/gltex.h" +#include "../gl/gltools.h" +#include "../NvTriStrip/qtwrapper.h" + +#include +#include + +#include +#include +#include + +#define BASESIZE 512.0 +#define GRIDSIZE 16.0 +#define GRIDSEGS 4 +#define ZOOMUNIT 64.0 + +UVWidget * UVWidget::createEditor( NifModel * nif, const QModelIndex & idx ) +{ + UVWidget * uvw = new UVWidget; + uvw->setAttribute( Qt::WA_DeleteOnClose ); + if ( ! uvw->setNifData( nif, idx ) ) + { + qWarning() << tr( "Could not load texture data for UV editor." ); + delete uvw; + return NULL; + } + uvw->show(); + return uvw; +} + +static GLshort vertArray[4][2] = { + { 0, 0 }, { 1, 0 }, + { 1, 1 }, { 0, 1 } +}; + +static GLshort texArray[4][2] = { + { 0, 0 }, { 1, 0 }, + { 1, 1 }, { 0, 1 } +}; + +static GLdouble glUnit = ( 1.0 / BASESIZE ); +static GLdouble glGridD = GRIDSIZE * glUnit; + +UVWidget::UVWidget( QWidget * parent ) + : QGLWidget( QGLFormat( QGL::SampleBuffers ), parent, 0, Qt::Tool | Qt::WindowStaysOnTopHint ), undoStack( new QUndoStack( this ) ) +{ + setWindowTitle( tr("UV Editor") ); + setFocusPolicy( Qt::StrongFocus ); + + textures = new TexCache( this ); + + zoom = 1.2; + + pos = QPoint( 0, 0 ); + + mousePos = QPoint( -1000, -1000 ); + + setCursor( QCursor( Qt::CrossCursor ) ); + setMouseTracking( true ); + + setContextMenuPolicy( Qt::ActionsContextMenu ); + + QAction * aUndo = undoStack->createUndoAction( this ); + QAction * aRedo = undoStack->createRedoAction( this ); + + aUndo->setShortcut( QKeySequence::Undo ); + aRedo->setShortcut( QKeySequence::Redo ); + + addAction( aUndo ); + addAction( aRedo ); + + QAction * aSep = new QAction( this ); + aSep->setSeparator( true ); + addAction( aSep ); + + QAction * aSelectAll = new QAction( tr( "Select &All" ), this ); + aSelectAll->setShortcut( QKeySequence::SelectAll ); + connect( aSelectAll, SIGNAL( triggered() ), this, SLOT( selectAll() ) ); + addAction( aSelectAll ); + + QAction * aSelectNone = new QAction( tr( "Select &None" ), this ); + connect( aSelectNone, SIGNAL( triggered() ), this, SLOT( selectNone() ) ); + addAction( aSelectNone ); + + QAction * aSelectFaces = new QAction( tr( "Select &Faces" ), this ); + connect( aSelectFaces, SIGNAL( triggered() ), this, SLOT( selectFaces() ) ); + addAction( aSelectFaces ); + + QAction * aSelectConnected = new QAction( tr( "Select &Connected" ), this ); + connect( aSelectConnected, SIGNAL( triggered() ), this, SLOT( selectConnected() ) ); + addAction( aSelectConnected ); + + aSep = new QAction( this ); + aSep->setSeparator( true ); + addAction( aSep ); + + aTextureBlend = new QAction( tr( "Texture Alpha Blending" ), this ); + aTextureBlend->setCheckable( true ); + aTextureBlend->setChecked( true ); + connect( aTextureBlend, SIGNAL( toggled( bool ) ), this, SLOT( updateGL() ) ); + addAction( aTextureBlend ); + + connect( Options::get(), SIGNAL( sigChanged() ), this, SLOT( updateGL() ) ); +} + +UVWidget::~UVWidget() +{ + delete textures; +} + +void UVWidget::initializeGL() +{ + glMatrixMode( GL_MODELVIEW ); + + initializeTextureUnits( context() ); + + glShadeModel( GL_SMOOTH ); + glShadeModel( GL_LINE_SMOOTH ); + + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glEnable( GL_BLEND ); + + glDepthFunc( GL_LEQUAL ); + glEnable( GL_DEPTH_TEST ); + + glEnable( GL_MULTISAMPLE ); + glDisable( GL_LIGHTING ); + + qglClearColor( Options::bgColor() ); + + bindTexture( texfile ); + + glEnableClientState( GL_VERTEX_ARRAY ); + glVertexPointer( 2, GL_SHORT, 0, vertArray ); + + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 2, GL_SHORT, 0, texArray ); + + // check for errors + GLenum err; + while ( ( err = glGetError() ) != GL_NO_ERROR ) { + qDebug() << "GL ERROR (init) : " << (const char *) gluErrorString( err ); + } +} + +void UVWidget::resizeGL( int width, int height ) +{ + updateViewRect( width, height ); +} + +void UVWidget::paintGL() +{ + glPushAttrib( GL_ALL_ATTRIB_BITS ); + + glMatrixMode( GL_PROJECTION ); + glPushMatrix(); + glLoadIdentity(); + + setupViewport( width(), height() ); + + glMatrixMode( GL_MODELVIEW ); + glPushMatrix(); + glLoadIdentity(); + + qglClearColor( Options::bgColor() ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + glDisable( GL_DEPTH_TEST ); + glDepthMask( GL_FALSE ); + + // draw texture + + glPushMatrix(); + glLoadIdentity(); + + glEnable( GL_TEXTURE_2D ); + + if ( aTextureBlend->isChecked() ) + glEnable( GL_BLEND ); + else + glDisable( GL_BLEND ); + + glTranslatef( -0.5f, -0.5f, 0.0f ); + + glTranslatef( -1.0f, -1.0f, 0.0f ); + for( int i = 0; i < 3; i++ ) + { + for( int j = 0; j < 3; j++ ) + { + if( i == 1 && j == 1 ) { + glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + } + else { + glColor4f( 0.5f, 0.5f, 0.5f, 1.0f ); + } + + glDrawArrays( GL_QUADS, 0, 4 ); + + glTranslatef( 1.0f, 0.0f, 0.0f ); + } + + glTranslatef( -3.0f, 1.0f, 0.0f ); + } + glTranslatef( 1.0f, -2.0f, 0.0f ); + + glDisable( GL_TEXTURE_2D ); + + glPopMatrix(); + + + // draw grid + + glPushMatrix(); + glLoadIdentity(); + + glEnable( GL_BLEND ); + + glLineWidth( 0.8f ); + glBegin( GL_LINES ); + int glGridMinX = qRound( qMin( glViewRect[0], glViewRect[1] ) / glGridD ); + int glGridMaxX = qRound( qMax( glViewRect[0], glViewRect[1] ) / glGridD ); + int glGridMinY = qRound( qMin( glViewRect[2], glViewRect[3] ) / glGridD ); + int glGridMaxY = qRound( qMax( glViewRect[2], glViewRect[3] ) / glGridD ); + for( int i = glGridMinX; i < glGridMaxX; i++ ) + { + GLdouble glGridPos = glGridD * i; + + if( ( i % GRIDSEGS ) == 0 ) { + glLineWidth( 1.2f ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.2f ); + } + else if( zoom > 2.0 ) { + continue; + } + + glVertex2d( glGridPos, glViewRect[2] ); + glVertex2d( glGridPos, glViewRect[3] ); + + if( ( i % GRIDSEGS ) == 0 ) { + glLineWidth( 0.8f ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.1f ); + } + } + for( int i = glGridMinY; i < glGridMaxY; i++ ) + { + GLdouble glGridPos = glGridD * i; + + if( ( i % GRIDSEGS ) == 0 ) { + glLineWidth( 1.2f ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.2f ); + } + else if( zoom > 2.0 ) { + continue; + } + + glVertex2d( glViewRect[0], glGridPos ); + glVertex2d( glViewRect[1], glGridPos ); + + if( ( i % GRIDSEGS ) == 0 ) { + glLineWidth( 0.8f ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.1f ); + } + } + glEnd(); + + glPopMatrix(); + + + + drawTexCoords(); + + + glDisable( GL_DEPTH_TEST ); + glDepthMask( GL_FALSE ); + + if( ! selectRect.isNull() ) + { + glLoadIdentity(); + glHighlightColor(); + glBegin( GL_LINE_LOOP ); + glVertex( mapToContents( selectRect.topLeft() ) ); + glVertex( mapToContents( selectRect.topRight() ) ); + glVertex( mapToContents( selectRect.bottomRight() ) ); + glVertex( mapToContents( selectRect.bottomLeft() ) ); + glEnd(); + } + + if ( ! selectPoly.isEmpty() ) + { + glLoadIdentity(); + glHighlightColor(); + glBegin( GL_LINE_LOOP ); + foreach ( QPoint p, selectPoly ) + { + glVertex( mapToContents( p ) ); + } + glEnd(); + } + + + + glMatrixMode( GL_MODELVIEW ); + glPopMatrix(); + + glMatrixMode( GL_PROJECTION ); + glPopMatrix(); + + glPopAttrib(); +} + +void UVWidget::drawTexCoords() +{ + glMatrixMode( GL_MODELVIEW ); + + glPushMatrix(); + glLoadIdentity(); + + glScalef( 1.0f, 1.0f, 1.0f ); + glTranslatef( -0.5f, -0.5f, 0.0f ); + + Color4 nlColor( Options::nlColor() ); + nlColor.setAlpha( 0.5f ); + Color4 hlColor( Options::hlColor() ); + hlColor.setAlpha( 0.5f ); + + glLineWidth( 1.0f ); + glPointSize( 3.5f ); + + glEnable( GL_BLEND ); + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_LEQUAL ); + glDepthMask( GL_TRUE ); + + float z; + + // draw triangle edges + + for ( int i = 0; i < faces.size(); i++ ) + { + glBegin( GL_LINE_LOOP ); + for ( int j = 0; j < 3; j++ ) + { + int x = faces[i].tc[j]; + + if ( selection.contains( x ) ) + { + glColor( Color3( hlColor ) ); + z = 1.0f; + } + else + { + glColor( Color3( nlColor ) ); + z = 0.0f; + } + + glVertex( Vector3( texcoords[x], z ) ); + } + glEnd(); + } + + // draw points + + glBegin( GL_POINTS ); + for ( int i = 0; i < texcoords.size(); i++ ) + { + if ( selection.contains( i ) ) + { + glColor( Color3( hlColor ) ); + z = 1.0f; + } + else + { + glColor( Color3( nlColor ) ); + z = 0.0f; + } + glVertex( Vector3( texcoords[i], z ) ); + } + glEnd(); + + glPopMatrix(); +} + +void UVWidget::setupViewport( int width, int height ) +{ + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + + glViewport( 0, 0, width, height ); + + glOrtho( glViewRect[0], glViewRect[1], glViewRect[2], glViewRect[3], -10.0, +10.0 ); +} + +void UVWidget::updateViewRect( int width, int height ) +{ + GLdouble glOffX = glUnit * zoom * 0.5 * width; + GLdouble glOffY = glUnit * zoom * 0.5 * height; + GLdouble glPosX = glUnit * pos.x(); + GLdouble glPosY = glUnit * pos.y(); + + glViewRect[0] = - glOffX - glPosX; + glViewRect[1] = + glOffX - glPosX; + glViewRect[2] = + glOffY + glPosY; + glViewRect[3] = - glOffY + glPosY; +} + +QPoint UVWidget::mapFromContents( const Vector2 & v ) const +{ + float x = ( ( v[0] - 0.5 ) - glViewRect[ 0 ] ) / ( glViewRect[ 1 ] - glViewRect[ 0 ] ) * width(); + float y = ( ( v[1] - 0.5 ) - glViewRect[ 2 ] ) / ( glViewRect[ 3 ] - glViewRect[ 2 ] ) * height() * ( - 1 ) + height(); + + return QPointF( x, y ).toPoint(); +} + +Vector2 UVWidget::mapToContents( const QPoint & p ) const +{ + float x = ( (float) p.x() / (float) width() ) * ( glViewRect[ 1 ] - glViewRect[ 0 ] ) + glViewRect[ 0 ]; + float y = ( (float) p.y() / (float) height() ) * ( glViewRect[ 2 ] - glViewRect[ 3 ] ) + glViewRect[ 3 ]; + return Vector2( x, y ); +} + +QVector UVWidget::indices( const QPoint & p ) const +{ + return indices( QRect( p - QPoint( 2, 2 ), QSize( 5, 5 ) ) ); +} + +QVector UVWidget::indices( const QRegion & region ) const +{ + QList hits; + + for ( int i = 0; i < texcoords.count(); i++ ) + { + if ( region.contains( mapFromContents( texcoords[ i ] ) ) ) + hits << i; + } + + return hits.toVector(); +} + +bool UVWidget::bindTexture( const QString & filename ) +{ + GLuint mipmaps = 0; + GLfloat max_anisotropy = 0.0f; + + QString extensions( (const char *) glGetString( GL_EXTENSIONS ) ); + + if ( extensions.contains( "GL_EXT_texture_filter_anisotropic" ) ) + { + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, & max_anisotropy ); + //qWarning() << "maximum anisotropy" << max_anisotropy; + } + + if ( mipmaps = textures->bind( filename ) ) + { + if ( max_anisotropy > 0.0f ) + { + if ( Options::antialias() ) + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy ); + else + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f ); + } + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ); + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + // TODO: Add support for non-square textures + + glMatrixMode( GL_TEXTURE ); + glLoadIdentity(); + + glMatrixMode( GL_MODELVIEW ); + return true; + } + + return false; + } + + + +QSize UVWidget::sizeHint() const +{ + if( sHint.isValid() ) { + return sHint; + } + + return QSizeF( BASESIZE, BASESIZE ).toSize(); +} + +void UVWidget::setSizeHint( const QSize & s ) +{ + sHint = s; +} + +QSize UVWidget::minimumSizeHint() const +{ + return QSizeF( BASESIZE, BASESIZE ).toSize(); +} + +int UVWidget::heightForWidth( int width ) const +{ + if ( width < minimumSizeHint().height() ) + return minimumSizeHint().height(); + return width; +} + +void UVWidget::mousePressEvent( QMouseEvent * e ) +{ + QPoint dPos( e->pos() - mousePos ); + mousePos = e->pos(); + + if ( e->button() == Qt::LeftButton ) + { + QVector hits = indices( mousePos ); + + if ( hits.isEmpty() ) + { + if ( ! e->modifiers().testFlag( Qt::ShiftModifier ) ) + selectNone(); + + if ( e->modifiers().testFlag( Qt::AltModifier ) ) + { + selectPoly << e->pos(); + } + else + { + selectRect.setTopLeft( mousePos ); + selectRect.setBottomRight( mousePos ); + } + } + else + { + if ( dPos.manhattanLength() > 4 ) + selectCycle = 0; + else + selectCycle++; + + int h = hits[ selectCycle % hits.count() ]; + + if ( ! e->modifiers().testFlag( Qt::ShiftModifier ) ) + { + if ( ! isSelected( h ) ) + selectNone(); + select( h ); + } + else + { + select( h, ! isSelected( h ) ); + } + + if ( selection.isEmpty() ) + { + setCursor( QCursor( Qt::CrossCursor ) ); + } + else + { + setCursor( QCursor( Qt::SizeAllCursor ) ); + } + } + } + + updateGL(); +} + +void UVWidget::mouseMoveEvent( QMouseEvent * e ) +{ + QPoint dPos( e->pos() - mousePos ); + + switch ( e->buttons() ) + { + case Qt::LeftButton: + if ( ! selectRect.isNull() ) + { + selectRect.setBottomRight( e->pos() ); + } + else if ( ! selectPoly.isEmpty() ) + { + selectPoly << e->pos(); + } + else + { + moveSelection( glUnit * zoom * dPos.x(), glUnit * zoom * dPos.y() ); + } + break; + + case Qt::MidButton: + pos += zoom * QPointF( dPos.x(), -dPos.y() ); + updateViewRect( width(), height() ); + + setCursor( QCursor( Qt::OpenHandCursor ) ); + + break; + + case Qt::RightButton: + zoom *= 1.0 + ( dPos.y() / ZOOMUNIT ); + + if ( zoom < 0.1 ) + { + zoom = 0.1; + } + else if ( zoom > 10.0 ) + { + zoom = 10.0; + } + + updateViewRect( width(), height() ); + + setCursor( QCursor( Qt::SizeVerCursor ) ); + + break; + + default: + if ( indices( e->pos() ).count() ) + { + setCursor( QCursor( Qt::ArrowCursor ) ); + } + else + { + setCursor( QCursor( Qt::CrossCursor ) ); + } + return; + } + + mousePos = e->pos(); + + updateGL(); +} + +void UVWidget::mouseReleaseEvent( QMouseEvent * e ) +{ + switch( e->button() ) + { + case Qt::LeftButton: + if ( ! selectRect.isNull() ) + { + select( QRegion( selectRect.normalized() ) ); + selectRect = QRect(); + } + else if ( ! selectPoly.isEmpty() ) + { + if ( selectPoly.size() > 2 ) + { + select( QRegion( QPolygon( selectPoly.toVector() ) ) ); + } + selectPoly.clear(); + } + break; + default: + break; + } + + if ( indices( e->pos() ).count() ) + { + setCursor( QCursor( Qt::ArrowCursor ) ); + } + else + { + setCursor( QCursor( Qt::CrossCursor ) ); + } + + updateGL(); +} + +void UVWidget::wheelEvent( QWheelEvent * e ) +{ + switch( e->modifiers()) { + case Qt::NoModifier: + zoom *= 1.0 + ( e->delta() / 8.0 ) / ZOOMUNIT; + + if( zoom < 0.1 ) { + zoom = 0.1; + } + else if( zoom > 10.0 ) { + zoom = 10.0; + } + + updateViewRect( width(), height() ); + + break; + } + + updateGL(); +} + + + +bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) +{ + if ( nif ) + { + disconnect( nif ); + } + + undoStack->clear(); + + nif = nifModel; + iShape = nifIndex; + + connect( nif, SIGNAL( modelReset() ), this, SLOT( close() ) ); + connect( nif, SIGNAL( destroyed() ), this, SLOT( close() ) ); + connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( nifDataChanged( const QModelIndex & ) ) ); + connect( nif, SIGNAL( rowsRemoved( const QModelIndex &, int, int ) ), this, SLOT( nifDataChanged( const QModelIndex ) ) ); + + textures->setNifFolder( nif->getFolder() ); + + iShapeData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + if( nif->inherits( iShapeData, "NiTriBasedGeomData" ) ) + { + iTexCoords = nif->getIndex( iShapeData, "UV Sets" ).child( 0, 0 ); + if( ! iTexCoords.isValid() || ! nif->rowCount( iTexCoords ) ) + { + return false; + } + + texcoords = nif->getArray< Vector2 >( iTexCoords ); + + QVector< Triangle > tris; + + if( nif->isNiBlock( iShapeData, "NiTriShapeData" ) ) + { + tris = nif->getArray< Triangle >( iShapeData, "Triangles" ); + } + else if( nif->isNiBlock( iShapeData, "NiTriStripsData" ) ) + { + QModelIndex iPoints = nif->getIndex( iShapeData, "Points" ); + if( iPoints.isValid() ) + { + for( int r = 0; r < nif->rowCount( iPoints ); r++ ) + { + tris += triangulate( nif->getArray< quint16 >( iPoints.child( r, 0 ) ) ); + } + } + else + { + return false; + } + } + else + { + return false; + } + + QVectorIterator< Triangle > itri( tris ); + while ( itri.hasNext() ) + { + const Triangle & t = itri.next(); + + int fIdx = faces.size(); + faces.append( face( fIdx, t[0], t[1], t[2] ) ); + + for( int i = 0; i < 3; i++ ) + { + texcoords2faces.insertMulti( t[i], fIdx ); + } + } + } + + foreach( qint32 l, nif->getLinkArray( iShape, "Properties" ) ) + { + QModelIndex iTexProp = nif->getBlock( l, "NiTexturingProperty" ); + if( iTexProp.isValid() ) + { + QModelIndex iBaseTex = nif->getIndex( iTexProp, "Base Texture" ); + if( iBaseTex.isValid() ) + { + QModelIndex iTexSource = nif->getBlock( nif->getLink( iBaseTex, "Source" ) ); + if( iTexSource.isValid() ) + { + texfile = TexCache::find( nif->get( iTexSource, "File Name" ) , nif->getFolder() ); + return true; + } + } + } + else { + iTexProp = nif->getBlock( l, "NiTextureProperty" ); + if( iTexProp.isValid() ) + { + QModelIndex iTexSource = nif->getBlock( nif->getLink( iTexProp, "Image" ) ); + if( iTexSource.isValid() ) + { + texfile = TexCache::find( nif->get( iTexSource, "File Name" ) , nif->getFolder() ); + return true; + } + } + } + } + + return false; +} + +void UVWidget::updateNif() +{ + if ( nif && iTexCoords.isValid() ) + { + disconnect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( nifDataChanged( const QModelIndex & ) ) ); + nif->setArray< Vector2 >( iTexCoords, texcoords ); + connect( nif, SIGNAL( dataChanged( const QModelIndex &, const QModelIndex & ) ), this, SLOT( nifDataChanged( const QModelIndex & ) ) ); + } +} + +void UVWidget::nifDataChanged( const QModelIndex & idx ) +{ + if ( ! nif || ! iShape.isValid() || ! iShapeData.isValid() || ! iTexCoords.isValid() ) + { + close(); + return; + } + + if ( nif->getBlock( idx ) == iShapeData ) + { + //if ( ! setNifData( nif, iShape ) ) + { + close(); + return; + } + } +} + +bool UVWidget::isSelected( int index ) +{ + return selection.contains( index ); +} + +class UVWSelectCommand : public QUndoCommand +{ +public: + UVWSelectCommand( UVWidget * w, const QList & nSel ) : QUndoCommand(), uvw( w ), newSelection( nSel ) + { + setText( "Select" ); + } + + int id() const + { + return 0; + } + + bool mergeWith( const QUndoCommand * cmd ) + { + if ( cmd->id() == id() ) + { + newSelection = static_cast( cmd )->newSelection; + return true; + } + return false; + } + + void redo() + { + oldSelection = uvw->selection; + uvw->selection = newSelection; + uvw->updateGL(); + } + + void undo() + { + uvw->selection = oldSelection; + uvw->updateGL(); + } + +protected: + UVWidget * uvw; + QList oldSelection, newSelection; +}; + +void UVWidget::select( int index, bool yes ) +{ + QList selection = this->selection; + if ( yes ) + { + if ( ! selection.contains( index ) ) + selection.append( index ); + } + else + selection.removeAll( index ); + undoStack->push( new UVWSelectCommand( this, selection ) ); +} + +void UVWidget::select( const QRegion & r, bool add ) +{ + QList selection( add ? this->selection : QList() ); + foreach ( int s, indices( r ) ) + { + if ( ! selection.contains( s ) ) + selection.append( s ); + } + undoStack->push( new UVWSelectCommand( this, selection ) ); +} + +void UVWidget::selectNone() +{ + undoStack->push( new UVWSelectCommand( this, QList() ) ); +} + +void UVWidget::selectAll() +{ + QList selection; + for ( int s = 0; s < texcoords.count(); s++ ) + selection << s; + undoStack->push( new UVWSelectCommand( this, selection ) ); +} + +void UVWidget::selectFaces() +{ + QList selection = this->selection; + foreach ( int s, selection ) + { + foreach ( int f, texcoords2faces.values( s ) ) + { + for ( int i = 0; i < 3; i++ ) + { + if ( ! selection.contains( faces[f].tc[i] ) ) + selection.append( faces[f].tc[i] ); + } + } + } + undoStack->push( new UVWSelectCommand( this, selection ) ); +} + +void UVWidget::selectConnected() +{ + QList selection = this->selection; + bool more = true; + while ( more ) + { + more = false; + foreach ( int s, selection ) + { + foreach ( int f, texcoords2faces.values( s ) ) + { + for ( int i = 0; i < 3; i++ ) + { + if ( ! selection.contains( faces[f].tc[i] ) ) + { + selection.append( faces[f].tc[i] ); + more = true; + } + } + } + } + } + undoStack->push( new UVWSelectCommand( this, selection ) ); +} + +class UVWMoveCommand : public QUndoCommand +{ +public: + UVWMoveCommand( UVWidget * w, double dx, double dy ) : QUndoCommand(), uvw( w ), move( dx, dy ) + { + setText( "Move" ); + } + + int id() const + { + return 1; + } + + bool mergeWith( const QUndoCommand * cmd ) + { + if ( cmd->id() == id() ) + { + move += static_cast( cmd )->move; + return true; + } + return false; + } + + void redo() + { + foreach( int tc, uvw->selection ) + { + uvw->texcoords[tc] += move; + } + uvw->updateNif(); + uvw->updateGL(); + } + + void undo() + { + foreach( int tc, uvw->selection ) + { + uvw->texcoords[tc] -= move; + } + uvw->updateNif(); + uvw->updateGL(); + } + +protected: + UVWidget * uvw; + Vector2 move; +}; + +void UVWidget::moveSelection( double moveX, double moveY ) +{ + undoStack->push( new UVWMoveCommand( this, moveX, moveY ) ); +} diff --git a/widgets/uvedit.h b/widgets/uvedit.h index 79ba6c846..67ea56360 100644 --- a/widgets/uvedit.h +++ b/widgets/uvedit.h @@ -1,151 +1,151 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef UVEDIT_H -#define UVEDIT_H - -#include - -#include -#include -#include - -class NifModel; -class QModelIndex; -class QUndoStack; -class TexCache; -class Vector2; - -class UVWidget : public QGLWidget -{ - Q_OBJECT -protected: - UVWidget( QWidget * parent = 0 ); - ~UVWidget(); - -public: - static UVWidget * createEditor( NifModel * nif, const QModelIndex & index ); - - bool setNifData( NifModel * nif, const QModelIndex & index ); - - QSize sizeHint() const; - QSize minimumSizeHint() const; - - void setSizeHint( const QSize & s ); - - int heightForWidth( int width ) const; - -protected: - void initializeGL(); - void resizeGL( int width, int height ); - void paintGL(); - - void mousePressEvent( QMouseEvent * e ); - void mouseReleaseEvent( QMouseEvent * e ); - void mouseMoveEvent( QMouseEvent * e ); - void wheelEvent( QWheelEvent * e ); - -protected slots: - bool isSelected( int index ); - void select( int index, bool yes = true ); - void select( const QRegion & r, bool add = true ); - void selectAll(); - void selectNone(); - void selectFaces(); - void selectConnected(); - void moveSelection( double dx, double dy ); - -protected slots: - void nifDataChanged( const QModelIndex & ); - -private: - QList< int > selection; - - QRect selectRect; - QList selectPoly; - int selectCycle; - - - struct face { - int index; - - int tc[3]; - - bool contains( int v ) { return ( tc[0] == v || tc[1] == v || tc[2] == v ); } - - face() : index( -1 ) {} - face( int idx, int tc1, int tc2, int tc3 ) : index( idx ) { tc[0] = tc1; tc[1] = tc2; tc[2] = tc3; } - }; - - QVector< Vector2 > texcoords; - QVector< face > faces; - QMap< int, int > texcoords2faces; - - QSize sHint; - - TexCache * textures; - QString texfile; - - void drawTexCoords(); - - void setupViewport( int width, int height ); - void updateViewRect( int width, int height ); - bool bindTexture( const QString & filename ); - - QVector indices( const QPoint & p ) const; - QVector indices( const QRegion & r ) const; - - QPoint mapFromContents( const Vector2 & v ) const; - Vector2 mapToContents( const QPoint & p ) const; - - void updateNif(); - - QPointer nif; - QPersistentModelIndex iShape, iShapeData, iTexCoords; - - GLdouble glViewRect[4]; - - QPointF pos; - - QPoint mousePos; - - GLdouble zoom; - - QUndoStack * undoStack; - - friend class UVWSelectCommand; - friend class UVWMoveCommand; - - QAction * aTextureBlend; -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef UVEDIT_H +#define UVEDIT_H + +#include + +#include +#include +#include + +class NifModel; +class QModelIndex; +class QUndoStack; +class TexCache; +class Vector2; + +class UVWidget : public QGLWidget +{ + Q_OBJECT +protected: + UVWidget( QWidget * parent = 0 ); + ~UVWidget(); + +public: + static UVWidget * createEditor( NifModel * nif, const QModelIndex & index ); + + bool setNifData( NifModel * nif, const QModelIndex & index ); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void setSizeHint( const QSize & s ); + + int heightForWidth( int width ) const; + +protected: + void initializeGL(); + void resizeGL( int width, int height ); + void paintGL(); + + void mousePressEvent( QMouseEvent * e ); + void mouseReleaseEvent( QMouseEvent * e ); + void mouseMoveEvent( QMouseEvent * e ); + void wheelEvent( QWheelEvent * e ); + +protected slots: + bool isSelected( int index ); + void select( int index, bool yes = true ); + void select( const QRegion & r, bool add = true ); + void selectAll(); + void selectNone(); + void selectFaces(); + void selectConnected(); + void moveSelection( double dx, double dy ); + +protected slots: + void nifDataChanged( const QModelIndex & ); + +private: + QList< int > selection; + + QRect selectRect; + QList selectPoly; + int selectCycle; + + + struct face { + int index; + + int tc[3]; + + bool contains( int v ) { return ( tc[0] == v || tc[1] == v || tc[2] == v ); } + + face() : index( -1 ) {} + face( int idx, int tc1, int tc2, int tc3 ) : index( idx ) { tc[0] = tc1; tc[1] = tc2; tc[2] = tc3; } + }; + + QVector< Vector2 > texcoords; + QVector< face > faces; + QMap< int, int > texcoords2faces; + + QSize sHint; + + TexCache * textures; + QString texfile; + + void drawTexCoords(); + + void setupViewport( int width, int height ); + void updateViewRect( int width, int height ); + bool bindTexture( const QString & filename ); + + QVector indices( const QPoint & p ) const; + QVector indices( const QRegion & r ) const; + + QPoint mapFromContents( const Vector2 & v ) const; + Vector2 mapToContents( const QPoint & p ) const; + + void updateNif(); + + QPointer nif; + QPersistentModelIndex iShape, iShapeData, iTexCoords; + + GLdouble glViewRect[4]; + + QPointF pos; + + QPoint mousePos; + + GLdouble zoom; + + QUndoStack * undoStack; + + friend class UVWSelectCommand; + friend class UVWMoveCommand; + + QAction * aTextureBlend; +}; + +#endif diff --git a/widgets/valuedit.cpp b/widgets/valuedit.cpp index aa817211f..0a7c135f2 100644 --- a/widgets/valuedit.cpp +++ b/widgets/valuedit.cpp @@ -1,866 +1,866 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#include "valueedit.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "floatedit.h" - -ValueEdit::ValueEdit( QWidget * parent ) : QWidget( parent ), typ( NifValue::tNone ), edit( 0 ) -{ - setAutoFillBackground( true ); -} - -bool ValueEdit::canEdit( NifValue::Type t ) -{ - return t == NifValue::tByte || t == NifValue::tWord || t == NifValue::tInt || t == NifValue::tFlags - || t == NifValue::tLink || t == NifValue::tUpLink || t == NifValue::tFloat || t == NifValue::tText - || t == NifValue::tSizedString || t == NifValue::tLineString || t == NifValue::tChar8String - || t == NifValue::tShortString || t == NifValue::tStringIndex || t == NifValue::tString - || t == NifValue::tVector4 || t == NifValue::tVector3 || t == NifValue::tVector2 - || t == NifValue::tColor3 || t == NifValue::tColor4 - || t == NifValue::tMatrix || t == NifValue::tQuat || t == NifValue::tQuatXYZW - || t == NifValue::tTriangle || t == NifValue::tShort || t == NifValue::tUInt - ; -} - -class CenterLabel : public QLabel -{ -public: - CenterLabel( const QString & txt ) : QLabel( txt ) { setAlignment( Qt::AlignCenter ); } - CenterLabel() : QLabel() { setAlignment( Qt::AlignCenter ); } -}; - -class UIntSpinBox : public QSpinBox -{ -public: - UIntSpinBox( QWidget * parent ) : QSpinBox( parent ) { setRange( INT_MIN, INT_MAX ); } - -protected: - QString textFromValue( int i ) const - { - return QString::number( (unsigned int) i ); - } - - int valueFromText( const QString & text ) const - { - return text.toUInt(); - } -}; - -void ValueEdit::setValue( const NifValue & v ) -{ - typ = v.type(); - - if ( edit ) - { - delete edit; - edit = 0; - resize( this->baseSize() ); - } - - switch ( typ ) - { - case NifValue::tByte: - { - QSpinBox * be = new QSpinBox( this ); - be->setFrame(false); - be->setRange( 0, 0xff ); - be->setValue( v.toCount() ); - edit = be; - } break; - case NifValue::tWord: - case NifValue::tFlags: - { - QSpinBox * we = new QSpinBox( this ); - we->setFrame(false); - we->setRange( 0, 0xffff ); - we->setValue( v.toCount() ); - edit = we; - } break; - case NifValue::tShort: - { - QSpinBox * we = new QSpinBox( this ); - we->setFrame(false); - we->setRange( SHRT_MIN, SHRT_MAX ); - we->setValue( (short)v.toCount() ); - edit = we; - } break; - case NifValue::tInt: - { - QSpinBox * ie = new QSpinBox( this ); - ie->setFrame(false); - ie->setRange( INT_MIN, INT_MAX ); - ie->setValue( (int)v.toCount() ); - edit = ie; - } break; - case NifValue::tStringIndex: - { - QSpinBox * ie = new QSpinBox( this ); - ie->setFrame(false); - ie->setRange( -1, INT_MAX ); - ie->setValue( (int)v.toCount() ); - edit = ie; - } break; - case NifValue::tUInt: - { - QSpinBox * ie = new UIntSpinBox( this ); - ie->setFrame(false); - ie->setValue( v.toCount() ); - edit = ie; - } break; - case NifValue::tLink: - case NifValue::tUpLink: - { - QLineEdit * le = new QLineEdit( this ); - int tmp = v.toLink(); - if ( tmp > 0 ) { - le->setText( QString::number(tmp) ); - } - edit = le; - } break; - case NifValue::tFloat: - { - FloatEdit * fe = new FloatEdit( this ); - /* - fe->setFrame(false); - fe->setRange( -1e10, +1e10 ); - fe->setDecimals( 4 ); - */ - fe->setValue( v.toFloat() ); - edit = fe; - } break; - - case NifValue::tText: - case NifValue::tSizedString: - { - TextEdit * te = new TextEdit( v.toString(), this ); - te->resize( size() ); - connect( te, SIGNAL( sigResized(QResizeEvent *) ), this, SLOT( childResized(QResizeEvent *) ) ); - edit = te; - } - break; - case NifValue::tLineString: - case NifValue::tShortString: - case NifValue::tChar8String: - { - QLineEdit * le = new QLineEdit( this ); - le->setText( v.toString() ); - edit = le; - } break; - //case NifValue::tText: - //{ - // TextEdit * te = new TextEdit( v.toString(), this ); - // te->setMinimumSize( width(), height() ); - // te->setBaseSize( width(), height() * 5); - // edit = te; - //} break; - case NifValue::tColor4: - { - ColorEdit * ce = new ColorEdit( this ); - ce->setColor4( v.get() ); - edit = ce; - } break; - case NifValue::tColor3: - { - ColorEdit * ce = new ColorEdit( this ); - ce->setColor3( v.get() ); - edit = ce; - } break; - case NifValue::tVector4: - { - VectorEdit * ve = new VectorEdit( this ); - ve->setVector4( v.get() ); - edit = ve; - } break; - case NifValue::tVector3: - { - VectorEdit * ve = new VectorEdit( this ); - ve->setVector3( v.get() ); - edit = ve; - } break; - case NifValue::tVector2: - { - VectorEdit * ve = new VectorEdit( this ); - ve->setVector2( v.get() ); - edit = ve; - } break; - case NifValue::tMatrix: - { - RotationEdit * re = new RotationEdit( this ); - re->setMatrix( v.get() ); - edit = re; - } break; - case NifValue::tQuat: - case NifValue::tQuatXYZW: - { - RotationEdit * re = new RotationEdit( this ); - re->setQuat( v.get() ); - edit = re; - } break; - case NifValue::tTriangle: - { - TriangleEdit * te = new TriangleEdit( this ); - te->setTriangle( v.get() ); - edit = te; - } break; - case NifValue::tString: - { - if (/*???*/false) - { - QSpinBox * ie = new UIntSpinBox( this ); - ie->setFrame(false); - ie->setValue( v.toCount() ); - edit = ie; - } - else - { - QLineEdit * le = new QLineEdit( this ); - le->setText( v.toString() ); - edit = le; - } - } break; - case NifValue::tFilePath: - { - if (/*???*/false) - { - QSpinBox * ie = new UIntSpinBox( this ); - ie->setFrame(false); - ie->setValue( v.toCount() ); - edit = ie; - } - else - { - QLineEdit * le = new QLineEdit( this ); - le->setText( v.toString() ); - edit = le; - } - } break; - default: - edit = 0; - break; - } - - resizeEditor(); - - setFocusProxy( edit ); -} - -NifValue ValueEdit::getValue() const -{ - NifValue val( typ ); - - if ( edit ) switch ( typ ) - { - case NifValue::tByte: - case NifValue::tWord: - case NifValue::tShort: - case NifValue::tFlags: - case NifValue::tInt: - case NifValue::tUInt: - case NifValue::tStringIndex: - val.setCount( qobject_cast( edit )->value() ); - break; - case NifValue::tLink: - case NifValue::tUpLink: - { - QString str = qobject_cast( edit )->text(); - bool ok = false; - int tmp = str.toInt( &ok ); - if ( ok == false || tmp < 0 ) { - val.setLink( -1 ); - } - else - { - val.setLink( tmp ); - } - } - break; - case NifValue::tFloat: - val.setFloat( qobject_cast( edit )->value() ); - break; - case NifValue::tLineString: - case NifValue::tShortString: - case NifValue::tChar8String: - val.fromString( qobject_cast( edit )->text() ); - break; - case NifValue::tSizedString: - case NifValue::tText: - val.fromString( qobject_cast( edit )->toPlainText() ); - break; - case NifValue::tColor4: - val.set( qobject_cast( edit )->getColor4() ); - break; - case NifValue::tColor3: - val.set( qobject_cast( edit )->getColor3() ); - break; - case NifValue::tVector4: - val.set( qobject_cast( edit )->getVector4() ); - break; - case NifValue::tVector3: - val.set( qobject_cast( edit )->getVector3() ); - break; - case NifValue::tVector2: - val.set( qobject_cast( edit )->getVector2() ); - break; - case NifValue::tMatrix: - val.set( qobject_cast( edit )->getMatrix() ); - break; - case NifValue::tQuat: - case NifValue::tQuatXYZW: - val.set( qobject_cast( edit )->getQuat() ); - break; - case NifValue::tTriangle: - val.set( qobject_cast( edit )->getTriangle() ); - break; - default: - break; - } - - return val; -} - -void ValueEdit::resizeEditor() -{ - if ( edit ) - { - QSize sz = size(); - - switch ( typ ) - { - case NifValue::tSizedString: - case NifValue::tText: - { - TextEdit * te = (TextEdit *)edit; - te->move( QPoint( 0, 0 ) ); - resize( size() ); - te->CalcSize(); - } break; - - default: - { - edit->move( QPoint( 0, 0 ) ); - edit->resize( sz ); - resize( sz ); - } break; - } - } -} - -void ValueEdit::childResized( QResizeEvent * e ) -{ - switch ( typ ) - { - case NifValue::tSizedString: - case NifValue::tText: - { - //edit->move( QPoint( 1, 0 ) ); - resize( QSize( width(), e->size().height() ) ); - } break; - } -} - -void ValueEdit::resizeEvent( QResizeEvent * ) -{ - resizeEditor(); -} - -ColorEdit::ColorEdit( QWidget * parent ) : QWidget( parent ) -{ - QHBoxLayout * lay = new QHBoxLayout; - lay->setMargin( 0 ); - lay->setSpacing( 0 ); - setLayout( lay ); - - lay->addWidget( new CenterLabel( "R" ), 1 ); - lay->addWidget( r = new QDoubleSpinBox, 5 ); - r->setDecimals( 3 ); - r->setRange( 0, 1 ); - r->setSingleStep( 0.01 ); - connect( r, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); - lay->addWidget( new CenterLabel( "G" ), 1 ); - lay->addWidget( g = new QDoubleSpinBox, 5 ); - g->setDecimals( 3 ); - g->setRange( 0, 1 ); - g->setSingleStep( 0.01 ); - connect( g, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); - lay->addWidget( new CenterLabel( "B" ), 1 ); - lay->addWidget( b = new QDoubleSpinBox, 5 ); - b->setDecimals( 3 ); - b->setRange( 0, 1 ); - b->setSingleStep( 0.01 ); - connect( b, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); - lay->addWidget( al = new CenterLabel( "A" ), 1 ); - lay->addWidget( a = new QDoubleSpinBox, 5 ); - a->setDecimals( 3 ); - a->setRange( 0, 1 ); - a->setSingleStep( 0.01 ); - connect( a, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); - - setting = false; - setFocusProxy( r ); -} - -void ColorEdit::setColor4( const Color4 & v ) -{ - setting = true; - r->setValue( v[0] ); - g->setValue( v[1] ); - b->setValue( v[2] ); - a->setValue( v[3] ); a->setShown( true ); al->setShown( true ); - setting = false; -} - -void ColorEdit::setColor3( const Color3 & v ) -{ - setting = true; - r->setValue( v[0] ); - g->setValue( v[1] ); - b->setValue( v[2] ); - a->setValue( 1.0 ); a->setHidden( true ); al->setHidden( true ); - setting = false; -} - -void ColorEdit::sltChanged() -{ - if ( ! setting ) emit sigEdited(); -} - -Color4 ColorEdit::getColor4() const -{ - return Color4( r->value(), g->value(), b->value(), a->value() ); -} - -Color3 ColorEdit::getColor3() const -{ - return Color3( r->value(), g->value(), b->value() ); -} - - -VectorEdit::VectorEdit( QWidget * parent ) : QWidget( parent ) -{ - QHBoxLayout * lay = new QHBoxLayout( this ); - lay->setMargin( 0 ); - lay->setSpacing( 0 ); - - CenterLabel * xl, * yl; - - lay->addWidget( xl = new CenterLabel( "X" ), 1 ); - lay->addWidget( x = new QDoubleSpinBox, 5 ); - x->setDecimals( 4 ); - x->setRange( - 100000000, + 100000000 ); - connect( x, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); - lay->addWidget( yl = new CenterLabel( "Y" ), 1 ); - lay->addWidget( y = new QDoubleSpinBox, 5 ); - y->setDecimals( 4 ); - y->setRange( - 100000000, + 100000000 ); - connect( y, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); - lay->addWidget( zl = new CenterLabel( "Z" ), 1 ); - lay->addWidget( z = new QDoubleSpinBox, 5 ); - z->setDecimals( 4 ); - z->setRange( - 100000000, + 100000000 ); - connect( z, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); - lay->addWidget( wl = new CenterLabel( "W" ), 1 ); - lay->addWidget( w = new QDoubleSpinBox, 5 ); - w->setDecimals( 4 ); - w->setRange( - 100000000, + 100000000 ); - connect( w, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); - - /* - xl->setBuddy( x ); - yl->setBuddy( y ); - zl->setBuddy( z ); - wl->setBuddy( w ); - */ - - setting = false; - setFocusProxy( x ); -} - -void VectorEdit::setVector4( const Vector4 & v ) -{ - setting = true; - x->setValue( v[0] ); - y->setValue( v[1] ); - z->setValue( v[2] ); z->setShown( true ); zl->setShown( true ); - w->setValue( v[3] ); w->setShown( true ); wl->setShown( true ); - setting = false; -} - -void VectorEdit::setVector3( const Vector3 & v ) -{ - setting = true; - x->setValue( v[0] ); - y->setValue( v[1] ); - z->setValue( v[2] ); z->setShown( true ); zl->setShown( true ); - w->setValue( 0.0 ); w->setHidden( true ); wl->setHidden( true ); - setting = false; -} - -void VectorEdit::setVector2( const Vector2 & v ) -{ - setting = true; - x->setValue( v[0] ); - y->setValue( v[1] ); - z->setValue( 0.0 ); z->setHidden( true ); zl->setHidden( true ); - w->setValue( 0.0 ); w->setHidden( true ); wl->setHidden( true ); - setting = false; -} - -void VectorEdit::sltChanged() -{ - if ( ! setting ) emit sigEdited(); -} - -Vector4 VectorEdit::getVector4() const -{ - return Vector4( x->value(), y->value(), z->value(), w->value() ); -} - -Vector3 VectorEdit::getVector3() const -{ - return Vector3( x->value(), y->value(), z->value() ); -} - -Vector2 VectorEdit::getVector2() const -{ - return Vector2( x->value(), y->value() ); -} - - -RotationEdit::RotationEdit( QWidget * parent ) : QWidget( parent ), mode( mAuto ), setting( false ) -{ - actMode = new QAction( this ); - connect( actMode, SIGNAL( triggered() ), this, SLOT( switchMode() ) ); - QToolButton * btMode = new QToolButton( this ); - btMode->setDefaultAction( actMode ); - - QHBoxLayout * lay = new QHBoxLayout( this ); - lay->setMargin( 0 ); - lay->setSpacing( 0 ); - - lay->addWidget( btMode, 2 ); - - for ( int x = 0; x < 4; x++ ) - { - lay->addWidget( l[x] = new CenterLabel, 1 ); - lay->addWidget( v[x] = new QDoubleSpinBox, 5 ); - connect( v[x], SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); - } - - setFocusProxy( v[0] ); - - setupMode(); -} - -void RotationEdit::switchMode() -{ - Matrix m = getMatrix(); - - if ( mode == mAxis ) - mode = mEuler; - else - mode = mAxis; - - setupMode(); - - setMatrix( m ); -} - -void RotationEdit::setupMode() -{ - switch ( mode ) - { - case mAuto: - case mEuler: - { - actMode->setText( "Euler" ); - QStringList labs( QStringList() << "Y" << "P" << "R" ); - for ( int x = 0; x < 4; x++ ) - { - l[x]->setText( labs.value( x ) ); - v[x]->setDecimals( 1 ); - v[x]->setRange( - 360, + 360 ); - v[x]->setSingleStep( 1 ); - l[x]->setHidden( x == 3 ); - v[x]->setHidden( x == 3 ); - } - } break; - case mAxis: - { - actMode->setText( "Axis" ); - QStringList labs( QStringList() << "A" << "X" << "Y" << "Z" ); - for ( int x = 0; x < 4; x++ ) - { - l[x]->setText( labs.value( x ) ); - if ( x == 0 ) - { - v[x]->setDecimals( 1 ); - v[x]->setRange( - 360, + 360 ); - v[x]->setSingleStep( 1 ); - } - else - { - v[x]->setDecimals( 5 ); - v[x]->setRange( - 1.0, + 1.0 ); - v[x]->setSingleStep( 0.1 ); - } - l[x]->setHidden( false ); - v[x]->setHidden( false ); - } - } break; - } -} - -void RotationEdit::setMatrix( const Matrix & m ) -{ - setting = true; - - if ( mode == mAuto ) - { - mode = mEuler; - setupMode(); - } - - switch ( mode ) - { - case mAuto: - case mEuler: - { - float Y, P, R; - m.toEuler( Y, P, R ); - v[0]->setValue( Y / PI * 180 ); - v[1]->setValue( P / PI * 180 ); - v[2]->setValue( R / PI * 180 ); - } break; - case mAxis: - { - Vector3 axis; float angle; - m.toQuat().toAxisAngle( axis, angle ); - v[0]->setValue( angle / PI * 180 ); - for ( int x = 0; x < 3; x++ ) - v[x+1]->setValue( axis[x] ); - } break; - } - - setting = false; -} - -void RotationEdit::setQuat( const Quat & q ) -{ - setting = true; - - if ( mode == mAuto ) - { - mode = mAxis; - setupMode(); - } - - Matrix m; m.fromQuat( q ); - setMatrix( m ); - - setting = false; -} - -Matrix RotationEdit::getMatrix() const -{ - switch ( mode ) - { - case mAuto: - case mEuler: - { - Matrix m; m.fromEuler( v[0]->value() / 180 * PI, v[1]->value() / 180 * PI, v[2]->value() / 180 * PI ); - return m; - }; - case mAxis: - { - Quat q; - q.fromAxisAngle( Vector3( v[1]->value(), v[2]->value(), v[3]->value() ), v[0]->value() / 180 * PI ); - Matrix m; - m.fromQuat( q ); - return m; - }; - } - return Matrix(); -} - -Quat RotationEdit::getQuat() const -{ - switch ( mode ) - { - case mAuto: - case mEuler: - { - Matrix m; m.fromEuler( v[0]->value() / 180 * PI, v[1]->value() / 180 * PI, v[2]->value() / 180 * PI ); - return m.toQuat(); - } - case mAxis: - { - Quat q; - q.fromAxisAngle( Vector3( v[1]->value(), v[2]->value(), v[3]->value() ), v[0]->value() / 180 * PI ); - return q; - } - } - return Quat(); -} - -void RotationEdit::sltChanged() -{ - if ( ! setting ) emit sigEdited(); -} - - -TriangleEdit::TriangleEdit( QWidget * parent ) : QWidget( parent ) -{ - QHBoxLayout * lay = new QHBoxLayout( this ); - lay->setMargin( 0 ); - lay->setSpacing( 0 ); - - lay->addWidget( v1 = new QSpinBox ); - v1->setRange( 0, + 0xffff ); - lay->addWidget( v2 = new QSpinBox ); - v2->setRange( 0, + 0xffff ); - lay->addWidget( v3 = new QSpinBox ); - v3->setRange( 0, + 0xffff ); - - setFocusProxy( v1 ); -} - -void TriangleEdit::setTriangle( const Triangle & t ) -{ - v1->setValue( t[0] ); - v2->setValue( t[1] ); - v3->setValue( t[2] ); -} - - -Triangle TriangleEdit::getTriangle() const -{ - return Triangle( v1->value(), v2->value(), v3->value() ); -} - - -TextEdit::TextEdit(const QString & str, QWidget *parent) : QTextEdit(parent) -{ - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - //setFrameShape(QFrame::NoFrame); - setFrameShadow(QFrame::Plain); - setLineWrapMode(QTextEdit::NoWrap); - setAcceptRichText(false); - setAutoFormatting(QTextEdit::AutoNone); - setTabChangesFocus(true); - setPlainText(str); - CalcSize(); - connect( this, SIGNAL( textChanged() ), this, SLOT( sltTextChanged() ) ); - - QTextCursor cursor = this->textCursor(); - cursor.select(QTextCursor::LineUnderCursor); - setTextCursor( cursor ); -} - -void TextEdit::resizeEvent( QResizeEvent * e ) -{ - QTextEdit::resizeEvent(e); - emit sigResized( e ); -} - -void TextEdit::keyPressEvent(QKeyEvent *e) -{ - if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_F4) - { - if (e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ShiftModifier) - { - // Eat the Alt and Shift Modifiers and 'send' an enter instead - e->accept(); - - QKeyEvent newEvent( - e->type(), - e->key(), - Qt::NoModifier, - e->text(), - e->isAutoRepeat(), - e->count() - ); - QTextEdit::keyPressEvent(&newEvent); - } - else if (e->modifiers() == Qt::NoModifier) - { - e->ignore(); - } - else - { - QTextEdit::keyPressEvent(e); - } - } - else - { - QTextEdit::keyPressEvent(e); - } -} - -void TextEdit::sltTextChanged() -{ - CalcSize(); -} - -void TextEdit::CalcSize() -{ - QSize sz = this->size(); - QString text = this->toPlainText(); - int lines = text.count(QLatin1Char('\n'))+1; - if (lines > 5) lines = 5; - int ht = fontMetrics().lineSpacing() * lines + 10; // 10 extra lines because single lines seem to need a bit more than lineSpacing() - if ( lines == 1 ) - { - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - sz.setHeight( ht ); - resize( sz ); - } - else if ( ht > sz.height() ) - { - setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - sz.setHeight( ht ); - resize( sz ); - } -} +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#include "valueedit.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "floatedit.h" + +ValueEdit::ValueEdit( QWidget * parent ) : QWidget( parent ), typ( NifValue::tNone ), edit( 0 ) +{ + setAutoFillBackground( true ); +} + +bool ValueEdit::canEdit( NifValue::Type t ) +{ + return t == NifValue::tByte || t == NifValue::tWord || t == NifValue::tInt || t == NifValue::tFlags + || t == NifValue::tLink || t == NifValue::tUpLink || t == NifValue::tFloat || t == NifValue::tText + || t == NifValue::tSizedString || t == NifValue::tLineString || t == NifValue::tChar8String + || t == NifValue::tShortString || t == NifValue::tStringIndex || t == NifValue::tString + || t == NifValue::tVector4 || t == NifValue::tVector3 || t == NifValue::tVector2 + || t == NifValue::tColor3 || t == NifValue::tColor4 + || t == NifValue::tMatrix || t == NifValue::tQuat || t == NifValue::tQuatXYZW + || t == NifValue::tTriangle || t == NifValue::tShort || t == NifValue::tUInt + ; +} + +class CenterLabel : public QLabel +{ +public: + CenterLabel( const QString & txt ) : QLabel( txt ) { setAlignment( Qt::AlignCenter ); } + CenterLabel() : QLabel() { setAlignment( Qt::AlignCenter ); } +}; + +class UIntSpinBox : public QSpinBox +{ +public: + UIntSpinBox( QWidget * parent ) : QSpinBox( parent ) { setRange( INT_MIN, INT_MAX ); } + +protected: + QString textFromValue( int i ) const + { + return QString::number( (unsigned int) i ); + } + + int valueFromText( const QString & text ) const + { + return text.toUInt(); + } +}; + +void ValueEdit::setValue( const NifValue & v ) +{ + typ = v.type(); + + if ( edit ) + { + delete edit; + edit = 0; + resize( this->baseSize() ); + } + + switch ( typ ) + { + case NifValue::tByte: + { + QSpinBox * be = new QSpinBox( this ); + be->setFrame(false); + be->setRange( 0, 0xff ); + be->setValue( v.toCount() ); + edit = be; + } break; + case NifValue::tWord: + case NifValue::tFlags: + { + QSpinBox * we = new QSpinBox( this ); + we->setFrame(false); + we->setRange( 0, 0xffff ); + we->setValue( v.toCount() ); + edit = we; + } break; + case NifValue::tShort: + { + QSpinBox * we = new QSpinBox( this ); + we->setFrame(false); + we->setRange( SHRT_MIN, SHRT_MAX ); + we->setValue( (short)v.toCount() ); + edit = we; + } break; + case NifValue::tInt: + { + QSpinBox * ie = new QSpinBox( this ); + ie->setFrame(false); + ie->setRange( INT_MIN, INT_MAX ); + ie->setValue( (int)v.toCount() ); + edit = ie; + } break; + case NifValue::tStringIndex: + { + QSpinBox * ie = new QSpinBox( this ); + ie->setFrame(false); + ie->setRange( -1, INT_MAX ); + ie->setValue( (int)v.toCount() ); + edit = ie; + } break; + case NifValue::tUInt: + { + QSpinBox * ie = new UIntSpinBox( this ); + ie->setFrame(false); + ie->setValue( v.toCount() ); + edit = ie; + } break; + case NifValue::tLink: + case NifValue::tUpLink: + { + QLineEdit * le = new QLineEdit( this ); + int tmp = v.toLink(); + if ( tmp > 0 ) { + le->setText( QString::number(tmp) ); + } + edit = le; + } break; + case NifValue::tFloat: + { + FloatEdit * fe = new FloatEdit( this ); + /* + fe->setFrame(false); + fe->setRange( -1e10, +1e10 ); + fe->setDecimals( 4 ); + */ + fe->setValue( v.toFloat() ); + edit = fe; + } break; + + case NifValue::tText: + case NifValue::tSizedString: + { + TextEdit * te = new TextEdit( v.toString(), this ); + te->resize( size() ); + connect( te, SIGNAL( sigResized(QResizeEvent *) ), this, SLOT( childResized(QResizeEvent *) ) ); + edit = te; + } + break; + case NifValue::tLineString: + case NifValue::tShortString: + case NifValue::tChar8String: + { + QLineEdit * le = new QLineEdit( this ); + le->setText( v.toString() ); + edit = le; + } break; + //case NifValue::tText: + //{ + // TextEdit * te = new TextEdit( v.toString(), this ); + // te->setMinimumSize( width(), height() ); + // te->setBaseSize( width(), height() * 5); + // edit = te; + //} break; + case NifValue::tColor4: + { + ColorEdit * ce = new ColorEdit( this ); + ce->setColor4( v.get() ); + edit = ce; + } break; + case NifValue::tColor3: + { + ColorEdit * ce = new ColorEdit( this ); + ce->setColor3( v.get() ); + edit = ce; + } break; + case NifValue::tVector4: + { + VectorEdit * ve = new VectorEdit( this ); + ve->setVector4( v.get() ); + edit = ve; + } break; + case NifValue::tVector3: + { + VectorEdit * ve = new VectorEdit( this ); + ve->setVector3( v.get() ); + edit = ve; + } break; + case NifValue::tVector2: + { + VectorEdit * ve = new VectorEdit( this ); + ve->setVector2( v.get() ); + edit = ve; + } break; + case NifValue::tMatrix: + { + RotationEdit * re = new RotationEdit( this ); + re->setMatrix( v.get() ); + edit = re; + } break; + case NifValue::tQuat: + case NifValue::tQuatXYZW: + { + RotationEdit * re = new RotationEdit( this ); + re->setQuat( v.get() ); + edit = re; + } break; + case NifValue::tTriangle: + { + TriangleEdit * te = new TriangleEdit( this ); + te->setTriangle( v.get() ); + edit = te; + } break; + case NifValue::tString: + { + if (/*???*/false) + { + QSpinBox * ie = new UIntSpinBox( this ); + ie->setFrame(false); + ie->setValue( v.toCount() ); + edit = ie; + } + else + { + QLineEdit * le = new QLineEdit( this ); + le->setText( v.toString() ); + edit = le; + } + } break; + case NifValue::tFilePath: + { + if (/*???*/false) + { + QSpinBox * ie = new UIntSpinBox( this ); + ie->setFrame(false); + ie->setValue( v.toCount() ); + edit = ie; + } + else + { + QLineEdit * le = new QLineEdit( this ); + le->setText( v.toString() ); + edit = le; + } + } break; + default: + edit = 0; + break; + } + + resizeEditor(); + + setFocusProxy( edit ); +} + +NifValue ValueEdit::getValue() const +{ + NifValue val( typ ); + + if ( edit ) switch ( typ ) + { + case NifValue::tByte: + case NifValue::tWord: + case NifValue::tShort: + case NifValue::tFlags: + case NifValue::tInt: + case NifValue::tUInt: + case NifValue::tStringIndex: + val.setCount( qobject_cast( edit )->value() ); + break; + case NifValue::tLink: + case NifValue::tUpLink: + { + QString str = qobject_cast( edit )->text(); + bool ok = false; + int tmp = str.toInt( &ok ); + if ( ok == false || tmp < 0 ) { + val.setLink( -1 ); + } + else + { + val.setLink( tmp ); + } + } + break; + case NifValue::tFloat: + val.setFloat( qobject_cast( edit )->value() ); + break; + case NifValue::tLineString: + case NifValue::tShortString: + case NifValue::tChar8String: + val.fromString( qobject_cast( edit )->text() ); + break; + case NifValue::tSizedString: + case NifValue::tText: + val.fromString( qobject_cast( edit )->toPlainText() ); + break; + case NifValue::tColor4: + val.set( qobject_cast( edit )->getColor4() ); + break; + case NifValue::tColor3: + val.set( qobject_cast( edit )->getColor3() ); + break; + case NifValue::tVector4: + val.set( qobject_cast( edit )->getVector4() ); + break; + case NifValue::tVector3: + val.set( qobject_cast( edit )->getVector3() ); + break; + case NifValue::tVector2: + val.set( qobject_cast( edit )->getVector2() ); + break; + case NifValue::tMatrix: + val.set( qobject_cast( edit )->getMatrix() ); + break; + case NifValue::tQuat: + case NifValue::tQuatXYZW: + val.set( qobject_cast( edit )->getQuat() ); + break; + case NifValue::tTriangle: + val.set( qobject_cast( edit )->getTriangle() ); + break; + default: + break; + } + + return val; +} + +void ValueEdit::resizeEditor() +{ + if ( edit ) + { + QSize sz = size(); + + switch ( typ ) + { + case NifValue::tSizedString: + case NifValue::tText: + { + TextEdit * te = (TextEdit *)edit; + te->move( QPoint( 0, 0 ) ); + resize( size() ); + te->CalcSize(); + } break; + + default: + { + edit->move( QPoint( 0, 0 ) ); + edit->resize( sz ); + resize( sz ); + } break; + } + } +} + +void ValueEdit::childResized( QResizeEvent * e ) +{ + switch ( typ ) + { + case NifValue::tSizedString: + case NifValue::tText: + { + //edit->move( QPoint( 1, 0 ) ); + resize( QSize( width(), e->size().height() ) ); + } break; + } +} + +void ValueEdit::resizeEvent( QResizeEvent * ) +{ + resizeEditor(); +} + +ColorEdit::ColorEdit( QWidget * parent ) : QWidget( parent ) +{ + QHBoxLayout * lay = new QHBoxLayout; + lay->setMargin( 0 ); + lay->setSpacing( 0 ); + setLayout( lay ); + + lay->addWidget( new CenterLabel( "R" ), 1 ); + lay->addWidget( r = new QDoubleSpinBox, 5 ); + r->setDecimals( 3 ); + r->setRange( 0, 1 ); + r->setSingleStep( 0.01 ); + connect( r, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); + lay->addWidget( new CenterLabel( "G" ), 1 ); + lay->addWidget( g = new QDoubleSpinBox, 5 ); + g->setDecimals( 3 ); + g->setRange( 0, 1 ); + g->setSingleStep( 0.01 ); + connect( g, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); + lay->addWidget( new CenterLabel( "B" ), 1 ); + lay->addWidget( b = new QDoubleSpinBox, 5 ); + b->setDecimals( 3 ); + b->setRange( 0, 1 ); + b->setSingleStep( 0.01 ); + connect( b, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); + lay->addWidget( al = new CenterLabel( "A" ), 1 ); + lay->addWidget( a = new QDoubleSpinBox, 5 ); + a->setDecimals( 3 ); + a->setRange( 0, 1 ); + a->setSingleStep( 0.01 ); + connect( a, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); + + setting = false; + setFocusProxy( r ); +} + +void ColorEdit::setColor4( const Color4 & v ) +{ + setting = true; + r->setValue( v[0] ); + g->setValue( v[1] ); + b->setValue( v[2] ); + a->setValue( v[3] ); a->setShown( true ); al->setShown( true ); + setting = false; +} + +void ColorEdit::setColor3( const Color3 & v ) +{ + setting = true; + r->setValue( v[0] ); + g->setValue( v[1] ); + b->setValue( v[2] ); + a->setValue( 1.0 ); a->setHidden( true ); al->setHidden( true ); + setting = false; +} + +void ColorEdit::sltChanged() +{ + if ( ! setting ) emit sigEdited(); +} + +Color4 ColorEdit::getColor4() const +{ + return Color4( r->value(), g->value(), b->value(), a->value() ); +} + +Color3 ColorEdit::getColor3() const +{ + return Color3( r->value(), g->value(), b->value() ); +} + + +VectorEdit::VectorEdit( QWidget * parent ) : QWidget( parent ) +{ + QHBoxLayout * lay = new QHBoxLayout( this ); + lay->setMargin( 0 ); + lay->setSpacing( 0 ); + + CenterLabel * xl, * yl; + + lay->addWidget( xl = new CenterLabel( "X" ), 1 ); + lay->addWidget( x = new QDoubleSpinBox, 5 ); + x->setDecimals( 4 ); + x->setRange( - 100000000, + 100000000 ); + connect( x, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); + lay->addWidget( yl = new CenterLabel( "Y" ), 1 ); + lay->addWidget( y = new QDoubleSpinBox, 5 ); + y->setDecimals( 4 ); + y->setRange( - 100000000, + 100000000 ); + connect( y, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); + lay->addWidget( zl = new CenterLabel( "Z" ), 1 ); + lay->addWidget( z = new QDoubleSpinBox, 5 ); + z->setDecimals( 4 ); + z->setRange( - 100000000, + 100000000 ); + connect( z, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); + lay->addWidget( wl = new CenterLabel( "W" ), 1 ); + lay->addWidget( w = new QDoubleSpinBox, 5 ); + w->setDecimals( 4 ); + w->setRange( - 100000000, + 100000000 ); + connect( w, SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); + + /* + xl->setBuddy( x ); + yl->setBuddy( y ); + zl->setBuddy( z ); + wl->setBuddy( w ); + */ + + setting = false; + setFocusProxy( x ); +} + +void VectorEdit::setVector4( const Vector4 & v ) +{ + setting = true; + x->setValue( v[0] ); + y->setValue( v[1] ); + z->setValue( v[2] ); z->setShown( true ); zl->setShown( true ); + w->setValue( v[3] ); w->setShown( true ); wl->setShown( true ); + setting = false; +} + +void VectorEdit::setVector3( const Vector3 & v ) +{ + setting = true; + x->setValue( v[0] ); + y->setValue( v[1] ); + z->setValue( v[2] ); z->setShown( true ); zl->setShown( true ); + w->setValue( 0.0 ); w->setHidden( true ); wl->setHidden( true ); + setting = false; +} + +void VectorEdit::setVector2( const Vector2 & v ) +{ + setting = true; + x->setValue( v[0] ); + y->setValue( v[1] ); + z->setValue( 0.0 ); z->setHidden( true ); zl->setHidden( true ); + w->setValue( 0.0 ); w->setHidden( true ); wl->setHidden( true ); + setting = false; +} + +void VectorEdit::sltChanged() +{ + if ( ! setting ) emit sigEdited(); +} + +Vector4 VectorEdit::getVector4() const +{ + return Vector4( x->value(), y->value(), z->value(), w->value() ); +} + +Vector3 VectorEdit::getVector3() const +{ + return Vector3( x->value(), y->value(), z->value() ); +} + +Vector2 VectorEdit::getVector2() const +{ + return Vector2( x->value(), y->value() ); +} + + +RotationEdit::RotationEdit( QWidget * parent ) : QWidget( parent ), mode( mAuto ), setting( false ) +{ + actMode = new QAction( this ); + connect( actMode, SIGNAL( triggered() ), this, SLOT( switchMode() ) ); + QToolButton * btMode = new QToolButton( this ); + btMode->setDefaultAction( actMode ); + + QHBoxLayout * lay = new QHBoxLayout( this ); + lay->setMargin( 0 ); + lay->setSpacing( 0 ); + + lay->addWidget( btMode, 2 ); + + for ( int x = 0; x < 4; x++ ) + { + lay->addWidget( l[x] = new CenterLabel, 1 ); + lay->addWidget( v[x] = new QDoubleSpinBox, 5 ); + connect( v[x], SIGNAL( valueChanged( double ) ), this, SLOT( sltChanged() ) ); + } + + setFocusProxy( v[0] ); + + setupMode(); +} + +void RotationEdit::switchMode() +{ + Matrix m = getMatrix(); + + if ( mode == mAxis ) + mode = mEuler; + else + mode = mAxis; + + setupMode(); + + setMatrix( m ); +} + +void RotationEdit::setupMode() +{ + switch ( mode ) + { + case mAuto: + case mEuler: + { + actMode->setText( "Euler" ); + QStringList labs( QStringList() << "Y" << "P" << "R" ); + for ( int x = 0; x < 4; x++ ) + { + l[x]->setText( labs.value( x ) ); + v[x]->setDecimals( 1 ); + v[x]->setRange( - 360, + 360 ); + v[x]->setSingleStep( 1 ); + l[x]->setHidden( x == 3 ); + v[x]->setHidden( x == 3 ); + } + } break; + case mAxis: + { + actMode->setText( "Axis" ); + QStringList labs( QStringList() << "A" << "X" << "Y" << "Z" ); + for ( int x = 0; x < 4; x++ ) + { + l[x]->setText( labs.value( x ) ); + if ( x == 0 ) + { + v[x]->setDecimals( 1 ); + v[x]->setRange( - 360, + 360 ); + v[x]->setSingleStep( 1 ); + } + else + { + v[x]->setDecimals( 5 ); + v[x]->setRange( - 1.0, + 1.0 ); + v[x]->setSingleStep( 0.1 ); + } + l[x]->setHidden( false ); + v[x]->setHidden( false ); + } + } break; + } +} + +void RotationEdit::setMatrix( const Matrix & m ) +{ + setting = true; + + if ( mode == mAuto ) + { + mode = mEuler; + setupMode(); + } + + switch ( mode ) + { + case mAuto: + case mEuler: + { + float Y, P, R; + m.toEuler( Y, P, R ); + v[0]->setValue( Y / PI * 180 ); + v[1]->setValue( P / PI * 180 ); + v[2]->setValue( R / PI * 180 ); + } break; + case mAxis: + { + Vector3 axis; float angle; + m.toQuat().toAxisAngle( axis, angle ); + v[0]->setValue( angle / PI * 180 ); + for ( int x = 0; x < 3; x++ ) + v[x+1]->setValue( axis[x] ); + } break; + } + + setting = false; +} + +void RotationEdit::setQuat( const Quat & q ) +{ + setting = true; + + if ( mode == mAuto ) + { + mode = mAxis; + setupMode(); + } + + Matrix m; m.fromQuat( q ); + setMatrix( m ); + + setting = false; +} + +Matrix RotationEdit::getMatrix() const +{ + switch ( mode ) + { + case mAuto: + case mEuler: + { + Matrix m; m.fromEuler( v[0]->value() / 180 * PI, v[1]->value() / 180 * PI, v[2]->value() / 180 * PI ); + return m; + }; + case mAxis: + { + Quat q; + q.fromAxisAngle( Vector3( v[1]->value(), v[2]->value(), v[3]->value() ), v[0]->value() / 180 * PI ); + Matrix m; + m.fromQuat( q ); + return m; + }; + } + return Matrix(); +} + +Quat RotationEdit::getQuat() const +{ + switch ( mode ) + { + case mAuto: + case mEuler: + { + Matrix m; m.fromEuler( v[0]->value() / 180 * PI, v[1]->value() / 180 * PI, v[2]->value() / 180 * PI ); + return m.toQuat(); + } + case mAxis: + { + Quat q; + q.fromAxisAngle( Vector3( v[1]->value(), v[2]->value(), v[3]->value() ), v[0]->value() / 180 * PI ); + return q; + } + } + return Quat(); +} + +void RotationEdit::sltChanged() +{ + if ( ! setting ) emit sigEdited(); +} + + +TriangleEdit::TriangleEdit( QWidget * parent ) : QWidget( parent ) +{ + QHBoxLayout * lay = new QHBoxLayout( this ); + lay->setMargin( 0 ); + lay->setSpacing( 0 ); + + lay->addWidget( v1 = new QSpinBox ); + v1->setRange( 0, + 0xffff ); + lay->addWidget( v2 = new QSpinBox ); + v2->setRange( 0, + 0xffff ); + lay->addWidget( v3 = new QSpinBox ); + v3->setRange( 0, + 0xffff ); + + setFocusProxy( v1 ); +} + +void TriangleEdit::setTriangle( const Triangle & t ) +{ + v1->setValue( t[0] ); + v2->setValue( t[1] ); + v3->setValue( t[2] ); +} + + +Triangle TriangleEdit::getTriangle() const +{ + return Triangle( v1->value(), v2->value(), v3->value() ); +} + + +TextEdit::TextEdit(const QString & str, QWidget *parent) : QTextEdit(parent) +{ + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + //setFrameShape(QFrame::NoFrame); + setFrameShadow(QFrame::Plain); + setLineWrapMode(QTextEdit::NoWrap); + setAcceptRichText(false); + setAutoFormatting(QTextEdit::AutoNone); + setTabChangesFocus(true); + setPlainText(str); + CalcSize(); + connect( this, SIGNAL( textChanged() ), this, SLOT( sltTextChanged() ) ); + + QTextCursor cursor = this->textCursor(); + cursor.select(QTextCursor::LineUnderCursor); + setTextCursor( cursor ); +} + +void TextEdit::resizeEvent( QResizeEvent * e ) +{ + QTextEdit::resizeEvent(e); + emit sigResized( e ); +} + +void TextEdit::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_F4) + { + if (e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ShiftModifier) + { + // Eat the Alt and Shift Modifiers and 'send' an enter instead + e->accept(); + + QKeyEvent newEvent( + e->type(), + e->key(), + Qt::NoModifier, + e->text(), + e->isAutoRepeat(), + e->count() + ); + QTextEdit::keyPressEvent(&newEvent); + } + else if (e->modifiers() == Qt::NoModifier) + { + e->ignore(); + } + else + { + QTextEdit::keyPressEvent(e); + } + } + else + { + QTextEdit::keyPressEvent(e); + } +} + +void TextEdit::sltTextChanged() +{ + CalcSize(); +} + +void TextEdit::CalcSize() +{ + QSize sz = this->size(); + QString text = this->toPlainText(); + int lines = text.count(QLatin1Char('\n'))+1; + if (lines > 5) lines = 5; + int ht = fontMetrics().lineSpacing() * lines + 10; // 10 extra lines because single lines seem to need a bit more than lineSpacing() + if ( lines == 1 ) + { + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + sz.setHeight( ht ); + resize( sz ); + } + else if ( ht > sz.height() ) + { + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + sz.setHeight( ht ); + resize( sz ); + } +} diff --git a/widgets/valueedit.h b/widgets/valueedit.h index 29f3791f4..a27f806b8 100644 --- a/widgets/valueedit.h +++ b/widgets/valueedit.h @@ -1,211 +1,211 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2008, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. -3. The name of the NIF File Format Library and Tools projectmay not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - -***** END LICENCE BLOCK *****/ - -#ifndef VALUEEDIT_H -#define VALUEEDIT_H - -#include "nifvalue.h" - -#include -#include - -class QDoubleSpinBox; -class QLabel; -class QSpinBox; - -class ValueEdit : public QWidget -{ - Q_OBJECT -public: - ValueEdit( QWidget * parent = 0 ); - - Q_PROPERTY( NifValue value READ getValue WRITE setValue USER true ); - - NifValue getValue() const; - - static bool canEdit( NifValue::Type t ); - -public slots: - void setValue( const NifValue & v ); - void childResized( QResizeEvent * e ); - -protected: - void resizeEditor(); - void resizeEvent( QResizeEvent * e ); - -private: - NifValue::Type typ; - - QWidget * edit; -}; - -class VectorEdit : public QWidget -{ - Q_OBJECT -public: - VectorEdit( QWidget * parent = 0 ); - - Q_PROPERTY( Vector4 vector4 READ getVector4 WRITE setVector4 STORED false ); - Q_PROPERTY( Vector3 vector3 READ getVector3 WRITE setVector3 STORED false ); - Q_PROPERTY( Vector2 vector2 READ getVector2 WRITE setVector2 STORED false ); - - Vector4 getVector4() const; - Vector3 getVector3() const; - Vector2 getVector2() const; - -signals: - void sigEdited(); - -public slots: - void setVector4( const Vector4 & ); - void setVector3( const Vector3 & ); - void setVector2( const Vector2 & ); - -protected slots: - void sltChanged(); - -private: - QDoubleSpinBox * x; - QDoubleSpinBox * y; - QDoubleSpinBox * z; - QDoubleSpinBox * w; - - QLabel * wl, * zl; - - bool setting; -}; - -class ColorEdit : public QWidget -{ - Q_OBJECT -public: - ColorEdit( QWidget * parent = 0 ); - - Q_PROPERTY( Color4 color4 READ getColor4 WRITE setColor4 STORED false ); - Q_PROPERTY( Color3 color3 READ getColor3 WRITE setColor3 STORED false ); - - Color4 getColor4() const; - Color3 getColor3() const; - -signals: - void sigEdited(); - -public slots: - void setColor4( const Color4 & ); - void setColor3( const Color3 & ); - -protected slots: - void sltChanged(); - -private: - QDoubleSpinBox * r, * g, * b, * a; - QLabel * al; - bool setting; -}; - -class RotationEdit : public QWidget -{ - Q_OBJECT -public: - RotationEdit( QWidget * parent = 0 ); - - Q_PROPERTY( Matrix matrix READ getMatrix WRITE setMatrix STORED false ); - Q_PROPERTY( Quat quat READ getQuat WRITE setQuat STORED false ); - - Matrix getMatrix() const; - Quat getQuat() const; - -signals: - void sigEdited(); - -public slots: - void setMatrix( const Matrix & ); - void setQuat( const Quat & ); - -protected slots: - void sltChanged(); - - void switchMode(); - void setupMode(); - -private: - enum { - mAuto, mEuler, mAxis - } mode; - - QLabel * l[4]; - QDoubleSpinBox * v[4]; - - QAction * actMode; - - bool setting; -}; - -class TriangleEdit : public QWidget -{ - Q_OBJECT -public: - TriangleEdit( QWidget * parent = 0 ); - - Q_PROPERTY( Triangle triangle READ getTriangle WRITE setTriangle STORED false ); - - Triangle getTriangle() const; - -public slots: - void setTriangle( const Triangle & ); - -private: - QSpinBox * v1; - QSpinBox * v2; - QSpinBox * v3; -}; - - -class TextEdit : public QTextEdit -{ - Q_OBJECT -public: - TextEdit(const QString & str, QWidget *parent = 0); - void CalcSize(); - -signals: - void sigResized( QResizeEvent * e ); - -public slots: - void sltTextChanged(); - -protected: - void resizeEvent( QResizeEvent * e ); - void keyPressEvent(QKeyEvent *e); -}; - -#endif +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2008, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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. +3. The name of the NIF File Format Library and Tools projectmay not be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +***** END LICENCE BLOCK *****/ + +#ifndef VALUEEDIT_H +#define VALUEEDIT_H + +#include "nifvalue.h" + +#include +#include + +class QDoubleSpinBox; +class QLabel; +class QSpinBox; + +class ValueEdit : public QWidget +{ + Q_OBJECT +public: + ValueEdit( QWidget * parent = 0 ); + + Q_PROPERTY( NifValue value READ getValue WRITE setValue USER true ); + + NifValue getValue() const; + + static bool canEdit( NifValue::Type t ); + +public slots: + void setValue( const NifValue & v ); + void childResized( QResizeEvent * e ); + +protected: + void resizeEditor(); + void resizeEvent( QResizeEvent * e ); + +private: + NifValue::Type typ; + + QWidget * edit; +}; + +class VectorEdit : public QWidget +{ + Q_OBJECT +public: + VectorEdit( QWidget * parent = 0 ); + + Q_PROPERTY( Vector4 vector4 READ getVector4 WRITE setVector4 STORED false ); + Q_PROPERTY( Vector3 vector3 READ getVector3 WRITE setVector3 STORED false ); + Q_PROPERTY( Vector2 vector2 READ getVector2 WRITE setVector2 STORED false ); + + Vector4 getVector4() const; + Vector3 getVector3() const; + Vector2 getVector2() const; + +signals: + void sigEdited(); + +public slots: + void setVector4( const Vector4 & ); + void setVector3( const Vector3 & ); + void setVector2( const Vector2 & ); + +protected slots: + void sltChanged(); + +private: + QDoubleSpinBox * x; + QDoubleSpinBox * y; + QDoubleSpinBox * z; + QDoubleSpinBox * w; + + QLabel * wl, * zl; + + bool setting; +}; + +class ColorEdit : public QWidget +{ + Q_OBJECT +public: + ColorEdit( QWidget * parent = 0 ); + + Q_PROPERTY( Color4 color4 READ getColor4 WRITE setColor4 STORED false ); + Q_PROPERTY( Color3 color3 READ getColor3 WRITE setColor3 STORED false ); + + Color4 getColor4() const; + Color3 getColor3() const; + +signals: + void sigEdited(); + +public slots: + void setColor4( const Color4 & ); + void setColor3( const Color3 & ); + +protected slots: + void sltChanged(); + +private: + QDoubleSpinBox * r, * g, * b, * a; + QLabel * al; + bool setting; +}; + +class RotationEdit : public QWidget +{ + Q_OBJECT +public: + RotationEdit( QWidget * parent = 0 ); + + Q_PROPERTY( Matrix matrix READ getMatrix WRITE setMatrix STORED false ); + Q_PROPERTY( Quat quat READ getQuat WRITE setQuat STORED false ); + + Matrix getMatrix() const; + Quat getQuat() const; + +signals: + void sigEdited(); + +public slots: + void setMatrix( const Matrix & ); + void setQuat( const Quat & ); + +protected slots: + void sltChanged(); + + void switchMode(); + void setupMode(); + +private: + enum { + mAuto, mEuler, mAxis + } mode; + + QLabel * l[4]; + QDoubleSpinBox * v[4]; + + QAction * actMode; + + bool setting; +}; + +class TriangleEdit : public QWidget +{ + Q_OBJECT +public: + TriangleEdit( QWidget * parent = 0 ); + + Q_PROPERTY( Triangle triangle READ getTriangle WRITE setTriangle STORED false ); + + Triangle getTriangle() const; + +public slots: + void setTriangle( const Triangle & ); + +private: + QSpinBox * v1; + QSpinBox * v2; + QSpinBox * v3; +}; + + +class TextEdit : public QTextEdit +{ + Q_OBJECT +public: + TextEdit(const QString & str, QWidget *parent = 0); + void CalcSize(); + +signals: + void sigResized( QResizeEvent * e ); + +public slots: + void sltTextChanged(); + +protected: + void resizeEvent( QResizeEvent * e ); + void keyPressEvent(QKeyEvent *e); +}; + +#endif diff --git a/widgets/xmlcheck.cpp b/widgets/xmlcheck.cpp index 46f291f42..7a5e1974e 100644 --- a/widgets/xmlcheck.cpp +++ b/widgets/xmlcheck.cpp @@ -1,486 +1,486 @@ -#include "xmlcheck.h" - -#include "../kfmmodel.h" -#include "../nifmodel.h" -#include "../config.h" - -#include "fileselect.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define NUM_THREADS 2 - - -TestShredder * TestShredder::create() -{ - TestShredder * shredder = new TestShredder(); - shredder->setAttribute( Qt::WA_DeleteOnClose ); - shredder->show(); - return shredder; -} - - -TestShredder::TestShredder() - : QWidget() -{ - NIFSKOPE_QSETTINGS(settings); - settings.beginGroup( "XML Checker" ); - - directory = new FileSelector( FileSelector::Folder, "Dir", QBoxLayout::RightToLeft ); - directory->setText( settings.value( "Directory" ).toString() ); - - recursive = new QCheckBox( "Recursive", this ); - recursive->setChecked( settings.value( "Recursive", true ).toBool() ); - recursive->setToolTip( "Recurse into sub directories" ); - - chkNif = new QCheckBox( "*.nif", this ); - chkNif->setChecked( settings.value( "check nif", true ).toBool() ); - chkNif->setToolTip( "Check .nif files" ); - - chkKf = new QCheckBox( "*.kf(a)", this ); - chkKf->setChecked( settings.value( "check kf", true ).toBool() ); - chkKf->setToolTip( "Check .kf files" ); - - chkKfm = new QCheckBox( "*.kfm", this ); - chkKfm->setChecked( settings.value( "check kfm", true ).toBool() ); - chkKfm->setToolTip( "Check .kfm files" ); - - QAction * aChoose = new QAction( "Block Match", this ); - connect( aChoose, SIGNAL( triggered() ), this, SLOT( chooseBlock() ) ); - QToolButton * btChoose = new QToolButton( this ); - btChoose->setDefaultAction( aChoose ); - - blockMatch = new QLineEdit( this ); - - repErr = new QCheckBox( "report errors only", this ); - repErr->setChecked( settings.value( "report errors only", true ).toBool() ); - - count = new QSpinBox(); - count->setRange( 1, 8 ); - count->setValue( settings.value( "Threads", NUM_THREADS ).toInt() ); - count->setPrefix( "threads " ); - connect( count, SIGNAL( valueChanged( int ) ), this, SLOT( renumberThreads( int ) ) ); - - //Version Check - verMatch = new QLineEdit(this); - - text = new QTextBrowser(); - text->setHidden( false ); - text->setReadOnly( true ); - text->setOpenExternalLinks( true ); - - progress = new QProgressBar( this ); - - label = new QLabel( this ); - label->setHidden( true ); - - btRun = new QPushButton( "run", this ); - btRun->setCheckable( true ); - connect( btRun, SIGNAL( clicked() ), this, SLOT( run() ) ); - - QPushButton * btXML = new QPushButton( "Reload XML", this ); - connect( btXML, SIGNAL( clicked() ), this, SLOT( xml() ) ); - - QPushButton * btClose = new QPushButton( "Close", this ); - connect( btClose, SIGNAL( clicked() ), this, SLOT( close() ) ); - - QVBoxLayout * lay = new QVBoxLayout(); - setLayout( lay ); - - QHBoxLayout * hbox = new QHBoxLayout(); - lay->addLayout( hbox ); - hbox->addWidget( directory ); - hbox->addWidget( recursive ); - hbox->addWidget( chkNif ); - hbox->addWidget( chkKf ); - hbox->addWidget( chkKfm ); - - lay->addLayout( hbox = new QHBoxLayout() ); - hbox->addWidget( btChoose ); - hbox->addWidget( blockMatch ); - hbox->addWidget( repErr ); - hbox->addWidget( count ); - - lay->addLayout( hbox = new QHBoxLayout() ); - hbox->addWidget( new QLabel( "Version Match:" ) ); - hbox->addWidget( verMatch ); - - lay->addWidget( text ); - - lay->addLayout( hbox = new QHBoxLayout() ); - hbox->addWidget( progress ); - hbox->addWidget( label ); - - lay->addLayout( hbox = new QHBoxLayout() ); - hbox->addWidget( btRun ); - hbox->addWidget( btXML ); - hbox->addWidget( btClose ); - - renumberThreads( count->value() ); -} - -TestShredder::~TestShredder() -{ - NIFSKOPE_QSETTINGS(settings); - settings.beginGroup( "XML Checker" ); - - settings.setValue( "Directory", directory->text() ); - settings.setValue( "Recursive", recursive->isChecked() ); - settings.setValue( "check nif", chkNif->isChecked() ); - settings.setValue( "check kf", chkKf->isChecked() ); - settings.setValue( "check kfm", chkKfm->isChecked() ); - settings.setValue( "report errors only", repErr->isChecked() ); - settings.setValue( "Threads", count->value() ); - - queue.clear(); -} - -void TestShredder::xml() -{ - //btRun->setChecked( false ); - //queue.clear(); - //foreach ( TestThread * thread, threads ) - // thread->wait(); - NifModel::loadXML(); - KfmModel::loadXML(); -} - -void TestShredder::renumberThreads( int num ) -{ - while ( threads.count() < num ) - { - TestThread * thread = new TestThread( this, &queue ); - connect( thread, SIGNAL( sigStart( const QString & ) ), this, SLOT( threadStarted() ) ); - connect( thread, SIGNAL( sigReady( const QString & ) ), text, SLOT( append( const QString & ) ) ); - connect( thread, SIGNAL( finished() ), this, SLOT( threadFinished() ) ); - threads.append( thread ); - - thread->blockMatch = blockMatch->text(); - thread->verMatch = NifModel::version2number( verMatch->text() ); - thread->reportAll = ! repErr->isChecked(); - - if ( btRun->isChecked() ) - { - thread->start(); - } - } - while ( threads.count() > num ) - { - TestThread * thread = threads.takeLast(); - delete thread; - } -} - -void TestShredder::run() -{ - progress->setMaximum( progress->maximum() - queue.count() ); - queue.clear(); - - if ( ! btRun->isChecked() ) - return; - - foreach ( TestThread * thread, threads ) - thread->wait(); - - text->clear(); - label->setHidden( true ); - - QStringList extensions; - if ( chkNif->isChecked() ) - extensions << "*.nif" << "*.nifcache" << "*.texcache"; - if ( chkKf->isChecked() ) - extensions << "*.kf" << "*.kfa"; - if ( chkKfm->isChecked() ) - extensions << "*.kfm"; - - queue.init( directory->text(), extensions, recursive->isChecked() ); - - time = QDateTime::currentDateTime(); - - progress->setRange( 0, queue.count() ); - progress->setValue( 0 ); - - foreach ( TestThread * thread, threads ) - { - thread->verMatch = NifModel::version2number( verMatch->text() ); - thread->blockMatch = blockMatch->text(); - thread->reportAll = ! repErr->isChecked(); - thread->start(); - } -} - -void TestShredder::threadStarted() -{ - progress->setValue( progress->maximum() - queue.count() ); -} - -void TestShredder::threadFinished() -{ - if ( queue.isEmpty() ) - { - foreach ( TestThread * thread, threads ) - if ( thread->isRunning() ) - return; - - btRun->setChecked( false ); - - label->setText( QString( "%1 files in %2 seconds" ).arg( progress->maximum() ).arg( time.secsTo( QDateTime::currentDateTime() ) ) ); - label->setVisible( true ); - } -} - -void TestShredder::chooseBlock() -{ - QStringList ids = NifModel::allNiBlocks(); - ids.sort(); - - QMap< QString, QMenu *> map; - foreach ( QString id, ids ) - { - QString x( "Other" ); - - if ( id.startsWith( "Ni" ) ) - x = QString("Ni&") + id.mid( 2, 1 ) + "..."; - if ( id.startsWith( "bhk" ) || id.startsWith( "hk" ) ) - x = "Havok"; - if ( id.startsWith( "BS" ) || id == "AvoidNode" || id == "RootCollisionNode" ) - x = "Bethesda"; - if ( id.startsWith( "Fx" ) ) - x = "Firaxis"; - - if ( ! map.contains( x ) ) - map[ x ] = new QMenu( x ); - map[ x ]->addAction( id ); - } - - QMenu menu; - foreach ( QMenu * m, map ) - menu.addMenu( m ); - - QAction * act = menu.exec( QCursor::pos() ); - if ( act ) - blockMatch->setText( act->text() ); -} - -void TestShredder::closeEvent( QCloseEvent * e ) -{ - foreach ( TestThread * thread, threads ) - if ( thread->isRunning() ) - { - e->ignore(); - queue.clear(); - } -} - -/* - * File Queue - */ - -QQueue FileQueue::make( const QString & dname, const QStringList & extensions, bool recursive ) -{ - QQueue queue; - - QDir dir( dname ); - if ( recursive ) - { - dir.setFilter( QDir::Dirs ); - foreach ( QString d, dir.entryList() ) - if ( d != "." && d != ".." ) - queue += make( dir.filePath( d ), extensions, true ); - } - - dir.setFilter( QDir::Files ); - dir.setNameFilters( extensions ); - foreach ( QString f, dir.entryList() ) - queue.enqueue( dir.filePath( f ) ); - - return queue; -} - -void FileQueue::init( const QString & dname, const QStringList & extensions, bool recursive ) -{ - QQueue queue = make( dname, extensions, recursive ); - - mutex.lock(); - this->queue = queue; - mutex.unlock(); -} - -QString FileQueue::dequeue() -{ - QMutexLocker lock( & mutex ); - if ( queue.isEmpty() ) - return QString(); - else - return queue.dequeue(); -} - -int FileQueue::count() -{ - QMutexLocker lock( & mutex ); - return queue.count(); -} - -void FileQueue::clear() -{ - QMutexLocker lock( & mutex ); - queue.clear(); -} - -/* - * Thread - */ - -TestThread::TestThread( QObject * o, FileQueue * q ) - : QThread( o ), queue( q ) -{ - reportAll = true; -} - -TestThread::~TestThread() -{ - if ( isRunning() ) - { - quit.lock(); - wait(); - quit.unlock(); - } -} - -void TestThread::run() -{ - NifModel nif; - nif.setMessageMode( BaseModel::CollectMessages ); - KfmModel kfm; - kfm.setMessageMode( BaseModel::CollectMessages ); - - QString filepath = queue->dequeue(); - while ( ! filepath.isEmpty() ) - { - emit sigStart( filepath ); - - BaseModel * model = & nif; - QReadWriteLock * lock = &nif.XMLlock; - - if ( filepath.endsWith( ".KFM", Qt::CaseInsensitive ) ) - { - model = & kfm; - lock = & kfm.XMLlock; - } - - bool kf = ( filepath.endsWith( ".KF", Qt::CaseInsensitive ) || filepath.endsWith( ".KFA", Qt::CaseInsensitive ) ); - - { // lock the XML lock - QReadLocker lck( lock ); - - if ( model == &nif && NifModel::earlyRejection( filepath, blockMatch, verMatch ) ) - { - bool loaded = model->loadFromFile( filepath ); - - QString result = QString( "%1 (%2)" ).arg( filepath ).arg( model->getVersion() ); - QList messages = model->getMessages(); - - bool blk_match = false; - if ( loaded && model == & nif ) - for ( int b = 0; b < nif.getBlockCount(); b++ ) - { - //In case early rejection failed, such as if this is an older file without the block types in the header - //note if any of these blocks types match the specified one. - if ( blockMatch.isEmpty() == false && NifModel::inherits( nif.getBlockName( nif.getBlock(b) ), blockMatch ) ) { - blk_match = true; - } - messages += checkLinks( &nif, nif.getBlock( b ), kf ); - } - - bool rep = reportAll; - - //Don't show anything if block match is on but the requested type wasn't found & we're in block match mode - - if ( blockMatch.isEmpty() == true || blk_match == true ) - { - foreach ( Message msg, messages ) - { - if ( msg.type() != QtDebugMsg ) - { - result += "
" + msg; - rep |= true; - } - } - if ( rep ) - emit sigReady( result ); - } - } - } - - if ( quit.tryLock() ) - quit.unlock(); - else - break; - - filepath = queue->dequeue(); - } -} - -static QString linkId( const NifModel * nif, QModelIndex idx ) -{ - QString id = QString( "%1 (%2)" ).arg( nif->itemName( idx ) ).arg( nif->itemTmplt( idx ) ); - while ( idx.parent().isValid() ) - { - idx = idx.parent(); - id.prepend( QString( "%1/" ).arg( nif->itemName( idx ) ) ); - } - return id; -} - -QList TestThread::checkLinks( const NifModel * nif, const QModelIndex & iParent, bool kf ) -{ - QList messages; - for ( int r = 0; r < nif->rowCount( iParent ); r++ ) - { - QModelIndex idx = iParent.child( r, 0 ); - bool child; - if ( nif->isLink( idx, &child ) ) - { - qint32 l = nif->getLink( idx ); - if ( l < 0 ) - { - //This is not really an error - //if ( ! child && ! kf ) - // messages.append( Message() << "unassigned parent link" << linkId( nif, idx ) ); - } - else if ( l >= nif->getBlockCount() ) - messages.append( Message() << "invalid link" << linkId( nif, idx ) ); - else - { - QString tmplt = nif->itemTmplt( idx ); - if ( ! tmplt.isEmpty() ) - { - QModelIndex iBlock = nif->getBlock( l ); - if ( ! nif->inherits( iBlock, tmplt ) ) - messages.append( Message() << "link" << linkId( nif, idx ) << "points to wrong block type" << nif->itemName( iBlock ) ); - } - } - } - if ( nif->rowCount( idx ) > 0 ) - messages += checkLinks( nif, idx, kf ); - } - return messages; -} +#include "xmlcheck.h" + +#include "../kfmmodel.h" +#include "../nifmodel.h" +#include "../config.h" + +#include "fileselect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NUM_THREADS 2 + + +TestShredder * TestShredder::create() +{ + TestShredder * shredder = new TestShredder(); + shredder->setAttribute( Qt::WA_DeleteOnClose ); + shredder->show(); + return shredder; +} + + +TestShredder::TestShredder() + : QWidget() +{ + NIFSKOPE_QSETTINGS(settings); + settings.beginGroup( "XML Checker" ); + + directory = new FileSelector( FileSelector::Folder, "Dir", QBoxLayout::RightToLeft ); + directory->setText( settings.value( "Directory" ).toString() ); + + recursive = new QCheckBox( "Recursive", this ); + recursive->setChecked( settings.value( "Recursive", true ).toBool() ); + recursive->setToolTip( "Recurse into sub directories" ); + + chkNif = new QCheckBox( "*.nif", this ); + chkNif->setChecked( settings.value( "check nif", true ).toBool() ); + chkNif->setToolTip( "Check .nif files" ); + + chkKf = new QCheckBox( "*.kf(a)", this ); + chkKf->setChecked( settings.value( "check kf", true ).toBool() ); + chkKf->setToolTip( "Check .kf files" ); + + chkKfm = new QCheckBox( "*.kfm", this ); + chkKfm->setChecked( settings.value( "check kfm", true ).toBool() ); + chkKfm->setToolTip( "Check .kfm files" ); + + QAction * aChoose = new QAction( "Block Match", this ); + connect( aChoose, SIGNAL( triggered() ), this, SLOT( chooseBlock() ) ); + QToolButton * btChoose = new QToolButton( this ); + btChoose->setDefaultAction( aChoose ); + + blockMatch = new QLineEdit( this ); + + repErr = new QCheckBox( "report errors only", this ); + repErr->setChecked( settings.value( "report errors only", true ).toBool() ); + + count = new QSpinBox(); + count->setRange( 1, 8 ); + count->setValue( settings.value( "Threads", NUM_THREADS ).toInt() ); + count->setPrefix( "threads " ); + connect( count, SIGNAL( valueChanged( int ) ), this, SLOT( renumberThreads( int ) ) ); + + //Version Check + verMatch = new QLineEdit(this); + + text = new QTextBrowser(); + text->setHidden( false ); + text->setReadOnly( true ); + text->setOpenExternalLinks( true ); + + progress = new QProgressBar( this ); + + label = new QLabel( this ); + label->setHidden( true ); + + btRun = new QPushButton( "run", this ); + btRun->setCheckable( true ); + connect( btRun, SIGNAL( clicked() ), this, SLOT( run() ) ); + + QPushButton * btXML = new QPushButton( "Reload XML", this ); + connect( btXML, SIGNAL( clicked() ), this, SLOT( xml() ) ); + + QPushButton * btClose = new QPushButton( "Close", this ); + connect( btClose, SIGNAL( clicked() ), this, SLOT( close() ) ); + + QVBoxLayout * lay = new QVBoxLayout(); + setLayout( lay ); + + QHBoxLayout * hbox = new QHBoxLayout(); + lay->addLayout( hbox ); + hbox->addWidget( directory ); + hbox->addWidget( recursive ); + hbox->addWidget( chkNif ); + hbox->addWidget( chkKf ); + hbox->addWidget( chkKfm ); + + lay->addLayout( hbox = new QHBoxLayout() ); + hbox->addWidget( btChoose ); + hbox->addWidget( blockMatch ); + hbox->addWidget( repErr ); + hbox->addWidget( count ); + + lay->addLayout( hbox = new QHBoxLayout() ); + hbox->addWidget( new QLabel( "Version Match:" ) ); + hbox->addWidget( verMatch ); + + lay->addWidget( text ); + + lay->addLayout( hbox = new QHBoxLayout() ); + hbox->addWidget( progress ); + hbox->addWidget( label ); + + lay->addLayout( hbox = new QHBoxLayout() ); + hbox->addWidget( btRun ); + hbox->addWidget( btXML ); + hbox->addWidget( btClose ); + + renumberThreads( count->value() ); +} + +TestShredder::~TestShredder() +{ + NIFSKOPE_QSETTINGS(settings); + settings.beginGroup( "XML Checker" ); + + settings.setValue( "Directory", directory->text() ); + settings.setValue( "Recursive", recursive->isChecked() ); + settings.setValue( "check nif", chkNif->isChecked() ); + settings.setValue( "check kf", chkKf->isChecked() ); + settings.setValue( "check kfm", chkKfm->isChecked() ); + settings.setValue( "report errors only", repErr->isChecked() ); + settings.setValue( "Threads", count->value() ); + + queue.clear(); +} + +void TestShredder::xml() +{ + //btRun->setChecked( false ); + //queue.clear(); + //foreach ( TestThread * thread, threads ) + // thread->wait(); + NifModel::loadXML(); + KfmModel::loadXML(); +} + +void TestShredder::renumberThreads( int num ) +{ + while ( threads.count() < num ) + { + TestThread * thread = new TestThread( this, &queue ); + connect( thread, SIGNAL( sigStart( const QString & ) ), this, SLOT( threadStarted() ) ); + connect( thread, SIGNAL( sigReady( const QString & ) ), text, SLOT( append( const QString & ) ) ); + connect( thread, SIGNAL( finished() ), this, SLOT( threadFinished() ) ); + threads.append( thread ); + + thread->blockMatch = blockMatch->text(); + thread->verMatch = NifModel::version2number( verMatch->text() ); + thread->reportAll = ! repErr->isChecked(); + + if ( btRun->isChecked() ) + { + thread->start(); + } + } + while ( threads.count() > num ) + { + TestThread * thread = threads.takeLast(); + delete thread; + } +} + +void TestShredder::run() +{ + progress->setMaximum( progress->maximum() - queue.count() ); + queue.clear(); + + if ( ! btRun->isChecked() ) + return; + + foreach ( TestThread * thread, threads ) + thread->wait(); + + text->clear(); + label->setHidden( true ); + + QStringList extensions; + if ( chkNif->isChecked() ) + extensions << "*.nif" << "*.nifcache" << "*.texcache"; + if ( chkKf->isChecked() ) + extensions << "*.kf" << "*.kfa"; + if ( chkKfm->isChecked() ) + extensions << "*.kfm"; + + queue.init( directory->text(), extensions, recursive->isChecked() ); + + time = QDateTime::currentDateTime(); + + progress->setRange( 0, queue.count() ); + progress->setValue( 0 ); + + foreach ( TestThread * thread, threads ) + { + thread->verMatch = NifModel::version2number( verMatch->text() ); + thread->blockMatch = blockMatch->text(); + thread->reportAll = ! repErr->isChecked(); + thread->start(); + } +} + +void TestShredder::threadStarted() +{ + progress->setValue( progress->maximum() - queue.count() ); +} + +void TestShredder::threadFinished() +{ + if ( queue.isEmpty() ) + { + foreach ( TestThread * thread, threads ) + if ( thread->isRunning() ) + return; + + btRun->setChecked( false ); + + label->setText( QString( "%1 files in %2 seconds" ).arg( progress->maximum() ).arg( time.secsTo( QDateTime::currentDateTime() ) ) ); + label->setVisible( true ); + } +} + +void TestShredder::chooseBlock() +{ + QStringList ids = NifModel::allNiBlocks(); + ids.sort(); + + QMap< QString, QMenu *> map; + foreach ( QString id, ids ) + { + QString x( "Other" ); + + if ( id.startsWith( "Ni" ) ) + x = QString("Ni&") + id.mid( 2, 1 ) + "..."; + if ( id.startsWith( "bhk" ) || id.startsWith( "hk" ) ) + x = "Havok"; + if ( id.startsWith( "BS" ) || id == "AvoidNode" || id == "RootCollisionNode" ) + x = "Bethesda"; + if ( id.startsWith( "Fx" ) ) + x = "Firaxis"; + + if ( ! map.contains( x ) ) + map[ x ] = new QMenu( x ); + map[ x ]->addAction( id ); + } + + QMenu menu; + foreach ( QMenu * m, map ) + menu.addMenu( m ); + + QAction * act = menu.exec( QCursor::pos() ); + if ( act ) + blockMatch->setText( act->text() ); +} + +void TestShredder::closeEvent( QCloseEvent * e ) +{ + foreach ( TestThread * thread, threads ) + if ( thread->isRunning() ) + { + e->ignore(); + queue.clear(); + } +} + +/* + * File Queue + */ + +QQueue FileQueue::make( const QString & dname, const QStringList & extensions, bool recursive ) +{ + QQueue queue; + + QDir dir( dname ); + if ( recursive ) + { + dir.setFilter( QDir::Dirs ); + foreach ( QString d, dir.entryList() ) + if ( d != "." && d != ".." ) + queue += make( dir.filePath( d ), extensions, true ); + } + + dir.setFilter( QDir::Files ); + dir.setNameFilters( extensions ); + foreach ( QString f, dir.entryList() ) + queue.enqueue( dir.filePath( f ) ); + + return queue; +} + +void FileQueue::init( const QString & dname, const QStringList & extensions, bool recursive ) +{ + QQueue queue = make( dname, extensions, recursive ); + + mutex.lock(); + this->queue = queue; + mutex.unlock(); +} + +QString FileQueue::dequeue() +{ + QMutexLocker lock( & mutex ); + if ( queue.isEmpty() ) + return QString(); + else + return queue.dequeue(); +} + +int FileQueue::count() +{ + QMutexLocker lock( & mutex ); + return queue.count(); +} + +void FileQueue::clear() +{ + QMutexLocker lock( & mutex ); + queue.clear(); +} + +/* + * Thread + */ + +TestThread::TestThread( QObject * o, FileQueue * q ) + : QThread( o ), queue( q ) +{ + reportAll = true; +} + +TestThread::~TestThread() +{ + if ( isRunning() ) + { + quit.lock(); + wait(); + quit.unlock(); + } +} + +void TestThread::run() +{ + NifModel nif; + nif.setMessageMode( BaseModel::CollectMessages ); + KfmModel kfm; + kfm.setMessageMode( BaseModel::CollectMessages ); + + QString filepath = queue->dequeue(); + while ( ! filepath.isEmpty() ) + { + emit sigStart( filepath ); + + BaseModel * model = & nif; + QReadWriteLock * lock = &nif.XMLlock; + + if ( filepath.endsWith( ".KFM", Qt::CaseInsensitive ) ) + { + model = & kfm; + lock = & kfm.XMLlock; + } + + bool kf = ( filepath.endsWith( ".KF", Qt::CaseInsensitive ) || filepath.endsWith( ".KFA", Qt::CaseInsensitive ) ); + + { // lock the XML lock + QReadLocker lck( lock ); + + if ( model == &nif && NifModel::earlyRejection( filepath, blockMatch, verMatch ) ) + { + bool loaded = model->loadFromFile( filepath ); + + QString result = QString( "%1 (%2)" ).arg( filepath ).arg( model->getVersion() ); + QList messages = model->getMessages(); + + bool blk_match = false; + if ( loaded && model == & nif ) + for ( int b = 0; b < nif.getBlockCount(); b++ ) + { + //In case early rejection failed, such as if this is an older file without the block types in the header + //note if any of these blocks types match the specified one. + if ( blockMatch.isEmpty() == false && NifModel::inherits( nif.getBlockName( nif.getBlock(b) ), blockMatch ) ) { + blk_match = true; + } + messages += checkLinks( &nif, nif.getBlock( b ), kf ); + } + + bool rep = reportAll; + + //Don't show anything if block match is on but the requested type wasn't found & we're in block match mode + + if ( blockMatch.isEmpty() == true || blk_match == true ) + { + foreach ( Message msg, messages ) + { + if ( msg.type() != QtDebugMsg ) + { + result += "
" + msg; + rep |= true; + } + } + if ( rep ) + emit sigReady( result ); + } + } + } + + if ( quit.tryLock() ) + quit.unlock(); + else + break; + + filepath = queue->dequeue(); + } +} + +static QString linkId( const NifModel * nif, QModelIndex idx ) +{ + QString id = QString( "%1 (%2)" ).arg( nif->itemName( idx ) ).arg( nif->itemTmplt( idx ) ); + while ( idx.parent().isValid() ) + { + idx = idx.parent(); + id.prepend( QString( "%1/" ).arg( nif->itemName( idx ) ) ); + } + return id; +} + +QList TestThread::checkLinks( const NifModel * nif, const QModelIndex & iParent, bool kf ) +{ + QList messages; + for ( int r = 0; r < nif->rowCount( iParent ); r++ ) + { + QModelIndex idx = iParent.child( r, 0 ); + bool child; + if ( nif->isLink( idx, &child ) ) + { + qint32 l = nif->getLink( idx ); + if ( l < 0 ) + { + //This is not really an error + //if ( ! child && ! kf ) + // messages.append( Message() << "unassigned parent link" << linkId( nif, idx ) ); + } + else if ( l >= nif->getBlockCount() ) + messages.append( Message() << "invalid link" << linkId( nif, idx ) ); + else + { + QString tmplt = nif->itemTmplt( idx ); + if ( ! tmplt.isEmpty() ) + { + QModelIndex iBlock = nif->getBlock( l ); + if ( ! nif->inherits( iBlock, tmplt ) ) + messages.append( Message() << "link" << linkId( nif, idx ) << "points to wrong block type" << nif->itemName( iBlock ) ); + } + } + } + if ( nif->rowCount( idx ) > 0 ) + messages += checkLinks( nif, idx, kf ); + } + return messages; +} diff --git a/widgets/xmlcheck.h b/widgets/xmlcheck.h index a08a1dc89..0703033e0 100644 --- a/widgets/xmlcheck.h +++ b/widgets/xmlcheck.h @@ -1,113 +1,113 @@ -#ifndef SPELL_DEBUG_H -#define SPELL_DEBUG_H - -#include -#include -#include -#include -#include -#include - -#include "../message.h" - -class QCheckBox; -class QGroupBox; -class QHBoxLayout; -class QLabel; -class QLineEdit; -class QProgressBar; -class QPushButton; -class QSpinBox; -class QTextBrowser; -class QVBoxLayout; - -class FileSelector; - -class FileQueue -{ -public: - FileQueue() {} - - QString dequeue(); - - bool isEmpty() { return count() == 0; } - int count(); - - void init( const QString & directory, const QStringList & extensions, bool recursive ); - void clear(); - -protected: - QQueue make( const QString & directory, const QStringList & extensions, bool recursive ); - - QMutex mutex; - QQueue queue; -}; - -class TestThread : public QThread -{ - Q_OBJECT -public: - TestThread( QObject * o, FileQueue * q ); - ~TestThread(); - - QString blockMatch; - quint32 verMatch; - bool reportAll; - -signals: - void sigStart( const QString & file ); - void sigReady( const QString & result ); - -protected: - void run(); - - QList checkLinks( const class NifModel * nif, const class QModelIndex & iParent, bool kf ); - - FileQueue * queue; - - QMutex quit; -}; - -//! The XML checker widget. -class TestShredder : public QWidget -{ - Q_OBJECT -public: - TestShredder(); - ~TestShredder(); - - static TestShredder * create(); - -protected slots: - void chooseBlock(); - void run(); - void xml(); - - void threadStarted(); - void threadFinished(); - - void renumberThreads( int ); - -protected: - void closeEvent( QCloseEvent * ); - - FileSelector * directory; - QLineEdit * blockMatch; - QCheckBox * recursive; - QCheckBox * chkNif, * chkKf, * chkKfm; - QCheckBox * repErr; - QSpinBox * count; - QLineEdit * verMatch; - QTextBrowser * text; - QProgressBar * progress; - QLabel * label; - QPushButton * btRun; - - FileQueue queue; - - QList threads; - - QDateTime time; -}; - -#endif +#ifndef SPELL_DEBUG_H +#define SPELL_DEBUG_H + +#include +#include +#include +#include +#include +#include + +#include "../message.h" + +class QCheckBox; +class QGroupBox; +class QHBoxLayout; +class QLabel; +class QLineEdit; +class QProgressBar; +class QPushButton; +class QSpinBox; +class QTextBrowser; +class QVBoxLayout; + +class FileSelector; + +class FileQueue +{ +public: + FileQueue() {} + + QString dequeue(); + + bool isEmpty() { return count() == 0; } + int count(); + + void init( const QString & directory, const QStringList & extensions, bool recursive ); + void clear(); + +protected: + QQueue make( const QString & directory, const QStringList & extensions, bool recursive ); + + QMutex mutex; + QQueue queue; +}; + +class TestThread : public QThread +{ + Q_OBJECT +public: + TestThread( QObject * o, FileQueue * q ); + ~TestThread(); + + QString blockMatch; + quint32 verMatch; + bool reportAll; + +signals: + void sigStart( const QString & file ); + void sigReady( const QString & result ); + +protected: + void run(); + + QList checkLinks( const class NifModel * nif, const class QModelIndex & iParent, bool kf ); + + FileQueue * queue; + + QMutex quit; +}; + +//! The XML checker widget. +class TestShredder : public QWidget +{ + Q_OBJECT +public: + TestShredder(); + ~TestShredder(); + + static TestShredder * create(); + +protected slots: + void chooseBlock(); + void run(); + void xml(); + + void threadStarted(); + void threadFinished(); + + void renumberThreads( int ); + +protected: + void closeEvent( QCloseEvent * ); + + FileSelector * directory; + QLineEdit * blockMatch; + QCheckBox * recursive; + QCheckBox * chkNif, * chkKf, * chkKfm; + QCheckBox * repErr; + QSpinBox * count; + QLineEdit * verMatch; + QTextBrowser * text; + QProgressBar * progress; + QLabel * label; + QPushButton * btRun; + + FileQueue queue; + + QList threads; + + QDateTime time; +}; + +#endif