From ca9224ed7f8813b2eb9bd4cd70e641308c2d4685 Mon Sep 17 00:00:00 2001 From: gugus Date: Mon, 21 May 2012 10:58:04 +0200 Subject: [PATCH 001/298] starting scale function --- apps/openmw/mwworld/class.cpp | 5 +++++ apps/openmw/mwworld/class.hpp | 2 ++ apps/openmw/mwworld/world.cpp | 5 +++++ apps/openmw/mwworld/world.hpp | 2 ++ 4 files changed, 14 insertions(+) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 5fb503847..f1c50c29a 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -196,4 +196,9 @@ namespace MWWorld { return ""; } + + void adjustScale(float& x, float& y, float& z) + { + } + } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 513dc942b..0fc12bfe2 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -193,6 +193,8 @@ namespace MWWorld virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string /// (default implementation: return empty string) + + virtual void adjustScale(float& x, float& y, float& z); }; } diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index eeda92277..fbbeef154 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -611,6 +611,11 @@ namespace MWWorld mPhysics->moveObject (ptr.getRefData().getHandle(), Ogre::Vector3 (x, y, z)); } + void World::scaleObject (Ptr ptr, float x, float y, float z) + { + MWWorld::Class::get(ptr).adjustScale(x,y,z); + } + void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const { const int cellSize = 8192; diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index 49a3cf029..cb849cc1b 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -222,6 +222,8 @@ namespace MWWorld void moveObject (Ptr ptr, float x, float y, float z); + void scaleObject (Ptr ptr, float x, float y, float z); + void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const; ///< Convert cell numbers to position. From 51b6d5cae055a6e24fc16f706b2246950052e55b Mon Sep 17 00:00:00 2001 From: gugus Date: Fri, 25 May 2012 18:23:06 +0200 Subject: [PATCH 002/298] Scale *should* work. (no script instruction yet) --- apps/openmw/mwworld/class.cpp | 5 ++++- apps/openmw/mwworld/class.hpp | 4 +++- apps/openmw/mwworld/world.cpp | 16 ++++++++++++++-- apps/openmw/mwworld/world.hpp | 4 +++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 6a6765253..4064a451a 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -203,8 +203,11 @@ namespace MWWorld return ""; } - void adjustScale(float& x, float& y, float& z) + void Class::adjustScale(const MWWorld::Ptr& ptr,float& scale) const { } + void Class::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const + { + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 0ed0b0e0d..a8639daa9 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -197,7 +197,9 @@ namespace MWWorld ///< @return the enchantment ID if the object is enchanted, otherwise an empty string /// (default implementation: return empty string) - virtual void adjustScale(float& x, float& y, float& z); + virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; + + virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const; }; } diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index c2926c1a3..6be4a13ba 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -611,9 +611,21 @@ namespace MWWorld mPhysics->moveObject (ptr.getRefData().getHandle(), Ogre::Vector3 (x, y, z)); } - void World::scaleObject (Ptr ptr, float x, float y, float z) + void World::scaleObject (Ptr ptr, float scale) + { + MWWorld::Class::get(ptr).adjustScale(ptr,scale); + + ptr.getCellRef().scale = scale; + scale = scale/ptr.getRefData().getBaseNode()->getScale().x; + ptr.getRefData().getBaseNode()->setScale(scale,scale,scale); + mPhysics->scaleObject( Class::get(ptr).getId(ptr), scale ); + /// \todo cell change for non-player ref + + //mRendering->moveObject (ptr, Ogre::Vector3 (x, y, z)); + } + + void World::rotateObject (Ptr ptr,float x,float y,float z) { - MWWorld::Class::get(ptr).adjustScale(x,y,z); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index cc6a665fc..e684ca39e 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -222,7 +222,9 @@ namespace MWWorld void moveObject (Ptr ptr, float x, float y, float z); - void scaleObject (Ptr ptr, float x, float y, float z); + void scaleObject (Ptr ptr, float scale); + + void rotateObject (Ptr ptr,float x,float y,float z); void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const; ///< Convert cell numbers to position. From 77084b27c07b5948717638d6a8c4f8d93bca9259 Mon Sep 17 00:00:00 2001 From: gugus Date: Mon, 28 May 2012 16:01:35 +0200 Subject: [PATCH 003/298] some work for rotation/scaling --- apps/openmw/mwscript/statsextensions.cpp | 16 ++++++++++++++++ apps/openmw/mwworld/world.cpp | 10 +++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index d23519281..6e9b1e583 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -500,6 +500,22 @@ namespace MWScript } }; + template + class OpSetScale : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Float scale = runtime[0].mInteger; + runtime.pop(); + + MWBase::Environment::get().getWorld()->scaleObject(ptr,scale); + } + }; + const int numberOfAttributes = 8; const int opcodeGetAttribute = 0x2000027; diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 6be4a13ba..2a3332593 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -619,13 +619,17 @@ namespace MWWorld scale = scale/ptr.getRefData().getBaseNode()->getScale().x; ptr.getRefData().getBaseNode()->setScale(scale,scale,scale); mPhysics->scaleObject( Class::get(ptr).getId(ptr), scale ); - /// \todo cell change for non-player ref - - //mRendering->moveObject (ptr, Ogre::Vector3 (x, y, z)); } void World::rotateObject (Ptr ptr,float x,float y,float z) { + MWWorld::Class::get(ptr).adjustRotation(ptr,x,y,z); + + ptr.getRefData().getPosition().rot[0] = x; + ptr.getRefData().getPosition().rot[0] = y; + ptr.getRefData().getPosition().rot[0] = z; + //ptr.getRefData().getBaseNode()->rotate(ptr.getRefData().getBaseNode()->get + //mPhysics->scaleObject( Class::get(ptr).getId(ptr), scale ); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const From 26d6c9453c3d1d80936189cce56a7eb38a148946 Mon Sep 17 00:00:00 2001 From: gugus Date: Tue, 29 May 2012 10:15:29 +0200 Subject: [PATCH 004/298] more work on rotation --- apps/openmw/mwworld/world.cpp | 44 ++++++++++++++++++++++++++++++++--- apps/openmw/mwworld/world.hpp | 4 +++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 2a3332593..116710a1e 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -621,17 +621,55 @@ namespace MWWorld mPhysics->scaleObject( Class::get(ptr).getId(ptr), scale ); } - void World::rotateObject (Ptr ptr,float x,float y,float z) + void World::rotateObject (Ptr ptr,float x,float y,float z,bool WorldAxis) { MWWorld::Class::get(ptr).adjustRotation(ptr,x,y,z); - ptr.getRefData().getPosition().rot[0] = x; + /*ptr.getRefData().getPosition().rot[0] = x; ptr.getRefData().getPosition().rot[0] = y; - ptr.getRefData().getPosition().rot[0] = z; + ptr.getRefData().getPosition().rot[0] = z;*/ + if(WorldAxis) + { + ptr.getRefData().getBaseNode()->rotate(Ogre::Vector3::UNIT_X,Ogre::Degree(x)); + ptr.getRefData().getBaseNode()->rotate(Ogre::Vector3::UNIT_X,Ogre::Degree(y)); + ptr.getRefData().getBaseNode()->rotate(Ogre::Vector3::UNIT_X,Ogre::Degree(z)); + } + else + { + Ogre::Matrix3 axis = ptr.getRefData().getBaseNode()->getLocalAxes(); + Ogre::Vector3 xAxis = axis.GetColumn(0); + Ogre::Vector3 yAxis = axis.GetColumn(1); + Ogre::Vector3 zAxis = axis.GetColumn(2); + ptr.getRefData().getBaseNode()->rotate(xAxis,Ogre::Degree(x)); + ptr.getRefData().getBaseNode()->rotate(yAxis,Ogre::Degree(y)); + ptr.getRefData().getBaseNode()->rotate(zAxis,Ogre::Degree(z)); + } + Ogre::Matrix3 rot; + ptr.getRefData().getBaseNode()->getOrientation().ToRotationMatrix(rot); + Ogre::Radian rx,ry,rz; + rot.ToEulerAnglesXYZ(rx,ry,rz); + + ptr.getRefData().getPosition().rot[0] = rx.valueRadians(); + ptr.getRefData().getPosition().rot[0] = ry.valueRadians(); + ptr.getRefData().getPosition().rot[0] = rz.valueRadians(); //ptr.getRefData().getBaseNode()->rotate(ptr.getRefData().getBaseNode()->get //mPhysics->scaleObject( Class::get(ptr).getId(ptr), scale ); } + void setObjectRotation (Ptr ptr,float x,float y,float z) + { + MWWorld::Class::get(ptr).adjustRotation(ptr,x,y,z); + + ptr.getRefData().getPosition().rot[0] = Ogre::Degree(x).valueRadians(); + ptr.getRefData().getPosition().rot[0] = Ogre::Degree(y).valueRadians(); + ptr.getRefData().getPosition().rot[0] = Ogre::Degree(z).valueRadians(); + + Ogre::Quaternion rotx(Ogre::Degree(x),Ogre::Vector3::UNIT_X); + Ogre::Quaternion roty(Ogre::Degree(y),Ogre::Vector3::UNIT_Y); + Ogre::Quaternion rotz(Ogre::Degree(z),Ogre::Vector3::UNIT_Z); + ptr.getRefData().getBaseNode()->setOrientation(rotx*roty*rotz); + } + void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const { const int cellSize = 8192; diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index e684ca39e..6f810e976 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -224,7 +224,9 @@ namespace MWWorld void scaleObject (Ptr ptr, float scale); - void rotateObject (Ptr ptr,float x,float y,float z); + void rotateObject (Ptr ptr,float x,float y,float z,bool WorldAxis); + + void setObjectRotation (Ptr ptr,float x,float y,float z); void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const; ///< Convert cell numbers to position. From d8051095d669c552123f4662ea501c9cd021e6bd Mon Sep 17 00:00:00 2001 From: gugus Date: Tue, 29 May 2012 10:36:12 +0200 Subject: [PATCH 005/298] rotation are updated in the physis system --- apps/openmw/mwworld/physicssystem.cpp | 13 +++++++++++-- apps/openmw/mwworld/world.cpp | 8 ++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 808c712a0..c4e34a540 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -288,17 +288,26 @@ namespace MWWorld void PhysicsSystem::rotateObject (const std::string& handle, const Ogre::Quaternion& rotation) { - if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) + if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) { // TODO very dirty hack to avoid crash during setup -> needs cleaning up to allow // start positions others than 0, 0, 0 act->setRotation(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w)); } + if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle)) + { + body->setWorldTransform(btTransform(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w),body->getWorldTransform().getOrigin())); + } } void PhysicsSystem::scaleObject (const std::string& handle, float scale) { - + if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle)) + { + // TODO very dirty hack to avoid crash during setup -> needs cleaning up to allow + // start positions others than 0, 0, 0 + //body->setWorldTransform(btTransform().se + } } bool PhysicsSystem::toggleCollisionMode() diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 116710a1e..69da98096 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -625,9 +625,6 @@ namespace MWWorld { MWWorld::Class::get(ptr).adjustRotation(ptr,x,y,z); - /*ptr.getRefData().getPosition().rot[0] = x; - ptr.getRefData().getPosition().rot[0] = y; - ptr.getRefData().getPosition().rot[0] = z;*/ if(WorldAxis) { ptr.getRefData().getBaseNode()->rotate(Ogre::Vector3::UNIT_X,Ogre::Degree(x)); @@ -652,11 +649,13 @@ namespace MWWorld ptr.getRefData().getPosition().rot[0] = rx.valueRadians(); ptr.getRefData().getPosition().rot[0] = ry.valueRadians(); ptr.getRefData().getPosition().rot[0] = rz.valueRadians(); + + mPhysics->rotateObject(Class::get(ptr).getId(ptr),ptr.getRefData().getBaseNode()->getOrientation()); //ptr.getRefData().getBaseNode()->rotate(ptr.getRefData().getBaseNode()->get //mPhysics->scaleObject( Class::get(ptr).getId(ptr), scale ); } - void setObjectRotation (Ptr ptr,float x,float y,float z) + void World::setObjectRotation (Ptr ptr,float x,float y,float z) { MWWorld::Class::get(ptr).adjustRotation(ptr,x,y,z); @@ -668,6 +667,7 @@ namespace MWWorld Ogre::Quaternion roty(Ogre::Degree(y),Ogre::Vector3::UNIT_Y); Ogre::Quaternion rotz(Ogre::Degree(z),Ogre::Vector3::UNIT_Z); ptr.getRefData().getBaseNode()->setOrientation(rotx*roty*rotz); + mPhysics->rotateObject(Class::get(ptr).getId(ptr),ptr.getRefData().getBaseNode()->getOrientation()); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const From f83ffecae5cf5433069f355f5d41b3c87386c845 Mon Sep 17 00:00:00 2001 From: gugus Date: Tue, 29 May 2012 15:57:13 +0200 Subject: [PATCH 006/298] SetAngle and SetScale script instruction --- apps/openmw/mwscript/docs/vmformat.txt | 4 ++- apps/openmw/mwscript/statsextensions.cpp | 38 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 0920c72f8..bb8f8a214 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -146,4 +146,6 @@ op 0x200014f: ForceGreeting op 0x2000150: ForceGreeting, explicit reference op 0x2000151: ToggleFullHelp op 0x2000152: Goodbye -opcodes 0x2000153-0x3ffffff unused +op 0x2000153: SetScale +op 0x2000154: SetAngle +opcodes 0x2000155-0x3ffffff unused diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 6e9b1e583..c68febec3 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -516,6 +516,35 @@ namespace MWScript } }; + template + class OpSetAngle : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float angle = runtime[0].mInteger; + runtime.pop(); + + if(axis == "X") + { + MWBase::Environment::get().getWorld()->setObjectRotation(ptr,angle,0,0); + } + if(axis == "Y") + { + MWBase::Environment::get().getWorld()->setObjectRotation(ptr,0,angle,0); + } + if(axis == "Z") + { + MWBase::Environment::get().getWorld()->setObjectRotation(ptr,0,0,angle); + } + } + }; + const int numberOfAttributes = 8; const int opcodeGetAttribute = 0x2000027; @@ -562,6 +591,9 @@ namespace MWScript const int opcodeModDisposition = 0x200014d; const int opcodeModDispositionExplicit = 0x200014e; + const int opcodeSetScale = 0x2000153; + const int opcodeSetAngle = 0x2000154; + void registerExtensions (Compiler::Extensions& extensions) { static const char *attributes[numberOfAttributes] = @@ -644,6 +676,9 @@ namespace MWScript extensions.registerInstruction("moddisposition","l",opcodeModDisposition, opcodeModDispositionExplicit); extensions.registerFunction("getpcrank",'l',"/S",opcodeGetPCRank,opcodeGetPCRankExplicit); + + extensions.registerInstruction("setscale","/l",opcodeSetScale); + extensions.registerInstruction("setangle","/Sl",opcodeSetAngle); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -715,6 +750,9 @@ namespace MWScript interpreter.installSegment5(opcodeModDispositionExplicit,new OpModDisposition); interpreter.installSegment3(opcodeGetPCRank,new OpGetPCRank); interpreter.installSegment3(opcodeGetPCRankExplicit,new OpGetPCRank); + + interpreter.installSegment5(opcodeSetScale,new OpSetScale); + interpreter.installSegment5(opcodeSetAngle,new OpSetAngle); } } } From a711a3ebe1b67886a58b004ddc13e5bbfc446fa2 Mon Sep 17 00:00:00 2001 From: gugus Date: Tue, 29 May 2012 16:45:43 +0200 Subject: [PATCH 007/298] Various fixes --- apps/openmw/mwscript/docs/vmformat.txt | 4 +++- apps/openmw/mwscript/statsextensions.cpp | 10 +++++++--- apps/openmw/mwworld/physicssystem.cpp | 4 ---- apps/openmw/mwworld/world.cpp | 1 + libs/openengine/bullet/physic.cpp | 24 ++++++++++++++++++++---- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index bb8f8a214..86cf73117 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -147,5 +147,7 @@ op 0x2000150: ForceGreeting, explicit reference op 0x2000151: ToggleFullHelp op 0x2000152: Goodbye op 0x2000153: SetScale -op 0x2000154: SetAngle +op 0x2000154: SetScale, explicit reference +op 0x2000155: SetAngle +op 0x2000156: SetAngle, explicit reference opcodes 0x2000155-0x3ffffff unused diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index c68febec3..83bfacf40 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -592,7 +592,9 @@ namespace MWScript const int opcodeModDispositionExplicit = 0x200014e; const int opcodeSetScale = 0x2000153; - const int opcodeSetAngle = 0x2000154; + const int opcodeSetScaleExplicit = 0x2000154; + const int opcodeSetAngle = 0x2000155; + const int opcodeSetAngleExplicit = 0x2000156; void registerExtensions (Compiler::Extensions& extensions) { @@ -677,8 +679,8 @@ namespace MWScript opcodeModDispositionExplicit); extensions.registerFunction("getpcrank",'l',"/S",opcodeGetPCRank,opcodeGetPCRankExplicit); - extensions.registerInstruction("setscale","/l",opcodeSetScale); - extensions.registerInstruction("setangle","/Sl",opcodeSetAngle); + extensions.registerInstruction("setscale","l",opcodeSetScale,opcodeSetScaleExplicit); + extensions.registerInstruction("setangle","Sl",opcodeSetAngle,opcodeSetAngleExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -752,7 +754,9 @@ namespace MWScript interpreter.installSegment3(opcodeGetPCRankExplicit,new OpGetPCRank); interpreter.installSegment5(opcodeSetScale,new OpSetScale); + interpreter.installSegment5(opcodeSetScaleExplicit,new OpSetScale); interpreter.installSegment5(opcodeSetAngle,new OpSetAngle); + interpreter.installSegment5(opcodeSetAngleExplicit,new OpSetAngle); } } } diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index c4e34a540..8ebdf2981 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -290,8 +290,6 @@ namespace MWWorld { if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) { - // TODO very dirty hack to avoid crash during setup -> needs cleaning up to allow - // start positions others than 0, 0, 0 act->setRotation(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w)); } if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle)) @@ -304,8 +302,6 @@ namespace MWWorld { if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle)) { - // TODO very dirty hack to avoid crash during setup -> needs cleaning up to allow - // start positions others than 0, 0, 0 //body->setWorldTransform(btTransform().se } } diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 69da98096..db96b6a19 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -668,6 +668,7 @@ namespace MWWorld Ogre::Quaternion rotz(Ogre::Degree(z),Ogre::Vector3::UNIT_Z); ptr.getRefData().getBaseNode()->setOrientation(rotx*roty*rotz); mPhysics->rotateObject(Class::get(ptr).getId(ptr),ptr.getRefData().getBaseNode()->getOrientation()); + std::cout << Class::get(ptr).getId(ptr); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index d30d5e9f1..4ead88994 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -392,8 +392,16 @@ namespace Physic RigidBody* PhysicEngine::getRigidBody(std::string name) { - RigidBody* body = RigidBodyMap[name]; - return body; + RigidBodyContainer::iterator it = RigidBodyMap.find(name); + if (it != RigidBodyMap.end() ) + { + RigidBody* body = RigidBodyMap[name]; + return body; + } + else + { + return 0; + } } void PhysicEngine::stepSimulation(double deltaT) @@ -450,8 +458,16 @@ namespace Physic PhysicActor* PhysicEngine::getCharacter(std::string name) { - PhysicActor* act = PhysicActorMap[name]; - return act; + PhysicActorContainer::iterator it = PhysicActorMap.find(name); + if (it != PhysicActorMap.end() ) + { + PhysicActor* act = PhysicActorMap[name]; + return act; + } + else + { + return 0; + } } void PhysicEngine::emptyEventLists(void) From bef4bef5d229e7f4f5335f6a4e3a6f83a8fc5d80 Mon Sep 17 00:00:00 2001 From: Mark Siewert Date: Sun, 10 Jun 2012 11:14:46 +0200 Subject: [PATCH 008/298] - Add support for loading multiple esm/esp files. Selection in omwlauncher is recognized and applied. - Quick hack for multiple terrain palettes. Prevents crashes and/or wrong textures in masters/plugins beyond the first. - Support deleting parent entries from the list. --- apps/openmw/engine.cpp | 30 ++++++++++++++----- apps/openmw/engine.hpp | 8 +++-- apps/openmw/main.cpp | 24 ++++++++------- apps/openmw/mwrender/terrain.cpp | 24 +++++++++++---- apps/openmw/mwrender/terrain.hpp | 5 +++- apps/openmw/mwworld/world.cpp | 36 ++++++++++++++++------ apps/openmw/mwworld/world.hpp | 3 +- components/esm/esm_reader.hpp | 8 +++++ components/esm/loadland.cpp | 1 + components/esm/loadland.hpp | 1 + components/esm_store/reclists.hpp | 50 ++++++++++++++++++++++++------- components/esm_store/store.cpp | 7 +++++ 12 files changed, 149 insertions(+), 48 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index e192b1f88..4fd43fa3f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -228,18 +228,32 @@ void OMW::Engine::setCell (const std::string& cellName) // Set master file (esm) // - If the given name does not have an extension, ".esm" is added automatically -// - Currently OpenMW only supports one master at the same time. void OMW::Engine::addMaster (const std::string& master) { - assert (mMaster.empty()); - mMaster = master; + mMaster.push_back(master); + std::string &str = mMaster.back(); - // Append .esm if not already there - std::string::size_type sep = mMaster.find_last_of ("."); + // Append .esm if not already there + std::string::size_type sep = str.find_last_of ("."); if (sep == std::string::npos) { - mMaster += ".esm"; + str += ".esm"; + } +} + +// Add plugin file (esp) + +void OMW::Engine::addPlugin (const std::string& plugin) +{ + mPlugins.push_back(plugin); + std::string &str = mPlugins.back(); + + // Append .esp if not already there + std::string::size_type sep = str.find_last_of ("."); + if (sep == std::string::npos) + { + str += ".esp"; } } @@ -341,8 +355,8 @@ void OMW::Engine::go() MWGui::CursorReplace replacer; // Create the world - mEnvironment.setWorld (new MWWorld::World (*mOgre, mFileCollections, mMaster, - mResDir, mNewGame, mEncoding, mFallbackMap)); + mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins, + mResDir, mNewGame, mEncoding, mFallbackMap) ); // Create window manager - this manages all the MW-specific GUI windows MWScript::registerExtensions (mExtensions); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index cf1ef3b9c..1cbc63609 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -68,7 +68,8 @@ namespace OMW boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; std::string mCellName; - std::string mMaster; + std::vector mMaster; + std::vector mPlugins; int mFpsLevel; bool mDebug; bool mVerboseScripts; @@ -124,9 +125,12 @@ namespace OMW /// Set master file (esm) /// - If the given name does not have an extension, ".esm" is added automatically - /// - Currently OpenMW only supports one master at the same time. void addMaster(const std::string& master); + /// Same as "addMaster", but for plugin files (esp) + /// - If the given name does not have an extension, ".esp" is added automatically + void addPlugin(const std::string& plugin); + /// Enable fps counter void showFPS(int level); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 68aa12fb3..3a63361f9 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -239,19 +239,23 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat master.push_back("Morrowind"); } - if (master.size() > 1) - { - std::cout - << "Ignoring all but the first master file (multiple master files not yet supported)." + StringsVector plugin = variables["plugin"].as(); + int cnt = master.size() + plugin.size(); + if (cnt > 255) + { + std::cerr + << "Error: Trying to load more than 255 master and plugin files! This will break all combaibility and is not supported!" << std::endl; + return false; } - engine.addMaster(master[0]); - - StringsVector plugin = variables["plugin"].as(); - if (!plugin.empty()) + for (std::vector::size_type i = 0; i < master.size(); i++) { - std::cout << "Ignoring plugin files (plugins not yet supported)." << std::endl; - } + engine.addMaster(master[i]); + } + for (std::vector::size_type i = 0; i < plugin.size(); i++) + { + engine.addPlugin(plugin[i]); + } // startup-settings engine.setCell(variables["start"].as()); diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index 4dac750c7..9247d42cf 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -156,7 +156,7 @@ namespace MWRender std::map indexes; initTerrainTextures(&terrainData, cellX, cellY, x * numTextures, y * numTextures, - numTextures, indexes); + numTextures, indexes, land->plugin); if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL) { @@ -218,8 +218,14 @@ namespace MWRender void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, int cellX, int cellY, int fromX, int fromY, int size, - std::map& indexes) + std::map& indexes, + size_t plugin) { + // FIXME: In a multiple esm configuration, we have multiple palettes. Since this code + // crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need + // to adopt the following code for a dynamic palette. And this is evil - the current design + // does not work well for this task... + assert(terrainData != NULL && "Must have valid terrain data"); assert(fromX >= 0 && fromY >= 0 && "Can't get a terrain texture on terrain outside the current cell"); @@ -232,12 +238,16 @@ namespace MWRender // //If we don't sort the ltex indexes, the splatting order may differ between //cells which may lead to inconsistent results when shading between cells + int num = MWBase::Environment::get().getWorld()->getStore().landTexts.getSizePlugin(plugin); std::set ltexIndexes; for ( int y = fromY - 1; y < fromY + size + 1; y++ ) { for ( int x = fromX - 1; x < fromX + size + 1; x++ ) { - ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y)); + int idx = getLtexIndexAt(cellX, cellY, x, y); + if (idx > num) + idx = 0; + ltexIndexes.insert(idx); } } @@ -249,7 +259,7 @@ namespace MWRender iter != ltexIndexes.end(); ++iter ) { - const uint16_t ltexIndex = *iter; + uint16_t ltexIndex = *iter; //this is the base texture, so we can ignore this at present if ( ltexIndex == baseTexture ) { @@ -262,8 +272,10 @@ namespace MWRender { //NB: All vtex ids are +1 compared to the ltex ids - assert( (int)MWBase::Environment::get().getWorld()->getStore().landTexts.getSize() >= (int)ltexIndex - 1 && + /* + assert( (int)mEnvironment.mWorld->getStore().landTexts.getSizePlugin(plugin) >= (int)ltexIndex - 1 && "LAND.VTEX must be within the bounds of the LTEX array"); + */ std::string texture; if ( ltexIndex == 0 ) @@ -272,7 +284,7 @@ namespace MWRender } else { - texture = MWBase::Environment::get().getWorld()->getStore().landTexts.search(ltexIndex-1)->texture; + texture = MWBase::Environment::get().getWorld()->getStore().landTexts.search(ltexIndex-1, plugin)->texture; //TODO this is needed due to MWs messed up texture handling texture = texture.substr(0, texture.rfind(".")) + ".dds"; } diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp index 273ede084..5bda71eef 100644 --- a/apps/openmw/mwrender/terrain.hpp +++ b/apps/openmw/mwrender/terrain.hpp @@ -63,11 +63,14 @@ namespace MWRender{ * @param size the size (number of splats) to get * @param indexes a mapping of ltex index to the terrain texture layer that * can be used by initTerrainBlendMaps + * @param plugin the index of the plugin providing the texture list for this + * cell data; required because MW uses texture data on a per-plugin base */ void initTerrainTextures(Ogre::Terrain::ImportData* terrainData, int cellX, int cellY, int fromX, int fromY, int size, - std::map& indexes); + std::map& indexes, + size_t plugin = 0); /** * Creates the blend (splatting maps) for the given terrain from the ltex data. diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index e8d555689..fe3ddd5a8 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -176,7 +176,8 @@ namespace MWWorld World::World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::string& master, const boost::filesystem::path& resDir, bool newGame, + const std::vector& master, + const std::vector& plugins, const boost::filesystem::path& resDir, bool newGame, const std::string& encoding, std::map fallbackMap) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), mSky (true), mNextDynamicRecord (0), mCells (mStore, mEsm, *this), @@ -189,15 +190,32 @@ namespace MWWorld mWeatherManager = new MWWorld::WeatherManager(mRendering); - boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master)); - - std::cout << "Loading ESM " << masterPath.string() << "\n"; - - // This parses the ESM file and loads a sample cell - mEsm.setEncoding(encoding); - mEsm.open (masterPath.string()); - mStore.load (mEsm); + int idx = 0; + for (std::vector::size_type i = 0; i < master.size(); i++, idx++) + { + boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master[i])); + + std::cout << "Loading ESM " << masterPath.string() << "\n"; + + // This parses the ESM file and loads a sample cell + mEsm.setEncoding(encoding); + mEsm.open (masterPath.string()); + mEsm.setIndex(idx); + mStore.load (mEsm); + } + for (std::vector::size_type i = 0; i < plugins.size(); i++, idx++) + { + boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i])); + + std::cout << "Loading ESP " << pluginPath.string() << "\n"; + + // This parses the ESP file and loads a sample cell + mEsm.setEncoding(encoding); + mEsm.open (pluginPath.string()); + mEsm.setIndex(idx); + mStore.load (mEsm); + } MWRender::Player* play = &(mRendering->getPlayer()); mPlayer = new MWWorld::Player (play, mStore.npcs.find ("player"), *this); mPhysics->addActor (mPlayer->getPlayer().getRefData().getHandle(), "", Ogre::Vector3 (0, 0, 0)); diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index e8391773b..9e36765ff 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -117,7 +117,8 @@ namespace MWWorld World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::string& master, const boost::filesystem::path& resDir, bool newGame, + const std::vector& master, + const std::vector& plugins, const boost::filesystem::path& resDir, bool newGame, const std::string& encoding, std::map fallbackMap); ~World(); diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index 340482891..4d61c3575 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -193,6 +193,14 @@ public: void openRaw(const std::string &file); + // This is a quick hack for multiple esm/esp files. Each plugin introduces its own + // terrain palette, but ESMReader does not pass a reference to the correct plugin + // to the individual load() methods. This hack allows to pass this reference + // indirectly to the load() method. + int idx; + void setIndex(const int index) {idx = index;} + const int getIndex() {return idx;} + /************************************************************************* * * Medium-level reading shortcuts diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 96afdf831..822952c91 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -23,6 +23,7 @@ Land::~Land() void Land::load(ESMReader &esm) { mEsm = &esm; + plugin = mEsm->getIndex(); // Get the grid location esm.getSubNameIs("INTV"); diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index ebc314a28..d03012d38 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -17,6 +17,7 @@ struct Land int flags; // Only first four bits seem to be used, don't know what // they mean. int X, Y; // Map coordinates. + int plugin; // Plugin index, used to reference the correct material palette. // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. diff --git a/components/esm_store/reclists.hpp b/components/esm_store/reclists.hpp index 48bf050cd..dd66d718a 100644 --- a/components/esm_store/reclists.hpp +++ b/components/esm_store/reclists.hpp @@ -26,6 +26,7 @@ namespace ESMS virtual void load(ESMReader &esm, const std::string &id) = 0; virtual int getSize() = 0; + virtual void remove(const std::string &id) {}; virtual void listIdentifier (std::vector& identifier) const = 0; static std::string toLower (const std::string& name) @@ -57,6 +58,14 @@ namespace ESMS list[id2].load(esm); } + // Delete the given object ID + void remove(const std::string &id) + { + std::string id2 = toLower (id); + + list.erase(id2); + } + // Find the given object ID, or return NULL if not found. const X* search(const std::string &id) const { @@ -268,38 +277,57 @@ namespace ESMS { virtual ~LTexList() {} - // TODO: For multiple ESM/ESP files we need one list per file. - std::vector ltex; + // For multiple ESM/ESP files we need one list per file. + typedef std::vector LandTextureList; + std::vector ltex; LTexList() { + ltex.push_back(LandTextureList()); + LandTextureList <exl = ltex[0]; // More than enough to hold Morrowind.esm. - ltex.reserve(128); + ltexl.reserve(128); } - const LandTexture* search(size_t index) const + const LandTexture* search(size_t index, size_t plugin) const { - assert(index < ltex.size()); - return <ex.at(index); + assert(plugin < ltex.size()); + const LandTextureList <exl = ltex[plugin]; + + assert(index < ltexl.size()); + return <exl.at(index); } int getSize() { return ltex.size(); } int getSize() const { return ltex.size(); } - virtual void listIdentifier (std::vector& identifier) const {} + int getSizePlugin(size_t plugin) { assert(plugin < ltex.size()); return ltex[plugin].size(); } + int getSizePlugin(size_t plugin) const { assert(plugin < ltex.size()); return ltex[plugin].size(); } - void load(ESMReader &esm, const std::string &id) + virtual void listIdentifier (std::vector& identifier) const {} + + void load(ESMReader &esm, const std::string &id, size_t plugin) { LandTexture lt; lt.load(esm); lt.id = id; // Make sure we have room for the structure - if(lt.index + 1 > (int)ltex.size()) - ltex.resize(lt.index+1); + if (plugin >= ltex.size()) { + ltex.resize(plugin+1); + } + LandTextureList <exl = ltex[plugin]; + if(lt.index + 1 > (int)ltexl.size()) + ltexl.resize(lt.index+1); // Store it - ltex[lt.index] = lt; + ltexl[lt.index] = lt; + } + + void load(ESMReader &esm, const std::string &id) + { + size_t plugin = esm.getIndex(); + load(esm, id, plugin); } }; diff --git a/components/esm_store/store.cpp b/components/esm_store/store.cpp index c676601e5..b6972355c 100644 --- a/components/esm_store/store.cpp +++ b/components/esm_store/store.cpp @@ -67,6 +67,13 @@ void ESMStore::load(ESMReader &esm) { // Load it std::string id = esm.getHNOString("NAME"); + // ... unless it got deleted! + if (esm.isNextSub("DELE")) { + esm.skipRecord(); + all.erase(id); + it->second->remove(id); + continue; + } it->second->load(esm, id); if (n.val==ESM::REC_DIAL) From aa827442e86e5baa2239b78542adde22a36b0535 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 24 Jun 2012 11:39:21 +0200 Subject: [PATCH 009/298] Issue #314: Generalised ActiveSpells class so that it can handle lasting effects from potions too --- apps/openmw/mwmechanics/activespells.cpp | 36 ++++++++++++++++-------- apps/openmw/mwmechanics/activespells.hpp | 6 ++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index ced2a5c3f..f456a0e51 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -3,6 +3,9 @@ #include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwworld/world.hpp" @@ -42,14 +45,13 @@ namespace MWMechanics for (TIterator iter (begin()); iter!=end(); ++iter) { - const ESM::Spell& spell = - *MWBase::Environment::get().getWorld()->getStore().spells.find (iter->first); + const ESM::EffectList& effects = getEffectList (iter->first); const MWWorld::TimeStamp& start = iter->second.first; float magnitude = iter->second.second; - for (std::vector::const_iterator iter (spell.effects.list.begin()); - iter!=spell.effects.list.end(); ++iter) + for (std::vector::const_iterator iter (effects.list.begin()); + iter!=effects.list.end(); ++iter) { if (iter->duration) { @@ -70,18 +72,31 @@ namespace MWMechanics } } + const ESM::EffectList& ActiveSpells::getEffectList (const std::string& id) const + { + if (const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().spells.search (id)) + return spell->effects; + + if (const ESM::Potion *potion = + MWBase::Environment::get().getWorld()->getStore().potions.search (id)) + return potion->effects; + + throw std::runtime_error ("ID " + id + " can not produce lasting effects"); + } + ActiveSpells::ActiveSpells() : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} void ActiveSpells::addSpell (const std::string& id) { - const ESM::Spell& spell = *MWBase::Environment::get().getWorld()->getStore().spells.find (id); + const ESM::EffectList& effects = getEffectList (id); bool found = false; - for (std::vector::const_iterator iter (spell.effects.list.begin()); - iter!=spell.effects.list.end(); ++iter) + for (std::vector::const_iterator iter (effects.list.begin()); + iter!=effects.list.end(); ++iter) { if (iter->duration) { @@ -137,13 +152,12 @@ namespace MWMechanics double ActiveSpells::timeToExpire (const TIterator& iterator) const { - const ESM::Spell& spell = - *MWBase::Environment::get().getWorld()->getStore().spells.find (iterator->first); + const ESM::EffectList& effects = getEffectList (iterator->first); int duration = 0; - for (std::vector::const_iterator iter (spell.effects.list.begin()); - iter!=spell.effects.list.end(); ++iter) + for (std::vector::const_iterator iter (effects.list.begin()); + iter!=effects.list.end(); ++iter) { if (iter->duration>duration) duration = iter->duration; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 179321c58..91c528587 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -12,11 +12,15 @@ namespace ESM { struct Spell; + struct EffectList; } namespace MWMechanics { /// \brief Lasting spell effects + /// + /// \note The name of this class is slightly misleading, since it also handels lasting potion + /// effects. class ActiveSpells { public: @@ -33,6 +37,8 @@ namespace MWMechanics void update() const; + const ESM::EffectList& getEffectList (const std::string& id) const; + public: ActiveSpells(); From 83c3972a89d78cddee8c12b7616bd862fb6c30fa Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 24 Jun 2012 16:23:43 +0200 Subject: [PATCH 010/298] Issue #314: added return value to addSpell function --- apps/openmw/mwmechanics/activespells.cpp | 6 ++++-- apps/openmw/mwmechanics/activespells.hpp | 4 +++- apps/openmw/mwworld/class.cpp | 5 +++++ apps/openmw/mwworld/class.hpp | 6 ++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index f456a0e51..e9215ec57 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -89,7 +89,7 @@ namespace MWMechanics : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - void ActiveSpells::addSpell (const std::string& id) + bool ActiveSpells::addSpell (const std::string& id) { const ESM::EffectList& effects = getEffectList (id); @@ -106,7 +106,7 @@ namespace MWMechanics } if (!found) - return; + return false; TContainer::iterator iter = mSpells.find (id); @@ -119,6 +119,8 @@ namespace MWMechanics iter->second = std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random); mSpellsChanged = true; + + return true; } void ActiveSpells::removeSpell (const std::string& id) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 91c528587..2226cea84 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -43,9 +43,11 @@ namespace MWMechanics ActiveSpells(); - void addSpell (const std::string& id); + bool addSpell (const std::string& id); ///< Overwrites an existing spell with the same ID. If the spell does not have any /// non-instant effects, it is ignored. + /// + /// \return Has the spell been added? void removeSpell (const std::string& id); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index fe39406fe..dde3afbbb 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -32,6 +32,11 @@ namespace MWWorld } + bool Class::apply (const MWWorld::Ptr& ptr, const std::string& id) const + { + return false; + } + MWMechanics::CreatureStats& Class::getCreatureStats (const Ptr& ptr) const { throw std::runtime_error ("class does not have creature stats"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 46781d516..8dd427bde 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -164,6 +164,12 @@ namespace MWWorld /// effects). Throws an exception, if the object can't hold other objects. /// (default implementation: throws an exception) + virtual bool apply (const MWWorld::Ptr& ptr, const std::string& id) const; + ///< Apply \a id on \a ptr. + /// \return Any effect? + /// + /// (default implementation: ignore and return false) + static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. From 046e9686f9f845616b01f1cec5b5a8429651af71 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 2 Jul 2012 21:41:21 -0700 Subject: [PATCH 011/298] Cleanup RecordPtrT This moves the index resolution into a separate post method instead of always checking when access. As a result, it reduces the size of it down to the size of a pointer, as opposed to 2 pointers + 1 int. The appropriate methods are added to the various node types to make sure they're resolved. --- components/nif/controlled.hpp | 111 +-- components/nif/controller.hpp | 225 +++--- components/nif/data.hpp | 1321 ++++++++++++++++----------------- components/nif/effect.hpp | 99 +-- components/nif/extra.hpp | 92 +-- components/nif/nif_file.cpp | 24 +- components/nif/nif_file.hpp | 146 ++-- components/nif/node.hpp | 340 +++++---- components/nif/property.hpp | 322 ++++---- components/nif/record.hpp | 27 +- components/nif/record_ptr.hpp | 140 ++-- 11 files changed, 1464 insertions(+), 1383 deletions(-) diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index d59950132..24281146f 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -25,6 +25,7 @@ #define _NIF_CONTROLLED_H_ #include "extra.hpp" +#include "controller.hpp" namespace Nif { @@ -33,92 +34,104 @@ namespace Nif class Controlled : public Extra { public: - ControllerPtr controller; - - void read(NIFFile *nif) - { - Extra::read(nif); - controller.read(nif); - } + ControllerPtr controller; + + void read(NIFFile *nif) + { + Extra::read(nif); + controller.read(nif); + } + + void post(NIFFile *nif) + { + Extra::post(nif); + controller.post(nif); + } }; /// Has name, extra-data and controller class Named : public Controlled { public: - Misc::SString name; + Misc::SString name; - void read(NIFFile *nif) - { - name = nif->getString(); - Controlled::read(nif); - } + void read(NIFFile *nif) + { + name = nif->getString(); + Controlled::read(nif); + } }; typedef Named NiSequenceStreamHelper; class NiParticleGrowFade : public Controlled { public: - void read(NIFFile *nif) - { - Controlled::read(nif); + void read(NIFFile *nif) + { + Controlled::read(nif); - // Two floats. - nif->skip(8); - } + // Two floats. + nif->skip(8); + } }; class NiParticleColorModifier : public Controlled { public: - NiColorDataPtr data; - - void read(NIFFile *nif) - { - Controlled::read(nif); - data.read(nif); - } + NiColorDataPtr data; + + void read(NIFFile *nif) + { + Controlled::read(nif); + data.read(nif); + } + + void post(NIFFile *nif) + { + Controlled::post(nif); + data.post(nif); + } }; class NiGravity : public Controlled { public: - void read(NIFFile *nif) - { - Controlled::read(nif); + void read(NIFFile *nif) + { + Controlled::read(nif); - // two floats, one int, six floats - nif->skip(9*4); - } + // two floats, one int, six floats + nif->skip(9*4); + } }; // NiPinaColada class NiPlanarCollider : public Controlled { public: - void read(NIFFile *nif) - { - Controlled::read(nif); + void read(NIFFile *nif) + { + Controlled::read(nif); - // (I think) 4 floats + 4 vectors - nif->skip(4*16); - } + // (I think) 4 floats + 4 vectors + nif->skip(4*16); + } }; class NiParticleRotation : public Controlled { public: - void read(NIFFile *nif) - { - Controlled::read(nif); - - /* - byte (0 or 1) - float (1) - float*3 - */ - nif->skip(17); - } + void read(NIFFile *nif) + { + Controlled::read(nif); + + /* + byte (0 or 1) + float (1) + float*3 + */ + nif->skip(17); + } }; } // Namespace diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index d6fb22255..d00c1bc0e 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -34,136 +34,187 @@ namespace Nif class Controller : public Record { public: - ControllerPtr next; - int flags; - float frequency, phase; - float timeStart, timeStop; - ControlledPtr target; - - void read(NIFFile *nif) - { - next.read(nif); - - flags = nif->getShort(); - - frequency = nif->getFloat(); - phase = nif->getFloat(); - timeStart = nif->getFloat(); - timeStop = nif->getFloat(); - - target.read(nif); - } + ControllerPtr next; + int flags; + float frequency, phase; + float timeStart, timeStop; + ControlledPtr target; + + void read(NIFFile *nif) + { + next.read(nif); + + flags = nif->getShort(); + + frequency = nif->getFloat(); + phase = nif->getFloat(); + timeStart = nif->getFloat(); + timeStop = nif->getFloat(); + + target.read(nif); + } + + void post(NIFFile *nif) + { + Record::post(nif); + next.post(nif); + target.post(nif); + } }; class NiBSPArrayController : public Controller { public: - void read(NIFFile *nif) - { - Controller::read(nif); - - // At the moment, just skip it all - nif->skip(111); - int s = nif->getShort(); - nif->skip(15 + s*40); - } + void read(NIFFile *nif) + { + Controller::read(nif); + + // At the moment, just skip it all + nif->skip(111); + int s = nif->getShort(); + nif->skip(15 + s*40); + } }; typedef NiBSPArrayController NiParticleSystemController; class NiMaterialColorController : public Controller { public: - NiPosDataPtr data; - - void read(NIFFile *nif) - { - Controller::read(nif); - data.read(nif); - } + NiPosDataPtr data; + + void read(NIFFile *nif) + { + Controller::read(nif); + data.read(nif); + } + + void post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } }; class NiPathController : public Controller { public: - NiPosDataPtr posData; - NiFloatDataPtr floatData; - - void read(NIFFile *nif) - { - Controller::read(nif); - - /* - int = 1 - 2xfloat - short = 0 or 1 - */ - nif->skip(14); - posData.read(nif); - floatData.read(nif); - } + NiPosDataPtr posData; + NiFloatDataPtr floatData; + + void read(NIFFile *nif) + { + Controller::read(nif); + + /* + int = 1 + 2xfloat + short = 0 or 1 + */ + nif->skip(14); + posData.read(nif); + floatData.read(nif); + } + + void post(NIFFile *nif) + { + Controller::post(nif); + + posData.post(nif); + floatData.post(nif); + } }; class NiUVController : public Controller { public: - NiUVDataPtr data; + NiUVDataPtr data; - void read(NIFFile *nif) - { - Controller::read(nif); + void read(NIFFile *nif) + { + Controller::read(nif); - nif->getShort(); // always 0 - data.read(nif); - } + nif->getShort(); // always 0 + data.read(nif); + } + + void post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } }; class NiKeyframeController : public Controller { public: - NiKeyframeDataPtr data; - - void read(NIFFile *nif) - { - Controller::read(nif); - data.read(nif); - } + NiKeyframeDataPtr data; + + void read(NIFFile *nif) + { + Controller::read(nif); + data.read(nif); + } + + void post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } }; class NiAlphaController : public Controller { public: - NiFloatDataPtr data; - - void read(NIFFile *nif) - { - Controller::read(nif); - data.read(nif); - } + NiFloatDataPtr data; + + void read(NIFFile *nif) + { + Controller::read(nif); + data.read(nif); + } + + void post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } }; class NiGeomMorpherController : public Controller { public: - NiMorphDataPtr data; - - void read(NIFFile *nif) - { - Controller::read(nif); - data.read(nif); - nif->getByte(); // always 0 - } + NiMorphDataPtr data; + + void read(NIFFile *nif) + { + Controller::read(nif); + data.read(nif); + nif->getByte(); // always 0 + } + + void post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } }; class NiVisController : public Controller { public: - NiVisDataPtr data; - - void read(NIFFile *nif) - { - Controller::read(nif); - data.read(nif); - } + NiVisDataPtr data; + + void read(NIFFile *nif) + { + Controller::read(nif); + data.read(nif); + } + + void post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } }; } // Namespace diff --git a/components/nif/data.hpp b/components/nif/data.hpp index df9079758..eaa11b5ee 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -34,794 +34,777 @@ namespace Nif class NiSourceTexture : public Named { public: + // Is this an external (references a separate texture file) or + // internal (data is inside the nif itself) texture? + bool external; + + Misc::SString filename; // In case of external textures + NiPixelDataPtr data; // In case of internal textures + + /* Pixel layout + 0 - Palettised + 1 - High color 16 + 2 - True color 32 + 3 - Compressed + 4 - Bumpmap + 5 - Default */ + int pixel; + + /* Mipmap format + 0 - no + 1 - yes + 2 - default */ + int mipmap; + + /* Alpha + 0 - none + 1 - binary + 2 - smooth + 3 - default (use material alpha, or multiply material with texture if present) + */ + int alpha; + + void read(NIFFile *nif) + { + Named::read(nif); - // Is this an external (references a separate texture file) or - // internal (data is inside the nif itself) texture? - bool external; - - Misc::SString filename; // In case of external textures - NiPixelDataPtr data; // In case of internal textures - - /* Pixel layout - 0 - Palettised - 1 - High color 16 - 2 - True color 32 - 3 - Compressed - 4 - Bumpmap - 5 - Default */ - int pixel; - - /* Mipmap format - 0 - no - 1 - yes - 2 - default */ - int mipmap; - - /* Alpha - 0 - none - 1 - binary - 2 - smooth - 3 - default (use material alpha, or multiply material with texture if present) - */ - int alpha; - - void read(NIFFile *nif) - { - Named::read(nif); - - external = !!nif->getByte(); - - if(external) filename = nif->getString(); - else - { - nif->getByte(); // always 1 - data.read(nif); - } + external = !!nif->getByte(); + if(external) + filename = nif->getString(); + else + { + nif->getByte(); // always 1 + data.read(nif); + } - pixel = nif->getInt(); - mipmap = nif->getInt(); - alpha = nif->getInt(); + pixel = nif->getInt(); + mipmap = nif->getInt(); + alpha = nif->getInt(); - nif->getByte(); // always 1 - } + nif->getByte(); // always 1 + } + + void post(NIFFile *nif) + { + Named::post(nif); + data.post(nif); + } }; // Common ancestor for several data classes class ShapeData : public Record { public: - Misc::FloatArray vertices, normals, colors, uvlist; - const Vector *center; - float radius; + Misc::FloatArray vertices, normals, colors, uvlist; + const Vector *center; + float radius; - void read(NIFFile *nif) - { - int verts = nif->getShort(); + void read(NIFFile *nif) + { + int verts = nif->getShort(); - if(nif->getInt()) - vertices = nif->getFloatLen(verts*3); + if(nif->getInt()) + vertices = nif->getFloatLen(verts*3); - if(nif->getInt()) - normals = nif->getFloatLen(verts*3); + if(nif->getInt()) + normals = nif->getFloatLen(verts*3); - center = nif->getVector(); - radius = nif->getFloat(); + center = nif->getVector(); + radius = nif->getFloat(); - if(nif->getInt()) - colors = nif->getFloatLen(verts*4); + if(nif->getInt()) + colors = nif->getFloatLen(verts*4); - int uvs = nif->getShort(); + int uvs = nif->getShort(); - // Only the first 6 bits are used as a count. I think the rest are - // flags of some sort. - uvs &= 0x3f; + // Only the first 6 bits are used as a count. I think the rest are + // flags of some sort. + uvs &= 0x3f; - if(nif->getInt()) - uvlist = nif->getFloatLen(uvs*verts*2); - } + if(nif->getInt()) + uvlist = nif->getFloatLen(uvs*verts*2); + } }; class NiTriShapeData : public ShapeData { public: - // Triangles, three vertex indices per triangle - Misc::SliceArray triangles; - - void read(NIFFile *nif) - { - ShapeData::read(nif); - - int tris = nif->getShort(); - if(tris) - { - // We have three times as many vertices as triangles, so this - // is always equal to tris*3. - int cnt = nif->getInt(); - triangles = nif->getArrayLen(cnt); - } - - // Read the match list, which lists the vertices that are equal to - // vertices. We don't actually need need this for anything, so - // just skip it. - int verts = nif->getShort(); - if(verts) - { - for(int i=0;igetShort(); - nif->skip(num*sizeof(short)); - } - } - } + // Triangles, three vertex indices per triangle + Misc::SliceArray triangles; + + void read(NIFFile *nif) + { + ShapeData::read(nif); + + int tris = nif->getShort(); + if(tris) + { + // We have three times as many vertices as triangles, so this + // is always equal to tris*3. + int cnt = nif->getInt(); + triangles = nif->getArrayLen(cnt); + } + + // Read the match list, which lists the vertices that are equal to + // vertices. We don't actually need need this for anything, so + // just skip it. + int verts = nif->getShort(); + if(verts) + { + for(int i=0;igetShort(); + nif->skip(num*sizeof(short)); + } + } + } }; class NiAutoNormalParticlesData : public ShapeData { public: - int activeCount; + int activeCount; - void read(NIFFile *nif) - { - ShapeData::read(nif); + void read(NIFFile *nif) + { + ShapeData::read(nif); - // Should always match the number of vertices - activeCount = nif->getShort(); + // Should always match the number of vertices + activeCount = nif->getShort(); - // Skip all the info, we don't support particles yet - nif->getFloat(); // Active radius ? - nif->getShort(); // Number of valid entries in the following arrays ? + // Skip all the info, we don't support particles yet + nif->getFloat(); // Active radius ? + nif->getShort(); // Number of valid entries in the following arrays ? - if(nif->getInt()) - // Particle sizes - nif->getFloatLen(activeCount); - } + if(nif->getInt()) + { + // Particle sizes + nif->getFloatLen(activeCount); + } + } }; class NiRotatingParticlesData : public NiAutoNormalParticlesData { public: - void read(NIFFile *nif) - { - NiAutoNormalParticlesData::read(nif); - - if(nif->getInt()) - // Rotation quaternions. I THINK activeCount is correct here, - // but verts (vertex number) might also be correct, if there is - // any case where the two don't match. - nif->getArrayLen(activeCount); - } + void read(NIFFile *nif) + { + NiAutoNormalParticlesData::read(nif); + + if(nif->getInt()) + { + // Rotation quaternions. I THINK activeCount is correct here, + // but verts (vertex number) might also be correct, if there is + // any case where the two don't match. + nif->getArrayLen(activeCount); + } + } }; class NiPosData : public Record { public: - void read(NIFFile *nif) - { - int count = nif->getInt(); - int type = nif->getInt(); - if(type != 1 && type != 2) - nif->fail("Cannot handle NiPosData type"); - - // TODO: Could make structs of these. Seems to be identical to - // translation in NiKeyframeData. - for(int i=0; igetFloat(); - nif->getVector(); // This isn't really shared between type 1 - // and type 2, most likely - if(type == 2) - { - nif->getVector(); - nif->getVector(); - } - } - } + void read(NIFFile *nif) + { + int count = nif->getInt(); + int type = nif->getInt(); + if(type != 1 && type != 2) + nif->fail("Cannot handle NiPosData type"); + + // TODO: Could make structs of these. Seems to be identical to + // translation in NiKeyframeData. + for(int i=0; igetFloat(); + nif->getVector(); // This isn't really shared between type 1 + // and type 2, most likely + if(type == 2) + { + nif->getVector(); + nif->getVector(); + } + } + } }; class NiUVData : public Record { public: - void read(NIFFile *nif) - { - // TODO: This is claimed to be a "float animation key", which is - // also used in FloatData and KeyframeData. We could probably - // reuse and refactor a lot of this if we actually use it at some - // point. - - for(int i=0; i<2; i++) - { - int count = nif->getInt(); - - if(count) - { - nif->getInt(); // always 2 - nif->getArrayLen(count); // Really one time float + one vector - } - } - // Always 0 - nif->getInt(); - nif->getInt(); - } + void read(NIFFile *nif) + { + // TODO: This is claimed to be a "float animation key", which is + // also used in FloatData and KeyframeData. We could probably + // reuse and refactor a lot of this if we actually use it at some + // point. + for(int i=0; i<2; i++) + { + int count = nif->getInt(); + if(count) + { + nif->getInt(); // always 2 + nif->getArrayLen(count); // Really one time float + one vector + } + } + // Always 0 + nif->getInt(); + nif->getInt(); + } }; class NiFloatData : public Record { public: - void read(NIFFile *nif) - { - int count = nif->getInt(); - nif->getInt(); // always 2 - nif->getArrayLen(count); // Really one time float + one vector - } + void read(NIFFile *nif) + { + int count = nif->getInt(); + nif->getInt(); // always 2 + nif->getArrayLen(count); // Really one time float + one vector + } }; class NiPixelData : public Record { public: - unsigned int rmask, gmask, bmask, amask; - int bpp, mips; + unsigned int rmask, gmask, bmask, amask; + int bpp, mips; - void read(NIFFile *nif) - { - nif->getInt(); // always 0 or 1 + void read(NIFFile *nif) + { + nif->getInt(); // always 0 or 1 - rmask = nif->getInt(); // usually 0xff - gmask = nif->getInt(); // usually 0xff00 - bmask = nif->getInt(); // usually 0xff0000 - amask = nif->getInt(); // usually 0xff000000 or zero + rmask = nif->getInt(); // usually 0xff + gmask = nif->getInt(); // usually 0xff00 + bmask = nif->getInt(); // usually 0xff0000 + amask = nif->getInt(); // usually 0xff000000 or zero - bpp = nif->getInt(); + bpp = nif->getInt(); - // Unknown - nif->skip(12); + // Unknown + nif->skip(12); - mips = nif->getInt(); + mips = nif->getInt(); - // Bytes per pixel, should be bpp * 8 - /*int bytes =*/ nif->getInt(); + // Bytes per pixel, should be bpp * 8 + /*int bytes =*/ nif->getInt(); - for(int i=0; igetInt(); - /*int y =*/ nif->getInt(); - /*int offset =*/ nif->getInt(); - } + for(int i=0; igetInt(); + /*int y =*/ nif->getInt(); + /*int offset =*/ nif->getInt(); + } - // Skip the data - unsigned int dataSize = nif->getInt(); - nif->skip(dataSize); - } + // Skip the data + unsigned int dataSize = nif->getInt(); + nif->skip(dataSize); + } }; class NiColorData : public Record { public: - struct ColorData - { - float time; - Vector4 rgba; - }; - - void read(NIFFile *nif) - { - int count = nif->getInt(); - nif->getInt(); // always 1 - - // Skip the data - assert(sizeof(ColorData) == 4*5); - nif->skip(sizeof(ColorData) * count); - } + struct ColorData + { + float time; + Vector4 rgba; + }; + + void read(NIFFile *nif) + { + int count = nif->getInt(); + nif->getInt(); // always 1 + + // Skip the data + assert(sizeof(ColorData) == 4*5); + nif->skip(sizeof(ColorData) * count); + } }; class NiVisData : public Record { public: - void read(NIFFile *nif) - { - int count = nif->getInt(); - /* - Each VisData consists of: - float time; - byte isSet; - - If you implement this, make sure you use a packed struct - (sizeof==5), or read each element individually. - */ - nif->skip(count*5); - } + void read(NIFFile *nif) + { + int count = nif->getInt(); + /* + Each VisData consists of: + float time; + byte isSet; + + If you implement this, make sure you use a packed struct + (sizeof==5), or read each element individually. + */ + nif->skip(count*5); + } }; class NiSkinInstance : public Record { public: - NiSkinDataPtr data; - NodePtr root; - NodeList bones; - - void read(NIFFile *nif) - { - data.read(nif); - root.read(nif); - bones.read(nif); + NiSkinDataPtr data; + NodePtr root; + NodeList bones; - if(data.empty() || root.empty()) - nif->fail("NiSkinInstance missing root or data"); - } + void read(NIFFile *nif) + { + data.read(nif); + root.read(nif); + bones.read(nif); + } - void post(NIFFile *nif); + void post(NIFFile *nif); }; class NiSkinData : public Record { public: - // This is to make sure the structs are packed, ie. that the - // compiler doesn't mess them up with extra alignment bytes. + // This is to make sure the structs are packed, ie. that the + // compiler doesn't mess them up with extra alignment bytes. #pragma pack(push) #pragma pack(1) - struct BoneTrafo - { - Matrix rotation; // Rotation offset from bone? - Vector trans; // Translation - float scale; // Probably scale (always 1) - }; - struct BoneTrafoCopy - { - Ogre::Quaternion rotation; - Ogre::Vector3 trans; - float scale; - }; - - struct VertWeight - { - short vertex; - float weight; - }; + struct BoneTrafo + { + Matrix rotation; // Rotation offset from bone? + Vector trans; // Translation + float scale; // Probably scale (always 1) + }; + struct BoneTrafoCopy + { + Ogre::Quaternion rotation; + Ogre::Vector3 trans; + float scale; + }; + + struct VertWeight + { + short vertex; + float weight; + }; #pragma pack(pop) - struct BoneInfo - { - const BoneTrafo *trafo; - const Vector4 *unknown; - Misc::SliceArray weights; - }; - struct BoneInfoCopy - { - std::string bonename; - unsigned short bonehandle; - BoneTrafoCopy trafo; - Vector4 unknown; - //std::vector weights; - }; - struct IndividualWeight - { - float weight; + struct BoneInfo + { + const BoneTrafo *trafo; + const Vector4 *unknown; + Misc::SliceArray weights; + }; + struct BoneInfoCopy + { + std::string bonename; + unsigned short bonehandle; + BoneTrafoCopy trafo; + Vector4 unknown; + //std::vector weights; + }; + struct IndividualWeight + { + float weight; unsigned int boneinfocopyindex; - }; - - const BoneTrafo *trafo; - std::vector bones; + }; - void read(NIFFile *nif) - { - assert(sizeof(BoneTrafo) == 4*(9+3+1)); - assert(sizeof(VertWeight) == 6); + const BoneTrafo *trafo; + std::vector bones; - trafo = nif->getPtr(); + void read(NIFFile *nif) + { + assert(sizeof(BoneTrafo) == 4*(9+3+1)); + assert(sizeof(VertWeight) == 6); - int boneNum = nif->getInt(); - nif->getInt(); // -1 + trafo = nif->getPtr(); - bones.resize(boneNum); + int boneNum = nif->getInt(); + nif->getInt(); // -1 - for(int i=0;igetPtr(); - bi.unknown = nif->getVector4(); + bi.trafo = nif->getPtr(); + bi.unknown = nif->getVector4(); - // Number of vertex weights - int count = nif->getShort(); - bi.weights = nif->getArrayLen(count); - } - } + // Number of vertex weights + int count = nif->getShort(); + bi.weights = nif->getArrayLen(count); + } + } }; class NiMorphData : public Record { - float startTime; - float stopTime; - std::vector initialVertices; - std::vector > relevantTimes; - std::vector > relevantData; - std::vector > additionalVertices; - + float startTime; + float stopTime; + std::vector initialVertices; + std::vector > relevantTimes; + std::vector > relevantData; + std::vector > additionalVertices; public: - float getStartTime(){ - return startTime; - } - float getStopTime(){ - return stopTime; - } - void setStartTime(float time){ - startTime = time; - } - - void setStopTime(float time){ - stopTime = time; - } - std::vector getInitialVertices(){ - return initialVertices; - } - std::vector > getRelevantData(){ - return relevantData; - } - std::vector > getRelevantTimes(){ - return relevantTimes; - } - std::vector > getAdditionalVertices(){ - return additionalVertices; - } - -void read(NIFFile *nif) - { - int morphCount = nif->getInt(); - int vertCount = nif->getInt(); - nif->getByte(); - int magic = nif->getInt(); - /*int type =*/ nif->getInt(); - for(int i = 0; i < vertCount; i++){ - - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - initialVertices.push_back(Ogre::Vector3(x, y, z)); - } - - for(int i=1; i& getInitialVertices() const + { return initialVertices; } + const std::vector >& getRelevantData() const + { return relevantData; } + const std::vector >& getRelevantTimes() const + { return relevantTimes; } + const std::vector >& getAdditionalVertices() const + { return additionalVertices; } + + void read(NIFFile *nif) { - magic = nif->getInt(); - /*type =*/ nif->getInt(); - std::vector current; - std::vector currentTime; - for(int i = 0; i < magic; i++){ - // Time, data, forward, backward tangents - float time = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - current.push_back(Ogre::Vector3(x,y,z)); - currentTime.push_back(time); - //nif->getFloatLen(4*magic); - } - if(magic){ - relevantData.push_back(current); - relevantTimes.push_back(currentTime); - } - std::vector verts; - for(int i = 0; i < vertCount; i++){ - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - verts.push_back(Ogre::Vector3(x, y, z)); - } - additionalVertices.push_back(verts); - } - } + int morphCount = nif->getInt(); + int vertCount = nif->getInt(); + nif->getByte(); + int magic = nif->getInt(); + /*int type =*/ nif->getInt(); + + for(int i = 0; i < vertCount; i++) + { + float x = nif->getFloat(); + float y = nif->getFloat(); + float z = nif->getFloat(); + initialVertices.push_back(Ogre::Vector3(x, y, z)); + } + + for(int i=1; igetInt(); + /*type =*/ nif->getInt(); + std::vector current; + std::vector currentTime; + for(int i = 0; i < magic; i++) + { + // Time, data, forward, backward tangents + float time = nif->getFloat(); + float x = nif->getFloat(); + float y = nif->getFloat(); + float z = nif->getFloat(); + current.push_back(Ogre::Vector3(x,y,z)); + currentTime.push_back(time); + //nif->getFloatLen(4*magic); + } + + if(magic) + { + relevantData.push_back(current); + relevantTimes.push_back(currentTime); + } + + std::vector verts; + for(int i = 0; i < vertCount; i++) + { + float x = nif->getFloat(); + float y = nif->getFloat(); + float z = nif->getFloat(); + verts.push_back(Ogre::Vector3(x, y, z)); + } + additionalVertices.push_back(verts); + } + } }; class NiKeyframeData : public Record { - std::string bonename; - //Rotations - std::vector quats; - std::vector tbc; - std::vector rottime; - float startTime; - float stopTime; - int rtype; - - //Translations - std::vector translist1; - std::vector translist2; - std::vector translist3; - std::vector transtbc; - std::vector transtime; - int ttype; - - //Scalings - - std::vector scalefactor; - std::vector scaletime; - std::vector forwards; - std::vector backwards; - std::vector tbcscale; - int stype; - - - + std::string bonename; + //Rotations + std::vector quats; + std::vector tbc; + std::vector rottime; + float startTime; + float stopTime; + int rtype; + + //Translations + std::vector translist1; + std::vector translist2; + std::vector translist3; + std::vector transtbc; + std::vector transtime; + int ttype; + + //Scalings + std::vector scalefactor; + std::vector scaletime; + std::vector forwards; + std::vector backwards; + std::vector tbcscale; + int stype; + public: - void clone(NiKeyframeData c) - { - quats = c.getQuat(); - tbc = c.getrTbc(); - rottime = c.getrTime(); + void clone(const NiKeyframeData &c) + { + quats = c.getQuat(); + tbc = c.getrTbc(); + rottime = c.getrTime(); - //types - ttype = c.getTtype(); - rtype = c.getRtype(); - stype = c.getStype(); + //types + ttype = c.getTtype(); + rtype = c.getRtype(); + stype = c.getStype(); - translist1 = c.getTranslist1(); - translist2 = c.getTranslist2(); + translist1 = c.getTranslist1(); + translist2 = c.getTranslist2(); translist3 = c.getTranslist3(); - transtime = c.gettTime(); - - bonename = c.getBonename(); - - - } - - void setBonename(std::string bone) - { - bonename = bone; - } - void setStartTime(float start) - { - startTime = start; - } - void setStopTime(float end) - { - stopTime = end; - } - void read(NIFFile *nif) - { - // Rotations first - int count = nif->getInt(); - //std::vector quat(count); - //std::vector rottime(count); - if(count) - { - - //TYPE1 LINEAR_KEY - //TYPE2 QUADRATIC_KEY - //TYPE3 TBC_KEY - //TYPE4 XYZ_ROTATION_KEY - //TYPE5 UNKNOWN_KEY - rtype = nif->getInt(); - //std::cout << "Count: " << count << "Type: " << type << "\n"; - - if(rtype == 1) - { - //We need to actually read in these values instead of skipping them - //nif->skip(count*4*5); // time + quaternion - for (int i = 0; i < count; i++) { - float time = nif->getFloat(); - float w = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - Ogre::Quaternion quat = Ogre::Quaternion(Ogre::Real(w), Ogre::Real(x), Ogre::Real(y), Ogre::Real(z)); - quats.push_back(quat); - rottime.push_back(time); - //if(time == 0.0 || time > 355.5) - // std::cout <<"Time:" << time << "W:" << w <<"X:" << x << "Y:" << y << "Z:" << z << "\n"; - } - } - else if(rtype == 3) - { //Example - node 116 in base_anim.nif - for (int i = 0; i < count; i++) { - float time = nif->getFloat(); - float w = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - - float tbcx = nif->getFloat(); - float tbcy = nif->getFloat(); - float tbcz = nif->getFloat(); - Ogre::Quaternion quat = Ogre::Quaternion(Ogre::Real(w), Ogre::Real(x), Ogre::Real(y), Ogre::Real(z)); - Ogre::Vector3 vec = Ogre::Vector3(tbcx, tbcy, tbcz); - quats.push_back(quat); - rottime.push_back(time); - tbc.push_back(vec); - //if(time == 0.0 || time > 355.5) - // std::cout <<"Time:" << time << "W:" << w <<"X:" << x << "Y:" << y << "Z:" << z << "\n"; - } - - //nif->skip(count*4*8); // rot1 + tension+bias+continuity - } - else if(rtype == 4) - { - for(int j=0;jgetFloat(); // time - for(int i=0; i<3; i++) - { - int cnt = nif->getInt(); - int type = nif->getInt(); - if(type == 1) - nif->skip(cnt*4*2); // time + unknown - else if(type == 2) - nif->skip(cnt*4*4); // time + unknown vector - else nif->fail("Unknown sub-rotation type"); - } - } - } - else nif->fail("Unknown rotation type in NiKeyframeData"); - } - //first = false; - - // Then translation - count = nif->getInt(); - - if(count) - { - ttype = nif->getInt(); - - //std::cout << "TransCount:" << count << " Type: " << type << "\n"; - if(ttype == 1) { - for (int i = 0; i < count; i++) { - float time = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - Ogre::Vector3 trans = Ogre::Vector3(x, y, z); - translist1.push_back(trans); - transtime.push_back(time); - } - //nif->getFloatLen(count*4); // time + translation - } - else if(ttype == 2) - { //Example - node 116 in base_anim.nif - for (int i = 0; i < count; i++) { - float time = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - float x2 = nif->getFloat(); - float y2 = nif->getFloat(); - float z2 = nif->getFloat(); - float x3 = nif->getFloat(); - float y3 = nif->getFloat(); - float z3 = nif->getFloat(); - Ogre::Vector3 trans = Ogre::Vector3(x, y, z); - Ogre::Vector3 trans2 = Ogre::Vector3(x2, y2, z2); - Ogre::Vector3 trans3 = Ogre::Vector3(x3, y3, z3); - transtime.push_back(time); - translist1.push_back(trans); - translist2.push_back(trans2); - translist3.push_back(trans3); - } - - //nif->getFloatLen(count*10); // trans1 + forward + backward - } - else if(ttype == 3){ - for (int i = 0; i < count; i++) { - float time = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - float t = nif->getFloat(); - float b = nif->getFloat(); - float c = nif->getFloat(); - Ogre::Vector3 trans = Ogre::Vector3(x, y, z); - Ogre::Vector3 tbc = Ogre::Vector3(t, b, c); - translist1.push_back(trans); - transtbc.push_back(tbc); - transtime.push_back(time); - } - //nif->getFloatLen(count*7); // trans1 + tension,bias,continuity - } - else nif->fail("Unknown translation type"); - } - - // Finally, scalings - count = nif->getInt(); - if(count) - { - stype = nif->getInt(); - - - for(int i = 0; i < count; i++){ - - - //int size = 0; - if(stype >= 1 && stype < 4) - { - float time = nif->getFloat(); - float scale = nif->getFloat(); - scaletime.push_back(time); - scalefactor.push_back(scale); - //size = 2; // time+scale - } - else nif->fail("Unknown scaling type"); - if(stype == 2){ - //size = 4; // 1 + forward + backward (floats) - float forward = nif->getFloat(); - float backward = nif->getFloat(); - forwards.push_back(forward); - backwards.push_back(backward); - } - else if(stype == 3){ - float tbcx = nif->getFloat(); - float tbcy = nif->getFloat(); - float tbcz = nif->getFloat(); - Ogre::Vector3 vec = Ogre::Vector3(tbcx, tbcy, tbcz); - tbcscale.push_back(vec); - - //size = 5; // 1 + tbc - } - - } - } - else - stype = 0; - } - int getRtype(){ - return rtype; - } - int getStype(){ - return stype; - } - int getTtype(){ - return ttype; - } - float getStartTime(){ - return startTime; - } - float getStopTime(){ - return stopTime; - } - std::vector getQuat(){ - return quats; - } - std::vector getrTbc(){ - return tbc; - } - std::vector getrTime(){ - return rottime; - } - - std::vector getTranslist1(){ - return translist1; - } - std::vector getTranslist2(){ - return translist2; - } - std::vector getTranslist3(){ - return translist3; - } - std::vector gettTime(){ - return transtime; - } - std::vector getScalefactor(){ - return scalefactor; - } - std::vector getForwards(){ - return forwards; - } - std::vector getBackwards(){ - return backwards; - } - std::vector getScaleTbc(){ - return tbcscale; - } - - std::vector getsTime(){ - return scaletime; - } - std::string getBonename(){ return bonename; - } + transtime = c.gettTime(); + bonename = c.getBonename(); + } + void setBonename(std::string bone) + { bonename = bone; } + void setStartTime(float start) + { startTime = start; } + void setStopTime(float end) + { stopTime = end; } + + void read(NIFFile *nif) + { + // Rotations first + int count = nif->getInt(); + //std::vector quat(count); + //std::vector rottime(count); + if(count) + { + //TYPE1 LINEAR_KEY + //TYPE2 QUADRATIC_KEY + //TYPE3 TBC_KEY + //TYPE4 XYZ_ROTATION_KEY + //TYPE5 UNKNOWN_KEY + rtype = nif->getInt(); + //std::cout << "Count: " << count << "Type: " << type << "\n"; + + if(rtype == 1) + { + //We need to actually read in these values instead of skipping them + //nif->skip(count*4*5); // time + quaternion + for (int i = 0; i < count; i++) + { + float time = nif->getFloat(); + float w = nif->getFloat(); + float x = nif->getFloat(); + float y = nif->getFloat(); + float z = nif->getFloat(); + Ogre::Quaternion quat = Ogre::Quaternion(Ogre::Real(w), Ogre::Real(x), Ogre::Real(y), Ogre::Real(z)); + quats.push_back(quat); + rottime.push_back(time); + //if(time == 0.0 || time > 355.5) + // std::cout <<"Time:" << time << "W:" << w <<"X:" << x << "Y:" << y << "Z:" << z << "\n"; + } + } + else if(rtype == 3) + { + //Example - node 116 in base_anim.nif + for (int i = 0; i < count; i++) + { + float time = nif->getFloat(); + float w = nif->getFloat(); + float x = nif->getFloat(); + float y = nif->getFloat(); + float z = nif->getFloat(); + + float tbcx = nif->getFloat(); + float tbcy = nif->getFloat(); + float tbcz = nif->getFloat(); + + Ogre::Quaternion quat = Ogre::Quaternion(Ogre::Real(w), Ogre::Real(x), Ogre::Real(y), Ogre::Real(z)); + Ogre::Vector3 vec = Ogre::Vector3(tbcx, tbcy, tbcz); + quats.push_back(quat); + rottime.push_back(time); + tbc.push_back(vec); + //if(time == 0.0 || time > 355.5) + // std::cout <<"Time:" << time << "W:" << w <<"X:" << x << "Y:" << y << "Z:" << z << "\n"; + } + } + else if(rtype == 4) + { + for(int j=0;jgetFloat(); // time + for(int i=0; i<3; i++) + { + int cnt = nif->getInt(); + int type = nif->getInt(); + if(type == 1) + nif->skip(cnt*4*2); // time + unknown + else if(type == 2) + nif->skip(cnt*4*4); // time + unknown vector + else + nif->fail("Unknown sub-rotation type"); + } + } + } + else + nif->fail("Unknown rotation type in NiKeyframeData"); + } + //first = false; + + // Then translation + count = nif->getInt(); + if(count) + { + ttype = nif->getInt(); + + //std::cout << "TransCount:" << count << " Type: " << type << "\n"; + if(ttype == 1) + { + for(int i = 0; i < count; i++) + { + float time = nif->getFloat(); + float x = nif->getFloat(); + float y = nif->getFloat(); + float z = nif->getFloat(); + + Ogre::Vector3 trans = Ogre::Vector3(x, y, z); + translist1.push_back(trans); + transtime.push_back(time); + } + //nif->getFloatLen(count*4); // time + translation + } + else if(ttype == 2) + { + //Example - node 116 in base_anim.nif + for(int i = 0; i < count; i++) + { + float time = nif->getFloat(); + float x = nif->getFloat(); + float y = nif->getFloat(); + float z = nif->getFloat(); + float x2 = nif->getFloat(); + float y2 = nif->getFloat(); + float z2 = nif->getFloat(); + float x3 = nif->getFloat(); + float y3 = nif->getFloat(); + float z3 = nif->getFloat(); + + Ogre::Vector3 trans = Ogre::Vector3(x, y, z); + Ogre::Vector3 trans2 = Ogre::Vector3(x2, y2, z2); + Ogre::Vector3 trans3 = Ogre::Vector3(x3, y3, z3); + transtime.push_back(time); + translist1.push_back(trans); + translist2.push_back(trans2); + translist3.push_back(trans3); + } + + //nif->getFloatLen(count*10); // trans1 + forward + backward + } + else if(ttype == 3) + { + for(int i = 0; i < count; i++) + { + float time = nif->getFloat(); + float x = nif->getFloat(); + float y = nif->getFloat(); + float z = nif->getFloat(); + float t = nif->getFloat(); + float b = nif->getFloat(); + float c = nif->getFloat(); + Ogre::Vector3 trans = Ogre::Vector3(x, y, z); + Ogre::Vector3 tbc = Ogre::Vector3(t, b, c); + translist1.push_back(trans); + transtbc.push_back(tbc); + transtime.push_back(time); + } + //nif->getFloatLen(count*7); // trans1 + tension,bias,continuity + } + else nif->fail("Unknown translation type"); + } + + // Finally, scalings + count = nif->getInt(); + if(count) + { + stype = nif->getInt(); + + for(int i = 0; i < count; i++) + { + //int size = 0; + if(stype >= 1 && stype < 4) + { + float time = nif->getFloat(); + float scale = nif->getFloat(); + scaletime.push_back(time); + scalefactor.push_back(scale); + //size = 2; // time+scale + } + else + nif->fail("Unknown scaling type"); + + if(stype == 2) + { + //size = 4; // 1 + forward + backward (floats) + float forward = nif->getFloat(); + float backward = nif->getFloat(); + forwards.push_back(forward); + backwards.push_back(backward); + } + else if(stype == 3) + { + //size = 5; // 1 + tbc + float tbcx = nif->getFloat(); + float tbcy = nif->getFloat(); + float tbcz = nif->getFloat(); + Ogre::Vector3 vec = Ogre::Vector3(tbcx, tbcy, tbcz); + tbcscale.push_back(vec); + } + } + } + else + stype = 0; + } + + int getRtype() const + { return rtype; } + int getStype() const + { return stype; } + int getTtype() const + { return ttype; } + float getStartTime() const + { return startTime; } + float getStopTime() const + { return stopTime; } + const std::vector& getQuat() const + { return quats; } + const std::vector& getrTbc() const + { return tbc; } + const std::vector& getrTime() const + { return rottime; } + + const std::vector& getTranslist1() const + { return translist1; } + const std::vector& getTranslist2() const + { return translist2; } + const std::vector& getTranslist3() const + { return translist3; } + const std::vector& gettTime() const + { return transtime; } + const std::vector& getScalefactor() const + { return scalefactor; } + const std::vector& getForwards() const + { return forwards; } + const std::vector& getBackwards() const + { return backwards; } + const std::vector& getScaleTbc() const + { return tbcscale; } + + const std::vector& getsTime() const + { return scaletime; } + const std::string& getBonename() const + { return bonename; } }; } // Namespace diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index b0cc64228..bac412c76 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -35,57 +35,62 @@ typedef Node Effect; // NiPointLight and NiSpotLight? struct NiLight : Effect { - struct SLight - { - float dimmer; - Vector ambient; - Vector diffuse; - Vector specular; - }; - - const SLight *light; - - void read(NIFFile *nif) - { - Effect::read(nif); - - nif->getInt(); // 1 - nif->getInt(); // 1? - light = nif->getPtr(); - } + struct SLight + { + float dimmer; + Vector ambient; + Vector diffuse; + Vector specular; + }; + const SLight *light; + + void read(NIFFile *nif) + { + Effect::read(nif); + + nif->getInt(); // 1 + nif->getInt(); // 1? + light = nif->getPtr(); + } }; struct NiTextureEffect : Effect { - NiSourceTexturePtr texture; - - void read(NIFFile *nif) - { - Effect::read(nif); - - int tmp = nif->getInt(); - if(tmp) nif->getInt(); // always 1? - - /* - 3 x Vector4 = [1,0,0,0] - int = 2 - int = 0 or 3 - int = 2 - int = 2 - */ - nif->skip(16*4); - - texture.read(nif); - - /* - byte = 0 - vector4 = [1,0,0,0] - short = 0 - short = -75 - short = 0 - */ - nif->skip(23); - } + NiSourceTexturePtr texture; + + void read(NIFFile *nif) + { + Effect::read(nif); + + int tmp = nif->getInt(); + if(tmp) nif->getInt(); // always 1? + + /* + 3 x Vector4 = [1,0,0,0] + int = 2 + int = 0 or 3 + int = 2 + int = 2 + */ + nif->skip(16*4); + + texture.read(nif); + + /* + byte = 0 + vector4 = [1,0,0,0] + short = 0 + short = -75 + short = 0 + */ + nif->skip(23); + } + + void post(NIFFile *nif) + { + Effect::post(nif); + texture.post(nif); + } }; } // Namespace diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index eec1aa7b4..ad788661a 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -38,70 +38,70 @@ namespace Nif class Extra : public Record { public: - ExtraPtr extra; + ExtraPtr extra; - void read(NIFFile *nif) { extra.read(nif); } + void read(NIFFile *nif) { extra.read(nif); } + void post(NIFFile *nif) { extra.post(nif); } }; class NiVertWeightsExtraData : public Extra { public: - void read(NIFFile *nif) - { - Extra::read(nif); + void read(NIFFile *nif) + { + Extra::read(nif); - // We should have s*4+2 == i, for some reason. Might simply be the - // size of the rest of the record, unhelpful as that may be. - /*int i =*/ nif->getInt(); - int s = nif->getShort(); // number of vertices + // We should have s*4+2 == i, for some reason. Might simply be the + // size of the rest of the record, unhelpful as that may be. + /*int i =*/ nif->getInt(); + int s = nif->getShort(); // number of vertices - nif->getFloatLen(s); // vertex weights I guess - } + nif->getFloatLen(s); // vertex weights I guess + } }; class NiTextKeyExtraData : public Extra { public: - struct TextKey - { - float time; - Misc::SString text; - }; - - std::vector list; - - void read(NIFFile *nif) - { - Extra::read(nif); - - nif->getInt(); // 0 - - int keynum = nif->getInt(); - list.resize(keynum); - for(int i=0; igetFloat(); - list[i].text = nif->getString(); - } - } + struct TextKey + { + float time; + Misc::SString text; + }; + std::vector list; + + void read(NIFFile *nif) + { + Extra::read(nif); + + nif->getInt(); // 0 + + int keynum = nif->getInt(); + list.resize(keynum); + for(int i=0; igetFloat(); + list[i].text = nif->getString(); + } + } }; class NiStringExtraData : public Extra { public: - /* Two known meanings: - "MRK" - marker, only visible in the editor, not rendered in-game - "NCO" - no collision - */ - Misc::SString string; - - void read(NIFFile *nif) - { - Extra::read(nif); - - nif->getInt(); // size of string + 4. Really useful... - string = nif->getString(); - } + /* Two known meanings: + "MRK" - marker, only visible in the editor, not rendered in-game + "NCO" - no collision + */ + Misc::SString string; + + void read(NIFFile *nif) + { + Extra::read(nif); + + nif->getInt(); // size of string + 4. Really useful... + string = nif->getString(); + } }; } // Namespace diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 80ea7a0b7..48dd76510 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -190,17 +190,23 @@ void NIFFile::parse() void NiSkinInstance::post(NIFFile *nif) { - int bnum = bones.length(); - if(bnum != static_cast (data->bones.size())) - nif->fail("Mismatch in NiSkinData bone count"); + data.post(nif); + root.post(nif); + bones.post(nif); - root->makeRootBone(data->trafo); + if(data.empty() || root.empty()) + nif->fail("NiSkinInstance missing root or data"); - for(int i=0; ifail("Oops: Missing bone! Don't know how to handle this."); + size_t bnum = bones.length(); + if(bnum != data->bones.size()) + nif->fail("Mismatch in NiSkinData bone count"); + + root->makeRootBone(data->trafo); - bones[i].makeBone(i, data->bones[i]); + for(int i=0; ifail("Oops: Missing bone! Don't know how to handle this."); + bones[i].makeBone(i, data->bones[i]); } } diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 951ae1f75..bb6f73259 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -43,113 +43,107 @@ namespace Nif class NIFFile { - enum NIFVersion - { - VER_MW = 0x04000002 // Morrowind NIFs + enum NIFVersion { + VER_MW = 0x04000002 // Morrowind NIFs }; - /// Nif file version - int ver; + /// Nif file version + int ver; - /// Input stream - StreamPtr inp; + /// Input stream + StreamPtr inp; - /// File name, used for error messages - std::string filename; + /// File name, used for error messages + std::string filename; - /// Record list - std::vector records; + /// Record list + std::vector records; - /// Parse the file - void parse(); + /// Parse the file + void parse(); - public: - /// Used for error handling - void fail(const std::string &msg) +public: + /// Used for error handling + void fail(const std::string &msg) { - std::string err = "NIFFile Error: " + msg; - err += "\nFile: " + filename; - throw std::runtime_error(err); + std::string err = "NIFFile Error: " + msg; + err += "\nFile: " + filename; + throw std::runtime_error(err); } - /// Open a NIF stream. The name is used for error messages. - NIFFile(StreamPtr nif, const std::string &name) - : filename(name) + /// Open a NIF stream. The name is used for error messages. + NIFFile(StreamPtr nif, const std::string &name) + : filename(name) { - /* Load the entire file into memory. This allows us to use - direct pointers to the data arrays in the NIF, instead of - individually allocating and copying each one. - - The NIF data is only stored temporarily in memory, since once - the mesh data is loaded it is siphoned into OGRE and - deleted. For that reason, we might improve this further and - use a shared region/pool based allocation scheme in the - future, especially since only one NIFFile will ever be loaded - at any given time. - */ - inp = StreamPtr(new BufferStream(nif)); - - parse(); + /* Load the entire file into memory. This allows us to use + direct pointers to the data arrays in the NIF, instead of + individually allocating and copying each one. + + The NIF data is only stored temporarily in memory, since once + the mesh data is loaded it is siphoned into OGRE and + deleted. For that reason, we might improve this further and + use a shared region/pool based allocation scheme in the + future, especially since only one NIFFile will ever be loaded + at any given time. + */ + inp = StreamPtr(new BufferStream(nif)); + + parse(); } - ~NIFFile() + ~NIFFile() { - for(std::size_t i=0; i= 0 && index < static_cast (records.size())); - Record *res = records[index]; - assert(res != NULL); - return res; - } - - /// Number of records - int numRecords() { return records.size(); } + /// Get a given record + Record *getRecord(size_t index) + { + Record *res = records.at(index); + assert(res != NULL); + return res; + } - /* ************************************************ + /// Number of records + int numRecords() { return records.size(); } + /************************************************* Parser functions + ****************************************************/ - ****************************************************/ - - void skip(size_t size) { inp->getPtr(size); } + void skip(size_t size) { inp->getPtr(size); } - template const X* getPtr() { return (const X*)inp->getPtr(sizeof(X)); } - template X getType() { return *getPtr(); } - unsigned short getShort() { return getType(); } - int getInt() { return getType(); } - float getFloat() { return getType(); } - char getByte() { return getType(); } + template const X* getPtr() { return (const X*)inp->getPtr(sizeof(X)); } + template X getType() { return *getPtr(); } + unsigned short getShort() { return getType(); } + int getInt() { return getType(); } + float getFloat() { return getType(); } + char getByte() { return getType(); } - template - Misc::SliceArray getArrayLen(int num) + template + Misc::SliceArray getArrayLen(int num) { return Misc::SliceArray((const X*)inp->getPtr(num*sizeof(X)),num); } - template - Misc::SliceArray getArray() + template + Misc::SliceArray getArray() { - int len = getInt(); - return getArrayLen(len); + int len = getInt(); + return getArrayLen(len); } - Misc::SString getString() { return getArray(); } + Misc::SString getString() { return getArray(); } - const Vector *getVector() { return getPtr(); } - const Matrix *getMatrix() { return getPtr(); } - const Transformation *getTrafo() { return getPtr(); } - const Vector4 *getVector4() { return getPtr(); } + const Vector *getVector() { return getPtr(); } + const Matrix *getMatrix() { return getPtr(); } + const Transformation *getTrafo() { return getPtr(); } + const Vector4 *getVector4() { return getPtr(); } - Misc::FloatArray getFloatLen(int num) + Misc::FloatArray getFloatLen(int num) { return getArrayLen(num); } - // For fixed-size strings where you already know the size - const char *getString(int size) + // For fixed-size strings where you already know the size + const char *getString(int size) { return (const char*)inp->getPtr(size); } }; diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 080042746..fe9d10c7a 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -26,6 +26,7 @@ #include "controlled.hpp" #include "data.hpp" +#include "property.hpp" namespace Nif { @@ -37,191 +38,220 @@ namespace Nif class Node : public Named { public: - // Node flags. Interpretation depends somewhat on the type of node. - int flags; - const Transformation *trafo; - PropertyList props; - - // Bounding box info - bool hasBounds; - const Vector *boundPos; - const Matrix *boundRot; - const Vector *boundXYZ; // Box size - - void read(NIFFile *nif) - { - Named::read(nif); - - flags = nif->getShort(); - trafo = nif->getTrafo(); - props.read(nif); - - hasBounds = !!nif->getInt(); - if(hasBounds) - { - nif->getInt(); // always 1 - boundPos = nif->getVector(); - boundRot = nif->getMatrix(); - boundXYZ = nif->getVector(); - } - - boneTrafo = NULL; - boneIndex = -1; - } - - // Bone transformation. If set, node is a part of a skeleton. - const NiSkinData::BoneTrafo *boneTrafo; - - // Bone weight info, from NiSkinData - const NiSkinData::BoneInfo *boneInfo; - - // Bone index. If -1, this node is either not a bone, or if - // boneTrafo is set it is the root bone in the skeleton. - short boneIndex; - - void makeRootBone(const NiSkinData::BoneTrafo *tr) - { - boneTrafo = tr; - boneIndex = -1; - } - - void makeBone(short ind, const NiSkinData::BoneInfo &bi) - { - boneInfo = &bi; - boneTrafo = bi.trafo; - boneIndex = ind; - } + // Node flags. Interpretation depends somewhat on the type of node. + int flags; + const Transformation *trafo; + PropertyList props; + + // Bounding box info + bool hasBounds; + const Vector *boundPos; + const Matrix *boundRot; + const Vector *boundXYZ; // Box size + + void read(NIFFile *nif) + { + Named::read(nif); + + flags = nif->getShort(); + trafo = nif->getTrafo(); + props.read(nif); + + hasBounds = !!nif->getInt(); + if(hasBounds) + { + nif->getInt(); // always 1 + boundPos = nif->getVector(); + boundRot = nif->getMatrix(); + boundXYZ = nif->getVector(); + } + + boneTrafo = NULL; + boneIndex = -1; + } + + void post(NIFFile *nif) + { + Named::post(nif); + props.post(nif); + } + + // Bone transformation. If set, node is a part of a skeleton. + const NiSkinData::BoneTrafo *boneTrafo; + + // Bone weight info, from NiSkinData + const NiSkinData::BoneInfo *boneInfo; + + // Bone index. If -1, this node is either not a bone, or if + // boneTrafo is set it is the root bone in the skeleton. + short boneIndex; + + void makeRootBone(const NiSkinData::BoneTrafo *tr) + { + boneTrafo = tr; + boneIndex = -1; + } + + void makeBone(short ind, const NiSkinData::BoneInfo &bi) + { + boneInfo = &bi; + boneTrafo = bi.trafo; + boneIndex = ind; + } }; struct NiTriShapeCopy { - std::string sname; - std::vector boneSequence; - Nif::NiSkinData::BoneTrafoCopy trafo; - //Ogre::Quaternion initialBoneRotation; - //Ogre::Vector3 initialBoneTranslation; - std::vector vertices; - std::vector normals; - std::vector boneinfo; - std::map > vertsToWeights; - Nif::NiMorphData morph; + std::string sname; + std::vector boneSequence; + Nif::NiSkinData::BoneTrafoCopy trafo; + //Ogre::Quaternion initialBoneRotation; + //Ogre::Vector3 initialBoneTranslation; + std::vector vertices; + std::vector normals; + std::vector boneinfo; + std::map > vertsToWeights; + Nif::NiMorphData morph; }; struct NiNode : Node { - NodeList children; - NodeList effects; - - /* Known NiNode flags: - - 0x01 hidden - 0x02 use mesh for collision - 0x04 use bounding box for collision (?) - 0x08 unknown, but common - 0x20, 0x40, 0x80 unknown - */ - - void read(NIFFile *nif) - { - Node::read(nif); - children.read(nif); - effects.read(nif); - } + NodeList children; + NodeList effects; + + /* Known NiNode flags: + 0x01 hidden + 0x02 use mesh for collision + 0x04 use bounding box for collision (?) + 0x08 unknown, but common + 0x20, 0x40, 0x80 unknown + */ + + void read(NIFFile *nif) + { + Node::read(nif); + children.read(nif); + effects.read(nif); + } + + void post(NIFFile *nif) + { + Node::post(nif); + children.post(nif); + effects.post(nif); + } }; struct NiTriShape : Node { - /* Possible flags: - 0x40 - mesh has no vertex normals ? - - Only flags included in 0x47 (ie. 0x01, 0x02, 0x04 and 0x40) have - been observed so far. - */ - - NiTriShapeDataPtr data; - NiSkinInstancePtr skin; - - void read(NIFFile *nif) - { - Node::read(nif); - data.read(nif); - skin.read(nif); - } - - NiTriShapeCopy clone(){ - NiTriShapeCopy copy; - copy.sname = name.toString(); - float *ptr = (float*)data->vertices.ptr; - float *ptrNormals = (float*)data->normals.ptr; - int numVerts = data->vertices.length / 3; - for(int i = 0; i < numVerts; i++) - { - float *current = (float*) (ptr + i * 3); - copy.vertices.push_back(Ogre::Vector3(*current, *(current + 1), *(current + 2))); - - if(ptrNormals){ - float *currentNormals = (float*) (ptrNormals + i * 3); - copy.normals.push_back(Ogre::Vector3(*currentNormals, *(currentNormals + 1), *(currentNormals + 2))); - } - } - - - return copy; - } + /* Possible flags: + 0x40 - mesh has no vertex normals ? + + Only flags included in 0x47 (ie. 0x01, 0x02, 0x04 and 0x40) have + been observed so far. + */ + + NiTriShapeDataPtr data; + NiSkinInstancePtr skin; + + void read(NIFFile *nif) + { + Node::read(nif); + data.read(nif); + skin.read(nif); + } + + void post(NIFFile *nif) + { + Node::post(nif); + data.post(nif); + skin.post(nif); + } + + NiTriShapeCopy clone() + { + NiTriShapeCopy copy; + copy.sname = name.toString(); + float *ptr = (float*)data->vertices.ptr; + float *ptrNormals = (float*)data->normals.ptr; + int numVerts = data->vertices.length / 3; + for(int i = 0; i < numVerts; i++) + { + float *current = (float*) (ptr + i * 3); + copy.vertices.push_back(Ogre::Vector3(*current, *(current + 1), *(current + 2))); + + if(ptrNormals) + { + float *currentNormals = (float*) (ptrNormals + i * 3); + copy.normals.push_back(Ogre::Vector3(*currentNormals, *(currentNormals + 1), *(currentNormals + 2))); + } + } + + return copy; + } }; struct NiCamera : Node { - struct Camera - { - // Camera frustrum - float left, right, top, bottom, nearDist, farDist; + struct Camera + { + // Camera frustrum + float left, right, top, bottom, nearDist, farDist; - // Viewport - float vleft, vright, vtop, vbottom; + // Viewport + float vleft, vright, vtop, vbottom; - // Level of detail modifier - float LOD; - }; + // Level of detail modifier + float LOD; + }; + const Camera *cam; - const Camera *cam; + void read(NIFFile *nif) + { + Node::read(nif); - void read(NIFFile *nif) - { - Node::read(nif); + nif->getPtr(); - nif->getPtr(); - - nif->getInt(); // -1 - nif->getInt(); // 0 - } + nif->getInt(); // -1 + nif->getInt(); // 0 + } }; struct NiAutoNormalParticles : Node { - NiAutoNormalParticlesDataPtr data; - - void read(NIFFile *nif) - { - Node::read(nif); - data.read(nif); - nif->getInt(); // -1 - } + NiAutoNormalParticlesDataPtr data; + + void read(NIFFile *nif) + { + Node::read(nif); + data.read(nif); + nif->getInt(); // -1 + } + + void post(NIFFile *nif) + { + Node::post(nif); + data.post(nif); + } }; struct NiRotatingParticles : Node { - NiRotatingParticlesDataPtr data; - - void read(NIFFile *nif) - { - Node::read(nif); - data.read(nif); - nif->getInt(); // -1 - } + NiRotatingParticlesDataPtr data; + + void read(NIFFile *nif) + { + Node::read(nif); + data.read(nif); + nif->getInt(); // -1 + } + + void post(NIFFile *nif) + { + Node::post(nif); + data.post(nif); + } }; - - } // Namespace #endif diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 1a16854af..619e3db0e 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -32,104 +32,116 @@ namespace Nif class Property : public Named { public: - // The meaning of these depends on the actual property type. - int flags; - - void read(NIFFile *nif) - { - Named::read(nif); - flags = nif->getShort(); - } + // The meaning of these depends on the actual property type. + int flags; + + void read(NIFFile *nif) + { + Named::read(nif); + flags = nif->getShort(); + } }; class NiTexturingProperty : public Property { public: - // A sub-texture - struct Texture - { - /* Clamp mode - 0 - clampS clampT - 1 - clampS wrapT - 2 - wrapS clampT - 3 - wrapS wrapT - */ - - /* Filter: - 0 - nearest - 1 - bilinear - 2 - trilinear - 3, 4, 5 - who knows + // A sub-texture + struct Texture + { + /* Clamp mode + 0 - clampS clampT + 1 - clampS wrapT + 2 - wrapS clampT + 3 - wrapS wrapT + */ + + /* Filter: + 0 - nearest + 1 - bilinear + 2 - trilinear + 3, 4, 5 - who knows + */ + bool inUse; + NiSourceTexturePtr texture; + + int clamp, set, filter; + short unknown2; + + void read(NIFFile *nif) + { + inUse = !!nif->getInt(); + if(!inUse) return; + + texture.read(nif); + clamp = nif->getInt(); + filter = nif->getInt(); + set = nif->getInt(); + + // I have no idea, but I think these are actually two + // PS2-specific shorts (ps2L and ps2K), followed by an unknown + // short. + nif->skip(6); + } + + void post(NIFFile *nif) + { + texture.post(nif); + } + }; + + /* Apply mode: + 0 - replace + 1 - decal + 2 - modulate + 3 - hilight // These two are for PS2 only? + 4 - hilight2 */ - bool inUse; - NiSourceTexturePtr texture; - - int clamp, set, filter; - short unknown2; + int apply; + + /* + * The textures in this list are as follows: + * + * 0 - Base texture + * 1 - Dark texture + * 2 - Detail texture + * 3 - Gloss texture (never used?) + * 4 - Glow texture + * 5 - Bump map texture + * 6 - Decal texture + */ + Texture textures[7]; void read(NIFFile *nif) { - inUse = !!nif->getInt(); - if(!inUse) return; - - texture.read(nif); - clamp = nif->getInt(); - filter = nif->getInt(); - set = nif->getInt(); - - // I have no idea, but I think these are actually two - // PS2-specific shorts (ps2L and ps2K), followed by an unknown - // short. - nif->skip(6); + Property::read(nif); + apply = nif->getInt(); + + // Unknown, always 7. Probably the number of textures to read + // below + nif->getInt(); + + textures[0].read(nif); // Base + textures[1].read(nif); // Dark + textures[2].read(nif); // Detail + textures[3].read(nif); // Gloss (never present) + textures[4].read(nif); // Glow + textures[5].read(nif); // Bump map + if(textures[5].inUse) + { + // Ignore these at the moment + /*float lumaScale =*/ nif->getFloat(); + /*float lumaOffset =*/ nif->getFloat(); + /*const Vector4 *lumaMatrix =*/ nif->getVector4(); + } + textures[6].read(nif); // Decal + } + + void post(NIFFile *nif) + { + Property::post(nif); + for(int i = 0;i < 7;i++) + textures[i].post(nif); } - }; - - /* Apply mode: - 0 - replace - 1 - decal - 2 - modulate - 3 - hilight // These two are for PS2 only? - 4 - hilight2 - */ - int apply; - - /* - * The textures in this list are as follows: - * - * 0 - Base texture - * 1 - Dark texture - * 2 - Detail texture - * 3 - Gloss texture (never used?) - * 4 - Glow texture - * 5 - Bump map texture - * 6 - Decal texture - */ - Texture textures[7]; - - void read(NIFFile *nif) - { - Property::read(nif); - apply = nif->getInt(); - - // Unknown, always 7. Probably the number of textures to read - // below - nif->getInt(); - - textures[0].read(nif); // Base - textures[1].read(nif); // Dark - textures[2].read(nif); // Detail - textures[3].read(nif); // Gloss (never present) - textures[4].read(nif); // Glow - textures[5].read(nif); // Bump map - if(textures[5].inUse) - { - // Ignore these at the moment - /*float lumaScale =*/ nif->getFloat(); - /*float lumaOffset =*/ nif->getFloat(); - /*const Vector4 *lumaMatrix =*/ nif->getVector4(); - } - textures[6].read(nif); // Decal - } }; // These contain no other data than the 'flags' field in Property @@ -140,88 +152,88 @@ typedef Property NiSpecularProperty; typedef Property NiWireframeProperty; // The rest are all struct-based -template +template struct StructPropT : Property { - const Struct* data; + const T* data; - void read(NIFFile *nif) - { - Property::read(nif); - data = nif->getPtr(); - } + void read(NIFFile *nif) + { + Property::read(nif); + data = nif->getPtr(); + } }; struct S_MaterialProperty { - // The vector components are R,G,B - Vector ambient, diffuse, specular, emissive; - float glossiness, alpha; + // The vector components are R,G,B + Vector ambient, diffuse, specular, emissive; + float glossiness, alpha; }; struct S_VertexColorProperty { - /* Vertex mode: - 0 - source ignore - 1 - source emmisive - 2 - source amb diff - - Lighting mode - 0 - lighting emmisive - 1 - lighting emmisive ambient/diffuse - */ - int vertmode, lightmode; + /* Vertex mode: + 0 - source ignore + 1 - source emmisive + 2 - source amb diff + + Lighting mode + 0 - lighting emmisive + 1 - lighting emmisive ambient/diffuse + */ + int vertmode, lightmode; }; struct S_AlphaProperty { - /* - In NiAlphaProperty, the flags have the following meaning: - - Bit 0 : alpha blending enable - Bits 1-4 : source blend mode - Bits 5-8 : destination blend mode - Bit 9 : alpha test enable - Bit 10-12 : alpha test mode - Bit 13 : no sorter flag ( disables triangle sorting ) - - blend modes (glBlendFunc): - 0000 GL_ONE - 0001 GL_ZERO - 0010 GL_SRC_COLOR - 0011 GL_ONE_MINUS_SRC_COLOR - 0100 GL_DST_COLOR - 0101 GL_ONE_MINUS_DST_COLOR - 0110 GL_SRC_ALPHA - 0111 GL_ONE_MINUS_SRC_ALPHA - 1000 GL_DST_ALPHA - 1001 GL_ONE_MINUS_DST_ALPHA - 1010 GL_SRC_ALPHA_SATURATE - - test modes (glAlphaFunc): - 000 GL_ALWAYS - 001 GL_LESS - 010 GL_EQUAL - 011 GL_LEQUAL - 100 GL_GREATER - 101 GL_NOTEQUAL - 110 GL_GEQUAL - 111 GL_NEVER - - Taken from: - http://niftools.sourceforge.net/doc/nif/NiAlphaProperty.html - - Right now we only use standard alpha blending (see the Ogre code - that sets it up) and it appears that this is the only blending - used in the original game. Bloodmoon (along with several mods) do - however use other settings, such as discarding pixel values with - alpha < 1.0. This is faster because we don't have to mess with the - depth stuff like we did for blending. And OGRE has settings for - this too. - */ - - // Tested against when certain flags are set (see above.) - unsigned char threshold; + /* + In NiAlphaProperty, the flags have the following meaning: + + Bit 0 : alpha blending enable + Bits 1-4 : source blend mode + Bits 5-8 : destination blend mode + Bit 9 : alpha test enable + Bit 10-12 : alpha test mode + Bit 13 : no sorter flag ( disables triangle sorting ) + + blend modes (glBlendFunc): + 0000 GL_ONE + 0001 GL_ZERO + 0010 GL_SRC_COLOR + 0011 GL_ONE_MINUS_SRC_COLOR + 0100 GL_DST_COLOR + 0101 GL_ONE_MINUS_DST_COLOR + 0110 GL_SRC_ALPHA + 0111 GL_ONE_MINUS_SRC_ALPHA + 1000 GL_DST_ALPHA + 1001 GL_ONE_MINUS_DST_ALPHA + 1010 GL_SRC_ALPHA_SATURATE + + test modes (glAlphaFunc): + 000 GL_ALWAYS + 001 GL_LESS + 010 GL_EQUAL + 011 GL_LEQUAL + 100 GL_GREATER + 101 GL_NOTEQUAL + 110 GL_GEQUAL + 111 GL_NEVER + + Taken from: + http://niftools.sourceforge.net/doc/nif/NiAlphaProperty.html + + Right now we only use standard alpha blending (see the Ogre code + that sets it up) and it appears that this is the only blending + used in the original game. Bloodmoon (along with several mods) do + however use other settings, such as discarding pixel values with + alpha < 1.0. This is faster because we don't have to mess with the + depth stuff like we did for blending. And OGRE has settings for + this too. + */ + + // Tested against when certain flags are set (see above.) + unsigned char threshold; }; typedef StructPropT NiAlphaProperty; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 40f91f84f..06fdce55e 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -88,26 +88,25 @@ enum RecordType /// Base class for all records struct Record { - // Record type and type name - int recType; - Misc::SString recName; + // Record type and type name + int recType; + Misc::SString recName; - Record() : recType(RC_MISSING) {} + Record() : recType(RC_MISSING) {} - /// Parses the record from file - virtual void read(NIFFile *nif) = 0; + /// Parses the record from file + virtual void read(NIFFile *nif) = 0; - /// Does post-processing, after the entire tree is loaded - virtual void post(NIFFile *nif) {} + /// Does post-processing, after the entire tree is loaded + virtual void post(NIFFile *nif) {} virtual ~Record() {} - /* - Use these later if you want custom allocation of all NIF objects - - static void* operator new(size_t size); - static void operator delete(void *p); - */ + /* + Use these later if you want custom allocation of all NIF objects + static void* operator new(size_t size); + static void operator delete(void *p); + */ }; } // Namespace diff --git a/components/nif/record_ptr.hpp b/components/nif/record_ptr.hpp index c5618941e..ac91e25e3 100644 --- a/components/nif/record_ptr.hpp +++ b/components/nif/record_ptr.hpp @@ -37,61 +37,51 @@ namespace Nif template class RecordPtrT { - int index; - X* ptr; - NIFFile *nif; - - public: - - RecordPtrT() : index(-2), ptr(NULL) {} - - /// Read the index from the nif - void read(NIFFile *_nif) - { - // Can only read the index once - assert(index == -2); - - // Store the NIFFile pointer for later - nif = _nif; - - // And the index, of course - index = nif->getInt(); - } - - /** Set the pointer explicitly. May be used when you are pointing to - records in another file, eg. when you have a .nif / .kf pair. - */ - void set(X *p) - { - ptr = p; - index = -1; - } - - /// Look up the actual object from the index - X* getPtr() - { - // Have we found the pointer already? - if(ptr == NULL) - { - // Get the record - assert(index >= 0); - Record *r = nif->getRecord(index); - - // And cast it - ptr = dynamic_cast(r); - assert(ptr != NULL); - } - return ptr; - } + union { + intptr_t index; + X* ptr; + }; - /// Syntactic sugar - X* operator->() { return getPtr(); } - X& get() { return *getPtr(); } +public: + RecordPtrT() : index(-2) {} - /// Pointers are allowed to be empty - bool empty() { return index == -1 && ptr == NULL; } + /// Read the index from the nif + void read(NIFFile *nif) + { + // Can only read the index once + assert(index == -2); - int getIndex() { return index; } + // Store the index for later + index = nif->getInt(); + } + + /// Resolve index to pointer + void post(NIFFile *nif) + { + if(index < 0) + ptr = NULL; + else + { + Record *r = nif->getRecord(index); + // And cast it + ptr = dynamic_cast(r); + assert(ptr != NULL); + } + } + + /// Look up the actual object from the index + X* getPtr() + { + assert(ptr != NULL); + return ptr; + } + X& get() { return *getPtr(); } + + /// Syntactic sugar + X* operator->() { return getPtr(); } + + /// Pointers are allowed to be empty + bool empty() { return ptr == NULL; } }; /** A list of references to other records. These are read as a list, @@ -101,40 +91,38 @@ class RecordPtrT template class RecordListT { - typedef RecordPtrT Ptr; - std::vector list; - - public: + typedef RecordPtrT Ptr; + std::vector list; - void read(NIFFile *nif) - { - int len = nif->getInt(); - list.resize(len); +public: + void read(NIFFile *nif) + { + int len = nif->getInt(); + list.resize(len); - assert(len >= 0 && len < 1000); - for(int i=0;i= 0 && index < static_cast (list.size())); - return list[index].get(); + for(size_t i=0;i < list.size();i++) + list[i].post(nif); } - bool has(int index) - { - assert(index >= 0 && index < static_cast (list.size())); - return !list[index].empty(); - } + X& operator[](size_t index) + { + return list.at(index).get(); + } - int getIndex(int index) + bool has(size_t index) { - if(has(index)) return list[index].getIndex(); - else return -1; + assert(index >= 0 && index < static_cast (list.size())); + return !list.at(index).empty(); } - int length() { return list.size(); } + int length() + { return list.size(); } }; From 291599c6098c446d4cf00b9f18342d040f5b59ab Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 2 Jul 2012 22:49:44 -0700 Subject: [PATCH 012/298] Store the parents of NIF's nodes --- components/nif/node.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index fe9d10c7a..e4cc9291e 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -31,6 +31,8 @@ namespace Nif { +class NiNode; + /** A Node is an object that's part of the main NIF tree. It has parent node (unless it's the root), and transformation (location and rotation) relative to it's parent. @@ -66,6 +68,8 @@ public: boundXYZ = nif->getVector(); } + parent = NULL; + boneTrafo = NULL; boneIndex = -1; } @@ -76,6 +80,10 @@ public: props.post(nif); } + // Parent node, or NULL for the root node. As far as I'm aware, only + // NiNodes (or types derived from NiNodes) can be parents. + NiNode *parent; + // Bone transformation. If set, node is a part of a skeleton. const NiSkinData::BoneTrafo *boneTrafo; @@ -139,6 +147,9 @@ struct NiNode : Node Node::post(nif); children.post(nif); effects.post(nif); + + for(size_t i = 0;i < children.length();i++) + children[i].parent = this; } }; From 24399a45c117550620d2028fa572fd2dbac330cf Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Jul 2012 22:27:22 +0200 Subject: [PATCH 013/298] add submodule with current code --- .gitmodules | 3 +++ CMakeLists.txt | 3 +++ extern/shiny | 1 + 3 files changed, 7 insertions(+) create mode 100644 .gitmodules create mode 160000 extern/shiny diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..f53c97677 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "extern/shiny"] + path = extern/shiny + url = git@github.com:scrawl/shiny.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 84cef306e..569e28675 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -432,6 +432,9 @@ if(WIN32) include(CPack) endif(WIN32) +# Extern +add_subdirectory (extern/shiny) + # Components add_subdirectory (components) diff --git a/extern/shiny b/extern/shiny new file mode 160000 index 000000000..51b573fc6 --- /dev/null +++ b/extern/shiny @@ -0,0 +1 @@ +Subproject commit 51b573fc66ac9a61fe780070692cc600a89f51dd From a1e48b0febcfaf23ecf00b9e91b183ae1cd80fd8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Jul 2012 22:48:16 +0200 Subject: [PATCH 014/298] builds now --- CMakeLists.txt | 2 +- apps/openmw/CMakeLists.txt | 3 ++- apps/openmw/mwrender/renderingmanager.cpp | 8 ++++++++ apps/openmw/mwrender/renderingmanager.hpp | 7 +++++++ extern/shiny | 2 +- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 569e28675..e56550430 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,7 +189,7 @@ endif() find_package(OGRE REQUIRED) find_package(MyGUI REQUIRED) -find_package(Boost REQUIRED COMPONENTS system filesystem program_options thread) +find_package(Boost REQUIRED COMPONENTS system filesystem program_options thread wave) find_package(OIS REQUIRED) find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 9534ecc90..911bd70d5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -101,7 +101,8 @@ target_link_libraries(openmw ${BULLET_LIBRARIES} ${MYGUI_LIBRARIES} ${MYGUI_PLATFORM_LIBRARIES} - components + "shiny" + "shiny.OgrePlatform" ) # Fix for not visible pthreads functions for linker with glibc 2.15 diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5b76f5ae2..8672946fc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -14,6 +14,9 @@ #include #include +#include +#include + #include #include @@ -47,6 +50,11 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mWater = 0; + // material system + sh::OgrePlatform* platform = new sh::OgrePlatform("General", "./"); + platform->setCacheFolder ("./"); + mFactory = new sh::Factory(platform); + //The fog type must be set before any terrain objects are created as if the //fog type is set to FOG_NONE then the initially created terrain won't have any fog configureFog(1, ColourValue(1,1,1)); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 9aaba3803..db5a731b4 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -43,6 +43,11 @@ namespace MWWorld class CellStore; } +namespace sh +{ + class Factory; +} + namespace MWRender { @@ -174,6 +179,8 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList private: + sh::Factory* mFactory; + void setAmbientMode(); void setMenuTransparency(float val); diff --git a/extern/shiny b/extern/shiny index 51b573fc6..392894931 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 51b573fc66ac9a61fe780070692cc600a89f51dd +Subproject commit 3928949316713d0c8aaf1ad564734d24ad773be9 From 4ea6530772fed9069d61c9ee9e5201e3e0635de6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 4 Jul 2012 00:26:57 +0200 Subject: [PATCH 015/298] hello world. --- apps/openmw/mwrender/renderingmanager.cpp | 4 +- apps/openmw/mwrender/sky.cpp | 3 +- cmake/OpenMWMacros.cmake | 8 ++ components/nifogre/ogre_nif_loader.cpp | 75 ++++-------------- files/CMakeLists.txt | 17 ++-- files/materials/core.h | 96 +++++++++++++++++++++++ files/materials/objects.mat | 29 +++++++ files/materials/objects.shader | 27 +++++++ files/materials/objects.shaderset | 15 ++++ files/mygui/CMakeLists.txt | 64 +-------------- 10 files changed, 203 insertions(+), 135 deletions(-) create mode 100644 files/materials/core.h create mode 100644 files/materials/objects.mat create mode 100644 files/materials/objects.shader create mode 100644 files/materials/objects.shaderset diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8672946fc..bfa27e42f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -51,7 +51,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mWater = 0; // material system - sh::OgrePlatform* platform = new sh::OgrePlatform("General", "./"); + sh::OgrePlatform* platform = new sh::OgrePlatform("General", (resDir / "materials").string()); platform->setCacheFolder ("./"); mFactory = new sh::Factory(platform); @@ -270,10 +270,12 @@ void RenderingManager::setWaterHeight(const float height) void RenderingManager::skyEnable () { + /* if(mSkyManager) mSkyManager->enable(); mOcclusionQuery->setSunNode(mSkyManager->getSunNode()); + */ } void RenderingManager::skyDisable () diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 45e48240b..e1e8d6a5c 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -416,7 +416,7 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) void SkyManager::create() { /// \todo preload all the textures and meshes that are used for sky rendering - +/* // Create overlay used for thunderstorm MaterialPtr material = MaterialManager::getSingleton().create( "ThunderMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME ); Pass* pass = material->getTechnique(0)->getPass(0); @@ -696,6 +696,7 @@ void SkyManager::create() mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); mCreated = true; + */ } SkyManager::~SkyManager() diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 024338d3a..c2567830d 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -22,3 +22,11 @@ endforeach (f) endforeach (u) source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_dir) + +macro (copy_all_files file_expression destination_dir) +file (GLOB ALL "${file_expression}") +foreach (f ${ALL}) +get_filename_component(filename ${f} NAME) +configure_file(${f} ${destination_dir}/${filename} COPYONLY) +endforeach (f) +endmacro (copy_all_files) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 895c51a0d..a83a03a17 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -33,6 +33,8 @@ #include #include +#include + #include #include @@ -226,35 +228,32 @@ void NIFLoader::createMaterial(const String &name, int alphaFlags, float alphaTest, const String &texName) { - MaterialPtr material = MaterialManager::getSingleton().create(name, resourceGroup); - - - //Hardware Skinning code, textures may be the wrong color if enabled + sh::MaterialInstance* instance = sh::Factory::getInstance ().createMaterialInstance (name, "openmw_objects_base"); + instance->setProperty ("ambient", sh::makeProperty ( + new sh::Vector3(ambient.array[0], ambient.array[1], ambient.array[2]))); - /* if(!mSkel.isNull()){ - material->removeAllTechniques(); + instance->setProperty ("diffuse", sh::makeProperty ( + new sh::Vector4(diffuse.array[0], diffuse.array[1], diffuse.array[2], alpha))); - Ogre::Technique* tech = material->createTechnique(); - //tech->setSchemeName("blahblah"); - Pass* pass = tech->createPass(); - pass->setVertexProgram("Ogre/BasicVertexPrograms/AmbientOneTexture");*/ + instance->setProperty ("specular", sh::makeProperty ( + new sh::Vector4(specular.array[0], specular.array[1], specular.array[2], glossiness))); + instance->setProperty ("emissive", sh::makeProperty ( + new sh::Vector3(emissive.array[0], emissive.array[1], emissive.array[2]))); - // This assigns the texture to this material. If the texture name is - // a file name, and this file exists (in a resource directory), it - // will automatically be loaded when needed. If not (such as for - // internal NIF textures that we might support later), we should - // already have inserted a manual loader for the texture. + instance->setProperty ("diffuseMap", sh::makeProperty(texName)); +/* if (!texName.empty()) { Pass *pass = material->getTechnique(0)->getPass(0); /*TextureUnitState *txt =*/ + /* pass->createTextureUnitState(texName); pass->setVertexColourTracking(TVC_DIFFUSE); - +*/ // As of yet UNTESTED code from Chris: /*pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL); @@ -282,7 +281,7 @@ void NIFLoader::createMaterial(const String &name, else pass->setDepthWriteEnabled(true); */ - +/* // Add transparency if NiAlphaProperty was present if (alphaFlags != -1) { @@ -315,48 +314,8 @@ void NIFLoader::createMaterial(const String &name, material->getTechnique(0)->setShadowCasterMaterial("depth_shadow_caster_noalpha"); } } + */ - if (Settings::Manager::getBool("enabled", "Shadows")) - { - bool split = Settings::Manager::getBool("split", "Shadows"); - const int numsplits = 3; - for (int i = 0; i < (split ? numsplits : 1); ++i) - { - TextureUnitState* tu = material->getTechnique(0)->getPass(0)->createTextureUnitState(); - tu->setName("shadowMap" + StringConverter::toString(i)); - tu->setContentType(TextureUnitState::CONTENT_SHADOW); - tu->setTextureAddressingMode(TextureUnitState::TAM_BORDER); - tu->setTextureBorderColour(ColourValue::White); - } - } - - if (Settings::Manager::getBool("shaders", "Objects")) - { - material->getTechnique(0)->getPass(0)->setVertexProgram("main_vp"); - material->getTechnique(0)->getPass(0)->setFragmentProgram("main_fp"); - - material->getTechnique(0)->getPass(0)->setFog(true); // force-disable fixed function fog, it is calculated in shader - } - - // Create a fallback technique without shadows and without mrt - Technique* tech2 = material->createTechnique(); - tech2->setSchemeName("Fallback"); - Pass* pass2 = tech2->createPass(); - pass2->createTextureUnitState(texName); - pass2->setVertexColourTracking(TVC_DIFFUSE); - if (Settings::Manager::getBool("shaders", "Objects")) - { - pass2->setVertexProgram("main_fallback_vp"); - pass2->setFragmentProgram("main_fallback_fp"); - pass2->setFog(true); // force-disable fixed function fog, it is calculated in shader - } - - // Add material bells and whistles - material->setAmbient(ambient.array[0], ambient.array[1], ambient.array[2]); - material->setDiffuse(diffuse.array[0], diffuse.array[1], diffuse.array[2], alpha); - material->setSpecular(specular.array[0], specular.array[1], specular.array[2], alpha); - material->setSelfIllumination(emissive.array[0], emissive.array[1], emissive.array[2]); - material->setShininess(glossiness); } // Takes a name and adds a unique part to it. This is just used to diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index 8ab3d5b51..ce8bc9fbd 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -1,16 +1,9 @@ project(resources) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/caustic_0.png "${OpenMW_BINARY_DIR}/resources/water/caustic_0.png" COPYONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/underwater.cg "${OpenMW_BINARY_DIR}/resources/water/underwater.cg" COPYONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/perlinvolume.dds "${OpenMW_BINARY_DIR}/resources/water/perlinvolume.dds" COPYONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/water.compositor "${OpenMW_BINARY_DIR}/resources/water/water.compositor" COPYONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/water.material "${OpenMW_BINARY_DIR}/resources/water/water.material" COPYONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/WaterNormal2.tga "${OpenMW_BINARY_DIR}/resources/water/WaterNormal2.tga" COPYONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/water.cg "${OpenMW_BINARY_DIR}/resources/water/water.cg" COPYONLY) +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/water/* "${OpenMW_BINARY_DIR}/resources/water/") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer/gbuffer.cg "${OpenMW_BINARY_DIR}/resources/gbuffer/gbuffer.cg" COPYONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer/gbuffer.material "${OpenMW_BINARY_DIR}/resources/gbuffer/gbuffer.material" COPYONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer/gbuffer.compositor "${OpenMW_BINARY_DIR}/resources/gbuffer/gbuffer.compositor" COPYONLY) +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer/* "${OpenMW_BINARY_DIR}/resources/gbuffer/") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/shadows/depthshadowcaster.material "${OpenMW_BINARY_DIR}/resources/shadows/depthshadowcaster.material" COPYONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/shadows/depthshadowcaster.cg "${OpenMW_BINARY_DIR}/resources/shadows/depthshadowcaster.cg" COPYONLY) +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/shadows/* "${OpenMW_BINARY_DIR}/resources/shadows/") + +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/materials/* "${OpenMW_BINARY_DIR}/resources/materials/") diff --git a/files/materials/core.h b/files/materials/core.h new file mode 100644 index 000000000..2894ca382 --- /dev/null +++ b/files/materials/core.h @@ -0,0 +1,96 @@ +#if SH_HLSL == 1 || SH_CG == 1 + + #define shTexture2D sampler2D + #define shSample(tex, coord) tex2D(tex, coord) + #define shLerp(a, b, t) lerp(a, b, t) + #define shSaturate(a) saturate(a) + + #define shSampler2D(name) , uniform sampler2D name : register(s@shCounter(0)) @shUseSampler(name) + + #define shMatrixMult(m, v) mul(m, v) + + #define shUniform(s) , uniform s + + #define shInput(type, name) , in type name : TEXCOORD@shCounter(1) + #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2) + + #ifdef SH_VERTEX_SHADER + + #define shOutputPosition oPosition + #define shInputPosition iPosition + + + #define SH_BEGIN_PROGRAM \ + void main( \ + float4 iPosition : POSITION \ + , out float4 oPosition : POSITION + + #define SH_START_PROGRAM \ + ) \ + + #endif + + #ifdef SH_FRAGMENT_SHADER + + #define shOutputColor oColor + + #define SH_BEGIN_PROGRAM \ + void main( \ + out float4 oColor : COLOR + + #define SH_START_PROGRAM \ + ) \ + + #endif + +#endif + +#if SH_GLSL == 1 + @shGlslVersion(420) + + #define float2 vec2 + #define float3 vec3 + #define float4 vec4 + #define int2 ivec2 + #define int3 ivec3 + #define int4 ivec4 + #define shTexture2D sampler2D + #define shSample(tex, coord) texture(tex, coord) + #define shLerp(a, b, t) mix(a, b, t) + #define shSaturate(a) clamp(a, 0.0, 1.0) + + #define shUniform(s) uniform s; + + #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name) + + #define shMatrixMult(m, v) m * v + + #define shInputPosition vertex + #define shOutputPosition gl_Position + #define shOutputColor oColor + + #define float4x4 mat4 + + #define shInput(type, name) in type name; + #define shOutput(type, name) out type name; + + + #ifdef SH_VERTEX_SHADER + + #define SH_BEGIN_PROGRAM \ + in float4 shInputPosition; + #define SH_START_PROGRAM \ + void main(void) + + #endif + + #ifdef SH_FRAGMENT_SHADER + + #define SH_BEGIN_PROGRAM \ + out float4 oColor; + #define SH_START_PROGRAM \ + void main(void) + + + #endif +#endif diff --git a/files/materials/objects.mat b/files/materials/objects.mat new file mode 100644 index 000000000..564018724 --- /dev/null +++ b/files/materials/objects.mat @@ -0,0 +1,29 @@ +material openmw_objects_base +{ + diffuse 1.0 1.0 1.0 1.0 + specular 0.4 0.4 0.4 32 + ambient 1.0 1.0 1.0 + emissive 0.0 0.0 0.0 + has_vertex_colour false + diffuseMap black.png + + pass + { + vertex_program openmw_objects_vertex + fragment_program openmw_objects_fragment + + diffuse $diffuse + specular $specular + ambient $ambient + emissive $emissive + + ffp_vertex_colour_ambient $has_vertex_colour + has_vertex_colour $has_vertex_colour + + texture_unit diffuseMap + { + texture $diffuseMap + create_in_ffp true + } + } +} diff --git a/files/materials/objects.shader b/files/materials/objects.shader new file mode 100644 index 000000000..7bd91e526 --- /dev/null +++ b/files/materials/objects.shader @@ -0,0 +1,27 @@ +#include "core.h" + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shInput(float2, uv0) + shOutput(float2, UV) + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + } + +#else + + SH_BEGIN_PROGRAM + shSampler2D(diffuseMap) + shInput(float2, UV) + + SH_START_PROGRAM + { + // shOutputColor = float4(1.0, 0.0, 0.0, 1.0); + shOutputColor = shSample(diffuseMap, UV); + } + +#endif diff --git a/files/materials/objects.shaderset b/files/materials/objects.shaderset new file mode 100644 index 000000000..7d01973e3 --- /dev/null +++ b/files/materials/objects.shaderset @@ -0,0 +1,15 @@ +shader_set openmw_objects_vertex +{ + source objects.shader + type vertex + profiles_cg vs_2_0 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set openmw_objects_fragment +{ + source objects.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_hlsl ps_2_0 +} diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index f41cdf54e..b8a04a31c 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -1,68 +1,6 @@ -# Minimal MyGUI build system for OpenMW # Copy resource files into the build directory set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) -configure_file("${SDIR}/bigbars.png" "${DDIR}/bigbars.png" COPYONLY) -configure_file("${SDIR}/black.png" "${DDIR}/black.png" COPYONLY) -configure_file("${SDIR}/core.skin" "${DDIR}/core.skin" COPYONLY) -configure_file("${SDIR}/core.xml" "${DDIR}/core.xml" COPYONLY) -configure_file("${SDIR}/mwgui.png" "${DDIR}/mwgui.png" COPYONLY) -configure_file("${SDIR}/openmw_resources.xml" "${DDIR}/openmw_resources.xml" COPYONLY) -configure_file("${SDIR}/openmw_settings.xml" "${DDIR}/openmw_settings.xml" COPYONLY) -configure_file("${SDIR}/openmw_box.skin.xml" "${DDIR}/openmw_box.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_button.skin.xml" "${DDIR}/openmw_button.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_list.skin.xml" "${DDIR}/openmw_list.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_edit.skin.xml" "${DDIR}/openmw_edit.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_console.layout" "${DDIR}/openmw_console.layout" COPYONLY) -configure_file("${SDIR}/openmw_console.skin.xml" "${DDIR}/openmw_console.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw.font.xml" "${DDIR}/openmw.font.xml" COPYONLY) -configure_file("${SDIR}/openmw_hud_box.skin.xml" "${DDIR}/openmw_hud_box.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_hud_energybar.skin.xml" "${DDIR}/openmw_hud_energybar.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_hud.layout" "${DDIR}/openmw_hud.layout" COPYONLY) -configure_file("${SDIR}/openmw_text_input.layout" "${DDIR}/openmw_text_input.layout" COPYONLY) -configure_file("${SDIR}/openmw_infobox.layout" "${DDIR}/openmw_infobox.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_race.layout" "${DDIR}/openmw_chargen_race.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_class.layout" "${DDIR}/openmw_chargen_class.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_generate_class_result.layout" "${DDIR}/openmw_chargen_generate_class_result.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_create_class.layout" "${DDIR}/openmw_chargen_create_class.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_select_specialization.layout" "${DDIR}/openmw_chargen_select_specialization.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_select_attribute.layout" "${DDIR}/openmw_chargen_select_attribute.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_select_skill.layout" "${DDIR}/openmw_chargen_select_skill.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_class_description.layout" "${DDIR}/openmw_chargen_class_description.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_birth.layout" "${DDIR}/openmw_chargen_birth.layout" COPYONLY) -configure_file("${SDIR}/openmw_chargen_review.layout" "${DDIR}/openmw_chargen_review.layout" COPYONLY) -configure_file("${SDIR}/openmw_dialogue_window.layout" "${DDIR}/openmw_dialogue_window.layout" COPYONLY) -configure_file("${SDIR}/openmw_dialogue_window_skin.xml" "${DDIR}/openmw_dialogue_window_skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_inventory_window.layout" "${DDIR}/openmw_inventory_window.layout" COPYONLY) -configure_file("${SDIR}/openmw_container_window.layout" "${DDIR}/openmw_container_window.layout" COPYONLY) -configure_file("${SDIR}/openmw_layers.xml" "${DDIR}/openmw_layers.xml" COPYONLY) -configure_file("${SDIR}/openmw_mainmenu.layout" "${DDIR}/openmw_mainmenu.layout" COPYONLY) -configure_file("${SDIR}/openmw_mainmenu_skin.xml" "${DDIR}/openmw_mainmenu_skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_map_window.layout" "${DDIR}/openmw_map_window.layout" COPYONLY) -configure_file("${SDIR}/openmw_map_window_skin.xml" "${DDIR}/openmw_map_window_skin.xml" COPYONLY) -configure_file("${SDIR}/openmw.pointer.xml" "${DDIR}/openmw.pointer.xml" COPYONLY) -configure_file("${SDIR}/openmw_progress.skin.xml" "${DDIR}/openmw_progress.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_stats_window.layout" "${DDIR}/openmw_stats_window.layout" COPYONLY) -configure_file("${SDIR}/openmw_text.skin.xml" "${DDIR}/openmw_text.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_windows.skin.xml" "${DDIR}/openmw_windows.skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_messagebox.layout" "${DDIR}/openmw_messagebox.layout" COPYONLY) -configure_file("${SDIR}/openmw_interactive_messagebox.layout" "${DDIR}/openmw_interactive_messagebox.layout" COPYONLY) -configure_file("${SDIR}/openmw_journal.layout" "${DDIR}/openmw_journal.layout" COPYONLY) -configure_file("${SDIR}/openmw_journal_skin.xml" "${DDIR}/openmw_journal_skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_tooltips.layout" "${DDIR}/openmw_tooltips.layout" COPYONLY) -configure_file("${SDIR}/openmw_scroll.layout" "${DDIR}/openmw_scroll.layout" COPYONLY) -configure_file("${SDIR}/openmw_scroll_skin.xml" "${DDIR}/openmw_scroll_skin.xml" COPYONLY) -configure_file("${SDIR}/openmw_book.layout" "${DDIR}/openmw_book.layout" COPYONLY) -configure_file("${SDIR}/openmw_count_window.layout" "${DDIR}/openmw_count_window.layout" COPYONLY) -configure_file("${SDIR}/openmw_trade_window.layout" "${DDIR}/openmw_trade_window.layout" COPYONLY) -configure_file("${SDIR}/openmw_settings_window.layout" "${DDIR}/openmw_settings_window.layout" COPYONLY) -configure_file("${SDIR}/openmw_confirmation_dialog.layout" "${DDIR}/openmw_confirmation_dialog.layout" COPYONLY) -configure_file("${SDIR}/openmw_alchemy_window.layout" "${DDIR}/openmw_alchemy_window.layout" COPYONLY) -configure_file("${SDIR}/openmw_spell_window.layout" "${DDIR}/openmw_spell_window.layout" COPYONLY) -configure_file("${SDIR}/atlas1.cfg" "${DDIR}/atlas1.cfg" COPYONLY) -configure_file("${SDIR}/smallbars.png" "${DDIR}/smallbars.png" COPYONLY) -configure_file("${SDIR}/EBGaramond-Regular.ttf" "${DDIR}/EBGaramond-Regular.ttf" COPYONLY) -configure_file("${SDIR}/Obliviontt.zip" "${DDIR}/Obliviontt.zip" COPYONLY) -configure_file("${SDIR}/VeraMono.ttf" "${DDIR}/VeraMono.ttf" COPYONLY) +copy_all_files(${SDIR}/* ${DDIR}) From de9b7a51de1ee7528a3ae2ed260f15c526602755 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 4 Jul 2012 00:33:09 +0200 Subject: [PATCH 016/298] change glsl #version, remove CG from required libraries --- CMakeLists.txt | 4 ++-- files/materials/core.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e56550430..51b2a45ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,7 +194,7 @@ find_package(OIS REQUIRED) find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) IF(OGRE_STATIC) -find_package(Cg REQUIRED) +find_package(Cg) IF(WIN32) set(OGRE_PLUGIN_INCLUDE_DIRS ${OGRE_Plugin_CgProgramManager_INCLUDE_DIRS} ${OGRE_Plugin_OctreeSceneManager_INCLUDE_DIRS} ${OGRE_Plugin_ParticleFX_INCLUDE_DIRS} ${OGRE_RenderSystem_Direct3D9_INCLUDE_DIRS} ${OGRE_RenderSystem_GL_INCLUDE_DIRS}) ELSE(WIN32) @@ -347,7 +347,7 @@ if(DPKG_PROGRAM) SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") - SET(CPACK_DEBIAN_PACKAGE_DEPENDS "nvidia-cg-toolkit (>= 2.1), libboost-filesystem1.46.1 (>= 1.46.1), libboost-program-options1.46.1 (>= 1.46.1), libboost-system1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libois-1.3.0 (>= 1.3.0), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") + SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-filesystem1.46.1 (>= 1.46.1), libboost-program-options1.46.1 (>= 1.46.1), libboost-system1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libois-1.3.0 (>= 1.3.0), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") diff --git a/files/materials/core.h b/files/materials/core.h index 2894ca382..2f51925a0 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -46,7 +46,7 @@ #endif #if SH_GLSL == 1 - @shGlslVersion(420) + @shGlslVersion(130) #define float2 vec2 #define float3 vec3 From d8d00123ea34662282e461dd3253dbd4b90fe908 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 3 Jul 2012 18:37:04 -0700 Subject: [PATCH 017/298] Watch for empty children node refs when setting parents --- components/nif/nif_file.cpp | 2 +- components/nif/node.hpp | 6 +++++- components/nif/record_ptr.hpp | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 48dd76510..42aa43f8b 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -203,7 +203,7 @@ void NiSkinInstance::post(NIFFile *nif) root->makeRootBone(data->trafo); - for(int i=0; ifail("Oops: Missing bone! Don't know how to handle this."); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index e4cc9291e..5a2847b6c 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -149,7 +149,11 @@ struct NiNode : Node effects.post(nif); for(size_t i = 0;i < children.length();i++) - children[i].parent = this; + { + // Why would a unique list of children contain empty refs? + if(children.has(i)) + children[i].parent = this; + } } }; diff --git a/components/nif/record_ptr.hpp b/components/nif/record_ptr.hpp index ac91e25e3..755094147 100644 --- a/components/nif/record_ptr.hpp +++ b/components/nif/record_ptr.hpp @@ -53,6 +53,7 @@ public: // Store the index for later index = nif->getInt(); + assert(index >= -1); } /// Resolve index to pointer @@ -117,11 +118,10 @@ public: bool has(size_t index) { - assert(index >= 0 && index < static_cast (list.size())); return !list.at(index).empty(); } - int length() + size_t length() { return list.size(); } }; From f8e3213996fc91583c6c1e935441709cdab43ec6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 4 Jul 2012 16:57:14 +0200 Subject: [PATCH 018/298] fog, mrt depth --- extern/shiny | 2 +- files/materials/core.h | 13 +++++++--- files/materials/objects.mat | 5 ++++ files/materials/objects.shader | 45 ++++++++++++++++++++++++++++++++-- 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/extern/shiny b/extern/shiny index 392894931..8d95f5346 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 3928949316713d0c8aaf1ad564734d24ad773be9 +Subproject commit 8d95f53464a779c7da643228ace02ae28ec6a503 diff --git a/files/materials/core.h b/files/materials/core.h index 2f51925a0..20127d3de 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -14,6 +14,8 @@ #define shInput(type, name) , in type name : TEXCOORD@shCounter(1) #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2) + #define shNormalInput(type) , in type normal : NORMAL + #ifdef SH_VERTEX_SHADER #define shOutputPosition oPosition @@ -32,7 +34,9 @@ #ifdef SH_FRAGMENT_SHADER - #define shOutputColor oColor + #define shOutputColor(num) oColor##num + + #define shDeclareMrtOutput(num) , out float4 oColor##num : COLOR##num #define SH_BEGIN_PROGRAM \ void main( \ @@ -67,13 +71,14 @@ #define shInputPosition vertex #define shOutputPosition gl_Position - #define shOutputColor oColor + #define shOutputColor(num) oColor##num #define float4x4 mat4 #define shInput(type, name) in type name; #define shOutput(type, name) out type name; + #define shNormalInput(type) in type normal; #ifdef SH_VERTEX_SHADER @@ -86,8 +91,10 @@ #ifdef SH_FRAGMENT_SHADER + #define shDeclareMrtOutput(num) out vec4 oColor##num; + #define SH_BEGIN_PROGRAM \ - out float4 oColor; + out float4 oColor0; #define SH_START_PROGRAM \ void main(void) diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 564018724..69b89a7e5 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -6,6 +6,8 @@ material openmw_objects_base emissive 0.0 0.0 0.0 has_vertex_colour false diffuseMap black.png + fog true + is_transparent false // real transparency, alpha rejection doesn't count here pass { @@ -16,9 +18,12 @@ material openmw_objects_base specular $specular ambient $ambient emissive $emissive + fog $fog ffp_vertex_colour_ambient $has_vertex_colour has_vertex_colour $has_vertex_colour + + is_transparent $is_transparent texture_unit diffuseMap { diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 7bd91e526..08e9913d9 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -1,15 +1,36 @@ #include "core.h" + +#define FOG @shPropertyBool(fog) +#define MRT @shPropertyNotBool(is_transparent) + +#if MRT +#define NEED_DEPTH +#endif + +#if FOG +#define NEED_DEPTH +#endif + #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) shInput(float2, uv0) shOutput(float2, UV) + shNormalInput(float4) + shOutput(float4, normalPassthrough) + #ifdef NEED_DEPTH + shOutput(float, depthPassthrough) + #endif SH_START_PROGRAM { shOutputPosition = shMatrixMult(wvp, shInputPosition); UV = uv0; + normalPassthrough = normal; + #ifdef NEED_DEPTH + depthPassthrough = shOutputPosition.z; + #endif } #else @@ -17,11 +38,31 @@ SH_BEGIN_PROGRAM shSampler2D(diffuseMap) shInput(float2, UV) + shDeclareMrtOutput(1) + shInput(float4, normalPassthrough) + #ifdef NEED_DEPTH + shInput(float, depthPassthrough) + #endif + + shUniform(float far) @shAutoConstant(far, far_clip_distance) + +#if FOG + shUniform(float3 fogColor) @shAutoConstant(fogColor, fog_colour) + shUniform(float4 fogParams) @shAutoConstant(fogParams, fog_params) + +#endif SH_START_PROGRAM { - // shOutputColor = float4(1.0, 0.0, 0.0, 1.0); - shOutputColor = shSample(diffuseMap, UV); + //shOutputColor(0) = float4((normalize(normalPassthrough.xyz)+float3(1.0,1.0,1.0)) / 2.f, 1.0); + shOutputColor(0) = shSample(diffuseMap, UV); + +#if FOG + float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); + shOutputColor(0).xyz = shLerp (shOutputColor(0).xyz, fogColor, fogValue); +#endif + + shOutputColor(1) = float4(depthPassthrough / far,1,1,1); } #endif From 19ecc7f890b6a92267c73ede17edb2bc109e8147 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 4 Jul 2012 17:28:22 +0200 Subject: [PATCH 019/298] fix performance --- components/nifogre/ogre_nif_loader.cpp | 4 +++- extern/shiny | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index a83a03a17..26a18b6b4 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -228,6 +228,9 @@ void NIFLoader::createMaterial(const String &name, int alphaFlags, float alphaTest, const String &texName) { + if (texName.empty()) + return; + sh::MaterialInstance* instance = sh::Factory::getInstance ().createMaterialInstance (name, "openmw_objects_base"); instance->setProperty ("ambient", sh::makeProperty ( new sh::Vector3(ambient.array[0], ambient.array[1], ambient.array[2]))); @@ -243,7 +246,6 @@ void NIFLoader::createMaterial(const String &name, instance->setProperty ("diffuseMap", sh::makeProperty(texName)); - /* if (!texName.empty()) { diff --git a/extern/shiny b/extern/shiny index 8d95f5346..6080431c8 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 8d95f53464a779c7da643228ace02ae28ec6a503 +Subproject commit 6080431c8ce37c6044dd7d1e319de3c7b8adfd69 From 9bd888d9a1a6572222bfa5f1a29e3cf840590b58 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 5 Jul 2012 00:39:25 +0200 Subject: [PATCH 020/298] transparency --- components/nifogre/ogre_nif_loader.cpp | 27 ++++++++++ extern/shiny | 2 +- files/materials/atmosphere.shader | 0 files/materials/atmosphere.shaderset | 0 files/materials/clouds.shader | 0 files/materials/clouds.shaderset | 0 files/materials/moon.shader | 0 files/materials/moon.shaderset | 0 files/materials/objects.mat | 7 +++ files/materials/objects.shader | 12 ++--- files/materials/sky.mat | 73 ++++++++++++++++++++++++++ files/materials/sun.shader | 0 files/materials/sun.shaderset | 0 files/materials/water.mat | 30 +++++++++++ files/materials/water.shader | 0 files/materials/water.shaderset | 0 16 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 files/materials/atmosphere.shader create mode 100644 files/materials/atmosphere.shaderset create mode 100644 files/materials/clouds.shader create mode 100644 files/materials/clouds.shaderset create mode 100644 files/materials/moon.shader create mode 100644 files/materials/moon.shaderset create mode 100644 files/materials/sky.mat create mode 100644 files/materials/sun.shader create mode 100644 files/materials/sun.shaderset create mode 100644 files/materials/water.mat create mode 100644 files/materials/water.shader create mode 100644 files/materials/water.shaderset diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 26a18b6b4..d1102087d 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -246,6 +246,33 @@ void NIFLoader::createMaterial(const String &name, instance->setProperty ("diffuseMap", sh::makeProperty(texName)); + // Add transparency if NiAlphaProperty was present + if (alphaFlags != -1) + { + // The 237 alpha flags are by far the most common. Check + // NiAlphaProperty in nif/property.h if you need to decode + // other values. 237 basically means normal transparencly. + if (alphaFlags == 237) + { + NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName); + if (result.first) + { + instance->setProperty("alpha_rejection_func", sh::makeProperty(new sh::StringValue("greater_equal"))); + instance->setProperty("alpha_rejection_value", sh::makeProperty(new sh::IntValue(result.second))); + } + else + { + // Enable transparency + instance->setProperty("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend"))); + instance->setProperty("depth_write", sh::makeProperty(new sh::BooleanValue(false))); + } + } + else + warn("Unhandled alpha setting for texture " + texName); + + } + + /* if (!texName.empty()) { diff --git a/extern/shiny b/extern/shiny index 6080431c8..3a1b8e2ae 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 6080431c8ce37c6044dd7d1e319de3c7b8adfd69 +Subproject commit 3a1b8e2aefa746ef0922e78f4be3ff736ce92fc3 diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader new file mode 100644 index 000000000..e69de29bb diff --git a/files/materials/atmosphere.shaderset b/files/materials/atmosphere.shaderset new file mode 100644 index 000000000..e69de29bb diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader new file mode 100644 index 000000000..e69de29bb diff --git a/files/materials/clouds.shaderset b/files/materials/clouds.shaderset new file mode 100644 index 000000000..e69de29bb diff --git a/files/materials/moon.shader b/files/materials/moon.shader new file mode 100644 index 000000000..e69de29bb diff --git a/files/materials/moon.shaderset b/files/materials/moon.shaderset new file mode 100644 index 000000000..e69de29bb diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 69b89a7e5..3fcc12d62 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -7,7 +7,11 @@ material openmw_objects_base has_vertex_colour false diffuseMap black.png fog true + is_transparent false // real transparency, alpha rejection doesn't count here + scene_blend default + alpha_rejection_value default + alpha_rejection_func default pass { @@ -24,6 +28,9 @@ material openmw_objects_base has_vertex_colour $has_vertex_colour is_transparent $is_transparent + scene_blend $scene_blend + alpha_rejection_value $alpha_rejection_value + alpha_rejection_func $alpha_rejection_func texture_unit diffuseMap { diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 08e9913d9..9ec09840d 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -20,17 +20,17 @@ shOutput(float2, UV) shNormalInput(float4) shOutput(float4, normalPassthrough) - #ifdef NEED_DEPTH +#ifdef NEED_DEPTH shOutput(float, depthPassthrough) - #endif +#endif SH_START_PROGRAM { shOutputPosition = shMatrixMult(wvp, shInputPosition); UV = uv0; normalPassthrough = normal; - #ifdef NEED_DEPTH +#ifdef NEED_DEPTH depthPassthrough = shOutputPosition.z; - #endif +#endif } #else @@ -41,9 +41,9 @@ shDeclareMrtOutput(1) shInput(float4, normalPassthrough) - #ifdef NEED_DEPTH +#ifdef NEED_DEPTH shInput(float, depthPassthrough) - #endif +#endif shUniform(float far) @shAutoConstant(far, far_clip_distance) diff --git a/files/materials/sky.mat b/files/materials/sky.mat new file mode 100644 index 000000000..a82816438 --- /dev/null +++ b/files/materials/sky.mat @@ -0,0 +1,73 @@ +material openmw_moon +{ + pass + { + vertex_program moon_vertex + fragment_program moon_fragment + + texture_unit diffuseMap + { + texture $diffuseMap + create_in_ffp true + } + } +} + +material openmw_clouds +{ + pass + { + vertex_program clouds_vertex + fragment_program clouds_fragment + + // second diffuse map is used for weather transitions + texture_unit diffuseMap1 + { + texture $diffuseMap1 + create_in_ffp true + } + + texture_unit diffuseMap2 + { + texture $diffuseMap2 + } + } +} + +material openmw_atmosphere +{ + pass + { + vertex_program atmosphere_vertex + fragment_program atmosphere_fragment + } +} + +material openmw_stars +{ + pass + { + vertex_program stars_vertex + fragment_program stars_fragment + + texture_unit diffuseMap + { + diffuseMap $diffuseMap + } + } +} + +// used for both sun and sun glare +material openmw_sun +{ + pass + { + vertex_program sun_vertex + fragment_program sun_fragment + + texture unit diffuseMap + { + diffuseMap $diffuseMap + } + } +} diff --git a/files/materials/sun.shader b/files/materials/sun.shader new file mode 100644 index 000000000..e69de29bb diff --git a/files/materials/sun.shaderset b/files/materials/sun.shaderset new file mode 100644 index 000000000..e69de29bb diff --git a/files/materials/water.mat b/files/materials/water.mat new file mode 100644 index 000000000..c701e5ffe --- /dev/null +++ b/files/materials/water.mat @@ -0,0 +1,30 @@ +// note: the fixed function water is created manually, not here + +material openmw_water +{ + pass + { + vertex_program water_vertex + fragment_program water_fragment + + texture_unit reflectionMap + { + texture_alias WaterReflection + } + + texture_unit refractionMap + { + texture_alias WaterRefraction + } + + texture_unit depthMap + { + + } + + texture_unit normalMap + { + texture + } + } +} diff --git a/files/materials/water.shader b/files/materials/water.shader new file mode 100644 index 000000000..e69de29bb diff --git a/files/materials/water.shaderset b/files/materials/water.shaderset new file mode 100644 index 000000000..e69de29bb From 8e683c2e05fac7c8aad3e95b9c756bdeb9dbd55f Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 5 Jul 2012 01:38:33 +0200 Subject: [PATCH 021/298] vertex colors --- components/nifogre/ogre_nif_loader.cpp | 14 ++++++++++---- components/nifogre/ogre_nif_loader.hpp | 3 ++- files/materials/core.h | 8 +++++++- files/materials/objects.shader | 17 +++++++++++++++++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index d1102087d..eb1714b61 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -226,7 +226,7 @@ void NIFLoader::createMaterial(const String &name, const Vector &emissive, float glossiness, float alpha, int alphaFlags, float alphaTest, - const String &texName) + const String &texName, bool vertexColor) { if (texName.empty()) return; @@ -246,6 +246,9 @@ void NIFLoader::createMaterial(const String &name, instance->setProperty ("diffuseMap", sh::makeProperty(texName)); + if (vertexColor) + instance->setProperty ("has_vertex_colour", sh::makeProperty(new sh::BooleanValue(true))); + // Add transparency if NiAlphaProperty was present if (alphaFlags != -1) { @@ -272,7 +275,6 @@ void NIFLoader::createMaterial(const String &name, } - /* if (!texName.empty()) { @@ -668,6 +670,8 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou NiTexturingProperty *t = NULL; NiMaterialProperty *m = NULL; NiAlphaProperty *a = NULL; + // can't make any sense of these values, so ignoring them for now + //NiVertexColorProperty *v = NULL; // Scan the property list for material information PropertyList &list = shape->props; @@ -685,6 +689,8 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou m = static_cast(pr); else if (pr->recType == RC_NiAlphaProperty) a = static_cast(pr); + //else if (pr->recType == RC_NiVertexColorProperty) + //v = static_cast(pr); } // Texture @@ -755,7 +761,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou { //std::cout << "new"; createMaterial(material, d->ambient, d->diffuse, d->specular, d->emissive, - d->glossiness, d->alpha, alphaFlags, alphaTest, texName); + d->glossiness, d->alpha, alphaFlags, alphaTest, texName, shape->data->colors.length != 0); MaterialMap.insert(std::make_pair(texName,material)); } } @@ -771,7 +777,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou } createMaterial(material, one, one, zero, zero, 0.0, 1.0, - alphaFlags, alphaTest, texName); + alphaFlags, alphaTest, texName, shape->data->colors.length != 0); } } } // End of material block, if(!hidden) ... diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index d73948fa8..fadb30af7 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -142,7 +142,8 @@ class NIFLoader : Ogre::ManualResourceLoader const Nif::Vector &emissive, float glossiness, float alpha, int alphaFlags, float alphaTest, - const Ogre::String &texName); + const Ogre::String &texName, + bool vertexColor); void findRealTexture(Ogre::String &texName); diff --git a/files/materials/core.h b/files/materials/core.h index 20127d3de..fe0172cd9 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -15,6 +15,8 @@ #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2) #define shNormalInput(type) , in type normal : NORMAL + + #define shColourInput(type) , in type colour : COLOR #ifdef SH_VERTEX_SHADER @@ -40,7 +42,7 @@ #define SH_BEGIN_PROGRAM \ void main( \ - out float4 oColor : COLOR + out float4 oColor0 : COLOR #define SH_START_PROGRAM \ ) \ @@ -69,7 +71,9 @@ #define shMatrixMult(m, v) m * v + // automatically recognized by ogre when the input name equals this #define shInputPosition vertex + #define shOutputPosition gl_Position #define shOutputColor(num) oColor##num @@ -78,7 +82,9 @@ #define shInput(type, name) in type name; #define shOutput(type, name) out type name; + // automatically recognized by ogre when the input name equals this #define shNormalInput(type) in type normal; + #define shColourInput(type) in type colour; #ifdef SH_VERTEX_SHADER diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 9ec09840d..9a78bb455 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -12,6 +12,8 @@ #define NEED_DEPTH #endif +#define HAS_VERTEXCOLOR @shPropertyBool(has_vertex_colour) + #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM @@ -22,6 +24,10 @@ shOutput(float4, normalPassthrough) #ifdef NEED_DEPTH shOutput(float, depthPassthrough) +#endif +#if HAS_VERTEXCOLOR + shColourInput(float4) + shOutput(float4, colorPassthrough) #endif SH_START_PROGRAM { @@ -31,6 +37,10 @@ #ifdef NEED_DEPTH depthPassthrough = shOutputPosition.z; #endif + +#if HAS_VERTEXCOLOR + colorPassthrough = colour; +#endif } #else @@ -50,13 +60,20 @@ #if FOG shUniform(float3 fogColor) @shAutoConstant(fogColor, fog_colour) shUniform(float4 fogParams) @shAutoConstant(fogParams, fog_params) +#endif +#ifdef HAS_VERTEXCOLOR + shInput(float4, colorPassthrough) #endif SH_START_PROGRAM { //shOutputColor(0) = float4((normalize(normalPassthrough.xyz)+float3(1.0,1.0,1.0)) / 2.f, 1.0); shOutputColor(0) = shSample(diffuseMap, UV); +#if HAS_VERTEXCOLOR + shOutputColor(0).xyz *= colorPassthrough.xyz; +#endif + #if FOG float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); shOutputColor(0).xyz = shLerp (shOutputColor(0).xyz, fogColor, fogValue); From 33c48b9481deb090ff411da6228757cb59b4bb4e Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 6 Jul 2012 04:28:08 +0200 Subject: [PATCH 022/298] update --- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/water.cpp | 2 +- components/nifogre/ogre_nif_loader.cpp | 91 +++++++------------------- extern/shiny | 2 +- files/materials/core.h | 2 +- files/materials/objects.mat | 23 ++++--- files/materials/objects.shader | 13 ++-- 7 files changed, 48 insertions(+), 87 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index dd0351721..4fa125c9b 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -229,7 +229,7 @@ void LocalMap::render(const float x, const float y, vp->setVisibilityMask(RV_Map); // use fallback techniques without shadows and without mrt - vp->setMaterialScheme("Fallback"); + vp->setMaterialScheme("simple"); rtt->update(); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 94fb3d6d9..666161052 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -336,7 +336,7 @@ void Water::applyRTT() vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f)); vp->setShadowsEnabled(false); // use fallback techniques without shadows and without mrt (currently not implemented for sky and terrain) - //vp->setMaterialScheme("Fallback"); + vp->setMaterialScheme("simple"); rtt->addListener(this); rtt->setActive(true); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index eb1714b61..164b51daf 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -33,6 +33,8 @@ #include #include +#include + #include #include @@ -260,8 +262,8 @@ void NIFLoader::createMaterial(const String &name, NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName); if (result.first) { - instance->setProperty("alpha_rejection_func", sh::makeProperty(new sh::StringValue("greater_equal"))); - instance->setProperty("alpha_rejection_value", sh::makeProperty(new sh::IntValue(result.second))); + instance->setProperty("alpha_rejection", + sh::makeProperty(new sh::StringValue("greater_equal " + boost::lexical_cast(result.second)))); } else { @@ -272,81 +274,34 @@ void NIFLoader::createMaterial(const String &name, } else warn("Unhandled alpha setting for texture " + texName); - } -/* - if (!texName.empty()) - { - Pass *pass = material->getTechnique(0)->getPass(0); - /*TextureUnitState *txt =*/ - /* - pass->createTextureUnitState(texName); - - pass->setVertexColourTracking(TVC_DIFFUSE); -*/ - // As of yet UNTESTED code from Chris: - /*pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); - pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL); - pass->setDepthCheckEnabled(true); + // As of yet UNTESTED code from Chris: + /*pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); + pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL); + pass->setDepthCheckEnabled(true); - // Add transparency if NiAlphaProperty was present - if (alphaFlags != -1) + // Add transparency if NiAlphaProperty was present + if (alphaFlags != -1) + { + std::cout << "Alpha flags set!" << endl; + if ((alphaFlags&1)) { - std::cout << "Alpha flags set!" << endl; - if ((alphaFlags&1)) - { - pass->setDepthWriteEnabled(false); - pass->setSceneBlending(getBlendFactor((alphaFlags>>1)&0xf), - getBlendFactor((alphaFlags>>5)&0xf)); - } - else - pass->setDepthWriteEnabled(true); - - if ((alphaFlags>>9)&1) - pass->setAlphaRejectSettings(getTestMode((alphaFlags>>10)&0x7), - alphaTest); - - pass->setTransparentSortingEnabled(!((alphaFlags>>13)&1)); + pass->setDepthWriteEnabled(false); + pass->setSceneBlending(getBlendFactor((alphaFlags>>1)&0xf), + getBlendFactor((alphaFlags>>5)&0xf)); } else - pass->setDepthWriteEnabled(true); */ + pass->setDepthWriteEnabled(true); -/* - // Add transparency if NiAlphaProperty was present - if (alphaFlags != -1) - { - // The 237 alpha flags are by far the most common. Check - // NiAlphaProperty in nif/property.h if you need to decode - // other values. 237 basically means normal transparencly. - if (alphaFlags == 237) - { - NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName); - if (result.first) - { - pass->setAlphaRejectFunction(CMPF_GREATER_EQUAL); - pass->setAlphaRejectValue(result.second); - } - else - { - // Enable transparency - pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); + if ((alphaFlags>>9)&1) + pass->setAlphaRejectSettings(getTestMode((alphaFlags>>10)&0x7), + alphaTest); - //pass->setDepthCheckEnabled(false); - pass->setDepthWriteEnabled(false); - //std::cout << "alpha 237; material: " << name << " texName: " << texName << std::endl; - } - } - else - warn("Unhandled alpha setting for texture " + texName); - } - else - { - material->getTechnique(0)->setShadowCasterMaterial("depth_shadow_caster_noalpha"); - } + pass->setTransparentSortingEnabled(!((alphaFlags>>13)&1)); } - */ - + else + pass->setDepthWriteEnabled(true); */ } // Takes a name and adds a unique part to it. This is just used to diff --git a/extern/shiny b/extern/shiny index 3a1b8e2ae..8af2cce2d 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 3a1b8e2aefa746ef0922e78f4be3ff736ce92fc3 +Subproject commit 8af2cce2db40aa67edcba78c534a268e595696c8 diff --git a/files/materials/core.h b/files/materials/core.h index fe0172cd9..0ee95057f 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -52,7 +52,7 @@ #endif #if SH_GLSL == 1 - @shGlslVersion(130) + @version 130 #define float2 vec2 #define float3 vec3 diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 3fcc12d62..fc23b947a 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -7,30 +7,35 @@ material openmw_objects_base has_vertex_colour false diffuseMap black.png fog true + mrt_output true + lighting true is_transparent false // real transparency, alpha rejection doesn't count here scene_blend default - alpha_rejection_value default - alpha_rejection_func default + alpha_rejection default pass { vertex_program openmw_objects_vertex fragment_program openmw_objects_fragment + shader_properties + { + fog $fog + mrt_output $mrt_output + lighting $lighting + has_vertex_colour $has_vertex_colour + is_transparent $is_transparent + } + diffuse $diffuse specular $specular ambient $ambient emissive $emissive - fog $fog + scene_blend $scene_blend + alpha_rejection $alpha_rejection ffp_vertex_colour_ambient $has_vertex_colour - has_vertex_colour $has_vertex_colour - - is_transparent $is_transparent - scene_blend $scene_blend - alpha_rejection_value $alpha_rejection_value - alpha_rejection_func $alpha_rejection_func texture_unit diffuseMap { diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 9a78bb455..3697e33e5 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -2,13 +2,10 @@ #define FOG @shPropertyBool(fog) -#define MRT @shPropertyNotBool(is_transparent) +#define MRT @shPropertyNotBool(is_transparent) && @shPropertyBool(mrt_output) +#define LIGHTING @shPropertyBool(lighting) -#if MRT -#define NEED_DEPTH -#endif - -#if FOG +#if FOG || MRT #define NEED_DEPTH #endif @@ -48,7 +45,9 @@ SH_BEGIN_PROGRAM shSampler2D(diffuseMap) shInput(float2, UV) +#if MRT shDeclareMrtOutput(1) +#endif shInput(float4, normalPassthrough) #ifdef NEED_DEPTH @@ -79,7 +78,9 @@ shOutputColor(0).xyz = shLerp (shOutputColor(0).xyz, fogColor, fogValue); #endif +#if MRT shOutputColor(1) = float4(depthPassthrough / far,1,1,1); +#endif } #endif From 71c865e2e99d205578720c9cd7f6cf67ff6b0306 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 6 Jul 2012 07:10:10 +0200 Subject: [PATCH 023/298] let there be light --- extern/shiny | 2 +- files/materials/objects.shader | 55 +++++++++++++++++++++++++--- files/materials/openmw.configuration | 15 ++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 files/materials/openmw.configuration diff --git a/extern/shiny b/extern/shiny index 8af2cce2d..ccaaefa59 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 8af2cce2db40aa67edcba78c534a268e595696c8 +Subproject commit ccaaefa59c0500cf790a1f3114c8b5593489afeb diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 3697e33e5..6dd05442c 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -9,6 +9,8 @@ #define NEED_DEPTH #endif +#define NUM_LIGHTS 8 + #define HAS_VERTEXCOLOR @shPropertyBool(has_vertex_colour) #ifdef SH_VERTEX_SHADER @@ -18,10 +20,15 @@ shInput(float2, uv0) shOutput(float2, UV) shNormalInput(float4) - shOutput(float4, normalPassthrough) #ifdef NEED_DEPTH shOutput(float, depthPassthrough) #endif + +#if LIGHTING + shOutput(float3, normalPassthrough) + shOutput(float3, objSpacePositionPassthrough) +#endif + #if HAS_VERTEXCOLOR shColourInput(float4) shOutput(float4, colorPassthrough) @@ -30,11 +37,15 @@ { shOutputPosition = shMatrixMult(wvp, shInputPosition); UV = uv0; - normalPassthrough = normal; + normalPassthrough = normal.xyz; #ifdef NEED_DEPTH depthPassthrough = shOutputPosition.z; #endif +#if LIGHTING + objSpacePositionPassthrough = shInputPosition.xyz; +#endif + #if HAS_VERTEXCOLOR colorPassthrough = colour; #endif @@ -48,13 +59,27 @@ #if MRT shDeclareMrtOutput(1) #endif - shInput(float4, normalPassthrough) #ifdef NEED_DEPTH shInput(float, depthPassthrough) #endif - + shUniform(float far) @shAutoConstant(far, far_clip_distance) + +#if LIGHTING + shInput(float3, normalPassthrough) + shInput(float3, objSpacePositionPassthrough) + shUniform(float4 lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) + shUniform(float passIteration) @shAutoConstant(passIteration, pass_iteration_number) + shUniform(float4 materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) + shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) + shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) + @shForeach(NUM_LIGHTS) + shUniform(float4 lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) + shUniform(float4 lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) + shUniform(float4 lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) + @shEndForeach +#endif #if FOG shUniform(float3 fogColor) @shAutoConstant(fogColor, fog_colour) @@ -66,9 +91,29 @@ #endif SH_START_PROGRAM { - //shOutputColor(0) = float4((normalize(normalPassthrough.xyz)+float3(1.0,1.0,1.0)) / 2.f, 1.0); shOutputColor(0) = shSample(diffuseMap, UV); +#if LIGHTING + float3 normal = normalize(normalPassthrough); + float3 lightDir, diffuse; + float d; + float3 ambient = materialAmbient.xyz * lightAmbient.xyz; + + @shForeach(NUM_LIGHTS) + + lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePositionPassthrough.xyz * lightPosObjSpace@shIterator.w); + d = length(lightDir); + + lightDir = normalize(lightDir); + + diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); + + @shEndForeach + + shOutputColor(0).xyz *= (ambient + diffuse + materialEmissive.xyz); +#endif + + #if HAS_VERTEXCOLOR shOutputColor(0).xyz *= colorPassthrough.xyz; #endif diff --git a/files/materials/openmw.configuration b/files/materials/openmw.configuration new file mode 100644 index 000000000..6122cd8cb --- /dev/null +++ b/files/materials/openmw.configuration @@ -0,0 +1,15 @@ +configuration water_reflection +{ + fog false + receives_shadows false + mrt_output false +} + +configuration local_map +{ + fog false + receives_shadows false + simple_water true + mrt_output false + lighting false +} From a095572205f2bd153784b7d0cf9930e0247a8667 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 6 Jul 2012 10:31:48 +0200 Subject: [PATCH 024/298] fixed some textures, started with sky --- apps/openmw/mwrender/renderingmanager.cpp | 2 - apps/openmw/mwrender/sky.cpp | 41 ++++++++++++-------- components/nifogre/ogre_nif_loader.cpp | 1 + extern/shiny | 2 +- files/materials/atmosphere.shader | 38 +++++++++++++++++++ files/materials/atmosphere.shaderset | 15 ++++++++ files/materials/clouds.shader | 38 +++++++++++++++++++ files/materials/clouds.shaderset | 15 ++++++++ files/materials/objects.mat | 2 +- files/materials/objects.shader | 11 +++++- files/materials/sky.mat | 46 +++++++++++++++++++++++ files/materials/sun.shader | 39 +++++++++++++++++++ files/materials/sun.shaderset | 15 ++++++++ 13 files changed, 244 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bfa27e42f..174a9e4f8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -270,12 +270,10 @@ void RenderingManager::setWaterHeight(const float height) void RenderingManager::skyEnable () { - /* if(mSkyManager) mSkyManager->enable(); mOcclusionQuery->setSunNode(mSkyManager->getSunNode()); - */ } void RenderingManager::skyDisable () diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index e1e8d6a5c..0a866ac7c 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -416,7 +416,7 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) void SkyManager::create() { /// \todo preload all the textures and meshes that are used for sky rendering -/* + // Create overlay used for thunderstorm MaterialPtr material = MaterialManager::getSingleton().create( "ThunderMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME ); Pass* pass = material->getTechnique(0)->getPass(0); @@ -517,6 +517,7 @@ void SkyManager::create() stars_fp->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); stars_fp->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); + /* for (unsigned int i=0; igetNumSubEntities(); ++i) { MaterialPtr mp = night1_ent->getSubEntity(i)->getMaterial(); @@ -531,6 +532,7 @@ void SkyManager::create() mp->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); mStarsMaterials[i] = mp; } + */ // Atmosphere (day) mesh = NifOgre::NIFLoader::load("meshes\\sky_atmosphere.nif"); @@ -543,8 +545,9 @@ void SkyManager::create() atmosphere_ent->setVisibilityFlags(RV_Sky); mAtmosphereDay = mRootNode->createChildSceneNode(); mAtmosphereDay->attachObject(atmosphere_ent); - mAtmosphereMaterial = atmosphere_ent->getSubEntity(0)->getMaterial(); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); + atmosphere_ent->getSubEntity (0)->setMaterialName ("openmw_atmosphere"); + //mAtmosphereMaterial = atmosphere_ent->getSubEntity(0)->getMaterial(); + //mAtmosphereMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); // Atmosphere shader HighLevelGpuProgramPtr vshader = mgr.createProgram("Atmosphere_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, @@ -570,7 +573,7 @@ void SkyManager::create() vshader->load(); vshader->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName()); + //mAtmosphereMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName()); HighLevelGpuProgramPtr fshader = mgr.createProgram("Atmosphere_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_FRAGMENT_PROGRAM); @@ -598,7 +601,7 @@ void SkyManager::create() fshader->load(); fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); + // mAtmosphereMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); // Clouds NifOgre::NIFLoader::load("meshes\\sky_clouds_01.nif"); @@ -607,8 +610,9 @@ void SkyManager::create() clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); SceneNode* clouds_node = mRootNode->createChildSceneNode(); clouds_node->attachObject(clouds_ent); - mCloudMaterial = clouds_ent->getSubEntity(0)->getMaterial(); - mCloudMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); + //mCloudMaterial = clouds_ent->getSubEntity(0)->getMaterial(); + clouds_ent->getSubEntity(0)->setMaterialName ("openmw_clouds"); + //mCloudMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); clouds_ent->setCastShadows(false); // Clouds vertex shader @@ -635,7 +639,7 @@ void SkyManager::create() vshader2->setSource(outStream3.str()); vshader2->load(); vshader2->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - mCloudMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader2->getName()); + //mCloudMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader2->getName()); // Clouds fragment shader mCloudFragmentShader = mgr.createProgram("Clouds_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, @@ -670,17 +674,19 @@ void SkyManager::create() mCloudFragmentShader->setSource(outStream2.str()); mCloudFragmentShader->load(); mCloudFragmentShader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - mCloudMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(mCloudFragmentShader->getName()); + //mCloudMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(mCloudFragmentShader->getName()); setCloudsOpacity(0.75); ModVertexAlpha(clouds_ent, 1); // I'm not sure if the materials are being used by any other objects // Make a unique "modifiable" copy of the materials to be sure - mCloudMaterial = mCloudMaterial->clone("Clouds"); - clouds_ent->getSubEntity(0)->setMaterial(mCloudMaterial); - mAtmosphereMaterial = mAtmosphereMaterial->clone("Atmosphere"); - atmosphere_ent->getSubEntity(0)->setMaterial(mAtmosphereMaterial); + //mCloudMaterial = mCloudMaterial->clone("Clouds"); + //clouds_ent->getSubEntity(0)->setMaterial(mCloudMaterial); + //mAtmosphereMaterial = mAtmosphereMaterial->clone("Atmosphere"); + //atmosphere_ent->getSubEntity(0)->setMaterial(mAtmosphereMaterial); + + /* mAtmosphereMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); mAtmosphereMaterial->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, 0.0); @@ -694,9 +700,9 @@ void SkyManager::create() mCloudMaterial->getTechnique(0)->getPass(0)->removeAllTextureUnitStates(); mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("textures\\tx_sky_cloudy.dds"); mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); +*/ mCreated = true; - */ } SkyManager::~SkyManager() @@ -724,7 +730,7 @@ void SkyManager::update(float duration) if (!mEnabled) return; // UV Scroll the clouds - mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstantFromTime("time", MWBase::Environment::get().getWorld()->getTimeScaleFactor()/30.f); + //mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstantFromTime("time", MWBase::Environment::get().getWorld()->getTimeScaleFactor()/30.f); /// \todo improve this mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); @@ -791,12 +797,14 @@ void SkyManager::setMoonColour (bool red) void SkyManager::setCloudsOpacity(float opacity) { if (!mCreated) return; - mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("opacity", Real(opacity)); + //mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("opacity", Real(opacity)); } void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { if (!mCreated) return; + + /* if (mClouds != weather.mCloudTexture) { mCloudMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("textures\\"+weather.mCloudTexture); @@ -857,6 +865,7 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) mStarsOpacity = weather.mNightFade; } } + */ float strength; float timeofday_angle = std::abs(mSunGlare->getPosition().z/mSunGlare->getPosition().length()); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 164b51daf..76562256f 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -34,6 +34,7 @@ #include #include +#include #include diff --git a/extern/shiny b/extern/shiny index ccaaefa59..1d689a23f 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit ccaaefa59c0500cf790a1f3114c8b5593489afeb +Subproject commit 1d689a23fa685ab680351219c32953a3bed7ac0c diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader index e69de29bb..a772e3c5e 100644 --- a/files/materials/atmosphere.shader +++ b/files/materials/atmosphere.shader @@ -0,0 +1,38 @@ +#include "core.h" + +#define MRT @shPropertyBool(mrt_output) + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shInput(float2, uv0) + shOutput(float2, UV) + + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + } + +#else + + SH_BEGIN_PROGRAM + shSampler2D(diffuseMap) + shInput(float2, UV) +#if MRT + shDeclareMrtOutput(1) +#endif + shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) + shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) + + SH_START_PROGRAM + { + shOutputColor(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); + +#if MRT + shOutputColor(1) = float4(1,1,1,1); +#endif + } + +#endif diff --git a/files/materials/atmosphere.shaderset b/files/materials/atmosphere.shaderset index e69de29bb..54108dbba 100644 --- a/files/materials/atmosphere.shaderset +++ b/files/materials/atmosphere.shaderset @@ -0,0 +1,15 @@ +shader_set atmosphere_vertex +{ + source atmosphere.shader + type vertex + profiles_cg vs_2_0 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set atmosphere_fragment +{ + source atmosphere.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_hlsl ps_2_0 +} diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index e69de29bb..a772e3c5e 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -0,0 +1,38 @@ +#include "core.h" + +#define MRT @shPropertyBool(mrt_output) + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shInput(float2, uv0) + shOutput(float2, UV) + + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + } + +#else + + SH_BEGIN_PROGRAM + shSampler2D(diffuseMap) + shInput(float2, UV) +#if MRT + shDeclareMrtOutput(1) +#endif + shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) + shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) + + SH_START_PROGRAM + { + shOutputColor(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); + +#if MRT + shOutputColor(1) = float4(1,1,1,1); +#endif + } + +#endif diff --git a/files/materials/clouds.shaderset b/files/materials/clouds.shaderset index e69de29bb..5fffb5658 100644 --- a/files/materials/clouds.shaderset +++ b/files/materials/clouds.shaderset @@ -0,0 +1,15 @@ +shader_set clouds_vertex +{ + source clouds.shader + type vertex + profiles_cg vs_2_0 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set clouds_fragment +{ + source clouds.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_hlsl ps_2_0 +} diff --git a/files/materials/objects.mat b/files/materials/objects.mat index fc23b947a..6dfe9db7c 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -39,7 +39,7 @@ material openmw_objects_base texture_unit diffuseMap { - texture $diffuseMap + direct_texture $diffuseMap create_in_ffp true } } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 6dd05442c..e066b43cc 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -64,7 +64,9 @@ shInput(float, depthPassthrough) #endif +#if MRT shUniform(float far) @shAutoConstant(far, far_clip_distance) +#endif #if LIGHTING shInput(float3, normalPassthrough) @@ -109,12 +111,16 @@ diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); @shEndForeach + +#if HAS_VERTEXCOLOR + ambient *= colorPassthrough.xyz; +#endif shOutputColor(0).xyz *= (ambient + diffuse + materialEmissive.xyz); #endif -#if HAS_VERTEXCOLOR +#if HAS_VERTEXCOLOR && !LIGHTING shOutputColor(0).xyz *= colorPassthrough.xyz; #endif @@ -123,6 +129,9 @@ shOutputColor(0).xyz = shLerp (shOutputColor(0).xyz, fogColor, fogValue); #endif + // prevent negative color output (for example with negative lights) + shOutputColor(0).xyz = max(shOutputColor(0).xyz, float3(0,0,0)); + #if MRT shOutputColor(1) = float4(depthPassthrough / far,1,1,1); #endif diff --git a/files/materials/sky.mat b/files/materials/sky.mat index a82816438..25c9185b7 100644 --- a/files/materials/sky.mat +++ b/files/materials/sky.mat @@ -1,10 +1,18 @@ material openmw_moon { + mrt_output true pass { vertex_program moon_vertex fragment_program moon_fragment + polygon_mode_overrideable off + + shader_properties + { + mrt_output $mrt_output + } + texture_unit diffuseMap { texture $diffuseMap @@ -15,10 +23,21 @@ material openmw_moon material openmw_clouds { + mrt_output true pass { vertex_program clouds_vertex fragment_program clouds_fragment + + polygon_mode_overrideable off + + scene_blend alpha_blend + depth_write off + + shader_properties + { + mrt_output $mrt_output + } // second diffuse map is used for weather transitions texture_unit diffuseMap1 @@ -36,19 +55,38 @@ material openmw_clouds material openmw_atmosphere { + mrt_output true pass { vertex_program atmosphere_vertex fragment_program atmosphere_fragment + + polygon_mode_overrideable off + + scene_blend alpha_blend + depth_write off + + shader_properties + { + mrt_output $mrt_output + } } } material openmw_stars { + mrt_output true pass { vertex_program stars_vertex fragment_program stars_fragment + + polygon_mode_overrideable off + + shader_properties + { + mrt_output $mrt_output + } texture_unit diffuseMap { @@ -60,10 +98,18 @@ material openmw_stars // used for both sun and sun glare material openmw_sun { + mrt_output true pass { vertex_program sun_vertex fragment_program sun_fragment + + polygon_mode_overrideable off + + shader_properties + { + mrt_output $mrt_output + } texture unit diffuseMap { diff --git a/files/materials/sun.shader b/files/materials/sun.shader index e69de29bb..811d45031 100644 --- a/files/materials/sun.shader +++ b/files/materials/sun.shader @@ -0,0 +1,39 @@ +#include "core.h" + +#define MRT @shPropertyBool(mrt_output) + + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shInput(float2, uv0) + shOutput(float2, UV) + + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + } + +#else + + SH_BEGIN_PROGRAM + shSampler2D(diffuseMap) + shInput(float2, UV) +#if MRT + shDeclareMrtOutput(1) +#endif + shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) + shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) + + SH_START_PROGRAM + { + shOutputColor(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); + +#if MRT + shOutputColor(1) = float4(1,1,1,1); +#endif + } + +#endif diff --git a/files/materials/sun.shaderset b/files/materials/sun.shaderset index e69de29bb..1b9e92a43 100644 --- a/files/materials/sun.shaderset +++ b/files/materials/sun.shaderset @@ -0,0 +1,15 @@ +shader_set sun_vertex +{ + source sun.shader + type vertex + profiles_cg vs_2_0 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set sun_fragment +{ + source sun.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_hlsl ps_2_0 +} From 771863e73bb3d2c68392851daaac630fdc4b078d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 6 Jul 2012 15:50:26 +0200 Subject: [PATCH 025/298] Issue #324: Started turning NpcStats into a proper class; fixed a cmake script bug; fixed a namespace issue --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwclass/npc.cpp | 5 +++-- apps/openmw/mwinput/inputmanager.cpp | 18 +++++++++--------- apps/openmw/mwmechanics/drawstate.hpp | 13 ++++++++----- apps/openmw/mwmechanics/npcstats.cpp | 17 +++++++++++++++++ apps/openmw/mwmechanics/npcstats.hpp | 20 +++++++++++++------- apps/openmw/mwworld/player.cpp | 8 ++++---- apps/openmw/mwworld/player.hpp | 4 ++-- 8 files changed, 57 insertions(+), 30 deletions(-) create mode 100644 apps/openmw/mwmechanics/npcstats.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 9534ecc90..36e59f6bd 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -60,7 +60,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanager stat creaturestats magiceffects movement actors drawstate spells - activespells + activespells npcstats ) add_openmw_dir (mwbase diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d4f711885..3a91ec63f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -233,7 +233,8 @@ namespace MWClass case Combat: - stats.mCombat = set; + // Combat stance ignored for now; need to be determined based on draw state instead of + // being maunally set. break; } } @@ -260,7 +261,7 @@ namespace MWClass case Combat: - return stats.mCombat; + return false; } return false; diff --git a/apps/openmw/mwinput/inputmanager.cpp b/apps/openmw/mwinput/inputmanager.cpp index a2bafcbf7..73cadacd0 100644 --- a/apps/openmw/mwinput/inputmanager.cpp +++ b/apps/openmw/mwinput/inputmanager.cpp @@ -102,15 +102,15 @@ private: { if (windows.isGuiMode()) return; - DrawState state = player.getDrawState(); - if (state == DrawState_Weapon || state == DrawState_Nothing) + MWMechanics::DrawState state = player.getDrawState(); + if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing) { - player.setDrawState(DrawState_Spell); + player.setDrawState(MWMechanics::DrawState_Spell); std::cout << "Player has now readied his hands for spellcasting!\n"; } else { - player.setDrawState(DrawState_Nothing); + player.setDrawState(MWMechanics::DrawState_Nothing); std::cout << "Player does not have any kind of attack ready now.\n"; } } @@ -119,15 +119,15 @@ private: { if (windows.isGuiMode()) return; - DrawState state = player.getDrawState(); - if (state == DrawState_Spell || state == DrawState_Nothing) + MWMechanics::DrawState state = player.getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) { - player.setDrawState(DrawState_Weapon); + player.setDrawState(MWMechanics::DrawState_Weapon); std::cout << "Player is now drawing his weapon.\n"; } else { - player.setDrawState(DrawState_Nothing); + player.setDrawState(MWMechanics::DrawState_Nothing); std::cout << "Player does not have any kind of attack ready now.\n"; } } @@ -336,7 +336,7 @@ private: poller.bind(A_MoveRight, KC_D); poller.bind(A_MoveForward, KC_W); poller.bind(A_MoveBackward, KC_S); - + poller.bind(A_Jump, KC_E); poller.bind(A_Crouch, KC_LCONTROL); } diff --git a/apps/openmw/mwmechanics/drawstate.hpp b/apps/openmw/mwmechanics/drawstate.hpp index 772086d90..94b48fdd8 100644 --- a/apps/openmw/mwmechanics/drawstate.hpp +++ b/apps/openmw/mwmechanics/drawstate.hpp @@ -3,11 +3,14 @@ #undef DrawState -enum DrawState +namespace MWMechanics { - DrawState_Weapon = 0, - DrawState_Spell = 1, - DrawState_Nothing = 2, -}; + enum DrawState + { + DrawState_Weapon = 0, + DrawState_Spell = 1, + DrawState_Nothing = 2, + }; +} #endif diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp new file mode 100644 index 000000000..70db00bb7 --- /dev/null +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -0,0 +1,17 @@ + +#include "npcstats.hpp" + +MWMechanics::NpcStats::NpcStats() +: mForceRun (false), mForceSneak (false), mRun (false), mSneak (false), + mDrawState (DrawState_Nothing) +{} + +MWMechanics::DrawState MWMechanics::NpcStats::getDrawState() const +{ + return mDrawState; +} + +void MWMechanics::NpcStats::setDrawState (DrawState state) +{ + mDrawState = state; +} diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 972863b72..c2907ccb5 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "stat.hpp" #include "drawstate.hpp" @@ -16,10 +17,14 @@ namespace MWMechanics /// \note For technical reasons the spell list and the currently selected spell is also handled by /// CreatureStats, even though they are actually NPC stats. - struct NpcStats + class NpcStats { - // NPCs other than the player can only have one faction. But for the sake of consistency - // we use the same data structure for the PC and the NPCs. + DrawState mDrawState; + + public: + + /// NPCs other than the player can only have one faction. But for the sake of consistency + /// we use the same data structure for the PC and the NPCs. /// \note the faction key must be in lowercase std::map mFactionRank; @@ -29,11 +34,12 @@ namespace MWMechanics bool mForceSneak; bool mRun; bool mSneak; - bool mCombat; - DrawState mDrawState; - NpcStats() : mForceRun (false), mForceSneak (false), mRun (false), mSneak (false), - mCombat (false) , mDrawState(DrawState_Nothing) {} + NpcStats(); + + DrawState getDrawState() const; + + void setDrawState (DrawState state); }; } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 0eeebad4e..d8b3285ef 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -56,10 +56,10 @@ namespace MWWorld mClass = new_class; } - void Player::setDrawState(const DrawState& value) + void Player::setDrawState (MWMechanics::DrawState state) { MWWorld::Ptr ptr = getPlayer(); - MWWorld::Class::get(ptr).getNpcStats(ptr).mDrawState = value; + MWWorld::Class::get(ptr).getNpcStats(ptr).setDrawState (state); } void Player::setAutoMove (bool enable) @@ -111,10 +111,10 @@ namespace MWWorld MWWorld::Class::get (ptr).setStance (ptr, MWWorld::Class::Run, !running); } - DrawState Player::getDrawState() + MWMechanics::DrawState Player::getDrawState() { MWWorld::Ptr ptr = getPlayer(); - return MWWorld::Class::get(ptr).getNpcStats(ptr).mDrawState; + return MWWorld::Class::get(ptr).getNpcStats(ptr).getDrawState(); } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index b1692bd81..2c87cf91b 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -83,7 +83,7 @@ namespace MWWorld void setClass (const ESM::Class& class_); - void setDrawState(const DrawState& state); + void setDrawState (MWMechanics::DrawState state); std::string getName() const { @@ -115,7 +115,7 @@ namespace MWWorld return mAutoMove; } - DrawState getDrawState(); + MWMechanics::DrawState getDrawState(); /// \todo constness void setAutoMove (bool enable); From d30ba14a176956757610206f2231ec15bd222243 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 6 Jul 2012 18:23:48 +0200 Subject: [PATCH 026/298] Issue #324: Finished turning NpcStats into a proper class --- apps/openmw/mwclass/npc.cpp | 22 ++++---- apps/openmw/mwdialogue/dialoguemanager.cpp | 18 +++--- apps/openmw/mwgui/stats_window.cpp | 2 +- apps/openmw/mwmechanics/mechanicsmanager.cpp | 20 +++---- apps/openmw/mwmechanics/npcstats.cpp | 44 ++++++++++++++- apps/openmw/mwmechanics/npcstats.hpp | 41 ++++++++++---- apps/openmw/mwmechanics/spellsuccess.hpp | 4 +- apps/openmw/mwscript/controlextensions.cpp | 58 ++++++++------------ apps/openmw/mwscript/statsextensions.cpp | 30 +++++----- apps/openmw/mwworld/inventorystore.cpp | 4 +- 10 files changed, 144 insertions(+), 99 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 3a91ec63f..d54b9441d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -67,11 +67,11 @@ namespace MWClass boost::algorithm::to_lower(faction); if(ref->base->npdt52.gold != -10) { - data->mNpcStats.mFactionRank[faction] = (int)ref->base->npdt52.rank; + data->mNpcStats.getFactionRanks()[faction] = (int)ref->base->npdt52.rank; } else { - data->mNpcStats.mFactionRank[faction] = (int)ref->base->npdt12.rank; + data->mNpcStats.getFactionRanks()[faction] = (int)ref->base->npdt12.rank; } } @@ -79,7 +79,7 @@ namespace MWClass if(ref->base->npdt52.gold != -10) { for (int i=0; i<27; ++i) - data->mNpcStats.mSkill[i].setBase (ref->base->npdt52.skills[i]); + data->mNpcStats.getSkill (i).setBase (ref->base->npdt52.skills[i]); data->mCreatureStats.mAttributes[0].set (ref->base->npdt52.strength); data->mCreatureStats.mAttributes[1].set (ref->base->npdt52.intelligence); @@ -201,12 +201,12 @@ namespace MWClass { case Run: - stats.mForceRun = force; + stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceRun, force); break; case Sneak: - stats.mForceSneak = force; + stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak, force); break; case Combat: @@ -223,12 +223,12 @@ namespace MWClass { case Run: - stats.mRun = set; + stats.setMovementFlag (MWMechanics::NpcStats::Flag_Run, set); break; case Sneak: - stats.mSneak = set; + stats.setMovementFlag (MWMechanics::NpcStats::Flag_Sneak, set); break; case Combat: @@ -247,17 +247,17 @@ namespace MWClass { case Run: - if (!ignoreForce && stats.mForceRun) + if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)) return true; - return stats.mRun; + return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Run); case Sneak: - if (!ignoreForce && stats.mForceSneak) + if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)) return true; - return stats.mSneak; + return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Sneak); case Combat: diff --git a/apps/openmw/mwdialogue/dialoguemanager.cpp b/apps/openmw/mwdialogue/dialoguemanager.cpp index 98562c053..64a976d40 100644 --- a/apps/openmw/mwdialogue/dialoguemanager.cpp +++ b/apps/openmw/mwdialogue/dialoguemanager.cpp @@ -203,10 +203,10 @@ namespace MWDialogue MWMechanics::NpcStats PCstats = MWWorld::Class::get(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()).getNpcStats(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); MWMechanics::NpcStats NPCstats = MWWorld::Class::get(actor).getNpcStats(actor); int sameFaction = 0; - if(!NPCstats.mFactionRank.empty()) + if(!NPCstats.getFactionRanks().empty()) { - std::string NPCFaction = NPCstats.mFactionRank.begin()->first; - if(PCstats.mFactionRank.find(toLower(NPCFaction)) != PCstats.mFactionRank.end()) sameFaction = 1; + std::string NPCFaction = NPCstats.getFactionRanks().begin()->first; + if(PCstats.getFactionRanks().find(toLower(NPCFaction)) != PCstats.getFactionRanks().end()) sameFaction = 1; } if(!selectCompare(comp,sameFaction,select.i)) return false; } @@ -525,8 +525,8 @@ namespace MWDialogue //MWWorld::Class npcClass = MWWorld::Class::get(actor); MWMechanics::NpcStats stats = MWWorld::Class::get(actor).getNpcStats(actor); - std::map::iterator it = stats.mFactionRank.find(toLower(info.npcFaction)); - if(it!=stats.mFactionRank.end()) + std::map::iterator it = stats.getFactionRanks().find(toLower(info.npcFaction)); + if(it!=stats.getFactionRanks().end()) { //check rank if(it->second < (int)info.data.rank) return false; @@ -542,8 +542,8 @@ namespace MWDialogue if(!info.pcFaction.empty()) { MWMechanics::NpcStats stats = MWWorld::Class::get(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()).getNpcStats(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - std::map::iterator it = stats.mFactionRank.find(toLower(info.pcFaction)); - if(it!=stats.mFactionRank.end()) + std::map::iterator it = stats.getFactionRanks().find(toLower(info.pcFaction)); + if(it!=stats.getFactionRanks().end()) { //check rank if(it->second < (int)info.data.PCrank) return false; @@ -903,13 +903,13 @@ namespace MWDialogue std::string factionID(""); MWMechanics::NpcStats stats = MWWorld::Class::get(mActor).getNpcStats(mActor); - if(stats.mFactionRank.empty()) + if(stats.getFactionRanks().empty()) { std::cout << "No faction for this actor!"; } else { - factionID = stats.mFactionRank.begin()->first; + factionID = stats.getFactionRanks().begin()->first; } return factionID; } diff --git a/apps/openmw/mwgui/stats_window.cpp b/apps/openmw/mwgui/stats_window.cpp index 0f8b41b28..be99982d9 100644 --- a/apps/openmw/mwgui/stats_window.cpp +++ b/apps/openmw/mwgui/stats_window.cpp @@ -270,7 +270,7 @@ void StatsWindow::onFrame () MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWMechanics::NpcStats PCstats = MWWorld::Class::get(player).getNpcStats(player); - setFactions(PCstats.mFactionRank); + setFactions(PCstats.getFactionRanks()); setBirthSign(MWBase::Environment::get().getWorld()->getPlayer().getBirthsign()); diff --git a/apps/openmw/mwmechanics/mechanicsmanager.cpp b/apps/openmw/mwmechanics/mechanicsmanager.cpp index 13052d064..ada05a11b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanager.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanager.cpp @@ -28,7 +28,7 @@ namespace MWMechanics creatureStats.mMagicEffects = MagicEffects(); for (int i=0; i<27; ++i) - npcStats.mSkill[i].setBase (player->npdt52.skills[i]); + npcStats.getSkill (i).setBase (player->npdt52.skills[i]); creatureStats.mAttributes[0].setBase (player->npdt52.strength); creatureStats.mAttributes[1].setBase (player->npdt52.intelligence); @@ -73,8 +73,8 @@ namespace MWMechanics if (index>=0 && index<27) { - npcStats.mSkill[index].setBase ( - npcStats.mSkill[index].getBase() + race->data.bonus[i].bonus); + npcStats.getSkill (index).setBase ( + npcStats.getSkill (index).getBase() + race->data.bonus[i].bonus); } } @@ -124,8 +124,8 @@ namespace MWMechanics if (index>=0 && index<27) { - npcStats.mSkill[index].setBase ( - npcStats.mSkill[index].getBase() + bonus); + npcStats.getSkill (index).setBase ( + npcStats.getSkill (index).getBase() + bonus); } } } @@ -141,8 +141,8 @@ namespace MWMechanics if (index>=0 && index<27) { - npcStats.mSkill[index].setBase ( - npcStats.mSkill[index].getBase() + 5); + npcStats.getSkill (index).setBase ( + npcStats.getSkill (index).getBase() + 5); } } } @@ -236,11 +236,11 @@ namespace MWMechanics //Loop over ESM::Skill::SkillEnum for(int i = 0; i < 27; ++i) { - if(npcStats.mSkill[i] != mWatchedNpc.mSkill[i]) + if(npcStats.getSkill (i) != mWatchedNpc.getSkill (i)) { update = true; - mWatchedNpc.mSkill[i] = npcStats.mSkill[i]; - MWBase::Environment::get().getWindowManager()->setValue((ESM::Skill::SkillEnum)i, npcStats.mSkill[i]); + mWatchedNpc.getSkill (i) = npcStats.getSkill (i); + MWBase::Environment::get().getWindowManager()->setValue((ESM::Skill::SkillEnum)i, npcStats.getSkill (i)); } } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 70db00bb7..cdb977029 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -1,9 +1,10 @@ #include "npcstats.hpp" +#include + MWMechanics::NpcStats::NpcStats() -: mForceRun (false), mForceSneak (false), mRun (false), mSneak (false), - mDrawState (DrawState_Nothing) +: mMovementFlags (0), mDrawState (DrawState_Nothing) {} MWMechanics::DrawState MWMechanics::NpcStats::getDrawState() const @@ -15,3 +16,42 @@ void MWMechanics::NpcStats::setDrawState (DrawState state) { mDrawState = state; } + +bool MWMechanics::NpcStats::getMovementFlag (Flag flag) const +{ + return mMovementFlags & flag; +} + +void MWMechanics::NpcStats::setMovementFlag (Flag flag, bool state) +{ + if (state) + mMovementFlags |= flag; + else + mMovementFlags &= ~flag; +} + +const MWMechanics::Stat& MWMechanics::NpcStats::getSkill (int index) const +{ + if (index<0 || index>=27) + throw std::runtime_error ("skill index out of range"); + + return mSkill[index]; +} + +MWMechanics::Stat& MWMechanics::NpcStats::getSkill (int index) +{ + if (index<0 || index>=27) + throw std::runtime_error ("skill index out of range"); + + return mSkill[index]; +} + +std::map& MWMechanics::NpcStats::getFactionRanks() +{ + return mFactionRank; +} + +const std::map& MWMechanics::NpcStats::getFactionRanks() const +{ + return mFactionRank; +} diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index c2907ccb5..16a777b71 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -19,27 +19,46 @@ namespace MWMechanics class NpcStats { - DrawState mDrawState; - public: - /// NPCs other than the player can only have one faction. But for the sake of consistency - /// we use the same data structure for the PC and the NPCs. - /// \note the faction key must be in lowercase - std::map mFactionRank; + enum Flag + { + Flag_ForceRun = 1, + Flag_ForceSneak = 2, + Flag_Run = 4, + Flag_Sneak = 8 + }; + + private: - Stat mSkill[27]; + /// NPCs other than the player can only have one faction. But for the sake of consistency + /// we use the same data structure for the PC and the NPCs. + /// \note the faction key must be in lowercase + std::map mFactionRank; - bool mForceRun; - bool mForceSneak; - bool mRun; - bool mSneak; + DrawState mDrawState; + unsigned int mMovementFlags; + Stat mSkill[27]; + + public: NpcStats(); DrawState getDrawState() const; void setDrawState (DrawState state); + + bool getMovementFlag (Flag flag) const; + + void setMovementFlag (Flag flag, bool state); + + const Stat& getSkill (int index) const; + + Stat& getSkill (int index); + + std::map& getFactionRanks(); + + const std::map& getFactionRanks() const; }; } diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp index 54172a72a..42a7d5bba 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -41,7 +41,7 @@ namespace MWMechanics { const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().magicEffects.find(it->effectID); int _school = effect->data.school; - int _skillLevel = stats.mSkill[spellSchoolToSkill(_school)].getModified(); + int _skillLevel = stats.getSkill (spellSchoolToSkill(_school)).getModified(); if (school == -1) { @@ -78,7 +78,7 @@ namespace MWMechanics NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); - int skillLevel = stats.mSkill[getSpellSchool(spellId, actor)].getModified(); + int skillLevel = stats.getSkill (getSpellSchool(spellId, actor)).getModified(); // Sound magic effect (reduces spell casting chance) int soundMagnitude = creatureStats.mMagicEffects.get (MWMechanics::EffectKey (48)).mMagnitude; diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 1f5bdcafb..24d8bcf1e 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -59,54 +59,36 @@ namespace MWScript }; template - class OpClearForceRun : public Interpreter::Opcode0 + class OpClearMovementFlag : public Interpreter::Opcode0 { - public: - - virtual void execute (Interpreter::Runtime& runtime) - { - MWWorld::Ptr ptr = R()(runtime); + MWMechanics::NpcStats::Flag mFlag; - MWWorld::Class::get (ptr).getNpcStats (ptr).mForceRun = false; - } - }; - - template - class OpForceRun : public Interpreter::Opcode0 - { public: - virtual void execute (Interpreter::Runtime& runtime) - { - MWWorld::Ptr ptr = R()(runtime); - - MWWorld::Class::get (ptr).getNpcStats (ptr).mForceRun = true; - } - }; - - template - class OpClearForceSneak : public Interpreter::Opcode0 - { - public: + OpClearMovementFlag (MWMechanics::NpcStats::Flag flag) : mFlag (flag) {} virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - MWWorld::Class::get (ptr).getNpcStats (ptr).mForceSneak = false; + MWWorld::Class::get (ptr).getNpcStats (ptr).setMovementFlag (mFlag, false); } }; template - class OpForceSneak : public Interpreter::Opcode0 + class OpSetMovementFlag : public Interpreter::Opcode0 { + MWMechanics::NpcStats::Flag mFlag; + public: + OpSetMovementFlag (MWMechanics::NpcStats::Flag flag) : mFlag (flag) {} + virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - MWWorld::Class::get (ptr).getNpcStats (ptr).mForceSneak = true; + MWWorld::Class::get (ptr).getNpcStats (ptr).setMovementFlag (mFlag, true); } }; @@ -165,19 +147,23 @@ namespace MWScript interpreter.installSegment5 (opcodeToggleCollision, new OpToggleCollision); - interpreter.installSegment5 (opcodeClearForceRun, new OpClearForceRun); - interpreter.installSegment5 (opcodeForceRun, new OpForceRun); - interpreter.installSegment5 (opcodeClearForceSneak, new OpClearForceSneak); - interpreter.installSegment5 (opcodeForceSneak, new OpForceSneak); + interpreter.installSegment5 (opcodeClearForceRun, + new OpClearMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); + interpreter.installSegment5 (opcodeForceRun, + new OpSetMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); + interpreter.installSegment5 (opcodeClearForceSneak, + new OpClearMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); + interpreter.installSegment5 (opcodeForceSneak, + new OpSetMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); interpreter.installSegment5 (opcodeClearForceRunExplicit, - new OpClearForceRun); + new OpClearMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); interpreter.installSegment5 (opcodeForceRunExplicit, - new OpForceRun); + new OpSetMovementFlag (MWMechanics::NpcStats::Flag_ForceRun)); interpreter.installSegment5 (opcodeClearForceSneakExplicit, - new OpClearForceSneak); + new OpClearMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); interpreter.installSegment5 (opcodeForceSneakExplicit, - new OpForceSneak); + new OpSetMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak)); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 15519b3a6..42781d766 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -237,7 +237,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = - MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex]. + MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). getModified(); runtime.push (value); @@ -260,7 +260,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex]. + MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). setModified (value, 0); } }; @@ -281,10 +281,10 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - value += MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex]. + value += MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). getModified(); - MWWorld::Class::get (ptr).getNpcStats (ptr).mSkill[mIndex]. + MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). setModified (value, 0, 100); } }; @@ -373,9 +373,9 @@ namespace MWScript if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - if(MWWorld::Class::get(player).getNpcStats(player).mFactionRank.find(factionID) == MWWorld::Class::get(player).getNpcStats(player).mFactionRank.end()) + if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) == MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) { - MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] = 0; + MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = 0; } } } @@ -402,13 +402,13 @@ namespace MWScript if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - if(MWWorld::Class::get(player).getNpcStats(player).mFactionRank.find(factionID) == MWWorld::Class::get(player).getNpcStats(player).mFactionRank.end()) + if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) == MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) { - MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] = 0; + MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = 0; } else { - MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] = MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] +1; + MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] +1; } } } @@ -435,9 +435,9 @@ namespace MWScript if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - if(MWWorld::Class::get(player).getNpcStats(player).mFactionRank.find(factionID) != MWWorld::Class::get(player).getNpcStats(player).mFactionRank.end()) + if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) != MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) { - MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] = MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID] -1; + MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] -1; } } } @@ -460,22 +460,22 @@ namespace MWScript } else { - if(MWWorld::Class::get(ptr).getNpcStats(ptr).mFactionRank.empty()) + if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) { //throw exception? } else { - factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).mFactionRank.begin()->first; + factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; } } boost::algorithm::to_lower(factionID); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); if(factionID!="") { - if(MWWorld::Class::get(player).getNpcStats(player).mFactionRank.find(factionID) != MWWorld::Class::get(player).getNpcStats(player).mFactionRank.end()) + if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) != MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) { - runtime.push(MWWorld::Class::get(player).getNpcStats(player).mFactionRank[factionID]); + runtime.push(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID]); } else { diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 9d99227ed..2fd702f6d 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -161,10 +161,10 @@ void MWWorld::InventoryStore::autoEquip (const MWMechanics::NpcStats& stats) if (testSkill!=-1 || oldSkill!=-1 || testSkill!=oldSkill) { - if (stats.mSkill[oldSkill].getModified()>stats.mSkill[testSkill].getModified()) + if (stats.getSkill (oldSkill).getModified()>stats.getSkill (testSkill).getModified()) continue; // rejected, because old item better matched the NPC's skills. - if (stats.mSkill[oldSkill].getModified() Date: Fri, 6 Jul 2012 18:25:16 +0200 Subject: [PATCH 027/298] Issue #324: Change a forward declartion to avoid potential problems with pesky MSVC --- apps/openmw/mwworld/class.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index fe7b5dc80..8f2d19b1c 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -22,7 +22,7 @@ namespace MWRender namespace MWMechanics { struct CreatureStats; - struct NpcStats; + class NpcStats; struct Movement; } From 0f41cc499dba44f07c8d92877f81f761ed8c824b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 6 Jul 2012 21:07:04 +0200 Subject: [PATCH 028/298] Issue #324: Added skill gain calculation function --- apps/openmw/mwmechanics/npcstats.cpp | 68 ++++++++++++++++++++++++++++ apps/openmw/mwmechanics/npcstats.hpp | 11 +++++ 2 files changed, 79 insertions(+) diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index cdb977029..6c95c5372 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -3,6 +3,15 @@ #include +#include +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + MWMechanics::NpcStats::NpcStats() : mMovementFlags (0), mDrawState (DrawState_Nothing) {} @@ -55,3 +64,62 @@ const std::map& MWMechanics::NpcStats::getFactionRanks() const { return mFactionRank; } + +float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& class_, int usageType, + int level) const +{ + if (level<0) + level = static_cast (getSkill (skillIndex).getBase()); + + const ESM::Skill *skill = MWBase::Environment::get().getWorld()->getStore().skills.find (skillIndex); + + float skillFactor = 1; + + if (usageType>=4) + throw std::runtime_error ("skill usage type out of range"); + + if (usageType>0) + { + skillFactor = skill->data.useValue[usageType]; + + if (skillFactor<=0) + throw std::runtime_error ("invalid skill gain factor"); + } + + float typeFactor = + MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fMiscSkillBonus")->f; + + for (int i=0; i<5; ++i) + if (class_.data.skills[i][0]==skillIndex) + { + typeFactor = + MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fMinorSkillBonus")->f; + + break; + } + + for (int i=0; i<5; ++i) + if (class_.data.skills[i][1]==skillIndex) + { + typeFactor = + MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fMajorSkillBonus")->f; + + break; + } + + if (typeFactor<=0) + throw std::runtime_error ("invalid skill type factor"); + + float specialisationFactor = 1; + + if (skill->data.specialization==class_.data.specialization) + { + specialisationFactor = + MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fSpecialSkillBonus")->f; + + if (specialisationFactor<=0) + throw std::runtime_error ("invalid skill specialisation factor"); + } + + return 1.0 / (level +1) * (1.0 / skillFactor) * typeFactor * specialisationFactor; +} diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 16a777b71..026404272 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -8,6 +8,11 @@ #include "stat.hpp" #include "drawstate.hpp" +namespace ESM +{ + struct Class; +} + namespace MWMechanics { /// \brief Additional stats for NPCs @@ -59,6 +64,12 @@ namespace MWMechanics std::map& getFactionRanks(); const std::map& getFactionRanks() const; + + float getSkillGain (int skillIndex, const ESM::Class& class_, int usageType = -1, + int level = -1) const; + ///< \param usageType: Usage specific factor, specified in the respective skill record; + /// -1: use a factor of 1.0 instead. + /// \param level Level to base calculation on; -1: use current level. }; } From e517e75d0984b16f94c03bd90869032aa3925447 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Jul 2012 01:25:14 +0200 Subject: [PATCH 029/298] update --- components/nifogre/ogre_nif_loader.cpp | 2 +- extern/shiny | 2 +- files/materials/objects.mat | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 76562256f..d12a78114 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -270,7 +270,7 @@ void NIFLoader::createMaterial(const String &name, { // Enable transparency instance->setProperty("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend"))); - instance->setProperty("depth_write", sh::makeProperty(new sh::BooleanValue(false))); + instance->setProperty("depth_write", sh::makeProperty(new sh::StringValue("off"))); } } else diff --git a/extern/shiny b/extern/shiny index 1d689a23f..88a192a67 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 1d689a23fa685ab680351219c32953a3bed7ac0c +Subproject commit 88a192a67d7dff636be4540be4a8654ad33f106a diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 6dfe9db7c..58c9a17bc 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -12,6 +12,7 @@ material openmw_objects_base is_transparent false // real transparency, alpha rejection doesn't count here scene_blend default + depth_write default alpha_rejection default pass @@ -34,6 +35,7 @@ material openmw_objects_base emissive $emissive scene_blend $scene_blend alpha_rejection $alpha_rejection + depth_write $depth_write ffp_vertex_colour_ambient $has_vertex_colour From fe86ce5a2c263b72c85a592e2e8f47dfa613fc80 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 7 Jul 2012 20:53:19 +0200 Subject: [PATCH 030/298] DrawState workaround --- apps/openmw/mwinput/inputmanager.cpp | 4 ++-- apps/openmw/mwmechanics/drawstate.hpp | 5 ++--- apps/openmw/mwmechanics/npcstats.cpp | 4 ++-- apps/openmw/mwmechanics/npcstats.hpp | 6 +++--- apps/openmw/mwworld/player.cpp | 4 ++-- apps/openmw/mwworld/player.hpp | 7 ++----- 6 files changed, 13 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwinput/inputmanager.cpp b/apps/openmw/mwinput/inputmanager.cpp index 73cadacd0..3a8315b71 100644 --- a/apps/openmw/mwinput/inputmanager.cpp +++ b/apps/openmw/mwinput/inputmanager.cpp @@ -102,7 +102,7 @@ private: { if (windows.isGuiMode()) return; - MWMechanics::DrawState state = player.getDrawState(); + MWMechanics::DrawState_ state = player.getDrawState(); if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing) { player.setDrawState(MWMechanics::DrawState_Spell); @@ -119,7 +119,7 @@ private: { if (windows.isGuiMode()) return; - MWMechanics::DrawState state = player.getDrawState(); + MWMechanics::DrawState_ state = player.getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) { player.setDrawState(MWMechanics::DrawState_Weapon); diff --git a/apps/openmw/mwmechanics/drawstate.hpp b/apps/openmw/mwmechanics/drawstate.hpp index 94b48fdd8..112b6e4f9 100644 --- a/apps/openmw/mwmechanics/drawstate.hpp +++ b/apps/openmw/mwmechanics/drawstate.hpp @@ -1,11 +1,10 @@ #ifndef GAME_MWMECHANICS_DRAWSTATE_H #define GAME_MWMECHANICS_DRAWSTATE_H -#undef DrawState - namespace MWMechanics { - enum DrawState + /// \note The _ suffix is required to avoid a collision with a Windoze macro. Die, Microsoft! Die! + enum DrawState_ { DrawState_Weapon = 0, DrawState_Spell = 1, diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 6c95c5372..08ac12374 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -16,12 +16,12 @@ MWMechanics::NpcStats::NpcStats() : mMovementFlags (0), mDrawState (DrawState_Nothing) {} -MWMechanics::DrawState MWMechanics::NpcStats::getDrawState() const +MWMechanics::DrawState_ MWMechanics::NpcStats::getDrawState() const { return mDrawState; } -void MWMechanics::NpcStats::setDrawState (DrawState state) +void MWMechanics::NpcStats::setDrawState (DrawState_ state) { mDrawState = state; } diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 026404272..1dccdd0d6 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -41,7 +41,7 @@ namespace MWMechanics /// \note the faction key must be in lowercase std::map mFactionRank; - DrawState mDrawState; + DrawState_ mDrawState; unsigned int mMovementFlags; Stat mSkill[27]; @@ -49,9 +49,9 @@ namespace MWMechanics NpcStats(); - DrawState getDrawState() const; + DrawState_ getDrawState() const; - void setDrawState (DrawState state); + void setDrawState (DrawState_ state); bool getMovementFlag (Flag flag) const; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index d8b3285ef..4d508c3e9 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -56,7 +56,7 @@ namespace MWWorld mClass = new_class; } - void Player::setDrawState (MWMechanics::DrawState state) + void Player::setDrawState (MWMechanics::DrawState_ state) { MWWorld::Ptr ptr = getPlayer(); MWWorld::Class::get(ptr).getNpcStats(ptr).setDrawState (state); @@ -111,7 +111,7 @@ namespace MWWorld MWWorld::Class::get (ptr).setStance (ptr, MWWorld::Class::Run, !running); } - MWMechanics::DrawState Player::getDrawState() + MWMechanics::DrawState_ Player::getDrawState() { MWWorld::Ptr ptr = getPlayer(); return MWWorld::Class::get(ptr).getNpcStats(ptr).getDrawState(); diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 2a02635b4..ee7c030a5 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -9,9 +9,6 @@ #include "../mwmechanics/drawstate.hpp" -#undef DrawState // How did this get defined again? - // Maybe it's defined by default in every file for windows? - namespace MWBase { class World; @@ -86,7 +83,7 @@ namespace MWWorld void setClass (const ESM::Class& class_); - void setDrawState (MWMechanics::DrawState state); + void setDrawState (MWMechanics::DrawState_ state); std::string getName() const { @@ -118,7 +115,7 @@ namespace MWWorld return mAutoMove; } - MWMechanics::DrawState getDrawState(); /// \todo constness + MWMechanics::DrawState_ getDrawState(); /// \todo constness void setAutoMove (bool enable); From e6716c25c382514eb06e6aeaaf4b2562e34fde7e Mon Sep 17 00:00:00 2001 From: gugus Date: Mon, 9 Jul 2012 15:41:19 +0200 Subject: [PATCH 031/298] little correction. --- apps/openmw/mwworld/worldimp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b623cd527..155bc8d81 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -604,7 +604,7 @@ namespace MWWorld ptr.getCellRef().scale = scale; scale = scale/ptr.getRefData().getBaseNode()->getScale().x; ptr.getRefData().getBaseNode()->setScale(scale,scale,scale); - mPhysics->scaleObject( Class::get(ptr).getId(ptr), scale ); + mPhysics->scaleObject( ptr.getRefData().getHandle(), scale ); } void World::rotateObject (Ptr& ptr,float x,float y,float z,bool WorldAxis) @@ -636,7 +636,7 @@ namespace MWWorld ptr.getRefData().getPosition().rot[0] = ry.valueRadians(); ptr.getRefData().getPosition().rot[0] = rz.valueRadians(); - mPhysics->rotateObject(Class::get(ptr).getId(ptr),ptr.getRefData().getBaseNode()->getOrientation()); + mPhysics->rotateObject(ptr.getRefData().getHandle(),ptr.getRefData().getBaseNode()->getOrientation()); //ptr.getRefData().getBaseNode()->rotate(ptr.getRefData().getBaseNode()->get //mPhysics->scaleObject( Class::get(ptr).getId(ptr), scale ); } From 0a67f60a6e334a942cda7f5391b41fcf948e3d06 Mon Sep 17 00:00:00 2001 From: gugus Date: Mon, 9 Jul 2012 18:47:59 +0200 Subject: [PATCH 032/298] Clean-up --- apps/openmw/mwbase/world.hpp | 6 +- apps/openmw/mwscript/extensions.cpp | 3 + apps/openmw/mwscript/statsextensions.cpp | 57 ------------ .../mwscript/transformationextensions.cpp | 89 +++++++++++++++++++ .../mwscript/transformationextensions.hpp | 25 ++++++ apps/openmw/mwworld/worldimp.cpp | 41 +-------- apps/openmw/mwworld/worldimp.hpp | 6 +- 7 files changed, 124 insertions(+), 103 deletions(-) create mode 100644 apps/openmw/mwscript/transformationextensions.cpp create mode 100644 apps/openmw/mwscript/transformationextensions.hpp diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 0dcf440f0..51d0a4f8e 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -178,11 +178,9 @@ namespace MWBase virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; - virtual void scaleObject (MWWorld::Ptr& ptr, float scale) = 0; + virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; - virtual void rotateObject (MWWorld::Ptr& ptr,float x,float y,float z,bool WorldAxis) = 0; - - virtual void setObjectRotation (MWWorld::Ptr& ptr,float x,float y,float z) = 0; + virtual void rotateObject(const MWWorld::Ptr& ptr,float x,float y,float z) = 0; virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const = 0; diff --git a/apps/openmw/mwscript/extensions.cpp b/apps/openmw/mwscript/extensions.cpp index 197494146..b7425aca0 100644 --- a/apps/openmw/mwscript/extensions.cpp +++ b/apps/openmw/mwscript/extensions.cpp @@ -15,6 +15,7 @@ #include "controlextensions.hpp" #include "dialogueextensions.hpp" #include "animationextensions.hpp" +#include "transformationextensions.hpp" namespace MWScript { @@ -31,6 +32,7 @@ namespace MWScript Control::registerExtensions (extensions); Dialogue::registerExtensions (extensions); Animation::registerExtensions (extensions); + Transformation::registerExtensions (extensions); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -47,5 +49,6 @@ namespace MWScript Control::installOpcodes (interpreter); Dialogue::installOpcodes (interpreter); Animation::installOpcodes (interpreter); + Transformation::installOpcodes (interpreter); } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 17cd184d7..4e41ae81d 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -505,50 +505,6 @@ namespace MWScript } }; - template - class OpSetScale : public Interpreter::Opcode0 - { - public: - - virtual void execute (Interpreter::Runtime& runtime) - { - MWWorld::Ptr ptr = R()(runtime); - - Interpreter::Type_Float scale = runtime[0].mInteger; - runtime.pop(); - - MWBase::Environment::get().getWorld()->scaleObject(ptr,scale); - } - }; - - template - class OpSetAngle : public Interpreter::Opcode0 - { - public: - - virtual void execute (Interpreter::Runtime& runtime) - { - MWWorld::Ptr ptr = R()(runtime); - - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - Interpreter::Type_Float angle = runtime[0].mInteger; - runtime.pop(); - - if(axis == "X") - { - MWBase::Environment::get().getWorld()->setObjectRotation(ptr,angle,0,0); - } - if(axis == "Y") - { - MWBase::Environment::get().getWorld()->setObjectRotation(ptr,0,angle,0); - } - if(axis == "Z") - { - MWBase::Environment::get().getWorld()->setObjectRotation(ptr,0,0,angle); - } - } - }; const int numberOfAttributes = 8; @@ -596,11 +552,6 @@ namespace MWScript const int opcodeModDisposition = 0x200014d; const int opcodeModDispositionExplicit = 0x200014e; - const int opcodeSetScale = 0x2000164; - const int opcodeSetScaleExplicit = 0x2000165; - const int opcodeSetAngle = 0x2000166; - const int opcodeSetAngleExplicit = 0x2000167; - void registerExtensions (Compiler::Extensions& extensions) { static const char *attributes[numberOfAttributes] = @@ -683,9 +634,6 @@ namespace MWScript extensions.registerInstruction("moddisposition","l",opcodeModDisposition, opcodeModDispositionExplicit); extensions.registerFunction("getpcrank",'l',"/S",opcodeGetPCRank,opcodeGetPCRankExplicit); - - extensions.registerInstruction("setscale","l",opcodeSetScale,opcodeSetScaleExplicit); - extensions.registerInstruction("setangle","Sl",opcodeSetAngle,opcodeSetAngleExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -757,11 +705,6 @@ namespace MWScript interpreter.installSegment5(opcodeModDispositionExplicit,new OpModDisposition); interpreter.installSegment3(opcodeGetPCRank,new OpGetPCRank); interpreter.installSegment3(opcodeGetPCRankExplicit,new OpGetPCRank); - - interpreter.installSegment5(opcodeSetScale,new OpSetScale); - interpreter.installSegment5(opcodeSetScaleExplicit,new OpSetScale); - interpreter.installSegment5(opcodeSetAngle,new OpSetAngle); - interpreter.installSegment5(opcodeSetAngleExplicit,new OpSetAngle); } } } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp new file mode 100644 index 000000000..14181a06e --- /dev/null +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -0,0 +1,89 @@ + +#include "statsextensions.hpp" + +#include + +#include + +#include + +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "../mwworld/class.hpp" + +#include "interpretercontext.hpp" +#include "ref.hpp" + +namespace MWScript +{ + namespace Transformation + { + template + class OpSetScale : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Float scale = runtime[0].mInteger; + runtime.pop(); + + MWBase::Environment::get().getWorld()->scaleObject(ptr,scale); + } + }; + + template + class OpSetAngle : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float angle = runtime[0].mInteger; + runtime.pop(); + + if(axis == "X") + { + MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,0,0); + } + if(axis == "Y") + { + MWBase::Environment::get().getWorld()->rotateObject(ptr,0,angle,0); + } + if(axis == "Z") + { + MWBase::Environment::get().getWorld()->rotateObject(ptr,0,0,angle); + } + } + }; + + const int opcodeSetScale = 0x2000164; + const int opcodeSetScaleExplicit = 0x2000165; + const int opcodeSetAngle = 0x2000166; + const int opcodeSetAngleExplicit = 0x2000167; + + void registerExtensions (Compiler::Extensions& extensions) + { + extensions.registerInstruction("setscale","f",opcodeSetScale,opcodeSetScaleExplicit); + extensions.registerInstruction("setangle","Sl",opcodeSetAngle,opcodeSetAngleExplicit); + } + + void installOpcodes (Interpreter::Interpreter& interpreter) + { + interpreter.installSegment5(opcodeSetScale,new OpSetScale); + interpreter.installSegment5(opcodeSetScaleExplicit,new OpSetScale); + interpreter.installSegment5(opcodeSetAngle,new OpSetAngle); + interpreter.installSegment5(opcodeSetAngleExplicit,new OpSetAngle); + } + } +} diff --git a/apps/openmw/mwscript/transformationextensions.hpp b/apps/openmw/mwscript/transformationextensions.hpp new file mode 100644 index 000000000..6ee1db1b8 --- /dev/null +++ b/apps/openmw/mwscript/transformationextensions.hpp @@ -0,0 +1,25 @@ +#ifndef GAME_SCRIPT_TRANSFORMATIONEXTENSIONS_H +#define GAME_SCRIPT_TRANSFORMATIONEXTENSIONS_H + +namespace Compiler +{ + class Extensions; +} + +namespace Interpreter +{ + class Interpreter; +} + +namespace MWScript +{ + /// \brief stats-related script functionality (creatures and NPCs) + namespace Transformation + { + void registerExtensions (Compiler::Extensions& extensions); + + void installOpcodes (Interpreter::Interpreter& interpreter); + } +} + +#endif \ No newline at end of file diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 155bc8d81..c6423563d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -597,7 +597,7 @@ namespace MWWorld mPhysics->moveObject (ptr.getRefData().getHandle(), Ogre::Vector3 (x, y, z)); } - void World::scaleObject (Ptr& ptr, float scale) + void World::scaleObject (const Ptr& ptr, float scale) { MWWorld::Class::get(ptr).adjustScale(ptr,scale); @@ -607,41 +607,7 @@ namespace MWWorld mPhysics->scaleObject( ptr.getRefData().getHandle(), scale ); } - void World::rotateObject (Ptr& ptr,float x,float y,float z,bool WorldAxis) - { - MWWorld::Class::get(ptr).adjustRotation(ptr,x,y,z); - - if(WorldAxis) - { - ptr.getRefData().getBaseNode()->rotate(Ogre::Vector3::UNIT_X,Ogre::Degree(x)); - ptr.getRefData().getBaseNode()->rotate(Ogre::Vector3::UNIT_X,Ogre::Degree(y)); - ptr.getRefData().getBaseNode()->rotate(Ogre::Vector3::UNIT_X,Ogre::Degree(z)); - } - else - { - Ogre::Matrix3 axis = ptr.getRefData().getBaseNode()->getLocalAxes(); - Ogre::Vector3 xAxis = axis.GetColumn(0); - Ogre::Vector3 yAxis = axis.GetColumn(1); - Ogre::Vector3 zAxis = axis.GetColumn(2); - ptr.getRefData().getBaseNode()->rotate(xAxis,Ogre::Degree(x)); - ptr.getRefData().getBaseNode()->rotate(yAxis,Ogre::Degree(y)); - ptr.getRefData().getBaseNode()->rotate(zAxis,Ogre::Degree(z)); - } - Ogre::Matrix3 rot; - ptr.getRefData().getBaseNode()->getOrientation().ToRotationMatrix(rot); - Ogre::Radian rx,ry,rz; - rot.ToEulerAnglesXYZ(rx,ry,rz); - - ptr.getRefData().getPosition().rot[0] = rx.valueRadians(); - ptr.getRefData().getPosition().rot[0] = ry.valueRadians(); - ptr.getRefData().getPosition().rot[0] = rz.valueRadians(); - - mPhysics->rotateObject(ptr.getRefData().getHandle(),ptr.getRefData().getBaseNode()->getOrientation()); - //ptr.getRefData().getBaseNode()->rotate(ptr.getRefData().getBaseNode()->get - //mPhysics->scaleObject( Class::get(ptr).getId(ptr), scale ); - } - - void World::setObjectRotation (Ptr& ptr,float x,float y,float z) + void World::rotateObject (const Ptr& ptr,float x,float y,float z) { MWWorld::Class::get(ptr).adjustRotation(ptr,x,y,z); @@ -653,8 +619,7 @@ namespace MWWorld Ogre::Quaternion roty(Ogre::Degree(y),Ogre::Vector3::UNIT_Y); Ogre::Quaternion rotz(Ogre::Degree(z),Ogre::Vector3::UNIT_Z); ptr.getRefData().getBaseNode()->setOrientation(rotx*roty*rotz); - mPhysics->rotateObject(Class::get(ptr).getId(ptr),ptr.getRefData().getBaseNode()->getOrientation()); - std::cout << Class::get(ptr).getId(ptr); + mPhysics->rotateObject(ptr.getRefData().getHandle(),ptr.getRefData().getBaseNode()->getOrientation()); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1f3073d44..8b39a78f1 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -219,11 +219,9 @@ namespace MWWorld virtual void moveObject (const Ptr& ptr, float x, float y, float z); - virtual void scaleObject (Ptr& ptr, float scale); + virtual void scaleObject (const Ptr& ptr, float scale); - virtual void rotateObject (Ptr& ptr,float x,float y,float z,bool WorldAxis); - - virtual void setObjectRotation (Ptr& ptr,float x,float y,float z); + virtual void rotateObject (const Ptr& ptr,float x,float y,float z); virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const; From 81d30ff63a119f9d2251688699f1e713576837bf Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 9 Jul 2012 19:20:41 +0200 Subject: [PATCH 033/298] temp commit --- apps/openmw/mwrender/localmap.cpp | 4 ++-- apps/openmw/mwrender/renderingmanager.cpp | 11 ++++++----- apps/openmw/mwrender/shadows.cpp | 6 +++--- apps/openmw/mwrender/water.cpp | 2 +- components/nifogre/ogre_nif_loader.cpp | 2 ++ extern/shiny | 2 +- files/materials/objects.mat | 3 +++ files/materials/objects.shader | 2 +- 8 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 4fa125c9b..1a4002b12 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -229,9 +229,9 @@ void LocalMap::render(const float x, const float y, vp->setVisibilityMask(RV_Map); // use fallback techniques without shadows and without mrt - vp->setMaterialScheme("simple"); + //vp->setMaterialScheme("local_map"); - rtt->update(); + //rtt->update(); // create "fog of war" texture TexturePtr tex2 = TextureManager::getSingleton().createManual( diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 174a9e4f8..ca26697b7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -76,9 +76,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const // Load resources ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); - // Due to the huge world size of MW, we'll want camera-relative rendering. - // This prevents precision artifacts when moving very far from the origin. - mRendering.getScene()->setCameraRelativeRendering(true); + // causes light flicker in opengl when moving.. + //mRendering.getScene()->setCameraRelativeRendering(true); // disable unsupported effects const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities(); @@ -525,12 +524,14 @@ Shadows* RenderingManager::getShadows() void RenderingManager::switchToInterior() { - mRendering.getScene()->setCameraRelativeRendering(false); + // causes light flicker in opengl when moving.. + //mRendering.getScene()->setCameraRelativeRendering(false); } void RenderingManager::switchToExterior() { - mRendering.getScene()->setCameraRelativeRendering(true); + // causes light flicker in opengl when moving.. + //mRendering.getScene()->setCameraRelativeRendering(true); } Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds) diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 9a4ae7243..1bea14530 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -53,7 +53,7 @@ void Shadows::recreate() mSceneMgr->setShadowTextureSelfShadow(true); mSceneMgr->setShadowCasterRenderBackFaces(true); - mSceneMgr->setShadowTextureCasterMaterial("depth_shadow_caster"); + // mSceneMgr->setShadowTextureCasterMaterial("openmw_shadowcaster_default"); mSceneMgr->setShadowTexturePixelFormat(PF_FLOAT32_R); mSceneMgr->setShadowDirectionalLightExtrusionDistance(1000000); @@ -111,7 +111,7 @@ void Shadows::recreate() // -------------------------------------------------------------------------------------------------------------------- // --------------------------- Debug overlays to display the content of shadow maps ----------------------------------- // -------------------------------------------------------------------------------------------------------------------- - /* + OverlayManager& mgr = OverlayManager::getSingleton(); Overlay* overlay; @@ -157,7 +157,7 @@ void Shadows::recreate() overlay->add2D(debugPanel); overlay->show(); } - */ + } PSSMShadowCameraSetup* Shadows::getPSSMSetup() diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 666161052..311df9169 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -336,7 +336,7 @@ void Water::applyRTT() vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f)); vp->setShadowsEnabled(false); // use fallback techniques without shadows and without mrt (currently not implemented for sky and terrain) - vp->setMaterialScheme("simple"); + //vp->setMaterialScheme("water_reflection"); rtt->addListener(this); rtt->setActive(true); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index d12a78114..f33ac2364 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -276,6 +276,8 @@ void NIFLoader::createMaterial(const String &name, else warn("Unhandled alpha setting for texture " + texName); } + //else + //instance->getMaterial ()->setShadowCasterMaterial ("openmw_shadowcaster_noalpha"); // As of yet UNTESTED code from Chris: /*pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); diff --git a/extern/shiny b/extern/shiny index 88a192a67..d25605ee3 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 88a192a67d7dff636be4540be4a8654ad33f106a +Subproject commit d25605ee3a2d67dc0b6367cd150cc6d0c6a3b661 diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 58c9a17bc..324fe1b2a 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -14,6 +14,9 @@ material openmw_objects_base scene_blend default depth_write default alpha_rejection default + shadow_transparency true // use diffuse alpha as mask for shadow + + shadow_caster_material openmw_shadowcaster pass { diff --git a/files/materials/objects.shader b/files/materials/objects.shader index e066b43cc..831699364 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -72,7 +72,7 @@ shInput(float3, normalPassthrough) shInput(float3, objSpacePositionPassthrough) shUniform(float4 lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - shUniform(float passIteration) @shAutoConstant(passIteration, pass_iteration_number) + //shUniform(float passIteration) @shAutoConstant(passIteration, pass_iteration_number) shUniform(float4 materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) From 557e114992458b1d7c2768baaf342a64dc01542e Mon Sep 17 00:00:00 2001 From: gugus Date: Mon, 9 Jul 2012 19:28:44 +0200 Subject: [PATCH 034/298] clean-up + getScale/Angle script instructions --- .../mwscript/transformationextensions.cpp | 59 +++++++++++++++++-- apps/openmw/mwworld/worldimp.cpp | 4 +- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 14181a06e..41eba4a99 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -1,6 +1,3 @@ - -#include "statsextensions.hpp" - #include #include @@ -17,6 +14,7 @@ #include "interpretercontext.hpp" #include "ref.hpp" +#include "OgreSceneNode.h" namespace MWScript { @@ -31,13 +29,25 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Float scale = runtime[0].mInteger; + Interpreter::Type_Float scale = runtime[0].mFloat; runtime.pop(); MWBase::Environment::get().getWorld()->scaleObject(ptr,scale); } }; + template + class OpGetScale : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push(ptr.getCellRef().scale); + } + }; + template class OpSetAngle : public Interpreter::Opcode0 { @@ -49,7 +59,7 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - Interpreter::Type_Float angle = runtime[0].mInteger; + Interpreter::Type_Float angle = runtime[0].mFloat; runtime.pop(); if(axis == "X") @@ -67,15 +77,48 @@ namespace MWScript } }; + template + class OpGetAngle : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + if(axis == "X") + { + runtime.push(ptr.getRefData().getPosition().rot[0]); + } + if(axis == "Y") + { + runtime.push(ptr.getRefData().getPosition().rot[1]); + } + if(axis == "Z") + { + runtime.push(ptr.getRefData().getPosition().rot[0]); + } + } + }; + const int opcodeSetScale = 0x2000164; const int opcodeSetScaleExplicit = 0x2000165; const int opcodeSetAngle = 0x2000166; const int opcodeSetAngleExplicit = 0x2000167; + const int opcodeGetScale = 0x2000168; + const int opcodeGetScaleExplicit = 0x2000169; + const int opcodeGetAngle = 0x200016a; + const int opcodeGetAngleExplicit = 0x200016b; void registerExtensions (Compiler::Extensions& extensions) { extensions.registerInstruction("setscale","f",opcodeSetScale,opcodeSetScaleExplicit); - extensions.registerInstruction("setangle","Sl",opcodeSetAngle,opcodeSetAngleExplicit); + extensions.registerFunction("getscale",'f',"",opcodeGetScale,opcodeGetScaleExplicit); + extensions.registerInstruction("setangle","Sf",opcodeSetAngle,opcodeSetAngleExplicit); + extensions.registerFunction("getangle",'f',"S",opcodeGetAngle,opcodeGetAngleExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -84,6 +127,10 @@ namespace MWScript interpreter.installSegment5(opcodeSetScaleExplicit,new OpSetScale); interpreter.installSegment5(opcodeSetAngle,new OpSetAngle); interpreter.installSegment5(opcodeSetAngleExplicit,new OpSetAngle); + interpreter.installSegment5(opcodeGetScale,new OpGetScale); + interpreter.installSegment5(opcodeGetScaleExplicit,new OpGetScale); + interpreter.installSegment5(opcodeGetAngle,new OpGetAngle); + interpreter.installSegment5(opcodeGetAngleExplicit,new OpGetAngle); } } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c6423563d..5309fbe40 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -612,8 +612,8 @@ namespace MWWorld MWWorld::Class::get(ptr).adjustRotation(ptr,x,y,z); ptr.getRefData().getPosition().rot[0] = Ogre::Degree(x).valueRadians(); - ptr.getRefData().getPosition().rot[0] = Ogre::Degree(y).valueRadians(); - ptr.getRefData().getPosition().rot[0] = Ogre::Degree(z).valueRadians(); + ptr.getRefData().getPosition().rot[1] = Ogre::Degree(y).valueRadians(); + ptr.getRefData().getPosition().rot[2] = Ogre::Degree(z).valueRadians(); Ogre::Quaternion rotx(Ogre::Degree(x),Ogre::Vector3::UNIT_X); Ogre::Quaternion roty(Ogre::Degree(y),Ogre::Vector3::UNIT_Y); From b803d0e9493467456aabfe573046d934f159496c Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 9 Jul 2012 19:46:36 +0200 Subject: [PATCH 035/298] converted shadow caster shader --- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/shadows.cpp | 2 +- apps/openmw/mwrender/water.cpp | 2 +- components/nifogre/ogre_nif_loader.cpp | 4 +- extern/shiny | 2 +- files/materials/shadowcaster.mat | 35 ++++++++++++ files/materials/shadowcaster.shader | 56 ++++++++++++++++++ files/materials/shadowcaster.shaderset | 15 +++++ files/shadows/depthshadowcaster.cg | 51 ----------------- files/shadows/depthshadowcaster.material | 73 ------------------------ 10 files changed, 112 insertions(+), 130 deletions(-) create mode 100644 files/materials/shadowcaster.mat create mode 100644 files/materials/shadowcaster.shader create mode 100644 files/materials/shadowcaster.shaderset delete mode 100644 files/shadows/depthshadowcaster.cg delete mode 100644 files/shadows/depthshadowcaster.material diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 1a4002b12..663388e94 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -229,7 +229,7 @@ void LocalMap::render(const float x, const float y, vp->setVisibilityMask(RV_Map); // use fallback techniques without shadows and without mrt - //vp->setMaterialScheme("local_map"); + vp->setMaterialScheme("local_map"); //rtt->update(); diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 1bea14530..93152244b 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -53,7 +53,7 @@ void Shadows::recreate() mSceneMgr->setShadowTextureSelfShadow(true); mSceneMgr->setShadowCasterRenderBackFaces(true); - // mSceneMgr->setShadowTextureCasterMaterial("openmw_shadowcaster_default"); + mSceneMgr->setShadowTextureCasterMaterial("openmw_shadowcaster_default"); mSceneMgr->setShadowTexturePixelFormat(PF_FLOAT32_R); mSceneMgr->setShadowDirectionalLightExtrusionDistance(1000000); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 311df9169..b8642a754 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -336,7 +336,7 @@ void Water::applyRTT() vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f)); vp->setShadowsEnabled(false); // use fallback techniques without shadows and without mrt (currently not implemented for sky and terrain) - //vp->setMaterialScheme("water_reflection"); + vp->setMaterialScheme("water_reflection"); rtt->addListener(this); rtt->setActive(true); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index f33ac2364..e5b196804 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -276,8 +276,8 @@ void NIFLoader::createMaterial(const String &name, else warn("Unhandled alpha setting for texture " + texName); } - //else - //instance->getMaterial ()->setShadowCasterMaterial ("openmw_shadowcaster_noalpha"); + else + instance->getMaterial ()->setShadowCasterMaterial ("openmw_shadowcaster_noalpha"); // As of yet UNTESTED code from Chris: /*pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); diff --git a/extern/shiny b/extern/shiny index d25605ee3..a0b3020bd 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit d25605ee3a2d67dc0b6367cd150cc6d0c6a3b661 +Subproject commit a0b3020bdba4433c1e3f45a1d5188eab0172bd88 diff --git a/files/materials/shadowcaster.mat b/files/materials/shadowcaster.mat new file mode 100644 index 000000000..25b791f1f --- /dev/null +++ b/files/materials/shadowcaster.mat @@ -0,0 +1,35 @@ + + +material openmw_shadowcaster_default +{ + create_configuration Default + pass + { + fog_override true + + vertex_program openmw_shadowcaster_vertex + fragment_program openmw_shadowcaster_fragment + + shader_properties + { + shadow_transparency true + } + } +} + +material openmw_shadowcaster_noalpha +{ + create_configuration Default + pass + { + fog_override true + + vertex_program openmw_shadowcaster_vertex + fragment_program openmw_shadowcaster_fragment + + shader_properties + { + shadow_transparency false + } + } +} diff --git a/files/materials/shadowcaster.shader b/files/materials/shadowcaster.shader new file mode 100644 index 000000000..f772066a6 --- /dev/null +++ b/files/materials/shadowcaster.shader @@ -0,0 +1,56 @@ +#include "core.h" + +#define ALPHA @shPropertyBool(shadow_transparency) + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM +#if ALPHA + shInput(float2, uv0) + shOutput(float2, UV) +#endif + shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shOutput(float2, depth) + SH_START_PROGRAM + { + // this is the view space position + shOutputPosition = shMatrixMult(wvp, shInputPosition); + + // depth info for the fragment. + depth.x = shOutputPosition.z; + depth.y = shOutputPosition.w; + + // clamp z to zero. seem to do the trick. :-/ + shOutputPosition.z = max(shOutputPosition.z, 0); + +#if ALPHA + UV = uv0; +#endif + } + +#else + + SH_BEGIN_PROGRAM +#if ALPHA + shInput(float2, UV) + shSampler2D(texture1) +#endif + shInput(float2, depth) + SH_START_PROGRAM + { + float finalDepth = depth.x / depth.y; + + +#if ALPHA + // use alpha channel of the first texture + float alpha = shSample(texture1, UV).a; + + // discard if alpha is less than 0.5 + if (alpha < 1.0) + discard; +#endif + + shOutputColor(0) = float4(finalDepth, finalDepth, finalDepth, 1); + } + +#endif diff --git a/files/materials/shadowcaster.shaderset b/files/materials/shadowcaster.shaderset new file mode 100644 index 000000000..5f4990ed1 --- /dev/null +++ b/files/materials/shadowcaster.shaderset @@ -0,0 +1,15 @@ +shader_set openmw_shadowcaster_vertex +{ + source shadowcaster.shader + type vertex + profiles_cg vs_2_0 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set openmw_shadowcaster_fragment +{ + source shadowcaster.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_hlsl ps_2_0 +} diff --git a/files/shadows/depthshadowcaster.cg b/files/shadows/depthshadowcaster.cg deleted file mode 100644 index 3457a4f8d..000000000 --- a/files/shadows/depthshadowcaster.cg +++ /dev/null @@ -1,51 +0,0 @@ -void main_vp( - float4 position : POSITION, - float2 uv : TEXCOORD0, - - out float4 oPosition : POSITION, - out float2 oDepth : TEXCOORD0, - out float2 oUv : TEXCOORD1, - - uniform float4x4 wvpMat) -{ - // this is the view space position - oPosition = mul(wvpMat, position); - - // depth info for the fragment. - oDepth.x = oPosition.z; - oDepth.y = oPosition.w; - - // clamp z to zero. seem to do the trick. :-/ - oPosition.z = max(oPosition.z, 0); - - oUv = uv; -} - -void main_fp( - float2 depth : TEXCOORD0, - float2 uv : TEXCOORD1, - uniform sampler2D texture1 : register(s0), - - out float4 oColour : COLOR) -{ - float finalDepth = depth.x / depth.y; - - // use alpha channel of the first texture - float alpha = tex2D(texture1, uv).a; - - // discard if alpha is less than 0.5 - clip((alpha >= 0.5) ? 1 : -1); - - oColour = float4(finalDepth, finalDepth, finalDepth, 1); -} - -void main_fp_noalpha( - float2 depth : TEXCOORD0, - float2 uv : TEXCOORD1, - - out float4 oColour : COLOR) -{ - float finalDepth = depth.x / depth.y; - - oColour = float4(finalDepth, finalDepth, finalDepth, 1); -} diff --git a/files/shadows/depthshadowcaster.material b/files/shadows/depthshadowcaster.material deleted file mode 100644 index f645cad01..000000000 --- a/files/shadows/depthshadowcaster.material +++ /dev/null @@ -1,73 +0,0 @@ -vertex_program depth_shadow_caster_vs cg -{ - source depthshadowcaster.cg - profiles vs_1_1 arbvp1 - entry_point main_vp - - default_params - { - param_named_auto wvpMat worldviewproj_matrix - } -} - -fragment_program depth_shadow_caster_ps cg -{ - source depthshadowcaster.cg - profiles ps_2_0 arbfp1 - entry_point main_fp - - default_params - { - } -} - -fragment_program depth_shadow_caster_ps_noalpha cg -{ - source depthshadowcaster.cg - profiles ps_2_0 arbfp1 - entry_point main_fp_noalpha - - default_params - { - } -} - -material depth_shadow_caster -{ - technique - { - pass - { - // force-disable fog (relevant for DirectX profiles below SM3 that always apply fixed function fog) - fog_override true - - vertex_program_ref depth_shadow_caster_vs - { - } - - fragment_program_ref depth_shadow_caster_ps - { - } - } - } -} - -material depth_shadow_caster_noalpha -{ - technique - { - pass - { - // force-disable fog (relevant for DirectX profiles below SM3 that always apply fixed function fog) - fog_override true - - vertex_program_ref depth_shadow_caster_vs - { - } - - fragment_program_ref depth_shadow_caster_ps_noalpha - { - } - } - } -} From 07fd9986ef995e7f21fac54aefa612f4613a4bb4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 9 Jul 2012 20:14:07 +0200 Subject: [PATCH 036/298] accidently removed the map. --- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/shadows.cpp | 4 +- apps/openmw/mwrender/sky.cpp | 63 +++---------------------------- apps/openmw/mwrender/sky.hpp | 2 + extern/shiny | 2 +- files/materials/atmosphere.shader | 15 ++++---- files/materials/shadowcaster.mat | 2 - files/materials/sky.mat | 5 +++ 8 files changed, 23 insertions(+), 72 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 663388e94..962f19a57 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -231,7 +231,7 @@ void LocalMap::render(const float x, const float y, // use fallback techniques without shadows and without mrt vp->setMaterialScheme("local_map"); - //rtt->update(); + rtt->update(); // create "fog of war" texture TexturePtr tex2 = TextureManager::getSingleton().createManual( diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 93152244b..2e09f4c31 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -111,7 +111,7 @@ void Shadows::recreate() // -------------------------------------------------------------------------------------------------------------------- // --------------------------- Debug overlays to display the content of shadow maps ----------------------------------- // -------------------------------------------------------------------------------------------------------------------- - +/* OverlayManager& mgr = OverlayManager::getSingleton(); Overlay* overlay; @@ -157,7 +157,7 @@ void Shadows::recreate() overlay->add2D(debugPanel); overlay->show(); } - +*/ } PSSMShadowCameraSetup* Shadows::getPSSMSetup() diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 0a866ac7c..f32883fdd 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -453,7 +453,6 @@ void SkyManager::create() HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); // Stars - /// \todo sky_night_02.nif (available in Bloodmoon) MeshPtr mesh = NifOgre::NIFLoader::load("meshes\\sky_night_01.nif"); Entity* night1_ent = mSceneMgr->createEntity("meshes\\sky_night_01.nif"); night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); @@ -548,60 +547,9 @@ void SkyManager::create() atmosphere_ent->getSubEntity (0)->setMaterialName ("openmw_atmosphere"); //mAtmosphereMaterial = atmosphere_ent->getSubEntity(0)->getMaterial(); //mAtmosphereMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); - // Atmosphere shader - HighLevelGpuProgramPtr vshader = mgr.createProgram("Atmosphere_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_VERTEX_PROGRAM); - - vshader->setParameter("profiles", "vs_2_x arbvp1"); - vshader->setParameter("entry_point", "main_vp"); - - StringUtil::StrStreamType outStream; - outStream << - "void main_vp( \n" - " float4 position : POSITION, \n" - " in float4 color : COLOR, \n" - " out float4 oPosition : POSITION, \n" - " out float4 oVertexColor : TEXCOORD0, \n" - " uniform float4x4 worldViewProj \n" - ") \n" - "{ \n" - " oPosition = mul( worldViewProj, position ); \n" - " oVertexColor = color; \n" - "}"; - vshader->setSource(outStream.str()); - vshader->load(); - - vshader->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - //mAtmosphereMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName()); - - HighLevelGpuProgramPtr fshader = mgr.createProgram("Atmosphere_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_FRAGMENT_PROGRAM); - - fshader->setParameter("profiles", "ps_2_x arbfp1"); - fshader->setParameter("entry_point", "main_fp"); - - StringUtil::StrStreamType _outStream; - _outStream << - "void main_fp( \n" - " in float4 iVertexColor : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n"; - if (RenderingManager::useMRT()) _outStream << - " out float4 oColor1 : COLOR1, \n"; - _outStream << - " uniform float4 emissive \n" - ") \n" - "{ \n" - " oColor = iVertexColor * emissive; \n"; - if (RenderingManager::useMRT()) _outStream << - " oColor1 = float4(1, 0, 0, 1); \n"; - _outStream << - "}"; - fshader->setSource(_outStream.str()); - fshader->load(); - - fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); // mAtmosphereMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); + atmosphere_ent->getSubEntity (0)->setMaterialName("openmw_atmosphere"); // Clouds NifOgre::NIFLoader::load("meshes\\sky_clouds_01.nif"); @@ -838,15 +786,14 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) mCloudMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(clr); mCloudColour = weather.mSunColor; } - +*/ if (mSkyColour != weather.mSkyColor) { - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(weather.mSkyColor); - mMasser->setSkyColour(weather.mSkyColor); - mSecunda->setSkyColour(weather.mSkyColor); mSkyColour = weather.mSkyColor; + sh::Factory::getInstance().setSharedParameter ("atmosphereColour", sh::makeProperty(new sh::Vector4( + weather.mSkyColor.r, weather.mSkyColor.g, weather.mSkyColor.b, 1.0))); } - +/* if (mCloudSpeed != weather.mCloudSpeed) { mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("speed", Real(weather.mCloudSpeed)); diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index e11745e82..6bb055824 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -7,6 +7,8 @@ #include #include +#include + #include "sky.hpp" #include "../mwworld/weather.hpp" diff --git a/extern/shiny b/extern/shiny index a0b3020bd..3e7e02a84 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit a0b3020bdba4433c1e3f45a1d5188eab0172bd88 +Subproject commit 3e7e02a846ce6c3de7e2344a82d346293115eb7d diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader index a772e3c5e..484381a1f 100644 --- a/files/materials/atmosphere.shader +++ b/files/materials/atmosphere.shader @@ -6,29 +6,28 @@ SH_BEGIN_PROGRAM shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shInput(float2, uv0) - shOutput(float2, UV) + + shColourInput(float4) + shOutput(float4, colourPassthrough) SH_START_PROGRAM { shOutputPosition = shMatrixMult(wvp, shInputPosition); - UV = uv0; + colourPassthrough = colour; } #else SH_BEGIN_PROGRAM - shSampler2D(diffuseMap) - shInput(float2, UV) + shInput(float4, colourPassthrough) #if MRT shDeclareMrtOutput(1) #endif - shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) + shUniform(float4 atmosphereColour) @shSharedParameter(atmosphereColour) SH_START_PROGRAM { - shOutputColor(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); + shOutputColor(0) = colourPassthrough * atmosphereColour; #if MRT shOutputColor(1) = float4(1,1,1,1); diff --git a/files/materials/shadowcaster.mat b/files/materials/shadowcaster.mat index 25b791f1f..7c11fddf6 100644 --- a/files/materials/shadowcaster.mat +++ b/files/materials/shadowcaster.mat @@ -1,5 +1,3 @@ - - material openmw_shadowcaster_default { create_configuration Default diff --git a/files/materials/sky.mat b/files/materials/sky.mat index 25c9185b7..c78570f7f 100644 --- a/files/materials/sky.mat +++ b/files/materials/sky.mat @@ -1,5 +1,6 @@ material openmw_moon { + allow_fixed_function false mrt_output true pass { @@ -23,6 +24,7 @@ material openmw_moon material openmw_clouds { + allow_fixed_function false mrt_output true pass { @@ -55,6 +57,7 @@ material openmw_clouds material openmw_atmosphere { + allow_fixed_function false mrt_output true pass { @@ -75,6 +78,7 @@ material openmw_atmosphere material openmw_stars { + allow_fixed_function false mrt_output true pass { @@ -98,6 +102,7 @@ material openmw_stars // used for both sun and sun glare material openmw_sun { + allow_fixed_function false mrt_output true pass { From 25a5657d80b09c6a878a8cd7f92c57181ce15ebb Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 9 Jul 2012 18:26:00 +0200 Subject: [PATCH 037/298] Issue #324: Modified the interface for modified stats --- apps/openmw/mwmechanics/stat.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 2449075a8..996036fc1 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -31,6 +31,11 @@ namespace MWMechanics return mModified; } + T getModifier() const + { + return mModified-mBase; + } + /// Set base and modified to \a value. void set (const T& value) { @@ -65,10 +70,9 @@ namespace MWMechanics mBase += diff; } - /// Change modified relatively. - void modify (const T& diff) + void setModifier (const T& modifier) { - mModified += diff; + mModified = mBase + modifier; } }; From 67c1c5ce1811271a15d93b8e0be906432cfe8a06 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 9 Jul 2012 20:42:45 +0200 Subject: [PATCH 038/298] Issue #324: adjusted setSkill script instructions according to the recent research --- apps/openmw/mwscript/statsextensions.cpp | 30 ++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 42781d766..c720e2c6a 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1,8 +1,12 @@ #include "statsextensions.hpp" +#include + #include +#include + #include #include @@ -260,8 +264,30 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). - setModified (value, 0); + MWMechanics::NpcStats& stats = MWWorld::Class::get (ptr).getNpcStats (ptr); + + MWWorld::LiveCellRef *ref = ptr.get(); + + assert (ref); + + const ESM::Class& class_ = + *MWBase::Environment::get().getWorld()->getStore().classes.find (ref->base->cls); + + float level = 0; + float progress = std::modf (stats.getSkill (mIndex).getBase(), &level); + + float modifier = stats.getSkill (mIndex).getModifier(); + + int newLevel = static_cast (value-modifier); + + progress = (progress / stats.getSkillGain (mIndex, class_, -1, level)) + * stats.getSkillGain (mIndex, class_, -1, newLevel); + + if (progress>=1) + progress = 0.999999999; + + stats.getSkill (mIndex).set (newLevel + progress); + stats.getSkill (mIndex).setModifier (modifier); } }; From a207c86fd14a69fd2ee8d7ba17278aa7127df96c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 9 Jul 2012 21:14:11 +0200 Subject: [PATCH 039/298] Issue #324: added some range checks --- apps/openmw/mwscript/statsextensions.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index c720e2c6a..33f086c6f 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -280,6 +280,11 @@ namespace MWScript int newLevel = static_cast (value-modifier); + if (newLevel<0) + newLevel = 0; + else if (newLevel>100) + newLevel = 100; + progress = (progress / stats.getSkillGain (mIndex, class_, -1, level)) * stats.getSkillGain (mIndex, class_, -1, newLevel); From 0d8150f4bd8782304c1ca297dd14633a4fe8627a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 9 Jul 2012 21:15:52 +0200 Subject: [PATCH 040/298] Issue #324: added useSkill function to NpcStats (adjust skill level according to use) --- apps/openmw/mwmechanics/npcstats.cpp | 15 +++++++++++++++ apps/openmw/mwmechanics/npcstats.hpp | 3 +++ 2 files changed, 18 insertions(+) diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 08ac12374..d2908e26e 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -1,6 +1,7 @@ #include "npcstats.hpp" +#include #include #include @@ -123,3 +124,17 @@ float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& cla return 1.0 / (level +1) * (1.0 / skillFactor) * typeFactor * specialisationFactor; } + +void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, int usageType) +{ + float base = getSkill (skillIndex).getBase(); + + int level = static_cast (base); + + base += getSkillGain (skillIndex, class_, usageType); + + if (static_cast (base)!=level) + base = level+1; + + getSkill (skillIndex).setBase (base); +} diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 1dccdd0d6..364dab03f 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -70,6 +70,9 @@ namespace MWMechanics ///< \param usageType: Usage specific factor, specified in the respective skill record; /// -1: use a factor of 1.0 instead. /// \param level Level to base calculation on; -1: use current level. + + void useSkill (int skillIndex, const ESM::Class& class_, int usageType = -1); + ///< Increase skill by usage. }; } From 1a9f59d5d466c7888f14bb82f7fc4ad58e4b14f3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jul 2012 00:07:33 +0200 Subject: [PATCH 041/298] shadows --- apps/openmw/mwrender/renderingmanager.cpp | 2 + apps/openmw/mwrender/shadows.cpp | 15 +++- extern/shiny | 2 +- files/materials/objects.mat | 20 +++++ files/materials/objects.shader | 90 +++++++++++++++++++++-- files/materials/objects.shaderset | 4 +- files/materials/shadows.h | 47 ++++++++++++ 7 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 files/materials/shadows.h diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ca26697b7..8ecb69b9e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -54,6 +54,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const sh::OgrePlatform* platform = new sh::OgrePlatform("General", (resDir / "materials").string()); platform->setCacheFolder ("./"); mFactory = new sh::Factory(platform); + mFactory->setSharedParameter ("pssmSplitPoints", sh::makeProperty(new sh::Vector4(0,0,0,0))); + mFactory->setSharedParameter ("shadowFar_fadeStart", sh::makeProperty(new sh::Vector4(0,0,0,0))); //The fog type must be set before any terrain objects are created as if the //fog type is set to FOG_NONE then the initially created terrain won't have any fog diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 2e09f4c31..b4982e22d 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include "renderconst.hpp" using namespace Ogre; @@ -84,6 +86,12 @@ void Shadows::recreate() mSceneMgr->setShadowTextureConfig(i, texsize/4, texsize/4, Ogre::PF_FLOAT32_R);*/ } + // Populate from split point 1, not 0, since split 0 isn't useful (usually 0) + const PSSMShadowCameraSetup::SplitPointList& splitPointList = getPSSMSetup()->getSplitPoints(); + sh::Vector4* splitPoints = new sh::Vector4(splitPointList[1], splitPointList[2], splitPointList[3], 1.0); + + sh::Factory::getInstance ().setSharedParameter ("pssmSplitPoints", sh::makeProperty(splitPoints)); + shadowCameraSetup = ShadowCameraSetupPtr(mPSSMSetup); } else @@ -96,6 +104,9 @@ void Shadows::recreate() } mSceneMgr->setShadowCameraSetup(shadowCameraSetup); + sh::Vector4* shadowFar_fadeStart = new sh::Vector4(mShadowFar, mFadeStart * mShadowFar, 0, 0); + sh::Factory::getInstance ().setSharedParameter ("shadowFar_fadeStart", sh::makeProperty(shadowFar_fadeStart)); + // Set visibility mask for the shadow render textures int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows") + (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows") @@ -111,7 +122,7 @@ void Shadows::recreate() // -------------------------------------------------------------------------------------------------------------------- // --------------------------- Debug overlays to display the content of shadow maps ----------------------------------- // -------------------------------------------------------------------------------------------------------------------- -/* + OverlayManager& mgr = OverlayManager::getSingleton(); Overlay* overlay; @@ -157,7 +168,7 @@ void Shadows::recreate() overlay->add2D(debugPanel); overlay->show(); } -*/ + } PSSMShadowCameraSetup* Shadows::getPSSMSetup() diff --git a/extern/shiny b/extern/shiny index 3e7e02a84..b3cfd41df 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 3e7e02a846ce6c3de7e2344a82d346293115eb7d +Subproject commit b3cfd41dff2758e268ce16f366b7e7857eee80ea diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 324fe1b2a..76e64112b 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -42,10 +42,30 @@ material openmw_objects_base ffp_vertex_colour_ambient $has_vertex_colour + texture_unit diffuseMap { direct_texture $diffuseMap create_in_ffp true } + + texture_unit shadowMap0 + { + content_type shadow + tex_address_mode clamp + filtering none + } + texture_unit shadowMap1 + { + content_type shadow + tex_address_mode clamp + filtering none + } + texture_unit shadowMap2 + { + content_type shadow + tex_address_mode clamp + filtering none + } } } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 831699364..dd1b489d5 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -1,20 +1,28 @@ #include "core.h" +#include "shadows.h" + #define FOG @shPropertyBool(fog) #define MRT @shPropertyNotBool(is_transparent) && @shPropertyBool(mrt_output) #define LIGHTING @shPropertyBool(lighting) -#if FOG || MRT +#define SHADOWS LIGHTING && 0 +#define SHADOWS_PSSM LIGHTING + +#define SHADOWS 1 && LIGHTING +#define SHADOWS_PSSM 0 && LIGHTING + +#if FOG || MRT || SHADOWS_PSSM #define NEED_DEPTH #endif -#define NUM_LIGHTS 8 - #define HAS_VERTEXCOLOR @shPropertyBool(has_vertex_colour) #ifdef SH_VERTEX_SHADER + // ------------------------------------- VERTEX --------------------------------------- + SH_BEGIN_PROGRAM shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) shInput(float2, uv0) @@ -33,11 +41,28 @@ shColourInput(float4) shOutput(float4, colorPassthrough) #endif + +#if SHADOWS + shOutput(float4, lightSpacePos0) + shUniform(float4x4 texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) + shUniform(float4x4 worldMatrix) @shAutoConstant(worldMatrix, world_matrix) +#endif + +#if SHADOWS_PSSM + @shForeach(3) + shOutput(float4, lightSpacePos@shIterator) + shUniform(float4x4 texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator) + @shEndForeach + shUniform(float4x4 worldMatrix) @shAutoConstant(worldMatrix, world_matrix) +#endif SH_START_PROGRAM { shOutputPosition = shMatrixMult(wvp, shInputPosition); UV = uv0; +#if LIGHTING normalPassthrough = normal.xyz; +#endif + #ifdef NEED_DEPTH depthPassthrough = shOutputPosition.z; #endif @@ -49,10 +74,22 @@ #if HAS_VERTEXCOLOR colorPassthrough = colour; #endif + +#if SHADOWS + lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); +#endif +#if SHADOWS_PSSM + float4 wPos = shMatrixMult(worldMatrix, shInputPosition); + @shForeach(3) + lightSpacePos@shIterator = shMatrixMult(texViewProjMatrix@shIterator, wPos); + @shEndForeach +#endif } #else + // ----------------------------------- FRAGMENT ------------------------------------------ + SH_BEGIN_PROGRAM shSampler2D(diffuseMap) shInput(float2, UV) @@ -76,7 +113,7 @@ shUniform(float4 materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) - @shForeach(NUM_LIGHTS) + @shForeach(8) shUniform(float4 lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) shUniform(float4 lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) shUniform(float4 lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) @@ -91,6 +128,24 @@ #ifdef HAS_VERTEXCOLOR shInput(float4, colorPassthrough) #endif + +#if SHADOWS + shInput(float4, lightSpacePos0) + shSampler2D(shadowMap0) + shUniform(float2 invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, 0) +#endif +#if SHADOWS_PSSM + @shForeach(3) + shInput(float4, lightSpacePos@shIterator) + shSampler2D(shadowMap@shIterator) + shUniform(float2 invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator) + @shEndForeach + shUniform(float4 pssmSplitPoints) @shSharedParameter(pssmSplitPoints) +#endif + +#if SHADOWS || SHADOWS_PSSM + shUniform(float4 shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) +#endif SH_START_PROGRAM { shOutputColor(0) = shSample(diffuseMap, UV); @@ -101,15 +156,38 @@ float d; float3 ambient = materialAmbient.xyz * lightAmbient.xyz; - @shForeach(NUM_LIGHTS) + @shForeach(8) + + // shadows only for the first (directional) light +#if @shIterator == 0 + #if SHADOWS + float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0); + #endif + #if SHADOWS_PSSM + float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depthPassthrough, pssmSplitPoints); + #endif + + #if SHADOWS || SHADOWS_PSSM + float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; + float fade = 1-((depthPassthrough - shadowFar_fadeStart.y) / fadeRange); + shadow = (depthPassthrough > shadowFar_fadeStart.x) ? 1 : ((depthPassthrough > shadowFar_fadeStart.y) ? 1-((1-shadow)*fade) : shadow); + #endif + + #if !SHADOWS && !SHADOWS_PSSM + float shadow = 1.0; + #endif +#endif lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePositionPassthrough.xyz * lightPosObjSpace@shIterator.w); d = length(lightDir); lightDir = normalize(lightDir); +#if @shIterator == 0 + diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; +#else diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); - +#endif @shEndForeach #if HAS_VERTEXCOLOR diff --git a/files/materials/objects.shaderset b/files/materials/objects.shaderset index 7d01973e3..e84368a5b 100644 --- a/files/materials/objects.shaderset +++ b/files/materials/objects.shaderset @@ -2,7 +2,7 @@ shader_set openmw_objects_vertex { source objects.shader type vertex - profiles_cg vs_2_0 arbvp1 + profiles_cg vs_2_0 vp40 arbvp1 profiles_hlsl vs_2_0 } @@ -10,6 +10,6 @@ shader_set openmw_objects_fragment { source objects.shader type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 profiles_hlsl ps_2_0 } diff --git a/files/materials/shadows.h b/files/materials/shadows.h new file mode 100644 index 000000000..a3518b976 --- /dev/null +++ b/files/materials/shadows.h @@ -0,0 +1,47 @@ + + + +float depthShadowPCF (shTexture2D shadowMap, float4 shadowMapPos, float2 offset) +{ + shadowMapPos /= shadowMapPos.w; + float3 o = float3(offset.xy, -offset.x) * 0.3; + //float3 o = float3(0,0,0); + float c = (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy - o.xy).r) ? 1 : 0; // top left + c += (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy + o.xy).r) ? 1 : 0; // bottom right + c += (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy + o.zy).r) ? 1 : 0; // bottom left + c += (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy - o.zy).r) ? 1 : 0; // top right + return c / 4; +} + + + +float pssmDepthShadow ( + + + float4 lightSpacePos0, + float2 invShadowmapSize0, + shTexture2D shadowMap0, + + float4 lightSpacePos1, + float2 invShadowmapSize1, + shTexture2D shadowMap1, + + float4 lightSpacePos2, + float2 invShadowmapSize2, + shTexture2D shadowMap2, + + float depth, + float4 pssmSplitPoints) + +{ + float shadow; + + if (depth < pssmSplitPoints.x) + shadow = depthShadowPCF(shadowMap0, lightSpacePos0, invShadowmapSize0); + else if (depth < pssmSplitPoints.y) + shadow = depthShadowPCF(shadowMap1, lightSpacePos1, invShadowmapSize1); + else + shadow = depthShadowPCF(shadowMap2, lightSpacePos2, invShadowmapSize2); + + return shadow; +} From 5d1e6b6bf6d8c22bf603a2aaffd53dff6b3fae35 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jul 2012 00:09:47 +0200 Subject: [PATCH 042/298] remove cg profile check --- apps/openmw/mwrender/renderingmanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8ecb69b9e..cd12cfad0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -85,8 +85,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities(); if (!waterShaderSupported()) Settings::Manager::setBool("shader", "Water", false); - if ( !(caps->isShaderProfileSupported("fp40") || caps->isShaderProfileSupported("ps_4_0")) - || !Settings::Manager::getBool("shaders", "Objects")) + if (!Settings::Manager::getBool("shaders", "Objects")) Settings::Manager::setBool("enabled", "Shadows", false); applyCompositors(); From 3537af051d0bd308db2c0466dfe4de683d5acbb7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jul 2012 00:17:52 +0200 Subject: [PATCH 043/298] removed the old shader helper --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 4 - apps/openmw/mwrender/renderingmanager.hpp | 3 - apps/openmw/mwrender/shaderhelper.cpp | 309 ---------------------- apps/openmw/mwrender/shaderhelper.hpp | 29 -- 5 files changed, 1 insertion(+), 346 deletions(-) delete mode 100644 apps/openmw/mwrender/shaderhelper.cpp delete mode 100644 apps/openmw/mwrender/shaderhelper.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 911bd70d5..382adde46 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects - renderinginterface localmap occlusionquery terrain terrainmaterial water shadows shaderhelper + renderinginterface localmap occlusionquery terrain terrainmaterial water shadows compositors ) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index cd12cfad0..5d9395460 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -30,7 +30,6 @@ #include "../mwinput/inputmanager.hpp" // FIXME #include "shadows.hpp" -#include "shaderhelper.hpp" #include "localmap.hpp" #include "water.hpp" #include "compositors.hpp" @@ -108,7 +107,6 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const cameraPitchNode->attachObject(mRendering.getCamera()); mShadows = new Shadows(&mRendering); - mShaderHelper = new ShaderHelper(this); mTerrainManager = new TerrainManager(mRendering.getScene(), this); @@ -132,7 +130,6 @@ RenderingManager::~RenderingManager () delete mPlayer; delete mSkyManager; delete mDebugging; - delete mShaderHelper; delete mShadows; delete mTerrainManager; delete mLocalMap; @@ -617,7 +614,6 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec else if (it->second == "shader" && it->first == "Water") { applyCompositors(); - mShaderHelper->applyShaders(); } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index db5a731b4..73739cfae 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -52,7 +52,6 @@ namespace MWRender { class Shadows; - class ShaderHelper; class LocalMap; class Water; class Compositors; @@ -223,8 +222,6 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList MWRender::Shadows* mShadows; - MWRender::ShaderHelper* mShaderHelper; - MWRender::Compositors* mCompositors; }; diff --git a/apps/openmw/mwrender/shaderhelper.cpp b/apps/openmw/mwrender/shaderhelper.cpp deleted file mode 100644 index 1f9086db4..000000000 --- a/apps/openmw/mwrender/shaderhelper.cpp +++ /dev/null @@ -1,309 +0,0 @@ -#include "shaderhelper.hpp" -#include "renderingmanager.hpp" -#include "shadows.hpp" - -#include -#include -#include -#include - -#include - -using namespace Ogre; -using namespace MWRender; - -ShaderHelper::ShaderHelper(RenderingManager* rend) -{ - mRendering = rend; - applyShaders(); -} - -void ShaderHelper::applyShaders() -{ - if (!Settings::Manager::getBool("shaders", "Objects")) return; - - bool mrt = RenderingManager::useMRT(); - bool shadows = Settings::Manager::getBool("enabled", "Shadows"); - bool split = Settings::Manager::getBool("split", "Shadows"); - - // shader for normal rendering - createShader(mrt, shadows, split, "main"); - - // fallback shader without mrt and without shadows - // (useful for reflection and for minimap) - createShader(false, false, false, "main_fallback"); -} - -void ShaderHelper::createShader(const bool mrt, const bool shadows, const bool split, const std::string& name) -{ - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - - const int numsplits = 3; - - // the number of lights to support. - // when rendering an object, OGRE automatically picks the lights that are - // closest to the object being rendered. unfortunately this mechanism does - // not work perfectly for objects batched together (they will all use the same - // lights). to work around this, we are simply pushing the maximum number - // of lights here in order to minimize disappearing lights. - int num_lights = Settings::Manager::getInt("num lights", "Objects"); - - { - // vertex - HighLevelGpuProgramPtr vertex; - if (!mgr.getByName(name+"_vp").isNull()) - mgr.remove(name+"_vp"); - - vertex = mgr.createProgram(name+"_vp", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_VERTEX_PROGRAM); - vertex->setParameter("profiles", "vs_4_0 vs_2_x vp40 arbvp1"); - vertex->setParameter("entry_point", "main_vp"); - StringUtil::StrStreamType outStream; - outStream << - "void main_vp( \n" - " float4 position : POSITION, \n" - " float4 normal : NORMAL, \n" - " float4 colour : COLOR, \n" - " in float2 uv : TEXCOORD0, \n" - " out float2 oUV : TEXCOORD0, \n" - " out float4 oPosition : POSITION, \n" - " out float4 oPositionObjSpace : TEXCOORD1, \n" - " out float4 oNormal : TEXCOORD2, \n" - " out float oDepth : TEXCOORD3, \n" - " out float4 oVertexColour : TEXCOORD4, \n"; - if (shadows && !split) outStream << - " out float4 oLightSpacePos0 : TEXCOORD5, \n" - " uniform float4x4 worldMatrix, \n" - " uniform float4x4 texViewProjMatrix0, \n"; - else - { - for (int i=0; isetSource(outStream.str()); - vertex->load(); - vertex->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - if (shadows) - { - vertex->getDefaultParameters()->setNamedAutoConstant("worldMatrix", GpuProgramParameters::ACT_WORLD_MATRIX); - if (!split) - vertex->getDefaultParameters()->setNamedAutoConstant("texViewProjMatrix0", GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, 0); - else - { - for (int i=0; igetDefaultParameters()->setNamedAutoConstant("texViewProjMatrix"+StringConverter::toString(i), GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, i); - } - } - } - } - - { - // fragment - HighLevelGpuProgramPtr fragment; - if (!mgr.getByName(name+"_fp").isNull()) - mgr.remove(name+"_fp"); - - fragment = mgr.createProgram(name+"_fp", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_FRAGMENT_PROGRAM); - fragment->setParameter("profiles", "ps_4_0 ps_2_x fp40 arbfp1"); - fragment->setParameter("entry_point", "main_fp"); - StringUtil::StrStreamType outStream; - - if (shadows) outStream << - "float depthShadow(sampler2D shadowMap, float4 shadowMapPos, float2 offset) \n" - "{ \n" - " shadowMapPos /= shadowMapPos.w; \n" - " float3 o = float3(offset.xy, -offset.x) * 0.3f; \n" - " float c = (shadowMapPos.z <= tex2D(shadowMap, shadowMapPos.xy - o.xy).r) ? 1 : 0; // top left \n" - " c += (shadowMapPos.z <= tex2D(shadowMap, shadowMapPos.xy + o.xy).r) ? 1 : 0; // bottom right \n" - " c += (shadowMapPos.z <= tex2D(shadowMap, shadowMapPos.xy + o.zy).r) ? 1 : 0; // bottom left \n" - " c += (shadowMapPos.z <= tex2D(shadowMap, shadowMapPos.xy - o.zy).r) ? 1 : 0; // top right \n" - " return c / 4; \n" - "} \n"; - - outStream << - "void main_fp( \n" - " in float2 uv : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n" - " uniform sampler2D texture : register(s0), \n" - " float4 positionObjSpace : TEXCOORD1, \n" - " float4 normal : TEXCOORD2, \n" - " float iDepth : TEXCOORD3, \n" - " float4 vertexColour : TEXCOORD4, \n" - " uniform float4 fogColour, \n" - " uniform float4 fogParams, \n"; - - if (shadows) outStream << - " uniform float4 shadowFar_fadeStart, \n"; - - if (shadows && !split) outStream << - " uniform sampler2D shadowMap : register(s1), \n" - " float4 lightSpacePos0 : TEXCOORD5, \n" - " uniform float4 invShadowmapSize0, \n"; - else - { - outStream << - " uniform float4 pssmSplitPoints, \n"; - for (int i=0; i shadowFar_fadeStart.x) ? 1 : ((iDepth > shadowFar_fadeStart.y) ? 1-((1-shadow)*fade) : shadow); \n" - " lightColour.xyz += shadow * lit(dot(normalize(lightDir), normalize(normal)), 0, 0).y * lightDiffuse"<setSource(outStream.str()); - fragment->load(); - - for (int i=0; igetDefaultParameters()->setNamedAutoConstant("lightPositionObjSpace"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE, i); - fragment->getDefaultParameters()->setNamedAutoConstant("lightDiffuse"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_DIFFUSE_COLOUR, i); - fragment->getDefaultParameters()->setNamedAutoConstant("lightAttenuation"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_ATTENUATION, i); - } - fragment->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - fragment->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); - fragment->getDefaultParameters()->setNamedAutoConstant("ambient", GpuProgramParameters::ACT_SURFACE_AMBIENT_COLOUR); - fragment->getDefaultParameters()->setNamedAutoConstant("lightAmbient", GpuProgramParameters::ACT_AMBIENT_LIGHT_COLOUR); - fragment->getDefaultParameters()->setNamedAutoConstant("fogColour", GpuProgramParameters::ACT_FOG_COLOUR); - fragment->getDefaultParameters()->setNamedAutoConstant("fogParams", GpuProgramParameters::ACT_FOG_PARAMS); - - if (shadows) - { - fragment->getDefaultParameters()->setNamedConstant("shadowFar_fadeStart", Vector4(mRendering->getShadows()->getShadowFar(), mRendering->getShadows()->getFadeStart()*mRendering->getShadows()->getShadowFar(), 0, 0)); - for (int i=0; i < (split ? numsplits : 1); ++i) - { - fragment->getDefaultParameters()->setNamedAutoConstant("invShadowmapSize" + StringConverter::toString(i), GpuProgramParameters::ACT_INVERSE_TEXTURE_SIZE, i+1); - } - if (split) - { - Vector4 splitPoints; - const PSSMShadowCameraSetup::SplitPointList& splitPointList = mRendering->getShadows()->getPSSMSetup()->getSplitPoints(); - // Populate from split point 1, not 0, since split 0 isn't useful (usually 0) - for (int i = 1; i < numsplits; ++i) - { - splitPoints[i-1] = splitPointList[i]; - } - fragment->getDefaultParameters()->setNamedConstant("pssmSplitPoints", splitPoints); - } - } - - if (mrt) - fragment->getDefaultParameters()->setNamedAutoConstant("far", GpuProgramParameters::ACT_FAR_CLIP_DISTANCE); - } -} diff --git a/apps/openmw/mwrender/shaderhelper.hpp b/apps/openmw/mwrender/shaderhelper.hpp deleted file mode 100644 index 356d345de..000000000 --- a/apps/openmw/mwrender/shaderhelper.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef GAME_SHADERHELPER_H -#define GAME_SHADERHELPER_H - -#include - -namespace MWRender -{ - class RenderingManager; - - /// - /// \brief manages the main shader - /// - class ShaderHelper - { - public: - ShaderHelper(RenderingManager* rend); - - void applyShaders(); - ///< apply new settings - - private: - RenderingManager* mRendering; - - void createShader(const bool mrt, const bool shadows, const bool split, const std::string& name); - }; - -} - -#endif From d3a31a24ce860cb907defe2288bf5bfd7a9c871c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 9 Jul 2012 20:53:54 -0700 Subject: [PATCH 044/298] Use proper strings and vectors instead of slice arrays for NIF files Slice arrays use pre-allocated pointers whose memory is managed externally. This is unnecessary and ultimately detrimental since it prevents any kind of data fixup (e.g. little endian to big endian, p[adding handling), and it also makes it difficult to use Ogre data streams. --- components/nif/controlled.hpp | 2 +- components/nif/data.hpp | 10 +-- components/nif/extra.hpp | 4 +- components/nif/nif_file.cpp | 8 +- components/nif/nif_file.hpp | 28 +++++-- components/nif/node.hpp | 8 +- components/nif/record.hpp | 2 +- components/nifbullet/bullet_nif_loader.cpp | 8 +- components/nifogre/ogre_nif_loader.cpp | 93 +++++++++++----------- 9 files changed, 86 insertions(+), 77 deletions(-) diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index 24281146f..b9d84b58a 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -53,7 +53,7 @@ public: class Named : public Controlled { public: - Misc::SString name; + std::string name; void read(NIFFile *nif) { diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 0684470ec..9cdbec537 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -39,8 +39,8 @@ public: // internal (data is inside the nif itself) texture? bool external; - Misc::SString filename; // In case of external textures - NiPixelDataPtr data; // In case of internal textures + std::string filename; // In case of external textures + NiPixelDataPtr data; // In case of internal textures /* Pixel layout 0 - Palettised @@ -96,7 +96,7 @@ public: class ShapeData : public Record { public: - Misc::FloatArray vertices, normals, colors, uvlist; + std::vector vertices, normals, colors, uvlist; const Vector *center; float radius; @@ -131,7 +131,7 @@ class NiTriShapeData : public ShapeData { public: // Triangles, three vertex indices per triangle - Misc::SliceArray triangles; + std::vector triangles; void read(NIFFile *nif) { @@ -390,7 +390,7 @@ public: { const BoneTrafo *trafo; const Vector4 *unknown; - Misc::SliceArray weights; + std::vector weights; }; struct BoneInfoCopy { diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index ad788661a..e5829fdfc 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -66,7 +66,7 @@ public: struct TextKey { float time; - Misc::SString text; + std::string text; }; std::vector list; @@ -93,7 +93,7 @@ public: "MRK" - marker, only visible in the editor, not rendered in-game "NCO" - no collision */ - Misc::SString string; + std::string string; void read(NIFFile *nif) { diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 42aa43f8b..35714bfbe 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -46,8 +46,8 @@ using namespace Misc; void NIFFile::parse() { // Check the header string - const char* head = getString(40); - if(!begins(head, "NetImmerse File Format")) + std::string head = getString(40); + if(head.compare(0, 22, "NetImmerse File Format") != 0) fail("Invalid NIF header"); // Get BCD version @@ -70,7 +70,7 @@ void NIFFile::parse() for(int i=0;irecType != RC_MISSING); diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index bb6f73259..d3f5f7c06 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -122,29 +122,41 @@ public: char getByte() { return getType(); } template - Misc::SliceArray getArrayLen(int num) - { return Misc::SliceArray((const X*)inp->getPtr(num*sizeof(X)),num); } + std::vector getArrayLen(int num) + { + std::vector v(num); + memcpy(&v[0], inp->getPtr(num*sizeof(X)), num*sizeof(X)); + return v; + } template - Misc::SliceArray getArray() + std::vector getArray() { int len = getInt(); return getArrayLen(len); } - Misc::SString getString() { return getArray(); } - const Vector *getVector() { return getPtr(); } const Matrix *getMatrix() { return getPtr(); } const Transformation *getTrafo() { return getPtr(); } const Vector4 *getVector4() { return getPtr(); } - Misc::FloatArray getFloatLen(int num) + std::vector getFloatLen(int num) { return getArrayLen(num); } // For fixed-size strings where you already know the size - const char *getString(int size) - { return (const char*)inp->getPtr(size); } + std::string getString(size_t size) + { + std::string str; + str.resize(size); + memcpy(&str[0], inp->getPtr(size), size); + return str.substr(0, str.find('\0')); + } + std::string getString() + { + size_t size = getInt(); + return getString(size); + } }; } // Namespace diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 5a2847b6c..9d99dbd1d 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -186,10 +186,10 @@ struct NiTriShape : Node NiTriShapeCopy clone() { NiTriShapeCopy copy; - copy.sname = name.toString(); - float *ptr = (float*)data->vertices.ptr; - float *ptrNormals = (float*)data->normals.ptr; - int numVerts = data->vertices.length / 3; + copy.sname = name; + float *ptr = (float*)&data->vertices[0]; + float *ptrNormals = (float*)&data->normals[0]; + int numVerts = data->vertices.size() / 3; for(int i = 0; i < numVerts; i++) { float *current = (float*) (ptr + i * 3); diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 06fdce55e..84f253eb8 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -90,7 +90,7 @@ struct Record { // Record type and type name int recType; - Misc::SString recName; + std::string recName; Record() : recType(RC_MISSING) {} diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index 30cb4562d..9f31a3eed 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -124,7 +124,7 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) if (node == NULL) { warn("First record in file was not a node, but a " + - r->recName.toString() + ". Skipping file."); + r->recName + ". Skipping file."); return; } @@ -292,10 +292,10 @@ void ManualBulletShapeLoader::handleNiTriShape(Nif::NiTriShape *shape, int flags Nif::NiTriShapeData *data = shape->data.getPtr(); - float* vertices = (float*)data->vertices.ptr; - unsigned short* triangles = (unsigned short*)data->triangles.ptr; + float* vertices = (float*)&data->vertices[0]; + unsigned short* triangles = (unsigned short*)&data->triangles[0]; - for(unsigned int i=0; i < data->triangles.length; i = i+3) + for(unsigned int i=0; i < data->triangles.size(); i = i+3) { Ogre::Vector3 b1(vertices[triangles[i+0]*3]*parentScale,vertices[triangles[i+0]*3+1]*parentScale,vertices[triangles[i+0]*3+2]*parentScale); Ogre::Vector3 b2(vertices[triangles[i+1]*3]*parentScale,vertices[triangles[i+1]*3+1]*parentScale,vertices[triangles[i+1]*3+2]*parentScale); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 895c51a0d..55dbba518 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -399,7 +399,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std { // cout << "s:" << shape << "\n"; NiTriShapeData *data = shape->data.getPtr(); - SubMesh *sub = mesh->createSubMesh(shape->name.toString()); + SubMesh *sub = mesh->createSubMesh(shape->name); int nextBuf = 0; @@ -407,7 +407,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std // great. // Add vertices - int numVerts = data->vertices.length / 3; + int numVerts = data->vertices.size() / 3; sub->vertexData = new VertexData(); sub->vertexData->vertexCount = numVerts; sub->useSharedVertices = false; @@ -422,12 +422,12 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std if(flip) { - float *datamod = new float[data->vertices.length]; + float *datamod = new float[data->vertices.size()]; //std::cout << "Shape" << shape->name.toString() << "\n"; for(int i = 0; i < numVerts; i++) { int index = i * 3; - const float *pos = data->vertices.ptr + index; + const float *pos = &data->vertices[index]; Ogre::Vector3 original = Ogre::Vector3(*pos ,*(pos+1), *(pos+2)); original = mTransform * original; mBoundingBox.merge(original); @@ -440,14 +440,14 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std } else { - vbuf->writeData(0, vbuf->getSizeInBytes(), data->vertices.ptr, false); + vbuf->writeData(0, vbuf->getSizeInBytes(), &data->vertices[0], false); } VertexBufferBinding* bind = sub->vertexData->vertexBufferBinding; bind->setBinding(nextBuf++, vbuf); - if (data->normals.length) + if (data->normals.size()) { decl->addElement(nextBuf, 0, VET_FLOAT3, VES_NORMAL); vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( @@ -459,11 +459,11 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std Quaternion rotation = mTransform.extractQuaternion(); rotation.normalise(); - float *datamod = new float[data->normals.length]; + float *datamod = new float[data->normals.size()]; for(int i = 0; i < numVerts; i++) { int index = i * 3; - const float *pos = data->normals.ptr + index; + const float *pos = &data->normals[index]; Ogre::Vector3 original = Ogre::Vector3(*pos ,*(pos+1), *(pos+2)); original = rotation * original; if (mNormaliseNormals) @@ -481,16 +481,16 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std } else { - vbuf->writeData(0, vbuf->getSizeInBytes(), data->normals.ptr, false); + vbuf->writeData(0, vbuf->getSizeInBytes(), &data->normals[0], false); } bind->setBinding(nextBuf++, vbuf); } // Vertex colors - if (data->colors.length) + if (data->colors.size()) { - const float *colors = data->colors.ptr; + const float *colors = &data->colors[0]; RenderSystem* rs = Root::getSingleton().getRenderSystem(); std::vector colorsRGB(numVerts); RGBA *pColour = &colorsRGB.front(); @@ -508,7 +508,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std bind->setBinding(nextBuf++, vbuf); } - if (data->uvlist.length) + if (data->uvlist.size()) { decl->addElement(nextBuf, 0, VET_FLOAT2, VES_TEXTURE_COORDINATES); @@ -518,12 +518,12 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std if(flip) { - float *datamod = new float[data->uvlist.length]; + float *datamod = new float[data->uvlist.size()]; - for(unsigned int i = 0; i < data->uvlist.length; i+=2){ - float x = *(data->uvlist.ptr + i); + for(unsigned int i = 0; i < data->uvlist.size(); i+=2){ + float x = data->uvlist[i]; - float y = *(data->uvlist.ptr + i + 1); + float y = data->uvlist[i + 1]; datamod[i] =x; datamod[i + 1] =y; @@ -532,13 +532,12 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std delete [] datamod; } else - vbuf->writeData(0, vbuf->getSizeInBytes(), data->uvlist.ptr, false); + vbuf->writeData(0, vbuf->getSizeInBytes(), &data->uvlist[0], false); bind->setBinding(nextBuf++, vbuf); } // Triangle faces - The total number of triangle points - int numFaces = data->triangles.length; - + int numFaces = data->triangles.size(); if (numFaces) { @@ -558,7 +557,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std for (size_t i = 0; i < sub->indexData->indexCount; i+=3) { - const short *pos = data->triangles.ptr + index; + const short *pos = &data->triangles[index]; uint16 i0 = (uint16) *(pos+0); uint16 i1 = (uint16) *(pos+1); uint16 i2 = (uint16) *(pos+2); @@ -578,7 +577,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std } else - ibuf->writeData(0, ibuf->getSizeInBytes(), data->triangles.ptr, false); + ibuf->writeData(0, ibuf->getSizeInBytes(), &data->triangles[0], false); sub->indexData->indexBuffer = ibuf; } @@ -706,8 +705,6 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou NiSourceTexture *st = t->textures[0].texture.getPtr(); if (st->external) { - SString tname = st->filename; - /* findRealTexture checks if the file actually exists. If it doesn't, and the name ends in .tga, it will try replacing the extension with .dds instead @@ -721,7 +718,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou problem since all the nif data is stored in a local throwaway buffer. */ - texName = "textures\\" + tname.toString(); + texName = "textures\\" + st->filename; findRealTexture(texName); } else warn("Found internal texture, ignoring."); @@ -795,9 +792,9 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou level. */ NiTriShapeData *data = shape->data.getPtr(); - int numVerts = data->vertices.length / 3; + int numVerts = data->vertices.size() / 3; - float *ptr = (float*)data->vertices.ptr; + float *ptr = (float*)&data->vertices[0]; float *optr = ptr; std::list vertexBoneAssignments; @@ -828,7 +825,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou std::vector vertexPosOriginal(numVerts, Ogre::Vector3::ZERO); std::vector vertexNormalOriginal(numVerts, Ogre::Vector3::ZERO); - float *ptrNormals = (float*)data->normals.ptr; + float *ptrNormals = (float*)&data->normals[0]; //the bone from skin->bones[boneIndex] is linked to skin->data->bones[boneIndex] //the first one contains a link to the bone, the second vertex transformation //relative to the bone @@ -849,13 +846,13 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou { if(mSkel.isNull()) { - std::cout << "No skeleton for :" << shape->skin->bones[boneIndex].name.toString() << std::endl; + std::cout << "No skeleton for :" << shape->skin->bones[boneIndex].name << std::endl; break; } //get the bone from bones array of skindata - if(!mSkel->hasBone(shape->skin->bones[boneIndex].name.toString())) + if(!mSkel->hasBone(shape->skin->bones[boneIndex].name)) std::cout << "We don't have this bone"; - bonePtr = mSkel->getBone(shape->skin->bones[boneIndex].name.toString()); + bonePtr = mSkel->getBone(shape->skin->bones[boneIndex].name); // final_vector = old_vector + old_rotation*new_vector*old_scale @@ -863,19 +860,19 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou Nif::NiSkinData::BoneInfoCopy boneinfocopy; boneinfocopy.trafo.rotation = convertRotation(it->trafo->rotation); boneinfocopy.trafo.trans = convertVector3(it->trafo->trans); - boneinfocopy.bonename = shape->skin->bones[boneIndex].name.toString(); + boneinfocopy.bonename = shape->skin->bones[boneIndex].name; boneinfocopy.bonehandle = bonePtr->getHandle(); copy.boneinfo.push_back(boneinfocopy); - for (unsigned int i=0; iweights.length; i++) + for (unsigned int i=0; iweights.size(); i++) { vecPos = bonePtr->_getDerivedPosition() + bonePtr->_getDerivedOrientation() * convertVector3(it->trafo->trans); vecRot = bonePtr->_getDerivedOrientation() * convertRotation(it->trafo->rotation); - unsigned int verIndex = (it->weights.ptr + i)->vertex; + unsigned int verIndex = it->weights[i].vertex; //boneinfo.weights.push_back(*(it->weights.ptr + i)); Nif::NiSkinData::IndividualWeight ind; - ind.weight = (it->weights.ptr + i)->weight; + ind.weight = it->weights[i].weight; ind.boneinfocopyindex = copy.boneinfo.size() - 1; if(copy.vertsToWeights.find(verIndex) == copy.vertsToWeights.end()) { @@ -893,7 +890,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou { //apply transformation to the vertices Vector3 absVertPos = vecPos + vecRot * Vector3(ptr + verIndex *3); - absVertPos = absVertPos * (it->weights.ptr + i)->weight; + absVertPos = absVertPos * it->weights[i].weight; vertexPosOriginal[verIndex] = Vector3(ptr + verIndex *3); mBoundingBox.merge(absVertPos); @@ -903,10 +900,10 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou //apply rotation to the normals (not every vertex has a normal) //FIXME: I guessed that vertex[i] = normal[i], is that true? - if (verIndex < data->normals.length) + if (verIndex < data->normals.size()) { Vector3 absNormalsPos = vecRot * Vector3(ptrNormals + verIndex *3); - absNormalsPos = absNormalsPos * (it->weights.ptr + i)->weight; + absNormalsPos = absNormalsPos * it->weights[i].weight; vertexNormalOriginal[verIndex] = Vector3(ptrNormals + verIndex *3); for (int j=0; j<3; j++) @@ -918,7 +915,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou else { Vector3 absVertPos = vecPos + vecRot * vertexPosOriginal[verIndex]; - absVertPos = absVertPos * (it->weights.ptr + i)->weight; + absVertPos = absVertPos * it->weights[i].weight; Vector3 old = Vector3(ptr + verIndex *3); absVertPos = absVertPos + old; @@ -929,10 +926,10 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou //apply rotation to the normals (not every vertex has a normal) //FIXME: I guessed that vertex[i] = normal[i], is that true? - if (verIndex < data->normals.length) + if (verIndex < data->normals.size()) { Vector3 absNormalsPos = vecRot * vertexNormalOriginal[verIndex]; - absNormalsPos = absNormalsPos * (it->weights.ptr + i)->weight; + absNormalsPos = absNormalsPos * it->weights[i].weight; Vector3 oldNormal = Vector3(ptrNormals + verIndex *3); absNormalsPos = absNormalsPos + oldNormal; @@ -945,7 +942,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou VertexBoneAssignment vba; vba.boneIndex = bonePtr->getHandle(); vba.vertexIndex = verIndex; - vba.weight = (it->weights.ptr + i)->weight; + vba.weight = it->weights[i].weight; vertexBoneAssignments.push_back(vba); @@ -981,9 +978,9 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou } // Remember to rotate all the vertex normals as well - if (data->normals.length) + if (data->normals.size()) { - ptr = (float*)data->normals.ptr; + ptr = (float*)&data->normals[0]; for (int i=0; i::iterator textiter = extra->list.begin(); textiter != extra->list.end(); textiter++) { - std::string text = textiter->text.toString(); + std::string text = textiter->text; replace(text.begin(), text.end(), '\n', '/'); @@ -1138,7 +1135,7 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, if (!mSkel.isNull()) //if there is a skeleton { - std::string name = node->name.toString(); + std::string name = node->name; // Quick-n-dirty workaround for the fact that several // bones may have the same name. @@ -1192,7 +1189,7 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, } else if (node->recType == RC_NiTriShape && bNiTri) { - std::string nodename = node->name.toString(); + std::string nodename = node->name; if (triname == "") { @@ -1334,7 +1331,7 @@ void NIFLoader::loadResource(Resource *resource) if (node == NULL) { warn("First record in file was not a node, but a " + - r->recName.toString() + ". Skipping file."); + r->recName + ". Skipping file."); return; } @@ -1358,7 +1355,7 @@ void NIFLoader::loadResource(Resource *resource) if (f->timeStart >= 10000000000000000.0f) continue; - data->setBonename(o->name.toString()); + data->setBonename(o->name); data->setStartTime(f->timeStart); data->setStopTime(f->timeStop); From 0143cacd2bebdf7a13b5e2539e54b2f3da8959bd Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 9 Jul 2012 21:35:36 -0700 Subject: [PATCH 045/298] Avoid returning pointers from NIFFile --- components/nif/data.hpp | 12 +++++----- components/nif/effect.hpp | 4 ++-- components/nif/nif_file.cpp | 2 +- components/nif/nif_file.hpp | 14 ++++++----- components/nif/nif_types.hpp | 4 ++-- components/nif/node.hpp | 14 +++++------ components/nif/property.hpp | 4 ++-- components/nifbullet/bullet_nif_loader.cpp | 2 +- components/nifogre/ogre_nif_loader.cpp | 28 +++++++++++----------- 9 files changed, 43 insertions(+), 41 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 9cdbec537..a9a5895f4 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -97,7 +97,7 @@ class ShapeData : public Record { public: std::vector vertices, normals, colors, uvlist; - const Vector *center; + Vector center; float radius; void read(NIFFile *nif) @@ -388,8 +388,8 @@ public: struct BoneInfo { - const BoneTrafo *trafo; - const Vector4 *unknown; + BoneTrafo trafo; + Vector4 unknown; std::vector weights; }; struct BoneInfoCopy @@ -406,7 +406,7 @@ public: unsigned int boneinfocopyindex; }; - const BoneTrafo *trafo; + BoneTrafo trafo; std::vector bones; void read(NIFFile *nif) @@ -414,7 +414,7 @@ public: assert(sizeof(BoneTrafo) == 4*(9+3+1)); assert(sizeof(VertWeight) == 6); - trafo = nif->getPtr(); + trafo = nif->getType(); int boneNum = nif->getInt(); nif->getInt(); // -1 @@ -424,7 +424,7 @@ public: { BoneInfo &bi = bones[i]; - bi.trafo = nif->getPtr(); + bi.trafo = nif->getType(); bi.unknown = nif->getVector4(); // Number of vertex weights diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index bac412c76..6ecc7c61a 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -42,7 +42,7 @@ struct NiLight : Effect Vector diffuse; Vector specular; }; - const SLight *light; + SLight light; void read(NIFFile *nif) { @@ -50,7 +50,7 @@ struct NiLight : Effect nif->getInt(); // 1 nif->getInt(); // 1? - light = nif->getPtr(); + light = nif->getType(); } }; diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 35714bfbe..36badbf0d 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -201,7 +201,7 @@ void NiSkinInstance::post(NIFFile *nif) if(bnum != data->bones.size()) nif->fail("Mismatch in NiSkinData bone count"); - root->makeRootBone(data->trafo); + root->makeRootBone(&data->trafo); for(size_t i=0; igetPtr(size); } - template const X* getPtr() { return (const X*)inp->getPtr(sizeof(X)); } - template X getType() { return *getPtr(); } + template X getType() + { + return *(const X*)inp->getPtr(sizeof(X)); + } unsigned short getShort() { return getType(); } int getInt() { return getType(); } float getFloat() { return getType(); } @@ -136,10 +138,10 @@ public: return getArrayLen(len); } - const Vector *getVector() { return getPtr(); } - const Matrix *getMatrix() { return getPtr(); } - const Transformation *getTrafo() { return getPtr(); } - const Vector4 *getVector4() { return getPtr(); } + Vector getVector() { return getType(); } + Matrix getMatrix() { return getType(); } + Transformation getTrafo() { return getType(); } + Vector4 getVector4() { return getType(); } std::vector getFloatLen(int num) { return getArrayLen(num); } diff --git a/components/nif/nif_types.hpp b/components/nif/nif_types.hpp index ee796cc99..41900a14e 100644 --- a/components/nif/nif_types.hpp +++ b/components/nif/nif_types.hpp @@ -60,7 +60,7 @@ struct Transformation float scale; Vector velocity; - static const Transformation* getIdentity() + static const Transformation& getIdentity() { static Transformation identity; static bool iset = false; @@ -73,7 +73,7 @@ struct Transformation iset = true; } - return &identity; + return identity; } }; #pragma pack(pop) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 9d99dbd1d..8a6af5777 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -42,14 +42,14 @@ class Node : public Named public: // Node flags. Interpretation depends somewhat on the type of node. int flags; - const Transformation *trafo; + Transformation trafo; PropertyList props; // Bounding box info bool hasBounds; - const Vector *boundPos; - const Matrix *boundRot; - const Vector *boundXYZ; // Box size + Vector boundPos; + Matrix boundRot; + Vector boundXYZ; // Box size void read(NIFFile *nif) { @@ -103,7 +103,7 @@ public: void makeBone(short ind, const NiSkinData::BoneInfo &bi) { boneInfo = &bi; - boneTrafo = bi.trafo; + boneTrafo = &bi.trafo; boneIndex = ind; } }; @@ -219,13 +219,13 @@ struct NiCamera : Node // Level of detail modifier float LOD; }; - const Camera *cam; + Camera cam; void read(NIFFile *nif) { Node::read(nif); - nif->getPtr(); + cam = nif->getType(); nif->getInt(); // -1 nif->getInt(); // 0 diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 619e3db0e..8485d978f 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -155,12 +155,12 @@ typedef Property NiWireframeProperty; template struct StructPropT : Property { - const T* data; + T data; void read(NIFFile *nif) { Property::read(nif); - data = nif->getPtr(); + data = nif->getType(); } }; diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index 9f31a3eed..f32b5e896 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -226,7 +226,7 @@ void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, Ogre::Vector3 finalPos; float finalScale; - Nif::Transformation &final = *((Nif::Transformation*)node->trafo); + Nif::Transformation &final = node->trafo; Ogre::Vector3 nodePos = getVector(&final); Ogre::Matrix3 nodeRot = getMatrix(&final); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 55dbba518..616e462fd 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -730,7 +730,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou if (a) { alphaFlags = a->flags; - alphaTest = a->data->threshold; + alphaTest = a->data.threshold; } // Material @@ -745,7 +745,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou if (m) { // Use NiMaterialProperty data to create the data - const S_MaterialProperty *d = m->data; + const S_MaterialProperty *d = &m->data; std::multimap::iterator itr = MaterialMap.find(texName); std::multimap::iterator lastElement; @@ -858,17 +858,17 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou Nif::NiSkinData::BoneInfoCopy boneinfocopy; - boneinfocopy.trafo.rotation = convertRotation(it->trafo->rotation); - boneinfocopy.trafo.trans = convertVector3(it->trafo->trans); + boneinfocopy.trafo.rotation = convertRotation(it->trafo.rotation); + boneinfocopy.trafo.trans = convertVector3(it->trafo.trans); boneinfocopy.bonename = shape->skin->bones[boneIndex].name; boneinfocopy.bonehandle = bonePtr->getHandle(); copy.boneinfo.push_back(boneinfocopy); for (unsigned int i=0; iweights.size(); i++) { vecPos = bonePtr->_getDerivedPosition() + - bonePtr->_getDerivedOrientation() * convertVector3(it->trafo->trans); + bonePtr->_getDerivedOrientation() * convertVector3(it->trafo.trans); - vecRot = bonePtr->_getDerivedOrientation() * convertRotation(it->trafo->rotation); + vecRot = bonePtr->_getDerivedOrientation() * convertRotation(it->trafo.rotation); unsigned int verIndex = it->weights[i].vertex; //boneinfo.weights.push_back(*(it->weights.ptr + i)); Nif::NiSkinData::IndividualWeight ind; @@ -959,9 +959,9 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou copy.boneSequence = boneSequence; // Rotate, scale and translate all the vertices, - const Matrix &rot = shape->trafo->rotation; - const Vector &pos = shape->trafo->pos; - float scale = shape->trafo->scale; + const Matrix &rot = shape->trafo.rotation; + const Vector &pos = shape->trafo.pos; + float scale = shape->trafo.scale; copy.trafo.trans = convertVector3(original.pos); copy.trafo.rotation = convertRotation(original.rotation); @@ -1148,19 +1148,19 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, parentBone->addChild(bone); bone->setInheritOrientation(true); - bone->setPosition(convertVector3(node->trafo->pos)); - bone->setOrientation(convertRotation(node->trafo->rotation)); + bone->setPosition(convertVector3(node->trafo.pos)); + bone->setOrientation(convertRotation(node->trafo.rotation)); } } } - Transformation original = *(node->trafo); + Transformation original = node->trafo; // Apply the parent transformation to this node. We overwrite the // existing data with the final transformation. if (trafo) { // Get a non-const reference to the node's data, since we're // overwriting it. TODO: Is this necessary? - Transformation &final = *((Transformation*)node->trafo); + Transformation &final = node->trafo; // For both position and rotation we have that: // final_vector = old_vector + old_rotation*new_vector*old_scale @@ -1184,7 +1184,7 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, { if (list.has(i)) - handleNode(&list[i], flags, node->trafo, bounds, bone, boneSequence); + handleNode(&list[i], flags, &node->trafo, bounds, bone, boneSequence); } } else if (node->recType == RC_NiTriShape && bNiTri) From b3aa453f9a2e005fb6ce203b56dffa612135a23c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 9 Jul 2012 22:02:12 -0700 Subject: [PATCH 046/298] Use Ogre data streams for loading NIFs --- components/nif/nif_file.hpp | 38 ++++++++-------------- components/nifbullet/bullet_nif_loader.cpp | 14 +------- components/nifbullet/bullet_nif_loader.hpp | 12 +------ components/nifogre/ogre_nif_loader.cpp | 24 +++----------- components/nifogre/ogre_nif_loader.hpp | 7 ---- 5 files changed, 21 insertions(+), 74 deletions(-) diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 59ba3a5b9..a2246e00e 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -24,9 +24,8 @@ #ifndef _NIF_FILE_H_ #define _NIF_FILE_H_ -#include -#include -#include +#include +#include #include #include @@ -36,8 +35,6 @@ #include "record.hpp" #include "nif_types.hpp" -using namespace Mangle::Stream; - namespace Nif { @@ -51,7 +48,7 @@ class NIFFile int ver; /// Input stream - StreamPtr inp; + Ogre::DataStreamPtr inp; /// File name, used for error messages std::string filename; @@ -72,22 +69,10 @@ public: } /// Open a NIF stream. The name is used for error messages. - NIFFile(StreamPtr nif, const std::string &name) + NIFFile(const std::string &name) : filename(name) { - /* Load the entire file into memory. This allows us to use - direct pointers to the data arrays in the NIF, instead of - individually allocating and copying each one. - - The NIF data is only stored temporarily in memory, since once - the mesh data is loaded it is siphoned into OGRE and - deleted. For that reason, we might improve this further and - use a shared region/pool based allocation scheme in the - future, especially since only one NIFFile will ever be loaded - at any given time. - */ - inp = StreamPtr(new BufferStream(nif)); - + inp = Ogre::ResourceGroupManager::getSingleton().openResource(name); parse(); } @@ -112,11 +97,14 @@ public: Parser functions ****************************************************/ - void skip(size_t size) { inp->getPtr(size); } + void skip(size_t size) { inp->skip(size); } template X getType() { - return *(const X*)inp->getPtr(sizeof(X)); + X obj; + if(inp->read(&obj, sizeof(X)) != sizeof(X)) + fail("Failed to read from NIF"); + return obj; } unsigned short getShort() { return getType(); } int getInt() { return getType(); } @@ -127,7 +115,8 @@ public: std::vector getArrayLen(int num) { std::vector v(num); - memcpy(&v[0], inp->getPtr(num*sizeof(X)), num*sizeof(X)); + if(inp->read(&v[0], num*sizeof(X)) != num*sizeof(X)) + fail("Failed to read from NIF"); return v; } @@ -151,7 +140,8 @@ public: { std::string str; str.resize(size); - memcpy(&str[0], inp->getPtr(size), size); + if(inp->read(&str[0], size) != size) + fail("Failed to read from NIF"); return str.substr(0, str.find('\0')); } std::string getString() diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index f32b5e896..ae9ac94c2 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -25,7 +25,6 @@ http://www.gnu.org/licenses/ . #include #include -#include #include "../nif/nif_file.hpp" #include "../nif/node.hpp" #include "../nif/data.hpp" @@ -47,13 +46,11 @@ typedef unsigned char ubyte; using namespace std; using namespace Ogre; using namespace Nif; -using namespace Mangle::VFS; using namespace NifBullet; ManualBulletShapeLoader::~ManualBulletShapeLoader() { - delete vfs; } Ogre::Matrix3 ManualBulletShapeLoader::getMatrix(Nif::Transformation* tr) @@ -94,20 +91,11 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mTriMesh = new btTriangleMesh(); - if (!vfs) vfs = new OgreVFS(resourceGroup); - - if (!vfs->isFile(resourceName)) - { - warn("File not found."); - return; - } - // Load the NIF. TODO: Wrap this in a try-catch block once we're out // of the early stages of development. Right now we WANT to catch // every error as early and intrusively as possible, as it's most // likely a sign of incomplete code rather than faulty input. - Nif::NIFFile nif(vfs->open(resourceName), resourceName); - + Nif::NIFFile nif(resourceName); if (nif.numRecords() < 1) { warn("Found no records in NIF."); diff --git a/components/nifbullet/bullet_nif_loader.hpp b/components/nifbullet/bullet_nif_loader.hpp index ed3aceac4..5a33074c9 100644 --- a/components/nifbullet/bullet_nif_loader.hpp +++ b/components/nifbullet/bullet_nif_loader.hpp @@ -50,14 +50,6 @@ namespace Nif class Matrix; } -namespace Mangle -{ - namespace VFS - { - class OgreVFS; - } -} - namespace NifBullet { @@ -68,7 +60,7 @@ class ManualBulletShapeLoader : public BulletShapeLoader { public: - ManualBulletShapeLoader():resourceGroup("General"){vfs = 0;} + ManualBulletShapeLoader():resourceGroup("General"){} virtual ~ManualBulletShapeLoader(); void warn(std::string msg) @@ -119,8 +111,6 @@ private: */ void handleNiTriShape(Nif::NiTriShape *shape, int flags,Ogre::Matrix3 parentRot,Ogre::Vector3 parentPos,float parentScales,bool raycastingOnly); - Mangle::VFS::OgreVFS *vfs; - std::string resourceName; std::string resourceGroup; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 616e462fd..64a1e0871 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -379,16 +379,12 @@ String NIFLoader::getUniqueName(const String &input) // is lost in that case. void NIFLoader::findRealTexture(String &texName) { - assert(vfs); - if (vfs->isFile(texName)) return; - - int len = texName.size(); - if (len < 4) return; + if(Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texName)) + return; // Change texture extension to .dds - texName[len-3] = 'd'; - texName[len-2] = 'd'; - texName[len-1] = 's'; + String::size_type pos = texName.rfind('.'); + texName.replace(pos, texName.length(), ".dds"); } //Handle node at top @@ -1290,9 +1286,6 @@ void NIFLoader::loadResource(Resource *resource) { calculateTransform(); } - // Set up the VFS if it hasn't been done already - if (!vfs) vfs = new OgreVFS(resourceGroup); - // Get the mesh mesh = dynamic_cast(resource); assert(mesh); @@ -1301,12 +1294,6 @@ void NIFLoader::loadResource(Resource *resource) resourceName = mesh->getName(); //std::cout << resourceName << "\n"; - if (!vfs->isFile(resourceName)) - { - warn("File "+resourceName+" not found."); - return; - } - // Helper that computes bounding boxes for us. BoundsFinder bounds; @@ -1314,8 +1301,7 @@ void NIFLoader::loadResource(Resource *resource) // of the early stages of development. Right now we WANT to catch // every error as early and intrusively as possible, as it's most // likely a sign of incomplete code rather than faulty input. - NIFFile nif(vfs->open(resourceName), resourceName); - + NIFFile nif(resourceName); if (nif.numRecords() < 1) { warn("Found no records in NIF."); diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index d73948fa8..55915b310 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -154,13 +154,6 @@ class NIFLoader : Ogre::ManualResourceLoader return resourceName + ".skel"; } - // This is the interface to the Ogre resource system. It allows us to - // load NIFs from BSAs, in the file system and in any other place we - // tell Ogre to look (eg. in zip or rar files.) It's also used to - // check for the existence of texture files, so we can exchange the - // extension from .tga to .dds if the texture is missing. - Mangle::VFS::OgreVFS *vfs; - std::string verbosePath; std::string resourceName; std::string resourceGroup; From 98ae7168b1fe10ec86f2e8810d06448dc0362669 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 10 Jul 2012 00:24:18 -0700 Subject: [PATCH 047/298] Fix double-incrementing a pointer --- components/nifogre/ogre_nif_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 64a1e0871..ec54319c1 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -968,7 +968,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou for (int i=0; i Date: Tue, 10 Jul 2012 00:27:13 -0700 Subject: [PATCH 048/298] Remove NIFFile::getType --- components/nif/data.hpp | 18 ++++-- components/nif/effect.hpp | 10 ++- components/nif/extra.hpp | 2 +- components/nif/nif_file.hpp | 124 ++++++++++++++++++++++++++++++------ components/nif/node.hpp | 19 +++++- components/nif/property.hpp | 23 ++++++- 6 files changed, 166 insertions(+), 30 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index a9a5895f4..1b5fa029b 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -105,16 +105,16 @@ public: int verts = nif->getShort(); if(nif->getInt()) - vertices = nif->getFloatLen(verts*3); + vertices = nif->getArrayLen(verts*3); if(nif->getInt()) - normals = nif->getFloatLen(verts*3); + normals = nif->getArrayLen(verts*3); center = nif->getVector(); radius = nif->getFloat(); if(nif->getInt()) - colors = nif->getFloatLen(verts*4); + colors = nif->getArrayLen(verts*4); int uvs = nif->getShort(); @@ -123,7 +123,7 @@ public: uvs &= 0x3f; if(nif->getInt()) - uvlist = nif->getFloatLen(uvs*verts*2); + uvlist = nif->getArrayLen(uvs*verts*2); } }; @@ -181,7 +181,7 @@ public: if(nif->getInt()) { // Particle sizes - nif->getFloatLen(activeCount); + nif->getArrayLen(activeCount); } } }; @@ -414,7 +414,9 @@ public: assert(sizeof(BoneTrafo) == 4*(9+3+1)); assert(sizeof(VertWeight) == 6); - trafo = nif->getType(); + trafo.rotation = nif->getMatrix(); + trafo.trans = nif->getVector(); + trafo.scale = nif->getFloat(); int boneNum = nif->getInt(); nif->getInt(); // -1 @@ -424,7 +426,9 @@ public: { BoneInfo &bi = bones[i]; - bi.trafo = nif->getType(); + bi.trafo.rotation = nif->getMatrix(); + bi.trafo.trans = nif->getVector(); + bi.trafo.scale = nif->getFloat(); bi.unknown = nif->getVector4(); // Number of vertex weights diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 6ecc7c61a..f48049ec4 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -41,6 +41,14 @@ struct NiLight : Effect Vector ambient; Vector diffuse; Vector specular; + + void read(NIFFile *nif) + { + nif->load(dimmer); + ambient = nif->getVector(); + diffuse = nif->getVector(); + specular = nif->getVector(); + } }; SLight light; @@ -50,7 +58,7 @@ struct NiLight : Effect nif->getInt(); // 1 nif->getInt(); // 1? - light = nif->getType(); + light.read(nif); } }; diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index e5829fdfc..5615d833e 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -56,7 +56,7 @@ public: /*int i =*/ nif->getInt(); int s = nif->getShort(); // number of vertices - nif->getFloatLen(s); // vertex weights I guess + nif->getArrayLen(s); // vertex weights I guess } }; diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index a2246e00e..c4b138232 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -99,20 +99,70 @@ public: void skip(size_t size) { inp->skip(size); } - template X getType() + uint32_t read_le32() { - X obj; - if(inp->read(&obj, sizeof(X)) != sizeof(X)) - fail("Failed to read from NIF"); - return obj; + uint8_t buffer[4]; + if(inp->read(buffer, 4) != 4) return 0; + return buffer[0] | (buffer[1]<<8) | (buffer[2]<<16) | (buffer[3]<<24); + } + uint16_t read_le16() + { + uint8_t buffer[2]; + if(inp->read(buffer, 2) != 2) return 0; + return buffer[0] | (buffer[1]<<8); + } + uint8_t read_byte() + { + uint8_t byte; + if(inp->read(&byte, 1) != 1) return 0; + return byte; + } + std::string read_string(size_t length) + { + std::string str; + str.resize(length); + if(inp->read(&str[0], length) != length) + return std::string(); + return str.substr(0, str.find('\0')); } - unsigned short getShort() { return getType(); } - int getInt() { return getType(); } - float getFloat() { return getType(); } - char getByte() { return getType(); } - template - std::vector getArrayLen(int num) + + char& load(char &c) { c = read_byte(); return c; } + unsigned char& load(unsigned char &c) { c = read_byte(); return c; } + short& load(short &s) { s = read_le16(); return s; } + unsigned short& load(unsigned short &s) { s = read_le16(); return s; } + int& load(int &i) { i = read_le32(); return i; } + unsigned int& load(unsigned int &i) { i = read_le32(); return i; } + float& load(float &f) + { + union { + int i; + float f; + } u = { read_le32() }; + f = u.f; + return f; + } + + template + T* load(T (&a)[N]) + { + for(size_t i = 0;i < N;i++) + load(a[i]); + return a; + } + + template + std::vector& load(std::vector &v, size_t size) + { + v.resize(size); + for(size_t i = 0;i < size;i++) + load(v[i]); + return v; + } + + + template + std::vector getArrayLen(size_t num) { std::vector v(num); if(inp->read(&v[0], num*sizeof(X)) != num*sizeof(X)) @@ -120,20 +170,47 @@ public: return v; } - template + template std::vector getArray() { - int len = getInt(); + size_t len = read_le32(); return getArrayLen(len); } - Vector getVector() { return getType(); } - Matrix getMatrix() { return getType(); } - Transformation getTrafo() { return getType(); } - Vector4 getVector4() { return getType(); } + char getByte() { char c; return load(c); } + unsigned short getShort() { unsigned short s; return load(s); } + int getInt() { int i; return load(i); } + float getFloat() { float f; return load(f); } + Vector getVector() + { + Vector v; + load(v.array); + return v; + } + Vector4 getVector4() + { + Vector4 v; + load(v.array); + return v; + } + Matrix getMatrix() + { + Matrix m; + m.v[0] = getVector(); + m.v[1] = getVector(); + m.v[2] = getVector(); + return m; + } + Transformation getTrafo() + { + Transformation t; + t.pos = getVector(); + t.rotation = getMatrix(); + load(t.scale); + t.velocity = getVector(); + return t; + } - std::vector getFloatLen(int num) - { return getArrayLen(num); } // For fixed-size strings where you already know the size std::string getString(size_t size) @@ -151,5 +228,14 @@ public: } }; +template<> +inline std::vector NIFFile::getArrayLen(size_t num) +{ + std::vector v(num); + for(size_t i = 0;i < num;i++) + load(v[i]); + return v; +} + } // Namespace #endif diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 8a6af5777..d5cd8fe82 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -218,6 +218,23 @@ struct NiCamera : Node // Level of detail modifier float LOD; + + void read(NIFFile *nif) + { + nif->load(left); + nif->load(right); + nif->load(top); + nif->load(bottom); + nif->load(nearDist); + nif->load(farDist); + + nif->load(vleft); + nif->load(vright); + nif->load(vtop); + nif->load(vbottom); + + nif->load(LOD); + } }; Camera cam; @@ -225,7 +242,7 @@ struct NiCamera : Node { Node::read(nif); - cam = nif->getType(); + cam.read(nif); nif->getInt(); // -1 nif->getInt(); // 0 diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 8485d978f..87e3ae5f2 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -160,7 +160,7 @@ struct StructPropT : Property void read(NIFFile *nif) { Property::read(nif); - data = nif->getType(); + data.read(nif); } }; @@ -169,6 +169,16 @@ struct S_MaterialProperty // The vector components are R,G,B Vector ambient, diffuse, specular, emissive; float glossiness, alpha; + + void read(NIFFile *nif) + { + ambient = nif->getVector(); + diffuse = nif->getVector(); + specular = nif->getVector(); + emissive = nif->getVector(); + nif->load(glossiness); + nif->load(alpha); + } }; struct S_VertexColorProperty @@ -183,6 +193,12 @@ struct S_VertexColorProperty 1 - lighting emmisive ambient/diffuse */ int vertmode, lightmode; + + void read(NIFFile *nif) + { + nif->load(vertmode); + nif->load(lightmode); + } }; struct S_AlphaProperty @@ -234,6 +250,11 @@ struct S_AlphaProperty // Tested against when certain flags are set (see above.) unsigned char threshold; + + void read(NIFFile *nif) + { + nif->load(threshold); + } }; typedef StructPropT NiAlphaProperty; From 410b69355539f9ad5d328e0fdd0b4b21b5962d98 Mon Sep 17 00:00:00 2001 From: gugus Date: Tue, 10 Jul 2012 11:15:46 +0200 Subject: [PATCH 049/298] setAngle improvement --- apps/openmw/mwclass/npc.cpp | 7 +++++++ apps/openmw/mwclass/npc.hpp | 2 ++ .../openmw/mwscript/transformationextensions.cpp | 16 ++++++++++------ apps/openmw/mwworld/worldimp.cpp | 4 ++-- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index ab4e2d5e6..0d2efcd9e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -347,4 +347,11 @@ namespace MWClass return weight; } + + void Npc::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const + { + y = 0; + x = 0; + std::cout << "dfdfdfdfnzdofnmqsldgnmqskdhblqkdbv lqksdf"; + } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 4cb733977..f50ed2159 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -76,6 +76,8 @@ namespace MWClass ///< Returns total weight of objects inside this object (including modifications from magic /// effects). Throws an exception, if the object can't hold other objects. + virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const; + static void registerSelf(); }; } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 41eba4a99..2ea80c0d8 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -62,17 +62,21 @@ namespace MWScript Interpreter::Type_Float angle = runtime[0].mFloat; runtime.pop(); + float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); + float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); + float az = Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees(); + if(axis == "X") { - MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,0,0); + MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az); } if(axis == "Y") { - MWBase::Environment::get().getWorld()->rotateObject(ptr,0,angle,0); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az); } if(axis == "Z") { - MWBase::Environment::get().getWorld()->rotateObject(ptr,0,0,angle); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); } } }; @@ -91,15 +95,15 @@ namespace MWScript if(axis == "X") { - runtime.push(ptr.getRefData().getPosition().rot[0]); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees()); } if(axis == "Y") { - runtime.push(ptr.getRefData().getPosition().rot[1]); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees()); } if(axis == "Z") { - runtime.push(ptr.getRefData().getPosition().rot[0]); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees()); } } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5309fbe40..1e1fae154 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -602,7 +602,7 @@ namespace MWWorld MWWorld::Class::get(ptr).adjustScale(ptr,scale); ptr.getCellRef().scale = scale; - scale = scale/ptr.getRefData().getBaseNode()->getScale().x; + //scale = scale/ptr.getRefData().getBaseNode()->getScale().x; ptr.getRefData().getBaseNode()->setScale(scale,scale,scale); mPhysics->scaleObject( ptr.getRefData().getHandle(), scale ); } @@ -618,7 +618,7 @@ namespace MWWorld Ogre::Quaternion rotx(Ogre::Degree(x),Ogre::Vector3::UNIT_X); Ogre::Quaternion roty(Ogre::Degree(y),Ogre::Vector3::UNIT_Y); Ogre::Quaternion rotz(Ogre::Degree(z),Ogre::Vector3::UNIT_Z); - ptr.getRefData().getBaseNode()->setOrientation(rotx*roty*rotz); + ptr.getRefData().getBaseNode()->setOrientation(rotz*rotx*roty); mPhysics->rotateObject(ptr.getRefData().getHandle(),ptr.getRefData().getBaseNode()->getOrientation()); } From ca37706b3406c1b88ee1c754097b4a917353679b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 10 Jul 2012 02:38:35 -0700 Subject: [PATCH 050/298] Use Ogre types for Matrix and Vector objects --- components/nif/data.hpp | 56 ++--- components/nif/effect.hpp | 6 +- components/nif/nif_file.hpp | 55 +++-- components/nif/nif_types.hpp | 40 +-- components/nif/node.hpp | 6 +- components/nif/property.hpp | 2 +- components/nifbullet/bullet_nif_loader.cpp | 20 +- components/nifbullet/bullet_nif_loader.hpp | 6 +- components/nifogre/ogre_nif_loader.cpp | 269 +++++++++------------ components/nifogre/ogre_nif_loader.hpp | 20 +- 10 files changed, 193 insertions(+), 287 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 1b5fa029b..667e77ffd 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -97,7 +97,7 @@ class ShapeData : public Record { public: std::vector vertices, normals, colors, uvlist; - Vector center; + Ogre::Vector3 center; float radius; void read(NIFFile *nif) @@ -198,7 +198,7 @@ public: // Rotation quaternions. I THINK activeCount is correct here, // but verts (vertex number) might also be correct, if there is // any case where the two don't match. - nif->getArrayLen(activeCount); + nif->skip(activeCount * 4*sizeof(float)); } } }; @@ -244,7 +244,7 @@ public: if(count) { nif->getInt(); // always 2 - nif->getArrayLen(count); // Really one time float + one vector + nif->skip(count * (sizeof(float) + 3*sizeof(float))); // Really one time float + one vector } } // Always 0 @@ -260,7 +260,7 @@ public: { int count = nif->getInt(); nif->getInt(); // always 2 - nif->getArrayLen(count); // Really one time float + one vector + nif->skip(count * (sizeof(float) + 3*sizeof(float))); // Really one time float + one vector } }; @@ -309,7 +309,7 @@ public: struct ColorData { float time; - Vector4 rgba; + Ogre::Vector4 rgba; }; void read(NIFFile *nif) @@ -318,25 +318,23 @@ public: nif->getInt(); // always 1 // Skip the data - assert(sizeof(ColorData) == 4*5); - nif->skip(sizeof(ColorData) * count); + nif->skip(count * 5*sizeof(float)); } }; class NiVisData : public Record { public: + struct VisData { + float time; + char isSet; + }; + void read(NIFFile *nif) { int count = nif->getInt(); - /* - Each VisData consists of: - float time; - byte isSet; - - If you implement this, make sure you use a packed struct - (sizeof==5), or read each element individually. - */ + + /* Skip VisData */ nif->skip(count*5); } }; @@ -361,16 +359,11 @@ public: class NiSkinData : public Record { public: - // This is to make sure the structs are packed, ie. that the - // compiler doesn't mess them up with extra alignment bytes. -#pragma pack(push) -#pragma pack(1) - struct BoneTrafo { - Matrix rotation; // Rotation offset from bone? - Vector trans; // Translation - float scale; // Probably scale (always 1) + Ogre::Matrix3 rotation; // Rotation offset from bone? + Ogre::Vector3 trans; // Translation + float scale; // Probably scale (always 1) }; struct BoneTrafoCopy { @@ -384,12 +377,12 @@ public: short vertex; float weight; }; -#pragma pack(pop) + struct BoneInfo { BoneTrafo trafo; - Vector4 unknown; + Ogre::Vector4 unknown; std::vector weights; }; struct BoneInfoCopy @@ -397,7 +390,7 @@ public: std::string bonename; unsigned short bonehandle; BoneTrafoCopy trafo; - Vector4 unknown; + Ogre::Vector4 unknown; //std::vector weights; }; struct IndividualWeight @@ -411,9 +404,6 @@ public: void read(NIFFile *nif) { - assert(sizeof(BoneTrafo) == 4*(9+3+1)); - assert(sizeof(VertWeight) == 6); - trafo.rotation = nif->getMatrix(); trafo.trans = nif->getVector(); trafo.scale = nif->getFloat(); @@ -432,8 +422,12 @@ public: bi.unknown = nif->getVector4(); // Number of vertex weights - int count = nif->getShort(); - bi.weights = nif->getArrayLen(count); + bi.weights.resize(nif->getShort()); + for(size_t j = 0;j < bi.weights.size();j++) + { + nif->load(bi.weights[j].vertex); + nif->load(bi.weights[j].weight); + } } } }; diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index f48049ec4..30877b48c 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -38,9 +38,9 @@ struct NiLight : Effect struct SLight { float dimmer; - Vector ambient; - Vector diffuse; - Vector specular; + Ogre::Vector3 ambient; + Ogre::Vector3 diffuse; + Ogre::Vector3 specular; void read(NIFFile *nif) { diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index c4b138232..d9fdd97ae 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -26,6 +26,9 @@ #include #include +#include +#include +#include #include #include @@ -162,44 +165,31 @@ public: template - std::vector getArrayLen(size_t num) - { - std::vector v(num); - if(inp->read(&v[0], num*sizeof(X)) != num*sizeof(X)) - fail("Failed to read from NIF"); - return v; - } - - template - std::vector getArray() - { - size_t len = read_le32(); - return getArrayLen(len); - } + std::vector getArrayLen(size_t num); char getByte() { char c; return load(c); } unsigned short getShort() { unsigned short s; return load(s); } int getInt() { int i; return load(i); } float getFloat() { float f; return load(f); } - Vector getVector() + Ogre::Vector3 getVector() { - Vector v; - load(v.array); - return v; + float a[3]; + load(a); + return Ogre::Vector3(a); } - Vector4 getVector4() + Ogre::Vector4 getVector4() { - Vector4 v; - load(v.array); - return v; + float a[4]; + load(a); + return Ogre::Vector4(a); } - Matrix getMatrix() + Ogre::Matrix3 getMatrix() { - Matrix m; - m.v[0] = getVector(); - m.v[1] = getVector(); - m.v[2] = getVector(); - return m; + float a[3*3]; + load(a); + return Ogre::Matrix3(Ogre::Real(a[0]), Ogre::Real(a[1]), Ogre::Real(a[2]), + Ogre::Real(a[3]), Ogre::Real(a[4]), Ogre::Real(a[5]), + Ogre::Real(a[6]), Ogre::Real(a[7]), Ogre::Real(a[8])); } Transformation getTrafo() { @@ -228,6 +218,15 @@ public: } }; +template<> +inline std::vector NIFFile::getArrayLen(size_t num) +{ + std::vector v(num); + for(size_t i = 0;i < num;i++) + load(v[i]); + return v; +} + template<> inline std::vector NIFFile::getArrayLen(size_t num) { diff --git a/components/nif/nif_types.hpp b/components/nif/nif_types.hpp index 41900a14e..705ed5994 100644 --- a/components/nif/nif_types.hpp +++ b/components/nif/nif_types.hpp @@ -24,41 +24,20 @@ #ifndef _NIF_TYPES_H_ #define _NIF_TYPES_H_ +#include +#include + // Common types used in NIF files namespace Nif { -/* These packing #pragmas aren't really necessary on 32 bit - machines. I haven't tested on 64 bit yet. In any case it doesn't - hurt to include them. We can't allow any compiler-generated padding - in any of these structs, since they are used to interface directly - with raw data from the NIF files. -*/ -#pragma pack(push) -#pragma pack(1) - -struct Vector -{ - float array[3]; -}; - -struct Vector4 -{ - float array[4]; -}; - -struct Matrix -{ - Vector v[3]; -}; - struct Transformation { - Vector pos; - Matrix rotation; + Ogre::Vector3 pos; + Ogre::Matrix3 rotation; float scale; - Vector velocity; + Ogre::Vector3 velocity; static const Transformation& getIdentity() { @@ -67,16 +46,15 @@ struct Transformation if (!iset) { identity.scale = 1.0f; - identity.rotation.v[0].array[0] = 1.0f; - identity.rotation.v[1].array[1] = 1.0f; - identity.rotation.v[2].array[2] = 1.0f; + identity.rotation[0][0] = 1.0f; + identity.rotation[1][1] = 1.0f; + identity.rotation[2][2] = 1.0f; iset = true; } return identity; } }; -#pragma pack(pop) } // Namespace #endif diff --git a/components/nif/node.hpp b/components/nif/node.hpp index d5cd8fe82..6ba3ce61d 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -47,9 +47,9 @@ public: // Bounding box info bool hasBounds; - Vector boundPos; - Matrix boundRot; - Vector boundXYZ; // Box size + Ogre::Vector3 boundPos; + Ogre::Matrix3 boundRot; + Ogre::Vector3 boundXYZ; // Box size void read(NIFFile *nif) { diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 87e3ae5f2..6ec277a62 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -167,7 +167,7 @@ struct StructPropT : Property struct S_MaterialProperty { // The vector components are R,G,B - Vector ambient, diffuse, specular, emissive; + Ogre::Vector3 ambient, diffuse, specular, emissive; float glossiness, alpha; void read(NIFFile *nif) diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index ae9ac94c2..41bc3b0a0 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -43,10 +43,6 @@ http://www.gnu.org/licenses/ . typedef unsigned char ubyte; -using namespace std; -using namespace Ogre; -using namespace Nif; - using namespace NifBullet; ManualBulletShapeLoader::~ManualBulletShapeLoader() @@ -55,18 +51,14 @@ ManualBulletShapeLoader::~ManualBulletShapeLoader() Ogre::Matrix3 ManualBulletShapeLoader::getMatrix(Nif::Transformation* tr) { - Ogre::Matrix3 rot(tr->rotation.v[0].array[0],tr->rotation.v[0].array[1],tr->rotation.v[0].array[2], - tr->rotation.v[1].array[0],tr->rotation.v[1].array[1],tr->rotation.v[1].array[2], - tr->rotation.v[2].array[0],tr->rotation.v[2].array[1],tr->rotation.v[2].array[2]); - return rot; + return tr->rotation; } Ogre::Vector3 ManualBulletShapeLoader::getVector(Nif::Transformation* tr) { - Ogre::Vector3 vect3(tr->pos.array[0],tr->pos.array[1],tr->pos.array[2]); - return vect3; + return tr->pos; } -btQuaternion ManualBulletShapeLoader::getbtQuat(Ogre::Matrix3 m) +btQuaternion ManualBulletShapeLoader::getbtQuat(Ogre::Matrix3 &m) { Ogre::Quaternion oquat(m); btQuaternion quat; @@ -77,10 +69,9 @@ btQuaternion ManualBulletShapeLoader::getbtQuat(Ogre::Matrix3 m) return quat; } -btVector3 ManualBulletShapeLoader::getbtVector(Nif::Vector v) +btVector3 ManualBulletShapeLoader::getbtVector(Ogre::Vector3 &v) { - btVector3 a(v.array[0],v.array[1],v.array[2]); - return a; + return btVector3(v[0], v[1], v[2]); } void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) @@ -108,7 +99,6 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) assert(r != NULL); Nif::Node *node = dynamic_cast(r); - if (node == NULL) { warn("First record in file was not a node, but a " + diff --git a/components/nifbullet/bullet_nif_loader.hpp b/components/nifbullet/bullet_nif_loader.hpp index 5a33074c9..bf288e081 100644 --- a/components/nifbullet/bullet_nif_loader.hpp +++ b/components/nifbullet/bullet_nif_loader.hpp @@ -46,8 +46,6 @@ namespace Nif class Node; class Transformation; class NiTriShape; - class Vector; - class Matrix; } namespace NifBullet @@ -91,9 +89,9 @@ private: Ogre::Vector3 getVector(Nif::Transformation* tr); - btQuaternion getbtQuat(Ogre::Matrix3 m); + btQuaternion getbtQuat(Ogre::Matrix3 &m); - btVector3 getbtVector(Nif::Vector v); + btVector3 getbtVector(Ogre::Vector3 &v); /** *Parse a node. diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index ec54319c1..ad604c8d4 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -39,9 +39,7 @@ typedef unsigned char ubyte; using namespace std; -using namespace Ogre; using namespace Nif; -using namespace Mangle::VFS; using namespace Misc; using namespace NifOgre; @@ -67,21 +65,6 @@ void NIFLoader::fail(string msg) assert(1); } -Vector3 NIFLoader::convertVector3(const Nif::Vector& vec) -{ - return Ogre::Vector3(vec.array); -} - -Quaternion NIFLoader::convertRotation(const Nif::Matrix& rot) -{ - Real matrix[3][3]; - - for (int i=0; i<3; i++) - for (int j=0; j<3; j++) - matrix[i][j] = rot.v[i].array[j]; - - return Quaternion(Matrix3(matrix)); -} // Helper class that computes the bounding box and of a mesh class BoundsFinder @@ -217,16 +200,16 @@ void NIFLoader::setOutputAnimFiles(bool output){ void NIFLoader::setVerbosePath(std::string path){ verbosePath = path; } -void NIFLoader::createMaterial(const String &name, - const Vector &ambient, - const Vector &diffuse, - const Vector &specular, - const Vector &emissive, +void NIFLoader::createMaterial(const Ogre::String &name, + const Ogre::Vector3 &ambient, + const Ogre::Vector3 &diffuse, + const Ogre::Vector3 &specular, + const Ogre::Vector3 &emissive, float glossiness, float alpha, int alphaFlags, float alphaTest, - const String &texName) + const Ogre::String &texName) { - MaterialPtr material = MaterialManager::getSingleton().create(name, resourceGroup); + Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().create(name, resourceGroup); //Hardware Skinning code, textures may be the wrong color if enabled @@ -249,11 +232,11 @@ void NIFLoader::createMaterial(const String &name, if (!texName.empty()) { - Pass *pass = material->getTechnique(0)->getPass(0); + Ogre::Pass *pass = material->getTechnique(0)->getPass(0); /*TextureUnitState *txt =*/ pass->createTextureUnitState(texName); - pass->setVertexColourTracking(TVC_DIFFUSE); + pass->setVertexColourTracking(Ogre::TVC_DIFFUSE); // As of yet UNTESTED code from Chris: /*pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); @@ -294,13 +277,13 @@ void NIFLoader::createMaterial(const String &name, NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName); if (result.first) { - pass->setAlphaRejectFunction(CMPF_GREATER_EQUAL); + pass->setAlphaRejectFunction(Ogre::CMPF_GREATER_EQUAL); pass->setAlphaRejectValue(result.second); } else { // Enable transparency - pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); + pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); //pass->setDepthCheckEnabled(false); pass->setDepthWriteEnabled(false); @@ -322,11 +305,11 @@ void NIFLoader::createMaterial(const String &name, const int numsplits = 3; for (int i = 0; i < (split ? numsplits : 1); ++i) { - TextureUnitState* tu = material->getTechnique(0)->getPass(0)->createTextureUnitState(); - tu->setName("shadowMap" + StringConverter::toString(i)); - tu->setContentType(TextureUnitState::CONTENT_SHADOW); - tu->setTextureAddressingMode(TextureUnitState::TAM_BORDER); - tu->setTextureBorderColour(ColourValue::White); + Ogre::TextureUnitState* tu = material->getTechnique(0)->getPass(0)->createTextureUnitState(); + tu->setName("shadowMap" + Ogre::StringConverter::toString(i)); + tu->setContentType(Ogre::TextureUnitState::CONTENT_SHADOW); + tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_BORDER); + tu->setTextureBorderColour(Ogre::ColourValue::White); } } @@ -339,11 +322,11 @@ void NIFLoader::createMaterial(const String &name, } // Create a fallback technique without shadows and without mrt - Technique* tech2 = material->createTechnique(); + Ogre::Technique* tech2 = material->createTechnique(); tech2->setSchemeName("Fallback"); - Pass* pass2 = tech2->createPass(); + Ogre::Pass* pass2 = tech2->createPass(); pass2->createTextureUnitState(texName); - pass2->setVertexColourTracking(TVC_DIFFUSE); + pass2->setVertexColourTracking(Ogre::TVC_DIFFUSE); if (Settings::Manager::getBool("shaders", "Objects")) { pass2->setVertexProgram("main_fallback_vp"); @@ -352,16 +335,16 @@ void NIFLoader::createMaterial(const String &name, } // Add material bells and whistles - material->setAmbient(ambient.array[0], ambient.array[1], ambient.array[2]); - material->setDiffuse(diffuse.array[0], diffuse.array[1], diffuse.array[2], alpha); - material->setSpecular(specular.array[0], specular.array[1], specular.array[2], alpha); - material->setSelfIllumination(emissive.array[0], emissive.array[1], emissive.array[2]); + material->setAmbient(ambient[0], ambient[1], ambient[2]); + material->setDiffuse(diffuse[0], diffuse[1], diffuse[2], alpha); + material->setSpecular(specular[0], specular[1], specular[2], alpha); + material->setSelfIllumination(emissive[0], emissive[1], emissive[2]); material->setShininess(glossiness); } // Takes a name and adds a unique part to it. This is just used to // make sure that all materials are given unique names. -String NIFLoader::getUniqueName(const String &input) +Ogre::String NIFLoader::getUniqueName(const Ogre::String &input) { static int addon = 0; static char buf[8]; @@ -377,13 +360,13 @@ String NIFLoader::getUniqueName(const String &input) // does not, change the string IN PLACE to say .dds instead and try // that. The texture may still not exist, but no information of value // is lost in that case. -void NIFLoader::findRealTexture(String &texName) +void NIFLoader::findRealTexture(Ogre::String &texName) { if(Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texName)) return; // Change texture extension to .dds - String::size_type pos = texName.rfind('.'); + Ogre::String::size_type pos = texName.rfind('.'); texName.replace(pos, texName.length(), ".dds"); } @@ -391,11 +374,11 @@ void NIFLoader::findRealTexture(String &texName) // Convert Nif::NiTriShape to Ogre::SubMesh, attached to the given // mesh. -void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std::list &vertexBoneAssignments) +void NIFLoader::createOgreSubMesh(NiTriShape *shape, const Ogre::String &material, std::list &vertexBoneAssignments) { // cout << "s:" << shape << "\n"; NiTriShapeData *data = shape->data.getPtr(); - SubMesh *sub = mesh->createSubMesh(shape->name); + Ogre::SubMesh *sub = mesh->createSubMesh(shape->name); int nextBuf = 0; @@ -404,17 +387,17 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std // Add vertices int numVerts = data->vertices.size() / 3; - sub->vertexData = new VertexData(); + sub->vertexData = new Ogre::VertexData(); sub->vertexData->vertexCount = numVerts; sub->useSharedVertices = false; - VertexDeclaration *decl = sub->vertexData->vertexDeclaration; - decl->addElement(nextBuf, 0, VET_FLOAT3, VES_POSITION); + Ogre::VertexDeclaration *decl = sub->vertexData->vertexDeclaration; + decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); - HardwareVertexBufferSharedPtr vbuf = - HardwareBufferManager::getSingleton().createVertexBuffer( - VertexElement::getTypeSize(VET_FLOAT3), - numVerts, HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, false); + Ogre::HardwareVertexBufferSharedPtr vbuf = + Ogre::HardwareBufferManager::getSingleton().createVertexBuffer( + Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + numVerts, Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, false); if(flip) { @@ -440,19 +423,19 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std } - VertexBufferBinding* bind = sub->vertexData->vertexBufferBinding; + Ogre::VertexBufferBinding* bind = sub->vertexData->vertexBufferBinding; bind->setBinding(nextBuf++, vbuf); if (data->normals.size()) { - decl->addElement(nextBuf, 0, VET_FLOAT3, VES_NORMAL); - vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( - VertexElement::getTypeSize(VET_FLOAT3), - numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false); + decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); + vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer( + Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + numVerts, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, false); if(flip) { - Quaternion rotation = mTransform.extractQuaternion(); + Ogre::Quaternion rotation = mTransform.extractQuaternion(); rotation.normalise(); float *datamod = new float[data->normals.size()]; @@ -487,19 +470,19 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std if (data->colors.size()) { const float *colors = &data->colors[0]; - RenderSystem* rs = Root::getSingleton().getRenderSystem(); - std::vector colorsRGB(numVerts); - RGBA *pColour = &colorsRGB.front(); + Ogre::RenderSystem* rs = Ogre::Root::getSingleton().getRenderSystem(); + std::vector colorsRGB(numVerts); + Ogre::RGBA *pColour = &colorsRGB.front(); for (int i=0; iconvertColourValue(ColourValue(colors[0],colors[1],colors[2], - colors[3]),pColour++); + rs->convertColourValue(Ogre::ColourValue(colors[0],colors[1],colors[2], + colors[3]),pColour++); colors += 4; } - decl->addElement(nextBuf, 0, VET_COLOUR, VES_DIFFUSE); - vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( - VertexElement::getTypeSize(VET_COLOUR), - numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); + decl->addElement(nextBuf, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); + vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer( + Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), + numVerts, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY); vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB.front(), true); bind->setBinding(nextBuf++, vbuf); } @@ -507,10 +490,10 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std if (data->uvlist.size()) { - decl->addElement(nextBuf, 0, VET_FLOAT2, VES_TEXTURE_COORDINATES); - vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( - VertexElement::getTypeSize(VET_FLOAT2), - numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY,false); + decl->addElement(nextBuf, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES); + vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer( + Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2), + numVerts, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY,false); if(flip) { @@ -539,24 +522,23 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std sub->indexData->indexCount = numFaces; sub->indexData->indexStart = 0; - HardwareIndexBufferSharedPtr ibuf = HardwareBufferManager::getSingleton(). - createIndexBuffer(HardwareIndexBuffer::IT_16BIT, - numFaces, - HardwareBuffer::HBU_STATIC_WRITE_ONLY, true); + Ogre::HardwareIndexBufferSharedPtr ibuf = Ogre::HardwareBufferManager::getSingleton(). + createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, numFaces, + Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, true); if(flip && mFlipVertexWinding && sub->indexData->indexCount % 3 == 0){ sub->indexData->indexBuffer = ibuf; - uint16 *datamod = new uint16[numFaces]; + uint16_t *datamod = new uint16_t[numFaces]; int index = 0; for (size_t i = 0; i < sub->indexData->indexCount; i+=3) { const short *pos = &data->triangles[index]; - uint16 i0 = (uint16) *(pos+0); - uint16 i1 = (uint16) *(pos+1); - uint16 i2 = (uint16) *(pos+2); + uint16_t i0 = (uint16_t) *(pos+0); + uint16_t i1 = (uint16_t) *(pos+1); + uint16_t i2 = (uint16_t) *(pos+2); //std::cout << "i0: " << i0 << "i1: " << i1 << "i2: " << i2 << "\n"; @@ -582,7 +564,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std //add vertex bone assignments - for (std::list::iterator it = vertexBoneAssignments.begin(); + for (std::list::iterator it = vertexBoneAssignments.begin(); it != vertexBoneAssignments.end(); it++) { sub->addBoneAssignment(*it); @@ -593,23 +575,8 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std // Helper math functions. Reinventing linear algebra for the win! -// Computes B = AxB (matrix*matrix) -static void matrixMul(const Matrix &A, Matrix &B) -{ - for (int i=0;i<3;i++) - { - float a = B.v[0].array[i]; - float b = B.v[1].array[i]; - float c = B.v[2].array[i]; - - B.v[0].array[i] = a*A.v[0].array[0] + b*A.v[0].array[1] + c*A.v[0].array[2]; - B.v[1].array[i] = a*A.v[1].array[0] + b*A.v[1].array[1] + c*A.v[1].array[2]; - B.v[2].array[i] = a*A.v[2].array[0] + b*A.v[2].array[1] + c*A.v[2].array[2]; - } -} - // Computes C = B + AxC*scale -static void vectorMulAdd(const Matrix &A, const Vector &B, float *C, float scale) +static void vectorMulAdd(const Ogre::Matrix3 &A, const Ogre::Vector3 &B, float *C, float scale) { // Keep the original values float a = C[0]; @@ -618,11 +585,11 @@ static void vectorMulAdd(const Matrix &A, const Vector &B, float *C, float scale // Perform matrix multiplication, scaling and addition for (int i=0;i<3;i++) - C[i] = B.array[i] + (a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2])*scale; + C[i] = B[i] + (a*A[i][0] + b*A[i][1] + c*A[i][2])*scale; } // Computes B = AxB (matrix*vector) -static void vectorMul(const Matrix &A, float *C) +static void vectorMul(const Ogre::Matrix3 &A, float *C) { // Keep the original values float a = C[0]; @@ -631,7 +598,7 @@ static void vectorMul(const Matrix &A, float *C) // Perform matrix multiplication, scaling and addition for (int i=0;i<3;i++) - C[i] = a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2]; + C[i] = a*A[i][0] + b*A[i][1] + c*A[i][2]; } @@ -666,7 +633,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou return; // Material name for this submesh, if any - String material; + Ogre::String material; // Skip the entire material phase for hidden nodes if (!hidden) @@ -695,7 +662,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou } // Texture - String texName; + Ogre::String texName; if (t && t->textures[0].inUse) { NiSourceTexture *st = t->textures[0].texture.getPtr(); @@ -768,14 +735,8 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou { // We only have a texture name. Create a default // material for it. - Vector zero, one; - for (int i=0; i<3;i++) - { - zero.array[i] = 0.0; - one.array[i] = 1.0; - } - - createMaterial(material, one, one, zero, zero, 0.0, 1.0, + const Ogre::Vector3 zero(0.0f), one(1.0f); + createMaterial(material, one, one, zero, zero, 0.0f, 1.0f, alphaFlags, alphaTest, texName); } } @@ -793,7 +754,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou float *ptr = (float*)&data->vertices[0]; float *optr = ptr; - std::list vertexBoneAssignments; + std::list vertexBoneAssignments; Nif::NiTriShapeCopy copy = shape->clone(); @@ -826,9 +787,9 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou //the first one contains a link to the bone, the second vertex transformation //relative to the bone int boneIndex = 0; - Bone *bonePtr; - Vector3 vecPos; - Quaternion vecRot; + Ogre::Bone *bonePtr; + Ogre::Vector3 vecPos; + Ogre::Quaternion vecRot; std::vector boneList = shape->skin->data->bones; @@ -854,17 +815,17 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou Nif::NiSkinData::BoneInfoCopy boneinfocopy; - boneinfocopy.trafo.rotation = convertRotation(it->trafo.rotation); - boneinfocopy.trafo.trans = convertVector3(it->trafo.trans); + boneinfocopy.trafo.rotation = it->trafo.rotation; + boneinfocopy.trafo.trans = it->trafo.trans; boneinfocopy.bonename = shape->skin->bones[boneIndex].name; boneinfocopy.bonehandle = bonePtr->getHandle(); copy.boneinfo.push_back(boneinfocopy); for (unsigned int i=0; iweights.size(); i++) { vecPos = bonePtr->_getDerivedPosition() + - bonePtr->_getDerivedOrientation() * convertVector3(it->trafo.trans); + bonePtr->_getDerivedOrientation() * it->trafo.trans; - vecRot = bonePtr->_getDerivedOrientation() * convertRotation(it->trafo.rotation); + vecRot = bonePtr->_getDerivedOrientation() * it->trafo.rotation; unsigned int verIndex = it->weights[i].vertex; //boneinfo.weights.push_back(*(it->weights.ptr + i)); Nif::NiSkinData::IndividualWeight ind; @@ -885,9 +846,9 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou if (vertexPosAbsolut[verIndex] == false) { //apply transformation to the vertices - Vector3 absVertPos = vecPos + vecRot * Vector3(ptr + verIndex *3); + Ogre::Vector3 absVertPos = vecPos + vecRot * Ogre::Vector3(ptr + verIndex *3); absVertPos = absVertPos * it->weights[i].weight; - vertexPosOriginal[verIndex] = Vector3(ptr + verIndex *3); + vertexPosOriginal[verIndex] = Ogre::Vector3(ptr + verIndex *3); mBoundingBox.merge(absVertPos); //convert it back to float * @@ -898,9 +859,9 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou //FIXME: I guessed that vertex[i] = normal[i], is that true? if (verIndex < data->normals.size()) { - Vector3 absNormalsPos = vecRot * Vector3(ptrNormals + verIndex *3); + Ogre::Vector3 absNormalsPos = vecRot * Ogre::Vector3(ptrNormals + verIndex *3); absNormalsPos = absNormalsPos * it->weights[i].weight; - vertexNormalOriginal[verIndex] = Vector3(ptrNormals + verIndex *3); + vertexNormalOriginal[verIndex] = Ogre::Vector3(ptrNormals + verIndex *3); for (int j=0; j<3; j++) (ptrNormals + verIndex*3)[j] = absNormalsPos[j]; @@ -910,9 +871,9 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou } else { - Vector3 absVertPos = vecPos + vecRot * vertexPosOriginal[verIndex]; + Ogre::Vector3 absVertPos = vecPos + vecRot * vertexPosOriginal[verIndex]; absVertPos = absVertPos * it->weights[i].weight; - Vector3 old = Vector3(ptr + verIndex *3); + Ogre::Vector3 old = Ogre::Vector3(ptr + verIndex *3); absVertPos = absVertPos + old; mBoundingBox.merge(absVertPos); @@ -924,9 +885,9 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou //FIXME: I guessed that vertex[i] = normal[i], is that true? if (verIndex < data->normals.size()) { - Vector3 absNormalsPos = vecRot * vertexNormalOriginal[verIndex]; + Ogre::Vector3 absNormalsPos = vecRot * vertexNormalOriginal[verIndex]; absNormalsPos = absNormalsPos * it->weights[i].weight; - Vector3 oldNormal = Vector3(ptrNormals + verIndex *3); + Ogre::Vector3 oldNormal = Ogre::Vector3(ptrNormals + verIndex *3); absNormalsPos = absNormalsPos + oldNormal; for (int j=0; j<3; j++) @@ -935,7 +896,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou } - VertexBoneAssignment vba; + Ogre::VertexBoneAssignment vba; vba.boneIndex = bonePtr->getHandle(); vba.vertexIndex = verIndex; vba.weight = it->weights[i].weight; @@ -955,12 +916,12 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou copy.boneSequence = boneSequence; // Rotate, scale and translate all the vertices, - const Matrix &rot = shape->trafo.rotation; - const Vector &pos = shape->trafo.pos; + const Ogre::Matrix3 &rot = shape->trafo.rotation; + const Ogre::Vector3 &pos = shape->trafo.pos; float scale = shape->trafo.scale; - copy.trafo.trans = convertVector3(original.pos); - copy.trafo.rotation = convertRotation(original.rotation); + copy.trafo.trans = original.pos; + copy.trafo.rotation = original.rotation; copy.trafo.scale = original.scale; //We don't use velocity for anything yet, so it does not need to be saved @@ -988,7 +949,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou boneIndex = mSkel->getNumBones() - 1; for(int i = 0; i < numVerts; i++){ - VertexBoneAssignment vba; + Ogre::VertexBoneAssignment vba; vba.boneIndex = boneIndex; vba.vertexIndex = i; vba.weight = 1; @@ -1012,15 +973,15 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou void NIFLoader::calculateTransform() { // Calculate transform - Matrix4 transform = Matrix4::IDENTITY; - transform = Matrix4::getScale(vector) * transform; + Ogre::Matrix4 transform = Ogre::Matrix4::IDENTITY; + transform = Ogre::Matrix4::getScale(vector) * transform; // Check whether we have to flip vertex winding. // We do have to, if we changed our right hand base. // We can test it by using the cross product from X and Y and see, if it is a non-negative // projection on Z. Actually it should be exactly Z, as we don't do non-uniform scaling yet, // but the test is cheap either way. - Matrix3 m3; + Ogre::Matrix3 m3; transform.extract3x3Matrix(m3); if (m3.GetColumn(0).crossProduct(m3.GetColumn(1)).dotProduct(m3.GetColumn(2)) < 0) @@ -1114,7 +1075,7 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, } } - Bone *bone = 0; + Ogre::Bone *bone = 0; // create skeleton or add bones if (node->recType == RC_NiNode) @@ -1124,7 +1085,7 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, { inTheSkeletonTree = true; - mSkel = SkeletonManager::getSingleton().create(getSkeletonName(), resourceGroup, true); + mSkel = Ogre::SkeletonManager::getSingleton().create(getSkeletonName(), resourceGroup, true); } else if (!mSkel.isNull() && !parentBone) inTheSkeletonTree = false; @@ -1144,8 +1105,8 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, parentBone->addChild(bone); bone->setInheritOrientation(true); - bone->setPosition(convertVector3(node->trafo.pos)); - bone->setOrientation(convertRotation(node->trafo.rotation)); + bone->setPosition(node->trafo.pos); + bone->setOrientation(node->trafo.rotation); } } } @@ -1160,14 +1121,13 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, // For both position and rotation we have that: // final_vector = old_vector + old_rotation*new_vector*old_scale - vectorMulAdd(trafo->rotation, trafo->pos, final.pos.array, trafo->scale); - vectorMulAdd(trafo->rotation, trafo->velocity, final.velocity.array, trafo->scale); + final.pos = trafo->pos + trafo->rotation*final.pos*trafo->scale; + final.velocity = trafo->velocity + trafo->rotation*final.velocity*trafo->scale; // Merge the rotations together - matrixMul(trafo->rotation, final.rotation); + final.rotation = trafo->rotation * final.rotation; - // Scalar values are so nice to deal with. Why can't everything - // just be scalar? + // Scale final.scale *= trafo->scale; } @@ -1200,7 +1160,7 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, } } -void NIFLoader::loadResource(Resource *resource) +void NIFLoader::loadResource(Ogre::Resource *resource) { inTheSkeletonTree = false; allanim.clear(); @@ -1287,7 +1247,7 @@ void NIFLoader::loadResource(Resource *resource) calculateTransform(); } // Get the mesh - mesh = dynamic_cast(resource); + mesh = dynamic_cast(resource); assert(mesh); // Look it up @@ -1352,8 +1312,8 @@ void NIFLoader::loadResource(Resource *resource) // set the bounding value. if (bounds.isValid()) { - mesh->_setBounds(AxisAlignedBox(bounds.minX(), bounds.minY(), bounds.minZ(), - bounds.maxX(), bounds.maxY(), bounds.maxZ())); + mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX(), bounds.minY(), bounds.minZ(), + bounds.maxX(), bounds.maxY(), bounds.maxZ())); mesh->_setBoundingSphereRadius(bounds.getRadius()); } if(hasAnim && addAnim){ @@ -1375,7 +1335,7 @@ void NIFLoader::loadResource(Resource *resource) for(std::vector::iterator iter = needBoneAssignments.begin(); iter != needBoneAssignments.end(); iter++) { int boneIndex = mSkel->getNumBones() - 1; - VertexBoneAssignment vba; + Ogre::VertexBoneAssignment vba; vba.boneIndex = boneIndex; vba.vertexIndex = 0; vba.weight = 1; @@ -1394,20 +1354,19 @@ void NIFLoader::loadResource(Resource *resource) -MeshPtr NIFLoader::load(const std::string &name, - const std::string &group) +Ogre::MeshPtr NIFLoader::load(const std::string &name, const std::string &group) { - MeshManager *m = MeshManager::getSingletonPtr(); + Ogre::MeshManager *m = Ogre::MeshManager::getSingletonPtr(); // Check if the resource already exists - ResourcePtr ptr = m->getByName(name, group); - MeshPtr themesh; + Ogre::ResourcePtr ptr = m->getByName(name, group); + Ogre::MeshPtr themesh; if (!ptr.isNull()){ - themesh = MeshPtr(ptr); + themesh = Ogre::MeshPtr(ptr); } else // Nope, create a new one. { - themesh = MeshManager::getSingleton().createManual(name, group, NIFLoader::getSingletonPtr()); + themesh = Ogre::MeshManager::getSingleton().createManual(name, group, NIFLoader::getSingletonPtr()); } return themesh; } diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 55915b310..64efc70c7 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -62,17 +62,8 @@ namespace Nif class Node; class Transformation; class NiTriShape; - class Vector; - class Matrix; } -namespace Mangle -{ - namespace VFS - { - class OgreVFS; - } -} namespace NifOgre { @@ -110,9 +101,6 @@ class NIFLoader : Ogre::ManualResourceLoader std::map* getTextIndices(std::string name); - Ogre::Vector3 convertVector3(const Nif::Vector& vec); - Ogre::Quaternion convertRotation(const Nif::Matrix& rot); - void setOutputAnimFiles(bool output); void setVerbosePath(std::string path); @@ -136,10 +124,10 @@ class NIFLoader : Ogre::ManualResourceLoader void createOgreSubMesh(Nif::NiTriShape *shape, const Ogre::String &material, std::list &vertexBoneAssignments); void createMaterial(const Ogre::String &name, - const Nif::Vector &ambient, - const Nif::Vector &diffuse, - const Nif::Vector &specular, - const Nif::Vector &emissive, + const Ogre::Vector3 &ambient, + const Ogre::Vector3 &diffuse, + const Ogre::Vector3 &specular, + const Ogre::Vector3 &emissive, float glossiness, float alpha, int alphaFlags, float alphaTest, const Ogre::String &texName); From 70c74ede055b2bbfcb727dcc9c8e92f082cd9554 Mon Sep 17 00:00:00 2001 From: gugus Date: Tue, 10 Jul 2012 11:53:12 +0200 Subject: [PATCH 051/298] changed rotation order --- apps/openmw/mwworld/worldimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1e1fae154..24baac144 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -618,7 +618,7 @@ namespace MWWorld Ogre::Quaternion rotx(Ogre::Degree(x),Ogre::Vector3::UNIT_X); Ogre::Quaternion roty(Ogre::Degree(y),Ogre::Vector3::UNIT_Y); Ogre::Quaternion rotz(Ogre::Degree(z),Ogre::Vector3::UNIT_Z); - ptr.getRefData().getBaseNode()->setOrientation(rotz*rotx*roty); + ptr.getRefData().getBaseNode()->setOrientation(rotz*roty*rotx); mPhysics->rotateObject(ptr.getRefData().getHandle(),ptr.getRefData().getBaseNode()->getOrientation()); } From 95b804a104ef4c187a53f3b8228932278ef4f752 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 10 Jul 2012 03:02:37 -0700 Subject: [PATCH 052/298] Remove NIFFile::getArrayLen --- components/nif/data.hpp | 17 ++++++++--------- components/nif/extra.hpp | 11 ++++++++--- components/nif/nif_file.cpp | 2 +- components/nif/nif_file.hpp | 34 ++-------------------------------- 4 files changed, 19 insertions(+), 45 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 667e77ffd..babc07545 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -105,25 +105,24 @@ public: int verts = nif->getShort(); if(nif->getInt()) - vertices = nif->getArrayLen(verts*3); + nif->load(vertices, verts*3); if(nif->getInt()) - normals = nif->getArrayLen(verts*3); + nif->load(normals, verts*3); center = nif->getVector(); radius = nif->getFloat(); if(nif->getInt()) - colors = nif->getArrayLen(verts*4); - - int uvs = nif->getShort(); + nif->load(colors, verts*4); // Only the first 6 bits are used as a count. I think the rest are // flags of some sort. + int uvs = nif->getShort(); uvs &= 0x3f; if(nif->getInt()) - uvlist = nif->getArrayLen(uvs*verts*2); + nif->load(uvlist, uvs*verts*2); } }; @@ -143,7 +142,7 @@ public: // We have three times as many vertices as triangles, so this // is always equal to tris*3. int cnt = nif->getInt(); - triangles = nif->getArrayLen(cnt); + nif->load(triangles, cnt); } // Read the match list, which lists the vertices that are equal to @@ -175,13 +174,13 @@ public: activeCount = nif->getShort(); // Skip all the info, we don't support particles yet - nif->getFloat(); // Active radius ? + nif->getFloat(); // Active radius ? nif->getShort(); // Number of valid entries in the following arrays ? if(nif->getInt()) { // Particle sizes - nif->getArrayLen(activeCount); + nif->skip(activeCount * sizeof(float)); } } }; diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 5615d833e..7659bb3d2 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -47,16 +47,21 @@ public: class NiVertWeightsExtraData : public Extra { public: + std::vector weights; + void read(NIFFile *nif) { Extra::read(nif); + int i; + unsigned short s; + // We should have s*4+2 == i, for some reason. Might simply be the // size of the rest of the record, unhelpful as that may be. - /*int i =*/ nif->getInt(); - int s = nif->getShort(); // number of vertices + nif->load(i); - nif->getArrayLen(s); // vertex weights I guess + nif->load(s); // number of vertices + nif->load(weights, s); // vertex weights I guess } }; diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 36badbf0d..5b88b45fe 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -46,7 +46,7 @@ using namespace Misc; void NIFFile::parse() { // Check the header string - std::string head = getString(40); + std::string head = read_string(40); if(head.compare(0, 22, "NetImmerse File Format") != 0) fail("Invalid NIF header"); diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index d9fdd97ae..6165f5811 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -164,9 +164,6 @@ public: } - template - std::vector getArrayLen(size_t num); - char getByte() { char c; return load(c); } unsigned short getShort() { unsigned short s; return load(s); } int getInt() { int i; return load(i); } @@ -202,39 +199,12 @@ public: } - // For fixed-size strings where you already know the size - std::string getString(size_t size) - { - std::string str; - str.resize(size); - if(inp->read(&str[0], size) != size) - fail("Failed to read from NIF"); - return str.substr(0, str.find('\0')); - } std::string getString() { - size_t size = getInt(); - return getString(size); + size_t size = read_le32(); + return read_string(size); } }; -template<> -inline std::vector NIFFile::getArrayLen(size_t num) -{ - std::vector v(num); - for(size_t i = 0;i < num;i++) - load(v[i]); - return v; -} - -template<> -inline std::vector NIFFile::getArrayLen(size_t num) -{ - std::vector v(num); - for(size_t i = 0;i < num;i++) - load(v[i]); - return v; -} - } // Namespace #endif From 164a5c8fe4d6ab0abda8116f872f90eaac6ce960 Mon Sep 17 00:00:00 2001 From: gugus Date: Tue, 10 Jul 2012 12:10:50 +0200 Subject: [PATCH 053/298] rotation now also work with the physic representation --- apps/openmw/mwworld/physicssystem.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 9848efe6e..7d7b237ae 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -291,12 +291,14 @@ namespace MWWorld void PhysicsSystem::rotateObject (const std::string& handle, const Ogre::Quaternion& rotation) { - if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) + if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) { - // TODO very dirty hack to avoid crash during setup -> needs cleaning up to allow - // start positions others than 0, 0, 0 act->setRotation(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w)); } + if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle)) + { + body->getWorldTransform().setRotation(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w)); + } } void PhysicsSystem::scaleObject (const std::string& handle, float scale) From 930459365b1ca8842acd1d36299bf8820e43242f Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 10 Jul 2012 03:52:01 -0700 Subject: [PATCH 054/298] Rename getShort->getUShort and getByte->getChar --- components/nif/controller.hpp | 8 +++---- components/nif/data.hpp | 39 ++++++++++++++++------------------- components/nif/nif_file.hpp | 6 +++--- components/nif/node.hpp | 2 +- components/nif/property.hpp | 12 +++++------ 5 files changed, 32 insertions(+), 35 deletions(-) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index d00c1bc0e..cbc19cd8f 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -44,7 +44,7 @@ public: { next.read(nif); - flags = nif->getShort(); + flags = nif->getUShort(); frequency = nif->getFloat(); phase = nif->getFloat(); @@ -71,7 +71,7 @@ public: // At the moment, just skip it all nif->skip(111); - int s = nif->getShort(); + int s = nif->getUShort(); nif->skip(15 + s*40); } }; @@ -133,7 +133,7 @@ public: { Controller::read(nif); - nif->getShort(); // always 0 + nif->getUShort(); // always 0 data.read(nif); } @@ -189,7 +189,7 @@ public: { Controller::read(nif); data.read(nif); - nif->getByte(); // always 0 + nif->getChar(); // always 0 } void post(NIFFile *nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index babc07545..e77fd6239 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -69,12 +69,12 @@ public: { Named::read(nif); - external = !!nif->getByte(); + external = !!nif->getChar(); if(external) filename = nif->getString(); else { - nif->getByte(); // always 1 + nif->getChar(); // always 1 data.read(nif); } @@ -82,7 +82,7 @@ public: mipmap = nif->getInt(); alpha = nif->getInt(); - nif->getByte(); // always 1 + nif->getChar(); // always 1 } void post(NIFFile *nif) @@ -102,7 +102,7 @@ public: void read(NIFFile *nif) { - int verts = nif->getShort(); + int verts = nif->getUShort(); if(nif->getInt()) nif->load(vertices, verts*3); @@ -118,7 +118,7 @@ public: // Only the first 6 bits are used as a count. I think the rest are // flags of some sort. - int uvs = nif->getShort(); + int uvs = nif->getUShort(); uvs &= 0x3f; if(nif->getInt()) @@ -136,7 +136,7 @@ public: { ShapeData::read(nif); - int tris = nif->getShort(); + int tris = nif->getUShort(); if(tris) { // We have three times as many vertices as triangles, so this @@ -148,15 +148,12 @@ public: // Read the match list, which lists the vertices that are equal to // vertices. We don't actually need need this for anything, so // just skip it. - int verts = nif->getShort(); - if(verts) + int verts = nif->getUShort(); + for(int i=0;igetShort(); - nif->skip(num*sizeof(short)); - } + // Number of vertices matching vertex 'i' + int num = nif->getUShort(); + nif->skip(num*sizeof(short)); } } }; @@ -171,11 +168,11 @@ public: ShapeData::read(nif); // Should always match the number of vertices - activeCount = nif->getShort(); + activeCount = nif->getUShort(); // Skip all the info, we don't support particles yet - nif->getFloat(); // Active radius ? - nif->getShort(); // Number of valid entries in the following arrays ? + nif->getFloat(); // Active radius ? + nif->getUShort(); // Number of valid entries in the following arrays ? if(nif->getInt()) { @@ -421,11 +418,11 @@ public: bi.unknown = nif->getVector4(); // Number of vertex weights - bi.weights.resize(nif->getShort()); + bi.weights.resize(nif->getUShort()); for(size_t j = 0;j < bi.weights.size();j++) { - nif->load(bi.weights[j].vertex); - nif->load(bi.weights[j].weight); + bi.weights[j].vertex = nif->getUShort(); + bi.weights[j].weight = nif->getFloat(); } } } @@ -464,7 +461,7 @@ public: { int morphCount = nif->getInt(); int vertCount = nif->getInt(); - nif->getByte(); + nif->getChar(); int magic = nif->getInt(); /*int type =*/ nif->getInt(); diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 6165f5811..2b46f84f3 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -164,8 +164,8 @@ public: } - char getByte() { char c; return load(c); } - unsigned short getShort() { unsigned short s; return load(s); } + char getChar() { char c; return load(c); } + unsigned short getUShort() { unsigned short s; return load(s); } int getInt() { int i; return load(i); } float getFloat() { float f; return load(f); } Ogre::Vector3 getVector() @@ -193,7 +193,7 @@ public: Transformation t; t.pos = getVector(); t.rotation = getMatrix(); - load(t.scale); + t.scale = getFloat(); t.velocity = getVector(); return t; } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 6ba3ce61d..f86ea5af9 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -55,7 +55,7 @@ public: { Named::read(nif); - flags = nif->getShort(); + flags = nif->getUShort(); trafo = nif->getTrafo(); props.read(nif); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 6ec277a62..1b455b14f 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -38,7 +38,7 @@ public: void read(NIFFile *nif) { Named::read(nif); - flags = nif->getShort(); + flags = nif->getUShort(); } }; @@ -176,8 +176,8 @@ struct S_MaterialProperty diffuse = nif->getVector(); specular = nif->getVector(); emissive = nif->getVector(); - nif->load(glossiness); - nif->load(alpha); + glossiness = nif->getFloat(); + alpha = nif->getFloat(); } }; @@ -196,8 +196,8 @@ struct S_VertexColorProperty void read(NIFFile *nif) { - nif->load(vertmode); - nif->load(lightmode); + vertmode = nif->getInt(); + lightmode = nif->getInt(); } }; @@ -253,7 +253,7 @@ struct S_AlphaProperty void read(NIFFile *nif) { - nif->load(threshold); + threshold = nif->getChar(); } }; From d30f64650a323f84d7ea554b7e0d3e4728cc1b47 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 10 Jul 2012 04:21:47 -0700 Subject: [PATCH 055/298] Make the read_* methods private and remove the generic load() methods --- components/nif/data.hpp | 12 ++-- components/nif/effect.hpp | 2 +- components/nif/extra.hpp | 11 +-- components/nif/nif_file.cpp | 2 +- components/nif/nif_file.hpp | 140 +++++++++++++++++------------------- components/nif/node.hpp | 26 +++---- 6 files changed, 89 insertions(+), 104 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index e77fd6239..118e21e54 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -105,16 +105,16 @@ public: int verts = nif->getUShort(); if(nif->getInt()) - nif->load(vertices, verts*3); + nif->getFloats(vertices, verts*3); if(nif->getInt()) - nif->load(normals, verts*3); + nif->getFloats(normals, verts*3); center = nif->getVector(); radius = nif->getFloat(); if(nif->getInt()) - nif->load(colors, verts*4); + nif->getFloats(colors, verts*4); // Only the first 6 bits are used as a count. I think the rest are // flags of some sort. @@ -122,7 +122,7 @@ public: uvs &= 0x3f; if(nif->getInt()) - nif->load(uvlist, uvs*verts*2); + nif->getFloats(uvlist, uvs*verts*2); } }; @@ -142,7 +142,7 @@ public: // We have three times as many vertices as triangles, so this // is always equal to tris*3. int cnt = nif->getInt(); - nif->load(triangles, cnt); + nif->getShorts(triangles, cnt); } // Read the match list, which lists the vertices that are equal to @@ -153,7 +153,7 @@ public: { // Number of vertices matching vertex 'i' int num = nif->getUShort(); - nif->skip(num*sizeof(short)); + nif->skip(num * sizeof(short)); } } }; diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 30877b48c..1a2ecace8 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -44,7 +44,7 @@ struct NiLight : Effect void read(NIFFile *nif) { - nif->load(dimmer); + dimmer = nif->getFloat(); ambient = nif->getVector(); diffuse = nif->getVector(); specular = nif->getVector(); diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 7659bb3d2..35781dbf5 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -47,21 +47,16 @@ public: class NiVertWeightsExtraData : public Extra { public: - std::vector weights; - void read(NIFFile *nif) { Extra::read(nif); - int i; - unsigned short s; - // We should have s*4+2 == i, for some reason. Might simply be the // size of the rest of the record, unhelpful as that may be. - nif->load(i); + /*int i =*/ nif->getInt(); + int s = nif->getUShort(); - nif->load(s); // number of vertices - nif->load(weights, s); // vertex weights I guess + nif->skip(s * sizeof(float)); // vertex weights I guess } }; diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 5b88b45fe..36badbf0d 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -46,7 +46,7 @@ using namespace Misc; void NIFFile::parse() { // Check the header string - std::string head = read_string(40); + std::string head = getString(40); if(head.compare(0, 22, "NetImmerse File Format") != 0) fail("Invalid NIF header"); diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 2b46f84f3..0218795e0 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -62,6 +62,33 @@ class NIFFile /// Parse the file void parse(); + uint8_t read_byte() + { + uint8_t byte; + if(inp->read(&byte, 1) != 1) return 0; + return byte; + } + uint16_t read_le16() + { + uint8_t buffer[2]; + if(inp->read(buffer, 2) != 2) return 0; + return buffer[0] | (buffer[1]<<8); + } + uint32_t read_le32() + { + uint8_t buffer[4]; + if(inp->read(buffer, 4) != 4) return 0; + return buffer[0] | (buffer[1]<<8) | (buffer[2]<<16) | (buffer[3]<<24); + } + float read_le32f() + { + union { + int i; + float f; + } u = { read_le32() }; + return u.f; + } + public: /// Used for error handling void fail(const std::string &msg) @@ -102,91 +129,34 @@ public: void skip(size_t size) { inp->skip(size); } - uint32_t read_le32() - { - uint8_t buffer[4]; - if(inp->read(buffer, 4) != 4) return 0; - return buffer[0] | (buffer[1]<<8) | (buffer[2]<<16) | (buffer[3]<<24); - } - uint16_t read_le16() - { - uint8_t buffer[2]; - if(inp->read(buffer, 2) != 2) return 0; - return buffer[0] | (buffer[1]<<8); - } - uint8_t read_byte() - { - uint8_t byte; - if(inp->read(&byte, 1) != 1) return 0; - return byte; - } - std::string read_string(size_t length) - { - std::string str; - str.resize(length); - if(inp->read(&str[0], length) != length) - return std::string(); - return str.substr(0, str.find('\0')); - } - - - char& load(char &c) { c = read_byte(); return c; } - unsigned char& load(unsigned char &c) { c = read_byte(); return c; } - short& load(short &s) { s = read_le16(); return s; } - unsigned short& load(unsigned short &s) { s = read_le16(); return s; } - int& load(int &i) { i = read_le32(); return i; } - unsigned int& load(unsigned int &i) { i = read_le32(); return i; } - float& load(float &f) - { - union { - int i; - float f; - } u = { read_le32() }; - f = u.f; - return f; - } - - template - T* load(T (&a)[N]) - { - for(size_t i = 0;i < N;i++) - load(a[i]); - return a; - } - - template - std::vector& load(std::vector &v, size_t size) - { - v.resize(size); - for(size_t i = 0;i < size;i++) - load(v[i]); - return v; - } - - - char getChar() { char c; return load(c); } - unsigned short getUShort() { unsigned short s; return load(s); } - int getInt() { int i; return load(i); } - float getFloat() { float f; return load(f); } + char getChar() { return read_byte(); } + short getShort() { return read_le16(); } + unsigned short getUShort() { return read_le16(); } + int getInt() { return read_le32(); } + float getFloat() { return read_le32f(); } Ogre::Vector3 getVector() { float a[3]; - load(a); + for(size_t i = 0;i < 3;i++) + a[i] = getFloat(); return Ogre::Vector3(a); } Ogre::Vector4 getVector4() { float a[4]; - load(a); + for(size_t i = 0;i < 4;i++) + a[i] = getFloat(); return Ogre::Vector4(a); } Ogre::Matrix3 getMatrix() { - float a[3*3]; - load(a); - return Ogre::Matrix3(Ogre::Real(a[0]), Ogre::Real(a[1]), Ogre::Real(a[2]), - Ogre::Real(a[3]), Ogre::Real(a[4]), Ogre::Real(a[5]), - Ogre::Real(a[6]), Ogre::Real(a[7]), Ogre::Real(a[8])); + Ogre::Real a[3][3]; + for(size_t i = 0;i < 3;i++) + { + for(size_t j = 0;j < 3;j++) + a[i][j] = Ogre::Real(getFloat()); + } + return Ogre::Matrix3(a); } Transformation getTrafo() { @@ -198,11 +168,31 @@ public: return t; } - + std::string getString(size_t length) + { + std::string str; + str.resize(length); + if(inp->read(&str[0], length) != length) + return std::string(); + return str.substr(0, str.find('\0')); + } std::string getString() { size_t size = read_le32(); - return read_string(size); + return getString(size); + } + + void getShorts(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getShort(); + } + void getFloats(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getFloat(); } }; diff --git a/components/nif/node.hpp b/components/nif/node.hpp index f86ea5af9..240dbe540 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -221,19 +221,19 @@ struct NiCamera : Node void read(NIFFile *nif) { - nif->load(left); - nif->load(right); - nif->load(top); - nif->load(bottom); - nif->load(nearDist); - nif->load(farDist); - - nif->load(vleft); - nif->load(vright); - nif->load(vtop); - nif->load(vbottom); - - nif->load(LOD); + left = nif->getFloat(); + right = nif->getFloat(); + top = nif->getFloat(); + bottom = nif->getFloat(); + nearDist = nif->getFloat(); + farDist = nif->getFloat(); + + vleft = nif->getFloat(); + vright = nif->getFloat(); + vtop = nif->getFloat(); + vbottom = nif->getFloat(); + + LOD = nif->getFloat(); } }; Camera cam; From f11bf49a9023cff3ceebdeab6326a5379f878995 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 10 Jul 2012 13:23:41 +0200 Subject: [PATCH 056/298] cmake fix; silenced some warnings --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwworld/physicssystem.cpp | 13 ++++----- components/nifbullet/bullet_nif_loader.cpp | 34 +++++++++++----------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 9534ecc90..b12c58f0d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -39,7 +39,7 @@ add_openmw_dir (mwscript locals scriptmanager compilercontext interpretercontext cellextensions miscextensions guiextensions soundextensions skyextensions statsextensions containerextensions aiextensions controlextensions extensions globalscripts ref dialogueextensions - animationextensions + animationextensions transformationextensions ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 7d7b237ae..105995aca 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -165,7 +165,6 @@ namespace MWWorld for (std::vector >::const_iterator iter (actors.begin()); iter!=actors.end(); ++iter) { - OEngine::Physic::PhysicActor* act = mEngine->getCharacter(iter->first); //dirty stuff to get the camera orientation. Must be changed! Ogre::SceneNode *sceneNode = mRender.getScene()->getSceneNode (iter->first); @@ -175,10 +174,10 @@ namespace MWWorld Ogre::Quaternion yawQuat = yawNode->getOrientation(); Ogre::Quaternion pitchQuat = pitchNode->getOrientation(); - + playerphysics->ps.viewangles.x = pitchQuat.getPitch().valueDegrees(); - + playerphysics->ps.viewangles.y = yawQuat.getYaw().valueDegrees() *-1 + 90; @@ -194,9 +193,9 @@ namespace MWWorld } - - - + + + mEngine->stepSimulation(dt); } @@ -307,7 +306,7 @@ namespace MWWorld { btTransform transform = mEngine->getRigidBody(handle)->getWorldTransform(); removeObject(handle); - + Ogre::Quaternion quat = Ogre::Quaternion(transform.getRotation().getW(), transform.getRotation().getX(), transform.getRotation().getY(), transform.getRotation().getZ()); Ogre::Vector3 vec = Ogre::Vector3(transform.getOrigin().getX(), transform.getOrigin().getY(), transform.getOrigin().getZ()); addObject(handle, handleToMesh[handle], quat, scale, vec); diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index 17c5f18ac..f66239e7b 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -82,17 +82,17 @@ static void vectorMulAdd(const Matrix &A, const Vector &B, float *C, float scale } // Computes B = AxB (matrix*vector) -static void vectorMul(const Matrix &A, float *C) -{ - // Keep the original values - float a = C[0]; - float b = C[1]; - float c = C[2]; +//static void vectorMul(const Matrix &A, float *C) +//{ +// // Keep the original values +// float a = C[0]; +// float b = C[1]; +// float c = C[2]; - // Perform matrix multiplication, scaling and addition - for (int i=0;i<3;i++) - C[i] = a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2]; -} +// // Perform matrix multiplication, scaling and addition +// for (int i=0;i<3;i++) +// C[i] = a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2]; +//} ManualBulletShapeLoader::~ManualBulletShapeLoader() @@ -233,7 +233,7 @@ bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node* node) void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, const Nif::Transformation *trafo,bool hasCollisionNode,bool isCollisionNode,bool raycastingOnly) { - + // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. flags |= node->flags; @@ -267,11 +267,11 @@ void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, } } - - + + if (trafo) { - + // Get a non-const reference to the node's data, since we're // overwriting it. TODO: Is this necessary? Transformation &final = *((Transformation*)node->trafo); @@ -287,9 +287,9 @@ void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, // Scalar values are so nice to deal with. Why can't everything // just be scalar? final.scale *= trafo->scale; - + } - + // For NiNodes, loop through children if (node->recType == Nif::RC_NiNode) @@ -304,7 +304,7 @@ void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, } } } - else if (node->recType == Nif::RC_NiTriShape && (isCollisionNode || !hasCollisionNode)) + else if (node->recType == Nif::RC_NiTriShape && (isCollisionNode || !hasCollisionNode)) { cShape->collide = true; handleNiTriShape(dynamic_cast(node), flags,getMatrix(node->trafo),getVector(node->trafo),node->trafo->scale,raycastingOnly); From dddf1b4ee57328f47226825a694a0e04d70149f8 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 10 Jul 2012 04:45:14 -0700 Subject: [PATCH 057/298] Rename getMatrix->getMatrix3 and getVector->getVector3 --- components/nif/data.hpp | 18 +++++++++--------- components/nif/effect.hpp | 6 +++--- components/nif/nif_file.hpp | 10 +++++----- components/nif/node.hpp | 6 +++--- components/nif/property.hpp | 8 ++++---- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 118e21e54..ad670bc5e 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -110,7 +110,7 @@ public: if(nif->getInt()) nif->getFloats(normals, verts*3); - center = nif->getVector(); + center = nif->getVector3(); radius = nif->getFloat(); if(nif->getInt()) @@ -214,12 +214,12 @@ public: for(int i=0; igetFloat(); - nif->getVector(); // This isn't really shared between type 1 - // and type 2, most likely + nif->getVector3(); // This isn't really shared between type 1 + // and type 2, most likely if(type == 2) { - nif->getVector(); - nif->getVector(); + nif->getVector3(); + nif->getVector3(); } } } @@ -400,8 +400,8 @@ public: void read(NIFFile *nif) { - trafo.rotation = nif->getMatrix(); - trafo.trans = nif->getVector(); + trafo.rotation = nif->getMatrix3(); + trafo.trans = nif->getVector3(); trafo.scale = nif->getFloat(); int boneNum = nif->getInt(); @@ -412,8 +412,8 @@ public: { BoneInfo &bi = bones[i]; - bi.trafo.rotation = nif->getMatrix(); - bi.trafo.trans = nif->getVector(); + bi.trafo.rotation = nif->getMatrix3(); + bi.trafo.trans = nif->getVector3(); bi.trafo.scale = nif->getFloat(); bi.unknown = nif->getVector4(); diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 1a2ecace8..850415dad 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -45,9 +45,9 @@ struct NiLight : Effect void read(NIFFile *nif) { dimmer = nif->getFloat(); - ambient = nif->getVector(); - diffuse = nif->getVector(); - specular = nif->getVector(); + ambient = nif->getVector3(); + diffuse = nif->getVector3(); + specular = nif->getVector3(); } }; SLight light; diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 0218795e0..a21882c6d 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -134,7 +134,7 @@ public: unsigned short getUShort() { return read_le16(); } int getInt() { return read_le32(); } float getFloat() { return read_le32f(); } - Ogre::Vector3 getVector() + Ogre::Vector3 getVector3() { float a[3]; for(size_t i = 0;i < 3;i++) @@ -148,7 +148,7 @@ public: a[i] = getFloat(); return Ogre::Vector4(a); } - Ogre::Matrix3 getMatrix() + Ogre::Matrix3 getMatrix3() { Ogre::Real a[3][3]; for(size_t i = 0;i < 3;i++) @@ -161,10 +161,10 @@ public: Transformation getTrafo() { Transformation t; - t.pos = getVector(); - t.rotation = getMatrix(); + t.pos = getVector3(); + t.rotation = getMatrix3(); t.scale = getFloat(); - t.velocity = getVector(); + t.velocity = getVector3(); return t; } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 240dbe540..64ef1e3e9 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -63,9 +63,9 @@ public: if(hasBounds) { nif->getInt(); // always 1 - boundPos = nif->getVector(); - boundRot = nif->getMatrix(); - boundXYZ = nif->getVector(); + boundPos = nif->getVector3(); + boundRot = nif->getMatrix3(); + boundXYZ = nif->getVector3(); } parent = NULL; diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 1b455b14f..b24e49b47 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -172,10 +172,10 @@ struct S_MaterialProperty void read(NIFFile *nif) { - ambient = nif->getVector(); - diffuse = nif->getVector(); - specular = nif->getVector(); - emissive = nif->getVector(); + ambient = nif->getVector3(); + diffuse = nif->getVector3(); + specular = nif->getVector3(); + emissive = nif->getVector3(); glossiness = nif->getFloat(); alpha = nif->getFloat(); } From 61b690eb4bd86e02db076abb9858c676d4b4eef5 Mon Sep 17 00:00:00 2001 From: gugus Date: Tue, 10 Jul 2012 14:25:39 +0200 Subject: [PATCH 058/298] ncp scale factor --- apps/openmw/mwclass/npc.cpp | 8 ++++++++ apps/openmw/mwclass/npc.hpp | 2 ++ 2 files changed, 10 insertions(+) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 0d2efcd9e..34575aa97 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -348,6 +348,14 @@ namespace MWClass return weight; } + void Npc::adjustScale(const MWWorld::Ptr& ptr,float& scale) const + { + //ptr. + //MWWorld::LiveCellRef* npc = ptr.get(); + //npc->base->race + //ESM::Race + } + void Npc::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const { y = 0; diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index f50ed2159..2c4e6cb88 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -76,6 +76,8 @@ namespace MWClass ///< Returns total weight of objects inside this object (including modifications from magic /// effects). Throws an exception, if the object can't hold other objects. + virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; + virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const; static void registerSelf(); From 1fef4f2bc217929992771cae66449fcf2365003f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 10 Jul 2012 16:59:26 +0200 Subject: [PATCH 059/298] splitting off credits from readme file --- credits.txt | 39 +++++++++++++++++++++++++++++++++++++++ readme.txt | 41 ----------------------------------------- 2 files changed, 39 insertions(+), 41 deletions(-) create mode 100644 credits.txt diff --git a/credits.txt b/credits.txt new file mode 100644 index 000000000..8d7cbe7d5 --- /dev/null +++ b/credits.txt @@ -0,0 +1,39 @@ +CREDITS + +Current Developers: +Aleksandar Jovanov +Alexander “Ace” Olofsson +athile +BrotherBrick +Cris “Mirceam” Mihalache +gugus / gus +Jacob “Yacoby” Essex +Jannik “scrawl” Heller +Jason “jhooks” Hooks +Karl-Felix “k1ll” Glatzer +Lukasz “lgro” Gromanowski +Marc “Zini” Zinnschlag +Michael “werdanith” Papageorgiou +Nikolay “corristo” Kasyanov +Pieter “pvdk” van der Kloet +Roman "Kromgart" Melnik +Sebastian “swick” Wick +Sylvain "Garvek" T. + +Retired Developers: +Ardekantur +Armin Preiml +Diggory Hardy +Jan Borsodi +Jan-Peter “peppe” Nilsson +Josua Grawitter +Nicolay Korslund +sergoz +Star-Demon +Yuri Krupenin + +OpenMW: +Thanks to DokterDume for kindly providing us with the Moon and Star logo used as the application icon and project logo. + +Launcher: +Thanks to Kevin Ryan for kindly providing us with the icon used for the Data Files tab. diff --git a/readme.txt b/readme.txt index aa981dba3..327d36586 100644 --- a/readme.txt +++ b/readme.txt @@ -90,47 +90,6 @@ Allowed options: --fallback arg fallback values -CREDITS - -Current Developers: -Aleksandar Jovanov -Alexander “Ace” Olofsson -athile -BrotherBrick -Cris “Mirceam” Mihalache -gugus / gus -Jacob “Yacoby” Essex -Jannik “scrawl” Heller -Jason “jhooks” Hooks -Karl-Felix “k1ll” Glatzer -Lukasz “lgro” Gromanowski -Marc “Zini” Zinnschlag -Michael “werdanith” Papageorgiou -Nikolay “corristo” Kasyanov -Pieter “pvdk” van der Kloet -Roman "Kromgart" Melnik -Sebastian “swick” Wick -Sylvain "Garvek" T. - -Retired Developers: -Ardekantur -Armin Preiml -Diggory Hardy -Jan Borsodi -Jan-Peter “peppe” Nilsson -Josua Grawitter -Nicolay Korslund -sergoz -Star-Demon -Yuri Krupenin - -OpenMW: -Thanks to DokterDume for kindly providing us with the Moon and Star logo used as the application icon and project logo. - -Launcher: -Thanks to Kevin Ryan for kindly providing us with the icon used for the Data Files tab. - - CHANGELOG 0.16.0 From 089ee335884ea2da2fec1845ea50789b03ef21c7 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 10 Jul 2012 17:05:52 +0200 Subject: [PATCH 060/298] some readme.txt improvements --- readme.txt | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 5 deletions(-) diff --git a/readme.txt b/readme.txt index 327d36586..ded9bcd7b 100644 --- a/readme.txt +++ b/readme.txt @@ -12,8 +12,6 @@ EBGaramond-Regular.ttf: OFL (see OFL.txt for more information) VeraMono.ttf: custom (see Bitstream Vera License.txt for more information) -THIS IS A WORK IN PROGRESS - INSTALLATION @@ -174,7 +172,6 @@ Task #113: Morrowind.ini Importer Task #215: Refactor the sound code Task #216: Update MyGUI - 0.13.0 Bug #145: Fixed sound problems after cell change @@ -232,7 +229,6 @@ Task #131: NPC Activation doesn't work properly Task #144: MWRender cleanup Task #155: cmake cleanup - 0.11.1 Bug #2: Resources loading doesn't work outside of bsa files @@ -259,4 +255,95 @@ Task #14: Replace tabs with 4 spaces Task #18: Move components from global namespace into their own namespace Task #123: refactor header files in components/esm -TODO add old changelog (take pre 0.11.1 changelog from wiki) +0.10.0 + +* NPC dialogue window (not functional yet) +* Collisions with objects +* Refactor the PlayerPos class +* Adjust file locations +* CMake files and test linking for Bullet +* Replace Ogre raycasting test for activation with something more precise +* Adjust player movement according to collision results +* FPS display +* Various Portability Improvements +* Mac OS X support is back! + +0.9.0 + +* Exterior cells loading, unloading and management +* Character Creation GUI +* Character creation +* Make cell names case insensitive when doing internal lookups +* Music player +* NPCs rendering + +0.8.0 + +* GUI +* Complete and working script engine +* In game console +* Sky rendering +* Sound and music +* Tons of smaller stuff + +0.7.0 + +* This release is a complete rewrite in C++. +* All D code has been culled, and all modules have been rewritten. +* The game is now back up to the level of rendering interior cells and moving around, but physics, sound, GUI, and scripting still remain to be ported from the old codebase. + +0.6.0 + +* Coded a GUI system using MyGUI +* Skinned MyGUI to look like Morrowind (work in progress) +* Integrated the Monster script engine +* Rewrote some functions into script code +* Very early MyGUI < > Monster binding +* Fixed Windows sound problems (replaced old openal32.dll) + +0.5.0 + +* Collision detection with Bullet +* Experimental walk & fall character physics +* New key bindings: + * t toggle physics mode (walking, flying, ghost), + * n night eye, brightens the scene +* Fixed incompatability with DMD 1.032 and newer compilers +* * (thanks to tomqyp) +* Various minor changes and updates + +0.4.0 + +* Switched from Audiere to OpenAL +* * (BIG thanks to Chris Robinson) +* Added complete Makefile (again) as a alternative build tool +* More realistic lighting (thanks again to Chris Robinson) +* Various localization fixes tested with Russian and French versions +* Temporary workaround for the Unicode issue: invalid UTF displayed as '?' +* Added ns option to disable sound, for debugging +* Various bug fixes +* Cosmetic changes to placate gdc Wall + +0.3.0 + +* Built and tested on Windows XP +* Partial support for FreeBSD (exceptions do not work) +* You no longer have to download Monster separately +* Made an alternative for building without DSSS (but DSSS still works) +* Renamed main program from 'morro' to 'openmw' +* Made the config system more robust +* Added oc switch for showing Ogre config window on startup +* Removed some config files, these are auto generated when missing. +* Separated plugins.cfg into linux and windows versions. +* Updated Makefile and sources for increased portability +* confirmed to work against OIS 1.0.0 (Ubuntu repository package) + +0.2.0 + +* Compiles with gdc +* Switched to DSSS for building D code +* Includes the program esmtool + +0.1.0 + +first release From 6c73f5e5184303ea0119f12a8a250abac5e89004 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 10 Jul 2012 22:25:19 +0300 Subject: [PATCH 061/298] Add some translators/reversers to credits.txt --- credits.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/credits.txt b/credits.txt index 8d7cbe7d5..16b41d56a 100644 --- a/credits.txt +++ b/credits.txt @@ -32,6 +32,25 @@ sergoz Star-Demon Yuri Krupenin +PR team and Translators: +Julien (jvoisin/ap0) Voisin +sirherrbatka +ElderTroll +spyboot +corristo +Okulo +penguinroad +Kingpix + +Reverser and Research: +natirips +Sadler +fragonard +Greendogo +Myckel +modred11 +HiPhish + OpenMW: Thanks to DokterDume for kindly providing us with the Moon and Star logo used as the application icon and project logo. From 865bfc6f4762c883713f99c5e2e9ec5800068e4e Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jul 2012 02:31:03 +0200 Subject: [PATCH 062/298] sync mrt_output setting --- apps/openmw/mwrender/objects.cpp | 15 +++++++++++++++ apps/openmw/mwrender/objects.hpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 6 +++++- apps/openmw/mwrender/sky.cpp | 9 --------- extern/shiny | 2 +- files/materials/atmosphere.shader | 4 ++-- files/materials/clouds.shader | 7 +++++-- files/materials/core.h | 4 ++-- files/materials/objects.shader | 20 ++++++++++---------- files/materials/shadowcaster.mat | 2 ++ files/materials/shadowcaster.shader | 2 +- files/materials/sky.mat | 4 ++-- files/materials/sun.shader | 4 ++-- 13 files changed, 49 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index fb2bfb3c5..b3457a5fa 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -450,3 +450,18 @@ void Objects::update(const float dt) it = mLights.erase(it); } } + +void Objects::rebuildStaticGeometry() +{ + for (std::map::iterator it = mStaticGeometry.begin(); it != mStaticGeometry.end(); ++it) + { + it->second->destroy(); + it->second->build(); + } + + for (std::map::iterator it = mStaticGeometrySmall.begin(); it != mStaticGeometrySmall.end(); ++it) + { + it->second->destroy(); + it->second->build(); + } +} diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index e240b11c9..443f25ecf 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -93,6 +93,8 @@ public: void removeCell(MWWorld::CellStore* store); void buildStaticGeometry(MWWorld::CellStore &cell); void setMwRoot(Ogre::SceneNode* root); + + void rebuildStaticGeometry(); }; } #endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5d9395460..fa20cd48d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -81,12 +81,14 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const //mRendering.getScene()->setCameraRelativeRendering(true); // disable unsupported effects - const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities(); + //const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities(); if (!waterShaderSupported()) Settings::Manager::setBool("shader", "Water", false); if (!Settings::Manager::getBool("shaders", "Objects")) Settings::Manager::setBool("enabled", "Shadows", false); + sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); + applyCompositors(); // Turn the entire scene (represented by the 'root' node) -90 @@ -614,6 +616,8 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec else if (it->second == "shader" && it->first == "Water") { applyCompositors(); + sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); + mObjects.rebuildStaticGeometry (); } } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index f32883fdd..f79ab6116 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -631,18 +631,9 @@ void SkyManager::create() // Make a unique "modifiable" copy of the materials to be sure //mCloudMaterial = mCloudMaterial->clone("Clouds"); //clouds_ent->getSubEntity(0)->setMaterial(mCloudMaterial); - //mAtmosphereMaterial = mAtmosphereMaterial->clone("Atmosphere"); - //atmosphere_ent->getSubEntity(0)->setMaterial(mAtmosphereMaterial); - /* - - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, 0.0); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setAmbient(0.0, 0.0, 0.0); mCloudMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); mCloudMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mCloudMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mCloudMaterial->getTechnique(0)->getPass(0)->removeAllTextureUnitStates(); diff --git a/extern/shiny b/extern/shiny index b3cfd41df..27a128c41 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit b3cfd41dff2758e268ce16f366b7e7857eee80ea +Subproject commit 27a128c414e2f92d6bcda379b9c9df04e69b7472 diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader index 484381a1f..831ba1138 100644 --- a/files/materials/atmosphere.shader +++ b/files/materials/atmosphere.shader @@ -27,10 +27,10 @@ SH_START_PROGRAM { - shOutputColor(0) = colourPassthrough * atmosphereColour; + shOutputColour(0) = colourPassthrough * atmosphereColour; #if MRT - shOutputColor(1) = float4(1,1,1,1); + shOutputColour(1) = float4(1,1,1,1); #endif } diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index a772e3c5e..1a80a27dd 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -8,9 +8,12 @@ shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) shInput(float2, uv0) shOutput(float2, UV) + shColourInput(float4) + shOutput(float4, colourPassthrough) SH_START_PROGRAM { + shOutputPosition = shMatrixMult(wvp, shInputPosition); UV = uv0; } @@ -28,10 +31,10 @@ SH_START_PROGRAM { - shOutputColor(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); + shOutputColour(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); #if MRT - shOutputColor(1) = float4(1,1,1,1); + shOutputColour(1) = float4(1,1,1,1); #endif } diff --git a/files/materials/core.h b/files/materials/core.h index 0ee95057f..b5d784dae 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -36,7 +36,7 @@ #ifdef SH_FRAGMENT_SHADER - #define shOutputColor(num) oColor##num + #define shOutputColour(num) oColor##num #define shDeclareMrtOutput(num) , out float4 oColor##num : COLOR##num @@ -75,7 +75,7 @@ #define shInputPosition vertex #define shOutputPosition gl_Position - #define shOutputColor(num) oColor##num + #define shOutputColour(num) oColor##num #define float4x4 mat4 diff --git a/files/materials/objects.shader b/files/materials/objects.shader index dd1b489d5..517845cb5 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -4,13 +4,13 @@ #define FOG @shPropertyBool(fog) -#define MRT @shPropertyNotBool(is_transparent) && @shPropertyBool(mrt_output) +#define MRT @shPropertyNotBool(is_transparent) && @shPropertyBool(mrt_output) && @shGlobalSettingBool(mrt_output) #define LIGHTING @shPropertyBool(lighting) #define SHADOWS LIGHTING && 0 #define SHADOWS_PSSM LIGHTING -#define SHADOWS 1 && LIGHTING +#define SHADOWS 0 && LIGHTING #define SHADOWS_PSSM 0 && LIGHTING #if FOG || MRT || SHADOWS_PSSM @@ -132,13 +132,13 @@ #if SHADOWS shInput(float4, lightSpacePos0) shSampler2D(shadowMap0) - shUniform(float2 invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, 0) + shUniform(float2 invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, 1) #endif #if SHADOWS_PSSM @shForeach(3) shInput(float4, lightSpacePos@shIterator) shSampler2D(shadowMap@shIterator) - shUniform(float2 invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator) + shUniform(float2 invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(1)) @shEndForeach shUniform(float4 pssmSplitPoints) @shSharedParameter(pssmSplitPoints) #endif @@ -148,7 +148,7 @@ #endif SH_START_PROGRAM { - shOutputColor(0) = shSample(diffuseMap, UV); + shOutputColour(0) = shSample(diffuseMap, UV); #if LIGHTING float3 normal = normalize(normalPassthrough); @@ -194,24 +194,24 @@ ambient *= colorPassthrough.xyz; #endif - shOutputColor(0).xyz *= (ambient + diffuse + materialEmissive.xyz); + shOutputColour(0).xyz *= (ambient + diffuse + materialEmissive.xyz); #endif #if HAS_VERTEXCOLOR && !LIGHTING - shOutputColor(0).xyz *= colorPassthrough.xyz; + shOutputColour(0).xyz *= colorPassthrough.xyz; #endif #if FOG float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); - shOutputColor(0).xyz = shLerp (shOutputColor(0).xyz, fogColor, fogValue); + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); #endif // prevent negative color output (for example with negative lights) - shOutputColor(0).xyz = max(shOutputColor(0).xyz, float3(0,0,0)); + shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0)); #if MRT - shOutputColor(1) = float4(depthPassthrough / far,1,1,1); + shOutputColour(1) = float4(depthPassthrough / far,1,1,1); #endif } diff --git a/files/materials/shadowcaster.mat b/files/materials/shadowcaster.mat index 7c11fddf6..5c5c8e088 100644 --- a/files/materials/shadowcaster.mat +++ b/files/materials/shadowcaster.mat @@ -1,6 +1,7 @@ material openmw_shadowcaster_default { create_configuration Default + allow_fixed_function false pass { fog_override true @@ -18,6 +19,7 @@ material openmw_shadowcaster_default material openmw_shadowcaster_noalpha { create_configuration Default + allow_fixed_function false pass { fog_override true diff --git a/files/materials/shadowcaster.shader b/files/materials/shadowcaster.shader index f772066a6..500207778 100644 --- a/files/materials/shadowcaster.shader +++ b/files/materials/shadowcaster.shader @@ -50,7 +50,7 @@ discard; #endif - shOutputColor(0) = float4(finalDepth, finalDepth, finalDepth, 1); + shOutputColour(0) = float4(finalDepth, finalDepth, finalDepth, 1); } #endif diff --git a/files/materials/sky.mat b/files/materials/sky.mat index c78570f7f..6465476dd 100644 --- a/files/materials/sky.mat +++ b/files/materials/sky.mat @@ -44,13 +44,13 @@ material openmw_clouds // second diffuse map is used for weather transitions texture_unit diffuseMap1 { - texture $diffuseMap1 + texture_alias cloud_texture_1 create_in_ffp true } texture_unit diffuseMap2 { - texture $diffuseMap2 + texture_alias cloud_texture_2 } } } diff --git a/files/materials/sun.shader b/files/materials/sun.shader index 811d45031..03ec7665c 100644 --- a/files/materials/sun.shader +++ b/files/materials/sun.shader @@ -29,10 +29,10 @@ SH_START_PROGRAM { - shOutputColor(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); + shOutputColour(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); #if MRT - shOutputColor(1) = float4(1,1,1,1); + shOutputColour(1) = float4(1,1,1,1); #endif } From 485adc8bdc0060abb071a57d3a94607608731294 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jul 2012 03:19:51 +0200 Subject: [PATCH 063/298] shader mode button in settingswindow --- apps/openmw/mwgui/settingswindow.cpp | 62 +++++++++++++++++++ apps/openmw/mwgui/settingswindow.hpp | 3 + apps/openmw/mwrender/renderingmanager.cpp | 27 +++++++++ files/mygui/openmw_settings_window.layout | 11 +++- files/mygui/openmw_windows.skin.xml | 74 +++++++++++++++++++++++ files/settings-default.cfg | 2 + 6 files changed, 178 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 5e25d5ece..122849634 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -74,6 +74,14 @@ namespace return "16 : 10"; return boost::lexical_cast(xaspect) + " : " + boost::lexical_cast(yaspect); } + + std::string hlslGlsl () + { + if (Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") == std::string::npos) + return "hlsl"; + else + return "glsl"; + } } namespace MWGui @@ -103,8 +111,10 @@ namespace MWGui getWidget(mReflectObjectsButton, "ReflectObjectsButton"); getWidget(mReflectActorsButton, "ReflectActorsButton"); getWidget(mReflectTerrainButton, "ReflectTerrainButton"); + getWidget(mShadersButton, "ShadersButton"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); + mShadersButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadersToggled); mFullscreenButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mWaterShaderButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mReflectObjectsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); @@ -189,6 +199,15 @@ namespace MWGui mReflectActorsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect actors", "Water") ? "#{sOn}" : "#{sOff}"); mReflectTerrainButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect terrain", "Water") ? "#{sOn}" : "#{sOff}"); + std::string shaders; + if (!Settings::Manager::getBool("shaders", "Objects")) + shaders = "off"; + else + { + shaders = Settings::Manager::getString("shader mode", "General"); + } + mShadersButton->setCaption (shaders); + if (!MWRender::RenderingManager::waterShaderSupported()) { mWaterShaderButton->setEnabled(false); @@ -280,6 +299,10 @@ namespace MWGui Settings::Manager::setBool("fullscreen", "Video", newState); apply(); } + } + else if (_sender == mShadersButton) + { + } else if (_sender == mVSyncButton) { @@ -306,6 +329,45 @@ namespace MWGui } } + void SettingsWindow::onShadersToggled(MyGUI::Widget* _sender) + { + std::string val = static_cast(_sender)->getCaption(); + if (val == "off") + val = hlslGlsl(); + else if (val == hlslGlsl()) + val = "cg"; + else + val = "off"; + + static_cast(_sender)->setCaption(val); + + if (val == "off") + { + Settings::Manager::setBool("shaders", "Objects", false); + + // water shader not supported with object shaders off + mWaterShaderButton->setCaptionWithReplacing("#{sOff}"); + mWaterShaderButton->setEnabled(false); + mReflectObjectsButton->setEnabled(false); + mReflectActorsButton->setEnabled(false); + mReflectTerrainButton->setEnabled(false); + Settings::Manager::setBool("shader", "Water", false); + } + else + { + // re-enable + mWaterShaderButton->setEnabled(true); + mReflectObjectsButton->setEnabled(true); + mReflectActorsButton->setEnabled(true); + mReflectTerrainButton->setEnabled(true); + + Settings::Manager::setBool("shaders", "Objects", true); + Settings::Manager::setString("shader mode", "General", val); + } + + apply(); + } + void SettingsWindow::onFpsToggled(MyGUI::Widget* _sender) { int newLevel = (Settings::Manager::getInt("fps", "HUD") + 1) % 3; diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index e3827c7b0..2bcab9d21 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -42,6 +42,7 @@ namespace MWGui MyGUI::Button* mReflectObjectsButton; MyGUI::Button* mReflectActorsButton; MyGUI::Button* mReflectTerrainButton; + MyGUI::Button* mShadersButton; // audio MyGUI::ScrollBar* mMasterVolumeSlider; @@ -59,6 +60,8 @@ namespace MWGui void onResolutionAccept(); void onResolutionCancel(); + void onShadersToggled(MyGUI::Widget* _sender); + void apply(); }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fa20cd48d..b3e3e42a4 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -42,6 +42,15 @@ namespace MWRender { RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir, OEngine::Physic::PhysicEngine* engine) :mRendering(_rend), mObjects(mRendering), mActors(mRendering), mAmbientMode(0), mSunEnabled(0) { + // select best shader mode + if (Settings::Manager::getString("shader mode", "General") == "") + { + if (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") == std::string::npos) + Settings::Manager::setString("shader mode", "General", "hlsl"); + else + Settings::Manager::setString("shader mode", "General", "glsl"); + } + mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); mRendering.setWindowEventListener(this); @@ -619,6 +628,24 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); mObjects.rebuildStaticGeometry (); } + else if (it->second == "shaders" && it->first == "Objects") + { + sh::Factory::getInstance ().setShadersEnabled (Settings::Manager::getBool("shaders", "Objects")); + mObjects.rebuildStaticGeometry (); + } + else if (it->second == "shader mode" && it->first == "General") + { + sh::Language lang; + std::string l = Settings::Manager::getString("shader mode", "General"); + if (l == "glsl") + lang = sh::Language_GLSL; + else if (l == "hlsl") + lang = sh::Language_HLSL; + else + lang = sh::Language_CG; + sh::Factory::getInstance ().setCurrentLanguage (lang); + mObjects.rebuildStaticGeometry (); + } } if (changeRes) diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 7c508b1e9..9d25b3ee5 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -1,7 +1,10 @@ - + + + + @@ -100,6 +103,12 @@ + + + + + + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index b379f84ce..a9eb23d68 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -322,6 +322,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 883f32ae0..bd8bb64b3 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -48,6 +48,8 @@ anisotropy = 4 # Number of texture mipmaps to generate num mipmaps = 5 +shader mode = + [Shadows] # Shadows are only supported when object shaders are on! enabled = false From e0a99f104b2b2030923d460a5a13420430e0b639 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jul 2012 04:28:45 +0200 Subject: [PATCH 064/298] settings tab "shadows" --- apps/openmw/mwgui/settingswindow.cpp | 66 ++++++++++++-- apps/openmw/mwgui/settingswindow.hpp | 9 ++ apps/openmw/mwrender/renderingmanager.cpp | 8 +- apps/openmw/mwrender/shadows.cpp | 104 ++++++++++++---------- files/materials/objects.shader | 9 +- files/mygui/openmw_settings_window.layout | 44 +++++++++ files/settings-default.cfg | 2 + 7 files changed, 182 insertions(+), 60 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 122849634..3ef89d422 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -112,6 +112,13 @@ namespace MWGui getWidget(mReflectActorsButton, "ReflectActorsButton"); getWidget(mReflectTerrainButton, "ReflectTerrainButton"); getWidget(mShadersButton, "ShadersButton"); + getWidget(mShadowsEnabledButton, "ShadowsEnabledButton"); + getWidget(mShadowsLargeDistance, "ShadowsLargeDistance"); + getWidget(mShadowsTextureSize, "ShadowsTextureSize"); + getWidget(mActorShadows, "ActorShadows"); + getWidget(mStaticsShadows, "StaticsShadows"); + getWidget(mMiscShadows, "MiscShadows"); + getWidget(mShadowsDebug, "ShadowsDebug"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); mShadersButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadersToggled); @@ -130,6 +137,14 @@ namespace MWGui mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); mAnisotropySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + mShadowsEnabledButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mShadowsLargeDistance->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mShadowsTextureSize->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSize); + mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mShadowsDebug->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mMasterVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); mVoiceVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); mEffectsVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); @@ -182,7 +197,6 @@ namespace MWGui std::string tf = Settings::Manager::getString("texture filtering", "General"); mTextureFilteringButton->setCaption(textureFilteringToStr(tf)); mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast(Settings::Manager::getInt("anisotropy", "General")) + ")"); - mAnisotropyBox->setVisible(tf == "anisotropic"); float val = (Settings::Manager::getFloat("max viewing distance", "Viewing distance")-sViewDistMin)/(sViewDistMax-sViewDistMin); int viewdist = (mViewDistanceSlider->getScrollRange()-1) * val; @@ -199,6 +213,14 @@ namespace MWGui mReflectActorsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect actors", "Water") ? "#{sOn}" : "#{sOff}"); mReflectTerrainButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect terrain", "Water") ? "#{sOn}" : "#{sOff}"); + mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); + mShadowsLargeDistance->setCaptionWithReplacing(Settings::Manager::getBool("split", "Shadows") ? "#{sOn}" : "#{sOff}"); + mShadowsEnabledButton->setCaptionWithReplacing(Settings::Manager::getBool("enabled", "Shadows") ? "#{sOn}" : "#{sOff}"); + mActorShadows->setCaptionWithReplacing(Settings::Manager::getBool("actor shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); + mStaticsShadows->setCaptionWithReplacing(Settings::Manager::getBool("statics shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); + mMiscShadows->setCaptionWithReplacing(Settings::Manager::getBool("misc shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); + mShadowsDebug->setCaptionWithReplacing(Settings::Manager::getBool("debug", "Shadows") ? "#{sOn}" : "#{sOff}"); + std::string shaders; if (!Settings::Manager::getBool("shaders", "Objects")) shaders = "off"; @@ -256,6 +278,25 @@ namespace MWGui mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); } + void SettingsWindow::onShadowTextureSize(MyGUI::Widget* _sender) + { + std::string size = mShadowsTextureSize->getCaption(); + + if (size == "512") + size = "1024"; + else if (size == "1024") + size = "2048"; + else if (size == "2048") + size = "4096"; + else + size = "512"; + + mShadowsTextureSize->setCaption(size); + + Settings::Manager::setString("texture size", "Shadows", size); + apply(); + } + void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { std::string on = mWindowManager.getGameSettingString("sOn", "On"); @@ -299,10 +340,6 @@ namespace MWGui Settings::Manager::setBool("fullscreen", "Video", newState); apply(); } - } - else if (_sender == mShadersButton) - { - } else if (_sender == mVSyncButton) { @@ -324,6 +361,18 @@ namespace MWGui Settings::Manager::setBool("reflect actors", "Water", newState); else if (_sender == mReflectTerrainButton) Settings::Manager::setBool("reflect terrain", "Water", newState); + else if (_sender == mShadowsEnabledButton) + Settings::Manager::setBool("enabled", "Shadows", newState); + else if (_sender == mShadowsLargeDistance) + Settings::Manager::setBool("split", "Shadows", newState); + else if (_sender == mActorShadows) + Settings::Manager::setBool("actor shadows", "Shadows", newState); + else if (_sender == mStaticsShadows) + Settings::Manager::setBool("statics shadows", "Shadows", newState); + else if (_sender == mMiscShadows) + Settings::Manager::setBool("misc shadows", "Shadows", newState); + else if (_sender == mShadowsDebug) + Settings::Manager::setBool("debug", "Shadows", newState); apply(); } @@ -352,6 +401,11 @@ namespace MWGui mReflectActorsButton->setEnabled(false); mReflectTerrainButton->setEnabled(false); Settings::Manager::setBool("shader", "Water", false); + + // shadows not supported + mShadowsEnabledButton->setEnabled(false); + mShadowsEnabledButton->setCaptionWithReplacing("#{sOff}"); + Settings::Manager::setBool("enabled", "Shadows", false); } else { @@ -360,6 +414,7 @@ namespace MWGui mReflectObjectsButton->setEnabled(true); mReflectActorsButton->setEnabled(true); mReflectTerrainButton->setEnabled(true); + mShadowsEnabledButton->setEnabled(true); Settings::Manager::setBool("shaders", "Objects", true); Settings::Manager::setString("shader mode", "General", val); @@ -390,7 +445,6 @@ namespace MWGui next = "none"; mTextureFilteringButton->setCaption(textureFilteringToStr(next)); - mAnisotropyBox->setVisible(next == "anisotropic"); Settings::Manager::setString("texture filtering", "General", next); apply(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 2bcab9d21..840d266f1 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -44,6 +44,14 @@ namespace MWGui MyGUI::Button* mReflectTerrainButton; MyGUI::Button* mShadersButton; + MyGUI::Button* mShadowsEnabledButton; + MyGUI::Button* mShadowsLargeDistance; + MyGUI::Button* mShadowsTextureSize; + MyGUI::Button* mActorShadows; + MyGUI::Button* mStaticsShadows; + MyGUI::Button* mMiscShadows; + MyGUI::Button* mShadowsDebug; + // audio MyGUI::ScrollBar* mMasterVolumeSlider; MyGUI::ScrollBar* mVoiceVolumeSlider; @@ -61,6 +69,7 @@ namespace MWGui void onResolutionCancel(); void onShadersToggled(MyGUI::Widget* _sender); + void onShadowTextureSize(MyGUI::Widget* _sender); void apply(); }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b3e3e42a4..d0613648d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -62,8 +62,6 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const sh::OgrePlatform* platform = new sh::OgrePlatform("General", (resDir / "materials").string()); platform->setCacheFolder ("./"); mFactory = new sh::Factory(platform); - mFactory->setSharedParameter ("pssmSplitPoints", sh::makeProperty(new sh::Vector4(0,0,0,0))); - mFactory->setSharedParameter ("shadowFar_fadeStart", sh::makeProperty(new sh::Vector4(0,0,0,0))); //The fog type must be set before any terrain objects are created as if the //fog type is set to FOG_NONE then the initially created terrain won't have any fog @@ -646,6 +644,12 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec sh::Factory::getInstance ().setCurrentLanguage (lang); mObjects.rebuildStaticGeometry (); } + else if (it->first == "Shadows") + { + mShadows->recreate (); + + mObjects.rebuildStaticGeometry (); + } } if (changeRes) diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index b4982e22d..4c5188e37 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -36,6 +36,9 @@ void Shadows::recreate() bool split = Settings::Manager::getBool("split", "Shadows"); //const bool split = false; + sh::Factory::getInstance ().setGlobalSetting ("shadows", enabled && !split ? "true" : "false"); + sh::Factory::getInstance ().setGlobalSetting ("shadows_pssm", enabled && split ? "true" : "false"); + if (!enabled) { mSceneMgr->setShadowTechnique(SHADOWTYPE_NONE); @@ -122,53 +125,62 @@ void Shadows::recreate() // -------------------------------------------------------------------------------------------------------------------- // --------------------------- Debug overlays to display the content of shadow maps ----------------------------------- // -------------------------------------------------------------------------------------------------------------------- + if (Settings::Manager::getBool("debug", "Shadows")) + { + OverlayManager& mgr = OverlayManager::getSingleton(); + Overlay* overlay; + + // destroy if already exists + if (overlay = mgr.getByName("DebugOverlay")) + mgr.destroy(overlay); + + overlay = mgr.create("DebugOverlay"); + for (size_t i = 0; i < (split ? 3 : 1); ++i) { + TexturePtr tex = mRendering->getScene()->getShadowTexture(i); + + // Set up a debug panel to display the shadow + + if (MaterialManager::getSingleton().resourceExists("Ogre/DebugTexture" + StringConverter::toString(i))) + MaterialManager::getSingleton().remove("Ogre/DebugTexture" + StringConverter::toString(i)); + MaterialPtr debugMat = MaterialManager::getSingleton().create( + "Ogre/DebugTexture" + StringConverter::toString(i), + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + + debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false); + TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(tex->getName()); + t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + + OverlayContainer* debugPanel; + + // destroy container if exists + try + { + if (debugPanel = + static_cast( + mgr.getOverlayElement("Ogre/DebugTexPanel" + StringConverter::toString(i) + ))) + mgr.destroyOverlayElement(debugPanel); + } + catch (Ogre::Exception&) {} + + debugPanel = (OverlayContainer*) + (OverlayManager::getSingleton().createOverlayElement("Panel", "Ogre/DebugTexPanel" + StringConverter::toString(i))); + debugPanel->_setPosition(0.8, i*0.25); + debugPanel->_setDimensions(0.2, 0.24); + debugPanel->setMaterialName(debugMat->getName()); + debugPanel->show(); + overlay->add2D(debugPanel); + overlay->show(); + } + } + else + { + OverlayManager& mgr = OverlayManager::getSingleton(); + Overlay* overlay; - OverlayManager& mgr = OverlayManager::getSingleton(); - Overlay* overlay; - - // destroy if already exists - if (overlay = mgr.getByName("DebugOverlay")) - mgr.destroy(overlay); - - overlay = mgr.create("DebugOverlay"); - for (size_t i = 0; i < (split ? 3 : 1); ++i) { - TexturePtr tex = mRendering->getScene()->getShadowTexture(i); - - // Set up a debug panel to display the shadow - - if (MaterialManager::getSingleton().resourceExists("Ogre/DebugTexture" + StringConverter::toString(i))) - MaterialManager::getSingleton().remove("Ogre/DebugTexture" + StringConverter::toString(i)); - MaterialPtr debugMat = MaterialManager::getSingleton().create( - "Ogre/DebugTexture" + StringConverter::toString(i), - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - - debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false); - TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(tex->getName()); - t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); - - OverlayContainer* debugPanel; - - // destroy container if exists - try - { - if (debugPanel = - static_cast( - mgr.getOverlayElement("Ogre/DebugTexPanel" + StringConverter::toString(i) - ))) - mgr.destroyOverlayElement(debugPanel); - } - catch (Ogre::Exception&) {} - - debugPanel = (OverlayContainer*) - (OverlayManager::getSingleton().createOverlayElement("Panel", "Ogre/DebugTexPanel" + StringConverter::toString(i))); - debugPanel->_setPosition(0.8, i*0.25); - debugPanel->_setDimensions(0.2, 0.24); - debugPanel->setMaterialName(debugMat->getName()); - debugPanel->show(); - overlay->add2D(debugPanel); - overlay->show(); - } - + if (overlay = mgr.getByName("DebugOverlay")) + mgr.destroy(overlay); + } } PSSMShadowCameraSetup* Shadows::getPSSMSetup() diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 517845cb5..c80a592a3 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -7,11 +7,8 @@ #define MRT @shPropertyNotBool(is_transparent) && @shPropertyBool(mrt_output) && @shGlobalSettingBool(mrt_output) #define LIGHTING @shPropertyBool(lighting) -#define SHADOWS LIGHTING && 0 -#define SHADOWS_PSSM LIGHTING - -#define SHADOWS 0 && LIGHTING -#define SHADOWS_PSSM 0 && LIGHTING +#define SHADOWS_PSSM LIGHTING && @shGlobalSettingBool(shadows_pssm) +#define SHADOWS LIGHTING && @shGlobalSettingBool(shadows) #if FOG || MRT || SHADOWS_PSSM #define NEED_DEPTH @@ -183,7 +180,7 @@ lightDir = normalize(lightDir); -#if @shIterator == 0 +#if @shIterator == 0 && (SHADOWS || SHADOWS_PSSM) diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; #else diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 9d25b3ee5..d8fbca69c 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -190,6 +190,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index bd8bb64b3..a723e307c 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -76,6 +76,8 @@ statics shadows = true # Fraction of the total shadow distance after which the shadow starts to fade out fade start = 0.8 +debug = false + [HUD] # FPS counter # 0: not visible From b2313be6d755b48002cc7e51b47a7af2134f3025 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jul 2012 04:31:33 +0200 Subject: [PATCH 065/298] submodule update --- extern/shiny | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/shiny b/extern/shiny index 27a128c41..34e952bf3 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 27a128c414e2f92d6bcda379b9c9df04e69b7472 +Subproject commit 34e952bf3d5e09adfd3e5c0f6462d612e193e447 From 395a7600fd061c73a24007d902c3148a31bb5038 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 10 Jul 2012 23:13:03 -0700 Subject: [PATCH 066/298] Initialize identify transform when declaring the identity object --- components/nif/nif_types.hpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/components/nif/nif_types.hpp b/components/nif/nif_types.hpp index 705ed5994..f364cd2d4 100644 --- a/components/nif/nif_types.hpp +++ b/components/nif/nif_types.hpp @@ -41,17 +41,9 @@ struct Transformation static const Transformation& getIdentity() { - static Transformation identity; - static bool iset = false; - if (!iset) - { - identity.scale = 1.0f; - identity.rotation[0][0] = 1.0f; - identity.rotation[1][1] = 1.0f; - identity.rotation[2][2] = 1.0f; - iset = true; - } - + static const Transformation identity = { + Ogre::Vector3::ZERO, Ogre::Matrix3::IDENTITY, 1.0f, Ogre::Vector3::ZERO + }; return identity; } }; From ad7383be439f9c3260dfc97ea8dbbc859342faac Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jul 2012 09:08:55 +0200 Subject: [PATCH 067/298] sky --- apps/openmw/mwrender/renderingmanager.cpp | 10 + apps/openmw/mwrender/shadows.cpp | 14 +- apps/openmw/mwrender/sky.cpp | 254 ++++++---------------- apps/openmw/mwrender/sky.hpp | 10 +- extern/shiny | 2 +- files/materials/atmosphere.shader | 6 +- files/materials/clouds.shader | 27 ++- files/materials/core.h | 6 +- files/materials/objects.shader | 40 ++-- files/materials/shadowcaster.shader | 2 +- files/materials/shadows.h | 4 +- files/materials/sky.mat | 40 +--- files/materials/sun.shader | 10 +- 13 files changed, 145 insertions(+), 280 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d0613648d..475e906c4 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -63,6 +63,16 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const platform->setCacheFolder ("./"); mFactory = new sh::Factory(platform); + sh::Language lang; + std::string l = Settings::Manager::getString("shader mode", "General"); + if (l == "glsl") + lang = sh::Language_GLSL; + else if (l == "hlsl") + lang = sh::Language_HLSL; + else + lang = sh::Language_CG; + mFactory->setCurrentLanguage (lang); + //The fog type must be set before any terrain objects are created as if the //fog type is set to FOG_NONE then the initially created terrain won't have any fog configureFog(1, ColourValue(1,1,1)); diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 4c5188e37..3d9f13243 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -91,16 +91,16 @@ void Shadows::recreate() // Populate from split point 1, not 0, since split 0 isn't useful (usually 0) const PSSMShadowCameraSetup::SplitPointList& splitPointList = getPSSMSetup()->getSplitPoints(); - sh::Vector4* splitPoints = new sh::Vector4(splitPointList[1], splitPointList[2], splitPointList[3], 1.0); + sh::Vector3* splitPoints = new sh::Vector3(splitPointList[1], splitPointList[2], splitPointList[3]); - sh::Factory::getInstance ().setSharedParameter ("pssmSplitPoints", sh::makeProperty(splitPoints)); + sh::Factory::getInstance ().setSharedParameter ("pssmSplitPoints", sh::makeProperty(splitPoints)); shadowCameraSetup = ShadowCameraSetupPtr(mPSSMSetup); } else { LiSPSMShadowCameraSetup* lispsmSetup = new LiSPSMShadowCameraSetup(); - lispsmSetup->setOptimalAdjustFactor(2); + lispsmSetup->setOptimalAdjustFactor(64); //lispsmSetup->setCameraLightDirectionThreshold(Degree(0)); //lispsmSetup->setUseAggressiveFocusRegion(false); shadowCameraSetup = ShadowCameraSetupPtr(lispsmSetup); @@ -131,7 +131,7 @@ void Shadows::recreate() Overlay* overlay; // destroy if already exists - if (overlay = mgr.getByName("DebugOverlay")) + if ((overlay = mgr.getByName("DebugOverlay"))) mgr.destroy(overlay); overlay = mgr.create("DebugOverlay"); @@ -155,10 +155,10 @@ void Shadows::recreate() // destroy container if exists try { - if (debugPanel = + if ((debugPanel = static_cast( mgr.getOverlayElement("Ogre/DebugTexPanel" + StringConverter::toString(i) - ))) + )))) mgr.destroyOverlayElement(debugPanel); } catch (Ogre::Exception&) {} @@ -178,7 +178,7 @@ void Shadows::recreate() OverlayManager& mgr = OverlayManager::getSingleton(); Overlay* overlay; - if (overlay = mgr.getByName("DebugOverlay")) + if ((overlay = mgr.getByName("DebugOverlay"))) mgr.destroy(overlay); } } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index f79ab6116..65a9e8953 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -15,6 +15,8 @@ #include #include +#include + #include #include "../mwbase/environment.hpp" @@ -50,7 +52,7 @@ void BillboardObject::setSize(const float size) void BillboardObject::setVisibility(const float visibility) { - mMaterial->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, visibility); + //mMaterial->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, visibility); } void BillboardObject::setPosition(const Vector3& pPosition) @@ -76,7 +78,7 @@ void BillboardObject::setVisibilityFlags(int flags) void BillboardObject::setColour(const ColourValue& pColour) { - mMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(pColour); + //mMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(pColour); } void BillboardObject::setRenderQueue(unsigned int id) @@ -112,6 +114,12 @@ void BillboardObject::init(const String& textureName, mBBSet->createBillboard(0,0,0); mBBSet->setCastShadows(false); + sh::MaterialInstance* m = sh::Factory::getInstance().createMaterialInstance ("BillboardMaterial"+StringConverter::toString(bodyCount), "openmw_sun"); + m->setProperty("texture", sh::makeProperty(new sh::StringValue(textureName))); + + mBBSet->setMaterialName("BillboardMaterial"+StringConverter::toString(bodyCount)); + + /* mMaterial = MaterialManager::getSingleton().create("BillboardMaterial"+StringConverter::toString(bodyCount), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); mMaterial->removeAllTechniques(); Pass* p = mMaterial->createTechnique()->createPass(); @@ -123,7 +131,6 @@ void BillboardObject::init(const String& textureName, p->setAmbient(0.0,0.0,0.0); p->setPolygonModeOverrideable(false); p->createTextureUnitState(textureName); - mBBSet->setMaterialName("BillboardMaterial"+StringConverter::toString(bodyCount)); HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); HighLevelGpuProgramPtr vshader; @@ -183,7 +190,7 @@ void BillboardObject::init(const String& textureName, fshader->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); mMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); - +*/ bodyCount++; } @@ -194,6 +201,8 @@ Moon::Moon( const String& textureName, { init(textureName, initialSize, position, rootNode); + + /* HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); HighLevelGpuProgramPtr vshader; if (mgr.resourceExists("Moon_VP")) @@ -261,6 +270,8 @@ Moon::Moon( const String& textureName, fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); mMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); + */ + setVisibility(1.0); mPhase = Moon::Phase_Full; @@ -273,7 +284,7 @@ void Moon::setType(const Moon::Type& type) void Moon::setSkyColour(const Ogre::ColourValue& colour) { - mMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("skyColour", colour); + //mMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("skyColour", colour); } void Moon::setPhase(const Moon::Phase& phase) @@ -295,7 +306,7 @@ void Moon::setPhase(const Moon::Phase& phase) textureName += ".dds"; - mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(textureName); + //mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(textureName); mPhase = phase; } @@ -387,8 +398,6 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) , mSceneMgr(NULL) , mAtmosphereDay(NULL) , mAtmosphereNight(NULL) - , mCloudMaterial() - , mAtmosphereMaterial() , mCloudFragmentShader() , mClouds() , mNextClouds() @@ -406,6 +415,7 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) , mMasserEnabled(true) , mSecundaEnabled(true) , mCreated(false) + , mCloudAnimationTimer(0.f) { mSceneMgr = pMwRoot->getCreator(); mRootNode = mCamera->getParentSceneNode()->createChildSceneNode(); @@ -415,7 +425,19 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) void SkyManager::create() { - /// \todo preload all the textures and meshes that are used for sky rendering + sh::Factory::getInstance().setSharedParameter ("cloudBlendFactor", + sh::makeProperty(new sh::FloatValue(0))); + sh::Factory::getInstance().setSharedParameter ("cloudOpacity", + sh::makeProperty(new sh::FloatValue(1))); + sh::Factory::getInstance().setSharedParameter ("cloudColour", + sh::makeProperty(new sh::Vector3(1,1,1))); + sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", + sh::makeProperty(new sh::FloatValue(0))); + sh::Factory::getInstance().setSharedParameter ("nightFade", + sh::makeProperty(new sh::FloatValue(0))); + + sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", ""); + sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", ""); // Create overlay used for thunderstorm MaterialPtr material = MaterialManager::getSingleton().create( "ThunderMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME ); @@ -449,9 +471,6 @@ void SkyManager::create() mSunGlare->setRenderQueue(RQG_SkiesLate); mSunGlare->setVisibilityFlags(RV_Glare); - - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - // Stars MeshPtr mesh = NifOgre::NIFLoader::load("meshes\\sky_night_01.nif"); Entity* night1_ent = mSceneMgr->createEntity("meshes\\sky_night_01.nif"); @@ -462,76 +481,18 @@ void SkyManager::create() mAtmosphereNight = mRootNode->createChildSceneNode(); mAtmosphereNight->attachObject(night1_ent); - // Stars vertex shader - HighLevelGpuProgramPtr stars_vp = mgr.createProgram("Stars_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_VERTEX_PROGRAM); - stars_vp->setParameter("profiles", "vs_2_x arbvp1"); - stars_vp->setParameter("entry_point", "main_vp"); - StringUtil::StrStreamType outStream4; - outStream4 << - "void main_vp( \n" - " float4 position : POSITION, \n" - " in float2 uv : TEXCOORD0, \n" - " out float2 oUV : TEXCOORD0, \n" - " out float oFade : TEXCOORD1, \n" - " out float4 oPosition : POSITION, \n" - " uniform float4x4 worldViewProj \n" - ") \n" - "{ \n" - " oUV = uv; \n" - " oFade = (position.z > 50) ? 1.f : 0.f; \n" - " oPosition = mul( worldViewProj, position ); \n" - "}"; - stars_vp->setSource(outStream4.str()); - stars_vp->load(); - stars_vp->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - - // Stars fragment shader - HighLevelGpuProgramPtr stars_fp = mgr.createProgram("Stars_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_FRAGMENT_PROGRAM); - stars_fp->setParameter("profiles", "ps_2_x arbfp1"); - stars_fp->setParameter("entry_point", "main_fp"); - StringUtil::StrStreamType outStream5; - outStream5 << - "void main_fp( \n" - " in float2 uv : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n"; - if (RenderingManager::useMRT()) outStream5 << - " out float4 oColor1 : COLOR1, \n"; - outStream5 << - " in float fade : TEXCOORD1, \n" - " uniform sampler2D texture : TEXUNIT0, \n" - " uniform float opacity, \n" - " uniform float4 diffuse, \n" - " uniform float4 emissive \n" - ") \n" - "{ \n" - " oColor = tex2D(texture, uv) * float4(emissive.xyz, 1) * float4(1,1,1,fade*diffuse.a); \n"; - if (RenderingManager::useMRT()) outStream5 << - " oColor1 = float4(1, 0, 0, 1); \n"; - outStream5 << - "}"; - stars_fp->setSource(outStream5.str()); - stars_fp->load(); - stars_fp->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - stars_fp->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); - - /* for (unsigned int i=0; igetNumSubEntities(); ++i) { - MaterialPtr mp = night1_ent->getSubEntity(i)->getMaterial(); - mp->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); - mp->getTechnique(0)->getPass(0)->setAmbient(0.0, 0.0, 0.0); - mp->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, 1.0); - mp->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mp->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - mp->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); - mp->getTechnique(0)->getPass(0)->setVertexProgram(stars_vp->getName()); - mp->getTechnique(0)->getPass(0)->setFragmentProgram(stars_fp->getName()); - mp->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); - mStarsMaterials[i] = mp; + std::string matName = "openmw_stars_" + boost::lexical_cast(i); + sh::MaterialInstance* m = sh::Factory::getInstance ().createMaterialInstance (matName, "openmw_stars"); + + std::string textureName = sh::retrieveValue( + sh::Factory::getInstance().getMaterialInstance(night1_ent->getSubEntity (i)->getMaterialName ())->getProperty("diffuseMap"), NULL).get(); + + m->setProperty ("texture", sh::makeProperty(new sh::StringValue(textureName))); + + night1_ent->getSubEntity(i)->setMaterialName (matName); } - */ // Atmosphere (day) mesh = NifOgre::NIFLoader::load("meshes\\sky_atmosphere.nif"); @@ -545,11 +506,6 @@ void SkyManager::create() mAtmosphereDay = mRootNode->createChildSceneNode(); mAtmosphereDay->attachObject(atmosphere_ent); atmosphere_ent->getSubEntity (0)->setMaterialName ("openmw_atmosphere"); - //mAtmosphereMaterial = atmosphere_ent->getSubEntity(0)->getMaterial(); - //mAtmosphereMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); - // Atmosphere shader - // mAtmosphereMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); - atmosphere_ent->getSubEntity (0)->setMaterialName("openmw_atmosphere"); // Clouds NifOgre::NIFLoader::load("meshes\\sky_clouds_01.nif"); @@ -558,89 +514,11 @@ void SkyManager::create() clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); SceneNode* clouds_node = mRootNode->createChildSceneNode(); clouds_node->attachObject(clouds_ent); - //mCloudMaterial = clouds_ent->getSubEntity(0)->getMaterial(); clouds_ent->getSubEntity(0)->setMaterialName ("openmw_clouds"); - //mCloudMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); clouds_ent->setCastShadows(false); - // Clouds vertex shader - HighLevelGpuProgramPtr vshader2 = mgr.createProgram("Clouds_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_VERTEX_PROGRAM); - vshader2->setParameter("profiles", "vs_2_x arbvp1"); - vshader2->setParameter("entry_point", "main_vp"); - StringUtil::StrStreamType outStream3; - outStream3 << - "void main_vp( \n" - " float4 position : POSITION, \n" - " in float4 color : COLOR, \n" - " out float4 oColor : TEXCOORD1, \n" - " in float2 uv : TEXCOORD0, \n" - " out float2 oUV : TEXCOORD0, \n" - " out float4 oPosition : POSITION, \n" - " uniform float4x4 worldViewProj \n" - ") \n" - "{ \n" - " oUV = uv; \n" - " oColor = color; \n" - " oPosition = mul( worldViewProj, position ); \n" - "}"; - vshader2->setSource(outStream3.str()); - vshader2->load(); - vshader2->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - //mCloudMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader2->getName()); - - // Clouds fragment shader - mCloudFragmentShader = mgr.createProgram("Clouds_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_FRAGMENT_PROGRAM); - mCloudFragmentShader->setParameter("profiles", "ps_2_x arbfp1"); - mCloudFragmentShader->setParameter("entry_point", "main_fp"); - StringUtil::StrStreamType outStream2; - outStream2 << - "void main_fp( \n" - " in float2 uv : TEXCOORD0, \n" - " in float4 color : TEXCOORD1, \n" - " out float4 oColor : COLOR, \n"; - if (RenderingManager::useMRT()) outStream2 << - " out float4 oColor1 : COLOR1, \n"; - outStream2 << - " uniform sampler2D texture : TEXUNIT0, \n" - " uniform sampler2D secondTexture : TEXUNIT1, \n" - " uniform float transitionFactor, \n" - " uniform float time, \n" - " uniform float speed, \n" - " uniform float opacity, \n" - " uniform float4 emissive \n" - ") \n" - "{ \n" - " uv += float2(0,1) * time * speed * 0.003; \n" // Scroll in y direction - " float4 tex = lerp(tex2D(texture, uv), tex2D(secondTexture, uv), transitionFactor); \n" - " oColor = color * float4(emissive.xyz,1) * tex * float4(1,1,1,opacity); \n"; - if (RenderingManager::useMRT()) outStream2 << - " oColor1 = float4(1, 0, 0, 1); \n"; - outStream2 << - "}"; - mCloudFragmentShader->setSource(outStream2.str()); - mCloudFragmentShader->load(); - mCloudFragmentShader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - //mCloudMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(mCloudFragmentShader->getName()); - setCloudsOpacity(0.75); - ModVertexAlpha(clouds_ent, 1); - // I'm not sure if the materials are being used by any other objects - // Make a unique "modifiable" copy of the materials to be sure - //mCloudMaterial = mCloudMaterial->clone("Clouds"); - //clouds_ent->getSubEntity(0)->setMaterial(mCloudMaterial); - /* - mCloudMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); - mCloudMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mCloudMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); - - mCloudMaterial->getTechnique(0)->getPass(0)->removeAllTextureUnitStates(); - mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("textures\\tx_sky_cloudy.dds"); - mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); -*/ - mCreated = true; } @@ -669,7 +547,10 @@ void SkyManager::update(float duration) if (!mEnabled) return; // UV Scroll the clouds - //mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstantFromTime("time", MWBase::Environment::get().getWorld()->getTimeScaleFactor()/30.f); + mCloudAnimationTimer += duration * mCloudSpeed * (MWBase::Environment::get().getWorld()->getTimeScaleFactor()/30.f); + sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", + sh::makeProperty(new sh::FloatValue(mCloudAnimationTimer))); + /// \todo improve this mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); @@ -678,15 +559,15 @@ void SkyManager::update(float duration) if (mSunEnabled) { - // take 1/5 sec for fading the glare effect from invisible to full + // take 1/10 sec for fading the glare effect from invisible to full if (mGlareFade > mGlare) { - mGlareFade -= duration*5; + mGlareFade -= duration*10; if (mGlareFade < mGlare) mGlareFade = mGlare; } else if (mGlareFade < mGlare) { - mGlareFade += duration*5; + mGlareFade += duration*10; if (mGlareFade > mGlare) mGlareFade = mGlare; } @@ -733,39 +614,34 @@ void SkyManager::setMoonColour (bool red) : ColourValue(1.0, 1.0, 1.0)); } -void SkyManager::setCloudsOpacity(float opacity) -{ - if (!mCreated) return; - //mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("opacity", Real(opacity)); -} - void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { if (!mCreated) return; - /* if (mClouds != weather.mCloudTexture) { - mCloudMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("textures\\"+weather.mCloudTexture); + sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", "textures\\"+weather.mCloudTexture); mClouds = weather.mCloudTexture; } if (mNextClouds != weather.mNextCloudTexture) { - mCloudMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName("textures\\"+weather.mNextCloudTexture); + sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", "textures\\"+weather.mNextCloudTexture); mNextClouds = weather.mNextCloudTexture; } if (mCloudBlendFactor != weather.mCloudBlendFactor) { - mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("transitionFactor", Real(weather.mCloudBlendFactor)); mCloudBlendFactor = weather.mCloudBlendFactor; + sh::Factory::getInstance().setSharedParameter ("cloudBlendFactor", + sh::makeProperty(new sh::FloatValue(weather.mCloudBlendFactor))); } if (mCloudOpacity != weather.mCloudOpacity) { - mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("opacity", Real(weather.mCloudOpacity)); mCloudOpacity = weather.mCloudOpacity; + sh::Factory::getInstance().setSharedParameter ("cloudOpacity", + sh::makeProperty(new sh::FloatValue(weather.mCloudOpacity))); } if (mCloudColour != weather.mSunColor) @@ -774,23 +650,21 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) weather.mSunColor.g*0.7 + weather.mAmbientColor.g*0.7, weather.mSunColor.b*0.7 + weather.mAmbientColor.b*0.7); - mCloudMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(clr); + sh::Factory::getInstance().setSharedParameter ("cloudColour", + sh::makeProperty(new sh::Vector3(clr.r, clr.g, clr.b))); + mCloudColour = weather.mSunColor; } -*/ + if (mSkyColour != weather.mSkyColor) { mSkyColour = weather.mSkyColor; sh::Factory::getInstance().setSharedParameter ("atmosphereColour", sh::makeProperty(new sh::Vector4( - weather.mSkyColor.r, weather.mSkyColor.g, weather.mSkyColor.b, 1.0))); - } -/* - if (mCloudSpeed != weather.mCloudSpeed) - { - mCloudMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("speed", Real(weather.mCloudSpeed)); - mCloudSpeed = weather.mCloudSpeed; + weather.mSkyColor.r, weather.mSkyColor.g, weather.mSkyColor.b, weather.mSkyColor.a))); } + mCloudSpeed = weather.mCloudSpeed; + if (weather.mNight && mStarsOpacity != weather.mNightFade) { if (weather.mNightFade == 0) @@ -798,12 +672,14 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) else { mAtmosphereNight->setVisible(true); - for (int i=0; i<7; ++i) - mStarsMaterials[i]->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, weather.mNightFade); + + sh::Factory::getInstance().setSharedParameter ("nightFade", + sh::makeProperty(new sh::FloatValue(weather.mNightFade))); + mStarsOpacity = weather.mNightFade; } } - */ + float strength; float timeofday_angle = std::abs(mSunGlare->getPosition().z/mSunGlare->getPosition().length()); diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 6bb055824..f59583c04 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -139,9 +139,6 @@ namespace MWRender void setMoonColour (bool red); ///< change Secunda colour to red - void setCloudsOpacity(float opacity); - ///< change opacity of the clouds - void setWeather(const MWWorld::WeatherResult& weather); Ogre::SceneNode* getSunNode(); @@ -182,6 +179,8 @@ namespace MWRender int mDay; int mMonth; + float mCloudAnimationTimer; + BillboardObject* mSun; BillboardObject* mSunGlare; Moon* mMasser; @@ -194,11 +193,6 @@ namespace MWRender Ogre::SceneNode* mAtmosphereDay; Ogre::SceneNode* mAtmosphereNight; - Ogre::MaterialPtr mCloudMaterial; - Ogre::MaterialPtr mAtmosphereMaterial; - - Ogre::MaterialPtr mStarsMaterials[7]; - Ogre::HighLevelGpuProgramPtr mCloudFragmentShader; // remember some settings so we don't have to apply them again if they didnt change diff --git a/extern/shiny b/extern/shiny index 34e952bf3..7485a15c2 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 34e952bf3d5e09adfd3e5c0f6462d612e193e447 +Subproject commit 7485a15c26f129084a1c264fa1a98dc2de86f298 diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader index 831ba1138..295fa9376 100644 --- a/files/materials/atmosphere.shader +++ b/files/materials/atmosphere.shader @@ -1,11 +1,11 @@ #include "core.h" -#define MRT @shPropertyBool(mrt_output) +#define MRT @shGlobalSettingBool(mrt_output) #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM - shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) shColourInput(float4) shOutput(float4, colourPassthrough) @@ -23,7 +23,7 @@ #if MRT shDeclareMrtOutput(1) #endif - shUniform(float4 atmosphereColour) @shSharedParameter(atmosphereColour) + shUniform(float4, atmosphereColour) @shSharedParameter(atmosphereColour) SH_START_PROGRAM { diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index 1a80a27dd..7677ecd95 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -1,11 +1,11 @@ #include "core.h" -#define MRT @shPropertyBool(mrt_output) +#define MRT @shGlobalSettingBool(mrt_output) #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM - shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) shInput(float2, uv0) shOutput(float2, UV) shColourInput(float4) @@ -13,7 +13,7 @@ SH_START_PROGRAM { - + colourPassthrough = colour; shOutputPosition = shMatrixMult(wvp, shInputPosition); UV = uv0; } @@ -21,17 +21,28 @@ #else SH_BEGIN_PROGRAM - shSampler2D(diffuseMap) shInput(float2, UV) + shInput(float4, colourPassthrough) #if MRT shDeclareMrtOutput(1) #endif - shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) - + + shSampler2D(diffuseMap1) + shSampler2D(diffuseMap2) + + shUniform(float, cloudBlendFactor) @shSharedParameter(cloudBlendFactor) + shUniform(float, cloudAnimationTimer) @shSharedParameter(cloudAnimationTimer) + shUniform(float, cloudOpacity) @shSharedParameter(cloudOpacity) + shUniform(float3, cloudColour) @shSharedParameter(cloudColour) + SH_START_PROGRAM { - shOutputColour(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); + // Scroll in y direction + float2 scrolledUV = UV + float2(0,1) * cloudAnimationTimer * 0.003; + + float4 albedo = shSample(diffuseMap1, scrolledUV) * (1-cloudBlendFactor) + shSample(diffuseMap2, scrolledUV) * cloudBlendFactor; + + shOutputColour(0) = colourPassthrough * float4(cloudColour, 1) * albedo * float4(1,1,1, cloudOpacity); #if MRT shOutputColour(1) = float4(1,1,1,1); diff --git a/files/materials/core.h b/files/materials/core.h index b5d784dae..34095f93d 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -9,7 +9,7 @@ #define shMatrixMult(m, v) mul(m, v) - #define shUniform(s) , uniform s + #define shUniform(type, name) , uniform type name #define shInput(type, name) , in type name : TEXCOORD@shCounter(1) #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2) @@ -59,13 +59,13 @@ #define float4 vec4 #define int2 ivec2 #define int3 ivec3 - #define int4 ivec4 + #define int4 ivec4/ #define shTexture2D sampler2D #define shSample(tex, coord) texture(tex, coord) #define shLerp(a, b, t) mix(a, b, t) #define shSaturate(a) clamp(a, 0.0, 1.0) - #define shUniform(s) uniform s; + #define shUniform(type, name) uniform type name; #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name) diff --git a/files/materials/objects.shader b/files/materials/objects.shader index c80a592a3..3b4992f5b 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -21,7 +21,7 @@ // ------------------------------------- VERTEX --------------------------------------- SH_BEGIN_PROGRAM - shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) shInput(float2, uv0) shOutput(float2, UV) shNormalInput(float4) @@ -41,16 +41,16 @@ #if SHADOWS shOutput(float4, lightSpacePos0) - shUniform(float4x4 texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) - shUniform(float4x4 worldMatrix) @shAutoConstant(worldMatrix, world_matrix) + shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) + shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) #endif #if SHADOWS_PSSM @shForeach(3) shOutput(float4, lightSpacePos@shIterator) - shUniform(float4x4 texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator) + shUniform(float4x4, texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator) @shEndForeach - shUniform(float4x4 worldMatrix) @shAutoConstant(worldMatrix, world_matrix) + shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) #endif SH_START_PROGRAM { @@ -99,27 +99,27 @@ #endif #if MRT - shUniform(float far) @shAutoConstant(far, far_clip_distance) + shUniform(float, far) @shAutoConstant(far, far_clip_distance) #endif #if LIGHTING shInput(float3, normalPassthrough) shInput(float3, objSpacePositionPassthrough) - shUniform(float4 lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - //shUniform(float passIteration) @shAutoConstant(passIteration, pass_iteration_number) - shUniform(float4 materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) - shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) + shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) + //shUniform(float, passIteration) @shAutoConstant(passIteration, pass_iteration_number) + shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) + shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) + shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) @shForeach(8) - shUniform(float4 lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) - shUniform(float4 lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) - shUniform(float4 lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) + shUniform(float4, lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) + shUniform(float4, lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) + shUniform(float4, lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) @shEndForeach #endif #if FOG - shUniform(float3 fogColor) @shAutoConstant(fogColor, fog_colour) - shUniform(float4 fogParams) @shAutoConstant(fogParams, fog_params) + shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) + shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) #endif #ifdef HAS_VERTEXCOLOR @@ -129,19 +129,19 @@ #if SHADOWS shInput(float4, lightSpacePos0) shSampler2D(shadowMap0) - shUniform(float2 invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, 1) + shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, 1) #endif #if SHADOWS_PSSM @shForeach(3) shInput(float4, lightSpacePos@shIterator) shSampler2D(shadowMap@shIterator) - shUniform(float2 invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(1)) + shUniform(float2, invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(1)) @shEndForeach - shUniform(float4 pssmSplitPoints) @shSharedParameter(pssmSplitPoints) + shUniform(float3, pssmSplitPoints) @shSharedParameter(pssmSplitPoints) #endif #if SHADOWS || SHADOWS_PSSM - shUniform(float4 shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) + shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) #endif SH_START_PROGRAM { diff --git a/files/materials/shadowcaster.shader b/files/materials/shadowcaster.shader index 500207778..a5551509d 100644 --- a/files/materials/shadowcaster.shader +++ b/files/materials/shadowcaster.shader @@ -9,7 +9,7 @@ shInput(float2, uv0) shOutput(float2, UV) #endif - shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) shOutput(float2, depth) SH_START_PROGRAM { diff --git a/files/materials/shadows.h b/files/materials/shadows.h index a3518b976..9127d28f7 100644 --- a/files/materials/shadows.h +++ b/files/materials/shadows.h @@ -1,6 +1,4 @@ - - float depthShadowPCF (shTexture2D shadowMap, float4 shadowMapPos, float2 offset) { shadowMapPos /= shadowMapPos.w; @@ -31,7 +29,7 @@ float pssmDepthShadow ( shTexture2D shadowMap2, float depth, - float4 pssmSplitPoints) + float3 pssmSplitPoints) { float shadow; diff --git a/files/materials/sky.mat b/files/materials/sky.mat index 6465476dd..3f52966e8 100644 --- a/files/materials/sky.mat +++ b/files/materials/sky.mat @@ -1,7 +1,6 @@ material openmw_moon { allow_fixed_function false - mrt_output true pass { vertex_program moon_vertex @@ -9,11 +8,6 @@ material openmw_moon polygon_mode_overrideable off - shader_properties - { - mrt_output $mrt_output - } - texture_unit diffuseMap { texture $diffuseMap @@ -25,7 +19,6 @@ material openmw_moon material openmw_clouds { allow_fixed_function false - mrt_output true pass { vertex_program clouds_vertex @@ -36,11 +29,6 @@ material openmw_clouds scene_blend alpha_blend depth_write off - shader_properties - { - mrt_output $mrt_output - } - // second diffuse map is used for weather transitions texture_unit diffuseMap1 { @@ -58,7 +46,6 @@ material openmw_clouds material openmw_atmosphere { allow_fixed_function false - mrt_output true pass { vertex_program atmosphere_vertex @@ -68,33 +55,26 @@ material openmw_atmosphere scene_blend alpha_blend depth_write off - - shader_properties - { - mrt_output $mrt_output - } } } material openmw_stars { allow_fixed_function false - mrt_output true pass { vertex_program stars_vertex fragment_program stars_fragment polygon_mode_overrideable off - - shader_properties - { - mrt_output $mrt_output - } + + depth_check off + depth_write off + scene_blend alpha_blend texture_unit diffuseMap { - diffuseMap $diffuseMap + direct_texture $texture } } } @@ -103,7 +83,6 @@ material openmw_stars material openmw_sun { allow_fixed_function false - mrt_output true pass { vertex_program sun_vertex @@ -111,14 +90,9 @@ material openmw_sun polygon_mode_overrideable off - shader_properties - { - mrt_output $mrt_output - } - - texture unit diffuseMap + texture_unit diffuseMap { - diffuseMap $diffuseMap + direct_texture $texture } } } diff --git a/files/materials/sun.shader b/files/materials/sun.shader index 03ec7665c..4c7926756 100644 --- a/files/materials/sun.shader +++ b/files/materials/sun.shader @@ -1,12 +1,12 @@ #include "core.h" -#define MRT @shPropertyBool(mrt_output) +#define MRT @shGlobalSettingBool(mrt_output) #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM - shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) shInput(float2, uv0) shOutput(float2, UV) @@ -24,12 +24,14 @@ #if MRT shDeclareMrtOutput(1) #endif - shUniform(float4 materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - shUniform(float4 materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) + shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) + shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) SH_START_PROGRAM { shOutputColour(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); + + shOutputColour(0) = shSample(diffuseMap, UV); #if MRT shOutputColour(1) = float4(1,1,1,1); From cd6e9986b6268baa01161c373a921c9a2577f217 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jul 2012 09:09:38 +0200 Subject: [PATCH 068/298] forgot to add files --- files/materials/stars.shader | 46 +++++++++++++++++++++++++++++++++ files/materials/stars.shaderset | 15 +++++++++++ 2 files changed, 61 insertions(+) create mode 100644 files/materials/stars.shader create mode 100644 files/materials/stars.shaderset diff --git a/files/materials/stars.shader b/files/materials/stars.shader new file mode 100644 index 000000000..75592fc72 --- /dev/null +++ b/files/materials/stars.shader @@ -0,0 +1,46 @@ +#include "core.h" + +#define MRT @shGlobalSettingBool(mrt_output) + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) + + shInput(float2, uv0) + shOutput(float2, UV) + shOutput(float, fade) + + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + + fade = (shInputPosition.z > 50) ? 1 : 0; + } + +#else + + SH_BEGIN_PROGRAM +#if MRT + shDeclareMrtOutput(1) +#endif + + shInput(float2, UV) + shInput(float, fade) + + shSampler2D(diffuseMap) + shUniform(float, nightFade) @shSharedParameter(nightFade) + + + SH_START_PROGRAM + { + shOutputColour(0) = shSample(diffuseMap, UV) * float4(1,1,1, nightFade * fade); + + +#if MRT + shOutputColour(1) = float4(1,1,1,1); +#endif + } + +#endif diff --git a/files/materials/stars.shaderset b/files/materials/stars.shaderset new file mode 100644 index 000000000..0f8803450 --- /dev/null +++ b/files/materials/stars.shaderset @@ -0,0 +1,15 @@ +shader_set stars_vertex +{ + source stars.shader + type vertex + profiles_cg vs_2_0 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set stars_fragment +{ + source stars.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_hlsl ps_2_0 +} From 8e324c90dcecf5c5655f89aaa37e693587bd4950 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 11 Jul 2012 06:37:17 -0700 Subject: [PATCH 069/298] Constify some RecordPtrT methods --- components/nif/record_ptr.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/nif/record_ptr.hpp b/components/nif/record_ptr.hpp index 755094147..7e6a43f53 100644 --- a/components/nif/record_ptr.hpp +++ b/components/nif/record_ptr.hpp @@ -71,18 +71,21 @@ public: } /// Look up the actual object from the index - X* getPtr() + X* getPtr() const { assert(ptr != NULL); return ptr; } - X& get() { return *getPtr(); } + X& get() const + { return *getPtr(); } /// Syntactic sugar - X* operator->() { return getPtr(); } + X* operator->() const + { return getPtr(); } /// Pointers are allowed to be empty - bool empty() { return ptr == NULL; } + bool empty() const + { return ptr == NULL; } }; /** A list of references to other records. These are read as a list, From 7e8c146de629b387d71a9eb5406f9e43a4640c31 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 11 Jul 2012 06:39:03 -0700 Subject: [PATCH 070/298] Constify some RecordListT methods --- components/nif/record_ptr.hpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/components/nif/record_ptr.hpp b/components/nif/record_ptr.hpp index 7e6a43f53..8f8fc244a 100644 --- a/components/nif/record_ptr.hpp +++ b/components/nif/record_ptr.hpp @@ -114,17 +114,13 @@ public: list[i].post(nif); } - X& operator[](size_t index) - { - return list.at(index).get(); - } + X& operator[](size_t index) const + { return list.at(index).get(); } - bool has(size_t index) - { - return !list.at(index).empty(); - } + bool has(size_t index) const + { return !list.at(index).empty(); } - size_t length() + size_t length() const { return list.size(); } }; From 645b507ba06a34be6aaa9a75eb1a6b2cc409da48 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 12 Jul 2012 05:37:56 -0700 Subject: [PATCH 071/298] Return a reference to the RecordPtr from operator[] for consistency RecordListT is supposed to be a list of RecordPtrT objects. --- components/nif/nif_file.cpp | 4 ++-- components/nif/node.hpp | 4 ++-- components/nif/record_ptr.hpp | 7 ++----- components/nifbullet/bullet_nif_loader.cpp | 12 ++++++------ components/nifogre/ogre_nif_loader.cpp | 18 ++++++++---------- 5 files changed, 20 insertions(+), 25 deletions(-) diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 36badbf0d..231349302 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -205,8 +205,8 @@ void NiSkinInstance::post(NIFFile *nif) for(size_t i=0; ifail("Oops: Missing bone! Don't know how to handle this."); - bones[i].makeBone(i, data->bones[i]); + bones[i]->makeBone(i, data->bones[i]); } } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 64ef1e3e9..e7ba03276 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -151,8 +151,8 @@ struct NiNode : Node for(size_t i = 0;i < children.length();i++) { // Why would a unique list of children contain empty refs? - if(children.has(i)) - children[i].parent = this; + if(!children[i].empty()) + children[i]->parent = this; } } }; diff --git a/components/nif/record_ptr.hpp b/components/nif/record_ptr.hpp index 8f8fc244a..ef5bb1dee 100644 --- a/components/nif/record_ptr.hpp +++ b/components/nif/record_ptr.hpp @@ -114,11 +114,8 @@ public: list[i].post(nif); } - X& operator[](size_t index) const - { return list.at(index).get(); } - - bool has(size_t index) const - { return !list.at(index).empty(); } + const Ptr& operator[](size_t index) const + { return list.at(index); } size_t length() const { return list.size(); } diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index 4105c4c79..bfcaf36e5 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -138,9 +138,9 @@ bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node* node) int n = list.length(); for (int i=0; itrafo,hasCollisionNode,isCollisionNode,raycastingOnly); + handleNode(list[i].getPtr(), flags,&node->trafo,hasCollisionNode,isCollisionNode,raycastingOnly); } } } @@ -239,8 +239,8 @@ void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, int n = list.length(); for (int i=0; itrafo, hasCollisionNode,true,raycastingOnly); + if (!list[i].empty()) + handleNode(list[i].getPtr(), flags,&node->trafo, hasCollisionNode,true,raycastingOnly); } } } diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index aca1966b9..643d7acf8 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -649,10 +649,9 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou for (int i=0; irecType == RC_NiTexturingProperty) t = static_cast(pr); else if (pr->recType == RC_NiMaterialProperty) @@ -803,13 +802,13 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou { if(mSkel.isNull()) { - std::cout << "No skeleton for :" << shape->skin->bones[boneIndex].name << std::endl; + std::cout << "No skeleton for :" << shape->skin->bones[boneIndex]->name << std::endl; break; } //get the bone from bones array of skindata - if(!mSkel->hasBone(shape->skin->bones[boneIndex].name)) + if(!mSkel->hasBone(shape->skin->bones[boneIndex]->name)) std::cout << "We don't have this bone"; - bonePtr = mSkel->getBone(shape->skin->bones[boneIndex].name); + bonePtr = mSkel->getBone(shape->skin->bones[boneIndex]->name); // final_vector = old_vector + old_rotation*new_vector*old_scale @@ -817,7 +816,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou Nif::NiSkinData::BoneInfoCopy boneinfocopy; boneinfocopy.trafo.rotation = it->trafo.rotation; boneinfocopy.trafo.trans = it->trafo.trans; - boneinfocopy.bonename = shape->skin->bones[boneIndex].name; + boneinfocopy.bonename = shape->skin->bones[boneIndex]->name; boneinfocopy.bonehandle = bonePtr->getHandle(); copy.boneinfo.push_back(boneinfocopy); for (unsigned int i=0; iweights.size(); i++) @@ -1138,9 +1137,8 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, int n = list.length(); for (int i = 0; itrafo, bounds, bone, boneSequence); + if (!list[i].empty()) + handleNode(list[i].getPtr(), flags, &node->trafo, bounds, bone, boneSequence); } } else if (node->recType == RC_NiTriShape && bNiTri) From b7b9f1133305ad05450b3c8e5ed83c523075ebb7 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 12 Jul 2012 06:47:38 -0700 Subject: [PATCH 072/298] Add generic classes to help deal with NIF keys --- components/nif/nif_file.hpp | 88 +++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index a21882c6d..9e0f6972b 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include @@ -98,6 +100,12 @@ public: throw std::runtime_error(err); } + void warn(const std::string &msg) + { + std::cerr<< "NIFFile Warning: "< +struct KeyT { + float mTime; + T mValue; + T mForwardValue; // Only for Quadratic interpolation + T mBackwardValue; // Only for Quadratic interpolation + float mTension; // Only for TBC interpolation + float mBias; // Only for TBC interpolation + float mContinuity; // Only for TBC interpolation +}; +typedef KeyT FloatKey; +typedef KeyT Vector3Key; +typedef KeyT QuaternionKey; + +template +struct KeyListT { + typedef std::vector< KeyT > VecType; + + static const int sLinearInterpolation = 1; + static const int sQuadraticInterpolation = 2; + static const int sTBCInterpolation = 3; + + int mInterpolationType; + VecType mKeys; + + void read(NIFFile *nif) + { + size_t count = nif->getInt(); + if(count == 0) return; + + mInterpolationType = nif->getInt(); + mKeys.resize(count); + if(mInterpolationType == sLinearInterpolation) + { + for(size_t i = 0;i < count;i++) + { + KeyT &key = mKeys[i]; + key.mTime = nif->getFloat(); + key.mValue = (nif->*getValue)(); + } + } + else if(mInterpolationType == sQuadraticInterpolation) + { + for(size_t i = 0;i < count;i++) + { + KeyT &key = mKeys[i]; + key.mTime = nif->getFloat(); + key.mValue = (nif->*getValue)(); + key.mForwardValue = (nif->*getValue)(); + key.mBackwardValue = (nif->*getValue)(); + } + } + else if(mInterpolationType == sTBCInterpolation) + { + for(size_t i = 0;i < count;i++) + { + KeyT &key = mKeys[i]; + key.mTime = nif->getFloat(); + key.mValue = (nif->*getValue)(); + key.mTension = nif->getFloat(); + key.mBias = nif->getFloat(); + key.mContinuity = nif->getFloat(); + } + } + else + nif->warn("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); + } +}; +typedef KeyListT FloatKeyList; +typedef KeyListT Vector3KeyList; +typedef KeyListT QuaternionKeyList; + } // Namespace #endif From b292665de90d444d69ea9af0750cb117db233859 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 12 Jul 2012 08:00:26 -0700 Subject: [PATCH 073/298] Use key lists to store some NIF data types --- components/nif/data.hpp | 47 +++++++++-------------------------------- 1 file changed, 10 insertions(+), 37 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index ad670bc5e..552e53b68 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -202,61 +202,34 @@ public: class NiPosData : public Record { public: + Vector3KeyList mKeyList; + void read(NIFFile *nif) { - int count = nif->getInt(); - int type = nif->getInt(); - if(type != 1 && type != 2) - nif->fail("Cannot handle NiPosData type"); - - // TODO: Could make structs of these. Seems to be identical to - // translation in NiKeyframeData. - for(int i=0; igetFloat(); - nif->getVector3(); // This isn't really shared between type 1 - // and type 2, most likely - if(type == 2) - { - nif->getVector3(); - nif->getVector3(); - } - } + mKeyList.read(nif); } }; class NiUVData : public Record { public: + FloatKeyList mKeyList[4]; + void read(NIFFile *nif) { - // TODO: This is claimed to be a "float animation key", which is - // also used in FloatData and KeyframeData. We could probably - // reuse and refactor a lot of this if we actually use it at some - // point. - for(int i=0; i<2; i++) - { - int count = nif->getInt(); - if(count) - { - nif->getInt(); // always 2 - nif->skip(count * (sizeof(float) + 3*sizeof(float))); // Really one time float + one vector - } - } - // Always 0 - nif->getInt(); - nif->getInt(); + for(int i = 0;i < 4;i++) + mKeyList[i].read(nif); } }; class NiFloatData : public Record { public: + FloatKeyList mKeyList; + void read(NIFFile *nif) { - int count = nif->getInt(); - nif->getInt(); // always 2 - nif->skip(count * (sizeof(float) + 3*sizeof(float))); // Really one time float + one vector + mKeyList.read(nif); } }; From 86b37c6c11b59ff7a9c6bf7c214b12ef9c53eb66 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 12 Jul 2012 11:21:52 -0700 Subject: [PATCH 074/298] Move the velocity out of the transformation object --- components/nif/nif_file.hpp | 1 - components/nif/nif_types.hpp | 3 +-- components/nif/node.hpp | 2 ++ components/nifbullet/bullet_nif_loader.cpp | 1 - components/nifogre/ogre_nif_loader.cpp | 1 - 5 files changed, 3 insertions(+), 5 deletions(-) diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 9e0f6972b..fa152f14d 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -179,7 +179,6 @@ public: t.pos = getVector3(); t.rotation = getMatrix3(); t.scale = getFloat(); - t.velocity = getVector3(); return t; } diff --git a/components/nif/nif_types.hpp b/components/nif/nif_types.hpp index f364cd2d4..a5fb61361 100644 --- a/components/nif/nif_types.hpp +++ b/components/nif/nif_types.hpp @@ -37,12 +37,11 @@ struct Transformation Ogre::Vector3 pos; Ogre::Matrix3 rotation; float scale; - Ogre::Vector3 velocity; static const Transformation& getIdentity() { static const Transformation identity = { - Ogre::Vector3::ZERO, Ogre::Matrix3::IDENTITY, 1.0f, Ogre::Vector3::ZERO + Ogre::Vector3::ZERO, Ogre::Matrix3::IDENTITY, 1.0f }; return identity; } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index e7ba03276..293793009 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -43,6 +43,7 @@ public: // Node flags. Interpretation depends somewhat on the type of node. int flags; Transformation trafo; + Ogre::Vector3 velocity; // Unused? Might be a run-time game state PropertyList props; // Bounding box info @@ -57,6 +58,7 @@ public: flags = nif->getUShort(); trafo = nif->getTrafo(); + velocity = nif->getVector3(); props.read(nif); hasBounds = !!nif->getInt(); diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index bfcaf36e5..c3b34e039 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -204,7 +204,6 @@ void ManualBulletShapeLoader::handleNode(Nif::Node *node, int flags, // For both position and rotation we have that: // final_vector = old_vector + old_rotation*new_vector*old_scale final.pos = trafo->pos + trafo->rotation*final.pos*trafo->scale; - final.velocity = trafo->velocity + trafo->rotation*final.velocity*trafo->scale; // Merge the rotations together final.rotation = trafo->rotation * final.rotation; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 643d7acf8..36c5d3b1b 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -1121,7 +1121,6 @@ void NIFLoader::handleNode(Nif::Node *node, int flags, // For both position and rotation we have that: // final_vector = old_vector + old_rotation*new_vector*old_scale final.pos = trafo->pos + trafo->rotation*final.pos*trafo->scale; - final.velocity = trafo->velocity + trafo->rotation*final.velocity*trafo->scale; // Merge the rotations together final.rotation = trafo->rotation * final.rotation; From 3f11b6b1ae961cc745a8624904f1f728b7aea246 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 12 Jul 2012 12:01:11 -0700 Subject: [PATCH 075/298] Cleanup a couple unneeded misc component references --- components/nif/record.hpp | 2 +- components/nifogre/ogre_nif_loader.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 84f253eb8..5c4141fa9 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -24,7 +24,7 @@ #ifndef _NIF_RECORD_H_ #define _NIF_RECORD_H_ -#include +#include namespace Nif { diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 36c5d3b1b..014384dd4 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -40,7 +40,6 @@ typedef unsigned char ubyte; using namespace std; using namespace Nif; -using namespace Misc; using namespace NifOgre; NIFLoader& NIFLoader::getSingleton() From 9995dff943592d85b2d3b9e2bbeaa75a74807dfd Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 12 Jul 2012 14:33:50 -0700 Subject: [PATCH 076/298] Use a key list for NiColorData --- components/nif/data.hpp | 12 ++---------- components/nif/nif_file.hpp | 2 ++ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 552e53b68..b7e2f172f 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -275,19 +275,11 @@ public: class NiColorData : public Record { public: - struct ColorData - { - float time; - Ogre::Vector4 rgba; - }; + Vector4KeyList mKeyList; void read(NIFFile *nif) { - int count = nif->getInt(); - nif->getInt(); // always 1 - - // Skip the data - nif->skip(count * 5*sizeof(float)); + mKeyList.read(nif); } }; diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index fa152f14d..c33790742 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -223,6 +223,7 @@ struct KeyT { }; typedef KeyT FloatKey; typedef KeyT Vector3Key; +typedef KeyT Vector4Key; typedef KeyT QuaternionKey; template @@ -281,6 +282,7 @@ struct KeyListT { }; typedef KeyListT FloatKeyList; typedef KeyListT Vector3KeyList; +typedef KeyListT Vector4KeyList; typedef KeyListT QuaternionKeyList; } // Namespace From 386ac56bdab827ef604362787d232d8278ff1d7e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 12 Jul 2012 20:12:18 -0700 Subject: [PATCH 077/298] Remove the NIF loader and code to manually transform the vertices This currently breaks just about everything. They should come back as it's all reimplemented, though. --- apps/openmw/engine.cpp | 7 +- apps/openmw/mwrender/animation.cpp | 594 +++------ apps/openmw/mwrender/animation.hpp | 53 +- apps/openmw/mwrender/creatureanimation.cpp | 56 +- apps/openmw/mwrender/npcanimation.cpp | 1266 ++++++++------------ apps/openmw/mwrender/npcanimation.hpp | 94 +- components/bsa/bsa_archive.cpp | 58 +- components/nif/data.hpp | 20 - components/nif/node.hpp | 36 - components/nifbullet/bullet_nif_loader.cpp | 2 +- components/nifogre/ogre_nif_loader.cpp | 1112 +---------------- components/nifogre/ogre_nif_loader.hpp | 98 +- libs/openengine/bullet/physic.cpp | 2 +- 13 files changed, 828 insertions(+), 2570 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 45b4ab514..fded32560 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -75,11 +75,8 @@ void OMW::Engine::executeLocalScripts() localScripts.setIgnore (MWWorld::Ptr()); } -void OMW::Engine::setAnimationVerbose(bool animverbose){ - if(animverbose){ - NifOgre::NIFLoader::getSingletonPtr()->setOutputAnimFiles(true); - NifOgre::NIFLoader::getSingletonPtr()->setVerbosePath(mCfgMgr.getLogPath().string()); - } +void OMW::Engine::setAnimationVerbose(bool animverbose) +{ } bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f0a6ab683..7e50706f9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -6,498 +6,236 @@ #include #include -namespace MWRender{ - std::map Animation::mUniqueIDs; - - Animation::Animation(OEngine::Render::OgreRenderer& _rend) - : insert(NULL) - , mRend(_rend) - , vecRotPos() - , time(0.0f) - , startTime(0.0f) - , stopTime(0.0f) - , animate(0) - , rindexI() - , tindexI() - , shapeNumber(0) - , shapeIndexI() - , shapes(NULL) - , transformations(NULL) - , textmappings(NULL) - , base(NULL) - { - } - - Animation::~Animation() - { - } - - std::string Animation::getUniqueID(std::string mesh){ - int counter; - std::string copy = mesh; - std::transform(copy.begin(), copy.end(), copy.begin(), ::tolower); - if(mUniqueIDs.find(copy) == mUniqueIDs.end()){ - counter = mUniqueIDs[copy] = 0; - } - else{ - mUniqueIDs[copy] = mUniqueIDs[copy] + 1; - counter = mUniqueIDs[copy]; - } - - std::stringstream out; - if(counter > 99 && counter < 1000) - out << "0"; - else if(counter > 9) - out << "00"; - else - out << "000"; - out << counter; - return out.str(); +namespace MWRender { + +std::map Animation::mUniqueIDs; + +Animation::Animation(OEngine::Render::OgreRenderer& _rend) + : insert(NULL) + , mRend(_rend) + , time(0.0f) + , startTime(0.0f) + , stopTime(0.0f) + , animate(0) + , rindexI() + , tindexI() + , shapeNumber(0) + , shapeIndexI() + , transformations(NULL) + , textmappings(NULL) + , base(NULL) +{ } - void Animation::startScript(std::string groupname, int mode, int loops){ - //If groupname is recognized set animate to true - //Set the start time and stop time - //How many times to loop - if(groupname == "all"){ - animate = loops; - time = startTime; - } - else if(textmappings){ - std::string startName = groupname + ": loop start"; - std::string stopName = groupname + ": loop stop"; +Animation::~Animation() +{ +} - bool first = false; +void Animation::startScript(std::string groupname, int mode, int loops) +{ + //If groupname is recognized set animate to true + //Set the start time and stop time + //How many times to loop + if(groupname == "all") + { + animate = loops; + time = startTime; + } + else if(textmappings) + { + std::string startName = groupname + ": loop start"; + std::string stopName = groupname + ": loop stop"; - if(loops > 1){ - startName = groupname + ": loop start"; - stopName = groupname + ": loop stop"; + bool first = false; - for(std::map::iterator iter = textmappings->begin(); iter != textmappings->end(); iter++){ + if(loops > 1) + { + startName = groupname + ": loop start"; + stopName = groupname + ": loop stop"; + for(std::map::iterator iter = textmappings->begin(); iter != textmappings->end(); iter++) + { std::string current = iter->first.substr(0, startName.size()); std::transform(current.begin(), current.end(), current.begin(), ::tolower); std::string current2 = iter->first.substr(0, stopName.size()); std::transform(current2.begin(), current2.end(), current2.begin(), ::tolower); - if(current == startName){ + if(current == startName) + { startTime = iter->second; - animate = loops; - time = startTime; - first = true; + animate = loops; + time = startTime; + first = true; } - if(current2 == stopName){ + if(current2 == stopName) + { stopTime = iter->second; if(first) break; } - } } - if(!first){ - startName = groupname + ": start"; - stopName = groupname + ": stop"; + } - for(std::map::iterator iter = textmappings->begin(); iter != textmappings->end(); iter++){ + if(!first) + { + startName = groupname + ": start"; + stopName = groupname + ": stop"; + for(std::map::iterator iter = textmappings->begin(); iter != textmappings->end(); iter++) + { std::string current = iter->first.substr(0, startName.size()); std::transform(current.begin(), current.end(), current.begin(), ::tolower); std::string current2 = iter->first.substr(0, stopName.size()); std::transform(current2.begin(), current2.end(), current2.begin(), ::tolower); - if(current == startName){ + if(current == startName) + { startTime = iter->second; - animate = loops; - time = startTime; - first = true; + animate = loops; + time = startTime; + first = true; } - if(current2 == stopName){ + if(current2 == stopName) + { stopTime = iter->second; if(first) break; } } - } - } - } - void Animation::stopScript(){ - animate = 0; } - void Animation::handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel){ - shapeNumber = 0; - - if (allshapes == NULL || creaturemodel == NULL || skel == NULL) - { - return; - } - - std::vector::iterator allshapesiter; - for(allshapesiter = allshapes->begin(); allshapesiter != allshapes->end(); allshapesiter++) - - { - //std::map vecPosRot; - - Nif::NiTriShapeCopy& copy = *allshapesiter; - std::vector* allvertices = ©.vertices; - - - - //std::set vertices; - //std::set normals; - //std::vector boneinfovector = copy.boneinfo; - std::map >* verticesToChange = ©.vertsToWeights; - - //std::cout << "Name " << copy.sname << "\n"; - Ogre::HardwareVertexBufferSharedPtr vbuf = creaturemodel->getMesh()->getSubMesh(copy.sname)->vertexData->vertexBufferBinding->getBuffer(0); - Ogre::Real* pReal = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_NORMAL)); - - - std::vector initialVertices = copy.morph.getInitialVertices(); - //Each shape has multiple indices - if(initialVertices.size() ) - { - - if(copy.vertices.size() == initialVertices.size()) - { - //Create if it doesn't already exist - if(shapeIndexI.size() == static_cast (shapeNumber)) - { - std::vector vec; - shapeIndexI.push_back(vec); - } - if(time >= copy.morph.getStartTime() && time <= copy.morph.getStopTime()){ - float x; - for (unsigned int i = 0; i < copy.morph.getAdditionalVertices().size(); i++){ - int j = 0; - if(shapeIndexI[shapeNumber].size() <= i) - shapeIndexI[shapeNumber].push_back(0); - - - if(timeIndex(time,copy.morph.getRelevantTimes()[i],(shapeIndexI[shapeNumber])[i], j, x)){ - int indexI = (shapeIndexI[shapeNumber])[i]; - std::vector relevantData = (copy.morph.getRelevantData()[i]); - float v1 = relevantData[indexI].x; - float v2 = relevantData[j].x; - float t = v1 + (v2 - v1) * x; - if ( t < 0 ) t = 0; - if ( t > 1 ) t = 1; - if( t != 0 && initialVertices.size() == copy.morph.getAdditionalVertices()[i].size()) - { - for (unsigned int v = 0; v < initialVertices.size(); v++){ - initialVertices[v] += ((copy.morph.getAdditionalVertices()[i])[v]) * t; - } - } - - } - - - - } - - allvertices = &initialVertices; - } - shapeNumber++; - } - } - - - if(verticesToChange->size() > 0){ - - for(std::map >::iterator iter = verticesToChange->begin(); - iter != verticesToChange->end(); iter++) - { - std::vector inds = iter->second; - int verIndex = iter->first; - Ogre::Vector3 currentVertex = (*allvertices)[verIndex]; - Nif::NiSkinData::BoneInfoCopy* boneinfocopy = &(allshapesiter->boneinfo[inds[0].boneinfocopyindex]); - Ogre::Bone *bonePtr = 0; - - - - Ogre::Vector3 vecPos; - Ogre::Quaternion vecRot; - std::map::iterator result = vecRotPos.find(boneinfocopy); - - if(result == vecRotPos.end()){ - bonePtr = skel->getBone(boneinfocopy->bonename); - - vecPos = bonePtr->_getDerivedPosition() + bonePtr->_getDerivedOrientation() * boneinfocopy->trafo.trans; - vecRot = bonePtr->_getDerivedOrientation() * boneinfocopy->trafo.rotation; - - - PosAndRot both; - both.vecPos = vecPos; - both.vecRot = vecRot; - vecRotPos[boneinfocopy] = both; - - } - else{ - PosAndRot both = result->second; - vecPos = both.vecPos; - vecRot = both.vecRot; - } - - Ogre::Vector3 absVertPos = (vecPos + vecRot * currentVertex) * inds[0].weight; - - - - for(std::size_t i = 1; i < inds.size(); i++){ - boneinfocopy = &(allshapesiter->boneinfo[inds[i].boneinfocopyindex]); - result = vecRotPos.find(boneinfocopy); - - - if(result == vecRotPos.end()){ - bonePtr = skel->getBone(boneinfocopy->bonename); - vecPos = bonePtr->_getDerivedPosition() + bonePtr->_getDerivedOrientation() * boneinfocopy->trafo.trans; - vecRot = bonePtr->_getDerivedOrientation() * boneinfocopy->trafo.rotation; - - PosAndRot both; - both.vecPos = vecPos; - both.vecRot = vecRot; - vecRotPos[boneinfocopy] = both; - - } - else{ - PosAndRot both = result->second; - vecPos = both.vecPos; - vecRot = both.vecRot; - } - - - absVertPos += (vecPos + vecRot * currentVertex) * inds[i].weight; - - - } - Ogre::Real* addr = (pReal + 3 * verIndex); - *addr = absVertPos.x; - *(addr+1) = absVertPos.y; - *(addr+2) = absVertPos.z; - - } - - - - - } - else - { - //Ogre::Bone *bonePtr = creaturemodel->getSkeleton()->getBone(copy.bonename); - Ogre::Quaternion shaperot = copy.trafo.rotation; - Ogre::Vector3 shapetrans = copy.trafo.trans; - float shapescale = copy.trafo.scale; - std::vector boneSequence = copy.boneSequence; - - Ogre::Vector3 transmult; - Ogre::Quaternion rotmult; - float scale; - if(boneSequence.size() > 0){ - std::vector::iterator boneSequenceIter = boneSequence.begin(); - if(skel->hasBone(*boneSequenceIter)){ - Ogre::Bone *bonePtr = skel->getBone(*boneSequenceIter); - - - - - transmult = bonePtr->getPosition(); - rotmult = bonePtr->getOrientation(); - scale = bonePtr->getScale().x; - boneSequenceIter++; - - for(; boneSequenceIter != boneSequence.end(); boneSequenceIter++) - { - if(skel->hasBone(*boneSequenceIter)){ - Ogre::Bone *bonePtr = skel->getBone(*boneSequenceIter); - // Computes C = B + AxC*scale - transmult = transmult + rotmult * bonePtr->getPosition(); - rotmult = rotmult * bonePtr->getOrientation(); - scale = scale * bonePtr->getScale().x; - } - //std::cout << "Bone:" << *boneSequenceIter << " "; - } - transmult = transmult + rotmult * shapetrans; - rotmult = rotmult * shaperot; - scale = shapescale * scale; - - //std::cout << "Position: " << transmult << "Rotation: " << rotmult << "\n"; - } - } - else - { - transmult = shapetrans; - rotmult = shaperot; - scale = shapescale; - } - - +} - // Computes C = B + AxC*scale - // final_vector = old_vector + old_rotation*new_vector*old_scale/ +void Animation::stopScript() +{ + animate = 0; +} - for(unsigned int i = 0; i < allvertices->size(); i++){ - Ogre::Vector3 current = transmult + rotmult * (*allvertices)[i]; - Ogre::Real* addr = pReal + i * 3; - *addr = current.x; - *(addr+1) = current.y; - *(addr + 2) = current.z; - }/* - for(int i = 0; i < allnormals.size(); i++){ - Ogre::Vector3 current =rotmult * allnormals[i]; - Ogre::Real* addr = pRealNormal + i * 3; - *addr = current.x; - *(addr+1) = current.y; - *(addr + 2) = current.z; +bool Animation::timeIndex(float time, const std::vector ×, int &i, int &j, float &x) +{ + size_t count; + if((count=times.size()) == 0) + return false; - }*/ + if(time <= times[0]) + { + i = j = 0; + x = 0.0; + return true; + } + if(time >= times[count-1]) + { + i = j = count - 1; + x = 0.0; + return true; + } - } - vbuf->unlock(); + if(i < 0 || (size_t)i >= count) + i = 0; - } + float tI = times[i]; + if(time > tI) + { + j = i + 1; + float tJ; + while(time >= (tJ=times[j])) + { + i = j++; + tI = tJ; + } + x = (time-tI) / (tJ-tI); + return true; + } + if(time < tI) + { + j = i - 1; + float tJ; + while(time <= (tJ=times[j])) + { + i = j--; + tI = tJ; + } + x = (time-tI) / (tJ-tI); + return true; } - bool Animation::timeIndex( float time, const std::vector & times, int & i, int & j, float & x ){ - int count; - if ( (count = times.size()) > 0 ) - { - if ( time <= times[0] ) - { - i = j = 0; - x = 0.0; - return true; - } - if ( time >= times[count - 1] ) - { - i = j = count - 1; - x = 0.0; - return true; - } - - if ( i < 0 || i >= count ) - i = 0; - - float tI = times[i]; - if ( time > tI ) - { - j = i + 1; - float tJ; - while ( time >= ( tJ = times[j]) ) - { - i = j++; - tI = tJ; - } - x = ( time - tI ) / ( tJ - tI ); - return true; - } - else if ( time < tI ) - { - j = i - 1; - float tJ; - while ( time <= ( tJ = times[j] ) ) - { - i = j--; - tI = tJ; - } - x = ( time - tI ) / ( tJ - tI ); - return true; - } - else - { - j = i; - x = 0.0; - return true; - } - } - else - return false; + j = i; + x = 0.0; + return true; } - void Animation::handleAnimationTransforms(){ - - +void Animation::handleAnimationTransforms() +{ Ogre::SkeletonInstance* skel = base->getSkeleton(); - Ogre::Bone* b = skel->getRootBone(); - b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3)); //This is a trick - - skel->_updateTransforms(); - //skel->_notifyManualBonesDirty(); - - base->getAllAnimationStates()->_notifyDirty(); - //base->_updateAnimation(); - //base->_notifyMoved(); - + b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3)); //This is a trick + skel->_updateTransforms(); + //skel->_notifyManualBonesDirty(); + base->getAllAnimationStates()->_notifyDirty(); + //base->_updateAnimation(); + //base->_notifyMoved(); std::vector::iterator iter; int slot = 0; - if(transformations){ - for(iter = transformations->begin(); iter != transformations->end(); iter++){ - if(time < iter->getStartTime() || time < startTime || time > iter->getStopTime()) - { - slot++; - continue; - } - - float x; - float x2; - - const std::vector & quats = iter->getQuat(); - - const std::vector & ttime = iter->gettTime(); - - - const std::vector & rtime = iter->getrTime(); - int rindexJ = rindexI[slot]; - - timeIndex(time, rtime, rindexI[slot], rindexJ, x2); - int tindexJ = tindexI[slot]; - - - const std::vector & translist1 = iter->getTranslist1(); - - timeIndex(time, ttime, tindexI[slot], tindexJ, x); - - Ogre::Vector3 t; - Ogre::Quaternion r; - - bool bTrans = translist1.size() > 0; - - - bool bQuats = quats.size() > 0; - - if(skel->hasBone(iter->getBonename())){ - Ogre::Bone* bone = skel->getBone(iter->getBonename()); - if(bTrans){ - Ogre::Vector3 v1 = translist1[tindexI[slot]]; - Ogre::Vector3 v2 = translist1[tindexJ]; - t = (v1 + (v2 - v1) * x); - bone->setPosition(t); - - } - if(bQuats){ - r = Ogre::Quaternion::Slerp(x2, quats[rindexI[slot]], quats[rindexJ], true); - bone->setOrientation(r); - } + if(transformations) + { + for(iter = transformations->begin(); iter != transformations->end(); iter++) + { + if(time < iter->getStartTime() || time < startTime || time > iter->getStopTime()) + { + slot++; + continue; + } + float x; + float x2; + const std::vector &quats = iter->getQuat(); + const std::vector &ttime = iter->gettTime(); + const std::vector &rtime = iter->getrTime(); + const std::vector &translist1 = iter->getTranslist1(); + int rindexJ = rindexI[slot]; + timeIndex(time, rtime, rindexI[slot], rindexJ, x2); + int tindexJ = tindexI[slot]; + timeIndex(time, ttime, tindexI[slot], tindexJ, x); - } + Ogre::Vector3 t; + Ogre::Quaternion r; + bool bTrans = translist1.size() > 0; + bool bQuats = quats.size() > 0; + if(skel->hasBone(iter->getBonename())) + { + Ogre::Bone* bone = skel->getBone(iter->getBonename()); + if(bTrans) + { + Ogre::Vector3 v1 = translist1[tindexI[slot]]; + Ogre::Vector3 v2 = translist1[tindexJ]; + t = (v1 + (v2 - v1) * x); + bone->setPosition(t); + } + if(bQuats) + { + r = Ogre::Quaternion::Slerp(x2, quats[rindexI[slot]], quats[rindexJ], true); + bone->setOrientation(r); + } + } - slot++; - } - skel->_updateTransforms(); + slot++; + } + skel->_updateTransforms(); base->getAllAnimationStates()->_notifyDirty(); -} + } } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 4ab60cff4..9d5404305 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -12,60 +12,47 @@ -namespace MWRender{ +namespace MWRender { -struct PosAndRot{ +struct PosAndRot { Ogre::Quaternion vecRot; Ogre::Vector3 vecPos; }; -class Animation{ - - protected: +class Animation { +protected: Ogre::SceneNode* insert; OEngine::Render::OgreRenderer &mRend; - std::map vecRotPos; static std::map mUniqueIDs; - - - - float time; - float startTime; - float stopTime; - int animate; - //Represents a rotation index for each bone - std::vectorrindexI; + float startTime; + float stopTime; + int animate; + //Represents a rotation index for each bone + std::vectorrindexI; //Represents a translation index for each bone - std::vectortindexI; - - //Only shapes with morphing data will use a shape number - int shapeNumber; - std::vector > shapeIndexI; - - //Ogre::SkeletonInstance* skel; - std::vector* shapes; //All the NiTriShapeData for a creature - + std::vectortindexI; + //Only shapes with morphing data will use a shape number + int shapeNumber; + std::vector > shapeIndexI; std::vector* transformations; std::map* textmappings; Ogre::Entity* base; - void handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel); void handleAnimationTransforms(); bool timeIndex( float time, const std::vector & times, int & i, int & j, float & x ); - std::string getUniqueID(std::string mesh); - public: - Animation(OEngine::Render::OgreRenderer& _rend); - virtual void runAnimation(float timepassed) = 0; - void startScript(std::string groupname, int mode, int loops); - void stopScript(); +public: + Animation(OEngine::Render::OgreRenderer& _rend); + virtual void runAnimation(float timepassed) = 0; + void startScript(std::string groupname, int mode, int loops); + void stopScript(); - virtual ~Animation(); - + virtual ~Animation(); }; + } #endif diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index e1fa7868c..6bdd58ac3 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -12,20 +12,22 @@ using namespace Ogre; using namespace NifOgre; namespace MWRender{ -CreatureAnimation::~CreatureAnimation(){ - +CreatureAnimation::~CreatureAnimation() +{ } -CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend): Animation(_rend){ + +CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend): Animation(_rend) +{ insert = ptr.getRefData().getBaseNode(); - MWWorld::LiveCellRef *ref = - ptr.get(); + MWWorld::LiveCellRef *ref = ptr.get(); assert (ref->base != NULL); - if(!ref->base->model.empty()){ - const std::string &mesh = "meshes\\" + ref->base->model; - std::string meshNumbered = mesh + getUniqueID(mesh) + ">|"; - NifOgre::NIFLoader::load(meshNumbered); - base = mRend.getScene()->createEntity(meshNumbered); + if(!ref->base->model.empty()) + { + std::string mesh = "meshes\\" + ref->base->model; + + NifOgre::NIFLoader::load(mesh); + base = mRend.getScene()->createEntity(mesh); base->setVisibilityFlags(RV_Actors); bool transparent = false; @@ -48,33 +50,22 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O } base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - std::string meshZero = mesh + "0000>|"; - - if((transformations = (NIFLoader::getSingletonPtr())->getAnim(meshZero))){ - - for(std::size_t init = 0; init < transformations->size(); init++){ - rindexI.push_back(0); - tindexI.push_back(0); - } - stopTime = transformations->begin()->getStopTime(); - startTime = transformations->begin()->getStartTime(); - shapes = (NIFLoader::getSingletonPtr())->getShapes(meshZero); - } - textmappings = NIFLoader::getSingletonPtr()->getTextIndices(meshZero); insert->attachObject(base); } } -void CreatureAnimation::runAnimation(float timepassed){ - vecRotPos.clear(); - if(animate > 0){ - //Add the amount of time passed to time +void CreatureAnimation::runAnimation(float timepassed) +{ + if(animate > 0) + { + //Add the amount of time passed to time - //Handle the animation transforms dependent on time + //Handle the animation transforms dependent on time - //Handle the shapes dependent on animation transforms + //Handle the shapes dependent on animation transforms time += timepassed; - if(time >= stopTime){ + if(time >= stopTime) + { animate--; //std::cout << "Stopping the animation\n"; if(animate == 0) @@ -84,8 +75,7 @@ void CreatureAnimation::runAnimation(float timepassed){ } handleAnimationTransforms(); - handleShapes(shapes, base, base->getSkeleton()); - - } + } } + } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index fa88b7277..fa33d18ff 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -13,13 +13,16 @@ using namespace Ogre; using namespace NifOgre; + namespace MWRender{ -NpcAnimation::~NpcAnimation(){ +NpcAnimation::~NpcAnimation() +{ } -NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv): Animation(_rend), mStateID(-1), inv(_inv), timeToChange(0), +NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv) + : Animation(_rend), mStateID(-1), inv(_inv), timeToChange(0), robe(inv.end()), helmet(inv.end()), shirt(inv.end()), cuirass(inv.end()), greaves(inv.end()), leftpauldron(inv.end()), rightpauldron(inv.end()), @@ -27,98 +30,59 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere leftglove(inv.end()), rightglove(inv.end()), skirtiter(inv.end()), pants(inv.end()), lclavicle(0), - rclavicle(0), - rupperArm(0), - lupperArm(0), - rUpperLeg(0), - lUpperLeg(0), - lForearm(0), - rForearm(0), - lWrist(0), - rWrist(0), - rKnee(0), - lKnee(0), - neck(0), - rAnkle(0), - lAnkle(0), - groin(0), - lfoot(0), - rfoot(0) - { - MWWorld::LiveCellRef *ref = - ptr.get(); - Ogre::Entity* blank = 0; - std::vector* blankshape = 0; - zero = std::make_pair(blank, blankshape); - chest = std::make_pair(blank, blankshape); - tail = std::make_pair(blank, blankshape); - lFreeFoot = std::make_pair(blank, blankshape); - rFreeFoot = std::make_pair(blank, blankshape); - rhand = std::make_pair(blank, blankshape); - lhand = std::make_pair(blank, blankshape); - skirt = std::make_pair(blank, blankshape); - for (int init = 0; init < 27; init++){ - partslots[init] = -1; //each slot is empty - partpriorities[init] = 0; - } - - - //Part selection on last character of the file string - // " Tri Chest - // * Tri Tail - // : Tri Left Foot - // < Tri Right Foot - // > Tri Left Hand - // ? Tri Right Hand - // | Normal - - //Mirroring Parts on second to last character - //suffix == '*' - // vector = Ogre::Vector3(-1,1,1); - // suffix == '?' - // vector = Ogre::Vector3(1,-1,1); - // suffix == '<' - // vector = Ogre::Vector3(1,1,-1); - - - std::string hairID = ref->base->hair; - std::string headID = ref->base->head; - headModel = "meshes\\" + - MWBase::Environment::get().getWorld()->getStore().bodyParts.find(headID)->model; - - hairModel = "meshes\\" + - MWBase::Environment::get().getWorld()->getStore().bodyParts.find(hairID)->model; - npcName = ref->base->name; - - //ESMStore::Races r = - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().races.find(ref->base->race); - - - bodyRaceID = headID.substr(0, headID.find_last_of("head_") - 4); - char secondtolast = bodyRaceID.at(bodyRaceID.length() - 2); - isFemale = tolower(secondtolast) == 'f'; - std::transform(bodyRaceID.begin(), bodyRaceID.end(), bodyRaceID.begin(), ::tolower); - isBeast = bodyRaceID == "b_n_khajiit_m_" || bodyRaceID == "b_n_khajiit_f_" || bodyRaceID == "b_n_argonian_m_" || bodyRaceID == "b_n_argonian_f_"; - - /*std::cout << "Race: " << ref->base->race ; - if(female){ - std::cout << " Sex: Female" << " Height: " << race->data.height.female << "\n"; - } - else{ - std::cout << " Sex: Male" << " Height: " << race->data.height.male << "\n"; - }*/ + rclavicle(0), + rupperArm(0), + lupperArm(0), + rUpperLeg(0), + lUpperLeg(0), + lForearm(0), + rForearm(0), + lWrist(0), + rWrist(0), + rKnee(0), + lKnee(0), + neck(0), + rAnkle(0), + lAnkle(0), + groin(0), + lfoot(0), + rfoot(0) +{ + MWWorld::LiveCellRef *ref = ptr.get(); + + for (int init = 0; init < 27; init++) + { + partslots[init] = -1; //each slot is empty + partpriorities[init] = 0; + } + + const ESMS::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Race *race = store.races.find(ref->base->race); + std::string hairID = ref->base->hair; + std::string headID = ref->base->head; + headModel = "meshes\\" + store.bodyParts.find(headID)->model; + hairModel = "meshes\\" + store.bodyParts.find(hairID)->model; + npcName = ref->base->name; + isFemale = !!(ref->base->flags&ESM::NPC::Female); + isBeast = !!(race->data.flags&ESM::Race::Beast); - std::string smodel = "meshes\\base_anim.nif"; - if(isBeast) - smodel = "meshes\\base_animkna.nif"; + bodyRaceID = "b_n_"+race->name; + std::transform(bodyRaceID.begin(), bodyRaceID.end(), bodyRaceID.begin(), ::tolower); - insert = ptr.getRefData().getBaseNode(); - assert(insert); + /*std::cout << "Race: " << ref->base->race ; + if(female) + std::cout << " Sex: Female" << " Height: " << race->data.height.female << "\n"; + else + std::cout << " Sex: Male" << " Height: " << race->data.height.male << "\n"; + */ - NifOgre::NIFLoader::load(smodel); + insert = ptr.getRefData().getBaseNode(); + assert(insert); + std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); + NifOgre::NIFLoader::load(smodel); base = mRend.getScene()->createEntity(smodel); base->setVisibilityFlags(RV_Actors); @@ -134,372 +98,290 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere while (passIt.hasMoreElements()) { Ogre::Pass* pass = passIt.getNext(); - if (pass->getDepthWriteEnabled() == false) transparent = true; } } } base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - - base->setSkipAnimationStateUpdate(true); //Magical line of code, this makes the bones //stay in the same place when we skipanim, or open a gui window - - - if((transformations = (NIFLoader::getSingletonPtr())->getAnim(smodel))){ - - for(unsigned int init = 0; init < transformations->size(); init++){ - rindexI.push_back(0); - tindexI.push_back(0); - } - - stopTime = transformations->begin()->getStopTime(); - startTime = transformations->begin()->getStartTime(); - } - textmappings = NIFLoader::getSingletonPtr()->getTextIndices(smodel); insert->attachObject(base); + if(isFemale) + insert->scale(race->data.height.female, race->data.height.female, race->data.height.female); + else + insert->scale(race->data.height.male, race->data.height.male, race->data.height.male); - - if(isFemale) - insert->scale(race->data.height.female, race->data.height.female, race->data.height.female); - else - insert->scale(race->data.height.male, race->data.height.male, race->data.height.male); - updateParts(); - + updateParts(); } -void NpcAnimation::updateParts(){ - - bool apparelChanged = false; - - - //inv.getSlot(MWWorld::InventoryStore::Slot_Robe); - if(robe != inv.getSlot(MWWorld::InventoryStore::Slot_Robe)){ - //A robe was added or removed - removePartGroup(MWWorld::InventoryStore::Slot_Robe); - robe = inv.getSlot(MWWorld::InventoryStore::Slot_Robe); - apparelChanged = true; - } - if(skirtiter != inv.getSlot(MWWorld::InventoryStore::Slot_Skirt)){ - //A robe was added or removed - removePartGroup(MWWorld::InventoryStore::Slot_Skirt); - skirtiter = inv.getSlot(MWWorld::InventoryStore::Slot_Skirt); - apparelChanged = true; - } - if(helmet != inv.getSlot(MWWorld::InventoryStore::Slot_Helmet)){ - apparelChanged = true; - helmet = inv.getSlot(MWWorld::InventoryStore::Slot_Helmet); - removePartGroup(MWWorld::InventoryStore::Slot_Helmet); - - } - if(cuirass != inv.getSlot(MWWorld::InventoryStore::Slot_Cuirass)){ - cuirass = inv.getSlot(MWWorld::InventoryStore::Slot_Cuirass); - removePartGroup(MWWorld::InventoryStore::Slot_Cuirass); - apparelChanged = true; +void NpcAnimation::updateParts() +{ + bool apparelChanged = false; - } - if(greaves != inv.getSlot(MWWorld::InventoryStore::Slot_Greaves)){ - greaves = inv.getSlot(MWWorld::InventoryStore::Slot_Greaves); - removePartGroup(MWWorld::InventoryStore::Slot_Greaves); - apparelChanged = true; - } - if(leftpauldron != inv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron)){ - leftpauldron = inv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron); - removePartGroup(MWWorld::InventoryStore::Slot_LeftPauldron); - apparelChanged = true; + //inv.getSlot(MWWorld::InventoryStore::Slot_Robe); + if(robe != inv.getSlot(MWWorld::InventoryStore::Slot_Robe)) + { + // A robe was added or removed + robe = inv.getSlot(MWWorld::InventoryStore::Slot_Robe); + removePartGroup(MWWorld::InventoryStore::Slot_Robe); + apparelChanged = true; + } + if(skirtiter != inv.getSlot(MWWorld::InventoryStore::Slot_Skirt)) + { + skirtiter = inv.getSlot(MWWorld::InventoryStore::Slot_Skirt); + removePartGroup(MWWorld::InventoryStore::Slot_Skirt); + apparelChanged = true; + } + if(helmet != inv.getSlot(MWWorld::InventoryStore::Slot_Helmet)) + { + helmet = inv.getSlot(MWWorld::InventoryStore::Slot_Helmet); + removePartGroup(MWWorld::InventoryStore::Slot_Helmet); + apparelChanged = true; + } + if(cuirass != inv.getSlot(MWWorld::InventoryStore::Slot_Cuirass)) + { + cuirass = inv.getSlot(MWWorld::InventoryStore::Slot_Cuirass); + removePartGroup(MWWorld::InventoryStore::Slot_Cuirass); + apparelChanged = true; + } + if(greaves != inv.getSlot(MWWorld::InventoryStore::Slot_Greaves)) + { + greaves = inv.getSlot(MWWorld::InventoryStore::Slot_Greaves); + removePartGroup(MWWorld::InventoryStore::Slot_Greaves); + apparelChanged = true; + } + if(leftpauldron != inv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron)) + { + leftpauldron = inv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron); + removePartGroup(MWWorld::InventoryStore::Slot_LeftPauldron); + apparelChanged = true; + } + if(rightpauldron != inv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron)) + { + rightpauldron = inv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron); + removePartGroup(MWWorld::InventoryStore::Slot_RightPauldron); + apparelChanged = true; + } + if(!isBeast && boots != inv.getSlot(MWWorld::InventoryStore::Slot_Boots)) + { + boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); + removePartGroup(MWWorld::InventoryStore::Slot_Boots); + apparelChanged = true; + } + if(leftglove != inv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet)) + { + leftglove = inv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet); + removePartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet); + apparelChanged = true; + } + if(rightglove != inv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet)) + { + rightglove = inv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet); + removePartGroup(MWWorld::InventoryStore::Slot_RightGauntlet); + apparelChanged = true; + } + if(shirt != inv.getSlot(MWWorld::InventoryStore::Slot_Shirt)) + { + shirt = inv.getSlot(MWWorld::InventoryStore::Slot_Shirt); + removePartGroup(MWWorld::InventoryStore::Slot_Shirt); + apparelChanged = true; + } + if(pants != inv.getSlot(MWWorld::InventoryStore::Slot_Pants)) + { + pants = inv.getSlot(MWWorld::InventoryStore::Slot_Pants); + removePartGroup(MWWorld::InventoryStore::Slot_Pants); + apparelChanged = true; + } - } - if(rightpauldron != inv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron)){ - rightpauldron = inv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron); - removePartGroup(MWWorld::InventoryStore::Slot_RightPauldron); - apparelChanged = true; + if(apparelChanged) + { + if(robe != inv.end()) + { + MWWorld::Ptr ptr = *robe; + + const ESM::Clothing *clothes = (ptr.get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Robe, 5, parts); + reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_Skirt, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RKnee, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LKnee, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RForearm, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LForearm, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RPauldron, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LPauldron, MWWorld::InventoryStore::Slot_Robe, 5); + } + if(skirtiter != inv.end()) + { + MWWorld::Ptr ptr = *skirtiter; + const ESM::Clothing *clothes = (ptr.get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Skirt, 4, parts); + reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Skirt, 4); + reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Skirt, 4); + reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Skirt, 4); } - if(!isBeast && boots != inv.getSlot(MWWorld::InventoryStore::Slot_Boots)){ - boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); - removePartGroup(MWWorld::InventoryStore::Slot_Boots); - apparelChanged = true; + if(helmet != inv.end()) + { + removeIndividualPart(ESM::PRT_Hair); + const ESM::Armor *armor = (helmet->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Helmet, 3, parts); } - if(leftglove != inv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet)){ - leftglove = inv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet); - removePartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet); - apparelChanged = true; - + if(cuirass != inv.end()) + { + const ESM::Armor *armor = (cuirass->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Cuirass, 3, parts); } - if(rightglove != inv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet)){ - rightglove = inv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet); - removePartGroup(MWWorld::InventoryStore::Slot_RightGauntlet); - apparelChanged = true; - + if(greaves != inv.end()) + { + const ESM::Armor *armor = (greaves->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Greaves, 3, parts); } - if(shirt != inv.getSlot(MWWorld::InventoryStore::Slot_Shirt)){ - shirt = inv.getSlot(MWWorld::InventoryStore::Slot_Shirt); - removePartGroup(MWWorld::InventoryStore::Slot_Shirt); - apparelChanged = true; + if(leftpauldron != inv.end()) + { + const ESM::Armor *armor = (leftpauldron->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_LeftPauldron, 3, parts); } - if(pants != inv.getSlot(MWWorld::InventoryStore::Slot_Pants)){ - pants = inv.getSlot(MWWorld::InventoryStore::Slot_Pants); - removePartGroup(MWWorld::InventoryStore::Slot_Pants); - apparelChanged = true; - + if(rightpauldron != inv.end()) + { + const ESM::Armor *armor = (rightpauldron->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_RightPauldron, 3, parts); } - - if(apparelChanged){ - - if(robe != inv.end()) + if(!isBeast && boots != inv.end()) + { + if(boots->getTypeName() == typeid(ESM::Clothing).name()) { - MWWorld::Ptr ptr = *robe; - - const ESM::Clothing *clothes = (ptr.get())->base; + const ESM::Clothing *clothes = (boots->get())->base; std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Robe, 5, parts); - reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_Skirt, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RKnee, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LKnee, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RForearm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LForearm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RPauldron, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LPauldron, MWWorld::InventoryStore::Slot_Robe, 5); + addPartGroup(MWWorld::InventoryStore::Slot_Boots, 2, parts); } - if(skirtiter != inv.end()) + else if(boots->getTypeName() == typeid(ESM::Armor).name()) { - MWWorld::Ptr ptr = *skirtiter; - - const ESM::Clothing *clothes = (ptr.get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Skirt, 4, parts); - reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Skirt, 4); - reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Skirt, 4); - reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Skirt, 4); - } - - if(helmet != inv.end()){ - removeIndividualPart(ESM::PRT_Hair); - const ESM::Armor *armor = (helmet->get())->base; + const ESM::Armor *armor = (boots->get())->base; std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Helmet, 3, parts); - + addPartGroup(MWWorld::InventoryStore::Slot_Boots, 3, parts); } - if(cuirass != inv.end()){ - const ESM::Armor *armor = (cuirass->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Cuirass, 3, parts); - + } + if(leftglove != inv.end()) + { + if(leftglove->getTypeName() == typeid(ESM::Clothing).name()) + { + const ESM::Clothing *clothes = (leftglove->get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 2, parts); } - if(greaves != inv.end()){ - const ESM::Armor *armor = (greaves->get())->base; + else + { + const ESM::Armor *armor = (leftglove->get())->base; std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Greaves, 3, parts); - + addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 3, parts); } - - if(leftpauldron != inv.end()){ - const ESM::Armor *armor = (leftpauldron->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_LeftPauldron, 3, parts); - + } + if(rightglove != inv.end()) + { + if(rightglove->getTypeName() == typeid(ESM::Clothing).name()) + { + const ESM::Clothing *clothes = (rightglove->get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 2, parts); } - if(rightpauldron != inv.end()){ - const ESM::Armor *armor = (rightpauldron->get())->base; + else + { + const ESM::Armor *armor = (rightglove->get())->base; std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_RightPauldron, 3, parts); - - } - if(!isBeast && boots != inv.end()){ - if(boots->getTypeName() == typeid(ESM::Clothing).name()){ - const ESM::Clothing *clothes = (boots->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Boots, 2, parts); - } - else if(boots->getTypeName() == typeid(ESM::Armor).name()) - { - const ESM::Armor *armor = (boots->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Boots, 3, parts); - } - - } - if(leftglove != inv.end()){ - if(leftglove->getTypeName() == typeid(ESM::Clothing).name()){ - const ESM::Clothing *clothes = (leftglove->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 2, parts); - } - else - { - const ESM::Armor *armor = (leftglove->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 3, parts); - } - + addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 3, parts); } - if(rightglove != inv.end()){ - if(rightglove->getTypeName() == typeid(ESM::Clothing).name()){ - const ESM::Clothing *clothes = (rightglove->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 2, parts); - } - else - { - const ESM::Armor *armor = (rightglove->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 3, parts); - } - } - - if(shirt != inv.end()){ - const ESM::Clothing *clothes = (shirt->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Shirt, 2, parts); - } - if(pants != inv.end()){ - const ESM::Clothing *clothes = (pants->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Pants, 2, parts); - } } - if(partpriorities[ESM::PRT_Head] < 1){ - addOrReplaceIndividualPart(ESM::PRT_Head, -1,1,headModel); - } - if(partpriorities[ESM::PRT_Hair] < 1 && partpriorities[ESM::PRT_Head] <= 1){ - addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1,hairModel); - } - if(partpriorities[ESM::PRT_Neck] < 1){ - const ESM::BodyPart *neckPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "neck"); - if(neckPart) - addOrReplaceIndividualPart(ESM::PRT_Neck, -1,1,"meshes\\" + neckPart->model); - } - if(partpriorities[ESM::PRT_Cuirass] < 1){ - const ESM::BodyPart *chestPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "chest"); - if(chestPart) - addOrReplaceIndividualPart(ESM::PRT_Cuirass, -1,1,"meshes\\" + chestPart->model); - } - - if(partpriorities[ESM::PRT_Groin] < 1){ - const ESM::BodyPart *groinPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "groin"); - if(groinPart) - addOrReplaceIndividualPart(ESM::PRT_Groin, -1,1,"meshes\\" + groinPart->model); - } - if(partpriorities[ESM::PRT_RHand] < 1){ - const ESM::BodyPart *handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hand"); - if(!handPart) - handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hands"); - if(handPart) - addOrReplaceIndividualPart(ESM::PRT_RHand, -1,1,"meshes\\" + handPart->model); - } - if(partpriorities[ESM::PRT_LHand] < 1){ - const ESM::BodyPart *handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hand"); - if(!handPart) - handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hands"); - if(handPart) - addOrReplaceIndividualPart(ESM::PRT_LHand, -1,1,"meshes\\" + handPart->model); - } + if(shirt != inv.end()) + { + const ESM::Clothing *clothes = (shirt->get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Shirt, 2, parts); + } + if(pants != inv.end()) + { + const ESM::Clothing *clothes = (pants->get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Pants, 2, parts); + } + } - if(partpriorities[ESM::PRT_RWrist] < 1){ - const ESM::BodyPart *wristPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "wrist"); - if(wristPart) - addOrReplaceIndividualPart(ESM::PRT_RWrist, -1,1,"meshes\\" + wristPart->model); - } - if(partpriorities[ESM::PRT_LWrist] < 1){ - const ESM::BodyPart *wristPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "wrist"); - if(wristPart) - addOrReplaceIndividualPart(ESM::PRT_LWrist, -1,1,"meshes\\" + wristPart->model); - } - if(partpriorities[ESM::PRT_RForearm] < 1){ - const ESM::BodyPart *forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "forearm"); - if(bodyRaceID == "b_n_argonian_f_") - forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search ("b_n_argonian_m_forearm"); - if(forearmPart) - addOrReplaceIndividualPart(ESM::PRT_RForearm, -1,1,"meshes\\" + forearmPart->model); - } - if(partpriorities[ESM::PRT_LForearm] < 1){ - const ESM::BodyPart *forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "forearm"); - if(bodyRaceID == "b_n_argonian_f_") - forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search ("b_n_argonian_m_forearm"); - if(forearmPart) - addOrReplaceIndividualPart(ESM::PRT_LForearm, -1,1,"meshes\\" + forearmPart->model); - } - if(partpriorities[ESM::PRT_RUpperarm] < 1){ - const ESM::BodyPart *armPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper arm"); - if(armPart) - addOrReplaceIndividualPart(ESM::PRT_RUpperarm, -1,1,"meshes\\" + armPart->model); - } - if(partpriorities[ESM::PRT_LUpperarm] < 1){ - const ESM::BodyPart *armPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper arm"); - if(armPart) - addOrReplaceIndividualPart(ESM::PRT_LUpperarm, -1,1,"meshes\\" + armPart->model); - } - if(partpriorities[ESM::PRT_RFoot] < 1){ - const ESM::BodyPart *footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "foot"); - if(isBeast && !footPart) - footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "feet"); - if(footPart) - addOrReplaceIndividualPart(ESM::PRT_RFoot, -1,1,"meshes\\" + footPart->model); - } - if(partpriorities[ESM::PRT_LFoot] < 1){ - const ESM::BodyPart *footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "foot"); - if(isBeast && !footPart) - footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "feet"); - if(footPart) - addOrReplaceIndividualPart(ESM::PRT_LFoot, -1,1,"meshes\\" + footPart->model); - } - if(partpriorities[ESM::PRT_RAnkle] < 1){ - const ESM::BodyPart *anklePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "ankle"); - if(anklePart) - addOrReplaceIndividualPart(ESM::PRT_RAnkle, -1,1,"meshes\\" + anklePart->model); - } - if(partpriorities[ESM::PRT_LAnkle] < 1){ - const ESM::BodyPart *anklePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "ankle"); - if(anklePart) - addOrReplaceIndividualPart(ESM::PRT_LAnkle, -1,1,"meshes\\" + anklePart->model); - } - if(partpriorities[ESM::PRT_RKnee] < 1){ - const ESM::BodyPart *kneePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "knee"); - if(kneePart) - addOrReplaceIndividualPart(ESM::PRT_RKnee, -1,1,"meshes\\" + kneePart->model); - } - if(partpriorities[ESM::PRT_LKnee] < 1){ - const ESM::BodyPart *kneePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "knee"); - if(kneePart) - addOrReplaceIndividualPart(ESM::PRT_LKnee, -1,1,"meshes\\" + kneePart->model); - } - if(partpriorities[ESM::PRT_RLeg] < 1){ - const ESM::BodyPart *legPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper leg"); - if(legPart) - addOrReplaceIndividualPart(ESM::PRT_RLeg, -1,1,"meshes\\" + legPart->model); - } - if(partpriorities[ESM::PRT_LLeg] < 1){ - const ESM::BodyPart *legPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper leg"); - if(legPart) - addOrReplaceIndividualPart(ESM::PRT_LLeg, -1,1,"meshes\\" + legPart->model); - } - if(partpriorities[ESM::PRT_Tail] < 1){ - const ESM::BodyPart *tailPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "tail"); - if(tailPart) - addOrReplaceIndividualPart(ESM::PRT_Tail, -1,1,"meshes\\" + tailPart->model); + if(partpriorities[ESM::PRT_Head] < 1) + addOrReplaceIndividualPart(ESM::PRT_Head, -1,1, headModel); + if(partpriorities[ESM::PRT_Hair] < 1 && partpriorities[ESM::PRT_Head] <= 1) + addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1, hairModel); + + static const struct { + ESM::PartReferenceType type; + const char name[2][12]; + } PartTypeList[] = { + { ESM::PRT_Neck, { "neck", "" } }, + { ESM::PRT_Cuirass, { "chest", "" } }, + { ESM::PRT_Groin, { "groin", "" } }, + { ESM::PRT_RHand, { "hand", "hands" } }, + { ESM::PRT_LHand, { "hand", "hands" } }, + { ESM::PRT_RWrist, { "wrist", "" } }, + { ESM::PRT_LWrist, { "wrist", "" } }, + { ESM::PRT_RForearm, { "forearm", "" } }, + { ESM::PRT_LForearm, { "forearm", "" } }, + { ESM::PRT_RUpperarm, { "upper arm", "" } }, + { ESM::PRT_LUpperarm, { "upper arm", "" } }, + { ESM::PRT_RFoot, { "foot", "feet" } }, + { ESM::PRT_LFoot, { "foot", "feet" } }, + { ESM::PRT_RAnkle, { "ankle", "" } }, + { ESM::PRT_LAnkle, { "ankle", "" } }, + { ESM::PRT_RKnee, { "knee", "" } }, + { ESM::PRT_LKnee, { "knee", "" } }, + { ESM::PRT_RLeg, { "upper leg", "" } }, + { ESM::PRT_LLeg, { "upper leg", "" } }, + { ESM::PRT_Tail, { "tail", "" } } + }; + + const ESMS::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + for(size_t i = 0;i < sizeof(PartTypeList)/sizeof(PartTypeList[0]);i++) + { + if(partpriorities[PartTypeList[i].type] < 1) + { + const ESM::BodyPart *part = NULL; + bool tryfemale = isFemale; + int ni = 0; + do { + part = store.bodyParts.search(bodyRaceID+(tryfemale?"_f_":"_m_")+PartTypeList[i].name[ni]); + if(part) break; + + ni ^= 1; + if(ni == 0) + { + if(!tryfemale) + break; + tryfemale = false; } + } while(1); - - - - - - - + if(part) + addOrReplaceIndividualPart(PartTypeList[i].type, -1,1, "meshes\\"+part->model); + } + } } -Ogre::Entity* NpcAnimation::insertBoundedPart(const std::string &mesh, std::string bonename){ +Ogre::Entity* NpcAnimation::insertBoundedPart(const std::string &mesh, const std::string &bonename) +{ NIFLoader::load(mesh); Ogre::Entity* part = mRend.getScene()->createEntity(mesh); @@ -508,361 +390,275 @@ Ogre::Entity* NpcAnimation::insertBoundedPart(const std::string &mesh, std::stri base->attachObjectToBone(bonename, part); return part; } -void NpcAnimation::insertFootPart(int type, const std::string &mesh){ - std::string meshAndSuffix = mesh; - if(type == ESM::PRT_LFoot) - meshAndSuffix += "*|"; - NIFLoader::load(meshAndSuffix); - Ogre::Entity* part = mRend.getScene()->createEntity(meshAndSuffix); - std::vector* shape = ((NIFLoader::getSingletonPtr())->getShapes(meshAndSuffix)); - if(shape == 0){ - if(type == ESM::PRT_LFoot){ - base->attachObjectToBone("Left Foot", part); - lfoot = part; - } - else if (type == ESM::PRT_RFoot){ - base->attachObjectToBone("Right Foot", part); - rfoot = part; - } - } - else{ - if(type == ESM::PRT_LFoot) - lFreeFoot = insertFreePart(mesh, "::"); - else if (type == ESM::PRT_RFoot) - rFreeFoot = insertFreePart(mesh, ":<"); - } - - - -} -std::pair*> NpcAnimation::insertFreePart(const std::string &mesh, const std::string& suffix){ - std::string meshNumbered = mesh + getUniqueID(mesh + suffix) + suffix; - NIFLoader::load(meshNumbered); - - Ogre::Entity* part = mRend.getScene()->createEntity(meshNumbered); - part->setVisibilityFlags(RV_Actors); - - insert->attachObject(part); - - std::vector* shape = ((NIFLoader::getSingletonPtr())->getShapes(mesh + "0000" + suffix)); - if(shape){ - handleShapes(shape, part, base->getSkeleton()); +void NpcAnimation::runAnimation(float timepassed) +{ + if(timeToChange > .2) + { + timeToChange = 0; + updateParts(); } - std::pair*> pair = std::make_pair(part, shape); - return pair; -} - - - -void NpcAnimation::runAnimation(float timepassed){ - - if(timeToChange > .2){ - - timeToChange = 0; - - updateParts(); - } - - timeToChange += timepassed; + timeToChange += timepassed; //1. Add the amount of time passed to time - //2. Handle the animation transforms dependent on time + //2. Handle the animation transforms dependent on time - //3. Handle the shapes dependent on animation transforms - if(animate > 0){ + //3. Handle the shapes dependent on animation transforms + if(animate > 0) + { time += timepassed; - - if(time > stopTime){ + if(time > stopTime) + { animate--; - if(animate == 0) time = stopTime; else time = startTime + (time - stopTime); } - handleAnimationTransforms(); - - - vecRotPos.clear(); - - - if(lFreeFoot.first) - handleShapes(lFreeFoot.second, lFreeFoot.first, base->getSkeleton()); - if(rFreeFoot.first) - handleShapes(rFreeFoot.second, rFreeFoot.first, base->getSkeleton()); - - if(chest.first) - handleShapes(chest.second, chest.first, base->getSkeleton()); - if(tail.first) - handleShapes(tail.second, tail.first, base->getSkeleton()); - if(skirt.first){ - handleShapes(skirt.second, skirt.first, base->getSkeleton()); - } - if(lhand.first) - handleShapes(lhand.second, lhand.first, base->getSkeleton()); - if(rhand.first) - handleShapes(rhand.second, rhand.first, base->getSkeleton()); - -} + handleAnimationTransforms(); + } } -void NpcAnimation::removeIndividualPart(int type){ +void NpcAnimation::removeIndividualPart(int type) +{ partpriorities[type] = 0; partslots[type] = -1; - if(type == ESM::PRT_Head && head){ //0 - base->detachObjectFromBone(head); - head = 0; - } - else if(type == ESM::PRT_Hair && hair){//1 - base->detachObjectFromBone(hair); - hair = 0; - } - else if(type == ESM::PRT_Neck && neck){//2 - base->detachObjectFromBone(neck); - neck = 0; - } - else if(type == ESM::PRT_Cuirass && chest.first){//3 - insert->detachObject(chest.first); - chest = zero; - } - else if(type == ESM::PRT_Groin && groin){//4 - base->detachObjectFromBone(groin); - groin = 0; - } - else if(type == ESM::PRT_Skirt && skirt.first){//5 - insert->detachObject(skirt.first); - skirt = zero; - } - else if(type == ESM::PRT_RHand && rhand.first){//6 - insert->detachObject(rhand.first); - rhand = zero; - } - else if(type == ESM::PRT_LHand && lhand.first){//7 - insert->detachObject(lhand.first); - lhand = zero; - } - else if(type == ESM::PRT_RWrist && rWrist){//8 - base->detachObjectFromBone(rWrist); - rWrist = 0; - } - else if(type == ESM::PRT_LWrist && lWrist){//9 - base->detachObjectFromBone(lWrist); - lWrist = 0; - } - else if(type == ESM::PRT_Shield){//10 - - } - else if(type == ESM::PRT_RForearm && rForearm){//11 - base->detachObjectFromBone(rForearm); - rForearm = 0; - } - else if(type == ESM::PRT_LForearm && lForearm){//12 - base->detachObjectFromBone(lForearm); - lForearm = 0; - } - else if(type == ESM::PRT_RUpperarm && rupperArm){//13 - base->detachObjectFromBone(rupperArm); - rupperArm = 0; - } - else if(type == ESM::PRT_LUpperarm && lupperArm){//14 - base->detachObjectFromBone(lupperArm); - lupperArm = 0; - } - else if(type == ESM::PRT_RFoot){ //15 - if(rfoot){ - base->detachObjectFromBone(rfoot); - rfoot = 0; - } - else if(rFreeFoot.first){ - insert->detachObject(rFreeFoot.first); - rFreeFoot = zero; - } - } - else if(type == ESM::PRT_LFoot){ //16 - if(lfoot){ - base->detachObjectFromBone(lfoot); - lfoot = 0; - } - else if(lFreeFoot.first){ - insert->detachObject(lFreeFoot.first); - lFreeFoot = zero; - } - } - else if(type == ESM::PRT_RAnkle && rAnkle){ //17 - base->detachObjectFromBone(rAnkle); - rAnkle = 0; - } - else if(type == ESM::PRT_LAnkle && lAnkle){ //18 - base->detachObjectFromBone(lAnkle); - lAnkle = 0; - } - else if(type == ESM::PRT_RKnee && rKnee){ //19 - base->detachObjectFromBone(rKnee); - rKnee = 0; - } - else if(type == ESM::PRT_LKnee && lKnee){ //20 - base->detachObjectFromBone(lKnee); - lKnee = 0; - } - else if(type == ESM::PRT_RLeg && rUpperLeg){ //21 - base->detachObjectFromBone(rUpperLeg); - rUpperLeg = 0; - } - else if(type == ESM::PRT_LLeg && lUpperLeg){ //22 - base->detachObjectFromBone(lUpperLeg); - lUpperLeg = 0; - } - else if(type == ESM::PRT_RPauldron && rclavicle){ //23 - base->detachObjectFromBone(rclavicle); - rclavicle = 0; - } - else if(type == ESM::PRT_LPauldron && lclavicle){ //24 - base->detachObjectFromBone(lclavicle); - lclavicle = 0; - } - else if(type == ESM::PRT_Weapon){ //25 - - } - else if(type == ESM::PRT_Tail && tail.first){ //26 - insert->detachObject(tail.first); - tail = zero; - } - - - - + if(type == ESM::PRT_Head && head) //0 + { + base->detachObjectFromBone(head); + head = 0; } - - void NpcAnimation::reserveIndividualPart(int type, int group, int priority){ - if(priority > partpriorities[type]){ - removeIndividualPart(type); - partpriorities[type] = priority; - partslots[type] = group; - } + else if(type == ESM::PRT_Hair && hair) //1 + { + base->detachObjectFromBone(hair); + hair = 0; + } + else if(type == ESM::PRT_Neck && neck) //2 + { + base->detachObjectFromBone(neck); + neck = 0; + } + else if(type == ESM::PRT_Groin && groin)//4 + { + base->detachObjectFromBone(groin); + groin = 0; + } + else if(type == ESM::PRT_RWrist && rWrist)//8 + { + base->detachObjectFromBone(rWrist); + rWrist = 0; + } + else if(type == ESM::PRT_LWrist && lWrist) //9 + { + base->detachObjectFromBone(lWrist); + lWrist = 0; + } + else if(type == ESM::PRT_Shield) //10 + { + } + else if(type == ESM::PRT_RForearm && rForearm) //11 + { + base->detachObjectFromBone(rForearm); + rForearm = 0; + } + else if(type == ESM::PRT_LForearm && lForearm) //12 + { + base->detachObjectFromBone(lForearm); + lForearm = 0; + } + else if(type == ESM::PRT_RUpperarm && rupperArm) //13 + { + base->detachObjectFromBone(rupperArm); + rupperArm = 0; + } + else if(type == ESM::PRT_LUpperarm && lupperArm) //14 + { + base->detachObjectFromBone(lupperArm); + lupperArm = 0; + } + else if(type == ESM::PRT_RFoot && rfoot) //15 + { + base->detachObjectFromBone(rfoot); + rfoot = 0; + } + else if(type == ESM::PRT_LFoot && lfoot) //16 + { + base->detachObjectFromBone(lfoot); + lfoot = 0; + } + else if(type == ESM::PRT_RAnkle && rAnkle) //17 + { + base->detachObjectFromBone(rAnkle); + rAnkle = 0; + } + else if(type == ESM::PRT_LAnkle && lAnkle) //18 + { + base->detachObjectFromBone(lAnkle); + lAnkle = 0; + } + else if(type == ESM::PRT_RKnee && rKnee) //19 + { + base->detachObjectFromBone(rKnee); + rKnee = 0; + } + else if(type == ESM::PRT_LKnee && lKnee) //20 + { + base->detachObjectFromBone(lKnee); + lKnee = 0; + } + else if(type == ESM::PRT_RLeg && rUpperLeg) //21 + { + base->detachObjectFromBone(rUpperLeg); + rUpperLeg = 0; + } + else if(type == ESM::PRT_LLeg && lUpperLeg) //22 + { + base->detachObjectFromBone(lUpperLeg); + lUpperLeg = 0; + } + else if(type == ESM::PRT_RPauldron && rclavicle) //23 + { + base->detachObjectFromBone(rclavicle); + rclavicle = 0; } + else if(type == ESM::PRT_LPauldron && lclavicle) //24 + { + base->detachObjectFromBone(lclavicle); + lclavicle = 0; + } + else if(type == ESM::PRT_Weapon) //25 + { + } +} - void NpcAnimation::removePartGroup(int group){ - for(int i = 0; i < 27; i++){ - if(partslots[i] == group){ - removeIndividualPart(i); - } - } +void NpcAnimation::reserveIndividualPart(int type, int group, int priority) +{ + if(priority > partpriorities[type]) + { + removeIndividualPart(type); + partpriorities[type] = priority; + partslots[type] = group; } - bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, const std::string &mesh){ - if(priority > partpriorities[type]){ - removeIndividualPart(type); - partslots[type] = group; - partpriorities[type] = priority; - switch(type){ - case ESM::PRT_Head: //0 - head = insertBoundedPart(mesh, "Head"); - break; - case ESM::PRT_Hair: //1 - hair = insertBoundedPart(mesh, "Head"); - break; - case ESM::PRT_Neck: //2 - neck = insertBoundedPart(mesh, "Neck"); - break; - case ESM::PRT_Cuirass: //3 - chest = insertFreePart(mesh, ":\""); - break; - case ESM::PRT_Groin: //4 - groin = insertBoundedPart(mesh, "Groin"); - break; - case ESM::PRT_Skirt: //5 - skirt = insertFreePart(mesh, ":|"); - break; - case ESM::PRT_RHand: //6 - rhand = insertFreePart(mesh, ":?"); - break; - case ESM::PRT_LHand: //7 - lhand = insertFreePart(mesh, ":>"); - break; - case ESM::PRT_RWrist: //8 - rWrist = insertBoundedPart(mesh, "Right Wrist"); - break; - case ESM::PRT_LWrist: //9 - lWrist = insertBoundedPart(mesh + "*|", "Left Wrist"); - break; - case ESM::PRT_Shield: //10 - break; - case ESM::PRT_RForearm: //11 - rForearm = insertBoundedPart(mesh, "Right Forearm"); - break; - case ESM::PRT_LForearm: //12 - lForearm = insertBoundedPart(mesh + "*|", "Left Forearm"); - break; - case ESM::PRT_RUpperarm: //13 - rupperArm = insertBoundedPart(mesh, "Right Upper Arm"); - break; - case ESM::PRT_LUpperarm: //14 - lupperArm = insertBoundedPart(mesh + "*|", "Left Upper Arm"); - break; - case ESM::PRT_RFoot: //15 - insertFootPart(type, mesh); - break; - case ESM::PRT_LFoot: //16 - insertFootPart(type, mesh); - break; - case ESM::PRT_RAnkle: //17 - rAnkle = insertBoundedPart(mesh , "Right Ankle"); - break; - case ESM::PRT_LAnkle: //18 - lAnkle = insertBoundedPart(mesh + "*|", "Left Ankle"); - break; - case ESM::PRT_RKnee: //19 - rKnee = insertBoundedPart(mesh , "Right Knee"); - break; - case ESM::PRT_LKnee: //20 - lKnee = insertBoundedPart(mesh + "*|", "Left Knee"); - break; - case ESM::PRT_RLeg: //21 - rUpperLeg = insertBoundedPart(mesh, "Right Upper Leg"); - break; - case ESM::PRT_LLeg: //22 - lUpperLeg = insertBoundedPart(mesh + "*|", "Left Upper Leg"); - break; - case ESM::PRT_RPauldron: //23 - rclavicle = insertBoundedPart(mesh , "Right Clavicle"); - break; - case ESM::PRT_LPauldron: //24 - lclavicle = insertBoundedPart(mesh + "*|", "Left Clavicle"); - break; - case ESM::PRT_Weapon: //25 - break; - case ESM::PRT_Tail: //26 - tail = insertFreePart(mesh, ":*"); - break; +} +void NpcAnimation::removePartGroup(int group) +{ + for(int i = 0; i < 27; i++) + { + if(partslots[i] == group) + removeIndividualPart(i); + } +} - } - return true; - } +bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, const std::string &mesh) +{ + if(priority <= partpriorities[type]) return false; - } - void NpcAnimation::addPartGroup(int group, int priority, std::vector& parts){ - for(std::size_t i = 0; i < parts.size(); i++) - { - ESM::PartReference part = parts[i]; + removeIndividualPart(type); + partslots[type] = group; + partpriorities[type] = priority; + switch(type) + { + case ESM::PRT_Head: //0 + head = insertBoundedPart(mesh, "Head"); + break; + case ESM::PRT_Hair: //1 + hair = insertBoundedPart(mesh, "Head"); + break; + case ESM::PRT_Neck: //2 + neck = insertBoundedPart(mesh, "Neck"); + break; + case ESM::PRT_Cuirass: //3 + break; + case ESM::PRT_Groin: //4 + groin = insertBoundedPart(mesh, "Groin"); + break; + case ESM::PRT_Skirt: //5 + break; + case ESM::PRT_RHand: //6 + break; + case ESM::PRT_LHand: //7 + break; + case ESM::PRT_RWrist: //8 + rWrist = insertBoundedPart(mesh, "Right Wrist"); + break; + case ESM::PRT_LWrist: //9 + lWrist = insertBoundedPart(mesh, "Left Wrist"); + break; + case ESM::PRT_Shield: //10 + break; + case ESM::PRT_RForearm: //11 + rForearm = insertBoundedPart(mesh, "Right Forearm"); + break; + case ESM::PRT_LForearm: //12 + lForearm = insertBoundedPart(mesh, "Left Forearm"); + break; + case ESM::PRT_RUpperarm: //13 + rupperArm = insertBoundedPart(mesh, "Right Upper Arm"); + break; + case ESM::PRT_LUpperarm: //14 + lupperArm = insertBoundedPart(mesh, "Left Upper Arm"); + break; + case ESM::PRT_RFoot: //15 + lupperArm = insertBoundedPart(mesh, "Right Foot"); + break; + case ESM::PRT_LFoot: //16 + lupperArm = insertBoundedPart(mesh, "Left Foot"); + break; + case ESM::PRT_RAnkle: //17 + rAnkle = insertBoundedPart(mesh, "Right Ankle"); + break; + case ESM::PRT_LAnkle: //18 + lAnkle = insertBoundedPart(mesh, "Left Ankle"); + break; + case ESM::PRT_RKnee: //19 + rKnee = insertBoundedPart(mesh, "Right Knee"); + break; + case ESM::PRT_LKnee: //20 + lKnee = insertBoundedPart(mesh, "Left Knee"); + break; + case ESM::PRT_RLeg: //21 + rUpperLeg = insertBoundedPart(mesh, "Right Upper Leg"); + break; + case ESM::PRT_LLeg: //22 + lUpperLeg = insertBoundedPart(mesh, "Left Upper Leg"); + break; + case ESM::PRT_RPauldron: //23 + rclavicle = insertBoundedPart(mesh , "Right Clavicle"); + break; + case ESM::PRT_LPauldron: //24 + lclavicle = insertBoundedPart(mesh, "Left Clavicle"); + break; + case ESM::PRT_Weapon: //25 + break; + case ESM::PRT_Tail: //26 + break; + } + return true; +} - const ESM::BodyPart *bodypart = 0; +void NpcAnimation::addPartGroup(int group, int priority, std::vector &parts) +{ + for(std::size_t i = 0; i < parts.size(); i++) + { + ESM::PartReference &part = parts[i]; - if(isFemale) - bodypart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (part.female); - if(!bodypart) - bodypart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (part.male); - if(bodypart){ - addOrReplaceIndividualPart(part.part, group,priority,"meshes\\" + bodypart->model); - } - else - reserveIndividualPart(part.part, group, priority); + const ESM::BodyPart *bodypart = 0; + if(isFemale) + bodypart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search(part.female); + if(!bodypart) + bodypart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search(part.male); - } + if(bodypart) + addOrReplaceIndividualPart(part.part, group,priority,"meshes\\" + bodypart->model); + else + reserveIndividualPart(part.part, group, priority); } } + +} diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 8f4f8181d..151af4163 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -20,54 +20,43 @@ namespace MWRender{ class NpcAnimation: public Animation{ private: - MWWorld::InventoryStore& inv; - int mStateID; - //Free Parts - std::pair*> chest; - std::pair*> skirt; - std::pair*> lhand; - std::pair*> rhand; - std::pair*> tail; - std::pair*> lFreeFoot; - std::pair*> rFreeFoot; + MWWorld::InventoryStore& inv; + int mStateID; - int partslots[27]; //Each part slot is taken by clothing, armor, or is empty - int partpriorities[27]; - std::pair*> zero; + int partslots[27]; //Each part slot is taken by clothing, armor, or is empty + int partpriorities[27]; + //Bounded Parts + Ogre::Entity* lclavicle; + Ogre::Entity* rclavicle; + Ogre::Entity* rupperArm; + Ogre::Entity* lupperArm; + Ogre::Entity* rUpperLeg; + Ogre::Entity* lUpperLeg; + Ogre::Entity* lForearm; + Ogre::Entity* rForearm; + Ogre::Entity* lWrist; + Ogre::Entity* rWrist; + Ogre::Entity* rKnee; + Ogre::Entity* lKnee; + Ogre::Entity* neck; + Ogre::Entity* rAnkle; + Ogre::Entity* lAnkle; + Ogre::Entity* groin; + Ogre::Entity* lfoot; + Ogre::Entity* rfoot; + Ogre::Entity* hair; + Ogre::Entity* head; - - //Bounded Parts - Ogre::Entity* lclavicle; - Ogre::Entity* rclavicle; - Ogre::Entity* rupperArm; - Ogre::Entity* lupperArm; - Ogre::Entity* rUpperLeg; - Ogre::Entity* lUpperLeg; - Ogre::Entity* lForearm; - Ogre::Entity* rForearm; - Ogre::Entity* lWrist; - Ogre::Entity* rWrist; - Ogre::Entity* rKnee; - Ogre::Entity* lKnee; - Ogre::Entity* neck; - Ogre::Entity* rAnkle; - Ogre::Entity* lAnkle; - Ogre::Entity* groin; - Ogre::Entity* lfoot; - Ogre::Entity* rfoot; - Ogre::Entity* hair; - Ogre::Entity* head; - - Ogre::SceneNode* insert; + Ogre::SceneNode* insert; bool isBeast; bool isFemale; - std::string headModel; - std::string hairModel; - std::string npcName; - std::string bodyRaceID; - float timeToChange; - MWWorld::ContainerStoreIterator robe; + std::string headModel; + std::string hairModel; + std::string npcName; + std::string bodyRaceID; + float timeToChange; + MWWorld::ContainerStoreIterator robe; MWWorld::ContainerStoreIterator helmet; MWWorld::ContainerStoreIterator shirt; MWWorld::ContainerStoreIterator cuirass; @@ -80,22 +69,19 @@ private: MWWorld::ContainerStoreIterator rightglove; MWWorld::ContainerStoreIterator skirtiter; - public: - NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv); - virtual ~NpcAnimation(); - Ogre::Entity* insertBoundedPart(const std::string &mesh, std::string bonename); - std::pair*> insertFreePart(const std::string &mesh, const std::string& suffix); - void insertFootPart(int type, const std::string &mesh); - virtual void runAnimation(float timepassed); - void updateParts(); +public: + NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv); + virtual ~NpcAnimation(); + Ogre::Entity* insertBoundedPart(const std::string &mesh, const std::string &bonename); + virtual void runAnimation(float timepassed); + void updateParts(); void removeIndividualPart(int type); void reserveIndividualPart(int type, int group, int priority); bool addOrReplaceIndividualPart(int type, int group, int priority, const std::string &mesh); - void removePartGroup(int group); + void removePartGroup(int group); void addPartGroup(int group, int priority, std::vector& parts); - - }; + } #endif diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index e9ce3f615..dd12a53ff 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -64,28 +64,13 @@ static bool fsstrict = false; /// An OGRE Archive wrapping a BSAFile archive class DirArchive: public Ogre::FileSystemArchive { - boost::filesystem::path currentdir; std::map, ciLessBoost> m; unsigned int cutoff; bool findFile(const String& filename, std::string& copy) const { - { - String passed = filename; - if(filename.at(filename.length() - 2) == '>' || filename.at(filename.length() - 2) == ':') - passed = filename.substr(0, filename.length() - 6); - else if(filename.at(filename.length() - 2) == '"') - passed = filename.substr(0, filename.length() - 9); - else if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' - || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' - || filename.at(filename.length() - 1) == '|') - passed = filename.substr(0, filename.length() - 2); - - - copy = passed; - } - + copy = filename; std::replace(copy.begin(), copy.end(), '\\', '/'); if(copy.at(0) == '/') @@ -225,46 +210,23 @@ public: // OGRE's fault. You should NOT expect an open() command not to // have any side effects on the archive, and hence this function // should not have been declared const in the first place. - BSAFile *narc = (BSAFile*)&arc; - - String passed = filename; - if(filename.at(filename.length() - 2) == '>' || filename.at(filename.length() - 2) == ':') - passed = filename.substr(0, filename.length() - 6); - else if(filename.at(filename.length() - 2) == '"') - passed = filename.substr(0, filename.length() - 9); - else if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' - || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' - || filename.at(filename.length() - 1) == '|') - passed = filename.substr(0, filename.length() - 2); - - + BSAFile *narc = const_cast(&arc); + // Open the file - StreamPtr strm = narc->getFile(passed.c_str()); + StreamPtr strm = narc->getFile(filename.c_str()); // Wrap it into an Ogre::DataStream. return DataStreamPtr(new Mangle2OgreStream(strm)); } -bool exists(const String& filename) { - return cexists(filename); -} + bool exists(const String& filename) { + return arc.exists(filename.c_str()); + } - // Check if the file exists. bool cexists(const String& filename) const { - String passed = filename; - if(filename.at(filename.length() - 2) == '>' || filename.at(filename.length() - 2) == ':') - passed = filename.substr(0, filename.length() - 6); - else if(filename.at(filename.length() - 2) == '"') - passed = filename.substr(0, filename.length() - 9); - else if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' - || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' - || filename.at(filename.length() - 1) == '|') - passed = filename.substr(0, filename.length() - 2); - - - -return arc.exists(passed.c_str()); -} + return arc.exists(filename.c_str()); + } + time_t getModifiedTime(const String&) { return 0; } // This is never called as far as I can see. diff --git a/components/nif/data.hpp b/components/nif/data.hpp index b7e2f172f..2d1e8bd3b 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -326,12 +326,6 @@ public: Ogre::Vector3 trans; // Translation float scale; // Probably scale (always 1) }; - struct BoneTrafoCopy - { - Ogre::Quaternion rotation; - Ogre::Vector3 trans; - float scale; - }; struct VertWeight { @@ -339,26 +333,12 @@ public: float weight; }; - struct BoneInfo { BoneTrafo trafo; Ogre::Vector4 unknown; std::vector weights; }; - struct BoneInfoCopy - { - std::string bonename; - unsigned short bonehandle; - BoneTrafoCopy trafo; - Ogre::Vector4 unknown; - //std::vector weights; - }; - struct IndividualWeight - { - float weight; - unsigned int boneinfocopyindex; - }; BoneTrafo trafo; std::vector bones; diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 293793009..1f1b91a46 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -110,20 +110,6 @@ public: } }; -struct NiTriShapeCopy -{ - std::string sname; - std::vector boneSequence; - Nif::NiSkinData::BoneTrafoCopy trafo; - //Ogre::Quaternion initialBoneRotation; - //Ogre::Vector3 initialBoneTranslation; - std::vector vertices; - std::vector normals; - std::vector boneinfo; - std::map > vertsToWeights; - Nif::NiMorphData morph; -}; - struct NiNode : Node { NodeList children; @@ -184,28 +170,6 @@ struct NiTriShape : Node data.post(nif); skin.post(nif); } - - NiTriShapeCopy clone() - { - NiTriShapeCopy copy; - copy.sname = name; - float *ptr = (float*)&data->vertices[0]; - float *ptrNormals = (float*)&data->normals[0]; - int numVerts = data->vertices.size() / 3; - for(int i = 0; i < numVerts; i++) - { - float *current = (float*) (ptr + i * 3); - copy.vertices.push_back(Ogre::Vector3(*current, *(current + 1), *(current + 2))); - - if(ptrNormals) - { - float *currentNormals = (float*) (ptrNormals + i * 3); - copy.normals.push_back(Ogre::Vector3(*currentNormals, *(currentNormals + 1), *(currentNormals + 2))); - } - } - - return copy; - } }; struct NiCamera : Node diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index c3b34e039..5f562504b 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -79,7 +79,7 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) // of the early stages of development. Right now we WANT to catch // every error as early and intrusively as possible, as it's most // likely a sign of incomplete code rather than faulty input. - Nif::NIFFile nif(resourceName); + Nif::NIFFile nif(resourceName.substr(0, resourceName.length()-7)); if (nif.numRecords() < 1) { warn("Found no records in NIF."); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 014384dd4..883cf7fce 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -42,28 +42,6 @@ using namespace std; using namespace Nif; using namespace NifOgre; -NIFLoader& NIFLoader::getSingleton() -{ - static NIFLoader instance; - return instance; -} - -NIFLoader* NIFLoader::getSingletonPtr() -{ - return &getSingleton(); -} - -void NIFLoader::warn(string msg) -{ - std::cerr << "NIFLoader: Warn:" << msg << "\n"; -} - -void NIFLoader::fail(string msg) -{ - std::cerr << "NIFLoader: Fail: "<< msg << std::endl; - assert(1); -} - // Helper class that computes the bounding box and of a mesh class BoundsFinder @@ -149,6 +127,19 @@ public: } }; + +void NIFLoader::warn(const std::string &msg) +{ + std::cerr << "NIFLoader: Warn:" << msg << "\n"; +} + +void NIFLoader::fail(const std::string &msg) +{ + std::cerr << "NIFLoader: Fail: "<< msg << std::endl; + assert(1); +} + + // Conversion of blend / test mode from NIF -> OGRE. // Not in use yet, so let's comment it out. /* @@ -193,12 +184,7 @@ static CompareFunction getTestMode(int mode) } */ -void NIFLoader::setOutputAnimFiles(bool output){ - mOutputAnimFiles = output; -} -void NIFLoader::setVerbosePath(std::string path){ - verbosePath = path; -} +#if 0 void NIFLoader::createMaterial(const Ogre::String &name, const Ogre::Vector3 &ambient, const Ogre::Vector3 &diffuse, @@ -211,17 +197,6 @@ void NIFLoader::createMaterial(const Ogre::String &name, Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().create(name, resourceGroup); - //Hardware Skinning code, textures may be the wrong color if enabled - - /* if(!mSkel.isNull()){ - material->removeAllTechniques(); - - Ogre::Technique* tech = material->createTechnique(); - //tech->setSchemeName("blahblah"); - Pass* pass = tech->createPass(); - pass->setVertexProgram("Ogre/BasicVertexPrograms/AmbientOneTexture");*/ - - // This assigns the texture to this material. If the texture name is // a file name, and this file exists (in a resource directory), it // will automatically be loaded when needed. If not (such as for @@ -302,14 +277,14 @@ void NIFLoader::createMaterial(const Ogre::String &name, { bool split = Settings::Manager::getBool("split", "Shadows"); const int numsplits = 3; - for (int i = 0; i < (split ? numsplits : 1); ++i) - { + for (int i = 0; i < (split ? numsplits : 1); ++i) + { Ogre::TextureUnitState* tu = material->getTechnique(0)->getPass(0)->createTextureUnitState(); tu->setName("shadowMap" + Ogre::StringConverter::toString(i)); tu->setContentType(Ogre::TextureUnitState::CONTENT_SHADOW); tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_BORDER); tu->setTextureBorderColour(Ogre::ColourValue::White); - } + } } if (Settings::Manager::getBool("shaders", "Objects")) @@ -340,1067 +315,28 @@ void NIFLoader::createMaterial(const Ogre::String &name, material->setSelfIllumination(emissive[0], emissive[1], emissive[2]); material->setShininess(glossiness); } +#endif -// Takes a name and adds a unique part to it. This is just used to -// make sure that all materials are given unique names. -Ogre::String NIFLoader::getUniqueName(const Ogre::String &input) -{ - static int addon = 0; - static char buf[8]; - snprintf(buf, 8, "_%d", addon++); - - // Don't overflow the buffer - if (addon > 999999) addon = 0; - - return input + buf; -} - -// Check if the given texture name exists in the real world. If it -// does not, change the string IN PLACE to say .dds instead and try -// that. The texture may still not exist, but no information of value -// is lost in that case. -void NIFLoader::findRealTexture(Ogre::String &texName) -{ - if(Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texName)) - return; - - // Change texture extension to .dds - Ogre::String::size_type pos = texName.rfind('.'); - texName.replace(pos, texName.length(), ".dds"); -} - -//Handle node at top - -// Convert Nif::NiTriShape to Ogre::SubMesh, attached to the given -// mesh. -void NIFLoader::createOgreSubMesh(NiTriShape *shape, const Ogre::String &material, std::list &vertexBoneAssignments) -{ - // cout << "s:" << shape << "\n"; - NiTriShapeData *data = shape->data.getPtr(); - Ogre::SubMesh *sub = mesh->createSubMesh(shape->name); - - int nextBuf = 0; - - // This function is just one long stream of Ogre-barf, but it works - // great. - - // Add vertices - int numVerts = data->vertices.size() / 3; - sub->vertexData = new Ogre::VertexData(); - sub->vertexData->vertexCount = numVerts; - sub->useSharedVertices = false; - - Ogre::VertexDeclaration *decl = sub->vertexData->vertexDeclaration; - decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); - - Ogre::HardwareVertexBufferSharedPtr vbuf = - Ogre::HardwareBufferManager::getSingleton().createVertexBuffer( - Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), - numVerts, Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, false); - - if(flip) - { - float *datamod = new float[data->vertices.size()]; - //std::cout << "Shape" << shape->name.toString() << "\n"; - for(int i = 0; i < numVerts; i++) - { - int index = i * 3; - const float *pos = &data->vertices[index]; - Ogre::Vector3 original = Ogre::Vector3(*pos ,*(pos+1), *(pos+2)); - original = mTransform * original; - mBoundingBox.merge(original); - datamod[index] = original.x; - datamod[index+1] = original.y; - datamod[index+2] = original.z; - } - vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); - delete [] datamod; - } - else - { - vbuf->writeData(0, vbuf->getSizeInBytes(), &data->vertices[0], false); - } - - - Ogre::VertexBufferBinding* bind = sub->vertexData->vertexBufferBinding; - bind->setBinding(nextBuf++, vbuf); - - if (data->normals.size()) - { - decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); - vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer( - Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), - numVerts, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, false); - - if(flip) - { - Ogre::Quaternion rotation = mTransform.extractQuaternion(); - rotation.normalise(); - - float *datamod = new float[data->normals.size()]; - for(int i = 0; i < numVerts; i++) - { - int index = i * 3; - const float *pos = &data->normals[index]; - Ogre::Vector3 original = Ogre::Vector3(*pos ,*(pos+1), *(pos+2)); - original = rotation * original; - if (mNormaliseNormals) - { - original.normalise(); - } - - - datamod[index] = original.x; - datamod[index+1] = original.y; - datamod[index+2] = original.z; - } - vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); - delete [] datamod; - } - else - { - vbuf->writeData(0, vbuf->getSizeInBytes(), &data->normals[0], false); - } - bind->setBinding(nextBuf++, vbuf); - } - - - // Vertex colors - if (data->colors.size()) - { - const float *colors = &data->colors[0]; - Ogre::RenderSystem* rs = Ogre::Root::getSingleton().getRenderSystem(); - std::vector colorsRGB(numVerts); - Ogre::RGBA *pColour = &colorsRGB.front(); - for (int i=0; iconvertColourValue(Ogre::ColourValue(colors[0],colors[1],colors[2], - colors[3]),pColour++); - colors += 4; - } - decl->addElement(nextBuf, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); - vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer( - Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), - numVerts, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY); - vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB.front(), true); - bind->setBinding(nextBuf++, vbuf); - } - - if (data->uvlist.size()) - { - - decl->addElement(nextBuf, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES); - vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer( - Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2), - numVerts, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY,false); - - if(flip) - { - float *datamod = new float[data->uvlist.size()]; - - for(unsigned int i = 0; i < data->uvlist.size(); i+=2){ - float x = data->uvlist[i]; - - float y = data->uvlist[i + 1]; - - datamod[i] =x; - datamod[i + 1] =y; - } - vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); - delete [] datamod; - } - else - vbuf->writeData(0, vbuf->getSizeInBytes(), &data->uvlist[0], false); - bind->setBinding(nextBuf++, vbuf); - } - - // Triangle faces - The total number of triangle points - int numFaces = data->triangles.size(); - if (numFaces) - { - - sub->indexData->indexCount = numFaces; - sub->indexData->indexStart = 0; - Ogre::HardwareIndexBufferSharedPtr ibuf = Ogre::HardwareBufferManager::getSingleton(). - createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, numFaces, - Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, true); - - if(flip && mFlipVertexWinding && sub->indexData->indexCount % 3 == 0){ - - sub->indexData->indexBuffer = ibuf; - - uint16_t *datamod = new uint16_t[numFaces]; - int index = 0; - for (size_t i = 0; i < sub->indexData->indexCount; i+=3) - { - - const short *pos = &data->triangles[index]; - uint16_t i0 = (uint16_t) *(pos+0); - uint16_t i1 = (uint16_t) *(pos+1); - uint16_t i2 = (uint16_t) *(pos+2); - - //std::cout << "i0: " << i0 << "i1: " << i1 << "i2: " << i2 << "\n"; - - - datamod[index] = i2; - datamod[index+1] = i1; - datamod[index+2] = i0; - - index += 3; - } - - ibuf->writeData(0, ibuf->getSizeInBytes(), datamod, false); - delete [] datamod; - - } - else - ibuf->writeData(0, ibuf->getSizeInBytes(), &data->triangles[0], false); - sub->indexData->indexBuffer = ibuf; - } - - // Set material if one was given - if (!material.empty()) sub->setMaterialName(material); - - //add vertex bone assignments - - for (std::list::iterator it = vertexBoneAssignments.begin(); - it != vertexBoneAssignments.end(); it++) - { - sub->addBoneAssignment(*it); - } - if(mSkel.isNull()) - needBoneAssignments.push_back(sub); -} - -// Helper math functions. Reinventing linear algebra for the win! - -// Computes C = B + AxC*scale -static void vectorMulAdd(const Ogre::Matrix3 &A, const Ogre::Vector3 &B, float *C, float scale) -{ - // Keep the original values - float a = C[0]; - float b = C[1]; - float c = C[2]; - - // Perform matrix multiplication, scaling and addition - for (int i=0;i<3;i++) - C[i] = B[i] + (a*A[i][0] + b*A[i][1] + c*A[i][2])*scale; -} - -// Computes B = AxB (matrix*vector) -static void vectorMul(const Ogre::Matrix3 &A, float *C) -{ - // Keep the original values - float a = C[0]; - float b = C[1]; - float c = C[2]; - - // Perform matrix multiplication, scaling and addition - for (int i=0;i<3;i++) - C[i] = a*A[i][0] + b*A[i][1] + c*A[i][2]; -} - - -void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bounds, Transformation original, std::vector boneSequence) -{ - assert(shape != NULL); - - bool saveTheShape = inTheSkeletonTree; - // Interpret flags - bool hidden = (flags & 0x01) != 0; // Not displayed - bool collide = (flags & 0x02) != 0; // Use mesh for collision - bool bbcollide = (flags & 0x04) != 0; // Use bounding box for collision - - // Bounding box collision isn't implemented, always use mesh for now. - if (bbcollide) - { - collide = true; - bbcollide = false; - } - - // If the object was marked "NCO" earlier, it shouldn't collide with - // anything. - if (flags & 0x800) - { - collide = false; - bbcollide = false; - } - - if (!collide && !bbcollide && hidden) - // This mesh apparently isn't being used for anything, so don't - // bother setting it up. - return; - - // Material name for this submesh, if any - Ogre::String material; - - // Skip the entire material phase for hidden nodes - if (!hidden) - { - // These are set below if present - NiTexturingProperty *t = NULL; - NiMaterialProperty *m = NULL; - NiAlphaProperty *a = NULL; - - // Scan the property list for material information - PropertyList &list = shape->props; - int n = list.length(); - for (int i=0; irecType == RC_NiTexturingProperty) - t = static_cast(pr); - else if (pr->recType == RC_NiMaterialProperty) - m = static_cast(pr); - else if (pr->recType == RC_NiAlphaProperty) - a = static_cast(pr); - } - - // Texture - Ogre::String texName; - if (t && t->textures[0].inUse) - { - NiSourceTexture *st = t->textures[0].texture.getPtr(); - if (st->external) - { - /* findRealTexture checks if the file actually - exists. If it doesn't, and the name ends in .tga, it - will try replacing the extension with .dds instead - and search for that. Bethesda at some at some point - converted all their BSA textures from tga to dds for - increased load speed, but all texture file name - references were kept as .tga. - - The function replaces the name in place (that's why - we cast away the const modifier), but this is no - problem since all the nif data is stored in a local - throwaway buffer. - */ - texName = "textures\\" + st->filename; - findRealTexture(texName); - } - else warn("Found internal texture, ignoring."); - } - - // Alpha modifiers - int alphaFlags = -1; - ubyte alphaTest = 0; - if (a) - { - alphaFlags = a->flags; - alphaTest = a->data.threshold; - } - - // Material - if (m || !texName.empty()) - { - // If we're here, then this mesh has a material. Thus we - // need to calculate a snappy material name. It should - // contain the mesh name (mesh->getName()) but also has to - // be unique. One mesh may use many materials. - material = getUniqueName(mesh->getName()); - - if (m) - { - // Use NiMaterialProperty data to create the data - const S_MaterialProperty *d = &m->data; - - std::multimap::iterator itr = MaterialMap.find(texName); - std::multimap::iterator lastElement; - lastElement = MaterialMap.upper_bound(texName); - if (itr != MaterialMap.end()) - { - for ( ; itr != lastElement; ++itr) - { - //std::cout << "OK!"; - //MaterialPtr mat = MaterialManager::getSingleton().getByName(itr->second,recourceGroup); - material = itr->second; - //if( mat->getA - } - } - else - { - //std::cout << "new"; - createMaterial(material, d->ambient, d->diffuse, d->specular, d->emissive, - d->glossiness, d->alpha, alphaFlags, alphaTest, texName); - MaterialMap.insert(std::make_pair(texName,material)); - } - } - else - { - // We only have a texture name. Create a default - // material for it. - const Ogre::Vector3 zero(0.0f), one(1.0f); - createMaterial(material, one, one, zero, zero, 0.0f, 1.0f, - alphaFlags, alphaTest, texName); - } - } - } // End of material block, if(!hidden) ... - - /* Do in-place transformation of all the vertices and normals. This - is pretty messy stuff, but we need it to make the sub-meshes - appear in the correct place. Neither Ogre nor Bullet support - nested levels of sub-meshes with transformations applied to each - level. - */ - NiTriShapeData *data = shape->data.getPtr(); - int numVerts = data->vertices.size() / 3; - - float *ptr = (float*)&data->vertices[0]; - float *optr = ptr; - - std::list vertexBoneAssignments; - - Nif::NiTriShapeCopy copy = shape->clone(); - - if(!shape->controller.empty()) - { - Nif::Controller* cont = shape->controller.getPtr(); - if(cont->recType == RC_NiGeomMorpherController) - { - Nif::NiGeomMorpherController* morph = dynamic_cast (cont); - copy.morph = morph->data.get(); - copy.morph.setStartTime(morph->timeStart); - copy.morph.setStopTime(morph->timeStop); - saveTheShape = true; - } - - } - //use niskindata for the position of vertices. - if (!shape->skin.empty()) - { - - - - // vector that stores if the position of a vertex is absolute - std::vector vertexPosAbsolut(numVerts,false); - std::vector vertexPosOriginal(numVerts, Ogre::Vector3::ZERO); - std::vector vertexNormalOriginal(numVerts, Ogre::Vector3::ZERO); - - float *ptrNormals = (float*)&data->normals[0]; - //the bone from skin->bones[boneIndex] is linked to skin->data->bones[boneIndex] - //the first one contains a link to the bone, the second vertex transformation - //relative to the bone - int boneIndex = 0; - Ogre::Bone *bonePtr; - Ogre::Vector3 vecPos; - Ogre::Quaternion vecRot; - - std::vector boneList = shape->skin->data->bones; - - /* - Iterate through the boneList which contains what vertices are linked to - the bone (it->weights array) and at what position (it->trafo) - That position is added to every vertex. - */ - for (std::vector::iterator it = boneList.begin(); - it != boneList.end(); it++) - { - if(mSkel.isNull()) - { - std::cout << "No skeleton for :" << shape->skin->bones[boneIndex]->name << std::endl; - break; - } - //get the bone from bones array of skindata - if(!mSkel->hasBone(shape->skin->bones[boneIndex]->name)) - std::cout << "We don't have this bone"; - bonePtr = mSkel->getBone(shape->skin->bones[boneIndex]->name); - - // final_vector = old_vector + old_rotation*new_vector*old_scale - - - Nif::NiSkinData::BoneInfoCopy boneinfocopy; - boneinfocopy.trafo.rotation = it->trafo.rotation; - boneinfocopy.trafo.trans = it->trafo.trans; - boneinfocopy.bonename = shape->skin->bones[boneIndex]->name; - boneinfocopy.bonehandle = bonePtr->getHandle(); - copy.boneinfo.push_back(boneinfocopy); - for (unsigned int i=0; iweights.size(); i++) - { - vecPos = bonePtr->_getDerivedPosition() + - bonePtr->_getDerivedOrientation() * it->trafo.trans; - - vecRot = bonePtr->_getDerivedOrientation() * it->trafo.rotation; - unsigned int verIndex = it->weights[i].vertex; - //boneinfo.weights.push_back(*(it->weights.ptr + i)); - Nif::NiSkinData::IndividualWeight ind; - ind.weight = it->weights[i].weight; - ind.boneinfocopyindex = copy.boneinfo.size() - 1; - if(copy.vertsToWeights.find(verIndex) == copy.vertsToWeights.end()) - { - std::vector blank; - blank.push_back(ind); - copy.vertsToWeights[verIndex] = blank; - } - else - { - copy.vertsToWeights[verIndex].push_back(ind); - } - - //Check if the vertex is relativ, FIXME: Is there a better solution? - if (vertexPosAbsolut[verIndex] == false) - { - //apply transformation to the vertices - Ogre::Vector3 absVertPos = vecPos + vecRot * Ogre::Vector3(ptr + verIndex *3); - absVertPos = absVertPos * it->weights[i].weight; - vertexPosOriginal[verIndex] = Ogre::Vector3(ptr + verIndex *3); - - mBoundingBox.merge(absVertPos); - //convert it back to float * - for (int j=0; j<3; j++) - (ptr + verIndex*3)[j] = absVertPos[j]; - - //apply rotation to the normals (not every vertex has a normal) - //FIXME: I guessed that vertex[i] = normal[i], is that true? - if (verIndex < data->normals.size()) - { - Ogre::Vector3 absNormalsPos = vecRot * Ogre::Vector3(ptrNormals + verIndex *3); - absNormalsPos = absNormalsPos * it->weights[i].weight; - vertexNormalOriginal[verIndex] = Ogre::Vector3(ptrNormals + verIndex *3); - - for (int j=0; j<3; j++) - (ptrNormals + verIndex*3)[j] = absNormalsPos[j]; - } - - vertexPosAbsolut[verIndex] = true; - } - else - { - Ogre::Vector3 absVertPos = vecPos + vecRot * vertexPosOriginal[verIndex]; - absVertPos = absVertPos * it->weights[i].weight; - Ogre::Vector3 old = Ogre::Vector3(ptr + verIndex *3); - absVertPos = absVertPos + old; - - mBoundingBox.merge(absVertPos); - //convert it back to float * - for (int j=0; j<3; j++) - (ptr + verIndex*3)[j] = absVertPos[j]; - - //apply rotation to the normals (not every vertex has a normal) - //FIXME: I guessed that vertex[i] = normal[i], is that true? - if (verIndex < data->normals.size()) - { - Ogre::Vector3 absNormalsPos = vecRot * vertexNormalOriginal[verIndex]; - absNormalsPos = absNormalsPos * it->weights[i].weight; - Ogre::Vector3 oldNormal = Ogre::Vector3(ptrNormals + verIndex *3); - absNormalsPos = absNormalsPos + oldNormal; - - for (int j=0; j<3; j++) - (ptrNormals + verIndex*3)[j] = absNormalsPos[j]; - } - } - - - Ogre::VertexBoneAssignment vba; - vba.boneIndex = bonePtr->getHandle(); - vba.vertexIndex = verIndex; - vba.weight = it->weights[i].weight; - - - vertexBoneAssignments.push_back(vba); - } - - - boneIndex++; - } - - - } - else - { - - copy.boneSequence = boneSequence; - // Rotate, scale and translate all the vertices, - const Ogre::Matrix3 &rot = shape->trafo.rotation; - const Ogre::Vector3 &pos = shape->trafo.pos; - float scale = shape->trafo.scale; - - copy.trafo.trans = original.pos; - copy.trafo.rotation = original.rotation; - copy.trafo.scale = original.scale; - //We don't use velocity for anything yet, so it does not need to be saved - - // Computes C = B + AxC*scale - for (int i=0; inormals.size()) - { - ptr = (float*)&data->normals[0]; - for (int i=0; igetNumBones() - 1; - for(int i = 0; i < numVerts; i++){ - Ogre::VertexBoneAssignment vba; - vba.boneIndex = boneIndex; - vba.vertexIndex = i; - vba.weight = 1; - vertexBoneAssignments.push_back(vba); - } - } - } - - if (!hidden) - { - // Add this vertex set to the bounding box - bounds.add(optr, numVerts); - if(saveTheShape) - shapes.push_back(copy); - - // Create the submesh - createOgreSubMesh(shape, material, vertexBoneAssignments); - } -} - -void NIFLoader::calculateTransform() -{ - // Calculate transform - Ogre::Matrix4 transform = Ogre::Matrix4::IDENTITY; - transform = Ogre::Matrix4::getScale(vector) * transform; - - // Check whether we have to flip vertex winding. - // We do have to, if we changed our right hand base. - // We can test it by using the cross product from X and Y and see, if it is a non-negative - // projection on Z. Actually it should be exactly Z, as we don't do non-uniform scaling yet, - // but the test is cheap either way. - Ogre::Matrix3 m3; - transform.extract3x3Matrix(m3); - - if (m3.GetColumn(0).crossProduct(m3.GetColumn(1)).dotProduct(m3.GetColumn(2)) < 0) - { - mFlipVertexWinding = true; - } - - mTransform = transform; -} -void NIFLoader::handleNode(Nif::Node *node, int flags, - const Transformation *trafo, BoundsFinder &bounds, Ogre::Bone *parentBone, std::vector boneSequence) -{ - // Accumulate the flags from all the child nodes. This works for all - // the flags we currently use, at least. - flags |= node->flags; - - // Check for extra data - Extra *e = node; - while (!e->extra.empty()) - { - // Get the next extra data in the list - e = e->extra.getPtr(); - assert(e != NULL); - - if (e->recType == RC_NiStringExtraData) - { - // String markers may contain important information - // affecting the entire subtree of this node - NiStringExtraData *sd = (NiStringExtraData*)e; - - if (sd->string == "NCO") - // No collision. Use an internal flag setting to mark this. - flags |= 0x800; - else if (sd->string == "MRK") - // Marker objects. These are only visible in the - // editor. Until and unless we add an editor component to - // the engine, just skip this entire node. - return; - } - - if (e->recType == RC_NiTextKeyExtraData){ - Nif::NiTextKeyExtraData* extra = dynamic_cast (e); - - std::ofstream file; - - if(mOutputAnimFiles){ - std::string cut = ""; - for(unsigned int i = 0; i < name.length(); i++) - { - if(!(name.at(i) == '\\' || name.at(i) == '/' || name.at(i) == '>' || name.at(i) == '<' || name.at(i) == '?' || name.at(i) == '*' || name.at(i) == '|' || name.at(i) == ':' || name.at(i) == '"')) - { - cut += name.at(i); - } - } - - std::cout << "Outputting " << cut << "\n"; - - file.open((verbosePath + "/Indices" + cut + ".txt").c_str()); - } - - for(std::vector::iterator textiter = extra->list.begin(); textiter != extra->list.end(); textiter++) - { - std::string text = textiter->text; - - replace(text.begin(), text.end(), '\n', '/'); - - text.erase(std::remove(text.begin(), text.end(), '\r'), text.end()); - std::size_t i = 0; - while(i < text.length()){ - while(i < text.length() && text.at(i) == '/' ){ - i++; - } - std::size_t first = i; - int length = 0; - while(i < text.length() && text.at(i) != '/' ){ - i++; - length++; - } - if(first < text.length()){ - //length = text.length() - first; - std::string sub = text.substr(first, length); - - if(mOutputAnimFiles) - file << "Time: " << textiter->time << "|" << sub << "\n"; - - textmappings[sub] = textiter->time; - } - } - } - file.close(); - } - } - - Ogre::Bone *bone = 0; - - // create skeleton or add bones - if (node->recType == RC_NiNode) - { - //FIXME: "Bip01" isn't every time the root bone - if (node->name == "Bip01" || node->name == "Root Bone") //root node, create a skeleton - { - inTheSkeletonTree = true; - - mSkel = Ogre::SkeletonManager::getSingleton().create(getSkeletonName(), resourceGroup, true); - } - else if (!mSkel.isNull() && !parentBone) - inTheSkeletonTree = false; - - if (!mSkel.isNull()) //if there is a skeleton - { - std::string name = node->name; - - // Quick-n-dirty workaround for the fact that several - // bones may have the same name. - if(!mSkel->hasBone(name)) - { - boneSequence.push_back(name); - bone = mSkel->createBone(name); - - if (parentBone) - parentBone->addChild(bone); - - bone->setInheritOrientation(true); - bone->setPosition(node->trafo.pos); - bone->setOrientation(node->trafo.rotation); - } - } - } - Transformation original = node->trafo; - // Apply the parent transformation to this node. We overwrite the - // existing data with the final transformation. - if (trafo) - { - // Get a non-const reference to the node's data, since we're - // overwriting it. TODO: Is this necessary? - Transformation &final = node->trafo; - - // For both position and rotation we have that: - // final_vector = old_vector + old_rotation*new_vector*old_scale - final.pos = trafo->pos + trafo->rotation*final.pos*trafo->scale; - - // Merge the rotations together - final.rotation = trafo->rotation * final.rotation; - - // Scale - final.scale *= trafo->scale; - } - - // For NiNodes, loop through children - if (node->recType == RC_NiNode) - { - NodeList &list = ((NiNode*)node)->children; - int n = list.length(); - for (int i = 0; itrafo, bounds, bone, boneSequence); - } - } - else if (node->recType == RC_NiTriShape && bNiTri) - { - std::string nodename = node->name; - - if (triname == "") - { - handleNiTriShape(dynamic_cast(node), flags, bounds, original, boneSequence); - } - else if(nodename.length() >= triname.length()) - { - std::transform(nodename.begin(), nodename.end(), nodename.begin(), ::tolower); - if(triname == nodename.substr(0, triname.length())) - handleNiTriShape(dynamic_cast(node), flags, bounds, original, boneSequence); - } - } -} void NIFLoader::loadResource(Ogre::Resource *resource) { - inTheSkeletonTree = false; - allanim.clear(); - shapes.clear(); - needBoneAssignments.clear(); - // needBoneAssignments.clear(); - mBoundingBox.setNull(); - mesh = 0; - mSkel.setNull(); - flip = false; - name = resource->getName(); - char suffix = name.at(name.length() - 2); - bool addAnim = true; - bool hasAnim = false; - bool linkSkeleton = true; - //bool baddin = false; - bNiTri = true; - if(name == "meshes\\base_anim.nif" || name == "meshes\\base_animkna.nif") - { - bNiTri = false; - } - - if(suffix == '*') - { - vector = Ogre::Vector3(-1,1,1); - flip = true; - } - else if(suffix == '?'){ - vector = Ogre::Vector3(1,-1,1); - flip = true; - } - else if(suffix == '<'){ - vector = Ogre::Vector3(1,1,-1); - flip = true; - } - else if(suffix == '>') - { - //baddin = true; - bNiTri = true; - std::string sub = name.substr(name.length() - 6, 4); - - if(sub.compare("0000") != 0) - addAnim = false; - - } - else if(suffix == ':') - { - //baddin = true; - linkSkeleton = false; - bNiTri = true; - std::string sub = name.substr(name.length() - 6, 4); - - if(sub.compare("0000") != 0) - addAnim = false; - - } - - switch(name.at(name.length() - 1)) - { - case '"': - triname = "tri chest"; - break; - case '*': - triname = "tri tail"; - break; - case ':': - triname = "tri left foot"; - break; - case '<': - triname = "tri right foot"; - break; - case '>': - triname = "tri left hand"; - break; - case '?': - triname = "tri right hand"; - break; - default: - triname = ""; - break; - } - if(flip) - { - calculateTransform(); - } - // Get the mesh - mesh = dynamic_cast(resource); - assert(mesh); - - // Look it up - resourceName = mesh->getName(); - - - // Helper that computes bounding boxes for us. - BoundsFinder bounds; - - // Load the NIF. TODO: Wrap this in a try-catch block once we're out - // of the early stages of development. Right now we WANT to catch - // every error as early and intrusively as possible, as it's most - // likely a sign of incomplete code rather than faulty input. - NIFFile nif(resourceName); - if (nif.numRecords() < 1) - { - warn("Found no records in NIF."); - return; - } - - // The first record is assumed to be the root node - Record *r = nif.getRecord(0); - assert(r != NULL); - - Nif::Node *node = dynamic_cast(r); - - if (node == NULL) - { - warn("First record in file was not a node, but a " + - r->recName + ". Skipping file."); - return; - } - - // Handle the node - std::vector boneSequence; - - - - handleNode(node, 0, NULL, bounds, 0, boneSequence); - if(addAnim) - { - for(int i = 0; i < nif.numRecords(); i++) - { - Nif::NiKeyframeController *f = dynamic_cast(nif.getRecord(i)); - - if(f != NULL) - { - hasAnim = true; - Nif::Node *o = dynamic_cast(f->target.getPtr()); - Nif::NiKeyframeDataPtr data = f->data; - - if (f->timeStart >= 10000000000000000.0f) - continue; - data->setBonename(o->name); - data->setStartTime(f->timeStart); - data->setStopTime(f->timeStop); - - allanim.push_back(data.get()); - } - } - } - // set the bounding value. - if (bounds.isValid()) - { - mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX(), bounds.minY(), bounds.minZ(), - bounds.maxX(), bounds.maxY(), bounds.maxZ())); - mesh->_setBoundingSphereRadius(bounds.getRadius()); - } - if(hasAnim && addAnim){ - allanimmap[name] = allanim; - alltextmappings[name] = textmappings; - } - if(!mSkel.isNull() && shapes.size() > 0 && addAnim) - { - allshapesmap[name] = shapes; - - } - - if(flip){ - mesh->_setBounds(mBoundingBox, false); - } - - if (!mSkel.isNull() ) - { - for(std::vector::iterator iter = needBoneAssignments.begin(); iter != needBoneAssignments.end(); iter++) - { - int boneIndex = mSkel->getNumBones() - 1; - Ogre::VertexBoneAssignment vba; - vba.boneIndex = boneIndex; - vba.vertexIndex = 0; - vba.weight = 1; - - - (*iter)->addBoneAssignment(vba); - } - //Don't link on npc parts to eliminate redundant skeletons - //Will have to be changed later slightly for robes/skirts - if(linkSkeleton) - mesh->_notifySkeleton(mSkel); - } + warn("Found no records in NIF."); } - - - - Ogre::MeshPtr NIFLoader::load(const std::string &name, const std::string &group) { + Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); - Ogre::MeshManager *m = Ogre::MeshManager::getSingletonPtr(); // Check if the resource already exists - Ogre::ResourcePtr ptr = m->getByName(name, group); - Ogre::MeshPtr themesh; - if (!ptr.isNull()){ - themesh = Ogre::MeshPtr(ptr); - } - else // Nope, create a new one. + Ogre::MeshPtr themesh = meshMgr.getByName(name, group); + if(themesh.isNull()) { - themesh = Ogre::MeshManager::getSingleton().createManual(name, group, NIFLoader::getSingletonPtr()); + static NIFLoader loader; + themesh = meshMgr.createManual(name, group, &loader); } return themesh; } -/* -This function shares much of the same code handleShapes() in MWRender::Animation -This function also creates new position and normal buffers for submeshes. -This function points to existing texture and IndexData buffers -*/ - -std::vector* NIFLoader::getAnim(std::string lowername){ - - std::map,ciLessBoost>::iterator iter = allanimmap.find(lowername); - std::vector* pass = 0; - if(iter != allanimmap.end()) - pass = &(iter->second); - return pass; - -} -std::vector* NIFLoader::getShapes(std::string lowername){ - - std::map,ciLessBoost>::iterator iter = allshapesmap.find(lowername); - std::vector* pass = 0; - if(iter != allshapesmap.end()) - pass = &(iter->second); - return pass; -} - -std::map* NIFLoader::getTextIndices(std::string lowername){ - std::map, ciLessBoost>::iterator iter = alltextmappings.find(lowername); - std::map* pass = 0; - if(iter != alltextmappings.end()) - pass = &(iter->second); - return pass; -} - - - /* More code currently not in use, from the old D source. This was used in the first attempt at loading NIF meshes, where each submesh diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 64efc70c7..bc1ef304c 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -67,12 +67,9 @@ namespace Nif namespace NifOgre { - - /** Manual resource loader for NIF meshes. This is the main class responsible for translating the internal NIF mesh structure into - something Ogre can use. Later it will also handle the insertion of - collision meshes into Bullet / OgreBullet. + something Ogre can use. You have to insert meshes manually into Ogre like this: @@ -86,93 +83,18 @@ namespace NifOgre */ class NIFLoader : Ogre::ManualResourceLoader { - public: - static int numberOfMeshes; - static NIFLoader& getSingleton(); - static NIFLoader* getSingletonPtr(); - - virtual void loadResource(Ogre::Resource *resource); - - static Ogre::MeshPtr load(const std::string &name, - const std::string &group="General"); - //void insertMeshInsideBase(Ogre::Mesh* mesh); - std::vector* getAnim(std::string name); - std::vector* getShapes(std::string name); - std::map* getTextIndices(std::string name); - - - void setOutputAnimFiles(bool output); - void setVerbosePath(std::string path); - - private: - - NIFLoader() : resourceName(""), resourceGroup("General"), flip(false), mNormaliseNormals(false), - mFlipVertexWinding(false), mOutputAnimFiles(false), inTheSkeletonTree(false) {} - NIFLoader(NIFLoader& n) {} - - void calculateTransform(); - - - void warn(std::string msg); - void fail(std::string msg); - - void handleNode( Nif::Node *node, int flags, - const Nif::Transformation *trafo, BoundsFinder &bounds, Ogre::Bone *parentBone, std::vector boneSequence); - - void handleNiTriShape(Nif::NiTriShape *shape, int flags, BoundsFinder &bounds, Nif::Transformation original, std::vector boneSequence); - - void createOgreSubMesh(Nif::NiTriShape *shape, const Ogre::String &material, std::list &vertexBoneAssignments); - - void createMaterial(const Ogre::String &name, - const Ogre::Vector3 &ambient, - const Ogre::Vector3 &diffuse, - const Ogre::Vector3 &specular, - const Ogre::Vector3 &emissive, - float glossiness, float alpha, - int alphaFlags, float alphaTest, - const Ogre::String &texName); - - void findRealTexture(Ogre::String &texName); - - Ogre::String getUniqueName(const Ogre::String &input); - - //returns the skeleton name of this mesh - std::string getSkeletonName() - { - return resourceName + ".skel"; - } - - std::string verbosePath; - std::string resourceName; - std::string resourceGroup; - Ogre::Matrix4 mTransform; - Ogre::AxisAlignedBox mBoundingBox; - bool flip; - bool mNormaliseNormals; - bool mFlipVertexWinding; - bool bNiTri; - bool mOutputAnimFiles; - std::multimap MaterialMap; - - // pointer to the ogre mesh which is currently build - Ogre::Mesh *mesh; - Ogre::SkeletonPtr mSkel; - Ogre::Vector3 vector; - std::vector shapes; - std::string name; - std::string triname; - std::vector allanim; +public: + virtual void loadResource(Ogre::Resource *resource); - std::map textmappings; - std::map,ciLessBoost> alltextmappings; - std::map,ciLessBoost> allanimmap; - std::map,ciLessBoost> allshapesmap; - std::vector mAnim; - std::vector mS; - std::vector needBoneAssignments; - bool inTheSkeletonTree; + static Ogre::MeshPtr load(const std::string &name, + const std::string &group="General"); +private: + NIFLoader() {} + NIFLoader(NIFLoader& n) {} + void warn(const std::string &msg); + void fail(const std::string &msg); }; } diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 11c18010e..61d0c7b0e 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -336,7 +336,7 @@ namespace Physic char uniqueID[8]; sprintf( uniqueID, "%07.3f", scale ); std::string sid = uniqueID; - std::string outputstring = mesh + uniqueID + "\"|"; + std::string outputstring = mesh + uniqueID; //std::cout << "The string" << outputstring << "\n"; //get the shape from the .nif From ecdd4ee23f329cce4d48796f5a27a9af2eb22d5f Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 12 Jul 2012 20:55:11 -0700 Subject: [PATCH 078/298] Load NiMorphData and NiKeyframeData using proper key lists --- apps/openmw/mwrender/animation.cpp | 65 ----- components/nif/data.hpp | 371 ++--------------------------- 2 files changed, 19 insertions(+), 417 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 7e50706f9..13b797734 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -171,71 +171,6 @@ bool Animation::timeIndex(float time, const std::vector ×, int &i, i void Animation::handleAnimationTransforms() { - Ogre::SkeletonInstance* skel = base->getSkeleton(); - - Ogre::Bone* b = skel->getRootBone(); - b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3)); //This is a trick - - skel->_updateTransforms(); - //skel->_notifyManualBonesDirty(); - - base->getAllAnimationStates()->_notifyDirty(); - //base->_updateAnimation(); - //base->_notifyMoved(); - - std::vector::iterator iter; - int slot = 0; - if(transformations) - { - for(iter = transformations->begin(); iter != transformations->end(); iter++) - { - if(time < iter->getStartTime() || time < startTime || time > iter->getStopTime()) - { - slot++; - continue; - } - - float x; - float x2; - - const std::vector &quats = iter->getQuat(); - const std::vector &ttime = iter->gettTime(); - const std::vector &rtime = iter->getrTime(); - const std::vector &translist1 = iter->getTranslist1(); - - int rindexJ = rindexI[slot]; - timeIndex(time, rtime, rindexI[slot], rindexJ, x2); - - int tindexJ = tindexI[slot]; - timeIndex(time, ttime, tindexI[slot], tindexJ, x); - - Ogre::Vector3 t; - Ogre::Quaternion r; - - bool bTrans = translist1.size() > 0; - bool bQuats = quats.size() > 0; - if(skel->hasBone(iter->getBonename())) - { - Ogre::Bone* bone = skel->getBone(iter->getBonename()); - if(bTrans) - { - Ogre::Vector3 v1 = translist1[tindexI[slot]]; - Ogre::Vector3 v2 = translist1[tindexJ]; - t = (v1 + (v2 - v1) * x); - bone->setPosition(t); - } - if(bQuats) - { - r = Ogre::Quaternion::Slerp(x2, quats[rindexI[slot]], quats[rindexJ], true); - bone->setOrientation(r); - } - } - - slot++; - } - skel->_updateTransforms(); - base->getAllAnimationStates()->_notifyDirty(); - } } } diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 2d1e8bd3b..208337f35 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -373,378 +373,45 @@ public: } }; -class NiMorphData : public Record +struct NiMorphData : public Record { - float startTime; - float stopTime; - std::vector initialVertices; - std::vector > relevantTimes; - std::vector > relevantData; - std::vector > additionalVertices; - -public: - float getStartTime() const - { return startTime; } - float getStopTime() const - { return stopTime; } - - void setStartTime(float time) - { startTime = time; } - void setStopTime(float time) - { stopTime = time; } - - const std::vector& getInitialVertices() const - { return initialVertices; } - const std::vector >& getRelevantData() const - { return relevantData; } - const std::vector >& getRelevantTimes() const - { return relevantTimes; } - const std::vector >& getAdditionalVertices() const - { return additionalVertices; } + struct MorphData { + FloatKeyList mData; + std::vector mVertices; + }; + std::vector mMorphs; void read(NIFFile *nif) { int morphCount = nif->getInt(); int vertCount = nif->getInt(); nif->getChar(); - int magic = nif->getInt(); - /*int type =*/ nif->getInt(); - for(int i = 0; i < vertCount; i++) + mMorphs.resize(morphCount); + for(int i=0; igetFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - initialVertices.push_back(Ogre::Vector3(x, y, z)); - } + mMorphs[i].mData.read(nif); - for(int i=1; igetInt(); - /*type =*/ nif->getInt(); - std::vector current; - std::vector currentTime; - for(int i = 0; i < magic; i++) - { - // Time, data, forward, backward tangents - float time = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - current.push_back(Ogre::Vector3(x,y,z)); - currentTime.push_back(time); - //nif->getFloatLen(4*magic); - } - - if(magic) - { - relevantData.push_back(current); - relevantTimes.push_back(currentTime); - } - - std::vector verts; - for(int i = 0; i < vertCount; i++) - { - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - verts.push_back(Ogre::Vector3(x, y, z)); - } - additionalVertices.push_back(verts); + mMorphs[i].mVertices.resize(vertCount); + for(int j = 0;j < vertCount;j++) + mMorphs[i].mVertices[j] = nif->getVector3(); } } }; -class NiKeyframeData : public Record +struct NiKeyframeData : public Record { - std::string bonename; - //Rotations - std::vector quats; - std::vector tbc; - std::vector rottime; - float startTime; - float stopTime; - int rtype; - - //Translations - std::vector translist1; - std::vector translist2; - std::vector translist3; - std::vector transtbc; - std::vector transtime; - int ttype; - - //Scalings - std::vector scalefactor; - std::vector scaletime; - std::vector forwards; - std::vector backwards; - std::vector tbcscale; - int stype; - -public: - void clone(const NiKeyframeData &c) - { - quats = c.getQuat(); - tbc = c.getrTbc(); - rottime = c.getrTime(); - - //types - ttype = c.getTtype(); - rtype = c.getRtype(); - stype = c.getStype(); - - - translist1 = c.getTranslist1(); - translist2 = c.getTranslist2(); - translist3 = c.getTranslist3(); - - transtime = c.gettTime(); - - bonename = c.getBonename(); - } - - void setBonename(std::string bone) - { bonename = bone; } - void setStartTime(float start) - { startTime = start; } - void setStopTime(float end) - { stopTime = end; } + QuaternionKeyList mRotations; + Vector3KeyList mTranslations; + FloatKeyList mScales; void read(NIFFile *nif) { - // Rotations first - int count = nif->getInt(); - //std::vector quat(count); - //std::vector rottime(count); - if(count) - { - //TYPE1 LINEAR_KEY - //TYPE2 QUADRATIC_KEY - //TYPE3 TBC_KEY - //TYPE4 XYZ_ROTATION_KEY - //TYPE5 UNKNOWN_KEY - rtype = nif->getInt(); - //std::cout << "Count: " << count << "Type: " << type << "\n"; - - if(rtype == 1) - { - //We need to actually read in these values instead of skipping them - //nif->skip(count*4*5); // time + quaternion - for (int i = 0; i < count; i++) - { - float time = nif->getFloat(); - float w = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - Ogre::Quaternion quat = Ogre::Quaternion(Ogre::Real(w), Ogre::Real(x), Ogre::Real(y), Ogre::Real(z)); - quats.push_back(quat); - rottime.push_back(time); - //if(time == 0.0 || time > 355.5) - // std::cout <<"Time:" << time << "W:" << w <<"X:" << x << "Y:" << y << "Z:" << z << "\n"; - } - } - else if(rtype == 3) - { - //Example - node 116 in base_anim.nif - for (int i = 0; i < count; i++) - { - float time = nif->getFloat(); - float w = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - - float tbcx = nif->getFloat(); - float tbcy = nif->getFloat(); - float tbcz = nif->getFloat(); - - Ogre::Quaternion quat = Ogre::Quaternion(Ogre::Real(w), Ogre::Real(x), Ogre::Real(y), Ogre::Real(z)); - Ogre::Vector3 vec = Ogre::Vector3(tbcx, tbcy, tbcz); - quats.push_back(quat); - rottime.push_back(time); - tbc.push_back(vec); - //if(time == 0.0 || time > 355.5) - // std::cout <<"Time:" << time << "W:" << w <<"X:" << x << "Y:" << y << "Z:" << z << "\n"; - } - } - else if(rtype == 4) - { - for(int j=0;jgetFloat(); // time - for(int i=0; i<3; i++) - { - int cnt = nif->getInt(); - int type = nif->getInt(); - if(type == 1) - nif->skip(cnt*4*2); // time + unknown - else if(type == 2) - nif->skip(cnt*4*4); // time + unknown vector - else - nif->fail("Unknown sub-rotation type"); - } - } - } - else - nif->fail("Unknown rotation type in NiKeyframeData"); - } - //first = false; - - // Then translation - count = nif->getInt(); - if(count) - { - ttype = nif->getInt(); - - //std::cout << "TransCount:" << count << " Type: " << type << "\n"; - if(ttype == 1) - { - for(int i = 0; i < count; i++) - { - float time = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - - Ogre::Vector3 trans = Ogre::Vector3(x, y, z); - translist1.push_back(trans); - transtime.push_back(time); - } - //nif->getFloatLen(count*4); // time + translation - } - else if(ttype == 2) - { - //Example - node 116 in base_anim.nif - for(int i = 0; i < count; i++) - { - float time = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - float x2 = nif->getFloat(); - float y2 = nif->getFloat(); - float z2 = nif->getFloat(); - float x3 = nif->getFloat(); - float y3 = nif->getFloat(); - float z3 = nif->getFloat(); - - Ogre::Vector3 trans = Ogre::Vector3(x, y, z); - Ogre::Vector3 trans2 = Ogre::Vector3(x2, y2, z2); - Ogre::Vector3 trans3 = Ogre::Vector3(x3, y3, z3); - transtime.push_back(time); - translist1.push_back(trans); - translist2.push_back(trans2); - translist3.push_back(trans3); - } - - //nif->getFloatLen(count*10); // trans1 + forward + backward - } - else if(ttype == 3) - { - for(int i = 0; i < count; i++) - { - float time = nif->getFloat(); - float x = nif->getFloat(); - float y = nif->getFloat(); - float z = nif->getFloat(); - float t = nif->getFloat(); - float b = nif->getFloat(); - float c = nif->getFloat(); - Ogre::Vector3 trans = Ogre::Vector3(x, y, z); - Ogre::Vector3 tbc = Ogre::Vector3(t, b, c); - translist1.push_back(trans); - transtbc.push_back(tbc); - transtime.push_back(time); - } - //nif->getFloatLen(count*7); // trans1 + tension,bias,continuity - } - else nif->fail("Unknown translation type"); - } - - // Finally, scalings - count = nif->getInt(); - if(count) - { - stype = nif->getInt(); - - for(int i = 0; i < count; i++) - { - //int size = 0; - if(stype >= 1 && stype < 4) - { - float time = nif->getFloat(); - float scale = nif->getFloat(); - scaletime.push_back(time); - scalefactor.push_back(scale); - //size = 2; // time+scale - } - else - nif->fail("Unknown scaling type"); - - if(stype == 2) - { - //size = 4; // 1 + forward + backward (floats) - float forward = nif->getFloat(); - float backward = nif->getFloat(); - forwards.push_back(forward); - backwards.push_back(backward); - } - else if(stype == 3) - { - //size = 5; // 1 + tbc - float tbcx = nif->getFloat(); - float tbcy = nif->getFloat(); - float tbcz = nif->getFloat(); - Ogre::Vector3 vec = Ogre::Vector3(tbcx, tbcy, tbcz); - tbcscale.push_back(vec); - } - } - } - else - stype = 0; + mRotations.read(nif); + mTranslations.read(nif); + mScales.read(nif); } - - int getRtype() const - { return rtype; } - int getStype() const - { return stype; } - int getTtype() const - { return ttype; } - float getStartTime() const - { return startTime; } - float getStopTime() const - { return stopTime; } - const std::vector& getQuat() const - { return quats; } - const std::vector& getrTbc() const - { return tbc; } - const std::vector& getrTime() const - { return rottime; } - - const std::vector& getTranslist1() const - { return translist1; } - const std::vector& getTranslist2() const - { return translist2; } - const std::vector& getTranslist3() const - { return translist3; } - const std::vector& gettTime() const - { return transtime; } - const std::vector& getScalefactor() const - { return scalefactor; } - const std::vector& getForwards() const - { return forwards; } - const std::vector& getBackwards() const - { return backwards; } - const std::vector& getScaleTbc() const - { return tbcscale; } - - const std::vector& getsTime() const - { return scaletime; } - const std::string& getBonename() const - { return bonename; } }; } // Namespace From b8384162b6a2e1016ea982898d40376699d722d5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jul 2012 06:16:55 +0200 Subject: [PATCH 079/298] merge --- apps/openmw/mwrender/renderingmanager.cpp | 2 + apps/openmw/mwrender/terrain.cpp | 14 +- apps/openmw/mwrender/terrain.hpp | 2 +- apps/openmw/mwrender/terrainmaterial.cpp | 1772 +-------------------- apps/openmw/mwrender/terrainmaterial.hpp | 248 +-- extern/shiny | 2 +- files/materials/objects.mat | 6 - files/materials/objects.shader | 6 +- files/materials/openmw.configuration | 7 +- files/materials/terrain.shader | 57 + files/materials/terrain.shaderset | 15 + 11 files changed, 173 insertions(+), 1958 deletions(-) create mode 100644 files/materials/terrain.shader create mode 100644 files/materials/terrain.shaderset diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 475e906c4..d8aefaaa0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -105,6 +105,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const Settings::Manager::setBool("enabled", "Shadows", false); sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); + sh::Factory::getInstance ().setGlobalSetting ("fog", "true"); + sh::Factory::getInstance ().setGlobalSetting ("lighting", "true"); applyCompositors(); diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index f14db34e6..7610acadc 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -28,15 +28,16 @@ namespace MWRender mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Z, mLandSize, mWorldSize)), mRendering(rend) { mTerrainGlobals = OGRE_NEW TerrainGlobalOptions(); + TerrainMaterialGeneratorPtr matGen; - TerrainMaterialGeneratorB* matGenP = new TerrainMaterialGeneratorB(); + TerrainMaterial* matGenP = new TerrainMaterial(); matGen.bind(matGenP); mTerrainGlobals->setDefaultMaterialGenerator(matGen); TerrainMaterialGenerator::Profile* const activeProfile = mTerrainGlobals->getDefaultMaterialGenerator() ->getActiveProfile(); - mActiveProfile = static_cast(activeProfile); + mActiveProfile = static_cast(activeProfile); //The pixel error should be as high as possible without it being noticed //as it governs how fast mesh quality decreases. @@ -52,6 +53,7 @@ namespace MWRender //this seemed the distance where it wasn't too noticeable mTerrainGlobals->setCompositeMapDistance(mWorldSize*2); + /* mActiveProfile->setLightmapEnabled(false); mActiveProfile->setLayerSpecularMappingEnabled(false); mActiveProfile->setLayerNormalMappingEnabled(false); @@ -71,6 +73,7 @@ namespace MWRender //composite maps lead to a drastic increase in loading time so are //disabled mActiveProfile->setCompositeMapEnabled(false); + */ mTerrainGroup.setOrigin(Vector3(mWorldSize/2, 0, @@ -181,7 +184,7 @@ namespace MWRender if ( land->landData->usingColours ) { // disable or enable global colour map (depends on available vertex colours) - mActiveProfile->setGlobalColourMapEnabled(true); + //mActiveProfile->setGlobalColourMapEnabled(true); TexturePtr vertex = getVertexColours(land, cellX, cellY, x*(mLandSize-1), @@ -191,7 +194,10 @@ namespace MWRender //this is a hack to get around the fact that Ogre seems to //corrupt the global colour map leading to rendering errors MaterialPtr mat = terrain->getMaterial(); - mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); + /// \todo + //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); + + //mat = terrain->_getCompositeMapMaterial(); //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); } diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp index 7ac48047d..c83d96cf4 100644 --- a/apps/openmw/mwrender/terrain.hpp +++ b/apps/openmw/mwrender/terrain.hpp @@ -46,7 +46,7 @@ namespace MWRender{ RenderingManager* mRendering; - Ogre::TerrainMaterialGeneratorB::SM2Profile* mActiveProfile; + TerrainMaterial::Profile* mActiveProfile; /** * The length in verticies of a single terrain block. diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index a3265b2a5..577b346df 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -1,1746 +1,82 @@ -/* ------------------------------------------------------------------------------ -This source file is part of OGRE -(Object-oriented Graphics Rendering Engine) -For the latest info, see http://www.ogre3d.org/ - -Copyright (c) 2000-2011 Torus Knot Software Ltd - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. ------------------------------------------------------------------------------ -*/ #include "terrainmaterial.hpp" -#include "OgreTerrain.h" -#include "OgreMaterialManager.h" -#include "OgreTechnique.h" -#include "OgrePass.h" -#include "OgreTextureUnitState.h" -#include "OgreGpuProgramManager.h" -#include "OgreHighLevelGpuProgramManager.h" -#include "OgreHardwarePixelBuffer.h" -#include "OgreShadowCameraSetupPSSM.h" -#include -#include "renderingmanager.hpp" +#include -#undef far +#include -namespace Ogre -{ - //--------------------------------------------------------------------- - TerrainMaterialGeneratorB::TerrainMaterialGeneratorB() - { - // define the layers - // We expect terrain textures to have no alpha, so we use the alpha channel - // in the albedo texture to store specular reflection - // similarly we double-up the normal and height (for parallax) - mLayerDecl.samplers.push_back(TerrainLayerSampler("albedo_specular", PF_BYTE_RGBA)); - //mLayerDecl.samplers.push_back(TerrainLayerSampler("normal_height", PF_BYTE_RGBA)); - - mLayerDecl.elements.push_back( - TerrainLayerSamplerElement(0, TLSS_ALBEDO, 0, 3)); - //mLayerDecl.elements.push_back( - // TerrainLayerSamplerElement(0, TLSS_SPECULAR, 3, 1)); - //mLayerDecl.elements.push_back( - // TerrainLayerSamplerElement(1, TLSS_NORMAL, 0, 3)); - //mLayerDecl.elements.push_back( - // TerrainLayerSamplerElement(1, TLSS_HEIGHT, 3, 1)); +namespace MWRender +{ - mProfiles.push_back(OGRE_NEW SM2Profile(this, "SM2", "Profile for rendering on Shader Model 2 capable cards")); - // TODO - check hardware capabilities & use fallbacks if required (more profiles needed) - setActiveProfile("SM2"); + TerrainMaterial::TerrainMaterial() + { + mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("albedo_specular", Ogre::PF_BYTE_RGBA)); + //mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("normal_height", Ogre::PF_BYTE_RGBA)); - } - //--------------------------------------------------------------------- - TerrainMaterialGeneratorB::~TerrainMaterialGeneratorB() - { + mLayerDecl.elements.push_back( + Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_ALBEDO, 0, 3)); + //mLayerDecl.elements.push_back( + // Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_SPECULAR, 3, 1)); + //mLayerDecl.elements.push_back( + // Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_NORMAL, 0, 3)); + //mLayerDecl.elements.push_back( + // Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_HEIGHT, 3, 1)); - } - //--------------------------------------------------------------------- - //--------------------------------------------------------------------- - TerrainMaterialGeneratorB::SM2Profile::SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc) - : Profile(parent, name, desc) - , mShaderGen(0) - , mLayerNormalMappingEnabled(true) - , mLayerParallaxMappingEnabled(true) - , mLayerSpecularMappingEnabled(true) - , mGlobalColourMapEnabled(true) - , mLightmapEnabled(true) - , mCompositeMapEnabled(true) - , mReceiveDynamicShadows(true) - , mPSSM(0) - , mDepthShadows(false) - , mLowLodShadows(false) - , mShadowFar(1300) - { - } - //--------------------------------------------------------------------- - TerrainMaterialGeneratorB::SM2Profile::~SM2Profile() - { - OGRE_DELETE mShaderGen; - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::requestOptions(Terrain* terrain) - { - terrain->_setMorphRequired(true); - terrain->_setNormalMapRequired(true); - terrain->_setLightMapRequired(mLightmapEnabled, true); - terrain->_setCompositeMapRequired(mCompositeMapEnabled); - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setShadowFar(float far) - { - if (mShadowFar != far) - { - mShadowFar = far; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setShadowFadeStart(float fadestart) - { - if (mShadowFadeStart != fadestart) - { - mShadowFadeStart = fadestart; - mParent->_markChanged(); - } + mProfiles.push_back(OGRE_NEW Profile(this, "SM2", "Profile for rendering on Shader Model 2 capable cards")); + setActiveProfile("SM2"); } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setLayerNormalMappingEnabled(bool enabled) - { - if (enabled != mLayerNormalMappingEnabled) - { - mLayerNormalMappingEnabled = enabled; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setLayerParallaxMappingEnabled(bool enabled) - { - if (enabled != mLayerParallaxMappingEnabled) - { - mLayerParallaxMappingEnabled = enabled; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setLayerSpecularMappingEnabled(bool enabled) - { - if (enabled != mLayerSpecularMappingEnabled) - { - mLayerSpecularMappingEnabled = enabled; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setGlobalColourMapEnabled(bool enabled) - { - if (enabled != mGlobalColourMapEnabled) - { - mGlobalColourMapEnabled = enabled; - //mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setLightmapEnabled(bool enabled) - { - if (enabled != mLightmapEnabled) - { - mLightmapEnabled = enabled; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setCompositeMapEnabled(bool enabled) - { - if (enabled != mCompositeMapEnabled) - { - mCompositeMapEnabled = enabled; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsEnabled(bool enabled) - { - if (enabled != mReceiveDynamicShadows) - { - mReceiveDynamicShadows = enabled; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings) - { - if (pssmSettings != mPSSM) - { - mPSSM = pssmSettings; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsDepth(bool enabled) - { - if (enabled != mDepthShadows) - { - mDepthShadows = enabled; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsLowLod(bool enabled) - { - if (enabled != mLowLodShadows) - { - mLowLodShadows = enabled; - mParent->_markChanged(); - } - } - //--------------------------------------------------------------------- - uint8 TerrainMaterialGeneratorB::SM2Profile::getMaxLayers(const Terrain* terrain) const - { - // count the texture units free - uint8 freeTextureUnits = 16; - // lightmap - if (mLightmapEnabled) - --freeTextureUnits; - // normalmap - --freeTextureUnits; - // colourmap - //if (terrain->getGlobalColourMapEnabled()) - --freeTextureUnits; - if (isShadowingEnabled(HIGH_LOD, terrain)) - { - uint numShadowTextures = 1; - if (getReceiveDynamicShadowsPSSM()) - { - numShadowTextures = getReceiveDynamicShadowsPSSM()->getSplitCount(); - } - freeTextureUnits -= numShadowTextures; - } + // ----------------------------------------------------------------------------------------------------------------------- - // each layer needs 2.25 units (1xdiffusespec, (1xnormalheight), 0.25xblend) - return static_cast(freeTextureUnits / (1.25f + (mLayerNormalMappingEnabled||mLayerParallaxMappingEnabled))); - - - } - int TerrainMaterialGeneratorB::SM2Profile::getNumberOfLightsSupported() const + TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc) + : Ogre::TerrainMaterialGenerator::Profile(parent, name, desc) { - return Settings::Manager::getInt("num lights", "Terrain"); } - //--------------------------------------------------------------------- - MaterialPtr TerrainMaterialGeneratorB::SM2Profile::generate(const Terrain* terrain) - { - // re-use old material if exists - MaterialPtr mat = terrain->_getMaterial(); - if (mat.isNull()) - { - MaterialManager& matMgr = MaterialManager::getSingleton(); - - // it's important that the names are deterministic for a given terrain, so - // use the terrain pointer as an ID - const String& matName = terrain->getMaterialName(); - mat = matMgr.getByName(matName); - if (mat.isNull()) - { - mat = matMgr.create(matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - } - } - // clear everything - mat->removeAllTechniques(); - - // Automatically disable normal & parallax mapping if card cannot handle it - // We do this rather than having a specific technique for it since it's simpler - GpuProgramManager& gmgr = GpuProgramManager::getSingleton(); - if (!gmgr.isSyntaxSupported("ps_3_0") && !gmgr.isSyntaxSupported("ps_2_x") - && !gmgr.isSyntaxSupported("fp40") && !gmgr.isSyntaxSupported("arbfp1")) - { - setLayerNormalMappingEnabled(false); - setLayerParallaxMappingEnabled(false); - } - - addTechnique(mat, terrain, HIGH_LOD); - - // LOD - if(mCompositeMapEnabled) - { - addTechnique(mat, terrain, LOW_LOD); - Material::LodValueList lodValues; - lodValues.push_back(TerrainGlobalOptions::getSingleton().getCompositeMapDistance()); - mat->setLodLevels(lodValues); - Technique* lowLodTechnique = mat->getTechnique(1); - lowLodTechnique->setLodIndex(1); - } - - updateParams(mat, terrain); - - return mat; - - } - //--------------------------------------------------------------------- - MaterialPtr TerrainMaterialGeneratorB::SM2Profile::generateForCompositeMap(const Terrain* terrain) - { - // re-use old material if exists - MaterialPtr mat = terrain->_getCompositeMapMaterial(); - if (mat.isNull()) - { - MaterialManager& matMgr = MaterialManager::getSingleton(); - - // it's important that the names are deterministic for a given terrain, so - // use the terrain pointer as an ID - const String& matName = terrain->getMaterialName() + "/comp"; - mat = matMgr.getByName(matName); - if (mat.isNull()) - { - mat = matMgr.create(matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - } - } - // clear everything - mat->removeAllTechniques(); - - addTechnique(mat, terrain, RENDER_COMPOSITE_MAP); - - updateParamsForCompositeMap(mat, terrain); - - return mat; - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::addTechnique( - const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt) - { - - Technique* tech = mat->createTechnique(); - - // Only supporting one pass - Pass* pass = tech->createPass(); - - GpuProgramManager& gmgr = GpuProgramManager::getSingleton(); - HighLevelGpuProgramManager& hmgr = HighLevelGpuProgramManager::getSingleton(); - if (!mShaderGen) - { - bool check2x = mLayerNormalMappingEnabled || mLayerParallaxMappingEnabled; - if (hmgr.isLanguageSupported("cg")) - mShaderGen = OGRE_NEW ShaderHelperCg(); - else if (hmgr.isLanguageSupported("hlsl") && - ((check2x && gmgr.isSyntaxSupported("ps_2_x")) || - (!check2x && gmgr.isSyntaxSupported("ps_2_0")))) - mShaderGen = OGRE_NEW ShaderHelperHLSL(); - else if (hmgr.isLanguageSupported("glsl")) - mShaderGen = OGRE_NEW ShaderHelperGLSL(); - else - { - // todo - } - - // check SM3 features - mSM3Available = GpuProgramManager::getSingleton().isSyntaxSupported("ps_3_0"); - - } - HighLevelGpuProgramPtr vprog = mShaderGen->generateVertexProgram(this, terrain, tt); - HighLevelGpuProgramPtr fprog = mShaderGen->generateFragmentProgram(this, terrain, tt); - - pass->setVertexProgram(vprog->getName()); - pass->setFragmentProgram(fprog->getName()); - - if (tt == HIGH_LOD || tt == RENDER_COMPOSITE_MAP) - { - // global normal map - TextureUnitState* tu = pass->createTextureUnitState(); - tu->setTextureName(terrain->getTerrainNormalMap()->getName()); - tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); - - // global colour map - if (isGlobalColourMapEnabled()) - { - tu = pass->createTextureUnitState(""); - tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); - } - - // light map - if (isLightmapEnabled()) - { - tu = pass->createTextureUnitState(terrain->getLightmap()->getName()); - tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); - } - - // blend maps - uint maxLayers = getMaxLayers(terrain); - uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); - uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); - for (uint i = 0; i < numBlendTextures; ++i) - { - tu = pass->createTextureUnitState(terrain->getBlendTextureName(i)); - tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); - } - - // layer textures - for (uint i = 0; i < numLayers; ++i) - { - // diffuse / specular - tu = pass->createTextureUnitState(terrain->getLayerTextureName(i, 0)); - - // normal / height - if (mLayerNormalMappingEnabled || mLayerParallaxMappingEnabled) - tu = pass->createTextureUnitState(terrain->getLayerTextureName(i, 1)); - } - - } - else - { - // LOW_LOD textures - // composite map - TextureUnitState* tu = pass->createTextureUnitState(); - tu->setTextureName(terrain->getCompositeMap()->getName()); - tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); - - // That's it! - - } - - // Add shadow textures (always at the end) - if (isShadowingEnabled(tt, terrain)) - { - uint numTextures = 1; - if (getReceiveDynamicShadowsPSSM()) - { - numTextures = getReceiveDynamicShadowsPSSM()->getSplitCount(); - } - for (uint i = 0; i < numTextures; ++i) - { - TextureUnitState* tu = pass->createTextureUnitState(); - tu->setContentType(TextureUnitState::CONTENT_SHADOW); - tu->setTextureAddressingMode(TextureUnitState::TAM_BORDER); - tu->setTextureBorderColour(ColourValue::White); - } - } - - } - //--------------------------------------------------------------------- - bool TerrainMaterialGeneratorB::SM2Profile::isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const - { - return getReceiveDynamicShadowsEnabled() && tt != RENDER_COMPOSITE_MAP && - (tt != LOW_LOD || mLowLodShadows) && - terrain->getSceneManager()->isShadowTechniqueTextureBased(); - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::updateParams(const MaterialPtr& mat, const Terrain* terrain) - { - mShaderGen->updateParams(this, mat, terrain, false); - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain) - { - mShaderGen->updateParams(this, mat, terrain, true); - } - //--------------------------------------------------------------------- - //--------------------------------------------------------------------- - HighLevelGpuProgramPtr - TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateVertexProgram( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - HighLevelGpuProgramPtr ret = createVertexProgram(prof, terrain, tt); - - StringUtil::StrStreamType sourceStr; - generateVertexProgramSource(prof, terrain, tt, sourceStr); - ret->setSource(sourceStr.str()); - ret->load(); - defaultVpParams(prof, terrain, tt, ret); -#if OGRE_DEBUG_MODE - LogManager::getSingleton().stream(LML_TRIVIAL) << "*** Terrain Vertex Program: " - << ret->getName() << " ***\n" << ret->getSource() << "\n*** ***"; -#endif - - return ret; - - } - //--------------------------------------------------------------------- - HighLevelGpuProgramPtr - TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateFragmentProgram( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - HighLevelGpuProgramPtr ret = createFragmentProgram(prof, terrain, tt); - - StringUtil::StrStreamType sourceStr; - generateFragmentProgramSource(prof, terrain, tt, sourceStr); - - ret->setSource(sourceStr.str()); - ret->load(); - defaultFpParams(prof, terrain, tt, ret); - -#if OGRE_DEBUG_MODE - LogManager::getSingleton().stream(LML_TRIVIAL) << "*** Terrain Fragment Program: " - << ret->getName() << " ***\n" << ret->getSource() << "\n*** ***"; -#endif - - return ret; - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateVertexProgramSource( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - generateVpHeader(prof, terrain, tt, outStream); - - if (tt != LOW_LOD) - { - uint maxLayers = prof->getMaxLayers(terrain); - uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); - - for (uint i = 0; i < numLayers; ++i) - generateVpLayer(prof, terrain, tt, i, outStream); - } - - generateVpFooter(prof, terrain, tt, outStream); - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateFragmentProgramSource( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - generateFpHeader(prof, terrain, tt, outStream); - - if (tt != LOW_LOD) - { - uint maxLayers = prof->getMaxLayers(terrain); - uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); - - for (uint i = 0; i < numLayers; ++i) - generateFpLayer(prof, terrain, tt, i, outStream); - } - - generateFpFooter(prof, terrain, tt, outStream); - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::defaultVpParams( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog) - { - GpuProgramParametersSharedPtr params = prog->getDefaultParameters(); - params->setIgnoreMissingParams(true); - params->setNamedAutoConstant("worldMatrix", GpuProgramParameters::ACT_WORLD_MATRIX); - params->setNamedAutoConstant("viewProjMatrix", GpuProgramParameters::ACT_VIEWPROJ_MATRIX); - params->setNamedAutoConstant("lodMorph", GpuProgramParameters::ACT_CUSTOM, - Terrain::LOD_MORPH_CUSTOM_PARAM); - - if (prof->isShadowingEnabled(tt, terrain)) - { - uint numTextures = 1; - if (prof->getReceiveDynamicShadowsPSSM()) - { - numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); - } - for (uint i = 0; i < numTextures; ++i) - { - params->setNamedAutoConstant("texViewProjMatrix" + StringConverter::toString(i), - GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, i); - if (prof->getReceiveDynamicShadowsDepth()) - { - //params->setNamedAutoConstant("depthRange" + StringConverter::toString(i), - //GpuProgramParameters::ACT_SHADOW_SCENE_DEPTH_RANGE, i); - } - } - } - - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::defaultFpParams( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog) - { - GpuProgramParametersSharedPtr params = prog->getDefaultParameters(); - params->setIgnoreMissingParams(true); - - params->setNamedAutoConstant("ambient", GpuProgramParameters::ACT_AMBIENT_LIGHT_COLOUR); - - for (int i=0; igetNumberOfLightsSupported(); ++i) - { - params->setNamedAutoConstant("lightPosObjSpace"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE, i); - params->setNamedAutoConstant("lightDiffuseColour"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_DIFFUSE_COLOUR, i); - if (i > 0) - params->setNamedAutoConstant("lightAttenuation"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_ATTENUATION, i); - //params->setNamedAutoConstant("lightSpecularColour"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_SPECULAR_COLOUR, i); - } - - if (MWRender::RenderingManager::useMRT()) - params->setNamedAutoConstant("far", GpuProgramParameters::ACT_FAR_CLIP_DISTANCE); - - params->setNamedAutoConstant("eyePosObjSpace", GpuProgramParameters::ACT_CAMERA_POSITION_OBJECT_SPACE); - params->setNamedAutoConstant("fogColour", GpuProgramParameters::ACT_FOG_COLOUR); - params->setNamedAutoConstant("fogParams", GpuProgramParameters::ACT_FOG_PARAMS); - - if (prof->isShadowingEnabled(tt, terrain)) - { - params->setNamedConstant("shadowFar_fadeStart", Vector4(prof->mShadowFar, prof->mShadowFadeStart * prof->mShadowFar, 0, 0)); - uint numTextures = 1; - if (prof->getReceiveDynamicShadowsPSSM()) - { - PSSMShadowCameraSetup* pssm = prof->getReceiveDynamicShadowsPSSM(); - numTextures = pssm->getSplitCount(); - Vector4 splitPoints; - const PSSMShadowCameraSetup::SplitPointList& splitPointList = pssm->getSplitPoints(); - // Populate from split point 1, not 0, since split 0 isn't useful (usually 0) - for (uint i = 1; i < numTextures; ++i) - { - splitPoints[i-1] = splitPointList[i]; - } - params->setNamedConstant("pssmSplitPoints", splitPoints); - } - - if (prof->getReceiveDynamicShadowsDepth()) - { - size_t samplerOffset = (tt == HIGH_LOD) ? mShadowSamplerStartHi : mShadowSamplerStartLo; - for (uint i = 0; i < numTextures; ++i) - { - params->setNamedAutoConstant("inverseShadowmapSize" + StringConverter::toString(i), - GpuProgramParameters::ACT_INVERSE_TEXTURE_SIZE, i + samplerOffset); - } - } - } - - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateParams( - const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap) - { - Pass* p = mat->getTechnique(0)->getPass(0); - if (compositeMap) - { - updateVpParams(prof, terrain, RENDER_COMPOSITE_MAP, p->getVertexProgramParameters()); - updateFpParams(prof, terrain, RENDER_COMPOSITE_MAP, p->getFragmentProgramParameters()); - } - else - { - // high lod - updateVpParams(prof, terrain, HIGH_LOD, p->getVertexProgramParameters()); - updateFpParams(prof, terrain, HIGH_LOD, p->getFragmentProgramParameters()); - - if(prof->isCompositeMapEnabled()) - { - // low lod - p = mat->getTechnique(1)->getPass(0); - updateVpParams(prof, terrain, LOW_LOD, p->getVertexProgramParameters()); - updateFpParams(prof, terrain, LOW_LOD, p->getFragmentProgramParameters()); - } - } - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateVpParams( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params) - { - params->setIgnoreMissingParams(true); - uint maxLayers = prof->getMaxLayers(terrain); - uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); - uint numUVMul = numLayers / 4; - if (numLayers % 4) - ++numUVMul; - for (uint i = 0; i < numUVMul; ++i) - { - Vector4 uvMul( - terrain->getLayerUVMultiplier(i * 4), - terrain->getLayerUVMultiplier(i * 4 + 1), - terrain->getLayerUVMultiplier(i * 4 + 2), - terrain->getLayerUVMultiplier(i * 4 + 3) - ); - params->setNamedConstant("uvMul" + StringConverter::toString(i), uvMul); - } - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateFpParams( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params) - { - params->setIgnoreMissingParams(true); - // TODO - parameterise this? - Vector4 scaleBiasSpecular(0.03, -0.04, 32, 1); - params->setNamedConstant("scaleBiasSpecular", scaleBiasSpecular); - - } - //--------------------------------------------------------------------- - String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getChannel(uint idx) - { - uint rem = idx % 4; - switch(rem) - { - case 0: - default: - return "r"; - case 1: - return "g"; - case 2: - return "b"; - case 3: - return "a"; - }; - } - //--------------------------------------------------------------------- - String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getVertexProgramName( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - String progName = terrain->getMaterialName() + "/sm2/vp"; - - switch(tt) - { - case HIGH_LOD: - progName += "/hlod"; - break; - case LOW_LOD: - progName += "/llod"; - break; - case RENDER_COMPOSITE_MAP: - progName += "/comp"; - break; - } - - return progName; - - } - //--------------------------------------------------------------------- - String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getFragmentProgramName( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - - String progName = terrain->getMaterialName() + "/sm2/fp"; - - switch(tt) - { - case HIGH_LOD: - progName += "/hlod"; - break; - case LOW_LOD: - progName += "/llod"; - break; - case RENDER_COMPOSITE_MAP: - progName += "/comp"; - break; - } - - return progName; - } - //--------------------------------------------------------------------- - //--------------------------------------------------------------------- - HighLevelGpuProgramPtr - TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::createVertexProgram( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - String progName = getVertexProgramName(prof, terrain, tt); - HighLevelGpuProgramPtr ret = mgr.getByName(progName); - if (ret.isNull()) - { - ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_VERTEX_PROGRAM); - } - else - { - ret->unload(); - } - - ret->setParameter("profiles", "vs_3_0 vs_2_0 vp40 arbvp1"); - ret->setParameter("entry_point", "main_vp"); - - return ret; - - } - //--------------------------------------------------------------------- - HighLevelGpuProgramPtr - TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::createFragmentProgram( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - String progName = getFragmentProgramName(prof, terrain, tt); - - HighLevelGpuProgramPtr ret = mgr.getByName(progName); - if (ret.isNull()) - { - ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "cg", GPT_FRAGMENT_PROGRAM); - } - else - { - ret->unload(); - } - - ret->setParameter("profiles", "ps_3_0 ps_2_x fp40 arbfp1"); - ret->setParameter("entry_point", "main_fp"); - - return ret; - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpHeader( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - outStream << - "void main_vp(\n" - "float4 pos : POSITION,\n" - "float2 uv : TEXCOORD0,\n"; - if (tt != RENDER_COMPOSITE_MAP) - outStream << "float2 delta : TEXCOORD1,\n"; // lodDelta, lodThreshold - - outStream << - "uniform float4x4 worldMatrix,\n" - "uniform float4x4 viewProjMatrix,\n" - "uniform float2 lodMorph,\n"; // morph amount, morph LOD target - - // uv multipliers - uint maxLayers = prof->getMaxLayers(terrain); - uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); - uint numUVMultipliers = (numLayers / 4); - if (numLayers % 4) - ++numUVMultipliers; - for (uint i = 0; i < numUVMultipliers; ++i) - outStream << "uniform float4 uvMul" << i << ", \n"; - - outStream << - "out float4 oPos : POSITION,\n" - "out float4 oPosObj : TEXCOORD0 \n"; - - uint texCoordSet = 1; - outStream << - ", out float4 oUVMisc : COLOR0 // xy = uv, z = camDepth\n"; - - // layer UV's premultiplied, packed as xy/zw - uint numUVSets = numLayers / 2; - if (numLayers % 2) - ++numUVSets; - if (tt != LOW_LOD) - { - for (uint i = 0; i < numUVSets; ++i) - { - outStream << - ", out float4 oUV" << i << " : TEXCOORD" << texCoordSet++ << "\n"; - } - } - - if (prof->getParent()->getDebugLevel() && tt != RENDER_COMPOSITE_MAP) - { - outStream << ", out float2 lodInfo : TEXCOORD" << texCoordSet++ << "\n"; - } - - if (prof->isShadowingEnabled(tt, terrain)) - { - texCoordSet = generateVpDynamicShadowsParams(texCoordSet, prof, terrain, tt, outStream); - } - - // check we haven't exceeded texture coordinates - if (texCoordSet > 8) - { - OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, - "Requested options require too many texture coordinate sets! Try reducing the number of layers. requested: " + StringConverter::toString(texCoordSet), - __FUNCTION__); - } - - outStream << - ")\n" - "{\n" - " float4 worldPos = mul(worldMatrix, pos);\n" - " oPosObj = pos;\n"; - - if (tt != RENDER_COMPOSITE_MAP) - { - // determine whether to apply the LOD morph to this vertex - // we store the deltas against all vertices so we only want to apply - // the morph to the ones which would disappear. The target LOD which is - // being morphed to is stored in lodMorph.y, and the LOD at which - // the vertex should be morphed is stored in uv.w. If we subtract - // the former from the latter, and arrange to only morph if the - // result is negative (it will only be -1 in fact, since after that - // the vertex will never be indexed), we will achieve our aim. - // sign(vertexLOD - targetLOD) == -1 is to morph - outStream << - " float toMorph = -min(0, sign(delta.y - lodMorph.y));\n"; - // this will either be 1 (morph) or 0 (don't morph) - if (prof->getParent()->getDebugLevel()) - { - // x == LOD level (-1 since value is target level, we want to display actual) - outStream << "lodInfo.x = (lodMorph.y - 1) / " << terrain->getNumLodLevels() << ";\n"; - // y == LOD morph - outStream << "lodInfo.y = toMorph * lodMorph.x;\n"; - } - - // morph - switch (terrain->getAlignment()) - { - case Terrain::ALIGN_X_Y: - outStream << " worldPos.z += delta.x * toMorph * lodMorph.x;\n"; - break; - case Terrain::ALIGN_X_Z: - outStream << " worldPos.y += delta.x * toMorph * lodMorph.x;\n"; - break; - case Terrain::ALIGN_Y_Z: - outStream << " worldPos.x += delta.x * toMorph * lodMorph.x;\n"; - break; - }; - } - - - // generate UVs - if (tt != LOW_LOD) - { - for (uint i = 0; i < numUVSets; ++i) - { - uint layer = i * 2; - uint uvMulIdx = layer / 4; - - outStream << - " oUV" << i << ".xy = " << " uv.xy * uvMul" << uvMulIdx << "." << getChannel(layer) << ";\n"; - outStream << - " oUV" << i << ".zw = " << " uv.xy * uvMul" << uvMulIdx << "." << getChannel(layer+1) << ";\n"; - - } - - } - - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpHeader( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - - // Main header - outStream << - // helpers - "float4 expand(float4 v)\n" - "{ \n" - " return v * 2 - 1;\n" - "}\n\n\n"; - - if (prof->isShadowingEnabled(tt, terrain)) - generateFpDynamicShadowsHelpers(prof, terrain, tt, outStream); - - - outStream << - "void main_fp(\n" - "float4 position : TEXCOORD0,\n"; - - uint texCoordSet = 1; - outStream << - "float4 uvMisc : COLOR0,\n"; - - // UV's premultiplied, packed as xy/zw - uint maxLayers = prof->getMaxLayers(terrain); - uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); - uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); - uint numUVSets = numLayers / 2; - if (numLayers % 2) - ++numUVSets; - if (tt != LOW_LOD) - { - for (uint i = 0; i < numUVSets; ++i) - { - outStream << - "float4 layerUV" << i << " : TEXCOORD" << texCoordSet++ << ", \n"; - } - } - if (prof->getParent()->getDebugLevel() && tt != RENDER_COMPOSITE_MAP) - { - outStream << "float2 lodInfo : TEXCOORD" << texCoordSet++ << ", \n"; - } - - bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; - if (fog) - { - outStream << - "uniform float4 fogParams, \n" - "uniform float3 fogColour, \n"; - } - - uint currentSamplerIdx = 0; - - outStream << - // Only 1 light supported in this version - // deferred shading profile / generator later, ok? :) - "uniform float3 ambient,\n"; - - - for (int i=0; igetNumberOfLightsSupported(); ++i) - { - outStream << - "uniform float4 lightPosObjSpace"< 0) - outStream << - "uniform float4 lightAttenuation"<isGlobalColourMapEnabled()) - { - outStream << ", uniform sampler2D globalColourMap : register(s" - << currentSamplerIdx++ << ")\n"; - } - if (prof->isLightmapEnabled()) - { - outStream << ", uniform sampler2D lightMap : register(s" - << currentSamplerIdx++ << ")\n"; - } - // Blend textures - sampler definitions - for (uint i = 0; i < numBlendTextures; ++i) - { - outStream << ", uniform sampler2D blendTex" << i - << " : register(s" << currentSamplerIdx++ << ")\n"; - } - - // Layer textures - sampler definitions & UV multipliers - for (uint i = 0; i < numLayers; ++i) - { - outStream << ", uniform sampler2D difftex" << i - << " : register(s" << currentSamplerIdx++ << ")\n"; - - if (prof->mLayerNormalMappingEnabled || prof->mLayerParallaxMappingEnabled) - outStream << ", uniform sampler2D normtex" << i - << " : register(s" << currentSamplerIdx++ << ")\n"; - } - } - - if (prof->isShadowingEnabled(tt, terrain)) - { - generateFpDynamicShadowsParams(&texCoordSet, ¤tSamplerIdx, prof, terrain, tt, outStream); - } - - // check we haven't exceeded samplers - if (currentSamplerIdx > 16) - { - OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, - "Requested options require too many texture samplers! Try reducing the number of layers.", - __FUNCTION__); - } - - if (MWRender::RenderingManager::useMRT()) outStream << - " , out float4 oColor : COLOR \n" - " , out float4 oColor1 : COLOR1 \n" - " , uniform float far \n"; - else outStream << - " , out float4 oColor : COLOR \n"; - - outStream << - ")\n" - "{\n" - " float4 outputCol;\n" - " float shadow = 1.0;\n" - " float2 uv = uvMisc.xy;\n" - // base colour - " outputCol = float4(0,0,0,1);\n"; - - if (tt != LOW_LOD) - { - outStream << - // global normal - " float3 normal = expand(tex2D(globalNormal, uv)).rgb;\n"; - - // not needed anymore apparently - //" normal = float3(normal.x, normal.z, -normal.y); \n"; // convert Ogre to MW coordinate system - - } - - for (int i=0; igetNumberOfLightsSupported(); ++i) - outStream << - " float3 lightDir"<isLayerNormalMappingEnabled()) - { - // derive the tangent space basis - // we do this in the pixel shader because we don't have per-vertex normals - // because of the LOD, we use a normal map - // tangent is always +x or -z in object space depending on alignment - switch(terrain->getAlignment()) - { - case Terrain::ALIGN_X_Y: - case Terrain::ALIGN_X_Z: - outStream << " float3 tangent = float3(1, 0, 0);\n"; - break; - case Terrain::ALIGN_Y_Z: - outStream << " float3 tangent = float3(0, 0, -1);\n"; - break; - }; - - outStream << " float3 binormal = normalize(cross(tangent, normal));\n"; - // note, now we need to re-cross to derive tangent again because it wasn't orthonormal - outStream << " tangent = normalize(cross(normal, binormal));\n"; - // derive final matrix - outStream << " float3x3 TBN = float3x3(tangent, binormal, normal);\n"; - - // set up lighting result placeholders for interpolation - outStream << " float4 litRes, litResLayer;\n"; - outStream << " float3 TSlightDir, TSeyeDir, TShalfAngle, TSnormal;\n"; - if (prof->isLayerParallaxMappingEnabled()) - outStream << " float displacement;\n"; - // move - outStream << " TSlightDir = normalize(mul(TBN, lightDir));\n"; - outStream << " TSeyeDir = normalize(mul(TBN, eyeDir));\n"; - - } - else - { - if (prof->getNumberOfLightsSupported() > 1) - outStream << "float d; \n" - "float attn; \n"; - - outStream << - " eyeDir = normalize(eyeDir); \n"; - - // simple per-pixel lighting with no normal mapping - for (int i=0; igetNumberOfLightsSupported(); ++i) - { - outStream << - " float4 litRes"< 0) - outStream << - // pre-multiply light color with attenuation factor - "d = length( lightDir"<_isSM3Available()) - outStream << " if (" << blendWeightStr << " > 0.0003)\n { \n"; - - - // generate UV - outStream << " float2 uv" << layer << " = layerUV" << uvIdx << uvChannels << ";\n"; - - // calculate lighting here if normal mapping - if (prof->isLayerNormalMappingEnabled()) - { - if (prof->isLayerParallaxMappingEnabled() && tt != RENDER_COMPOSITE_MAP) - { - // modify UV - note we have to sample an extra time - outStream << " displacement = tex2D(normtex" << layer << ", uv" << layer << ").a\n" - " * scaleBiasSpecular.x + scaleBiasSpecular.y;\n"; - outStream << " uv" << layer << " += TSeyeDir.xy * displacement;\n"; - } - - // access TS normal map - outStream << " TSnormal = expand(tex2D(normtex" << layer << ", uv" << layer << ")).rgb;\n"; - outStream << " TShalfAngle = normalize(TSlightDir + TSeyeDir);\n"; - outStream << " litResLayer = lit(dot(TSlightDir, TSnormal), dot(TShalfAngle, TSnormal), scaleBiasSpecular.z);\n"; - if (!layer) - outStream << " litRes = litResLayer;\n"; - else - outStream << " litRes = lerp(litRes, litResLayer, " << blendWeightStr << ");\n"; - - } - - // sample diffuse texture - outStream << " float4 diffuseSpecTex" << layer - << " = tex2D(difftex" << layer << ", uv" << layer << ");\n"; - - // apply to common - if (!layer) - { - outStream << " diffuse = diffuseSpecTex0.rgb;\n"; - if (prof->isLayerSpecularMappingEnabled()) - outStream << " specular = diffuseSpecTex0.a;\n"; - } - else - { - outStream << " diffuse = lerp(diffuse, diffuseSpecTex" << layer - << ".rgb, " << blendWeightStr << ");\n"; - if (prof->isLayerSpecularMappingEnabled()) - outStream << " specular = lerp(specular, diffuseSpecTex" << layer - << ".a, " << blendWeightStr << ");\n"; - - } - - // End early-out - // Disable - causing some issues even when trying to force the use of texldd - - // comment by scrawl: - // on a NVIDIA card in opengl mode, didn't produce any problems, - // while increasing FPS from 170 to 185 (!!!) in the same area - // so let's try this out - if something does cause problems for - // someone else (with a different card / renderer) we can just - // add a vendor-specific check here - if (layer && prof->_isSM3Available()) - outStream << " } // early-out blend value\n"; - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpFooter( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - - outStream << - " oPos = mul(viewProjMatrix, worldPos);\n" - " oUVMisc.xy = uv.xy;\n"; - - outStream << - " // pass cam depth\n" - " oPosObj.w = oPos.z;\n"; - - if (prof->isShadowingEnabled(tt, terrain)) - generateVpDynamicShadows(prof, terrain, tt, outStream); - - outStream << - "}\n"; - - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpFooter( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - - if (tt == LOW_LOD) - { - if (prof->isShadowingEnabled(tt, terrain)) - { - generateFpDynamicShadows(prof, terrain, tt, outStream); - outStream << - " outputCol.rgb = diffuse * rtshadow;\n"; - } - else - { - outStream << - " outputCol.rgb = diffuse;\n"; - } - } - else - { - if (prof->isGlobalColourMapEnabled()) - { - // sample colour map and apply to diffuse - outStream << " diffuse *= tex2D(globalColourMap, uv).rgb;\n"; - } - if (prof->isLightmapEnabled()) - { - // sample lightmap - outStream << " shadow = tex2D(lightMap, uv).r;\n"; - } - - if (prof->isShadowingEnabled(tt, terrain)) - { - generateFpDynamicShadows(prof, terrain, tt, outStream); - } - - outStream << " outputCol.rgb += ambient * diffuse; \n"; - - // diffuse lighting - for (int i=0; igetNumberOfLightsSupported(); ++i) - { - // shadows only for first light (directional) - if (i==0) - outStream << " outputCol.rgb += litRes"<isLayerSpecularMappingEnabled()) - outStream << " specular = 0.0;\n"; - - if (tt == RENDER_COMPOSITE_MAP) - { - // Lighting embedded in alpha - outStream << - " outputCol.a = shadow;\n"; - - } - else - { - // Apply specular - //outStream << " outputCol.rgb += litRes.z * lightSpecularColour * specular * shadow;\n"; - - if (prof->getParent()->getDebugLevel()) - { - outStream << " outputCol.rg += lodInfo.xy;\n"; - } - } - } - - bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; - if (fog) - { - if (terrain->getSceneManager()->getFogMode() == FOG_LINEAR) - { - outStream << - " float fogVal = saturate((position.w - fogParams.y) * fogParams.w);\n"; - } - else - { - outStream << - " float fogVal = saturate(1 / (exp(position.w * fogParams.x)));\n"; - } - outStream << " outputCol.rgb = lerp(outputCol.rgb, fogColour, fogVal);\n"; - } - - // Final return - outStream << " oColor = outputCol;\n"; - - if (MWRender::RenderingManager::useMRT()) outStream << - " oColor1 = float4(position.w / far, 0, 0, 1); \n"; - - outStream - << "}\n"; - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadowsHelpers( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - // TODO make filtering configurable - outStream << - "// Simple PCF \n" - "// Number of samples in one dimension (square for total samples) \n" - "#define NUM_SHADOW_SAMPLES_1D 1.0 \n" - "#define SHADOW_FILTER_SCALE 1 \n" - - "#define SHADOW_SAMPLES NUM_SHADOW_SAMPLES_1D*NUM_SHADOW_SAMPLES_1D \n" - - "float4 offsetSample(float4 uv, float2 offset, float invMapSize) \n" - "{ \n" - " return float4(uv.xy + offset * invMapSize * uv.w, uv.z, uv.w); \n" - "} \n"; - - if (prof->getReceiveDynamicShadowsDepth()) - { - outStream << - "float calcDepthShadow(sampler2D shadowMap, float4 shadowMapPos, float2 offset) \n" - " { \n" - " shadowMapPos = shadowMapPos / shadowMapPos.w; \n" - " float2 uv = shadowMapPos.xy; \n" - " float3 o = float3(offset, -offset.x) * 0.3f; \n" - " // Note: We using 2x2 PCF. Good enough and is alot faster. \n" - " float c = (shadowMapPos.z <= tex2D(shadowMap, uv.xy - o.xy).r) ? 1 : 0; // top left \n" - " c += (shadowMapPos.z <= tex2D(shadowMap, uv.xy + o.xy).r) ? 1 : 0; // bottom right \n" - " c += (shadowMapPos.z <= tex2D(shadowMap, uv.xy + o.zy).r) ? 1 : 0; // bottom left \n" - " c += (shadowMapPos.z <= tex2D(shadowMap, uv.xy - o.zy).r) ? 1 : 0; // top right \n" - " return c / 4; \n" - " } \n"; - } - else - { - outStream << - "float calcSimpleShadow(sampler2D shadowMap, float4 shadowMapPos) \n" - "{ \n" - " return tex2Dproj(shadowMap, shadowMapPos).x; \n" - "} \n"; - - } - - if (prof->getReceiveDynamicShadowsPSSM()) - { - uint numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); - - - if (prof->getReceiveDynamicShadowsDepth()) - { - outStream << - "float calcPSSMDepthShadow("; - } - else - { - outStream << - "float calcPSSMSimpleShadow("; - } - - outStream << "\n "; - for (uint i = 0; i < numTextures; ++i) - outStream << "sampler2D shadowMap" << i << ", "; - outStream << "\n "; - for (uint i = 0; i < numTextures; ++i) - outStream << "float4 lsPos" << i << ", "; - if (prof->getReceiveDynamicShadowsDepth()) - { - outStream << "\n "; - for (uint i = 0; i < numTextures; ++i) - outStream << "float2 invShadowmapSize" << i << ", "; - } - outStream << "\n" - " float4 pssmSplitPoints, float camDepth) \n" - "{ \n" - " float shadow; \n" - " // calculate shadow \n"; - - for (uint i = 0; i < numTextures; ++i) - { - if (!i) - outStream << " if (camDepth <= pssmSplitPoints." << ShaderHelper::getChannel(i) << ") \n"; - else if (i < numTextures - 1) - outStream << " else if (camDepth <= pssmSplitPoints." << ShaderHelper::getChannel(i) << ") \n"; - else - outStream << " else \n"; - - outStream << - " { \n"; - if (prof->getReceiveDynamicShadowsDepth()) - { - outStream << - " shadow = calcDepthShadow(shadowMap" << i << ", lsPos" << i << ", invShadowmapSize" << i << ".xy); \n"; - } - else - { - outStream << - " shadow = calcSimpleShadow(shadowMap" << i << ", lsPos" << i << "); \n"; - } - outStream << - " } \n"; - - } - - outStream << - " return shadow; \n" - "} \n\n\n"; - } - - - } - //--------------------------------------------------------------------- - uint TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpDynamicShadowsParams( - uint texCoord, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - // out semantics & params - uint numTextures = 1; - if (prof->getReceiveDynamicShadowsPSSM()) - { - numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); - } - for (uint i = 0; i < numTextures; ++i) - { - outStream << - ", out float4 oLightSpacePos" << i << " : TEXCOORD" << texCoord++ << " \n" << - ", uniform float4x4 texViewProjMatrix" << i << " \n"; - if (prof->getReceiveDynamicShadowsDepth()) - { - outStream << - ", uniform float4 depthRange" << i << " // x = min, y = max, z = range, w = 1/range \n"; - } - } - - return texCoord; - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpDynamicShadows( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - uint numTextures = 1; - if (prof->getReceiveDynamicShadowsPSSM()) - { - numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); - } - - // Calculate the position of vertex in light space - for (uint i = 0; i < numTextures; ++i) - { - outStream << - " oLightSpacePos" << i << " = mul(texViewProjMatrix" << i << ", worldPos); \n"; - if (prof->getReceiveDynamicShadowsDepth()) - { - // make linear - //outStream << - // "oLightSpacePos" << i << ".z = (oLightSpacePos" << i << ".z - depthRange" << i << ".x) * depthRange" << i << ".w;\n"; - - } - } - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadowsParams( - uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - if (tt == HIGH_LOD) - mShadowSamplerStartHi = *sampler; - else if (tt == LOW_LOD) - mShadowSamplerStartLo = *sampler; - - // in semantics & params - uint numTextures = 1; - outStream << - ", uniform float4 shadowFar_fadeStart \n"; - if (prof->getReceiveDynamicShadowsPSSM()) - { - numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); - outStream << - ", uniform float4 pssmSplitPoints \n"; - } - for (uint i = 0; i < numTextures; ++i) - { - outStream << - ", float4 lightSpacePos" << i << " : TEXCOORD" << *texCoord << " \n" << - ", uniform sampler2D shadowMap" << i << " : register(s" << *sampler << ") \n"; - *sampler = *sampler + 1; - *texCoord = *texCoord + 1; - if (prof->getReceiveDynamicShadowsDepth()) - { - outStream << - ", uniform float4 inverseShadowmapSize" << i << " \n"; - } - } - - } - //--------------------------------------------------------------------- - void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadows( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) - { - if (prof->getReceiveDynamicShadowsPSSM()) - { - uint numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); - outStream << - " float camDepth = position.w;\n"; - - if (prof->getReceiveDynamicShadowsDepth()) - { - outStream << - " float rtshadow = calcPSSMDepthShadow("; - } - else - { - outStream << - " float rtshadow = calcPSSMSimpleShadow("; - } - for (uint i = 0; i < numTextures; ++i) - outStream << "shadowMap" << i << ", "; - outStream << "\n "; - - for (uint i = 0; i < numTextures; ++i) - outStream << "lightSpacePos" << i << ", "; - if (prof->getReceiveDynamicShadowsDepth()) - { - outStream << "\n "; - for (uint i = 0; i < numTextures; ++i) - outStream << "inverseShadowmapSize" << i << ".xy, "; - } - outStream << "\n" << - " pssmSplitPoints, camDepth);\n"; - - } - else - { - if (prof->getReceiveDynamicShadowsDepth()) - { - outStream << - " float rtshadow = calcDepthShadow(shadowMap0, lightSpacePos0, inverseShadowmapSize0.xy);"; - } - else - { - outStream << - " float rtshadow = calcSimpleShadow(shadowMap0, lightSpacePos0);"; - } - } - - outStream << - " float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; \n" - " float fade = 1-((position.w - shadowFar_fadeStart.y) / fadeRange); \n" - " rtshadow = (position.w > shadowFar_fadeStart.x) ? 1 : ((position.w > shadowFar_fadeStart.y) ? 1-((1-rtshadow)*fade) : rtshadow); \n" - " rtshadow = (1-(1-rtshadow)*0.6); \n" // make the shadow a little less intensive - " shadow = min(shadow, rtshadow);\n"; - - } - //--------------------------------------------------------------------- - //--------------------------------------------------------------------- - HighLevelGpuProgramPtr - TerrainMaterialGeneratorB::SM2Profile::ShaderHelperHLSL::createVertexProgram( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - String progName = getVertexProgramName(prof, terrain, tt); - - HighLevelGpuProgramPtr ret = mgr.getByName(progName); - if (ret.isNull()) - { - ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "hlsl", GPT_VERTEX_PROGRAM); - } - else - { - ret->unload(); - } - - if (prof->_isSM3Available()) - ret->setParameter("target", "vs_3_0"); - else - ret->setParameter("target", "vs_2_0"); - ret->setParameter("entry_point", "main_vp"); - - return ret; - - } - //--------------------------------------------------------------------- - HighLevelGpuProgramPtr - TerrainMaterialGeneratorB::SM2Profile::ShaderHelperHLSL::createFragmentProgram( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - String progName = getFragmentProgramName(prof, terrain, tt); - - - HighLevelGpuProgramPtr ret = mgr.getByName(progName); - if (ret.isNull()) - { - ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "hlsl", GPT_FRAGMENT_PROGRAM); - } - else - { - ret->unload(); - } + TerrainMaterial::Profile::~Profile() + { + } - if (prof->_isSM3Available()) - ret->setParameter("target", "ps_3_0"); - else - ret->setParameter("target", "ps_2_x"); - ret->setParameter("entry_point", "main_fp"); - return ret; + Ogre::MaterialPtr TerrainMaterial::Profile::generate(const Ogre::Terrain* terrain) + { + const Ogre::String& matName = terrain->getMaterialName(); + Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(matName); + if (!mat.isNull()) + Ogre::MaterialManager::getSingleton().remove(matName); - } - //--------------------------------------------------------------------- - //--------------------------------------------------------------------- - HighLevelGpuProgramPtr - TerrainMaterialGeneratorB::SM2Profile::ShaderHelperGLSL::createVertexProgram( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - String progName = getVertexProgramName(prof, terrain, tt); - switch(tt) - { - case HIGH_LOD: - progName += "/hlod"; - break; - case LOW_LOD: - progName += "/llod"; - break; - case RENDER_COMPOSITE_MAP: - progName += "/comp"; - break; - } + sh::MaterialInstance* m = sh::Factory::getInstance().createMaterialInstance (matName); - HighLevelGpuProgramPtr ret = mgr.getByName(progName); - if (ret.isNull()) - { - ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "glsl", GPT_VERTEX_PROGRAM); - } - else - { - ret->unload(); - } - return ret; + return Ogre::MaterialManager::getSingleton().getByName(matName); + } - } - //--------------------------------------------------------------------- - HighLevelGpuProgramPtr - TerrainMaterialGeneratorB::SM2Profile::ShaderHelperGLSL::createFragmentProgram( - const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) - { - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - String progName = getFragmentProgramName(prof, terrain, tt); + Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain) + { + throw std::runtime_error ("composite map not supported"); + } - HighLevelGpuProgramPtr ret = mgr.getByName(progName); - if (ret.isNull()) - { - ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "glsl", GPT_FRAGMENT_PROGRAM); - } - else - { - ret->unload(); - } + Ogre::uint8 TerrainMaterial::Profile::getMaxLayers(const Ogre::Terrain* terrain) const + { + return 32; + } - return ret; + void TerrainMaterial::Profile::updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain) + { + } - } + void TerrainMaterial::Profile::updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain) + { + } + void TerrainMaterial::Profile::requestOptions(Ogre::Terrain* terrain) + { + terrain->_setMorphRequired(true); + terrain->_setNormalMapRequired(false); + terrain->_setLightMapRequired(false); + terrain->_setCompositeMapRequired(false); + } } diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp index e94e20b7e..7cafbb97a 100644 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -26,246 +26,50 @@ THE SOFTWARE. ----------------------------------------------------------------------------- */ -#ifndef __Ogre_TerrainMaterialGeneratorB_H__ -#define __Ogre_TerrainMaterialGeneratorB_H__ +#ifndef MWRENDER_TERRAINMATERIAL_H +#define MWRENDER_TERRAINMATERIAL_H #include "OgreTerrainPrerequisites.h" #include "OgreTerrainMaterialGenerator.h" #include "OgreGpuProgramParams.h" -namespace Ogre +namespace MWRender { - class PSSMShadowCameraSetup; - /** \addtogroup Optional Components - * @{ - */ - /** \addtogroup Terrain - * Some details on the terrain component - * @{ - */ + class TerrainMaterial : public Ogre::TerrainMaterialGenerator + { + public: + class Profile : public Ogre::TerrainMaterialGenerator::Profile + { + protected: + Ogre::TerrainMaterialGenerator* mParent; + Ogre::String mName; + Ogre::String mDesc; + public: + Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc); + virtual ~Profile(); - /** A TerrainMaterialGenerator which can cope with normal mapped, specular mapped - terrain. - @note Requires the Cg plugin to render correctly - */ - class TerrainMaterialGeneratorB : public TerrainMaterialGenerator - { - public: - TerrainMaterialGeneratorB(); - ~TerrainMaterialGeneratorB(); + virtual bool isVertexCompressionSupported() const { return false; } - /** Shader model 2 profile target. - */ - class SM2Profile : public TerrainMaterialGenerator::Profile - { - public: - SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc); - ~SM2Profile(); + virtual Ogre::MaterialPtr generate(const Ogre::Terrain* terrain); - bool isVertexCompressionSupported() const {return false;} - - MaterialPtr generate(const Terrain* terrain); - MaterialPtr generateForCompositeMap(const Terrain* terrain); - uint8 getMaxLayers(const Terrain* terrain) const; - void updateParams(const MaterialPtr& mat, const Terrain* terrain); - void updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain); - void requestOptions(Terrain* terrain); + virtual Ogre::MaterialPtr generateForCompositeMap(const Ogre::Terrain* terrain); - void setShadowFar(float far); - void setShadowFadeStart(float fadestart); + virtual Ogre::uint8 getMaxLayers(const Ogre::Terrain* terrain) const; - /** Whether to support normal mapping per layer in the shader (default true). - */ - bool isLayerNormalMappingEnabled() const { return mLayerNormalMappingEnabled; } - /** Whether to support normal mapping per layer in the shader (default true). - */ - void setLayerNormalMappingEnabled(bool enabled); - /** Whether to support parallax mapping per layer in the shader (default true). - */ - bool isLayerParallaxMappingEnabled() const { return mLayerParallaxMappingEnabled; } - /** Whether to support parallax mapping per layer in the shader (default true). - */ - void setLayerParallaxMappingEnabled(bool enabled); - /** Whether to support specular mapping per layer in the shader (default true). - */ - bool isLayerSpecularMappingEnabled() const { return mLayerSpecularMappingEnabled; } - /** Whether to support specular mapping per layer in the shader (default true). - */ - void setLayerSpecularMappingEnabled(bool enabled); - /** Whether to support a global colour map over the terrain in the shader, - if it's present (default true). - */ - bool isGlobalColourMapEnabled() const { return mGlobalColourMapEnabled; } - /** Whether to support a global colour map over the terrain in the shader, - if it's present (default true). - */ - void setGlobalColourMapEnabled(bool enabled); - /** Whether to support a light map over the terrain in the shader, - if it's present (default true). - */ - bool isLightmapEnabled() const { return mLightmapEnabled; } - /** Whether to support a light map over the terrain in the shader, - if it's present (default true). - */ - void setLightmapEnabled(bool enabled); - /** Whether to use the composite map to provide a lower LOD technique - in the distance (default true). - */ - bool isCompositeMapEnabled() const { return mCompositeMapEnabled; } - /** Whether to use the composite map to provide a lower LOD technique - in the distance (default true). - */ - void setCompositeMapEnabled(bool enabled); - /** Whether to support dynamic texture shadows received from other - objects, on the terrain (default true). - */ - bool getReceiveDynamicShadowsEnabled() const { return mReceiveDynamicShadows; } - /** Whether to support dynamic texture shadows received from other - objects, on the terrain (default true). - */ - void setReceiveDynamicShadowsEnabled(bool enabled); + virtual void updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain); - /** Whether to use PSSM support dynamic texture shadows, and if so the - settings to use (default 0). - */ - void setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings); - /** Whether to use PSSM support dynamic texture shadows, and if so the - settings to use (default 0). - */ - PSSMShadowCameraSetup* getReceiveDynamicShadowsPSSM() const { return mPSSM; } - /** Whether to use depth shadows (default false). - */ - void setReceiveDynamicShadowsDepth(bool enabled); - /** Whether to use depth shadows (default false). - */ - bool getReceiveDynamicShadowsDepth() const { return mDepthShadows; } - /** Whether to use shadows on low LOD material rendering (when using composite map) (default false). - */ - void setReceiveDynamicShadowsLowLod(bool enabled); - /** Whether to use shadows on low LOD material rendering (when using composite map) (default false). - */ - bool getReceiveDynamicShadowsLowLod() const { return mLowLodShadows; } + virtual void updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain); - int getNumberOfLightsSupported() const; + virtual void requestOptions(Ogre::Terrain* terrain); - /// Internal - bool _isSM3Available() const { return mSM3Available; } - - protected: - - enum TechniqueType - { - HIGH_LOD, - LOW_LOD, - RENDER_COMPOSITE_MAP - }; - void addTechnique(const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt); - - /// Interface definition for helper class to generate shaders - class ShaderHelper : public TerrainAlloc - { - public: - ShaderHelper() : mShadowSamplerStartHi(0), mShadowSamplerStartLo(0) {} - virtual ~ShaderHelper() {} - virtual HighLevelGpuProgramPtr generateVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - virtual HighLevelGpuProgramPtr generateFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - virtual void updateParams(const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap); - protected: - virtual String getVertexProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - virtual String getFragmentProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - virtual HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0; - virtual HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0; - virtual void generateVertexProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - virtual void generateFragmentProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - virtual void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; - virtual void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; - virtual void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0; - virtual void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0; - virtual void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; - virtual void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; - virtual void defaultVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog); - virtual void defaultFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog); - virtual void updateVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params); - virtual void updateFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params); - static String getChannel(uint idx); - - size_t mShadowSamplerStartHi; - size_t mShadowSamplerStartLo; - - }; - - /// Utility class to help with generating shaders for Cg / HLSL. - class ShaderHelperCg : public ShaderHelper - { - protected: - HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream); - void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream); - void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - uint generateVpDynamicShadowsParams(uint texCoordStart, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - void generateVpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - void generateFpDynamicShadowsHelpers(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - void generateFpDynamicShadowsParams(uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - void generateFpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); - }; - - class ShaderHelperHLSL : public ShaderHelperCg - { - protected: - HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - }; - - /// Utility class to help with generating shaders for GLSL. - class ShaderHelperGLSL : public ShaderHelper - { - protected: - HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); - void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} - void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} - void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {} - void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {} - void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} - void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} - }; - - ShaderHelper* mShaderGen; - bool mLayerNormalMappingEnabled; - bool mLayerParallaxMappingEnabled; - bool mLayerSpecularMappingEnabled; - bool mGlobalColourMapEnabled; - bool mLightmapEnabled; - bool mCompositeMapEnabled; - bool mReceiveDynamicShadows; - PSSMShadowCameraSetup* mPSSM; - bool mDepthShadows; - bool mLowLodShadows; - bool mSM3Available; - float mShadowFar; - float mShadowFadeStart; - - bool isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const; - - }; - - - - - }; - - - - /** @} */ - /** @} */ + }; + TerrainMaterial(); + }; } + #endif diff --git a/extern/shiny b/extern/shiny index 7485a15c2..ca0549ae6 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 7485a15c26f129084a1c264fa1a98dc2de86f298 +Subproject commit ca0549ae6db7aa9efb125335942c61fb3f583a8d diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 76e64112b..96c6f8422 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -6,9 +6,6 @@ material openmw_objects_base emissive 0.0 0.0 0.0 has_vertex_colour false diffuseMap black.png - fog true - mrt_output true - lighting true is_transparent false // real transparency, alpha rejection doesn't count here scene_blend default @@ -25,9 +22,6 @@ material openmw_objects_base shader_properties { - fog $fog - mrt_output $mrt_output - lighting $lighting has_vertex_colour $has_vertex_colour is_transparent $is_transparent } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 3b4992f5b..c343a32b0 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -3,9 +3,9 @@ #include "shadows.h" -#define FOG @shPropertyBool(fog) -#define MRT @shPropertyNotBool(is_transparent) && @shPropertyBool(mrt_output) && @shGlobalSettingBool(mrt_output) -#define LIGHTING @shPropertyBool(lighting) +#define FOG @shGlobalSettingBool(fog) +#define MRT @shPropertyNotBool(is_transparent) && @shGlobalSettingBool(mrt_output) +#define LIGHTING @shGlobalSettingBool(lighting) #define SHADOWS_PSSM LIGHTING && @shGlobalSettingBool(shadows_pssm) #define SHADOWS LIGHTING && @shGlobalSettingBool(shadows) diff --git a/files/materials/openmw.configuration b/files/materials/openmw.configuration index 6122cd8cb..cddf49a03 100644 --- a/files/materials/openmw.configuration +++ b/files/materials/openmw.configuration @@ -1,15 +1,16 @@ configuration water_reflection { fog false - receives_shadows false + shadows false + shadows_pssm false mrt_output false } configuration local_map { fog false - receives_shadows false - simple_water true mrt_output false lighting false + shadows false + shadows_pssm false } diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader new file mode 100644 index 000000000..8cdcb339e --- /dev/null +++ b/files/materials/terrain.shader @@ -0,0 +1,57 @@ +#include "core.h" + +#define FOG @shPropertyBool(fog) +#define MRT @shGlobalSettingBool(mrt_output) + + +@shAllocatePassthrough(1, depth) + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) + shUniform(float4x4, viewProjMatrix) @shAutoConstant(viewProjMatrix, viewproj_matrix) + + shInput(float2, uv0) + + @shPassthroughVertexOutputs + + SH_START_PROGRAM + { + + + float4 worldPos = shMatrixMult(worldMatrix, shInputPosition); + + + shOutputPosition = shMatrixMult(viewProjMatrix, worldPos); + + @shPassthroughAssign(depth, shOutputPosition.z); + + } + +#else + + SH_BEGIN_PROGRAM + + + + @shPassthroughFragmentInputs + +#if MRT + shDeclareMrtOutput(1) +#endif + + + + SH_START_PROGRAM + { + + float depth = @shPassthroughReceive(depth); + + +#if MRT + //shOutputColour(1) = float4(1,1,1,1); +#endif + } + +#endif diff --git a/files/materials/terrain.shaderset b/files/materials/terrain.shaderset new file mode 100644 index 000000000..d31429dbb --- /dev/null +++ b/files/materials/terrain.shaderset @@ -0,0 +1,15 @@ +shader_set terrain_vertex +{ + source terrain.shader + type vertex + profiles_cg vs_2_0 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set terrain_fragment +{ + source terrain.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_hlsl ps_2_0 +} From 7a2d61304310abbdf081c34c12a300dffbf316bb Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 13 Jul 2012 08:50:46 +0200 Subject: [PATCH 080/298] Issue #314: extended Class::apply function to cover the actor --- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/class.hpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index f51aa401c..2ae6cb695 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -34,7 +34,7 @@ namespace MWWorld } - bool Class::apply (const MWWorld::Ptr& ptr, const std::string& id) const + bool Class::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const { return false; } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 1a3113f7c..968ae7917 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -165,8 +165,10 @@ namespace MWWorld /// effects). Throws an exception, if the object can't hold other objects. /// (default implementation: throws an exception) - virtual bool apply (const MWWorld::Ptr& ptr, const std::string& id) const; + virtual bool apply (const MWWorld::Ptr& ptr, const std::string& id, + const MWWorld::Ptr& actor) const; ///< Apply \a id on \a ptr. + /// \param actor Actor that is resposible for the ID being applied to \a ptr. /// \return Any effect? /// /// (default implementation: ignore and return false) From 805b81bf6e61b65f096d7a127538fa46e384ef6a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 13 Jul 2012 09:03:17 +0200 Subject: [PATCH 081/298] Issue #314: added skillUsageSucceeded function to Class --- apps/openmw/mwworld/class.cpp | 5 +++++ apps/openmw/mwworld/class.hpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 2ae6cb695..356eef9a9 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -39,6 +39,11 @@ namespace MWWorld return false; } + void Class::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const + { + throw std::runtime_error ("class does not represent an actor"); + } + MWMechanics::CreatureStats& Class::getCreatureStats (const Ptr& ptr) const { throw std::runtime_error ("class does not have creature stats"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 968ae7917..352c7bda1 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -173,6 +173,11 @@ namespace MWWorld /// /// (default implementation: ignore and return false) + virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const; + ///< Inform actor \a ptr that a skill use has succeeded. + /// + /// (default implementations: throws an exception) + static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. From 7731e9a6fac11623e9b24fd52efb95b8920c640f Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jul 2012 09:13:12 +0200 Subject: [PATCH 082/298] finished cleaning up sky --- apps/openmw/mwrender/sky.cpp | 303 ++++++----------------- apps/openmw/mwrender/sky.hpp | 21 +- apps/openmw/mwrender/terrain.cpp | 2 +- apps/openmw/mwrender/terrainmaterial.cpp | 20 +- apps/openmw/mwrender/terrainmaterial.hpp | 12 + apps/openmw/mwworld/weather.cpp | 13 +- extern/shiny | 2 +- files/materials/moon.shader | 52 ++++ files/materials/moon.shaderset | 15 ++ files/materials/sky.mat | 11 +- files/materials/sun.shader | 6 +- files/materials/terrain.shader | 4 +- 12 files changed, 202 insertions(+), 259 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 65a9e8953..f84c12b4e 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -19,6 +19,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -31,9 +33,33 @@ using namespace Ogre; BillboardObject::BillboardObject( const String& textureName, const float initialSize, const Vector3& position, - SceneNode* rootNode) + SceneNode* rootNode, + const std::string& material) { - init(textureName, initialSize, position, rootNode); + SceneManager* sceneMgr = rootNode->getCreator(); + + Vector3 finalPosition = position.normalisedCopy() * 1000.f; + + static unsigned int bodyCount=0; + + /// \todo These billboards are not 100% correct, might want to revisit them later + mBBSet = sceneMgr->createBillboardSet("SkyBillboardSet"+StringConverter::toString(bodyCount), 1); + mBBSet->setDefaultDimensions(550.f*initialSize, 550.f*initialSize); + mBBSet->setBillboardType(BBT_PERPENDICULAR_COMMON); + mBBSet->setCommonDirection( -position.normalisedCopy() ); + mBBSet->setVisibilityFlags(RV_Sky); + mNode = rootNode->createChildSceneNode(); + mNode->setPosition(finalPosition); + mNode->attachObject(mBBSet); + mBBSet->createBillboard(0,0,0); + mBBSet->setCastShadows(false); + + mMaterial = sh::Factory::getInstance().createMaterialInstance ("BillboardMaterial"+StringConverter::toString(bodyCount), material); + mMaterial->setProperty("texture", sh::makeProperty(new sh::StringValue(textureName))); + + mBBSet->setMaterialName("BillboardMaterial"+StringConverter::toString(bodyCount)); + + bodyCount++; } BillboardObject::BillboardObject() @@ -52,7 +78,13 @@ void BillboardObject::setSize(const float size) void BillboardObject::setVisibility(const float visibility) { - //mMaterial->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, visibility); + Ogre::MaterialPtr m = static_cast(mMaterial->getMaterial ())->getOgreMaterial (); + for (int i=0; igetNumTechniques(); ++i) + { + Ogre::Technique* t = m->getTechnique(i); + if (t->getNumPasses ()) + t->getPass(0)->setDiffuse (0,0,0, visibility); + } } void BillboardObject::setPosition(const Vector3& pPosition) @@ -78,6 +110,13 @@ void BillboardObject::setVisibilityFlags(int flags) void BillboardObject::setColour(const ColourValue& pColour) { + Ogre::MaterialPtr m = static_cast(mMaterial->getMaterial ())->getOgreMaterial (); + for (int i=0; igetNumTechniques(); ++i) + { + Ogre::Technique* t = m->getTechnique(i); + if (t->getNumPasses ()) + t->getPass(0)->setSelfIllumination (pColour); + } //mMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(pColour); } @@ -91,187 +130,13 @@ SceneNode* BillboardObject::getNode() return mNode; } -void BillboardObject::init(const String& textureName, - const float initialSize, - const Vector3& position, - SceneNode* rootNode) -{ - SceneManager* sceneMgr = rootNode->getCreator(); - - Vector3 finalPosition = position.normalisedCopy() * 1000.f; - - static unsigned int bodyCount=0; - - /// \todo These billboards are not 100% correct, might want to revisit them later - mBBSet = sceneMgr->createBillboardSet("SkyBillboardSet"+StringConverter::toString(bodyCount), 1); - mBBSet->setDefaultDimensions(550.f*initialSize, 550.f*initialSize); - mBBSet->setBillboardType(BBT_PERPENDICULAR_COMMON); - mBBSet->setCommonDirection( -position.normalisedCopy() ); - mBBSet->setVisibilityFlags(RV_Sky); - mNode = rootNode->createChildSceneNode(); - mNode->setPosition(finalPosition); - mNode->attachObject(mBBSet); - mBBSet->createBillboard(0,0,0); - mBBSet->setCastShadows(false); - - sh::MaterialInstance* m = sh::Factory::getInstance().createMaterialInstance ("BillboardMaterial"+StringConverter::toString(bodyCount), "openmw_sun"); - m->setProperty("texture", sh::makeProperty(new sh::StringValue(textureName))); - - mBBSet->setMaterialName("BillboardMaterial"+StringConverter::toString(bodyCount)); - - /* - mMaterial = MaterialManager::getSingleton().create("BillboardMaterial"+StringConverter::toString(bodyCount), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - mMaterial->removeAllTechniques(); - Pass* p = mMaterial->createTechnique()->createPass(); - p->setSceneBlending(SBT_TRANSPARENT_ALPHA); - p->setDepthCheckEnabled(false); - p->setDepthWriteEnabled(false); - p->setSelfIllumination(1.0,1.0,1.0); - p->setDiffuse(0.0,0.0,0.0,1.0); - p->setAmbient(0.0,0.0,0.0); - p->setPolygonModeOverrideable(false); - p->createTextureUnitState(textureName); - - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - HighLevelGpuProgramPtr vshader; - if (mgr.resourceExists("BBO_VP")) - vshader = mgr.getByName("BBO_VP"); - else - vshader = mgr.createProgram("BBO_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_VERTEX_PROGRAM); - vshader->setParameter("profiles", "vs_2_x arbvp1"); - vshader->setParameter("entry_point", "main_vp"); - StringUtil::StrStreamType outStream; - outStream << - "void main_vp( \n" - " float4 position : POSITION, \n" - " in float2 uv : TEXCOORD0, \n" - " out float2 oUV : TEXCOORD0, \n" - " out float4 oPosition : POSITION, \n" - " uniform float4x4 worldViewProj \n" - ") \n" - "{ \n" - " oUV = uv; \n" - " oPosition = mul( worldViewProj, position ); \n" - "}"; - vshader->setSource(outStream.str()); - vshader->load(); - vshader->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - mMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName()); - - HighLevelGpuProgramPtr fshader; - if (mgr.resourceExists("BBO_FP")) - fshader = mgr.getByName("BBO_FP"); - else - fshader = mgr.createProgram("BBO_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_FRAGMENT_PROGRAM); - - fshader->setParameter("profiles", "ps_2_x arbfp1"); - fshader->setParameter("entry_point", "main_fp"); - StringUtil::StrStreamType outStream2; - outStream2 << - "void main_fp( \n" - " in float2 uv : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n"; - if (RenderingManager::useMRT()) outStream2 << - " out float4 oColor1 : COLOR1, \n"; - outStream2 << - " uniform sampler2D texture : TEXUNIT0, \n" - " uniform float4 diffuse, \n" - " uniform float4 emissive \n" - ") \n" - "{ \n" - " float4 tex = tex2D(texture, uv); \n" - " oColor = float4(emissive.xyz,1) * tex * float4(1,1,1,diffuse.a); \n"; - if (RenderingManager::useMRT()) outStream2 << - " oColor1 = float4(1, 0, 0, 1); \n"; - outStream2 << - "}"; - fshader->setSource(outStream2.str()); - fshader->load(); - fshader->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); - fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - mMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); -*/ - bodyCount++; -} - Moon::Moon( const String& textureName, const float initialSize, const Vector3& position, - SceneNode* rootNode) + SceneNode* rootNode, + const std::string& material) + : BillboardObject(textureName, initialSize, position, rootNode, material) { - init(textureName, initialSize, position, rootNode); - - - /* - HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); - HighLevelGpuProgramPtr vshader; - if (mgr.resourceExists("Moon_VP")) - vshader = mgr.getByName("Moon_VP"); - else - vshader = mgr.createProgram("Moon_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_VERTEX_PROGRAM); - vshader->setParameter("profiles", "vs_2_x arbvp1"); - vshader->setParameter("entry_point", "main_vp"); - StringUtil::StrStreamType outStream; - outStream << - "void main_vp( \n" - " float4 position : POSITION, \n" - " in float2 uv : TEXCOORD0, \n" - " out float2 oUV : TEXCOORD0, \n" - " out float4 oPosition : POSITION, \n" - " uniform float4x4 worldViewProj \n" - ") \n" - "{ \n" - " oUV = uv; \n" - " oPosition = mul( worldViewProj, position ); \n" - "}"; - vshader->setSource(outStream.str()); - vshader->load(); - vshader->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - mMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName()); - - HighLevelGpuProgramPtr fshader; - if (mgr.resourceExists("Moon_FP")) - fshader = mgr.getByName("Moon_FP"); - else - fshader = mgr.createProgram("Moon_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_FRAGMENT_PROGRAM); - - fshader->setParameter("profiles", "ps_2_x arbfp1"); - fshader->setParameter("entry_point", "main_fp"); - StringUtil::StrStreamType outStream2; - outStream2 << - "void main_fp( \n" - " in float2 uv : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n"; - if (RenderingManager::useMRT()) outStream2 << - " out float4 oColor1 : COLOR1, \n"; - outStream2 << - " uniform sampler2D texture : TEXUNIT0, \n" - " uniform float4 skyColour, \n" - " uniform float4 diffuse, \n" - " uniform float4 emissive \n" - ") \n" - "{ \n" - " float4 tex = tex2D(texture, uv); \n" - " oColor = float4(emissive.xyz,1) * tex; \n"; - if (RenderingManager::useMRT()) outStream2 << - " oColor1 = float4(1, 0, 0, 1); \n"; - outStream2 << - // use a circle for the alpha (compute UV distance to center) - // looks a bit bad because its not filtered on the edges, - // but it's cheaper than a seperate alpha texture. - " float sqrUVdist = pow(uv.x-0.5,2) + pow(uv.y-0.5, 2); \n" - " oColor.a = diffuse.a * (sqrUVdist >= 0.24 ? 0 : 1); \n" - " oColor.rgb += (1-tex.a) * oColor.a * skyColour.rgb; \n"//fill dark side of moon with skycolour - " oColor.rgb += (1-diffuse.a) * skyColour.rgb; \n"//fade bump - "}"; - fshader->setSource(outStream2.str()); - fshader->load(); - fshader->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); - fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - mMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); - - */ - setVisibility(1.0); mPhase = Moon::Phase_Full; @@ -282,11 +147,6 @@ void Moon::setType(const Moon::Type& type) mType = type; } -void Moon::setSkyColour(const Ogre::ColourValue& colour) -{ - //mMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("skyColour", colour); -} - void Moon::setPhase(const Moon::Phase& phase) { // Colour texture @@ -306,7 +166,10 @@ void Moon::setPhase(const Moon::Phase& phase) textureName += ".dds"; - //mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(textureName); + if (mType == Moon::Type_Secunda) + sh::Factory::getInstance ().setTextureAlias ("secunda_texture", textureName); + else + sh::Factory::getInstance ().setTextureAlias ("masser_texture", textureName); mPhase = phase; } @@ -355,25 +218,12 @@ void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType) else if (i>= 33 && i <= 48) alpha = 64; // second bottom-most row else alpha = 255; } - + // NB we would have to swap R and B depending on rendersystem specific VertexElementType, but doesn't matter since they are both 1 uint8 tmpR = static_cast(255); uint8 tmpG = static_cast(255); uint8 tmpB = static_cast(255); uint8 tmpA = static_cast(alpha); - // This does not matter since R and B are always 1. - /*VertexElementType format = Root::getSingleton().getRenderSystem()->getColourVertexElementType(); - switch (format) - { - case VET_COLOUR_ARGB: - std::swap(tmpR, tmpB); - break; - case VET_COLOUR_ABGR: - break; - default: - break; - }*/ - // Modify *((uint32*)currentVertex) = tmpR | (tmpG << 8) | (tmpB << 16) | (tmpA << 24); @@ -405,8 +255,6 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) , mCloudOpacity(0.0f) , mCloudSpeed(0.0f) , mStarsOpacity(0.0f) - , mThunderOverlay(NULL) - , mThunderTextureUnit(NULL) , mRemainingTransitionTime(0.0f) , mGlareFade(0.0f) , mGlare(0.0f) @@ -416,6 +264,7 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) , mSecundaEnabled(true) , mCreated(false) , mCloudAnimationTimer(0.f) + , mMoonRed(false) { mSceneMgr = pMwRoot->getCreator(); mRootNode = mCamera->getParentSceneNode()->createChildSceneNode(); @@ -440,34 +289,23 @@ void SkyManager::create() sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", ""); // Create overlay used for thunderstorm - MaterialPtr material = MaterialManager::getSingleton().create( "ThunderMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME ); - Pass* pass = material->getTechnique(0)->getPass(0); - pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); - mThunderTextureUnit = pass->createTextureUnitState(); - mThunderTextureUnit->setColourOperationEx(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, ColourValue(1.f, 1.f, 1.f)); - mThunderTextureUnit->setAlphaOperation(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, 0.5f); - OverlayManager& ovm = OverlayManager::getSingleton(); - mThunderOverlay = ovm.create( "ThunderOverlay" ); - OverlayContainer* overlay_panel; - overlay_panel = (OverlayContainer*)ovm.createOverlayElement("Panel", "ThunderPanel"); - overlay_panel->_setPosition(0, 0); - overlay_panel->_setDimensions(1, 1); - overlay_panel->setMaterialName( "ThunderMaterial" ); - overlay_panel->show(); - mThunderOverlay->add2D(overlay_panel); - mThunderOverlay->hide(); - - mSecunda = new Moon("textures\\tx_secunda_full.dds", 0.5, Vector3(-0.4, 0.4, 0.5), mRootNode); + mLightning = mSceneMgr->createLight(); + mLightning->setType (Ogre::Light::LT_DIRECTIONAL); + mLightning->setDirection (Ogre::Vector3(0.3, -0.7, 0.3)); + mLightning->setVisible (false); + mLightning->setDiffuseColour (ColourValue(3,3,3)); + + mSecunda = new Moon("secunda_texture", 0.5, Vector3(-0.4, 0.4, 0.5), mRootNode, "openmw_moon"); mSecunda->setType(Moon::Type_Secunda); mSecunda->setRenderQueue(RQG_SkiesEarly+4); - mMasser = new Moon("textures\\tx_masser_full.dds", 0.75, Vector3(-0.4, 0.4, 0.5), mRootNode); + mMasser = new Moon("masser_texture", 0.75, Vector3(-0.4, 0.4, 0.5), mRootNode, "openmw_moon"); mMasser->setRenderQueue(RQG_SkiesEarly+3); mMasser->setType(Moon::Type_Masser); - mSun = new BillboardObject("textures\\tx_sun_05.dds", 1, Vector3(0.4, 0.4, 0.4), mRootNode); + mSun = new BillboardObject("textures\\tx_sun_05.dds", 1, Vector3(0.4, 0.4, 0.4), mRootNode, "openmw_sun"); mSun->setRenderQueue(RQG_SkiesEarly+4); - mSunGlare = new BillboardObject("textures\\tx_sun_flash_grey_05.dds", 3, Vector3(0.4, 0.4, 0.4), mRootNode); + mSunGlare = new BillboardObject("textures\\tx_sun_flash_grey_05.dds", 3, Vector3(0.4, 0.4, 0.4), mRootNode, "openmw_sun"); mSunGlare->setRenderQueue(RQG_SkiesLate); mSunGlare->setVisibilityFlags(RV_Glare); @@ -556,6 +394,8 @@ void SkyManager::update(float duration) mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); mSecunda->setPhase ( static_cast( (int) ((mDay % 32)/4.f)) ); + mSecunda->setColour ( mMoonRed ? ColourValue(1.0, 0.0784, 0.0784) : ColourValue(1,1,1,1)); + mMasser->setColour (ColourValue(1,1,1,1)); if (mSunEnabled) { @@ -609,9 +449,7 @@ void SkyManager::disable() void SkyManager::setMoonColour (bool red) { - if (!mCreated) return; - mSecunda->setColour( red ? ColourValue(1.0, 0.0784, 0.0784) - : ColourValue(1.0, 1.0, 1.0)); + mMoonRed = red; } void SkyManager::setWeather(const MWWorld::WeatherResult& weather) @@ -755,16 +593,21 @@ void SkyManager::secundaDisable() mSecundaEnabled = false; } -void SkyManager::setThunder(const float factor) +void SkyManager::setLightningStrength(const float factor) { if (!mCreated) return; if (factor > 0.f) { - mThunderOverlay->show(); - mThunderTextureUnit->setAlphaOperation(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, factor*0.6); + mLightning->setDiffuseColour (ColourValue(2*factor, 2*factor, 2*factor)); + mLightning->setVisible(true); } else - mThunderOverlay->hide(); + mLightning->setVisible(false); +} + +void SkyManager::setLightningDirection(const Ogre::Vector3& dir) +{ + mLightning->setDirection (dir); } void SkyManager::setMasserFade(const float fade) diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index f59583c04..4a0a7b36b 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -33,7 +33,8 @@ namespace MWRender BillboardObject( const Ogre::String& textureName, const float size, const Ogre::Vector3& position, - Ogre::SceneNode* rootNode + Ogre::SceneNode* rootNode, + const std::string& material ); BillboardObject(); @@ -52,13 +53,9 @@ namespace MWRender Ogre::SceneNode* getNode(); protected: - virtual void init(const Ogre::String& textureName, - const float size, - const Ogre::Vector3& position, - Ogre::SceneNode* rootNode); Ogre::SceneNode* mNode; - Ogre::MaterialPtr mMaterial; + sh::MaterialInstance* mMaterial; Ogre::BillboardSet* mBBSet; }; @@ -72,7 +69,8 @@ namespace MWRender Moon( const Ogre::String& textureName, const float size, const Ogre::Vector3& position, - Ogre::SceneNode* rootNode + Ogre::SceneNode* rootNode, + const std::string& material ); virtual ~Moon() {} @@ -97,7 +95,6 @@ namespace MWRender void setPhase(const Phase& phase); void setType(const Type& type); - void setSkyColour(const Ogre::ColourValue& colour); Phase getPhase() const; unsigned int getPhaseInt() const; @@ -163,7 +160,8 @@ namespace MWRender void secundaEnable(); void secundaDisable(); - void setThunder(const float factor); + void setLightningStrength(const float factor); + void setLightningDirection(const Ogre::Vector3& dir); void setGlare(const float glare); Ogre::Vector3 getRealSunPos(); @@ -175,6 +173,8 @@ namespace MWRender private: bool mCreated; + bool mMoonRed; + float mHour; int mDay; int mMonth; @@ -205,8 +205,7 @@ namespace MWRender Ogre::ColourValue mCloudColour; Ogre::ColourValue mSkyColour; - Ogre::Overlay* mThunderOverlay; - Ogre::TextureUnitState* mThunderTextureUnit; + Ogre::Light* mLightning; float mRemainingTransitionTime; diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index 7610acadc..67cee435c 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -193,7 +193,7 @@ namespace MWRender //this is a hack to get around the fact that Ogre seems to //corrupt the global colour map leading to rendering errors - MaterialPtr mat = terrain->getMaterial(); + //MaterialPtr mat = terrain->getMaterial(); /// \todo //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index 577b346df..a066a9bed 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -47,12 +47,30 @@ namespace MWRender Ogre::MaterialManager::getSingleton().remove(matName); - sh::MaterialInstance* m = sh::Factory::getInstance().createMaterialInstance (matName); + mMaterial = sh::Factory::getInstance().createMaterialInstance (matName); + mMaterial->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); + + createPass(); return Ogre::MaterialManager::getSingleton().getByName(matName); } + int TerrainMaterial::Profile::getLayersPerPass () const + { + return 10; + } + + void TerrainMaterial::Profile::createPass (int index) + { + int layerOffset = index * getLayersPerPass(); + + sh::MaterialInstancePass* p = mMaterial->createPass (); + + p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); + p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); + } + Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain) { throw std::runtime_error ("composite map not supported"); diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp index 7cafbb97a..73bbd7e07 100644 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -33,6 +33,11 @@ THE SOFTWARE. #include "OgreTerrainMaterialGenerator.h" #include "OgreGpuProgramParams.h" +namespace sh +{ + class MaterialInstance; +} + namespace MWRender { @@ -64,6 +69,13 @@ namespace MWRender virtual void requestOptions(Ogre::Terrain* terrain); + private: + sh::MaterialInstance* mMaterial; + + void createPass (int index=0); + + int getLayersPerPass () const; + }; TerrainMaterial(); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6c275ff80..b5f2e3a57 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -476,8 +476,7 @@ WeatherResult WeatherManager::transition(float factor) result.mSunDiscColor = lerp(current.mSunDiscColor, other.mSunDiscColor); result.mFogDepth = lerp(current.mFogDepth, other.mFogDepth); result.mWindSpeed = lerp(current.mWindSpeed, other.mWindSpeed); - //result.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed); - result.mCloudSpeed = current.mCloudSpeed; + result.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed); result.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity); result.mGlareView = lerp(current.mGlareView, other.mGlareView); result.mNightFade = lerp(current.mNightFade, other.mNightFade); @@ -689,13 +688,13 @@ void WeatherManager::update(float duration) mThunderFlash -= duration; if (mThunderFlash > 0) - mRendering->getSkyManager()->setThunder( mThunderFlash / WeatherGlobals::mThunderThreshold ); + mRendering->getSkyManager()->setLightningStrength( mThunderFlash / WeatherGlobals::mThunderThreshold ); else { srand(time(NULL)); mThunderChanceNeeded = rand() % 100; mThunderChance = 0; - mRendering->getSkyManager()->setThunder( 0.f ); + mRendering->getSkyManager()->setLightningStrength( 0.f ); } } else @@ -706,14 +705,14 @@ void WeatherManager::update(float duration) { mThunderFlash = WeatherGlobals::mThunderThreshold; - mRendering->getSkyManager()->setThunder( mThunderFlash / WeatherGlobals::mThunderThreshold ); + mRendering->getSkyManager()->setLightningStrength( mThunderFlash / WeatherGlobals::mThunderThreshold ); mThunderSoundDelay = WeatherGlobals::mThunderSoundDelay; } } } else - mRendering->getSkyManager()->setThunder(0.f); + mRendering->getSkyManager()->setLightningStrength(0.f); mRendering->setAmbientColour(result.mAmbientColor); mRendering->sunEnable(); @@ -725,7 +724,7 @@ void WeatherManager::update(float duration) { mRendering->sunDisable(); mRendering->skyDisable(); - mRendering->getSkyManager()->setThunder(0.f); + mRendering->getSkyManager()->setLightningStrength(0.f); } // play sounds diff --git a/extern/shiny b/extern/shiny index ca0549ae6..1c25aca08 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit ca0549ae6db7aa9efb125335942c61fb3f583a8d +Subproject commit 1c25aca082214beddd05fa9b8adf481c7299cf0e diff --git a/files/materials/moon.shader b/files/materials/moon.shader index e69de29bb..4a4eaf0b4 100644 --- a/files/materials/moon.shader +++ b/files/materials/moon.shader @@ -0,0 +1,52 @@ +#include "core.h" + +#define MRT @shGlobalSettingBool(mrt_output) + + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shInput(float2, uv0) + shOutput(float2, UV) + + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + } + +#else + + SH_BEGIN_PROGRAM + shSampler2D(diffuseMap) + shInput(float2, UV) +#if MRT + shDeclareMrtOutput(1) +#endif + shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) + shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) + + shUniform(float4, atmosphereColour) @shSharedParameter(atmosphereColour) + + SH_START_PROGRAM + { + + float4 tex = shSample(diffuseMap, UV); + + shOutputColour(0) = float4(materialEmissive.xyz, 1) * tex; + + // use a circle for the alpha (compute UV distance to center) + // looks a bit bad because it's not filtered on the edges, + // but cheaper than a seperate alpha texture. + float sqrUVdist = pow(UV.x-0.5,2) + pow(UV.y-0.5, 2); + shOutputColour(0).a = materialDiffuse.a * (sqrUVdist >= 0.24 ? 0 : 1); + shOutputColour(0).rgb += (1-tex.a) * shOutputColour(0).a * atmosphereColour.rgb; //fill dark side of moon with atmosphereColour + shOutputColour(0).rgb += (1-materialDiffuse.a) * atmosphereColour.rgb; //fade bump + +#if MRT + shOutputColour(1) = float4(1,1,1,1); +#endif + } + +#endif diff --git a/files/materials/moon.shaderset b/files/materials/moon.shaderset index e69de29bb..659481a96 100644 --- a/files/materials/moon.shaderset +++ b/files/materials/moon.shaderset @@ -0,0 +1,15 @@ +shader_set moon_vertex +{ + source moon.shader + type vertex + profiles_cg vs_2_0 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set moon_fragment +{ + source moon.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_hlsl ps_2_0 +} diff --git a/files/materials/sky.mat b/files/materials/sky.mat index 3f52966e8..17da7fc13 100644 --- a/files/materials/sky.mat +++ b/files/materials/sky.mat @@ -7,11 +7,13 @@ material openmw_moon fragment_program moon_fragment polygon_mode_overrideable off + depth_write off + depth_check off + scene_blend alpha_blend texture_unit diffuseMap { - texture $diffuseMap - create_in_ffp true + texture_alias $texture } } } @@ -33,7 +35,6 @@ material openmw_clouds texture_unit diffuseMap1 { texture_alias cloud_texture_1 - create_in_ffp true } texture_unit diffuseMap2 @@ -90,6 +91,10 @@ material openmw_sun polygon_mode_overrideable off + depth_check off + depth_write off + scene_blend alpha_blend + texture_unit diffuseMap { direct_texture $texture diff --git a/files/materials/sun.shader b/files/materials/sun.shader index 4c7926756..ceab60565 100644 --- a/files/materials/sun.shader +++ b/files/materials/sun.shader @@ -25,13 +25,11 @@ shDeclareMrtOutput(1) #endif shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) + //shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) SH_START_PROGRAM { - shOutputColour(0) = float4(1,1,1,materialDiffuse.a) * float4(materialEmissive.xyz, 1) * shSample(diffuseMap, UV); - - shOutputColour(0) = shSample(diffuseMap, UV); + shOutputColour(0) = float4(1,1,1,materialDiffuse.a) * shSample(diffuseMap, UV); #if MRT shOutputColour(1) = float4(1,1,1,1); diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 8cdcb339e..8b186def3 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -1,6 +1,6 @@ #include "core.h" -#define FOG @shPropertyBool(fog) +#define FOG @shGlobalSettingBool(fog) #define MRT @shGlobalSettingBool(mrt_output) @@ -47,6 +47,8 @@ { float depth = @shPassthroughReceive(depth); + + shOutputColour(0) = float4(1,0,0,1); #if MRT From 84d846cf07c75dc13f3fdf488a1d4dee87ee4865 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 13 Jul 2012 09:16:27 +0200 Subject: [PATCH 083/298] Issue #314: implemented apply and skillUsageSucceeded functions for MWClass::Npc --- apps/openmw/mwclass/npc.cpp | 22 ++++++++++++++++++++++ apps/openmw/mwclass/npc.hpp | 9 +++++++++ 2 files changed, 31 insertions(+) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d54b9441d..2570408f7 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -343,4 +343,26 @@ namespace MWClass return weight; } + + bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, + const MWWorld::Ptr& actor) const + { + MWMechanics::CreatureStats& stats = getCreatureStats (ptr); + + /// \todo consider instant effects + + return stats.mActiveSpells.addSpell (id); + } + + void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const + { + MWMechanics::NpcStats& stats = getNpcStats (ptr); + + MWWorld::LiveCellRef *ref = ptr.get(); + + const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().classes.find ( + ref->base->cls); + + stats.useSkill (skill, *class_, usageType); + } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 4cb733977..74a80c085 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -76,6 +76,15 @@ namespace MWClass ///< Returns total weight of objects inside this object (including modifications from magic /// effects). Throws an exception, if the object can't hold other objects. + virtual bool apply (const MWWorld::Ptr& ptr, const std::string& id, + const MWWorld::Ptr& actor) const; + ///< Apply \a id on \a ptr. + /// \param actor Actor that is resposible for the ID being applied to \a ptr. + /// \return Any effect? + + virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const; + ///< Inform actor \a ptr that a skill use has succeeded. + static void registerSelf(); }; } From 29b4a5e5f7e1611b7854d06a7c205a7615e06e20 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 13 Jul 2012 09:41:38 +0200 Subject: [PATCH 084/298] Issue #314: added apply actions --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwworld/actionapply.cpp | 28 +++++++++++++++++++ apps/openmw/mwworld/actionapply.hpp | 42 +++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 apps/openmw/mwworld/actionapply.cpp create mode 100644 apps/openmw/mwworld/actionapply.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 36e59f6bd..bb3f76f3c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -50,7 +50,7 @@ add_openmw_dir (mwworld refdata worldimp physicssystem scene globals class action nullaction actionteleport containerstore actiontalk actiontake manualref player cellfunctors cells localscripts customdata weather inventorystore ptr actionopen actionread - actionequip timestamp actionalchemy cellstore + actionequip timestamp actionalchemy cellstore actionapply ) add_openmw_dir (mwclass diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp new file mode 100644 index 000000000..c5228d798 --- /dev/null +++ b/apps/openmw/mwworld/actionapply.cpp @@ -0,0 +1,28 @@ + +#include "actionapply.hpp" + +#include "class.hpp" + +namespace MWWorld +{ + ActionApply::ActionApply (const Ptr& target, const std::string& id, const Ptr& actor) + : mTarget (target), mId (id), mActor (actor) + {} + + void ActionApply::execute() + { + MWWorld::Class::get (mTarget).apply (mTarget, mId, mActor); + } + + + ActionApplyWithSkill::ActionApplyWithSkill (const Ptr& target, const std::string& id, + const Ptr& actor, int skillIndex, int usageType) + : mTarget (target), mId (id), mActor (actor), mSkillIndex (skillIndex), mUsageType (usageType) + {} + + void ActionApplyWithSkill::execute() + { + if (MWWorld::Class::get (mTarget).apply (mTarget, mId, mActor)) + MWWorld::Class::get (mTarget).skillUsageSucceeded (mActor, mSkillIndex, mUsageType); + } +} diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp new file mode 100644 index 000000000..972417e02 --- /dev/null +++ b/apps/openmw/mwworld/actionapply.hpp @@ -0,0 +1,42 @@ + +#ifndef GAME_MWWORLD_ACTIONAPPLY_H +#define GAME_MWWORLD_ACTIONAPPLY_H + +#include + +#include "action.hpp" +#include "ptr.hpp" + +namespace MWWorld +{ + class ActionApply : public Action + { + Ptr mTarget; + std::string mId; + Ptr mActor; + + public: + + ActionApply (const Ptr& target, const std::string& id, const Ptr& actor); + + virtual void execute(); + }; + + class ActionApplyWithSkill : public Action + { + Ptr mTarget; + std::string mId; + Ptr mActor; + int mSkillIndex; + int mUsageType; + + public: + + ActionApplyWithSkill (const Ptr& target, const std::string& id, const Ptr& actor, + int skillIndex, int usageType); + + virtual void execute(); + }; +} + +#endif From d2fbb114757e884dbeb6b6a0dd6557ac64464c1e Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jul 2012 11:26:36 +0200 Subject: [PATCH 085/298] temp commit --- apps/openmw/mwrender/terrainmaterial.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index a066a9bed..ae04306f6 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -42,11 +42,13 @@ namespace MWRender Ogre::MaterialPtr TerrainMaterial::Profile::generate(const Ogre::Terrain* terrain) { const Ogre::String& matName = terrain->getMaterialName(); + + sh::Factory::getInstance().destroyMaterialInstance (matName); + Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(matName); if (!mat.isNull()) Ogre::MaterialManager::getSingleton().remove(matName); - mMaterial = sh::Factory::getInstance().createMaterialInstance (matName); mMaterial->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); @@ -54,6 +56,11 @@ namespace MWRender createPass(); return Ogre::MaterialManager::getSingleton().getByName(matName); + + /* + Ogre::MaterialPtr m = Ogre::MaterialManager::getSingleton().create(matName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + return m; + */ } int TerrainMaterial::Profile::getLayersPerPass () const From fb109ec7e2bc57c89afdd46f3ae95e070c97c66c Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jul 2012 11:30:47 +0200 Subject: [PATCH 086/298] use debug versions of ogre plugins in debug mode --- CMakeLists.txt | 6 ++++++ files/plugins.cfg.linux | 9 ++++----- files/plugins.cfg.mac | 8 ++++---- files/plugins.cfg.win32 | 10 +++++----- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 84cef306e..b561815ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,12 @@ set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") +# Debug suffix for plugins +set(DEBUG_SUFFIX "") +if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(DEBUG_SUFFIX "_d") +endif() + # doxygen main page configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp") diff --git a/files/plugins.cfg.linux b/files/plugins.cfg.linux index f34621a0f..bbce1f1b4 100644 --- a/files/plugins.cfg.linux +++ b/files/plugins.cfg.linux @@ -4,9 +4,8 @@ PluginFolder=${OGRE_PLUGIN_DIR_REL} # Define plugins -Plugin=RenderSystem_GL -Plugin=Plugin_ParticleFX -Plugin=Plugin_OctreeSceneManager -Plugin=Plugin_CgProgramManager - +Plugin=RenderSystem_GL${OGRE_RenderSystem_GL_LIBRARIES} +Plugin=Plugin_ParticleFX${DEBUG_SUFFIX} +Plugin=Plugin_OctreeSceneManager${DEBUG_SUFFIX} +Plugin=Plugin_CgProgramManager${DEBUG_SUFFIX} diff --git a/files/plugins.cfg.mac b/files/plugins.cfg.mac index 322070832..fac18dc8f 100644 --- a/files/plugins.cfg.mac +++ b/files/plugins.cfg.mac @@ -4,9 +4,9 @@ PluginFolder=${OGRE_PLUGIN_DIR} # Define plugins -Plugin=RenderSystem_GL.1.8.0 -Plugin=Plugin_ParticleFX.1.8.0 -Plugin=Plugin_OctreeSceneManager.1.8.0 -Plugin=Plugin_CgProgramManager.1.8.0 +Plugin=RenderSystem_GL${DEBUG_SUFFIX}.1.8.0 +Plugin=Plugin_ParticleFX${DEBUG_SUFFIX}.1.8.0 +Plugin=Plugin_OctreeSceneManager${DEBUG_SUFFIX}.1.8.0 +Plugin=Plugin_CgProgramManager${DEBUG_SUFFIX}.1.8.0 diff --git a/files/plugins.cfg.win32 b/files/plugins.cfg.win32 index ea12c0394..6b4e9ef9d 100644 --- a/files/plugins.cfg.win32 +++ b/files/plugins.cfg.win32 @@ -4,10 +4,10 @@ PluginFolder=.\ # Define plugins -Plugin=RenderSystem_Direct3D9 -Plugin=RenderSystem_GL -Plugin=Plugin_ParticleFX -Plugin=Plugin_OctreeSceneManager -Plugin=Plugin_CgProgramManager +Plugin=RenderSystem_Direct3D9${DEBUG_SUFFIX} +Plugin=RenderSystem_GL${DEBUG_SUFFIX} +Plugin=Plugin_ParticleFX${DEBUG_SUFFIX} +Plugin=Plugin_OctreeSceneManager${DEBUG_SUFFIX} +Plugin=Plugin_CgProgramManager${DEBUG_SUFFIX} From 1429c8d5cb088780d6cd969f2eb807f7f41b49e1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jul 2012 12:43:48 +0200 Subject: [PATCH 087/298] copy&paste mistake --- files/plugins.cfg.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/plugins.cfg.linux b/files/plugins.cfg.linux index bbce1f1b4..7b8d99e8f 100644 --- a/files/plugins.cfg.linux +++ b/files/plugins.cfg.linux @@ -4,7 +4,7 @@ PluginFolder=${OGRE_PLUGIN_DIR_REL} # Define plugins -Plugin=RenderSystem_GL${OGRE_RenderSystem_GL_LIBRARIES} +Plugin=RenderSystem_GL${DEBUG_SUFFIX} Plugin=Plugin_ParticleFX${DEBUG_SUFFIX} Plugin=Plugin_OctreeSceneManager${DEBUG_SUFFIX} Plugin=Plugin_CgProgramManager${DEBUG_SUFFIX} From e4d046f69c79145d7ce46173184065a5877f12d2 Mon Sep 17 00:00:00 2001 From: Carl Maxwell Date: Fri, 13 Jul 2012 03:51:58 -0700 Subject: [PATCH 088/298] Prepending m to the name of every member variable. I made a bunch of changes in apps/openmw/mwrender/animation.cpp because the scope brackets didn't line up in a bunch of places npcanimations.cpp & creatureanimations.cpp were the same kind of thing --- apps/openmw/mwclass/misc.hpp | 2 +- apps/openmw/mwclass/npc.hpp | 2 +- apps/openmw/mwclass/potion.hpp | 2 +- apps/openmw/mwclass/static.hpp | 2 +- apps/openmw/mwdialogue/dialoguemanager.cpp | 18 +- apps/openmw/mwdialogue/dialoguemanager.hpp | 4 +- apps/openmw/mwgui/birth.cpp | 60 +- apps/openmw/mwgui/birth.hpp | 12 +- apps/openmw/mwgui/class.cpp | 466 ++++---- apps/openmw/mwgui/class.hpp | 98 +- apps/openmw/mwgui/dialogue.cpp | 80 +- apps/openmw/mwgui/dialogue.hpp | 8 +- apps/openmw/mwgui/hud.cpp | 172 +-- apps/openmw/mwgui/hud.hpp | 20 +- apps/openmw/mwgui/journalwindow.cpp | 24 +- apps/openmw/mwgui/journalwindow.hpp | 12 +- apps/openmw/mwgui/messagebox.cpp | 4 +- apps/openmw/mwgui/messagebox.hpp | 2 +- apps/openmw/mwgui/race.cpp | 100 +- apps/openmw/mwgui/race.hpp | 26 +- apps/openmw/mwgui/review.cpp | 188 +-- apps/openmw/mwgui/review.hpp | 26 +- apps/openmw/mwgui/stats_window.cpp | 200 ++-- apps/openmw/mwgui/stats_window.hpp | 26 +- apps/openmw/mwgui/text_input.cpp | 8 +- apps/openmw/mwgui/text_input.hpp | 6 +- apps/openmw/mwgui/widgets.cpp | 166 +-- apps/openmw/mwgui/widgets.hpp | 66 +- apps/openmw/mwgui/window_manager.cpp | 206 ++-- apps/openmw/mwgui/window_manager.hpp | 52 +- apps/openmw/mwrender/animation.cpp | 751 ++++++------ apps/openmw/mwrender/animation.hpp | 45 +- apps/openmw/mwrender/creatureanimation.cpp | 81 +- apps/openmw/mwrender/creatureanimation.hpp | 3 +- apps/openmw/mwrender/npcanimation.cpp | 1227 +++++++++++--------- apps/openmw/mwrender/npcanimation.hpp | 101 +- 36 files changed, 2163 insertions(+), 2103 deletions(-) diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 46b5b9662..da5f0df96 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -9,7 +9,7 @@ namespace MWClass { public: - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 4cb733977..dcb9eaee0 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -48,7 +48,7 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr - virtual void setForceStance (const MWWorld::Ptr& ptr, Stance stance, bool force) const; + virtual void setForceStance (const MWWorld::Ptr& ptr, Stance stance, bool force) const; ///< Force or unforce a stance. virtual void setStance (const MWWorld::Ptr& ptr, Stance stance, bool set) const; diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 74779864a..97a4ab80a 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -9,7 +9,7 @@ namespace MWClass { public: - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index cd1626c19..c223df1ac 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -9,7 +9,7 @@ namespace MWClass { public: - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; diff --git a/apps/openmw/mwdialogue/dialoguemanager.cpp b/apps/openmw/mwdialogue/dialoguemanager.cpp index 41ffd1e93..f6a17baa5 100644 --- a/apps/openmw/mwdialogue/dialoguemanager.cpp +++ b/apps/openmw/mwdialogue/dialoguemanager.cpp @@ -591,7 +591,7 @@ namespace MWDialogue mIsInChoice = false; mCompilerContext.setExtensions (&extensions); mDialogueMap.clear(); - actorKnownTopics.clear(); + mActorKnownTopics.clear(); ESMS::RecListCaseT::MapType dialogueList = MWBase::Environment::get().getWorld()->getStore().dialogs.list; for(ESMS::RecListCaseT::MapType::iterator it = dialogueList.begin(); it!=dialogueList.end();it++) { @@ -601,24 +601,24 @@ namespace MWDialogue void DialogueManager::addTopic(std::string topic) { - knownTopics[toLower(topic)] = true; + mKnownTopics[toLower(topic)] = true; } void DialogueManager::parseText(std::string text) { std::list::iterator it; - for(it = actorKnownTopics.begin();it != actorKnownTopics.end();++it) + for(it = mActorKnownTopics.begin();it != mActorKnownTopics.end();++it) { size_t pos = find_str_ci(text,*it,0); if(pos !=std::string::npos) { if(pos==0) { - knownTopics[*it] = true; + mKnownTopics[*it] = true; } else if(text.substr(pos -1,1) == " ") { - knownTopics[*it] = true; + mKnownTopics[*it] = true; } } } @@ -632,7 +632,7 @@ namespace MWDialogue mActor = actor; - actorKnownTopics.clear(); + mActorKnownTopics.clear(); //initialise the GUI MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue); @@ -742,7 +742,7 @@ namespace MWDialogue std::list keywordList; int choice = mChoice; mChoice = -1; - actorKnownTopics.clear(); + mActorKnownTopics.clear(); MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); ESMS::RecListCaseT::MapType dialogueList = MWBase::Environment::get().getWorld()->getStore().dialogs.list; for(ESMS::RecListCaseT::MapType::iterator it = dialogueList.begin(); it!=dialogueList.end();it++) @@ -755,9 +755,9 @@ namespace MWDialogue { if (isMatching (mActor, *iter) && functionFilter(mActor,*iter,true)) { - actorKnownTopics.push_back(toLower(it->first)); + mActorKnownTopics.push_back(toLower(it->first)); //does the player know the topic? - if(knownTopics.find(toLower(it->first)) != knownTopics.end()) + if(mKnownTopics.find(toLower(it->first)) != mKnownTopics.end()) { keywordList.push_back(it->first); break; diff --git a/apps/openmw/mwdialogue/dialoguemanager.hpp b/apps/openmw/mwdialogue/dialoguemanager.hpp index 992175c0c..d139ddc01 100644 --- a/apps/openmw/mwdialogue/dialoguemanager.hpp +++ b/apps/openmw/mwdialogue/dialoguemanager.hpp @@ -26,8 +26,8 @@ namespace MWDialogue void updateTopics(); std::map mDialogueMap; - std::map knownTopics;// Those are the topics the player knows. - std::list actorKnownTopics; + std::map mKnownTopics;// Those are the topics the player knows. + std::list mActorKnownTopics; MWScript::CompilerContext mCompilerContext; std::ostream mErrorStream; diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 69056759d..0f1a04c27 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -15,15 +15,15 @@ BirthDialog::BirthDialog(WindowManager& parWindowManager) // Centre dialog center(); - getWidget(spellArea, "SpellArea"); + getWidget(mSpellArea, "SpellArea"); - getWidget(birthImage, "BirthsignImage"); + getWidget(mBirthImage, "BirthsignImage"); - getWidget(birthList, "BirthsignList"); - birthList->setScrollVisible(true); - birthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); - birthList->eventListMouseItemActivate += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); - birthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); + getWidget(mBirthList, "BirthsignList"); + mBirthList->setScrollVisible(true); + mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); + mBirthList->eventListMouseItemActivate += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); + mBirthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); @@ -68,14 +68,14 @@ void BirthDialog::open() void BirthDialog::setBirthId(const std::string &birthId) { - currentBirthId = birthId; - birthList->setIndexSelected(MyGUI::ITEM_NONE); - size_t count = birthList->getItemCount(); + mCurrentBirthId = birthId; + mBirthList->setIndexSelected(MyGUI::ITEM_NONE); + size_t count = mBirthList->getItemCount(); for (size_t i = 0; i < count; ++i) { - if (boost::iequals(*birthList->getItemDataAt(i), birthId)) + if (boost::iequals(*mBirthList->getItemDataAt(i), birthId)) { - birthList->setIndexSelected(i); + mBirthList->setIndexSelected(i); break; } } @@ -100,11 +100,11 @@ void BirthDialog::onSelectBirth(MyGUI::ListBox* _sender, size_t _index) if (_index == MyGUI::ITEM_NONE) return; - const std::string *birthId = birthList->getItemDataAt(_index); - if (boost::iequals(currentBirthId, *birthId)) + const std::string *birthId = mBirthList->getItemDataAt(_index); + if (boost::iequals(mCurrentBirthId, *birthId)) return; - currentBirthId = *birthId; + mCurrentBirthId = *birthId; updateSpells(); } @@ -112,7 +112,7 @@ void BirthDialog::onSelectBirth(MyGUI::ListBox* _sender, size_t _index) void BirthDialog::updateBirths() { - birthList->removeAllItems(); + mBirthList->removeAllItems(); const ESMS::ESMStore &store = mWindowManager.getStore(); @@ -122,34 +122,34 @@ void BirthDialog::updateBirths() for (; it != end; ++it) { const ESM::BirthSign &birth = it->second; - birthList->addItem(birth.name, it->first); - if (boost::iequals(it->first, currentBirthId)) - birthList->setIndexSelected(index); + mBirthList->addItem(birth.name, it->first); + if (boost::iequals(it->first, mCurrentBirthId)) + mBirthList->setIndexSelected(index); ++index; } } void BirthDialog::updateSpells() { - for (std::vector::iterator it = spellItems.begin(); it != spellItems.end(); ++it) + for (std::vector::iterator it = mSpellItems.begin(); it != mSpellItems.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } - spellItems.clear(); + mSpellItems.clear(); - if (currentBirthId.empty()) + if (mCurrentBirthId.empty()) return; MWSpellPtr spellWidget; const int lineHeight = 18; - MyGUI::IntCoord coord(0, 0, spellArea->getWidth(), 18); + MyGUI::IntCoord coord(0, 0, mSpellArea->getWidth(), 18); const ESMS::ESMStore &store = mWindowManager.getStore(); - const ESM::BirthSign *birth = store.birthSigns.find(currentBirthId); + const ESM::BirthSign *birth = store.birthSigns.find(mCurrentBirthId); std::string texturePath = std::string("textures\\") + birth->texture; fixTexturePath(texturePath); - birthImage->setImageTexture(texturePath); + mBirthImage->setImageTexture(texturePath); std::vector abilities, powers, spells; @@ -183,25 +183,25 @@ void BirthDialog::updateSpells() { if (!categories[category].spells.empty()) { - MyGUI::TextBox* label = spellArea->createWidget("SandBrightText", coord, MyGUI::Align::Default, std::string("Label")); + MyGUI::TextBox* label = mSpellArea->createWidget("SandBrightText", coord, MyGUI::Align::Default, std::string("Label")); label->setCaption(mWindowManager.getGameSettingString(categories[category].label, "")); - spellItems.push_back(label); + mSpellItems.push_back(label); coord.top += lineHeight; std::vector::const_iterator end = categories[category].spells.end(); for (std::vector::const_iterator it = categories[category].spells.begin(); it != end; ++it) { const std::string &spellId = *it; - spellWidget = spellArea->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("Spell") + boost::lexical_cast(i)); + spellWidget = mSpellArea->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("Spell") + boost::lexical_cast(i)); spellWidget->setWindowManager(&mWindowManager); spellWidget->setSpellId(spellId); - spellItems.push_back(spellWidget); + mSpellItems.push_back(spellWidget); coord.top += lineHeight; MyGUI::IntCoord spellCoord = coord; spellCoord.height = 24; // TODO: This should be fetched from the skin somehow, or perhaps a widget in the layout as a template? - spellWidget->createEffectWidgets(spellItems, spellArea, spellCoord, (category == 0) ? MWEffectList::EF_Constant : 0); + spellWidget->createEffectWidgets(mSpellItems, mSpellArea, spellCoord, (category == 0) ? MWEffectList::EF_Constant : 0); coord.top = spellCoord.top; ++i; diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp index e61be736a..770e4ba36 100644 --- a/apps/openmw/mwgui/birth.hpp +++ b/apps/openmw/mwgui/birth.hpp @@ -25,7 +25,7 @@ namespace MWGui GM_Female }; - const std::string &getBirthId() const { return currentBirthId; } + const std::string &getBirthId() const { return mCurrentBirthId; } void setBirthId(const std::string &raceId); void setNextButtonShow(bool shown); @@ -49,12 +49,12 @@ namespace MWGui void updateBirths(); void updateSpells(); - MyGUI::ListBox* birthList; - MyGUI::WidgetPtr spellArea; - MyGUI::ImageBox* birthImage; - std::vector spellItems; + MyGUI::ListBox* mBirthList; + MyGUI::WidgetPtr mSpellArea; + MyGUI::ImageBox* mBirthImage; + std::vector mSpellItems; - std::string currentBirthId; + std::string mCurrentBirthId; }; } #endif diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index d0f21f945..8ec1331ad 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -26,8 +26,8 @@ GenerateClassResultDialog::GenerateClassResultDialog(WindowManager& parWindowMan setText("ReflectT", mWindowManager.getGameSettingString("sMessageQuestionAnswer1", "")); - getWidget(classImage, "ClassImage"); - getWidget(className, "ClassName"); + getWidget(mClassImage, "ClassImage"); + getWidget(mClassName, "ClassName"); MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); @@ -51,15 +51,15 @@ void GenerateClassResultDialog::open() std::string GenerateClassResultDialog::getClassId() const { - return className->getCaption(); + return mClassName->getCaption(); } void GenerateClassResultDialog::setClassId(const std::string &classId) { - currentClassId = classId; - classImage->setImageTexture(std::string("textures\\levelup\\") + currentClassId + ".dds"); + mCurrentClassId = classId; + mClassImage->setImageTexture(std::string("textures\\levelup\\") + mCurrentClassId + ".dds"); const ESMS::ESMStore &store = mWindowManager.getStore(); - className->setCaption(store.classes.find(currentClassId)->name); + mClassName->setCaption(store.classes.find(mCurrentClassId)->name); } // widget controls @@ -82,29 +82,29 @@ PickClassDialog::PickClassDialog(WindowManager& parWindowManager) // Centre dialog center(); - getWidget(specializationName, "SpecializationName"); + getWidget(mSpecializationName, "SpecializationName"); - getWidget(favoriteAttribute[0], "FavoriteAttribute0"); - getWidget(favoriteAttribute[1], "FavoriteAttribute1"); - favoriteAttribute[0]->setWindowManager(&mWindowManager); - favoriteAttribute[1]->setWindowManager(&mWindowManager); + getWidget(mFavoriteAttribute[0], "FavoriteAttribute0"); + getWidget(mFavoriteAttribute[1], "FavoriteAttribute1"); + mFavoriteAttribute[0]->setWindowManager(&mWindowManager); + mFavoriteAttribute[1]->setWindowManager(&mWindowManager); for(int i = 0; i < 5; i++) { char theIndex = '0'+i; - getWidget(majorSkill[i], std::string("MajorSkill").append(1, theIndex)); - getWidget(minorSkill[i], std::string("MinorSkill").append(1, theIndex)); - majorSkill[i]->setWindowManager(&mWindowManager); - minorSkill[i]->setWindowManager(&mWindowManager); + getWidget(mMajorSkill[i], std::string("MajorSkill").append(1, theIndex)); + getWidget(mMinorSkill[i], std::string("MinorSkill").append(1, theIndex)); + mMajorSkill[i]->setWindowManager(&mWindowManager); + mMinorSkill[i]->setWindowManager(&mWindowManager); } - getWidget(classList, "ClassList"); - classList->setScrollVisible(true); - classList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); - classList->eventListMouseItemActivate += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); - classList->eventListChangePosition += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); + getWidget(mClassList, "ClassList"); + mClassList->setScrollVisible(true); + mClassList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); + mClassList->eventListMouseItemActivate += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); + mClassList->eventListChangePosition += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); - getWidget(classImage, "ClassImage"); + getWidget(mClassImage, "ClassImage"); MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); @@ -148,14 +148,14 @@ void PickClassDialog::open() void PickClassDialog::setClassId(const std::string &classId) { - currentClassId = classId; - classList->setIndexSelected(MyGUI::ITEM_NONE); - size_t count = classList->getItemCount(); + mCurrentClassId = classId; + mClassList->setIndexSelected(MyGUI::ITEM_NONE); + size_t count = mClassList->getItemCount(); for (size_t i = 0; i < count; ++i) { - if (boost::iequals(*classList->getItemDataAt(i), classId)) + if (boost::iequals(*mClassList->getItemDataAt(i), classId)) { - classList->setIndexSelected(i); + mClassList->setIndexSelected(i); break; } } @@ -180,11 +180,11 @@ void PickClassDialog::onSelectClass(MyGUI::ListBox* _sender, size_t _index) if (_index == MyGUI::ITEM_NONE) return; - const std::string *classId = classList->getItemDataAt(_index); - if (boost::iequals(currentClassId, *classId)) + const std::string *classId = mClassList->getItemDataAt(_index); + if (boost::iequals(mCurrentClassId, *classId)) return; - currentClassId = *classId; + mCurrentClassId = *classId; updateStats(); } @@ -192,7 +192,7 @@ void PickClassDialog::onSelectClass(MyGUI::ListBox* _sender, size_t _index) void PickClassDialog::updateClasses() { - classList->removeAllItems(); + mClassList->removeAllItems(); const ESMS::ESMStore &store = mWindowManager.getStore(); @@ -207,19 +207,19 @@ void PickClassDialog::updateClasses() continue; const std::string &id = it->first; - classList->addItem(klass.name, id); - if (boost::iequals(id, currentClassId)) - classList->setIndexSelected(index); + mClassList->addItem(klass.name, id); + if (boost::iequals(id, mCurrentClassId)) + mClassList->setIndexSelected(index); ++index; } } void PickClassDialog::updateStats() { - if (currentClassId.empty()) + if (mCurrentClassId.empty()) return; const ESMS::ESMStore &store = mWindowManager.getStore(); - const ESM::Class *klass = store.classes.search(currentClassId); + const ESM::Class *klass = store.classes.search(mCurrentClassId); if (!klass) return; @@ -231,23 +231,23 @@ void PickClassDialog::updateStats() "sSpecializationStealth" }; std::string specName = mWindowManager.getGameSettingString(specIds[specialization], specIds[specialization]); - specializationName->setCaption(specName); - ToolTips::createSpecializationToolTip(specializationName, specName, specialization); + mSpecializationName->setCaption(specName); + ToolTips::createSpecializationToolTip(mSpecializationName, specName, specialization); - favoriteAttribute[0]->setAttributeId(klass->data.attribute[0]); - favoriteAttribute[1]->setAttributeId(klass->data.attribute[1]); - ToolTips::createAttributeToolTip(favoriteAttribute[0], favoriteAttribute[0]->getAttributeId()); - ToolTips::createAttributeToolTip(favoriteAttribute[1], favoriteAttribute[1]->getAttributeId()); + mFavoriteAttribute[0]->setAttributeId(klass->data.attribute[0]); + mFavoriteAttribute[1]->setAttributeId(klass->data.attribute[1]); + ToolTips::createAttributeToolTip(mFavoriteAttribute[0], mFavoriteAttribute[0]->getAttributeId()); + ToolTips::createAttributeToolTip(mFavoriteAttribute[1], mFavoriteAttribute[1]->getAttributeId()); for (int i = 0; i < 5; ++i) { - minorSkill[i]->setSkillNumber(klass->data.skills[i][0]); - majorSkill[i]->setSkillNumber(klass->data.skills[i][1]); - ToolTips::createSkillToolTip(minorSkill[i], klass->data.skills[i][0]); - ToolTips::createSkillToolTip(majorSkill[i], klass->data.skills[i][1]); + mMinorSkill[i]->setSkillNumber(klass->data.skills[i][0]); + mMajorSkill[i]->setSkillNumber(klass->data.skills[i][1]); + ToolTips::createSkillToolTip(mMinorSkill[i], klass->data.skills[i][0]); + ToolTips::createSkillToolTip(mMajorSkill[i], klass->data.skills[i][1]); } - classImage->setImageTexture(std::string("textures\\levelup\\") + currentClassId + ".dds"); + mClassImage->setImageTexture(std::string("textures\\levelup\\") + mCurrentClassId + ".dds"); } /* InfoBoxDialog */ @@ -284,59 +284,59 @@ void InfoBoxDialog::layoutVertically(MyGUI::WidgetPtr widget, int margin) InfoBoxDialog::InfoBoxDialog(WindowManager& parWindowManager) : WindowBase("openmw_infobox.layout", parWindowManager) - , currentButton(-1) + , mCurrentButton(-1) { - getWidget(textBox, "TextBox"); - getWidget(text, "Text"); - text->getSubWidgetText()->setWordWrap(true); - getWidget(buttonBar, "ButtonBar"); + getWidget(mTextBox, "TextBox"); + getWidget(mText, "Text"); + mText->getSubWidgetText()->setWordWrap(true); + getWidget(mButtonBar, "ButtonBar"); center(); } void InfoBoxDialog::setText(const std::string &str) { - text->setCaption(str); - textBox->setVisible(!str.empty()); - fitToText(text); + mText->setCaption(str); + mTextBox->setVisible(!str.empty()); + fitToText(mText); } std::string InfoBoxDialog::getText() const { - return text->getCaption(); + return mText->getCaption(); } void InfoBoxDialog::setButtons(ButtonList &buttons) { - for (std::vector::iterator it = this->buttons.begin(); it != this->buttons.end(); ++it) + for (std::vector::iterator it = this->mButtons.begin(); it != this->mButtons.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } - this->buttons.clear(); - currentButton = -1; + this->mButtons.clear(); + mCurrentButton = -1; // TODO: The buttons should be generated from a template in the layout file, ie. cloning an existing widget MyGUI::ButtonPtr button; - MyGUI::IntCoord coord = MyGUI::IntCoord(0, 0, buttonBar->getWidth(), 10); + MyGUI::IntCoord coord = MyGUI::IntCoord(0, 0, mButtonBar->getWidth(), 10); ButtonList::const_iterator end = buttons.end(); for (ButtonList::const_iterator it = buttons.begin(); it != end; ++it) { const std::string &text = *it; - button = buttonBar->createWidget("MW_Button", coord, MyGUI::Align::Top | MyGUI::Align::HCenter, ""); + button = mButtonBar->createWidget("MW_Button", coord, MyGUI::Align::Top | MyGUI::Align::HCenter, ""); button->getSubWidgetText()->setWordWrap(true); button->setCaption(text); fitToText(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &InfoBoxDialog::onButtonClicked); coord.top += button->getHeight(); - this->buttons.push_back(button); + this->mButtons.push_back(button); } } void InfoBoxDialog::open() { // Fix layout - layoutVertically(textBox, 4); - layoutVertically(buttonBar, 6); + layoutVertically(mTextBox, 4); + layoutVertically(mButtonBar, 6); layoutVertically(mMainWidget, 4 + 6); center(); @@ -345,18 +345,18 @@ void InfoBoxDialog::open() int InfoBoxDialog::getChosenButton() const { - return currentButton; + return mCurrentButton; } void InfoBoxDialog::onButtonClicked(MyGUI::WidgetPtr _sender) { - std::vector::const_iterator end = buttons.end(); + std::vector::const_iterator end = mButtons.end(); int i = 0; - for (std::vector::const_iterator it = buttons.begin(); it != end; ++it) + for (std::vector::const_iterator it = mButtons.begin(); it != end; ++it) { if (*it == _sender) { - currentButton = i; + mCurrentButton = i; eventButtonSelected(i); return; } @@ -382,49 +382,49 @@ ClassChoiceDialog::ClassChoiceDialog(WindowManager& parWindowManager) CreateClassDialog::CreateClassDialog(WindowManager& parWindowManager) : WindowBase("openmw_chargen_create_class.layout", parWindowManager) - , specDialog(nullptr) - , attribDialog(nullptr) - , skillDialog(nullptr) - , descDialog(nullptr) + , mSpecDialog(nullptr) + , mAttribDialog(nullptr) + , mSkillDialog(nullptr) + , mDescDialog(nullptr) { // Centre dialog center(); setText("SpecializationT", mWindowManager.getGameSettingString("sChooseClassMenu1", "Specialization")); - getWidget(specializationName, "SpecializationName"); - specializationName->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationClicked); + getWidget(mSpecializationName, "SpecializationName"); + mSpecializationName->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationClicked); setText("FavoriteAttributesT", mWindowManager.getGameSettingString("sChooseClassMenu2", "Favorite Attributes:")); - getWidget(favoriteAttribute0, "FavoriteAttribute0"); - getWidget(favoriteAttribute1, "FavoriteAttribute1"); - favoriteAttribute0->setWindowManager(&mWindowManager); - favoriteAttribute1->setWindowManager(&mWindowManager); - favoriteAttribute0->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked); - favoriteAttribute1->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked); + getWidget(mFavoriteAttribute0, "FavoriteAttribute0"); + getWidget(mFavoriteAttribute1, "FavoriteAttribute1"); + mFavoriteAttribute0->setWindowManager(&mWindowManager); + mFavoriteAttribute1->setWindowManager(&mWindowManager); + mFavoriteAttribute0->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked); + mFavoriteAttribute1->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked); setText("MajorSkillT", mWindowManager.getGameSettingString("sSkillClassMajor", "")); setText("MinorSkillT", mWindowManager.getGameSettingString("sSkillClassMinor", "")); for(int i = 0; i < 5; i++) { char theIndex = '0'+i; - getWidget(majorSkill[i], std::string("MajorSkill").append(1, theIndex)); - getWidget(minorSkill[i], std::string("MinorSkill").append(1, theIndex)); - skills.push_back(majorSkill[i]); - skills.push_back(minorSkill[i]); + getWidget(mMajorSkill[i], std::string("MajorSkill").append(1, theIndex)); + getWidget(mMinorSkill[i], std::string("MinorSkill").append(1, theIndex)); + mSkills.push_back(mMajorSkill[i]); + mSkills.push_back(mMinorSkill[i]); } - std::vector::const_iterator end = skills.end(); - for (std::vector::const_iterator it = skills.begin(); it != end; ++it) + std::vector::const_iterator end = mSkills.end(); + for (std::vector::const_iterator it = mSkills.begin(); it != end; ++it) { (*it)->setWindowManager(&mWindowManager); (*it)->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onSkillClicked); } setText("LabelT", mWindowManager.getGameSettingString("sName", "")); - getWidget(editName, "EditName"); + getWidget(mEditName, "EditName"); // Make sure the edit box has focus - MyGUI::InputManager::getInstance().setKeyFocusWidget(editName); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mEditName); MyGUI::ButtonPtr descriptionButton; getWidget(descriptionButton, "DescriptionButton"); @@ -441,20 +441,20 @@ CreateClassDialog::CreateClassDialog(WindowManager& parWindowManager) // Set default skills, attributes - favoriteAttribute0->setAttributeId(ESM::Attribute::Strength); - favoriteAttribute1->setAttributeId(ESM::Attribute::Agility); + mFavoriteAttribute0->setAttributeId(ESM::Attribute::Strength); + mFavoriteAttribute1->setAttributeId(ESM::Attribute::Agility); - majorSkill[0]->setSkillId(ESM::Skill::Block); - majorSkill[1]->setSkillId(ESM::Skill::Armorer); - majorSkill[2]->setSkillId(ESM::Skill::MediumArmor); - majorSkill[3]->setSkillId(ESM::Skill::HeavyArmor); - majorSkill[4]->setSkillId(ESM::Skill::BluntWeapon); + mMajorSkill[0]->setSkillId(ESM::Skill::Block); + mMajorSkill[1]->setSkillId(ESM::Skill::Armorer); + mMajorSkill[2]->setSkillId(ESM::Skill::MediumArmor); + mMajorSkill[3]->setSkillId(ESM::Skill::HeavyArmor); + mMajorSkill[4]->setSkillId(ESM::Skill::BluntWeapon); - minorSkill[0]->setSkillId(ESM::Skill::LongBlade); - minorSkill[1]->setSkillId(ESM::Skill::Axe); - minorSkill[2]->setSkillId(ESM::Skill::Spear); - minorSkill[3]->setSkillId(ESM::Skill::Athletics); - minorSkill[4]->setSkillId(ESM::Skill::Enchant); + mMinorSkill[0]->setSkillId(ESM::Skill::LongBlade); + mMinorSkill[1]->setSkillId(ESM::Skill::Axe); + mMinorSkill[2]->setSkillId(ESM::Skill::Spear); + mMinorSkill[3]->setSkillId(ESM::Skill::Athletics); + mMinorSkill[4]->setSkillId(ESM::Skill::Enchant); setSpecialization(0); update(); @@ -462,44 +462,44 @@ CreateClassDialog::CreateClassDialog(WindowManager& parWindowManager) CreateClassDialog::~CreateClassDialog() { - delete specDialog; - delete attribDialog; - delete skillDialog; - delete descDialog; + delete mSpecDialog; + delete mAttribDialog; + delete mSkillDialog; + delete mDescDialog; } void CreateClassDialog::update() { for (int i = 0; i < 5; ++i) { - ToolTips::createSkillToolTip(majorSkill[i], majorSkill[i]->getSkillId()); - ToolTips::createSkillToolTip(minorSkill[i], minorSkill[i]->getSkillId()); + ToolTips::createSkillToolTip(mMajorSkill[i], mMajorSkill[i]->getSkillId()); + ToolTips::createSkillToolTip(mMinorSkill[i], mMinorSkill[i]->getSkillId()); } - ToolTips::createAttributeToolTip(favoriteAttribute0, favoriteAttribute0->getAttributeId()); - ToolTips::createAttributeToolTip(favoriteAttribute1, favoriteAttribute1->getAttributeId()); + ToolTips::createAttributeToolTip(mFavoriteAttribute0, mFavoriteAttribute0->getAttributeId()); + ToolTips::createAttributeToolTip(mFavoriteAttribute1, mFavoriteAttribute1->getAttributeId()); } std::string CreateClassDialog::getName() const { - return editName->getOnlyText(); + return mEditName->getOnlyText(); } std::string CreateClassDialog::getDescription() const { - return description; + return mDescription; } ESM::Class::Specialization CreateClassDialog::getSpecializationId() const { - return specializationId; + return mSpecializationId; } std::vector CreateClassDialog::getFavoriteAttributes() const { std::vector v; - v.push_back(favoriteAttribute0->getAttributeId()); - v.push_back(favoriteAttribute1->getAttributeId()); + v.push_back(mFavoriteAttribute0->getAttributeId()); + v.push_back(mFavoriteAttribute1->getAttributeId()); return v; } @@ -508,7 +508,7 @@ std::vector CreateClassDialog::getMajorSkills() const std::vector v; for(int i = 0; i < 5; i++) { - v.push_back(majorSkill[i]->getSkillId()); + v.push_back(mMajorSkill[i]->getSkillId()); } return v; } @@ -518,7 +518,7 @@ std::vector CreateClassDialog::getMinorSkills() const std::vector v; for(int i=0; i < 5; i++) { - v.push_back(minorSkill[i]->getSkillId()); + v.push_back(mMinorSkill[i]->getSkillId()); } return v; } @@ -557,108 +557,108 @@ void CreateClassDialog::open() void CreateClassDialog::onDialogCancel() { - if (specDialog) + if (mSpecDialog) { - mWindowManager.removeDialog(specDialog); - specDialog = 0; + mWindowManager.removeDialog(mSpecDialog); + mSpecDialog = 0; } - if (attribDialog) + if (mAttribDialog) { - mWindowManager.removeDialog(attribDialog); - attribDialog = 0; + mWindowManager.removeDialog(mAttribDialog); + mAttribDialog = 0; } - if (skillDialog) + if (mSkillDialog) { - mWindowManager.removeDialog(skillDialog); - skillDialog = 0; + mWindowManager.removeDialog(mSkillDialog); + mSkillDialog = 0; } - if (descDialog) + if (mDescDialog) { - mWindowManager.removeDialog(descDialog); - descDialog = 0; + mWindowManager.removeDialog(mDescDialog); + mDescDialog = 0; } } void CreateClassDialog::onSpecializationClicked(MyGUI::WidgetPtr _sender) { - delete specDialog; - specDialog = new SelectSpecializationDialog(mWindowManager); - specDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); - specDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationSelected); - specDialog->setVisible(true); + delete mSpecDialog; + mSpecDialog = new SelectSpecializationDialog(mWindowManager); + mSpecDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); + mSpecDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationSelected); + mSpecDialog->setVisible(true); } void CreateClassDialog::onSpecializationSelected() { - specializationId = specDialog->getSpecializationId(); - setSpecialization(specializationId); + mSpecializationId = mSpecDialog->getSpecializationId(); + setSpecialization(mSpecializationId); - mWindowManager.removeDialog(specDialog); - specDialog = 0; + mWindowManager.removeDialog(mSpecDialog); + mSpecDialog = 0; } void CreateClassDialog::setSpecialization(int id) { - specializationId = (ESM::Class::Specialization) id; + mSpecializationId = (ESM::Class::Specialization) id; static const char *specIds[3] = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; - std::string specName = mWindowManager.getGameSettingString(specIds[specializationId], specIds[specializationId]); - specializationName->setCaption(specName); - ToolTips::createSpecializationToolTip(specializationName, specName, specializationId); + std::string specName = mWindowManager.getGameSettingString(specIds[mSpecializationId], specIds[mSpecializationId]); + mSpecializationName->setCaption(specName); + ToolTips::createSpecializationToolTip(mSpecializationName, specName, mSpecializationId); } void CreateClassDialog::onAttributeClicked(Widgets::MWAttributePtr _sender) { - delete attribDialog; - attribDialog = new SelectAttributeDialog(mWindowManager); - attribDialog->setAffectedWidget(_sender); - attribDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); - attribDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeSelected); - attribDialog->setVisible(true); + delete mAttribDialog; + mAttribDialog = new SelectAttributeDialog(mWindowManager); + mAttribDialog->setAffectedWidget(_sender); + mAttribDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); + mAttribDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeSelected); + mAttribDialog->setVisible(true); } void CreateClassDialog::onAttributeSelected() { - ESM::Attribute::AttributeID id = attribDialog->getAttributeId(); - Widgets::MWAttributePtr attribute = attribDialog->getAffectedWidget(); - if (attribute == favoriteAttribute0) + ESM::Attribute::AttributeID id = mAttribDialog->getAttributeId(); + Widgets::MWAttributePtr attribute = mAttribDialog->getAffectedWidget(); + if (attribute == mFavoriteAttribute0) { - if (favoriteAttribute1->getAttributeId() == id) - favoriteAttribute1->setAttributeId(favoriteAttribute0->getAttributeId()); + if (mFavoriteAttribute1->getAttributeId() == id) + mFavoriteAttribute1->setAttributeId(mFavoriteAttribute0->getAttributeId()); } - else if (attribute == favoriteAttribute1) + else if (attribute == mFavoriteAttribute1) { - if (favoriteAttribute0->getAttributeId() == id) - favoriteAttribute0->setAttributeId(favoriteAttribute1->getAttributeId()); + if (mFavoriteAttribute0->getAttributeId() == id) + mFavoriteAttribute0->setAttributeId(mFavoriteAttribute1->getAttributeId()); } attribute->setAttributeId(id); - mWindowManager.removeDialog(attribDialog); - attribDialog = 0; + mWindowManager.removeDialog(mAttribDialog); + mAttribDialog = 0; update(); } void CreateClassDialog::onSkillClicked(Widgets::MWSkillPtr _sender) { - delete skillDialog; - skillDialog = new SelectSkillDialog(mWindowManager); - skillDialog->setAffectedWidget(_sender); - skillDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); - skillDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSkillSelected); - skillDialog->setVisible(true); + delete mSkillDialog; + mSkillDialog = new SelectSkillDialog(mWindowManager); + mSkillDialog->setAffectedWidget(_sender); + mSkillDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); + mSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSkillSelected); + mSkillDialog->setVisible(true); } void CreateClassDialog::onSkillSelected() { - ESM::Skill::SkillEnum id = skillDialog->getSkillId(); - Widgets::MWSkillPtr skill = skillDialog->getAffectedWidget(); + ESM::Skill::SkillEnum id = mSkillDialog->getSkillId(); + Widgets::MWSkillPtr skill = mSkillDialog->getAffectedWidget(); // Avoid duplicate skills by swapping any skill field that matches the selected one - std::vector::const_iterator end = skills.end(); - for (std::vector::const_iterator it = skills.begin(); it != end; ++it) + std::vector::const_iterator end = mSkills.end(); + for (std::vector::const_iterator it = mSkills.begin(); it != end; ++it) { if (*it == skill) continue; @@ -669,25 +669,25 @@ void CreateClassDialog::onSkillSelected() } } - skill->setSkillId(skillDialog->getSkillId()); - mWindowManager.removeDialog(skillDialog); - skillDialog = 0; + skill->setSkillId(mSkillDialog->getSkillId()); + mWindowManager.removeDialog(mSkillDialog); + mSkillDialog = 0; update(); } void CreateClassDialog::onDescriptionClicked(MyGUI::Widget* _sender) { - descDialog = new DescriptionDialog(mWindowManager); - descDialog->setTextInput(description); - descDialog->eventDone += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionEntered); - descDialog->setVisible(true); + mDescDialog = new DescriptionDialog(mWindowManager); + mDescDialog->setTextInput(mDescription); + mDescDialog->eventDone += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionEntered); + mDescDialog->setVisible(true); } void CreateClassDialog::onDescriptionEntered(WindowBase* parWindow) { - description = descDialog->getTextInput(); - mWindowManager.removeDialog(descDialog); - descDialog = 0; + mDescription = mDescDialog->getTextInput(); + mWindowManager.removeDialog(mDescDialog); + mDescDialog = 0; } void CreateClassDialog::onOkClicked(MyGUI::Widget* _sender) @@ -710,24 +710,24 @@ SelectSpecializationDialog::SelectSpecializationDialog(WindowManager& parWindowM setText("LabelT", mWindowManager.getGameSettingString("sSpecializationMenu1", "")); - getWidget(specialization0, "Specialization0"); - getWidget(specialization1, "Specialization1"); - getWidget(specialization2, "Specialization2"); + getWidget(mSpecialization0, "Specialization0"); + getWidget(mSpecialization1, "Specialization1"); + getWidget(mSpecialization2, "Specialization2"); std::string combat = mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Combat], ""); std::string magic = mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Magic], ""); std::string stealth = mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Stealth], ""); - specialization0->setCaption(combat); - specialization0->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); - specialization1->setCaption(magic); - specialization1->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); - specialization2->setCaption(stealth); - specialization2->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); - specializationId = ESM::Class::Combat; + mSpecialization0->setCaption(combat); + mSpecialization0->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); + mSpecialization1->setCaption(magic); + mSpecialization1->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); + mSpecialization2->setCaption(stealth); + mSpecialization2->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); + mSpecializationId = ESM::Class::Combat; - ToolTips::createSpecializationToolTip(specialization0, combat, ESM::Class::Combat); - ToolTips::createSpecializationToolTip(specialization1, magic, ESM::Class::Magic); - ToolTips::createSpecializationToolTip(specialization2, stealth, ESM::Class::Stealth); + ToolTips::createSpecializationToolTip(mSpecialization0, combat, ESM::Class::Combat); + ToolTips::createSpecializationToolTip(mSpecialization1, magic, ESM::Class::Magic); + ToolTips::createSpecializationToolTip(mSpecialization2, stealth, ESM::Class::Stealth); MyGUI::ButtonPtr cancelButton; getWidget(cancelButton, "CancelButton"); @@ -748,12 +748,12 @@ SelectSpecializationDialog::~SelectSpecializationDialog() void SelectSpecializationDialog::onSpecializationClicked(MyGUI::WidgetPtr _sender) { - if (_sender == specialization0) - specializationId = ESM::Class::Combat; - else if (_sender == specialization1) - specializationId = ESM::Class::Magic; - else if (_sender == specialization2) - specializationId = ESM::Class::Stealth; + if (_sender == mSpecialization0) + mSpecializationId = ESM::Class::Combat; + else if (_sender == mSpecialization1) + mSpecializationId = ESM::Class::Magic; + else if (_sender == mSpecialization2) + mSpecializationId = ESM::Class::Stealth; else return; @@ -807,7 +807,7 @@ SelectAttributeDialog::~SelectAttributeDialog() void SelectAttributeDialog::onAttributeClicked(Widgets::MWAttributePtr _sender) { // TODO: Change MWAttribute to set and get AttributeID enum instead of int - attributeId = static_cast(_sender->getAttributeId()); + mAttributeId = static_cast(_sender->getAttributeId()); eventItemSelected(); } @@ -833,44 +833,44 @@ SelectSkillDialog::SelectSkillDialog(WindowManager& parWindowManager) for(int i = 0; i < 9; i++) { char theIndex = '0'+i; - getWidget(combatSkill[i], std::string("CombatSkill").append(1, theIndex)); - getWidget(magicSkill[i], std::string("MagicSkill").append(1, theIndex)); - getWidget(stealthSkill[i], std::string("StealthSkill").append(1, theIndex)); + getWidget(mCombatSkill[i], std::string("CombatSkill").append(1, theIndex)); + getWidget(mMagicSkill[i], std::string("MagicSkill").append(1, theIndex)); + getWidget(mStealthSkill[i], std::string("StealthSkill").append(1, theIndex)); } - struct {Widgets::MWSkillPtr widget; ESM::Skill::SkillEnum skillId;} skills[3][9] = { + struct {Widgets::MWSkillPtr widget; ESM::Skill::SkillEnum skillId;} mSkills[3][9] = { { - {combatSkill[0], ESM::Skill::Block}, - {combatSkill[1], ESM::Skill::Armorer}, - {combatSkill[2], ESM::Skill::MediumArmor}, - {combatSkill[3], ESM::Skill::HeavyArmor}, - {combatSkill[4], ESM::Skill::BluntWeapon}, - {combatSkill[5], ESM::Skill::LongBlade}, - {combatSkill[6], ESM::Skill::Axe}, - {combatSkill[7], ESM::Skill::Spear}, - {combatSkill[8], ESM::Skill::Athletics} + {mCombatSkill[0], ESM::Skill::Block}, + {mCombatSkill[1], ESM::Skill::Armorer}, + {mCombatSkill[2], ESM::Skill::MediumArmor}, + {mCombatSkill[3], ESM::Skill::HeavyArmor}, + {mCombatSkill[4], ESM::Skill::BluntWeapon}, + {mCombatSkill[5], ESM::Skill::LongBlade}, + {mCombatSkill[6], ESM::Skill::Axe}, + {mCombatSkill[7], ESM::Skill::Spear}, + {mCombatSkill[8], ESM::Skill::Athletics} }, { - {magicSkill[0], ESM::Skill::Enchant}, - {magicSkill[1], ESM::Skill::Destruction}, - {magicSkill[2], ESM::Skill::Alteration}, - {magicSkill[3], ESM::Skill::Illusion}, - {magicSkill[4], ESM::Skill::Conjuration}, - {magicSkill[5], ESM::Skill::Mysticism}, - {magicSkill[6], ESM::Skill::Restoration}, - {magicSkill[7], ESM::Skill::Alchemy}, - {magicSkill[8], ESM::Skill::Unarmored} + {mMagicSkill[0], ESM::Skill::Enchant}, + {mMagicSkill[1], ESM::Skill::Destruction}, + {mMagicSkill[2], ESM::Skill::Alteration}, + {mMagicSkill[3], ESM::Skill::Illusion}, + {mMagicSkill[4], ESM::Skill::Conjuration}, + {mMagicSkill[5], ESM::Skill::Mysticism}, + {mMagicSkill[6], ESM::Skill::Restoration}, + {mMagicSkill[7], ESM::Skill::Alchemy}, + {mMagicSkill[8], ESM::Skill::Unarmored} }, { - {stealthSkill[0], ESM::Skill::Security}, - {stealthSkill[1], ESM::Skill::Sneak}, - {stealthSkill[2], ESM::Skill::Acrobatics}, - {stealthSkill[3], ESM::Skill::LightArmor}, - {stealthSkill[4], ESM::Skill::ShortBlade}, - {stealthSkill[5] ,ESM::Skill::Marksman}, - {stealthSkill[6] ,ESM::Skill::Mercantile}, - {stealthSkill[7] ,ESM::Skill::Speechcraft}, - {stealthSkill[8] ,ESM::Skill::HandToHand} + {mStealthSkill[0], ESM::Skill::Security}, + {mStealthSkill[1], ESM::Skill::Sneak}, + {mStealthSkill[2], ESM::Skill::Acrobatics}, + {mStealthSkill[3], ESM::Skill::LightArmor}, + {mStealthSkill[4], ESM::Skill::ShortBlade}, + {mStealthSkill[5] ,ESM::Skill::Marksman}, + {mStealthSkill[6] ,ESM::Skill::Mercantile}, + {mStealthSkill[7] ,ESM::Skill::Speechcraft}, + {mStealthSkill[8] ,ESM::Skill::HandToHand} } }; @@ -878,10 +878,10 @@ SelectSkillDialog::SelectSkillDialog(WindowManager& parWindowManager) { for (int i = 0; i < 9; ++i) { - skills[spec][i].widget->setWindowManager(&mWindowManager); - skills[spec][i].widget->setSkillId(skills[spec][i].skillId); - skills[spec][i].widget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked); - ToolTips::createSkillToolTip(skills[spec][i].widget, skills[spec][i].widget->getSkillId()); + mSkills[spec][i].widget->setWindowManager(&mWindowManager); + mSkills[spec][i].widget->setSkillId(mSkills[spec][i].skillId); + mSkills[spec][i].widget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked); + ToolTips::createSkillToolTip(mSkills[spec][i].widget, mSkills[spec][i].widget->getSkillId()); } } @@ -904,7 +904,7 @@ SelectSkillDialog::~SelectSkillDialog() void SelectSkillDialog::onSkillClicked(Widgets::MWSkillPtr _sender) { - skillId = _sender->getSkillId(); + mSkillId = _sender->getSkillId(); eventItemSelected(); } @@ -921,7 +921,7 @@ DescriptionDialog::DescriptionDialog(WindowManager& parWindowManager) // Centre dialog center(); - getWidget(textEdit, "TextEdit"); + getWidget(mTextEdit, "TextEdit"); MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); @@ -931,7 +931,7 @@ DescriptionDialog::DescriptionDialog(WindowManager& parWindowManager) okButton->setCoord(234 - buttonWidth, 214, buttonWidth, 24); // Make sure the edit box has focus - MyGUI::InputManager::getInstance().setKeyFocusWidget(textEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); MyGUI::InputManager::getInstance().addWidgetModal(mMainWidget); } diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index c9e7aef7b..4d8d9fa23 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -45,11 +45,11 @@ namespace MWGui void fitToText(MyGUI::TextBox* widget); void layoutVertically(MyGUI::WidgetPtr widget, int margin); - int currentButton; - MyGUI::WidgetPtr textBox; - MyGUI::TextBox* text; - MyGUI::WidgetPtr buttonBar; - std::vector buttons; + int mCurrentButton; + MyGUI::WidgetPtr mTextBox; + MyGUI::TextBox* mText; + MyGUI::WidgetPtr mButtonBar; + std::vector mButtons; }; // Lets the player choose between 3 ways of creating a class @@ -90,10 +90,10 @@ namespace MWGui void onBackClicked(MyGUI::Widget* _sender); private: - MyGUI::ImageBox* classImage; - MyGUI::TextBox* className; + MyGUI::ImageBox* mClassImage; + MyGUI::TextBox* mClassName; - std::string currentClassId; + std::string mCurrentClassId; }; class PickClassDialog : public WindowBase @@ -101,7 +101,7 @@ namespace MWGui public: PickClassDialog(WindowManager& parWindowManager); - const std::string &getClassId() const { return currentClassId; } + const std::string &getClassId() const { return mCurrentClassId; } void setClassId(const std::string &classId); void setNextButtonShow(bool shown); @@ -125,14 +125,14 @@ namespace MWGui void updateClasses(); void updateStats(); - MyGUI::ImageBox* classImage; - MyGUI::ListBox* classList; - MyGUI::TextBox* specializationName; - Widgets::MWAttributePtr favoriteAttribute[2]; - Widgets::MWSkillPtr majorSkill[5]; - Widgets::MWSkillPtr minorSkill[5]; + MyGUI::ImageBox* mClassImage; + MyGUI::ListBox* mClassList; + MyGUI::TextBox* mSpecializationName; + Widgets::MWAttributePtr mFavoriteAttribute[2]; + Widgets::MWSkillPtr mMajorSkill[5]; + Widgets::MWSkillPtr mMinorSkill[5]; - std::string currentClassId; + std::string mCurrentClassId; }; class SelectSpecializationDialog : public WindowBase @@ -141,7 +141,7 @@ namespace MWGui SelectSpecializationDialog(WindowManager& parWindowManager); ~SelectSpecializationDialog(); - ESM::Class::Specialization getSpecializationId() const { return specializationId; } + ESM::Class::Specialization getSpecializationId() const { return mSpecializationId; } // Events typedef delegates::CMultiDelegate0 EventHandle_Void; @@ -161,9 +161,9 @@ namespace MWGui void onCancelClicked(MyGUI::Widget* _sender); private: - MyGUI::TextBox *specialization0, *specialization1, *specialization2; + MyGUI::TextBox *mSpecialization0, *mSpecialization1, *mSpecialization2; - ESM::Class::Specialization specializationId; + ESM::Class::Specialization mSpecializationId; }; class SelectAttributeDialog : public WindowBase @@ -172,9 +172,9 @@ namespace MWGui SelectAttributeDialog(WindowManager& parWindowManager); ~SelectAttributeDialog(); - ESM::Attribute::AttributeID getAttributeId() const { return attributeId; } - Widgets::MWAttributePtr getAffectedWidget() const { return affectedWidget; } - void setAffectedWidget(Widgets::MWAttributePtr widget) { affectedWidget = widget; } + ESM::Attribute::AttributeID getAttributeId() const { return mAttributeId; } + Widgets::MWAttributePtr getAffectedWidget() const { return mAffectedWidget; } + void setAffectedWidget(Widgets::MWAttributePtr widget) { mAffectedWidget = widget; } // Events typedef delegates::CMultiDelegate0 EventHandle_Void; @@ -194,9 +194,9 @@ namespace MWGui void onCancelClicked(MyGUI::Widget* _sender); private: - Widgets::MWAttributePtr affectedWidget; + Widgets::MWAttributePtr mAffectedWidget; - ESM::Attribute::AttributeID attributeId; + ESM::Attribute::AttributeID mAttributeId; }; class SelectSkillDialog : public WindowBase @@ -205,9 +205,9 @@ namespace MWGui SelectSkillDialog(WindowManager& parWindowManager); ~SelectSkillDialog(); - ESM::Skill::SkillEnum getSkillId() const { return skillId; } - Widgets::MWSkillPtr getAffectedWidget() const { return affectedWidget; } - void setAffectedWidget(Widgets::MWSkillPtr widget) { affectedWidget = widget; } + ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } + Widgets::MWSkillPtr getAffectedWidget() const { return mAffectedWidget; } + void setAffectedWidget(Widgets::MWSkillPtr widget) { mAffectedWidget = widget; } // Events typedef delegates::CMultiDelegate0 EventHandle_Void; @@ -227,12 +227,12 @@ namespace MWGui void onCancelClicked(MyGUI::Widget* _sender); private: - Widgets::MWSkillPtr combatSkill[9]; - Widgets::MWSkillPtr magicSkill[9]; - Widgets::MWSkillPtr stealthSkill[9]; - Widgets::MWSkillPtr affectedWidget; + Widgets::MWSkillPtr mCombatSkill[9]; + Widgets::MWSkillPtr mMagicSkill[9]; + Widgets::MWSkillPtr mStealthSkill[9]; + Widgets::MWSkillPtr mAffectedWidget; - ESM::Skill::SkillEnum skillId; + ESM::Skill::SkillEnum mSkillId; }; class DescriptionDialog : public WindowBase @@ -241,14 +241,14 @@ namespace MWGui DescriptionDialog(WindowManager& parWindowManager); ~DescriptionDialog(); - std::string getTextInput() const { return textEdit ? textEdit->getOnlyText() : ""; } - void setTextInput(const std::string &text) { if (textEdit) textEdit->setOnlyText(text); } + std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; } + void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); } protected: void onOkClicked(MyGUI::Widget* _sender); private: - MyGUI::EditPtr textEdit; + MyGUI::EditPtr mTextEdit; }; class CreateClassDialog : public WindowBase @@ -294,20 +294,20 @@ namespace MWGui void update(); private: - MyGUI::EditPtr editName; - MyGUI::TextBox* specializationName; - Widgets::MWAttributePtr favoriteAttribute0, favoriteAttribute1; - Widgets::MWSkillPtr majorSkill[5]; - Widgets::MWSkillPtr minorSkill[5]; - std::vector skills; - std::string description; - - SelectSpecializationDialog *specDialog; - SelectAttributeDialog *attribDialog; - SelectSkillDialog *skillDialog; - DescriptionDialog *descDialog; - - ESM::Class::Specialization specializationId; + MyGUI::EditPtr mEditName; + MyGUI::TextBox* mSpecializationName; + Widgets::MWAttributePtr mFavoriteAttribute0, mFavoriteAttribute1; + Widgets::MWSkillPtr mMajorSkill[5]; + Widgets::MWSkillPtr mMinorSkill[5]; + std::vector mSkills; + std::string mDescription; + + SelectSpecializationDialog *mSpecDialog; + SelectAttributeDialog *mAttribDialog; + SelectSkillDialog *mSkillDialog; + DescriptionDialog *mDescDialog; + + ESM::Class::Specialization mSpecializationId; }; } #endif diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index acf0bf130..efb6dc036 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -51,9 +51,9 @@ DialogueWindow::DialogueWindow(WindowManager& parWindowManager) center(); //History view - getWidget(history, "History"); - history->setOverflowToTheLeft(true); - history->setMaxTextLength(1000000); + getWidget(mHistory, "History"); + mHistory->setOverflowToTheLeft(true); + mHistory->setMaxTextLength(1000000); Widget* eventbox; //An EditBox cannot receive mouse click events, so we use an @@ -63,36 +63,36 @@ DialogueWindow::DialogueWindow(WindowManager& parWindowManager) eventbox->eventMouseWheel += MyGUI::newDelegate(this, &DialogueWindow::onMouseWheel); //Topics list - getWidget(topicsList, "TopicsList"); - topicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic); + getWidget(mTopicsList, "TopicsList"); + mTopicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic); MyGUI::ButtonPtr byeButton; getWidget(byeButton, "ByeButton"); byeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onByeClicked); - getWidget(pDispositionBar, "Disposition"); - getWidget(pDispositionText,"DispositionText"); + getWidget(mDispositionBar, "Disposition"); + getWidget(mDispositionText,"DispositionText"); static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); } void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) { - ISubWidgetText* t = history->getClient()->getSubWidgetText(); + ISubWidgetText* t = mHistory->getClient()->getSubWidgetText(); if(t == nullptr) return; const IntPoint& lastPressed = InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); size_t cursorPosition = t->getCursorPosition(lastPressed); - MyGUI::UString color = history->getColorAtPos(cursorPosition); + MyGUI::UString color = mHistory->getColorAtPos(cursorPosition); if (!mEnabled && color == "#572D21") MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); if(color != "#B29154") { - UString key = history->getColorTextAt(cursorPosition); + UString key = mHistory->getColorTextAt(cursorPosition); if(color == "#686EBA") MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(key)); if(color == "#572D21") MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); @@ -101,15 +101,15 @@ void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) void DialogueWindow::onWindowResize(MyGUI::Window* _sender) { - topicsList->adjustSize(); + mTopicsList->adjustSize(); } void DialogueWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { - if (history->getVScrollPosition() - _rel*0.3 < 0) - history->setVScrollPosition(0); + if (mHistory->getVScrollPosition() - _rel*0.3 < 0) + mHistory->setVScrollPosition(0); else - history->setVScrollPosition(history->getVScrollPosition() - _rel*0.3); + mHistory->setVScrollPosition(mHistory->getVScrollPosition() - _rel*0.3); } void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) @@ -136,40 +136,40 @@ void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName) { mEnabled = true; mPtr = actor; - topicsList->setEnabled(true); + mTopicsList->setEnabled(true); setTitle(npcName); - topicsList->clear(); - history->eraseText(0,history->getTextLength()); + mTopicsList->clear(); + mHistory->eraseText(0,mHistory->getTextLength()); updateOptions(); } void DialogueWindow::setKeywords(std::list keyWords) { - topicsList->clear(); + mTopicsList->clear(); bool anyService = mShowTrade; if (mShowTrade) - topicsList->addItem(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sBarter")->str); + mTopicsList->addItem(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sBarter")->str); if (anyService) - topicsList->addSeparator(); + mTopicsList->addSeparator(); for(std::list::iterator it = keyWords.begin(); it != keyWords.end(); ++it) { - topicsList->addItem(*it); + mTopicsList->addItem(*it); } - topicsList->adjustSize(); + mTopicsList->adjustSize(); } void DialogueWindow::removeKeyword(std::string keyWord) { - if(topicsList->hasItem(keyWord)) + if(mTopicsList->hasItem(keyWord)) { - topicsList->removeItem(keyWord); + mTopicsList->removeItem(keyWord); } - topicsList->adjustSize(); + mTopicsList->adjustSize(); } void addColorInString(std::string& str, const std::string& keyword,std::string color1, std::string color2) @@ -206,9 +206,9 @@ void addColorInString(std::string& str, const std::string& keyword,std::string c std::string DialogueWindow::parseText(std::string text) { bool separatorReached = false; // only parse topics that are below the separator (this prevents actions like "Barter" that are not topics from getting blue-colored) - for(unsigned int i = 0;igetItemCount();i++) + for(unsigned int i = 0;igetItemCount();i++) { - std::string keyWord = topicsList->getItemNameAt(i); + std::string keyWord = mTopicsList->getItemNameAt(i); if (separatorReached && keyWord != "") addColorInString(text,keyWord,"#686EBA","#B29154"); else @@ -219,7 +219,7 @@ std::string DialogueWindow::parseText(std::string text) void DialogueWindow::addText(std::string text) { - history->addDialogText("#B29154"+parseText(text)+"#B29154"); + mHistory->addDialogText("#B29154"+parseText(text)+"#B29154"); } void DialogueWindow::addTitle(std::string text) @@ -227,37 +227,37 @@ void DialogueWindow::addTitle(std::string text) // This is called from the dialogue manager, so text is // case-smashed - thus we have to retrieve the correct case // of the text through the topic list. - for (size_t i=0; igetItemCount(); ++i) + for (size_t i=0; igetItemCount(); ++i) { - std::string item = topicsList->getItemNameAt(i); + std::string item = mTopicsList->getItemNameAt(i); if (lower_string(item) == text) text = item; } - history->addDialogHeading(text); + mHistory->addDialogHeading(text); } void DialogueWindow::askQuestion(std::string question) { - history->addDialogText("#572D21"+question+"#B29154"+" "); + mHistory->addDialogText("#572D21"+question+"#B29154"+" "); } void DialogueWindow::updateOptions() { //Clear the list of topics - topicsList->clear(); - history->eraseText(0,history->getTextLength()); + mTopicsList->clear(); + mHistory->eraseText(0, mHistory->getTextLength()); - pDispositionBar->setProgressRange(100); - pDispositionBar->setProgressPosition(40); - pDispositionText->eraseText(0,pDispositionText->getTextLength()); - pDispositionText->addText("#B29154"+std::string("40/100")+"#B29154"); + mDispositionBar->setProgressRange(100); + mDispositionBar->setProgressPosition(40); + mDispositionText->eraseText(0, mDispositionText->getTextLength()); + mDispositionText->addText("#B29154"+std::string("40/100")+"#B29154"); } void DialogueWindow::goodbye() { - history->addDialogText("\n#572D21" + MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGoodbye")->str); - topicsList->setEnabled(false); + mHistory->addDialogText("\n#572D21" + MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGoodbye")->str); + mTopicsList->setEnabled(false); mEnabled = false; } diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 5d808b5a7..e2824bead 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -73,10 +73,10 @@ namespace MWGui bool mEnabled; - DialogueHistory* history; - Widgets::MWList* topicsList; - MyGUI::ProgressPtr pDispositionBar; - MyGUI::EditPtr pDispositionText; + DialogueHistory* mHistory; + Widgets::MWList* mTopicsList; + MyGUI::ProgressPtr mDispositionBar; + MyGUI::EditPtr mDispositionText; }; } #endif diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 78eefa338..d2eef26ac 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -28,24 +28,24 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) , health(NULL) , magicka(NULL) , stamina(NULL) - , weapImage(NULL) - , spellImage(NULL) - , weapStatus(NULL) - , spellStatus(NULL) - , effectBox(NULL) - , effect1(NULL) - , minimap(NULL) - , compass(NULL) - , crosshair(NULL) + , mWeapImage(NULL) + , mSpellImage(NULL) + , mWeapStatus(NULL) + , mSpellStatus(NULL) + , mEffectBox(NULL) + , mEffect1(NULL) + , mMinimap(NULL) + , mCompass(NULL) + , mCrosshair(NULL) , fpsbox(NULL) , fpscounter(NULL) , trianglecounter(NULL) , batchcounter(NULL) - , hmsBaseLeft(0) - , weapBoxBaseLeft(0) - , spellBoxBaseLeft(0) - , effectBoxBaseRight(0) - , minimapBoxBaseRight(0) + , mHealthManaStaminaBaseLeft(0) + , mWeapBoxBaseLeft(0) + , mSpellBoxBaseLeft(0) + , mEffectBoxBaseRight(0) + , mMinimapBoxBaseRight(0) , mDragAndDrop(dragAndDrop) , mCellNameTimer(0.0f) , mCellNameBox(NULL) @@ -62,7 +62,7 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) getWidget(magicka, "Magicka"); getWidget(stamina, "Stamina"); - hmsBaseLeft = mHealthFrame->getLeft(); + mHealthManaStaminaBaseLeft = mHealthFrame->getLeft(); MyGUI::Widget *healthFrame, *magickaFrame, *fatigueFrame; getWidget(healthFrame, "HealthFrame"); @@ -75,33 +75,33 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); // Item and spell images and status bars - getWidget(weapBox, "WeapBox"); - getWidget(weapImage, "WeapImage"); - getWidget(weapStatus, "WeapStatus"); - weapBoxBaseLeft = weapBox->getLeft(); - weapBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWeaponClicked); - - getWidget(spellBox, "SpellBox"); - getWidget(spellImage, "SpellImage"); - getWidget(spellStatus, "SpellStatus"); - spellBoxBaseLeft = spellBox->getLeft(); - spellBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); - - getWidget(effectBox, "EffectBox"); - getWidget(effect1, "Effect1"); - effectBoxBaseRight = viewSize.width - effectBox->getRight(); - effectBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); - - getWidget(minimapBox, "MiniMapBox"); - minimapBoxBaseRight = viewSize.width - minimapBox->getRight(); - minimapBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); - getWidget(minimap, "MiniMap"); - getWidget(compass, "Compass"); + getWidget(mWeapBox, "WeapBox"); + getWidget(mWeapImage, "WeapImage"); + getWidget(mWeapStatus, "WeapStatus"); + mWeapBoxBaseLeft = mWeapBox->getLeft(); + mWeapBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWeaponClicked); + + getWidget(mSpellBox, "SpellBox"); + getWidget(mSpellImage, "SpellImage"); + getWidget(mSpellStatus, "SpellStatus"); + mSpellBoxBaseLeft = mSpellBox->getLeft(); + mSpellBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); + + getWidget(mEffectBox, "EffectBox"); + getWidget(mEffect1, "Effect1"); + mEffectBoxBaseRight = viewSize.width - mEffectBox->getRight(); + mEffectBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); + + getWidget(mMinimapBox, "MiniMapBox"); + mMinimapBoxBaseRight = viewSize.width - mMinimapBox->getRight(); + mMinimapBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); + getWidget(mMinimap, "MiniMap"); + getWidget(mCompass, "Compass"); getWidget(mCellNameBox, "CellName"); getWidget(mWeaponSpellBox, "WeaponSpellName"); - getWidget(crosshair, "Crosshair"); + getWidget(mCrosshair, "Crosshair"); setFpsLevel(fpsLevel); @@ -110,7 +110,7 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) setEffect("icons\\s\\tx_s_chameleon.dds"); - LocalMapBase::init(minimap, compass, this); + LocalMapBase::init(mMinimap, mCompass, this); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); @@ -159,7 +159,7 @@ void HUD::setBatchCount(unsigned int count) void HUD::setEffect(const char *img) { - effect1->setImageTexture(img); + mEffect1->setImageTexture(img); } void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) @@ -202,10 +202,10 @@ void HUD::setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellV { int weapDx = 0, spellDx = 0; if (!hmsVisible) - spellDx = weapDx = weapBoxBaseLeft - hmsBaseLeft; + spellDx = weapDx = mWeapBoxBaseLeft - mHealthManaStaminaBaseLeft; if (!weapVisible) - spellDx += spellBoxBaseLeft - weapBoxBaseLeft; + spellDx += mSpellBoxBaseLeft - mWeapBoxBaseLeft; mWeaponVisible = weapVisible; mSpellVisible = spellVisible; @@ -215,10 +215,10 @@ void HUD::setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellV health->setVisible(hmsVisible); stamina->setVisible(hmsVisible); magicka->setVisible(hmsVisible); - weapBox->setPosition(weapBoxBaseLeft - weapDx, weapBox->getTop()); - weapBox->setVisible(weapVisible); - spellBox->setPosition(spellBoxBaseLeft - spellDx, spellBox->getTop()); - spellBox->setVisible(spellVisible); + mWeapBox->setPosition(mWeapBoxBaseLeft - weapDx, mWeapBox->getTop()); + mWeapBox->setVisible(weapVisible); + mSpellBox->setPosition(mSpellBoxBaseLeft - spellDx, mSpellBox->getTop()); + mSpellBox->setVisible(spellVisible); } void HUD::setBottomRightVisibility(bool effectBoxVisible, bool minimapBoxVisible) @@ -228,12 +228,12 @@ void HUD::setBottomRightVisibility(bool effectBoxVisible, bool minimapBoxVisible // effect box can have variable width -> variable left coordinate int effectsDx = 0; if (!minimapBoxVisible) - effectsDx = (viewSize.width - minimapBoxBaseRight) - (viewSize.width - effectBoxBaseRight); + effectsDx = (viewSize.width - mMinimapBoxBaseRight) - (viewSize.width - mEffectBoxBaseRight); mMapVisible = minimapBoxVisible; - minimapBox->setVisible(minimapBoxVisible); - effectBox->setPosition((viewSize.width - effectBoxBaseRight) - effectBox->getWidth() + effectsDx, effectBox->getTop()); - effectBox->setVisible(effectBoxVisible); + mMinimapBox->setVisible(minimapBoxVisible); + mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop()); + mEffectBox->setVisible(effectBoxVisible); } void HUD::onWorldClicked(MyGUI::Widget* _sender) @@ -395,14 +395,14 @@ void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) mWeaponSpellBox->setVisible(true); } - spellStatus->setProgressRange(100); - spellStatus->setProgressPosition(successChancePercent); + mSpellStatus->setProgressRange(100); + mSpellStatus->setProgressPosition(successChancePercent); - if (spellImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(spellImage->getChildAt(0)); + if (mSpellImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mSpellImage->getChildAt(0)); - spellBox->setUserString("ToolTipType", "Spell"); - spellBox->setUserString("Spell", spellId); + mSpellBox->setUserString("ToolTipType", "Spell"); + mSpellBox->setUserString("Spell", spellId); // use the icon of the first effect const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().magicEffects.find(spell->effects.list.front().effectID); @@ -411,7 +411,7 @@ void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) icon.insert(slashPos+1, "b_"); icon = std::string("icons\\") + icon; Widgets::fixTexturePath(icon); - spellImage->setImageTexture(icon); + mSpellImage->setImageTexture(icon); } void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) @@ -425,17 +425,17 @@ void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) mWeaponSpellBox->setVisible(true); } - spellStatus->setProgressRange(100); - spellStatus->setProgressPosition(chargePercent); + mSpellStatus->setProgressRange(100); + mSpellStatus->setProgressPosition(chargePercent); - if (spellImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(spellImage->getChildAt(0)); + if (mSpellImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mSpellImage->getChildAt(0)); - spellBox->setUserString("ToolTipType", "ItemPtr"); - spellBox->setUserData(item); + mSpellBox->setUserString("ToolTipType", "ItemPtr"); + mSpellBox->setUserData(item); - spellImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); - MyGUI::ImageBox* itemBox = spellImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) + mSpellImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); + MyGUI::ImageBox* itemBox = mSpellImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) , MyGUI::Align::Stretch); std::string path = std::string("icons\\"); @@ -456,14 +456,14 @@ void HUD::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) mWeaponSpellBox->setVisible(true); } - weapBox->setUserString("ToolTipType", "ItemPtr"); - weapBox->setUserData(item); + mWeapBox->setUserString("ToolTipType", "ItemPtr"); + mWeapBox->setUserData(item); - weapStatus->setProgressRange(100); - weapStatus->setProgressPosition(durabilityPercent); + mWeapStatus->setProgressRange(100); + mWeapStatus->setProgressPosition(durabilityPercent); - if (weapImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(weapImage->getChildAt(0)); + if (mWeapImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mWeapImage->getChildAt(0)); std::string path = std::string("icons\\"); path+=MWWorld::Class::get(item).getInventoryIcon(item); @@ -471,14 +471,14 @@ void HUD::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) if (MWWorld::Class::get(item).getEnchantment(item) != "") { - weapImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); - MyGUI::ImageBox* itemBox = weapImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) + mWeapImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); + MyGUI::ImageBox* itemBox = mWeapImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) , MyGUI::Align::Stretch); itemBox->setImageTexture(path); itemBox->setNeedMouseFocus(false); } else - weapImage->setImageTexture(path); + mWeapImage->setImageTexture(path); } void HUD::unsetSelectedSpell() @@ -492,12 +492,12 @@ void HUD::unsetSelectedSpell() mWeaponSpellBox->setVisible(true); } - if (spellImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(spellImage->getChildAt(0)); - spellStatus->setProgressRange(100); - spellStatus->setProgressPosition(0); - spellImage->setImageTexture(""); - spellBox->clearUserStrings(); + if (mSpellImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mSpellImage->getChildAt(0)); + mSpellStatus->setProgressRange(100); + mSpellStatus->setProgressPosition(0); + mSpellImage->setImageTexture(""); + mSpellBox->clearUserStrings(); } void HUD::unsetSelectedWeapon() @@ -511,10 +511,10 @@ void HUD::unsetSelectedWeapon() mWeaponSpellBox->setVisible(true); } - if (weapImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(weapImage->getChildAt(0)); - weapStatus->setProgressRange(100); - weapStatus->setProgressPosition(0); - weapImage->setImageTexture("icons\\k\\stealth_handtohand.dds"); - weapBox->clearUserStrings(); + if (mWeapImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mWeapImage->getChildAt(0)); + mWeapStatus->setProgressRange(100); + mWeapStatus->setProgressPosition(0); + mWeapImage->setImageTexture("icons\\k\\stealth_handtohand.dds"); + mWeapBox->clearUserStrings(); } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index c6bcdd4e9..485c788fc 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -37,14 +37,14 @@ namespace MWGui MyGUI::ProgressPtr health, magicka, stamina; MyGUI::Widget* mHealthFrame; - MyGUI::Widget *weapBox, *spellBox; - MyGUI::ImageBox *weapImage, *spellImage; - MyGUI::ProgressPtr weapStatus, spellStatus; - MyGUI::Widget *effectBox, *minimapBox; - MyGUI::ImageBox* effect1; - MyGUI::ScrollView* minimap; - MyGUI::ImageBox* compass; - MyGUI::ImageBox* crosshair; + MyGUI::Widget *mWeapBox, *mSpellBox; + MyGUI::ImageBox *mWeapImage, *mSpellImage; + MyGUI::ProgressPtr mWeapStatus, mSpellStatus; + MyGUI::Widget *mEffectBox, *mMinimapBox; + MyGUI::ImageBox* mEffect1; + MyGUI::ScrollView* mMinimap; + MyGUI::ImageBox* mCompass; + MyGUI::ImageBox* mCrosshair; MyGUI::TextBox* mCellNameBox; MyGUI::TextBox* mWeaponSpellBox; @@ -55,9 +55,9 @@ namespace MWGui private: // bottom left elements - int hmsBaseLeft, weapBoxBaseLeft, spellBoxBaseLeft; + int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft; // bottom right elements - int minimapBoxBaseRight, effectBoxBaseRight; + int mMinimapBoxBaseRight, mEffectBoxBaseRight; DragAndDrop* mDragAndDrop; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 75b546c7b..8b50c9ef1 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -84,7 +84,7 @@ book formatText(std::string text,book mBook,int maxLine, int lineSize) MWGui::JournalWindow::JournalWindow (WindowManager& parWindowManager) : WindowBase("openmw_journal.layout", parWindowManager) - , lastPos(0) + , mLastPos(0) , mVisible(false) { //setCoord(0,0,498, 342); @@ -148,19 +148,19 @@ void MWGui::JournalWindow::open() { if(left) { - leftPages.push_back(*it); + mLeftPages.push_back(*it); } else { - rightPages.push_back(*it); + mRightPages.push_back(*it); } left = !left; } - if(!left) rightPages.push_back(""); + if(!left) mRightPages.push_back(""); - mPageNumber = leftPages.size()-1; - displayLeftText(leftPages[mPageNumber]); - displayRightText(rightPages[mPageNumber]); + mPageNumber = mLeftPages.size()-1; + displayLeftText(mLeftPages[mPageNumber]); + displayRightText(mRightPages[mPageNumber]); } else @@ -184,13 +184,13 @@ void MWGui::JournalWindow::displayRightText(std::string text) void MWGui::JournalWindow::notifyNextPage(MyGUI::WidgetPtr _sender) { - if(mPageNumber < int(leftPages.size())-1) + if(mPageNumber < int(mLeftPages.size())-1) { std::string nextSound = "book page2"; MWBase::Environment::get().getSoundManager()->playSound (nextSound, 1.0, 1.0); mPageNumber = mPageNumber + 1; - displayLeftText(leftPages[mPageNumber]); - displayRightText(rightPages[mPageNumber]); + displayLeftText(mLeftPages[mPageNumber]); + displayRightText(mRightPages[mPageNumber]); } } @@ -201,7 +201,7 @@ void MWGui::JournalWindow::notifyPrevPage(MyGUI::WidgetPtr _sender) std::string prevSound = "book page"; MWBase::Environment::get().getSoundManager()->playSound (prevSound, 1.0, 1.0); mPageNumber = mPageNumber - 1; - displayLeftText(leftPages[mPageNumber]); - displayRightText(rightPages[mPageNumber]); + displayLeftText(mLeftPages[mPageNumber]); + displayRightText(mRightPages[mPageNumber]); } } diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index cacdb9414..0c85ebf08 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -31,17 +31,17 @@ namespace MWGui void notifyNextPage(MyGUI::WidgetPtr _sender); void notifyPrevPage(MyGUI::WidgetPtr _sender); - static const int lineHeight; + static const int sLineHeight; - MyGUI::WidgetPtr skillAreaWidget, skillClientWidget; - MyGUI::ScrollBar* skillScrollerWidget; - int lastPos, clientHeight; + MyGUI::WidgetPtr mSkillAreaWidget, mSkillClientWidget; + MyGUI::ScrollBar* mSkillScrollerWidget; + int mLastPos, mClientHeight; MyGUI::EditPtr mLeftTextWidget; MyGUI::EditPtr mRightTextWidget; MyGUI::ButtonPtr mPrevBtn; MyGUI::ButtonPtr mNextBtn; - std::vector leftPages; - std::vector rightPages; + std::vector mLeftPages; + std::vector mRightPages; int mPageNumber; //store the number of the current left page bool mVisible; }; diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 68326a3c3..2b00ca05c 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -149,7 +149,7 @@ int MessageBoxManager::readPressedButton () MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message) : Layout("openmw_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) - , cMessage(message) + , mMessage(message) { // defines mFixedWidth = 300; @@ -160,7 +160,7 @@ MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::strin getWidget(mMessageWidget, "message"); mMessageWidget->setOverflowToTheLeft(true); - mMessageWidget->setCaptionWithReplacing(cMessage); + mMessageWidget->setCaptionWithReplacing(mMessage); MyGUI::IntSize size; size.width = mFixedWidth; diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 33155b2a0..75393ec94 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -59,7 +59,7 @@ namespace MWGui protected: MessageBoxManager& mMessageBoxManager; int mHeight; - const std::string& cMessage; + const std::string& mMessage; MyGUI::EditPtr mMessageWidget; int mFixedWidth; int mBottomPadding; diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 9ae453016..dd11ec011 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -18,11 +18,11 @@ using namespace Widgets; RaceDialog::RaceDialog(WindowManager& parWindowManager) : WindowBase("openmw_chargen_race.layout", parWindowManager) - , genderIndex(0) - , faceIndex(0) - , hairIndex(0) - , faceCount(10) - , hairCount(14) + , mGenderIndex(0) + , mFaceIndex(0) + , mHairIndex(0) + , mFaceCount(10) + , mHairCount(14) { // Centre dialog center(); @@ -31,13 +31,13 @@ RaceDialog::RaceDialog(WindowManager& parWindowManager) // real calls from outside the class later. setText("AppearanceT", mWindowManager.getGameSettingString("sRaceMenu1", "Appearance")); - getWidget(appearanceBox, "AppearanceBox"); + getWidget(mAppearanceBox, "AppearanceBox"); - getWidget(headRotate, "HeadRotate"); - headRotate->setScrollRange(50); - headRotate->setScrollPosition(20); - headRotate->setScrollViewPage(10); - headRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate); + getWidget(mHeadRotate, "HeadRotate"); + mHeadRotate->setScrollRange(50); + mHeadRotate->setScrollPosition(20); + mHeadRotate->setScrollViewPage(10); + mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate); // Set up next/previous buttons MyGUI::ButtonPtr prevButton, nextButton; @@ -61,16 +61,16 @@ RaceDialog::RaceDialog(WindowManager& parWindowManager) nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextHair); setText("RaceT", mWindowManager.getGameSettingString("sRaceMenu4", "Race")); - getWidget(raceList, "RaceList"); - raceList->setScrollVisible(true); - raceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); - raceList->eventListMouseItemActivate += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); - raceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); + getWidget(mRaceList, "RaceList"); + mRaceList->setScrollVisible(true); + mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); + mRaceList->eventListMouseItemActivate += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); + mRaceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); setText("SkillsT", mWindowManager.getGameSettingString("sBonusSkillTitle", "Skill Bonus")); - getWidget(skillList, "SkillList"); + getWidget(mSkillList, "SkillList"); setText("SpellPowerT", mWindowManager.getGameSettingString("sRaceMenu7", "Specials")); - getWidget(spellPowerList, "SpellPowerList"); + getWidget(mSpellPowerList, "SpellPowerList"); MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); @@ -117,14 +117,14 @@ void RaceDialog::open() void RaceDialog::setRaceId(const std::string &raceId) { - currentRaceId = raceId; - raceList->setIndexSelected(MyGUI::ITEM_NONE); - size_t count = raceList->getItemCount(); + mCurrentRaceId = raceId; + mRaceList->setIndexSelected(MyGUI::ITEM_NONE); + size_t count = mRaceList->getItemCount(); for (size_t i = 0; i < count; ++i) { - if (boost::iequals(*raceList->getItemDataAt(i), raceId)) + if (boost::iequals(*mRaceList->getItemDataAt(i), raceId)) { - raceList->setIndexSelected(i); + mRaceList->setIndexSelected(i); break; } } @@ -162,32 +162,32 @@ void RaceDialog::onHeadRotate(MyGUI::ScrollBar*, size_t _position) void RaceDialog::onSelectPreviousGender(MyGUI::Widget*) { - genderIndex = wrap(genderIndex - 1, 2); + mGenderIndex = wrap(mGenderIndex - 1, 2); } void RaceDialog::onSelectNextGender(MyGUI::Widget*) { - genderIndex = wrap(genderIndex + 1, 2); + mGenderIndex = wrap(mGenderIndex + 1, 2); } void RaceDialog::onSelectPreviousFace(MyGUI::Widget*) { - faceIndex = wrap(faceIndex - 1, faceCount); + mFaceIndex = wrap(mFaceIndex - 1, mFaceCount); } void RaceDialog::onSelectNextFace(MyGUI::Widget*) { - faceIndex = wrap(faceIndex + 1, faceCount); + mFaceIndex = wrap(mFaceIndex + 1, mFaceCount); } void RaceDialog::onSelectPreviousHair(MyGUI::Widget*) { - hairIndex = wrap(hairIndex - 1, hairCount); + mHairIndex = wrap(mHairIndex - 1, mHairCount); } void RaceDialog::onSelectNextHair(MyGUI::Widget*) { - hairIndex = wrap(hairIndex - 1, hairCount); + mHairIndex = wrap(mHairIndex - 1, mHairCount); } void RaceDialog::onSelectRace(MyGUI::ListBox* _sender, size_t _index) @@ -195,11 +195,11 @@ void RaceDialog::onSelectRace(MyGUI::ListBox* _sender, size_t _index) if (_index == MyGUI::ITEM_NONE) return; - const std::string *raceId = raceList->getItemDataAt(_index); - if (boost::iequals(currentRaceId, *raceId)) + const std::string *raceId = mRaceList->getItemDataAt(_index); + if (boost::iequals(mCurrentRaceId, *raceId)) return; - currentRaceId = *raceId; + mCurrentRaceId = *raceId; updateSkills(); updateSpellPowers(); } @@ -208,7 +208,7 @@ void RaceDialog::onSelectRace(MyGUI::ListBox* _sender, size_t _index) void RaceDialog::updateRaces() { - raceList->removeAllItems(); + mRaceList->removeAllItems(); const ESMS::ESMStore &store = mWindowManager.getStore(); @@ -222,30 +222,30 @@ void RaceDialog::updateRaces() if (!playable) // Only display playable races continue; - raceList->addItem(race.name, it->first); - if (boost::iequals(it->first, currentRaceId)) - raceList->setIndexSelected(index); + mRaceList->addItem(race.name, it->first); + if (boost::iequals(it->first, mCurrentRaceId)) + mRaceList->setIndexSelected(index); ++index; } } void RaceDialog::updateSkills() { - for (std::vector::iterator it = skillItems.begin(); it != skillItems.end(); ++it) + for (std::vector::iterator it = mSkillItems.begin(); it != mSkillItems.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } - skillItems.clear(); + mSkillItems.clear(); - if (currentRaceId.empty()) + if (mCurrentRaceId.empty()) return; MWSkillPtr skillWidget; const int lineHeight = 18; - MyGUI::IntCoord coord1(0, 0, skillList->getWidth(), 18); + MyGUI::IntCoord coord1(0, 0, mSkillList->getWidth(), 18); const ESMS::ESMStore &store = mWindowManager.getStore(); - const ESM::Race *race = store.races.find(currentRaceId); + const ESM::Race *race = store.races.find(mCurrentRaceId); int count = sizeof(race->data.bonus)/sizeof(race->data.bonus[0]); // TODO: Find a portable macro for this ARRAYSIZE? for (int i = 0; i < count; ++i) { @@ -253,7 +253,7 @@ void RaceDialog::updateSkills() if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes continue; - skillWidget = skillList->createWidget("MW_StatNameValue", coord1, MyGUI::Align::Default, + skillWidget = mSkillList->createWidget("MW_StatNameValue", coord1, MyGUI::Align::Default, std::string("Skill") + boost::lexical_cast(i)); skillWidget->setWindowManager(&mWindowManager); skillWidget->setSkillNumber(skillId); @@ -261,7 +261,7 @@ void RaceDialog::updateSkills() ToolTips::createSkillToolTip(skillWidget, skillId); - skillItems.push_back(skillWidget); + mSkillItems.push_back(skillWidget); coord1.top += lineHeight; } @@ -269,34 +269,34 @@ void RaceDialog::updateSkills() void RaceDialog::updateSpellPowers() { - for (std::vector::iterator it = spellPowerItems.begin(); it != spellPowerItems.end(); ++it) + for (std::vector::iterator it = mSpellPowerItems.begin(); it != mSpellPowerItems.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } - spellPowerItems.clear(); + mSpellPowerItems.clear(); - if (currentRaceId.empty()) + if (mCurrentRaceId.empty()) return; MWSpellPtr spellPowerWidget; const int lineHeight = 18; - MyGUI::IntCoord coord(0, 0, spellPowerList->getWidth(), 18); + MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), 18); const ESMS::ESMStore &store = mWindowManager.getStore(); - const ESM::Race *race = store.races.find(currentRaceId); + const ESM::Race *race = store.races.find(mCurrentRaceId); std::vector::const_iterator it = race->powers.list.begin(); std::vector::const_iterator end = race->powers.list.end(); for (int i = 0; it != end; ++it) { const std::string &spellpower = *it; - spellPowerWidget = spellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + boost::lexical_cast(i)); + spellPowerWidget = mSpellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + boost::lexical_cast(i)); spellPowerWidget->setWindowManager(&mWindowManager); spellPowerWidget->setSpellId(spellpower); spellPowerWidget->setUserString("ToolTipType", "Spell"); spellPowerWidget->setUserString("Spell", spellpower); - spellPowerItems.push_back(spellPowerWidget); + mSpellPowerItems.push_back(spellPowerWidget); coord.top += lineHeight; ++i; diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 217ce15aa..b523b8690 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -32,13 +32,13 @@ namespace MWGui GM_Female }; - const std::string &getRaceId() const { return currentRaceId; } - Gender getGender() const { return genderIndex == 0 ? GM_Male : GM_Female; } + const std::string &getRaceId() const { return mCurrentRaceId; } + Gender getGender() const { return mGenderIndex == 0 ? GM_Male : GM_Female; } // getFace() // getHair() void setRaceId(const std::string &raceId); - void setGender(Gender gender) { genderIndex = gender == GM_Male ? 0 : 1; } + void setGender(Gender gender) { mGenderIndex = gender == GM_Male ? 0 : 1; } // setFace() // setHair() @@ -75,20 +75,20 @@ namespace MWGui void updateSkills(); void updateSpellPowers(); - MyGUI::CanvasPtr appearanceBox; - MyGUI::ListBox* raceList; - MyGUI::ScrollBar* headRotate; + MyGUI::CanvasPtr mAppearanceBox; + MyGUI::ListBox* mRaceList; + MyGUI::ScrollBar* mHeadRotate; - MyGUI::WidgetPtr skillList; - std::vector skillItems; + MyGUI::WidgetPtr mSkillList; + std::vector mSkillItems; - MyGUI::WidgetPtr spellPowerList; - std::vector spellPowerItems; + MyGUI::WidgetPtr mSpellPowerList; + std::vector mSpellPowerItems; - int genderIndex, faceIndex, hairIndex; - int faceCount, hairCount; + int mGenderIndex, mFaceIndex, mHairIndex; + int mFaceCount, mHairCount; - std::string currentRaceId; + std::string mCurrentRaceId; }; } #endif diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index f9792ea34..7061f65c1 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -17,49 +17,49 @@ using namespace MWGui; using namespace Widgets; -const int ReviewDialog::lineHeight = 18; +const int ReviewDialog::sLineHeight = 18; ReviewDialog::ReviewDialog(WindowManager& parWindowManager) : WindowBase("openmw_chargen_review.layout", parWindowManager) - , lastPos(0) + , mLastPos(0) { // Centre dialog center(); // Setup static stats ButtonPtr button; - getWidget(nameWidget, "NameText"); + getWidget(mNameWidget, "NameText"); getWidget(button, "NameButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onNameClicked);; - getWidget(raceWidget, "RaceText"); + getWidget(mRaceWidget, "RaceText"); getWidget(button, "RaceButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onRaceClicked);; - getWidget(classWidget, "ClassText"); + getWidget(mClassWidget, "ClassText"); getWidget(button, "ClassButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onClassClicked);; - getWidget(birthSignWidget, "SignText"); + getWidget(mBirthSignWidget, "SignText"); getWidget(button, "SignButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBirthSignClicked);; // Setup dynamic stats - getWidget(health, "Health"); - health->setTitle(mWindowManager.getGameSettingString("sHealth", "")); - health->setValue(45, 45); + getWidget(mHealth, "Health"); + mHealth->setTitle(mWindowManager.getGameSettingString("sHealth", "")); + mHealth->setValue(45, 45); - getWidget(magicka, "Magicka"); - magicka->setTitle(mWindowManager.getGameSettingString("sMagic", "")); - magicka->setValue(50, 50); + getWidget(mMagicka, "Magicka"); + mMagicka->setTitle(mWindowManager.getGameSettingString("sMagic", "")); + mMagicka->setValue(50, 50); - getWidget(fatigue, "Fatigue"); - fatigue->setTitle(mWindowManager.getGameSettingString("sFatigue", "")); - fatigue->setValue(160, 160); + getWidget(mFatigue, "Fatigue"); + mFatigue->setTitle(mWindowManager.getGameSettingString("sFatigue", "")); + mFatigue->setValue(160, 160); // Setup attributes @@ -67,24 +67,24 @@ ReviewDialog::ReviewDialog(WindowManager& parWindowManager) for (int idx = 0; idx < ESM::Attribute::Length; ++idx) { getWidget(attribute, std::string("Attribute") + boost::lexical_cast(idx)); - attributeWidgets.insert(std::make_pair(static_cast(ESM::Attribute::attributeIds[idx]), attribute)); + mAttributeWidgets.insert(std::make_pair(static_cast(ESM::Attribute::attributeIds[idx]), attribute)); attribute->setWindowManager(&mWindowManager); attribute->setAttributeId(ESM::Attribute::attributeIds[idx]); attribute->setAttributeValue(MWAttribute::AttributeValue(0, 0)); } // Setup skills - getWidget(skillAreaWidget, "Skills"); - getWidget(skillClientWidget, "SkillClient"); - getWidget(skillScrollerWidget, "SkillScroller"); - skillClientWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); - skillScrollerWidget->eventScrollChangePosition += MyGUI::newDelegate(this, &ReviewDialog::onScrollChangePosition); + getWidget(mSkillAreaWidget, "Skills"); + getWidget(mSkillClientWidget, "SkillClient"); + getWidget(mSkillScrollerWidget, "SkillScroller"); + mSkillClientWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); + mSkillScrollerWidget->eventScrollChangePosition += MyGUI::newDelegate(this, &ReviewDialog::onScrollChangePosition); updateScroller(); for (int i = 0; i < ESM::Skill::Length; ++i) { - skillValues.insert(std::make_pair(i, MWMechanics::Stat())); - skillWidgetMap.insert(std::make_pair(i, static_cast (0))); + mSkillValues.insert(std::make_pair(i, MWMechanics::Stat())); + mSkillWidgetMap.insert(std::make_pair(i, static_cast (0))); } static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &ReviewDialog::onWindowResize); @@ -112,14 +112,14 @@ void ReviewDialog::open() void ReviewDialog::onScrollChangePosition(MyGUI::ScrollBar* scroller, size_t pos) { - int diff = lastPos - pos; + int diff = mLastPos - pos; // Adjust position of all widget according to difference if (diff == 0) return; - lastPos = pos; + mLastPos = pos; - std::vector::const_iterator end = skillWidgets.end(); - for (std::vector::const_iterator it = skillWidgets.begin(); it != end; ++it) + std::vector::const_iterator end = mSkillWidgets.end(); + for (std::vector::const_iterator it = mSkillWidgets.begin(); it != end; ++it) { (*it)->setCoord((*it)->getCoord() + MyGUI::IntPoint(0, diff)); } @@ -132,63 +132,63 @@ void ReviewDialog::onWindowResize(MyGUI::Window* window) void ReviewDialog::setPlayerName(const std::string &name) { - nameWidget->setCaption(name); + mNameWidget->setCaption(name); } -void ReviewDialog::setRace(const std::string &raceId_) +void ReviewDialog::setRace(const std::string &raceId) { - raceId = raceId_; - const ESM::Race *race = mWindowManager.getStore().races.search(raceId); + mRaceId = raceId; + const ESM::Race *race = mWindowManager.getStore().races.search(mRaceId); if (race) { - ToolTips::createRaceToolTip(raceWidget, race); - raceWidget->setCaption(race->name); + ToolTips::createRaceToolTip(mRaceWidget, race); + mRaceWidget->setCaption(race->name); } } void ReviewDialog::setClass(const ESM::Class& class_) { - klass = class_; - classWidget->setCaption(klass.name); - ToolTips::createClassToolTip(classWidget, klass); + mKlass = class_; + mClassWidget->setCaption(mKlass.name); + ToolTips::createClassToolTip(mClassWidget, mKlass); } void ReviewDialog::setBirthSign(const std::string& signId) { - birthSignId = signId; - const ESM::BirthSign *sign = mWindowManager.getStore().birthSigns.search(birthSignId); + mBirthSignId = signId; + const ESM::BirthSign *sign = mWindowManager.getStore().birthSigns.search(mBirthSignId); if (sign) { - birthSignWidget->setCaption(sign->name); - ToolTips::createBirthsignToolTip(birthSignWidget, birthSignId); + mBirthSignWidget->setCaption(sign->name); + ToolTips::createBirthsignToolTip(mBirthSignWidget, mBirthSignId); } } void ReviewDialog::setHealth(const MWMechanics::DynamicStat& value) { - health->setValue(value.getCurrent(), value.getModified()); + mHealth->setValue(value.getCurrent(), value.getModified()); std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); - health->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); + mHealth->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); } void ReviewDialog::setMagicka(const MWMechanics::DynamicStat& value) { - magicka->setValue(value.getCurrent(), value.getModified()); + mMagicka->setValue(value.getCurrent(), value.getModified()); std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); - magicka->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + mMagicka->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); } void ReviewDialog::setFatigue(const MWMechanics::DynamicStat& value) { - fatigue->setValue(value.getCurrent(), value.getModified()); + mFatigue->setValue(value.getCurrent(), value.getModified()); std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); - fatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); + mFatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::Stat& value) { - std::map::iterator attr = attributeWidgets.find(static_cast(attributeId)); - if (attr == attributeWidgets.end()) + std::map::iterator attr = mAttributeWidgets.find(static_cast(attributeId)); + if (attr == mAttributeWidgets.end()) return; attr->second->setAttributeValue(value); @@ -196,8 +196,8 @@ void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const M void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::Stat& value) { - skillValues[skillId] = value; - MyGUI::TextBox* widget = skillWidgetMap[skillId]; + mSkillValues[skillId] = value; + MyGUI::TextBox* widget = mSkillWidgetMap[skillId]; if (widget) { float modified = value.getModified(), base = value.getBase(); @@ -216,20 +216,20 @@ void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanic void ReviewDialog::configureSkills(const std::vector& major, const std::vector& minor) { - majorSkills = major; - minorSkills = minor; + mMajorSkills = major; + mMinorSkills = minor; // Update misc skills with the remaining skills not in major or minor std::set skillSet; std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); boost::array::const_iterator end = ESM::Skill::skillIds.end(); - miscSkills.clear(); + mMiscSkills.clear(); for (boost::array::const_iterator it = ESM::Skill::skillIds.begin(); it != end; ++it) { int skill = *it; if (skillSet.find(skill) == skillSet.end()) - miscSkills.push_back(skill); + mMiscSkills.push_back(skill); } updateSkillArea(); @@ -237,10 +237,10 @@ void ReviewDialog::configureSkills(const std::vector& major, const std::vec void ReviewDialog::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { - MyGUI::ImageBox* separator = skillClientWidget->createWidget("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Default); + MyGUI::ImageBox* separator = mSkillClientWidget->createWidget("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Default); separator->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); - skillWidgets.push_back(separator); + mSkillWidgets.push_back(separator); coord1.top += separator->getHeight(); coord2.top += separator->getHeight(); @@ -248,13 +248,13 @@ void ReviewDialog::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2 void ReviewDialog::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { - MyGUI::TextBox* groupWidget = skillClientWidget->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default); + MyGUI::TextBox* groupWidget = mSkillClientWidget->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default); groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); groupWidget->setCaption(label); - skillWidgets.push_back(groupWidget); + mSkillWidgets.push_back(groupWidget); - coord1.top += lineHeight; - coord2.top += lineHeight; + coord1.top += sLineHeight; + coord2.top += sLineHeight; } MyGUI::TextBox* ReviewDialog::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) @@ -262,20 +262,20 @@ MyGUI::TextBox* ReviewDialog::addValueItem(const std::string& text, const std::s MyGUI::TextBox* skillNameWidget; MyGUI::TextBox* skillValueWidget; - skillNameWidget = skillClientWidget->createWidget("SandText", coord1, MyGUI::Align::Default); + skillNameWidget = mSkillClientWidget->createWidget("SandText", coord1, MyGUI::Align::Default); skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); - skillValueWidget = skillClientWidget->createWidget("SandTextRight", coord2, MyGUI::Align::Top | MyGUI::Align::Right); + skillValueWidget = mSkillClientWidget->createWidget("SandTextRight", coord2, MyGUI::Align::Top | MyGUI::Align::Right); skillValueWidget->setCaption(value); skillValueWidget->_setWidgetState(state); skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); - skillWidgets.push_back(skillNameWidget); - skillWidgets.push_back(skillValueWidget); + mSkillWidgets.push_back(skillNameWidget); + mSkillWidgets.push_back(skillValueWidget); - coord1.top += lineHeight; - coord2.top += lineHeight; + coord1.top += sLineHeight; + coord2.top += sLineHeight; return skillValueWidget; } @@ -284,20 +284,20 @@ void ReviewDialog::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyG { MyGUI::TextBox* skillNameWidget; - skillNameWidget = skillClientWidget->createWidget("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); + skillNameWidget = mSkillClientWidget->createWidget("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); - skillWidgets.push_back(skillNameWidget); + mSkillWidgets.push_back(skillNameWidget); - coord1.top += lineHeight; - coord2.top += lineHeight; + coord1.top += sLineHeight; + coord2.top += sLineHeight; } void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { // Add a line separator if there are items above - if (!skillWidgets.empty()) + if (!mSkillWidgets.empty()) { addSeparator(coord1, coord2); } @@ -312,7 +312,7 @@ void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId continue; assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESMS::Skill::sSkillNameIds[skillId]; - const MWMechanics::Stat &stat = skillValues.find(skillId)->second; + const MWMechanics::Stat &stat = mSkillValues.find(skillId)->second; float base = stat.getBase(); float modified = stat.getModified(); @@ -325,44 +325,44 @@ void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId for (int i=0; i<2; ++i) { - ToolTips::createSkillToolTip(skillWidgets[skillWidgets.size()-1-i], skillId); + ToolTips::createSkillToolTip(mSkillWidgets[mSkillWidgets.size()-1-i], skillId); } - skillWidgetMap[skillId] = widget; + mSkillWidgetMap[skillId] = widget; } } void ReviewDialog::updateSkillArea() { - for (std::vector::iterator it = skillWidgets.begin(); it != skillWidgets.end(); ++it) + for (std::vector::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } - skillWidgets.clear(); + mSkillWidgets.clear(); const int valueSize = 40; - MyGUI::IntCoord coord1(10, 0, skillClientWidget->getWidth() - (10 + valueSize), 18); + MyGUI::IntCoord coord1(10, 0, mSkillClientWidget->getWidth() - (10 + valueSize), 18); MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height); - if (!majorSkills.empty()) - addSkills(majorSkills, "sSkillClassMajor", "Major Skills", coord1, coord2); + if (!mMajorSkills.empty()) + addSkills(mMajorSkills, "sSkillClassMajor", "Major Skills", coord1, coord2); - if (!minorSkills.empty()) - addSkills(minorSkills, "sSkillClassMinor", "Minor Skills", coord1, coord2); + if (!mMinorSkills.empty()) + addSkills(mMinorSkills, "sSkillClassMinor", "Minor Skills", coord1, coord2); - if (!miscSkills.empty()) - addSkills(miscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); + if (!mMiscSkills.empty()) + addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); - clientHeight = coord1.top; + mClientHeight = coord1.top; updateScroller(); } void ReviewDialog::updateScroller() { - skillScrollerWidget->setScrollRange(std::max(clientHeight - skillClientWidget->getHeight(), 0)); - skillScrollerWidget->setScrollPage(std::max(skillClientWidget->getHeight() - lineHeight, 0)); - if (clientHeight != 0) - skillScrollerWidget->setTrackSize( (skillAreaWidget->getHeight() / float(clientHeight)) * skillScrollerWidget->getLineSize() ); + mSkillScrollerWidget->setScrollRange(std::max(mClientHeight - mSkillClientWidget->getHeight(), 0)); + mSkillScrollerWidget->setScrollPage(std::max(mSkillClientWidget->getHeight() - sLineHeight, 0)); + if (mClientHeight != 0) + mSkillScrollerWidget->setTrackSize( (mSkillAreaWidget->getHeight() / float(mClientHeight)) * mSkillScrollerWidget->getLineSize() ); } // widget controls @@ -399,12 +399,12 @@ void ReviewDialog::onBirthSignClicked(MyGUI::Widget* _sender) void ReviewDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel) { - if (skillScrollerWidget->getScrollPosition() - _rel*0.3 < 0) - skillScrollerWidget->setScrollPosition(0); - else if (skillScrollerWidget->getScrollPosition() - _rel*0.3 > skillScrollerWidget->getScrollRange()-1) - skillScrollerWidget->setScrollPosition(skillScrollerWidget->getScrollRange()-1); + if (mSkillScrollerWidget->getScrollPosition() - _rel*0.3 < 0) + mSkillScrollerWidget->setScrollPosition(0); + else if (mSkillScrollerWidget->getScrollPosition() - _rel*0.3 > mSkillScrollerWidget->getScrollRange()-1) + mSkillScrollerWidget->setScrollPosition(mSkillScrollerWidget->getScrollRange()-1); else - skillScrollerWidget->setScrollPosition(skillScrollerWidget->getScrollPosition() - _rel*0.3); + mSkillScrollerWidget->setScrollPosition(mSkillScrollerWidget->getScrollPosition() - _rel*0.3); - onScrollChangePosition(skillScrollerWidget, skillScrollerWidget->getScrollPosition()); + onScrollChangePosition(mSkillScrollerWidget, mSkillScrollerWidget->getScrollPosition()); } diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 454a6c5a5..27b167033 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -82,23 +82,23 @@ namespace MWGui void onScrollChangePosition(MyGUI::ScrollBar* scroller, size_t pos); void onWindowResize(MyGUI::Window* window); - static const int lineHeight; + static const int sLineHeight; - MyGUI::TextBox *nameWidget, *raceWidget, *classWidget, *birthSignWidget; - MyGUI::WidgetPtr skillAreaWidget, skillClientWidget; - MyGUI::ScrollBar* skillScrollerWidget; - int lastPos, clientHeight; + MyGUI::TextBox *mNameWidget, *mRaceWidget, *mClassWidget, *mBirthSignWidget; + MyGUI::WidgetPtr mSkillAreaWidget, mSkillClientWidget; + MyGUI::ScrollBar* mSkillScrollerWidget; + int mLastPos, mClientHeight; - Widgets::MWDynamicStatPtr health, magicka, fatigue; + Widgets::MWDynamicStatPtr mHealth, mMagicka, mFatigue; - std::map attributeWidgets; + std::map mAttributeWidgets; - SkillList majorSkills, minorSkills, miscSkills; - std::map > skillValues; - std::map skillWidgetMap; - std::string name, raceId, birthSignId; - ESM::Class klass; - std::vector skillWidgets; //< Skills and other information + SkillList mMajorSkills, mMinorSkills, mMiscSkills; + std::map > mSkillValues; + std::map mSkillWidgetMap; + std::string mName, mRaceId, mBirthSignId; + ESM::Class mKlass; + std::vector mSkillWidgets; //< Skills and other information }; } #endif diff --git a/apps/openmw/mwgui/stats_window.cpp b/apps/openmw/mwgui/stats_window.cpp index 0f8b41b28..e7a74a9e1 100644 --- a/apps/openmw/mwgui/stats_window.cpp +++ b/apps/openmw/mwgui/stats_window.cpp @@ -19,26 +19,26 @@ using namespace MWGui; -const int StatsWindow::lineHeight = 18; +const int StatsWindow::sLineHeight = 18; StatsWindow::StatsWindow (WindowManager& parWindowManager) : WindowPinnableBase("openmw_stats_window.layout", parWindowManager) - , skillAreaWidget(NULL) - , skillClientWidget(NULL) - , skillScrollerWidget(NULL) - , lastPos(0) - , clientHeight(0) - , majorSkills() - , minorSkills() - , miscSkills() - , skillValues() - , skillWidgetMap() - , factionWidgetMap() + , mSkillAreaWidget(NULL) + , mSkillClientWidget(NULL) + , mSkillScrollerWidget(NULL) + , mLastPos(0) + , mClientHeight(0) + , mMajorSkills() + , mMinorSkills() + , mMiscSkills() + , mSkillValues() + , mSkillWidgetMap() + , mFactionWidgetMap() , mFactions() - , birthSignId() - , reputation(0) - , bounty(0) - , skillWidgets() + , mBirthSignId() + , mReputation(0) + , mBounty(0) + , mSkillWidgets() , mChanged(true) { setCoord(0,0,498, 342); @@ -62,21 +62,21 @@ StatsWindow::StatsWindow (WindowManager& parWindowManager) setText (names[i][0], store.gameSettings.find (names[i][1])->str); } - getWidget(skillAreaWidget, "Skills"); - getWidget(skillClientWidget, "SkillClient"); - getWidget(skillScrollerWidget, "SkillScroller"); + getWidget(mSkillAreaWidget, "Skills"); + getWidget(mSkillClientWidget, "SkillClient"); + getWidget(mSkillScrollerWidget, "SkillScroller"); getWidget(mLeftPane, "LeftPane"); getWidget(mRightPane, "RightPane"); - skillClientWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); + mSkillClientWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); - skillScrollerWidget->eventScrollChangePosition += MyGUI::newDelegate(this, &StatsWindow::onScrollChangePosition); + mSkillScrollerWidget->eventScrollChangePosition += MyGUI::newDelegate(this, &StatsWindow::onScrollChangePosition); updateScroller(); for (int i = 0; i < ESM::Skill::Length; ++i) { - skillValues.insert(std::pair >(i, MWMechanics::Stat())); - skillWidgetMap.insert(std::pair(i, nullptr)); + mSkillValues.insert(std::pair >(i, MWMechanics::Stat())); + mSkillWidgetMap.insert(std::pair(i, nullptr)); } MyGUI::WindowPtr t = static_cast(mMainWidget); @@ -85,14 +85,14 @@ StatsWindow::StatsWindow (WindowManager& parWindowManager) void StatsWindow::onScrollChangePosition(MyGUI::ScrollBar* scroller, size_t pos) { - int diff = lastPos - pos; + int diff = mLastPos - pos; // Adjust position of all widget according to difference if (diff == 0) return; - lastPos = pos; + mLastPos = pos; - std::vector::const_iterator end = skillWidgets.end(); - for (std::vector::const_iterator it = skillWidgets.begin(); it != end; ++it) + std::vector::const_iterator end = mSkillWidgets.end(); + for (std::vector::const_iterator it = mSkillWidgets.begin(); it != end; ++it) { (*it)->setCoord((*it)->getCoord() + MyGUI::IntPoint(0, diff)); } @@ -100,14 +100,14 @@ void StatsWindow::onScrollChangePosition(MyGUI::ScrollBar* scroller, size_t pos) void StatsWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { - if (skillScrollerWidget->getScrollPosition() - _rel*0.3 < 0) - skillScrollerWidget->setScrollPosition(0); - else if (skillScrollerWidget->getScrollPosition() - _rel*0.3 > skillScrollerWidget->getScrollRange()-1) - skillScrollerWidget->setScrollPosition(skillScrollerWidget->getScrollRange()-1); + if (mSkillScrollerWidget->getScrollPosition() - _rel*0.3 < 0) + mSkillScrollerWidget->setScrollPosition(0); + else if (mSkillScrollerWidget->getScrollPosition() - _rel*0.3 > mSkillScrollerWidget->getScrollRange()-1) + mSkillScrollerWidget->setScrollPosition(mSkillScrollerWidget->getScrollRange()-1); else - skillScrollerWidget->setScrollPosition(skillScrollerWidget->getScrollPosition() - _rel*0.3); + mSkillScrollerWidget->setScrollPosition(mSkillScrollerWidget->getScrollPosition() - _rel*0.3); - onScrollChangePosition(skillScrollerWidget, skillScrollerWidget->getScrollPosition()); + onScrollChangePosition(mSkillScrollerWidget, mSkillScrollerWidget->getScrollPosition()); } void StatsWindow::onWindowResize(MyGUI::Window* window) @@ -224,8 +224,8 @@ void StatsWindow::setValue (const std::string& id, int value) void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value) { - skillValues[parSkill] = value; - MyGUI::TextBox* widget = skillWidgetMap[(int)parSkill]; + mSkillValues[parSkill] = value; + MyGUI::TextBox* widget = mSkillWidgetMap[(int)parSkill]; if (widget) { float modified = value.getModified(), base = value.getBase(); @@ -243,20 +243,20 @@ void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechani void StatsWindow::configureSkills (const std::vector& major, const std::vector& minor) { - majorSkills = major; - minorSkills = minor; + mMajorSkills = major; + mMinorSkills = minor; // Update misc skills with the remaining skills not in major or minor std::set skillSet; std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); boost::array::const_iterator end = ESM::Skill::skillIds.end(); - miscSkills.clear(); + mMiscSkills.clear(); for (boost::array::const_iterator it = ESM::Skill::skillIds.begin(); it != end; ++it) { int skill = *it; if (skillSet.find(skill) == skillSet.end()) - miscSkills.push_back(skill); + mMiscSkills.push_back(skill); } updateSkillArea(); @@ -289,20 +289,20 @@ void StatsWindow::setFactions (const FactionList& factions) void StatsWindow::setBirthSign (const std::string& signId) { - if (signId != birthSignId) + if (signId != mBirthSignId) { - birthSignId = signId; + mBirthSignId = signId; mChanged = true; } } void StatsWindow::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { - MyGUI::ImageBox* separator = skillClientWidget->createWidget("MW_HLine", + MyGUI::ImageBox* separator = mSkillClientWidget->createWidget("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); separator->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); - skillWidgets.push_back(separator); + mSkillWidgets.push_back(separator); coord1.top += separator->getHeight(); coord2.top += separator->getHeight(); @@ -310,35 +310,35 @@ void StatsWindow::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) void StatsWindow::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { - MyGUI::TextBox* groupWidget = skillClientWidget->createWidget("SandBrightText", + MyGUI::TextBox* groupWidget = mSkillClientWidget->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); groupWidget->setCaption(label); groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); - skillWidgets.push_back(groupWidget); + mSkillWidgets.push_back(groupWidget); - coord1.top += lineHeight; - coord2.top += lineHeight; + coord1.top += sLineHeight; + coord2.top += sLineHeight; } MyGUI::TextBox* StatsWindow::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox *skillNameWidget, *skillValueWidget; - skillNameWidget = skillClientWidget->createWidget("SandText", coord1, MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); + skillNameWidget = mSkillClientWidget->createWidget("SandText", coord1, MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); - skillValueWidget = skillClientWidget->createWidget("SandTextRight", coord2, MyGUI::Align::Right | MyGUI::Align::Top); + skillValueWidget = mSkillClientWidget->createWidget("SandTextRight", coord2, MyGUI::Align::Right | MyGUI::Align::Top); skillValueWidget->setCaption(value); skillValueWidget->_setWidgetState(state); skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); - skillWidgets.push_back(skillNameWidget); - skillWidgets.push_back(skillValueWidget); + mSkillWidgets.push_back(skillNameWidget); + mSkillWidgets.push_back(skillValueWidget); - coord1.top += lineHeight; - coord2.top += lineHeight; + coord1.top += sLineHeight; + coord2.top += sLineHeight; return skillValueWidget; } @@ -347,14 +347,14 @@ MyGUI::Widget* StatsWindow::addItem(const std::string& text, MyGUI::IntCoord &co { MyGUI::TextBox* skillNameWidget; - skillNameWidget = skillClientWidget->createWidget("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); + skillNameWidget = mSkillClientWidget->createWidget("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); - skillWidgets.push_back(skillNameWidget); + mSkillWidgets.push_back(skillNameWidget); - coord1.top += lineHeight; - coord2.top += lineHeight; + coord1.top += sLineHeight; + coord2.top += sLineHeight; return skillNameWidget; } @@ -362,7 +362,7 @@ MyGUI::Widget* StatsWindow::addItem(const std::string& text, MyGUI::IntCoord &co void StatsWindow::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { // Add a line separator if there are items above - if (!skillWidgets.empty()) + if (!mSkillWidgets.empty()) { addSeparator(coord1, coord2); } @@ -377,7 +377,7 @@ void StatsWindow::addSkills(const SkillList &skills, const std::string &titleId, continue; assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESMS::Skill::sSkillNameIds[skillId]; - const MWMechanics::Stat &stat = skillValues.find(skillId)->second; + const MWMechanics::Stat &stat = mSkillValues.find(skillId)->second; float base = stat.getBase(); float modified = stat.getModified(); int progressPercent = (modified - float(static_cast(modified))) * 100; @@ -400,18 +400,18 @@ void StatsWindow::addSkills(const SkillList &skills, const std::string &titleId, for (int i=0; i<2; ++i) { - skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipLayout", "SkillToolTip"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_SkillName", "#{"+skillNameId+"}"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_SkillDescription", skill->description); - skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_SkillAttribute", "#{sGoverningAttribute}: #{" + attr->name + "}"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("ImageTexture_SkillImage", icon); - skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_SkillProgressText", boost::lexical_cast(progressPercent)+"/100"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("RangePosition_SkillProgress", boost::lexical_cast(progressPercent)); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "SkillToolTip"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillName", "#{"+skillNameId+"}"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillDescription", skill->description); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillAttribute", "#{sGoverningAttribute}: #{" + attr->name + "}"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ImageTexture_SkillImage", icon); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillProgressText", boost::lexical_cast(progressPercent)+"/100"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("RangePosition_SkillProgress", boost::lexical_cast(progressPercent)); } - skillWidgetMap[skillId] = widget; + mSkillWidgetMap[skillId] = widget; } } @@ -419,28 +419,28 @@ void StatsWindow::updateSkillArea() { mChanged = false; - for (std::vector::iterator it = skillWidgets.begin(); it != skillWidgets.end(); ++it) + for (std::vector::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } - skillWidgets.clear(); + mSkillWidgets.clear(); - skillScrollerWidget->setScrollPosition(0); - onScrollChangePosition(skillScrollerWidget, 0); - clientHeight = 0; + mSkillScrollerWidget->setScrollPosition(0); + onScrollChangePosition(mSkillScrollerWidget, 0); + mClientHeight = 0; const int valueSize = 40; - MyGUI::IntCoord coord1(10, 0, skillClientWidget->getWidth() - (10 + valueSize), 18); + MyGUI::IntCoord coord1(10, 0, mSkillClientWidget->getWidth() - (10 + valueSize), 18); MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height); - if (!majorSkills.empty()) - addSkills(majorSkills, "sSkillClassMajor", "Major Skills", coord1, coord2); + if (!mMajorSkills.empty()) + addSkills(mMajorSkills, "sSkillClassMajor", "Major Skills", coord1, coord2); - if (!minorSkills.empty()) - addSkills(minorSkills, "sSkillClassMinor", "Minor Skills", coord1, coord2); + if (!mMinorSkills.empty()) + addSkills(mMinorSkills, "sSkillClassMinor", "Minor Skills", coord1, coord2); - if (!miscSkills.empty()) - addSkills(miscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); + if (!mMiscSkills.empty()) + addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); const ESMS::ESMStore &store = mWindowManager.getStore(); @@ -463,7 +463,7 @@ void StatsWindow::updateSkillArea() if (!mFactions.empty()) { // Add a line separator if there are items above - if (!skillWidgets.empty()) + if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); addGroup(mWindowManager.getGameSettingString("sFaction", "Faction"), coord1, coord2); @@ -516,53 +516,53 @@ void StatsWindow::updateSkillArea() } } - if (!birthSignId.empty()) + if (!mBirthSignId.empty()) { // Add a line separator if there are items above - if (!skillWidgets.empty()) + if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); addGroup(mWindowManager.getGameSettingString("sBirthSign", "Sign"), coord1, coord2); - const ESM::BirthSign *sign = store.birthSigns.find(birthSignId); + const ESM::BirthSign *sign = store.birthSigns.find(mBirthSignId); MyGUI::Widget* w = addItem(sign->name, coord1, coord2); - ToolTips::createBirthsignToolTip(w, birthSignId); + ToolTips::createBirthsignToolTip(w, mBirthSignId); } // Add a line separator if there are items above - if (!skillWidgets.empty()) + if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); addValueItem(mWindowManager.getGameSettingString("sReputation", "Reputation"), - boost::lexical_cast(static_cast(reputation)), "normal", coord1, coord2); + boost::lexical_cast(static_cast(mReputation)), "normal", coord1, coord2); for (int i=0; i<2; ++i) { - skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sSkillsMenuReputationHelp}"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sSkillsMenuReputationHelp}"); } addValueItem(mWindowManager.getGameSettingString("sBounty", "Bounty"), - boost::lexical_cast(static_cast(bounty)), "normal", coord1, coord2); + boost::lexical_cast(static_cast(mBounty)), "normal", coord1, coord2); for (int i=0; i<2; ++i) { - skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); - skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sCrimeHelp}"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sCrimeHelp}"); } - clientHeight = coord1.top; + mClientHeight = coord1.top; updateScroller(); } void StatsWindow::updateScroller() { - skillScrollerWidget->setScrollRange(std::max(clientHeight - skillClientWidget->getHeight(), 0)); - skillScrollerWidget->setScrollPage(std::max(skillClientWidget->getHeight() - lineHeight, 0)); - if (clientHeight != 0) - skillScrollerWidget->setTrackSize( (skillAreaWidget->getHeight() / float(clientHeight)) * skillScrollerWidget->getLineSize() ); + mSkillScrollerWidget->setScrollRange(std::max(mClientHeight - mSkillClientWidget->getHeight(), 0)); + mSkillScrollerWidget->setScrollPage(std::max(mSkillClientWidget->getHeight() - sLineHeight, 0)); + if (mClientHeight != 0) + mSkillScrollerWidget->setTrackSize( (mSkillAreaWidget->getHeight() / float(mClientHeight)) * mSkillScrollerWidget->getLineSize() ); } void StatsWindow::onPinToggled() diff --git a/apps/openmw/mwgui/stats_window.hpp b/apps/openmw/mwgui/stats_window.hpp index 10b794cc0..2469c12e9 100644 --- a/apps/openmw/mwgui/stats_window.hpp +++ b/apps/openmw/mwgui/stats_window.hpp @@ -38,8 +38,8 @@ namespace MWGui void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value); void configureSkills (const SkillList& major, const SkillList& minor); - void setReputation (int reputation) { this->reputation = reputation; } - void setBounty (int bounty) { this->bounty = bounty; } + void setReputation (int reputation) { this->mReputation = reputation; } + void setBounty (int bounty) { this->mBounty = bounty; } void updateSkillArea(); private: @@ -57,23 +57,23 @@ namespace MWGui void onWindowResize(MyGUI::Window* window); void onMouseWheel(MyGUI::Widget* _sender, int _rel); - static const int lineHeight; + static const int sLineHeight; MyGUI::Widget* mLeftPane; MyGUI::Widget* mRightPane; - MyGUI::WidgetPtr skillAreaWidget, skillClientWidget; - MyGUI::ScrollBar* skillScrollerWidget; - int lastPos, clientHeight; + MyGUI::WidgetPtr mSkillAreaWidget, mSkillClientWidget; + MyGUI::ScrollBar* mSkillScrollerWidget; + int mLastPos, mClientHeight; - SkillList majorSkills, minorSkills, miscSkills; - std::map > skillValues; - std::map skillWidgetMap; - std::map factionWidgetMap; + SkillList mMajorSkills, mMinorSkills, mMiscSkills; + std::map > mSkillValues; + std::map mSkillWidgetMap; + std::map mFactionWidgetMap; FactionList mFactions; ///< Stores a list of factions and the current rank - std::string birthSignId; - int reputation, bounty; - std::vector skillWidgets; //< Skills and other information + std::string mBirthSignId; + int mReputation, mBounty; + std::vector mSkillWidgets; //< Skills and other information bool mChanged; diff --git a/apps/openmw/mwgui/text_input.cpp b/apps/openmw/mwgui/text_input.cpp index 17852ba5a..7d5b0cc6d 100644 --- a/apps/openmw/mwgui/text_input.cpp +++ b/apps/openmw/mwgui/text_input.cpp @@ -9,15 +9,15 @@ TextInputDialog::TextInputDialog(WindowManager& parWindowManager) // Centre dialog center(); - getWidget(textEdit, "TextEdit"); - textEdit->eventEditSelectAccept += newDelegate(this, &TextInputDialog::onTextAccepted); + getWidget(mTextEdit, "TextEdit"); + mTextEdit->eventEditSelectAccept += newDelegate(this, &TextInputDialog::onTextAccepted); MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TextInputDialog::onOkClicked); // Make sure the edit box has focus - MyGUI::InputManager::getInstance().setKeyFocusWidget(textEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } void TextInputDialog::setNextButtonShow(bool shown) @@ -43,7 +43,7 @@ void TextInputDialog::setTextLabel(const std::string &label) void TextInputDialog::open() { // Make sure the edit box has focus - MyGUI::InputManager::getInstance().setKeyFocusWidget(textEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); setVisible(true); } diff --git a/apps/openmw/mwgui/text_input.hpp b/apps/openmw/mwgui/text_input.hpp index fe355655f..835d5deaa 100644 --- a/apps/openmw/mwgui/text_input.hpp +++ b/apps/openmw/mwgui/text_input.hpp @@ -20,8 +20,8 @@ namespace MWGui public: TextInputDialog(WindowManager& parWindowManager); - std::string getTextInput() const { return textEdit ? textEdit->getOnlyText() : ""; } - void setTextInput(const std::string &text) { if (textEdit) textEdit->setOnlyText(text); } + std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; } + void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); } void setNextButtonShow(bool shown); void setTextLabel(const std::string &label); @@ -32,7 +32,7 @@ namespace MWGui void onTextAccepted(MyGUI::Edit* _sender); private: - MyGUI::EditPtr textEdit; + MyGUI::EditPtr mTextEdit; }; } #endif diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 65de7ce0a..c54ac6e38 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -27,16 +27,16 @@ void MWGui::Widgets::fixTexturePath(std::string &path) /* MWSkill */ MWSkill::MWSkill() - : manager(nullptr) - , skillId(ESM::Skill::Length) - , skillNameWidget(nullptr) - , skillValueWidget(nullptr) + : mManager(nullptr) + , mSkillId(ESM::Skill::Length) + , mSkillNameWidget(nullptr) + , mSkillValueWidget(nullptr) { } void MWSkill::setSkillId(ESM::Skill::SkillEnum skill) { - skillId = skill; + mSkillId = skill; updateWidgets(); } @@ -50,36 +50,36 @@ void MWSkill::setSkillNumber(int skill) throw new std::runtime_error("Skill number out of range"); } -void MWSkill::setSkillValue(const SkillValue& value_) +void MWSkill::setSkillValue(const SkillValue& value) { - value = value_; + mValue = value; updateWidgets(); } void MWSkill::updateWidgets() { - if (skillNameWidget && manager) + if (mSkillNameWidget && mManager) { - if (skillId == ESM::Skill::Length) + if (mSkillId == ESM::Skill::Length) { - static_cast(skillNameWidget)->setCaption(""); + static_cast(mSkillNameWidget)->setCaption(""); } else { - const std::string &name = manager->getGameSettingString(ESM::Skill::sSkillNameIds[skillId], ""); - static_cast(skillNameWidget)->setCaption(name); + const std::string &name = mManager->getGameSettingString(ESM::Skill::sSkillNameIds[mSkillId], ""); + static_cast(mSkillNameWidget)->setCaption(name); } } - if (skillValueWidget) + if (mSkillValueWidget) { - SkillValue::Type modified = value.getModified(), base = value.getBase(); - static_cast(skillValueWidget)->setCaption(boost::lexical_cast(modified)); + SkillValue::Type modified = mValue.getModified(), base = mValue.getBase(); + static_cast(mSkillValueWidget)->setCaption(boost::lexical_cast(modified)); if (modified > base) - skillValueWidget->_setWidgetState("increased"); + mSkillValueWidget->_setWidgetState("increased"); else if (modified < base) - skillValueWidget->_setWidgetState("decreased"); + mSkillValueWidget->_setWidgetState("decreased"); else - skillValueWidget->_setWidgetState("normal"); + mSkillValueWidget->_setWidgetState("normal"); } } @@ -96,14 +96,14 @@ void MWSkill::initialiseOverride() { Base::initialiseOverride(); - assignWidget(skillNameWidget, "StatName"); - assignWidget(skillValueWidget, "StatValue"); + assignWidget(mSkillNameWidget, "StatName"); + assignWidget(mSkillValueWidget, "StatValue"); MyGUI::ButtonPtr button; assignWidget(button, "StatNameButton"); if (button) { - skillNameWidget = button; + mSkillNameWidget = button; button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); } @@ -111,7 +111,7 @@ void MWSkill::initialiseOverride() assignWidget(button, "StatValueButton"); if (button) { - skillNameWidget = button; + mSkillNameWidget = button; button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); } } @@ -119,22 +119,22 @@ void MWSkill::initialiseOverride() /* MWAttribute */ MWAttribute::MWAttribute() - : manager(nullptr) - , id(-1) - , attributeNameWidget(nullptr) - , attributeValueWidget(nullptr) + : mManager(nullptr) + , mId(-1) + , mAttributeNameWidget(nullptr) + , mAttributeValueWidget(nullptr) { } void MWAttribute::setAttributeId(int attributeId) { - id = attributeId; + mId = attributeId; updateWidgets(); } -void MWAttribute::setAttributeValue(const AttributeValue& value_) +void MWAttribute::setAttributeValue(const AttributeValue& value) { - value = value_; + mValue = value; updateWidgets(); } @@ -145,11 +145,11 @@ void MWAttribute::onClicked(MyGUI::Widget* _sender) void MWAttribute::updateWidgets() { - if (attributeNameWidget && manager) + if (mAttributeNameWidget && mManager) { - if (id < 0 || id >= 8) + if (mId < 0 || mId >= 8) { - static_cast(attributeNameWidget)->setCaption(""); + static_cast(mAttributeNameWidget)->setCaption(""); } else { @@ -163,20 +163,20 @@ void MWAttribute::updateWidgets() "sAttributePersonality", "sAttributeLuck" }; - const std::string &name = manager->getGameSettingString(attributes[id], ""); - static_cast(attributeNameWidget)->setCaption(name); + const std::string &name = mManager->getGameSettingString(attributes[mId], ""); + static_cast(mAttributeNameWidget)->setCaption(name); } } - if (attributeValueWidget) + if (mAttributeValueWidget) { - AttributeValue::Type modified = value.getModified(), base = value.getBase(); - static_cast(attributeValueWidget)->setCaption(boost::lexical_cast(modified)); + AttributeValue::Type modified = mValue.getModified(), base = mValue.getBase(); + static_cast(mAttributeValueWidget)->setCaption(boost::lexical_cast(modified)); if (modified > base) - attributeValueWidget->_setWidgetState("increased"); + mAttributeValueWidget->_setWidgetState("increased"); else if (modified < base) - attributeValueWidget->_setWidgetState("decreased"); + mAttributeValueWidget->_setWidgetState("decreased"); else - attributeValueWidget->_setWidgetState("normal"); + mAttributeValueWidget->_setWidgetState("normal"); } } @@ -188,14 +188,14 @@ void MWAttribute::initialiseOverride() { Base::initialiseOverride(); - assignWidget(attributeNameWidget, "StatName"); - assignWidget(attributeValueWidget, "StatValue"); + assignWidget(mAttributeNameWidget, "StatName"); + assignWidget(mAttributeValueWidget, "StatValue"); MyGUI::ButtonPtr button; assignWidget(button, "StatNameButton"); if (button) { - attributeNameWidget = button; + mAttributeNameWidget = button; button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked); } @@ -203,7 +203,7 @@ void MWAttribute::initialiseOverride() assignWidget(button, "StatValueButton"); if (button) { - attributeValueWidget = button; + mAttributeValueWidget = button; button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked); } } @@ -212,21 +212,21 @@ void MWAttribute::initialiseOverride() MWSpell::MWSpell() : mWindowManager(nullptr) - , spellNameWidget(nullptr) + , mSpellNameWidget(nullptr) { } void MWSpell::setSpellId(const std::string &spellId) { - id = spellId; + mId = spellId; updateWidgets(); } void MWSpell::createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, int flags) { const ESMS::ESMStore &store = mWindowManager->getStore(); - const ESM::Spell *spell = store.spells.search(id); - MYGUI_ASSERT(spell, "spell with id '" << id << "' not found"); + const ESM::Spell *spell = store.spells.search(mId); + MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); MWSpellEffectPtr effect = nullptr; std::vector::const_iterator end = spell->effects.list.end(); @@ -253,14 +253,14 @@ void MWSpell::createEffectWidgets(std::vector &effects, MyGUI: void MWSpell::updateWidgets() { - if (spellNameWidget && mWindowManager) + if (mSpellNameWidget && mWindowManager) { const ESMS::ESMStore &store = mWindowManager->getStore(); - const ESM::Spell *spell = store.spells.search(id); + const ESM::Spell *spell = store.spells.search(mId); if (spell) - static_cast(spellNameWidget)->setCaption(spell->name); + static_cast(mSpellNameWidget)->setCaption(spell->name); else - static_cast(spellNameWidget)->setCaption(""); + static_cast(mSpellNameWidget)->setCaption(""); } } @@ -268,7 +268,7 @@ void MWSpell::initialiseOverride() { Base::initialiseOverride(); - assignWidget(spellNameWidget, "StatName"); + assignWidget(mSpellNameWidget, "StatName"); } MWSpell::~MWSpell() @@ -367,8 +367,8 @@ SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) MWSpellEffect::MWSpellEffect() : mWindowManager(nullptr) - , imageWidget(nullptr) - , textWidget(nullptr) + , mImageWidget(nullptr) + , mTextWidget(nullptr) , mRequestedWidth(0) { } @@ -388,7 +388,7 @@ void MWSpellEffect::updateWidgets() const ESM::MagicEffect *magicEffect = store.magicEffects.search(mEffectParams.mEffectID); if (!magicEffect) return; - if (textWidget) + if (mTextWidget) { std::string pt = mWindowManager->getGameSettingString("spoint", ""); std::string pts = mWindowManager->getGameSettingString("spoints", ""); @@ -448,14 +448,14 @@ void MWSpellEffect::updateWidgets() } } - static_cast(textWidget)->setCaption(spellLine); - mRequestedWidth = textWidget->getTextSize().width + 24; + static_cast(mTextWidget)->setCaption(spellLine); + mRequestedWidth = mTextWidget->getTextSize().width + 24; } - if (imageWidget) + if (mImageWidget) { std::string path = std::string("icons\\") + magicEffect->icon; fixTexturePath(path); - imageWidget->setImageTexture(path); + mImageWidget->setImageTexture(path); } } @@ -728,49 +728,49 @@ void MWSpellEffect::initialiseOverride() { Base::initialiseOverride(); - assignWidget(textWidget, "Text"); - assignWidget(imageWidget, "Image"); + assignWidget(mTextWidget, "Text"); + assignWidget(mImageWidget, "Image"); } /* MWDynamicStat */ MWDynamicStat::MWDynamicStat() -: value(0) -, max(1) -, textWidget(nullptr) -, barWidget(nullptr) -, barTextWidget(nullptr) +: mValue(0) +, mMax(1) +, mTextWidget(nullptr) +, mBarWidget(nullptr) +, mBarTextWidget(nullptr) { } -void MWDynamicStat::setValue(int cur, int max_) +void MWDynamicStat::setValue(int cur, int max) { - value = cur; - max = max_; + mValue = cur; + mMax = max; - if (barWidget) + if (mBarWidget) { - barWidget->setProgressRange(max); - barWidget->setProgressPosition(value); + mBarWidget->setProgressRange(mMax); + mBarWidget->setProgressPosition(mValue); } - if (barTextWidget) + if (mBarTextWidget) { - if (value >= 0 && max > 0) + if (mValue >= 0 && mMax > 0) { std::stringstream out; - out << value << "/" << max; - static_cast(barTextWidget)->setCaption(out.str().c_str()); + out << mValue << "/" << mMax; + static_cast(mBarTextWidget)->setCaption(out.str().c_str()); } else - static_cast(barTextWidget)->setCaption(""); + static_cast(mBarTextWidget)->setCaption(""); } } void MWDynamicStat::setTitle(const std::string& text) { - if (textWidget) - static_cast(textWidget)->setCaption(text); + if (mTextWidget) + static_cast(mTextWidget)->setCaption(text); } MWDynamicStat::~MWDynamicStat() @@ -781,7 +781,7 @@ void MWDynamicStat::initialiseOverride() { Base::initialiseOverride(); - assignWidget(textWidget, "Text"); - assignWidget(barWidget, "Bar"); - assignWidget(barTextWidget, "BarText"); + assignWidget(mTextWidget, "Text"); + assignWidget(mBarWidget, "Bar"); + assignWidget(mBarTextWidget, "BarText"); } diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 5d00baf87..d4947895b 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -73,7 +73,7 @@ namespace MWGui typedef std::vector SpellEffectList; - class MYGUI_EXPORT MWSkill : public Widget + class MYGUI_EXPORT MWSkill : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWSkill ); public: @@ -81,14 +81,14 @@ namespace MWGui typedef MWMechanics::Stat SkillValue; - void setWindowManager(WindowManager *m) { manager = m; } + void setWindowManager(WindowManager *m) { mManager = m; } void setSkillId(ESM::Skill::SkillEnum skillId); void setSkillNumber(int skillId); void setSkillValue(const SkillValue& value); - WindowManager *getWindowManager() const { return manager; } - ESM::Skill::SkillEnum getSkillId() const { return skillId; } - const SkillValue& getSkillValue() const { return value; } + WindowManager *getWindowManager() const { return mManager; } + ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } + const SkillValue& getSkillValue() const { return mValue; } // Events typedef delegates::CMultiDelegate1 EventHandle_SkillVoid; @@ -109,14 +109,14 @@ namespace MWGui void updateWidgets(); - WindowManager *manager; - ESM::Skill::SkillEnum skillId; - SkillValue value; - MyGUI::WidgetPtr skillNameWidget, skillValueWidget; + WindowManager *mManager; + ESM::Skill::SkillEnum mSkillId; + SkillValue mValue; + MyGUI::WidgetPtr mSkillNameWidget, mSkillValueWidget; }; typedef MWSkill* MWSkillPtr; - class MYGUI_EXPORT MWAttribute : public Widget + class MYGUI_EXPORT MWAttribute : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWAttribute ); public: @@ -124,13 +124,13 @@ namespace MWGui typedef MWMechanics::Stat AttributeValue; - void setWindowManager(WindowManager *m) { manager = m; } + void setWindowManager(WindowManager *m) { mManager = m; } void setAttributeId(int attributeId); void setAttributeValue(const AttributeValue& value); - WindowManager *getWindowManager() const { return manager; } - int getAttributeId() const { return id; } - const AttributeValue& getAttributeValue() const { return value; } + WindowManager *getWindowManager() const { return mManager; } + int getAttributeId() const { return mId; } + const AttributeValue& getAttributeValue() const { return mValue; } // Events typedef delegates::CMultiDelegate1 EventHandle_AttributeVoid; @@ -151,10 +151,10 @@ namespace MWGui void updateWidgets(); - WindowManager *manager; - int id; - AttributeValue value; - MyGUI::WidgetPtr attributeNameWidget, attributeValueWidget; + WindowManager *mManager; + int mId; + AttributeValue mValue; + MyGUI::WidgetPtr mAttributeNameWidget, mAttributeValueWidget; }; typedef MWAttribute* MWAttributePtr; @@ -162,7 +162,7 @@ namespace MWGui * @todo remove this class and use MWEffectList instead */ class MWSpellEffect; - class MYGUI_EXPORT MWSpell : public Widget + class MYGUI_EXPORT MWSpell : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWSpell ); public: @@ -182,7 +182,7 @@ namespace MWGui */ void createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, int flags); - const std::string &getSpellId() const { return id; } + const std::string &getSpellId() const { return mId; } protected: virtual ~MWSpell(); @@ -193,12 +193,12 @@ namespace MWGui void updateWidgets(); WindowManager* mWindowManager; - std::string id; - MyGUI::TextBox* spellNameWidget; + std::string mId; + MyGUI::TextBox* mSpellNameWidget; }; typedef MWSpell* MWSpellPtr; - class MYGUI_EXPORT MWEffectList : public Widget + class MYGUI_EXPORT MWEffectList : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWEffectList ); public: @@ -239,7 +239,7 @@ namespace MWGui }; typedef MWEffectList* MWEffectListPtr; - class MYGUI_EXPORT MWSpellEffect : public Widget + class MYGUI_EXPORT MWSpellEffect : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWSpellEffect ); public: @@ -269,13 +269,13 @@ namespace MWGui WindowManager* mWindowManager; SpellEffectParams mEffectParams; - MyGUI::ImageBox* imageWidget; - MyGUI::TextBox* textWidget; + MyGUI::ImageBox* mImageWidget; + MyGUI::TextBox* mTextWidget; int mRequestedWidth; }; typedef MWSpellEffect* MWSpellEffectPtr; - class MYGUI_EXPORT MWDynamicStat : public Widget + class MYGUI_EXPORT MWDynamicStat : public MyGUI::Widget { MYGUI_RTTI_DERIVED( MWDynamicStat ); public: @@ -284,8 +284,8 @@ namespace MWGui void setValue(int value, int max); void setTitle(const std::string& text); - int getValue() const { return value; } - int getMax() const { return max; } + int getValue() const { return mValue; } + int getMax() const { return mMax; } protected: virtual ~MWDynamicStat(); @@ -294,10 +294,10 @@ namespace MWGui private: - int value, max; - MyGUI::TextBox* textWidget; - MyGUI::ProgressPtr barWidget; - MyGUI::TextBox* barTextWidget; + int mValue, mMax; + MyGUI::TextBox* mTextWidget; + MyGUI::ProgressPtr mBarWidget; + MyGUI::TextBox* mBarTextWidget; }; typedef MWDynamicStat* MWDynamicStatPtr; } diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 62207d7d7..aa0595b85 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -43,13 +43,13 @@ using namespace MWGui; WindowManager::WindowManager( const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath) : mGuiManager(NULL) - , hud(NULL) - , map(NULL) - , menu(NULL) + , mHud(NULL) + , mMap(NULL) + , mMenu(NULL) , mStatsWindow(NULL) , mToolTips(NULL) , mMessageBoxManager(NULL) - , console(NULL) + , mConsole(NULL) , mJournal(NULL) , mDialogueWindow(NULL) , mBookWindow(NULL) @@ -61,21 +61,21 @@ WindowManager::WindowManager( , mAlchemyWindow(NULL) , mSpellWindow(NULL) , mCharGen(NULL) - , playerClass() - , playerName() - , playerRaceId() - , playerAttributes() - , playerMajorSkills() - , playerMinorSkills() - , playerSkillValues() - , playerHealth() - , playerMagicka() - , playerFatigue() - , gui(NULL) - , garbageDialogs() - , shown(GW_ALL) - , allowed(newGame ? GW_None : GW_ALL) - , showFPSLevel(fpsLevel) + , mPlayerClass() + , mPlayerName() + , mPlayerRaceId() + , mPlayerAttributes() + , mPlayerMajorSkills() + , mPlayerMinorSkills() + , mPlayerSkillValues() + , mPlayerHealth() + , mPlayerMagicka() + , mPlayerFatigue() + , mGui(NULL) + , mGarbageDialogs() + , mShown(GW_ALL) + , mAllowed(newGame ? GW_None : GW_ALL) + , mShowFPSLevel(fpsLevel) , mFPS(0.0f) , mTriangleCount(0) , mBatchCount(0) @@ -83,7 +83,7 @@ WindowManager::WindowManager( // Set up the GUI system mGuiManager = new OEngine::GUI::MyGUIManager(mOgre->getWindow(), mOgre->getScene(), false, logpath); - gui = mGuiManager->getGui(); + mGui = mGuiManager->getGui(); //Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); @@ -98,11 +98,11 @@ WindowManager::WindowManager( MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); // Get size info from the Gui object - assert(gui); + assert(mGui); int w = MyGUI::RenderManager::getInstance().getViewSize().width; int h = MyGUI::RenderManager::getInstance().getViewSize().height; - MyGUI::Widget* dragAndDropWidget = gui->createWidgetT("Widget","",0,0,w,h,MyGUI::Align::Default,"DragAndDrop","DragAndDropWidget"); + MyGUI::Widget* dragAndDropWidget = mGui->createWidgetT("Widget","",0,0,w,h,MyGUI::Align::Default,"DragAndDrop","DragAndDropWidget"); dragAndDropWidget->setVisible(false); mDragAndDrop = new DragAndDrop(); @@ -110,17 +110,17 @@ WindowManager::WindowManager( mDragAndDrop->mDraggedWidget = 0; mDragAndDrop->mDragAndDropWidget = dragAndDropWidget; - menu = new MainMenu(w,h); - map = new MapWindow(*this); + mMenu = new MainMenu(w,h); + mMap = new MapWindow(*this); mStatsWindow = new StatsWindow(*this); - console = new Console(w,h, extensions); + mConsole = new Console(w,h, extensions); mJournal = new JournalWindow(*this); mMessageBoxManager = new MessageBoxManager(this); mInventoryWindow = new InventoryWindow(*this,mDragAndDrop); mTradeWindow = new TradeWindow(*this); mDialogueWindow = new DialogueWindow(*this); mContainerWindow = new ContainerWindow(*this,mDragAndDrop); - hud = new HUD(w,h, showFPSLevel, mDragAndDrop); + mHud = new HUD(w,h, mShowFPSLevel, mDragAndDrop); mToolTips = new ToolTips(this); mScrollWindow = new ScrollWindow(*this); mBookWindow = new BookWindow(*this); @@ -131,19 +131,19 @@ WindowManager::WindowManager( mSpellWindow = new SpellWindow(*this); // The HUD is always on - hud->setVisible(true); + mHud->setVisible(true); mCharGen = new CharacterCreation(this); // Setup player stats for (int i = 0; i < ESM::Attribute::Length; ++i) { - playerAttributes.insert(std::make_pair(ESM::Attribute::attributeIds[i], MWMechanics::Stat())); + mPlayerAttributes.insert(std::make_pair(ESM::Attribute::attributeIds[i], MWMechanics::Stat())); } for (int i = 0; i < ESM::Skill::Length; ++i) { - playerSkillValues.insert(std::make_pair(ESM::Skill::skillIds[i], MWMechanics::Stat())); + mPlayerSkillValues.insert(std::make_pair(ESM::Skill::skillIds[i], MWMechanics::Stat())); } unsetSelectedSpell(); @@ -156,11 +156,11 @@ WindowManager::WindowManager( WindowManager::~WindowManager() { delete mGuiManager; - delete console; + delete mConsole; delete mMessageBoxManager; - delete hud; - delete map; - delete menu; + delete mHud; + delete mMap; + delete mMenu; delete mStatsWindow; delete mJournal; delete mDialogueWindow; @@ -183,13 +183,13 @@ WindowManager::~WindowManager() void WindowManager::cleanupGarbage() { // Delete any dialogs which are no longer in use - if (!garbageDialogs.empty()) + if (!mGarbageDialogs.empty()) { - for (std::vector::iterator it = garbageDialogs.begin(); it != garbageDialogs.end(); ++it) + for (std::vector::iterator it = mGarbageDialogs.begin(); it != mGarbageDialogs.end(); ++it) { delete *it; } - garbageDialogs.clear(); + mGarbageDialogs.clear(); } } @@ -197,18 +197,18 @@ void WindowManager::update() { cleanupGarbage(); - hud->setFPS(mFPS); - hud->setTriangleCount(mTriangleCount); - hud->setBatchCount(mBatchCount); + mHud->setFPS(mFPS); + mHud->setTriangleCount(mTriangleCount); + mHud->setBatchCount(mBatchCount); } void WindowManager::updateVisible() { // Start out by hiding everything except the HUD - map->setVisible(false); - menu->setVisible(false); + mMap->setVisible(false); + mMenu->setVisible(false); mStatsWindow->setVisible(false); - console->disable(); + mConsole->disable(); mJournal->setVisible(false); mDialogueWindow->setVisible(false); mContainerWindow->setVisible(false); @@ -230,10 +230,10 @@ void WindowManager::updateVisible() else mToolTips->enterGuiMode(); - setMinimapVisibility((allowed & GW_Map) && !map->pinned()); - setWeaponVisibility((allowed & GW_Inventory) && !mInventoryWindow->pinned()); - setSpellVisibility((allowed & GW_Magic) && !mSpellWindow->pinned()); - setHMSVisibility((allowed & GW_Stats) && !mStatsWindow->pinned()); + setMinimapVisibility((mAllowed & GW_Map) && !mMap->pinned()); + setWeaponVisibility((mAllowed & GW_Inventory) && !mInventoryWindow->pinned()); + setSpellVisibility((mAllowed & GW_Magic) && !mSpellWindow->pinned()); + setHMSVisibility((mAllowed & GW_Stats) && !mStatsWindow->pinned()); // If in game mode, don't show anything. if (gameMode) @@ -243,13 +243,13 @@ void WindowManager::updateVisible() switch(mode) { case GM_MainMenu: - menu->setVisible(true); + mMenu->setVisible(true); break; case GM_Settings: mSettingsWindow->setVisible(true); break; case GM_Console: - console->enable(); + mConsole->enable(); break; case GM_Scroll: mScrollWindow->setVisible(true); @@ -276,13 +276,13 @@ void WindowManager::updateVisible() // This is controlled both by what windows the // user has opened/closed (the 'shown' variable) and by what // windows we are allowed to show (the 'allowed' var.) - int eff = shown & allowed; + int eff = mShown & mAllowed; // Show the windows we want - map -> setVisible(eff & GW_Map); - mStatsWindow -> setVisible(eff & GW_Stats); + mMap ->setVisible(eff & GW_Map); + mStatsWindow ->setVisible(eff & GW_Stats); mInventoryWindow->setVisible(eff & GW_Inventory); - mSpellWindow->setVisible(eff & GW_Magic); + mSpellWindow ->setVisible(eff & GW_Magic); break; } case GM_Container: @@ -333,7 +333,7 @@ void WindowManager::setValue (const std::string& id, const MWMechanics::StatsetValue(parSkill, value); mCharGen->setValue(parSkill, value); - playerSkillValues[parSkill] = value; + mPlayerSkillValues[parSkill] = value; } void WindowManager::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { mStatsWindow->setValue (id, value); - hud->setValue (id, value); + mHud->setValue (id, value); mCharGen->setValue(id, value); if (id == "HBar") { - playerHealth = value; + mPlayerHealth = value; mCharGen->setPlayerHealth (value); } else if (id == "MBar") { - playerMagicka = value; + mPlayerMagicka = value; mCharGen->setPlayerMagicka (value); } else if (id == "FBar") { - playerFatigue = value; + mPlayerFatigue = value; mCharGen->setPlayerFatigue (value); } } @@ -372,11 +372,11 @@ void WindowManager::setValue (const std::string& id, const MWMechanics::DynamicS MWMechanics::DynamicStat WindowManager::getValue(const std::string& id) { if(id == "HBar") - return playerHealth; + return layerHealth; else if (id == "MBar") - return playerMagicka; + return mPlayerMagicka; else if (id == "FBar") - return playerFatigue; + return mPlayerFatigue; } #endif @@ -384,9 +384,9 @@ void WindowManager::setValue (const std::string& id, const std::string& value) { mStatsWindow->setValue (id, value); if (id=="name") - playerName = value; + mPlayerName = value; else if (id=="race") - playerRaceId = value; + mPlayerRaceId = value; } void WindowManager::setValue (const std::string& id, int value) @@ -396,16 +396,16 @@ void WindowManager::setValue (const std::string& id, int value) void WindowManager::setPlayerClass (const ESM::Class &class_) { - playerClass = class_; - mStatsWindow->setValue("class", playerClass.name); + mPlayerClass = class_; + mStatsWindow->setValue("class", mPlayerClass.name); } void WindowManager::configureSkills (const SkillList& major, const SkillList& minor) { mStatsWindow->configureSkills (major, minor); mCharGen->configureSkills(major, minor); - playerMajorSkills = major; - playerMinorSkills = minor; + mPlayerMajorSkills = major; + mPlayerMinorSkills = minor; } void WindowManager::setReputation (int reputation) @@ -429,7 +429,7 @@ void WindowManager::removeDialog(OEngine::GUI::Layout*dialog) if (!dialog) return; dialog->setVisible(false); - garbageDialogs.push_back(dialog); + mGarbageDialogs.push_back(dialog); } void WindowManager::messageBox (const std::string& message, const std::vector& buttons) @@ -484,12 +484,12 @@ void WindowManager::onFrame (float frameDuration) mStatsWindow->onFrame(); - hud->onFrame(frameDuration); + mHud->onFrame(frameDuration); mDialogueWindow->checkReferenceAvailable(); mTradeWindow->checkReferenceAvailable(); mContainerWindow->checkReferenceAvailable(); - console->checkReferenceAvailable(); + mConsole->checkReferenceAvailable(); } const ESMS::ESMStore& WindowManager::getStore() const @@ -513,56 +513,56 @@ void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) name = getGameSettingString("sDefaultCellname", "Wilderness"); } - map->setCellName( name ); - hud->setCellName( name ); + mMap->setCellName( name ); + mHud->setCellName( name ); - map->setCellPrefix("Cell"); - hud->setCellPrefix("Cell"); - map->setActiveCell( cell->cell->data.gridX, cell->cell->data.gridY ); - hud->setActiveCell( cell->cell->data.gridX, cell->cell->data.gridY ); + mMap->setCellPrefix("Cell"); + mHud->setCellPrefix("Cell"); + mMap->setActiveCell( cell->cell->data.gridX, cell->cell->data.gridY ); + mHud->setActiveCell( cell->cell->data.gridX, cell->cell->data.gridY ); } else { - map->setCellName( cell->cell->name ); - hud->setCellName( cell->cell->name ); - map->setCellPrefix( cell->cell->name ); - hud->setCellPrefix( cell->cell->name ); + mMap->setCellName( cell->cell->name ); + mHud->setCellName( cell->cell->name ); + mMap->setCellPrefix( cell->cell->name ); + mHud->setCellPrefix( cell->cell->name ); } } void WindowManager::setInteriorMapTexture(const int x, const int y) { - map->setActiveCell(x,y, true); - hud->setActiveCell(x,y, true); + mMap->setActiveCell(x,y, true); + mHud->setActiveCell(x,y, true); } void WindowManager::setPlayerPos(const float x, const float y) { - map->setPlayerPos(x,y); - hud->setPlayerPos(x,y); + mMap->setPlayerPos(x,y); + mHud->setPlayerPos(x,y); } void WindowManager::setPlayerDir(const float x, const float y) { - map->setPlayerDir(x,y); - hud->setPlayerDir(x,y); + mMap->setPlayerDir(x,y); + mHud->setPlayerDir(x,y); } void WindowManager::setHMSVisibility(bool visible) { - hud->setBottomLeftVisibility(visible, hud->weapBox->getVisible(), hud->spellBox->getVisible()); + mHud->setBottomLeftVisibility(visible, mHud->mWeapBox->getVisible(), mHud->mSpellBox->getVisible()); } void WindowManager::setMinimapVisibility(bool visible) { - hud->setBottomRightVisibility(hud->effectBox->getVisible(), visible); + mHud->setBottomRightVisibility(mHud->mEffectBox->getVisible(), visible); } void WindowManager::toggleFogOfWar() { - map->toggleFogOfWar(); - hud->toggleFogOfWar(); + mMap->toggleFogOfWar(); + mHud->toggleFogOfWar(); } void WindowManager::setFocusObject(const MWWorld::Ptr& focus) @@ -587,13 +587,13 @@ bool WindowManager::getFullHelp() const void WindowManager::setWeaponVisibility(bool visible) { - hud->setBottomLeftVisibility(hud->health->getVisible(), visible, hud->spellBox->getVisible()); + mHud->setBottomLeftVisibility(mHud->health->getVisible(), visible, mHud->mSpellBox->getVisible()); } void WindowManager::setSpellVisibility(bool visible) { - hud->setBottomLeftVisibility(hud->health->getVisible(), hud->weapBox->getVisible(), visible); - hud->setBottomRightVisibility(visible, hud->minimapBox->getVisible()); + mHud->setBottomLeftVisibility(mHud->health->getVisible(), mHud->mWeapBox->getVisible(), visible); + mHud->setBottomRightVisibility(visible, mHud->mMinimapBox->getVisible()); } void WindowManager::setMouseVisible(bool visible) @@ -618,7 +618,7 @@ void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _r void WindowManager::processChangedSettings(const Settings::CategorySettingVector& changed) { - hud->setFpsLevel(Settings::Manager::getInt("fps", "HUD")); + mHud->setFpsLevel(Settings::Manager::getInt("fps", "HUD")); mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); bool changeRes = false; @@ -637,8 +637,8 @@ void WindowManager::processChangedSettings(const Settings::CategorySettingVector { int x = Settings::Manager::getInt("resolution x", "Video"); int y = Settings::Manager::getInt("resolution y", "Video"); - hud->onResChange(x, y); - console->onResChange(x, y); + mHud->onResChange(x, y); + mConsole->onResChange(x, y); mSettingsWindow->center(); mAlchemyWindow->center(); mScrollWindow->center(); @@ -649,7 +649,7 @@ void WindowManager::processChangedSettings(const Settings::CategorySettingVector void WindowManager::pushGuiMode(GuiMode mode) { - if (mode==GM_Inventory && allowed==GW_None) + if (mode==GM_Inventory && mAllowed==GW_None) return; mGuiModes.push_back(mode); @@ -690,32 +690,32 @@ void WindowManager::removeGuiMode(GuiMode mode) void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent) { - hud->setSelectedSpell(spellId, successChancePercent); + mHud->setSelectedSpell(spellId, successChancePercent); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); mSpellWindow->setTitle(spell->name); } void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) { - hud->setSelectedEnchantItem(item, chargePercent); + mHud->setSelectedEnchantItem(item, chargePercent); mSpellWindow->setTitle(MWWorld::Class::get(item).getName(item)); } void WindowManager::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) { - hud->setSelectedWeapon(item, durabilityPercent); + mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(MWWorld::Class::get(item).getName(item)); } void WindowManager::unsetSelectedSpell() { - hud->unsetSelectedSpell(); + mHud->unsetSelectedSpell(); mSpellWindow->setTitle("#{sNone}"); } void WindowManager::unsetSelectedWeapon() { - hud->unsetSelectedWeapon(); + mHud->unsetSelectedWeapon(); mInventoryWindow->setTitle("#{sSkillHandtohand}"); } @@ -738,5 +738,5 @@ void WindowManager::getMousePosition(float &x, float &y) bool WindowManager::getWorldMouseOver() { - return hud->getWorldMouseOver(); + return mHud->getWorldMouseOver(); } diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index 03ffa6b59..db9505493 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -123,27 +123,27 @@ namespace MWGui void toggleVisible(GuiWindow wnd) { - shown = (shown & wnd) ? (GuiWindow) (shown & ~wnd) : (GuiWindow) (shown | wnd); + mShown = (mShown & wnd) ? (GuiWindow) (mShown & ~wnd) : (GuiWindow) (mShown | wnd); updateVisible(); } // Disallow all inventory mode windows void disallowAll() { - allowed = GW_None; + mAllowed = GW_None; updateVisible(); } // Allow one or more windows void allow(GuiWindow wnd) { - allowed = (GuiWindow)(allowed | wnd); + mAllowed = (GuiWindow)(mAllowed | wnd); updateVisible(); } bool isAllowed(GuiWindow wnd) const { - return allowed & wnd; + return mAllowed & wnd; } MWGui::DialogueWindow* getDialogueWindow() {return mDialogueWindow;} @@ -155,9 +155,9 @@ namespace MWGui MWGui::ConfirmationDialog* getConfirmationDialog() {return mConfirmationDialog;} MWGui::TradeWindow* getTradeWindow() {return mTradeWindow;} MWGui::SpellWindow* getSpellWindow() {return mSpellWindow;} - MWGui::Console* getConsole() {return console;} + MWGui::Console* getConsole() {return mConsole;} - MyGUI::Gui* getGui() const { return gui; } + MyGUI::Gui* getGui() const { return mGui; } void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount) { @@ -223,10 +223,10 @@ namespace MWGui void onFrame (float frameDuration); - std::map > getPlayerSkillValues() { return playerSkillValues; } - std::map > getPlayerAttributeValues() { return playerAttributes; } - SkillList getPlayerMinorSkills() { return playerMinorSkills; } - SkillList getPlayerMajorSkills() { return playerMajorSkills; } + std::map > getPlayerSkillValues() { return mPlayerSkillValues; } + std::map > getPlayerAttributeValues() { return mPlayerAttributes; } + SkillList getPlayerMinorSkills() { return mPlayerMinorSkills; } + SkillList getPlayerMajorSkills() { return mPlayerMajorSkills; } /** * Fetches a GMST string from the store, if there is no setting with the given @@ -243,13 +243,13 @@ namespace MWGui private: OEngine::GUI::MyGUIManager *mGuiManager; - HUD *hud; - MapWindow *map; - MainMenu *menu; + HUD *mHud; + MapWindow *mMap; + MainMenu *mMenu; ToolTips *mToolTips; StatsWindow *mStatsWindow; MessageBoxManager *mMessageBoxManager; - Console *console; + Console *mConsole; JournalWindow* mJournal; DialogueWindow *mDialogueWindow; ContainerWindow *mContainerWindow; @@ -267,33 +267,33 @@ namespace MWGui CharacterCreation* mCharGen; // Various stats about player as needed by window manager - ESM::Class playerClass; - std::string playerName; - std::string playerRaceId; - std::map > playerAttributes; - SkillList playerMajorSkills, playerMinorSkills; - std::map > playerSkillValues; - MWMechanics::DynamicStat playerHealth, playerMagicka, playerFatigue; + ESM::Class mPlayerClass; + std::string mPlayerName; + std::string mPlayerRaceId; + std::map > mPlayerAttributes; + SkillList mPlayerMajorSkills, mPlayerMinorSkills; + std::map > mPlayerSkillValues; + MWMechanics::DynamicStat mPlayerHealth, mPlayerMagicka, mPlayerFatigue; - MyGUI::Gui *gui; // Gui + MyGUI::Gui *mGui; // Gui std::vector mGuiModes; - std::vector garbageDialogs; + std::vector mGarbageDialogs; void cleanupGarbage(); - GuiWindow shown; // Currently shown windows in inventory mode + GuiWindow mShown; // Currently shown windows in inventory mode /* Currently ALLOWED windows in inventory mode. This is used at the start of the game, when windows are enabled one by one through script commands. You can manipulate this through using allow() and disableAll(). */ - GuiWindow allowed; + GuiWindow mAllowed; void updateVisible(); // Update visibility of all windows based on mode, shown and allowed settings - int showFPSLevel; + int mShowFPSLevel; float mFPS; unsigned int mTriangleCount; unsigned int mBatchCount; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f0a6ab683..549dbf6b2 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -6,25 +6,26 @@ #include #include -namespace MWRender{ - std::map Animation::mUniqueIDs; +namespace MWRender +{ + std::map Animation::sUniqueIDs; Animation::Animation(OEngine::Render::OgreRenderer& _rend) - : insert(NULL) + : mInsert(NULL) , mRend(_rend) - , vecRotPos() - , time(0.0f) - , startTime(0.0f) - , stopTime(0.0f) - , animate(0) - , rindexI() - , tindexI() - , shapeNumber(0) - , shapeIndexI() - , shapes(NULL) - , transformations(NULL) - , textmappings(NULL) - , base(NULL) + , mVecRotPos() + , mTime(0.0f) + , mStartTime(0.0f) + , mStopTime(0.0f) + , mAnimate(0) + , mRindexI() + , mTindexI() + , mShapeNumber(0) + , mShapeIndexI() + , mShapes(NULL) + , mTransformations(NULL) + , mTextmappings(NULL) + , mBase(NULL) { } @@ -32,178 +33,191 @@ namespace MWRender{ { } - std::string Animation::getUniqueID(std::string mesh){ - int counter; - std::string copy = mesh; - std::transform(copy.begin(), copy.end(), copy.begin(), ::tolower); - if(mUniqueIDs.find(copy) == mUniqueIDs.end()){ - counter = mUniqueIDs[copy] = 0; - } - else{ - mUniqueIDs[copy] = mUniqueIDs[copy] + 1; - counter = mUniqueIDs[copy]; - } + std::string Animation::getUniqueID(std::string mesh) + { + int counter; + std::string copy = mesh; + std::transform(copy.begin(), copy.end(), copy.begin(), ::tolower); + + if(sUniqueIDs.find(copy) == sUniqueIDs.end()) + { + counter = sUniqueIDs[copy] = 0; + } + else + { + sUniqueIDs[copy] = sUniqueIDs[copy] + 1; + counter = sUniqueIDs[copy]; + } - std::stringstream out; - if(counter > 99 && counter < 1000) - out << "0"; - else if(counter > 9) - out << "00"; - else - out << "000"; - out << counter; - return out.str(); -} - void Animation::startScript(std::string groupname, int mode, int loops){ - //If groupname is recognized set animate to true - //Set the start time and stop time - //How many times to loop - if(groupname == "all"){ - animate = loops; - time = startTime; + std::stringstream out; + + if(counter > 99 && counter < 1000) + out << "0"; + else if(counter > 9) + out << "00"; + else + out << "000"; + out << counter; + + return out.str(); + } + + void Animation::startScript(std::string groupname, int mode, int loops) + { + //If groupname is recognized set animate to true + //Set the start time and stop time + //How many times to loop + if(groupname == "all") + { + mAnimate = loops; + mTime = mStartTime; } - else if(textmappings){ + else if(mTextmappings) + { std::string startName = groupname + ": loop start"; std::string stopName = groupname + ": loop stop"; bool first = false; - if(loops > 1){ + if(loops > 1) + { startName = groupname + ": loop start"; stopName = groupname + ": loop stop"; - for(std::map::iterator iter = textmappings->begin(); iter != textmappings->end(); iter++){ - - std::string current = iter->first.substr(0, startName.size()); - std::transform(current.begin(), current.end(), current.begin(), ::tolower); - std::string current2 = iter->first.substr(0, stopName.size()); - std::transform(current2.begin(), current2.end(), current2.begin(), ::tolower); + for(std::map::iterator iter = mTextmappings->begin(); iter != mTextmappings->end(); iter++) + { - if(current == startName){ - startTime = iter->second; - animate = loops; - time = startTime; - first = true; - } - if(current2 == stopName){ - stopTime = iter->second; - if(first) - break; - } + std::string current = iter->first.substr(0, startName.size()); + std::transform(current.begin(), current.end(), current.begin(), ::tolower); + std::string current2 = iter->first.substr(0, stopName.size()); + std::transform(current2.begin(), current2.end(), current2.begin(), ::tolower); + + if(current == startName) + { + mStartTime = iter->second; + mAnimate = loops; + mTime = mStartTime; + first = true; + } + if(current2 == stopName) + { + mStopTime = iter->second; + if(first) + break; + } } } - if(!first){ + if(!first) + { startName = groupname + ": start"; stopName = groupname + ": stop"; - for(std::map::iterator iter = textmappings->begin(); iter != textmappings->end(); iter++){ - - std::string current = iter->first.substr(0, startName.size()); - std::transform(current.begin(), current.end(), current.begin(), ::tolower); - std::string current2 = iter->first.substr(0, stopName.size()); - std::transform(current2.begin(), current2.end(), current2.begin(), ::tolower); + for(std::map::iterator iter = mTextmappings->begin(); iter != mTextmappings->end(); iter++) + { - if(current == startName){ - startTime = iter->second; - animate = loops; - time = startTime; - first = true; - } - if(current2 == stopName){ - stopTime = iter->second; - if(first) - break; + std::string current = iter->first.substr(0, startName.size()); + std::transform(current.begin(), current.end(), current.begin(), ::tolower); + std::string current2 = iter->first.substr(0, stopName.size()); + std::transform(current2.begin(), current2.end(), current2.begin(), ::tolower); + + if(current == startName) + { + mStartTime = iter->second; + mAnimate = loops; + mTime = mStartTime; + first = true; + } + if(current2 == stopName) + { + mStopTime = iter->second; + if(first) + break; + } } } - } - } - - } - void Animation::stopScript(){ - animate = 0; } - - void Animation::handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel){ - shapeNumber = 0; + + void Animation::stopScript() + { + mAnimate = 0; + } + + void Animation::handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel) + { + mShapeNumber = 0; if (allshapes == NULL || creaturemodel == NULL || skel == NULL) - { return; - } std::vector::iterator allshapesiter; - for(allshapesiter = allshapes->begin(); allshapesiter != allshapes->end(); allshapesiter++) + for(allshapesiter = allshapes->begin(); allshapesiter != allshapes->end(); allshapesiter++) + { + //std::map vecPosRot; - { - //std::map vecPosRot; + Nif::NiTriShapeCopy& copy = *allshapesiter; + std::vector* allvertices = ©.vertices; - Nif::NiTriShapeCopy& copy = *allshapesiter; - std::vector* allvertices = ©.vertices; + //std::set vertices; + //std::set normals; + //std::vector boneinfovector = copy.boneinfo; + std::map >* verticesToChange = ©.vertsToWeights; + //std::cout << "Name " << copy.sname << "\n"; + Ogre::HardwareVertexBufferSharedPtr vbuf = creaturemodel->getMesh()->getSubMesh(copy.sname)->vertexData->vertexBufferBinding->getBuffer(0); + Ogre::Real* pReal = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_NORMAL)); - //std::set vertices; - //std::set normals; - //std::vector boneinfovector = copy.boneinfo; - std::map >* verticesToChange = ©.vertsToWeights; + std::vector initialVertices = copy.morph.getInitialVertices(); + //Each shape has multiple indices + if(initialVertices.size() ) + { + if(copy.vertices.size() == initialVertices.size()) + { + //Create if it doesn't already exist + if(mShapeIndexI.size() == static_cast (mShapeNumber)) + { + std::vector vec; + mShapeIndexI.push_back(vec); + } + if(mTime >= copy.morph.getStartTime() && mTime <= copy.morph.getStopTime()) + { + float x; + for (unsigned int i = 0; i < copy.morph.getAdditionalVertices().size(); i++) + { + int j = 0; + if(mShapeIndexI[mShapeNumber].size() <= i) + mShapeIndexI[mShapeNumber].push_back(0); + + if(timeIndex(mTime,copy.morph.getRelevantTimes()[i],(mShapeIndexI[mShapeNumber])[i], j, x)) + { + int indexI = (mShapeIndexI[mShapeNumber])[i]; + std::vector relevantData = (copy.morph.getRelevantData()[i]); + float v1 = relevantData[indexI].x; + float v2 = relevantData[j].x; + float t = v1 + (v2 - v1) * x; + + if ( t < 0 ) + t = 0; + if ( t > 1 ) + t = 1; + if( t != 0 && initialVertices.size() == copy.morph.getAdditionalVertices()[i].size()) + for (unsigned int v = 0; v < initialVertices.size(); v++) + initialVertices[v] += ((copy.morph.getAdditionalVertices()[i])[v]) * t; + } - //std::cout << "Name " << copy.sname << "\n"; - Ogre::HardwareVertexBufferSharedPtr vbuf = creaturemodel->getMesh()->getSubMesh(copy.sname)->vertexData->vertexBufferBinding->getBuffer(0); - Ogre::Real* pReal = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_NORMAL)); - - - std::vector initialVertices = copy.morph.getInitialVertices(); - //Each shape has multiple indices - if(initialVertices.size() ) - { - - if(copy.vertices.size() == initialVertices.size()) - { - //Create if it doesn't already exist - if(shapeIndexI.size() == static_cast (shapeNumber)) - { - std::vector vec; - shapeIndexI.push_back(vec); - } - if(time >= copy.morph.getStartTime() && time <= copy.morph.getStopTime()){ - float x; - for (unsigned int i = 0; i < copy.morph.getAdditionalVertices().size(); i++){ - int j = 0; - if(shapeIndexI[shapeNumber].size() <= i) - shapeIndexI[shapeNumber].push_back(0); - - - if(timeIndex(time,copy.morph.getRelevantTimes()[i],(shapeIndexI[shapeNumber])[i], j, x)){ - int indexI = (shapeIndexI[shapeNumber])[i]; - std::vector relevantData = (copy.morph.getRelevantData()[i]); - float v1 = relevantData[indexI].x; - float v2 = relevantData[j].x; - float t = v1 + (v2 - v1) * x; - if ( t < 0 ) t = 0; - if ( t > 1 ) t = 1; - if( t != 0 && initialVertices.size() == copy.morph.getAdditionalVertices()[i].size()) - { - for (unsigned int v = 0; v < initialVertices.size(); v++){ - initialVertices[v] += ((copy.morph.getAdditionalVertices()[i])[v]) * t; - } - } - - } - - - - } - - allvertices = &initialVertices; } - shapeNumber++; - } - } + + allvertices = &initialVertices; + } + mShapeNumber++; + } + } - if(verticesToChange->size() > 0){ + if(verticesToChange->size() > 0) + { for(std::map >::iterator iter = verticesToChange->begin(); iter != verticesToChange->end(); iter++) @@ -214,26 +228,25 @@ namespace MWRender{ Nif::NiSkinData::BoneInfoCopy* boneinfocopy = &(allshapesiter->boneinfo[inds[0].boneinfocopyindex]); Ogre::Bone *bonePtr = 0; - - Ogre::Vector3 vecPos; Ogre::Quaternion vecRot; - std::map::iterator result = vecRotPos.find(boneinfocopy); + std::map::iterator result = mVecRotPos.find(boneinfocopy); - if(result == vecRotPos.end()){ + if(result == mVecRotPos.end()) + { bonePtr = skel->getBone(boneinfocopy->bonename); vecPos = bonePtr->_getDerivedPosition() + bonePtr->_getDerivedOrientation() * boneinfocopy->trafo.trans; vecRot = bonePtr->_getDerivedOrientation() * boneinfocopy->trafo.rotation; - - PosAndRot both; - both.vecPos = vecPos; - both.vecRot = vecRot; - vecRotPos[boneinfocopy] = both; + PosAndRot both; + both.vecPos = vecPos; + both.vecRot = vecRot; + mVecRotPos[boneinfocopy] = both; } - else{ + else + { PosAndRot both = result->second; vecPos = both.vecPos; vecRot = both.vecRot; @@ -241,263 +254,249 @@ namespace MWRender{ Ogre::Vector3 absVertPos = (vecPos + vecRot * currentVertex) * inds[0].weight; - - - for(std::size_t i = 1; i < inds.size(); i++){ + for(std::size_t i = 1; i < inds.size(); i++) + { boneinfocopy = &(allshapesiter->boneinfo[inds[i].boneinfocopyindex]); - result = vecRotPos.find(boneinfocopy); + result = mVecRotPos.find(boneinfocopy); - - if(result == vecRotPos.end()){ + if(result == mVecRotPos.end()) + { bonePtr = skel->getBone(boneinfocopy->bonename); vecPos = bonePtr->_getDerivedPosition() + bonePtr->_getDerivedOrientation() * boneinfocopy->trafo.trans; vecRot = bonePtr->_getDerivedOrientation() * boneinfocopy->trafo.rotation; - PosAndRot both; - both.vecPos = vecPos; - both.vecRot = vecRot; - vecRotPos[boneinfocopy] = both; + PosAndRot both; + both.vecPos = vecPos; + both.vecRot = vecRot; + mVecRotPos[boneinfocopy] = both; } - else{ - PosAndRot both = result->second; - vecPos = both.vecPos; - vecRot = both.vecRot; + else + { + PosAndRot both = result->second; + vecPos = both.vecPos; + vecRot = both.vecRot; } - absVertPos += (vecPos + vecRot * currentVertex) * inds[i].weight; - } - Ogre::Real* addr = (pReal + 3 * verIndex); - *addr = absVertPos.x; - *(addr+1) = absVertPos.y; - *(addr+2) = absVertPos.z; + Ogre::Real* addr = (pReal + 3 * verIndex); + *addr = absVertPos.x; + *(addr+1) = absVertPos.y; + *(addr+2) = absVertPos.z; } + } + else + { + //Ogre::Bone *bonePtr = creaturemodel->getSkeleton()->getBone(copy.bonename); + Ogre::Quaternion shaperot = copy.trafo.rotation; + Ogre::Vector3 shapetrans = copy.trafo.trans; + float shapescale = copy.trafo.scale; + std::vector boneSequence = copy.boneSequence; + + Ogre::Vector3 transmult; + Ogre::Quaternion rotmult; + float scale; + if(boneSequence.size() > 0) + { + std::vector::iterator boneSequenceIter = boneSequence.begin(); + if(skel->hasBone(*boneSequenceIter)) + { + Ogre::Bone *bonePtr = skel->getBone(*boneSequenceIter); + + transmult = bonePtr->getPosition(); + rotmult = bonePtr->getOrientation(); + scale = bonePtr->getScale().x; + boneSequenceIter++; + + for(; boneSequenceIter != boneSequence.end(); boneSequenceIter++) + { + if(skel->hasBone(*boneSequenceIter)) + { + Ogre::Bone *bonePtr = skel->getBone(*boneSequenceIter); + // Computes C = B + AxC*scale + transmult = transmult + rotmult * bonePtr->getPosition(); + rotmult = rotmult * bonePtr->getOrientation(); + scale = scale * bonePtr->getScale().x; + } + //std::cout << "Bone:" << *boneSequenceIter << " "; + } + transmult = transmult + rotmult * shapetrans; + rotmult = rotmult * shaperot; + scale = shapescale * scale; - - - - } - else - { - //Ogre::Bone *bonePtr = creaturemodel->getSkeleton()->getBone(copy.bonename); - Ogre::Quaternion shaperot = copy.trafo.rotation; - Ogre::Vector3 shapetrans = copy.trafo.trans; - float shapescale = copy.trafo.scale; - std::vector boneSequence = copy.boneSequence; - - Ogre::Vector3 transmult; - Ogre::Quaternion rotmult; - float scale; - if(boneSequence.size() > 0){ - std::vector::iterator boneSequenceIter = boneSequence.begin(); - if(skel->hasBone(*boneSequenceIter)){ - Ogre::Bone *bonePtr = skel->getBone(*boneSequenceIter); - - - - - transmult = bonePtr->getPosition(); - rotmult = bonePtr->getOrientation(); - scale = bonePtr->getScale().x; - boneSequenceIter++; - - for(; boneSequenceIter != boneSequence.end(); boneSequenceIter++) - { - if(skel->hasBone(*boneSequenceIter)){ - Ogre::Bone *bonePtr = skel->getBone(*boneSequenceIter); - // Computes C = B + AxC*scale - transmult = transmult + rotmult * bonePtr->getPosition(); - rotmult = rotmult * bonePtr->getOrientation(); - scale = scale * bonePtr->getScale().x; - } - //std::cout << "Bone:" << *boneSequenceIter << " "; - } - transmult = transmult + rotmult * shapetrans; - rotmult = rotmult * shaperot; - scale = shapescale * scale; - - //std::cout << "Position: " << transmult << "Rotation: " << rotmult << "\n"; - } + //std::cout << "Position: " << transmult << "Rotation: " << rotmult << "\n"; } - else - { - transmult = shapetrans; - rotmult = shaperot; - scale = shapescale; - } - - - - - // Computes C = B + AxC*scale - // final_vector = old_vector + old_rotation*new_vector*old_scale/ - - for(unsigned int i = 0; i < allvertices->size(); i++){ - Ogre::Vector3 current = transmult + rotmult * (*allvertices)[i]; - Ogre::Real* addr = pReal + i * 3; - *addr = current.x; - *(addr+1) = current.y; - *(addr + 2) = current.z; - - }/* - for(int i = 0; i < allnormals.size(); i++){ - Ogre::Vector3 current =rotmult * allnormals[i]; - Ogre::Real* addr = pRealNormal + i * 3; - *addr = current.x; - *(addr+1) = current.y; - *(addr + 2) = current.z; - - }*/ + } + else + { + transmult = shapetrans; + rotmult = shaperot; + scale = shapescale; + } - } - vbuf->unlock(); + // Computes C = B + AxC*scale + // final_vector = old_vector + old_rotation*new_vector*old_scale/ - } + for(unsigned int i = 0; i < allvertices->size(); i++) + { + Ogre::Vector3 current = transmult + rotmult * (*allvertices)[i]; + Ogre::Real* addr = pReal + i * 3; + *addr = current.x; + *(addr+1) = current.y; + *(addr + 2) = current.z; + + }/* + for(int i = 0; i < allnormals.size(); i++){ + Ogre::Vector3 current =rotmult * allnormals[i]; + Ogre::Real* addr = pRealNormal + i * 3; + *addr = current.x; + *(addr+1) = current.y; + *(addr + 2) = current.z; + + }*/ + } + vbuf->unlock(); + } + } - bool Animation::timeIndex( float time, const std::vector & times, int & i, int & j, float & x ){ - int count; - if ( (count = times.size()) > 0 ) - { - if ( time <= times[0] ) - { - i = j = 0; - x = 0.0; - return true; - } - if ( time >= times[count - 1] ) - { - i = j = count - 1; - x = 0.0; - return true; - } - - if ( i < 0 || i >= count ) - i = 0; - - float tI = times[i]; - if ( time > tI ) - { - j = i + 1; - float tJ; - while ( time >= ( tJ = times[j]) ) - { - i = j++; - tI = tJ; - } - x = ( time - tI ) / ( tJ - tI ); - return true; - } - else if ( time < tI ) - { - j = i - 1; - float tJ; - while ( time <= ( tJ = times[j] ) ) - { - i = j--; - tI = tJ; - } - x = ( time - tI ) / ( tJ - tI ); - return true; - } - else - { - j = i; - x = 0.0; - return true; - } - } - else - return false; - -} + + bool Animation::timeIndex( float time, const std::vector & times, int & i, int & j, float & x ) + { + int count; + if ( (count = times.size()) > 0 ) + { + if ( time <= times[0] ) + { + i = j = 0; + x = 0.0; + return true; + } + if ( time >= times[count - 1] ) + { + i = j = count - 1; + x = 0.0; + return true; + } - void Animation::handleAnimationTransforms(){ + if ( i < 0 || i >= count ) + i = 0; + float tI = times[i]; + if ( time > tI ) + { + j = i + 1; + float tJ; + while ( time >= ( tJ = times[j]) ) + { + i = j++; + tI = tJ; + } + x = ( time - tI ) / ( tJ - tI ); + return true; + } + else if ( time < tI ) + { + j = i - 1; + float tJ; + while ( time <= ( tJ = times[j] ) ) + { + i = j--; + tI = tJ; + } + x = ( time - tI ) / ( tJ - tI ); + return true; + } + else + { + j = i; + x = 0.0; + return true; + } + } + else + return false; - Ogre::SkeletonInstance* skel = base->getSkeleton(); + } + void Animation::handleAnimationTransforms() + { + Ogre::SkeletonInstance* skel = mBase->getSkeleton(); - Ogre::Bone* b = skel->getRootBone(); - b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3)); //This is a trick + Ogre::Bone* b = skel->getRootBone(); + b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3)); //This is a trick skel->_updateTransforms(); - //skel->_notifyManualBonesDirty(); - - base->getAllAnimationStates()->_notifyDirty(); - //base->_updateAnimation(); - //base->_notifyMoved(); - - - - - std::vector::iterator iter; - int slot = 0; - if(transformations){ - for(iter = transformations->begin(); iter != transformations->end(); iter++){ - if(time < iter->getStartTime() || time < startTime || time > iter->getStopTime()) - { - slot++; - continue; - } - - float x; - float x2; - - const std::vector & quats = iter->getQuat(); - - const std::vector & ttime = iter->gettTime(); - + //skel->_notifyManualBonesDirty(); - const std::vector & rtime = iter->getrTime(); - int rindexJ = rindexI[slot]; + mBase->getAllAnimationStates()->_notifyDirty(); + //mBase->_updateAnimation(); + //mBase->_notifyMoved(); - timeIndex(time, rtime, rindexI[slot], rindexJ, x2); - int tindexJ = tindexI[slot]; - - - const std::vector & translist1 = iter->getTranslist1(); - - timeIndex(time, ttime, tindexI[slot], tindexJ, x); + std::vector::iterator iter; + int slot = 0; + if(mTransformations) + { + for(iter = mTransformations->begin(); iter != mTransformations->end(); iter++) + { + if(mTime < iter->getStartTime() || mTime < mStartTime || mTime > iter->getStopTime()) + { + slot++; + continue; + } - Ogre::Vector3 t; - Ogre::Quaternion r; + float x; + float x2; - bool bTrans = translist1.size() > 0; + const std::vector & quats = iter->getQuat(); + const std::vector & ttime = iter->gettTime(); + const std::vector & rtime = iter->getrTime(); + int rindexJ = mRindexI[slot]; - bool bQuats = quats.size() > 0; + timeIndex(mTime, rtime, mRindexI[slot], rindexJ, x2); + int tindexJ = mTindexI[slot]; - if(skel->hasBone(iter->getBonename())){ - Ogre::Bone* bone = skel->getBone(iter->getBonename()); - if(bTrans){ - Ogre::Vector3 v1 = translist1[tindexI[slot]]; - Ogre::Vector3 v2 = translist1[tindexJ]; - t = (v1 + (v2 - v1) * x); - bone->setPosition(t); + const std::vector & translist1 = iter->getTranslist1(); - } - if(bQuats){ - r = Ogre::Quaternion::Slerp(x2, quats[rindexI[slot]], quats[rindexJ], true); - bone->setOrientation(r); - } + timeIndex(mTime, ttime, mTindexI[slot], tindexJ, x); + Ogre::Vector3 t; + Ogre::Quaternion r; + bool bTrans = translist1.size() > 0; + bool bQuats = quats.size() > 0; + if(skel->hasBone(iter->getBonename())) + { + Ogre::Bone* bone = skel->getBone(iter->getBonename()); + + if(bTrans) + { + Ogre::Vector3 v1 = translist1[mTindexI[slot]]; + Ogre::Vector3 v2 = translist1[tindexJ]; + t = (v1 + (v2 - v1) * x); + bone->setPosition(t); - } + } + + if(bQuats) + { + r = Ogre::Quaternion::Slerp(x2, quats[mRindexI[slot]], quats[rindexJ], true); + bone->setOrientation(r); + } + } - slot++; + slot++; + } + skel->_updateTransforms(); + mBase->getAllAnimationStates()->_notifyDirty(); + } } - skel->_updateTransforms(); - base->getAllAnimationStates()->_notifyDirty(); -} -} } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 4ab60cff4..cebffaaf2 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -22,36 +22,30 @@ struct PosAndRot{ class Animation{ protected: - Ogre::SceneNode* insert; + Ogre::SceneNode* mInsert; OEngine::Render::OgreRenderer &mRend; - std::map vecRotPos; - static std::map mUniqueIDs; - - - - - - float time; - float startTime; - float stopTime; - int animate; - //Represents a rotation index for each bone - std::vectorrindexI; + std::map mVecRotPos; + static std::map sUniqueIDs; + + float mTime; + float mStartTime; + float mStopTime; + int mAnimate; + //Represents a rotation index for each bone + std::vectormRindexI; //Represents a translation index for each bone - std::vectortindexI; - - //Only shapes with morphing data will use a shape number - int shapeNumber; - std::vector > shapeIndexI; + std::vectormTindexI; - //Ogre::SkeletonInstance* skel; - std::vector* shapes; //All the NiTriShapeData for a creature + //Only shapes with morphing data will use a shape number + int mShapeNumber; + std::vector > mShapeIndexI; + //Ogre::SkeletonInstance* skel; + std::vector* mShapes; //All the NiTriShapeData for a creature - - std::vector* transformations; - std::map* textmappings; - Ogre::Entity* base; + std::vector* mTransformations; + std::map* mTextmappings; + Ogre::Entity* mBase; void handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel); void handleAnimationTransforms(); bool timeIndex( float time, const std::vector & times, int & i, int & j, float & x ); @@ -63,7 +57,6 @@ class Animation{ void startScript(std::string groupname, int mode, int loops); void stopScript(); - virtual ~Animation(); }; diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index e1fa7868c..20037a773 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -12,26 +12,28 @@ using namespace Ogre; using namespace NifOgre; namespace MWRender{ -CreatureAnimation::~CreatureAnimation(){ +CreatureAnimation::~CreatureAnimation() +{ +} -} -CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend): Animation(_rend){ - insert = ptr.getRefData().getBaseNode(); - MWWorld::LiveCellRef *ref = - ptr.get(); +CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend): Animation(_rend) +{ + mInsert = ptr.getRefData().getBaseNode(); + MWWorld::LiveCellRef *ref = ptr.get(); assert (ref->base != NULL); - if(!ref->base->model.empty()){ + if(!ref->base->model.empty()) + { const std::string &mesh = "meshes\\" + ref->base->model; std::string meshNumbered = mesh + getUniqueID(mesh) + ">|"; NifOgre::NIFLoader::load(meshNumbered); - base = mRend.getScene()->createEntity(meshNumbered); - base->setVisibilityFlags(RV_Actors); + mBase = mRend.getScene()->createEntity(meshNumbered); + mBase->setVisibilityFlags(RV_Actors); bool transparent = false; - for (unsigned int i=0; igetNumSubEntities(); ++i) + for (unsigned int i=0; i < mBase->getNumSubEntities(); ++i) { - Ogre::MaterialPtr mat = base->getSubEntity(i)->getMaterial(); + Ogre::MaterialPtr mat = mBase->getSubEntity(i)->getMaterial(); Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); while (techIt.hasMoreElements()) { @@ -46,46 +48,51 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O } } } - base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + mBase->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); std::string meshZero = mesh + "0000>|"; - if((transformations = (NIFLoader::getSingletonPtr())->getAnim(meshZero))){ - - for(std::size_t init = 0; init < transformations->size(); init++){ - rindexI.push_back(0); - tindexI.push_back(0); - } - stopTime = transformations->begin()->getStopTime(); - startTime = transformations->begin()->getStartTime(); - shapes = (NIFLoader::getSingletonPtr())->getShapes(meshZero); + if((mTransformations = (NIFLoader::getSingletonPtr())->getAnim(meshZero))) + { + for(std::size_t init = 0; init < mTransformations->size(); init++) + { + mRindexI.push_back(0); + mTindexI.push_back(0); + } + mStopTime = mTransformations->begin()->getStopTime(); + mStartTime = mTransformations->begin()->getStartTime(); + mShapes = (NIFLoader::getSingletonPtr())->getShapes(meshZero); } - textmappings = NIFLoader::getSingletonPtr()->getTextIndices(meshZero); - insert->attachObject(base); + mTextmappings = NIFLoader::getSingletonPtr()->getTextIndices(meshZero); + mInsert->attachObject(mBase); } } -void CreatureAnimation::runAnimation(float timepassed){ - vecRotPos.clear(); - if(animate > 0){ - //Add the amount of time passed to time +void CreatureAnimation::runAnimation(float timepassed) +{ + mVecRotPos.clear(); + if(mAnimate > 0) + { + //Add the amount of time passed to time - //Handle the animation transforms dependent on time + //Handle the animation transforms dependent on time - //Handle the shapes dependent on animation transforms - time += timepassed; - if(time >= stopTime){ - animate--; + //Handle the shapes dependent on animation transforms + mTime += timepassed; + if(mTime >= mStopTime) + { + mAnimate--; //std::cout << "Stopping the animation\n"; - if(animate == 0) - time = stopTime; + if(mAnimate == 0) + mTime = mStopTime; else - time = startTime + (time - stopTime); + mTime = mStartTime + (mTime - mStopTime); } handleAnimationTransforms(); - handleShapes(shapes, base, base->getSkeleton()); + handleShapes(mShapes, mBase, mBase->getSkeleton()); - } + } } + } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index f50b7904b..d158eecb4 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -17,8 +17,7 @@ class CreatureAnimation: public Animation{ public: virtual ~CreatureAnimation(); CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend); - virtual void runAnimation(float timepassed); - + virtual void runAnimation(float timepassed); }; } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index fa88b7277..ef075b12b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -14,118 +14,117 @@ using namespace Ogre; using namespace NifOgre; namespace MWRender{ -NpcAnimation::~NpcAnimation(){ - +NpcAnimation::~NpcAnimation() +{ } -NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv): Animation(_rend), mStateID(-1), inv(_inv), timeToChange(0), - robe(inv.end()), helmet(inv.end()), shirt(inv.end()), - cuirass(inv.end()), greaves(inv.end()), - leftpauldron(inv.end()), rightpauldron(inv.end()), - boots(inv.end()), - leftglove(inv.end()), rightglove(inv.end()), skirtiter(inv.end()), - pants(inv.end()), +NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv): Animation(_rend), mStateID(-1), mInv(_inv), timeToChange(0), + robe(mInv.end()), helmet(mInv.end()), shirt(mInv.end()), + cuirass(mInv.end()), greaves(mInv.end()), + leftpauldron(mInv.end()), rightpauldron(mInv.end()), + boots(mInv.end()), + leftglove(mInv.end()), rightglove(mInv.end()), skirtiter(mInv.end()), + pants(mInv.end()), lclavicle(0), - rclavicle(0), - rupperArm(0), - lupperArm(0), - rUpperLeg(0), - lUpperLeg(0), - lForearm(0), - rForearm(0), - lWrist(0), - rWrist(0), - rKnee(0), - lKnee(0), - neck(0), - rAnkle(0), - lAnkle(0), - groin(0), - lfoot(0), - rfoot(0) - { - MWWorld::LiveCellRef *ref = - ptr.get(); - Ogre::Entity* blank = 0; - std::vector* blankshape = 0; - zero = std::make_pair(blank, blankshape); - chest = std::make_pair(blank, blankshape); - tail = std::make_pair(blank, blankshape); - lFreeFoot = std::make_pair(blank, blankshape); - rFreeFoot = std::make_pair(blank, blankshape); - rhand = std::make_pair(blank, blankshape); - lhand = std::make_pair(blank, blankshape); - skirt = std::make_pair(blank, blankshape); - for (int init = 0; init < 27; init++){ - partslots[init] = -1; //each slot is empty - partpriorities[init] = 0; - } - - - //Part selection on last character of the file string - // " Tri Chest - // * Tri Tail - // : Tri Left Foot - // < Tri Right Foot - // > Tri Left Hand - // ? Tri Right Hand - // | Normal - - //Mirroring Parts on second to last character - //suffix == '*' - // vector = Ogre::Vector3(-1,1,1); - // suffix == '?' - // vector = Ogre::Vector3(1,-1,1); - // suffix == '<' - // vector = Ogre::Vector3(1,1,-1); - - - std::string hairID = ref->base->hair; - std::string headID = ref->base->head; - headModel = "meshes\\" + - MWBase::Environment::get().getWorld()->getStore().bodyParts.find(headID)->model; - - hairModel = "meshes\\" + - MWBase::Environment::get().getWorld()->getStore().bodyParts.find(hairID)->model; - npcName = ref->base->name; - - //ESMStore::Races r = - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().races.find(ref->base->race); - - - bodyRaceID = headID.substr(0, headID.find_last_of("head_") - 4); - char secondtolast = bodyRaceID.at(bodyRaceID.length() - 2); - isFemale = tolower(secondtolast) == 'f'; - std::transform(bodyRaceID.begin(), bodyRaceID.end(), bodyRaceID.begin(), ::tolower); - isBeast = bodyRaceID == "b_n_khajiit_m_" || bodyRaceID == "b_n_khajiit_f_" || bodyRaceID == "b_n_argonian_m_" || bodyRaceID == "b_n_argonian_f_"; - - /*std::cout << "Race: " << ref->base->race ; - if(female){ - std::cout << " Sex: Female" << " Height: " << race->data.height.female << "\n"; - } - else{ - std::cout << " Sex: Male" << " Height: " << race->data.height.male << "\n"; - }*/ - - - - std::string smodel = "meshes\\base_anim.nif"; - if(isBeast) - smodel = "meshes\\base_animkna.nif"; - - insert = ptr.getRefData().getBaseNode(); - assert(insert); - - NifOgre::NIFLoader::load(smodel); - - base = mRend.getScene()->createEntity(smodel); - - base->setVisibilityFlags(RV_Actors); + rclavicle(0), + rupperArm(0), + lupperArm(0), + rUpperLeg(0), + lUpperLeg(0), + lForearm(0), + rForearm(0), + lWrist(0), + rWrist(0), + rKnee(0), + lKnee(0), + neck(0), + rAnkle(0), + lAnkle(0), + groin(0), + lfoot(0), + rfoot(0) +{ + MWWorld::LiveCellRef *ref = ptr.get(); + Ogre::Entity* blank = 0; + std::vector* blankshape = 0; + mZero = std::make_pair(blank, blankshape); + mChest = std::make_pair(blank, blankshape); + mTail = std::make_pair(blank, blankshape); + mLFreeFoot = std::make_pair(blank, blankshape); + mRFreeFoot = std::make_pair(blank, blankshape); + mRhand = std::make_pair(blank, blankshape); + mLhand = std::make_pair(blank, blankshape); + mSkirt = std::make_pair(blank, blankshape); + for (int init = 0; init < 27; init++) + { + mPartslots[init] = -1; //each slot is empty + mPartPriorities[init] = 0; + } + + + //Part selection on last character of the file string + // " Tri Chest + // * Tri Tail + // : Tri Left Foot + // < Tri Right Foot + // > Tri Left Hand + // ? Tri Right Hand + // | Normal + + //Mirroring Parts on second to last character + //suffix == '*' + // vector = Ogre::Vector3(-1,1,1); + // suffix == '?' + // vector = Ogre::Vector3(1,-1,1); + // suffix == '<' + // vector = Ogre::Vector3(1,1,-1); + + + std::string hairID = ref->base->hair; + std::string headID = ref->base->head; + headModel = "meshes\\" + + MWBase::Environment::get().getWorld()->getStore().bodyParts.find(headID)->model; + + hairModel = "meshes\\" + + MWBase::Environment::get().getWorld()->getStore().bodyParts.find(hairID)->model; + npcName = ref->base->name; + + //ESMStore::Races r = + const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().races.find(ref->base->race); + + + bodyRaceID = headID.substr(0, headID.find_last_of("head_") - 4); + char secondtolast = bodyRaceID.at(bodyRaceID.length() - 2); + isFemale = tolower(secondtolast) == 'f'; + std::transform(bodyRaceID.begin(), bodyRaceID.end(), bodyRaceID.begin(), ::tolower); + isBeast = bodyRaceID == "b_n_khajiit_m_" || bodyRaceID == "b_n_khajiit_f_" || bodyRaceID == "b_n_argonian_m_" || bodyRaceID == "b_n_argonian_f_"; + + /*std::cout << "Race: " << ref->base->race ; + if(female){ + std::cout << " Sex: Female" << " Height: " << race->data.height.female << "\n"; + } + else{ + std::cout << " Sex: Male" << " Height: " << race->data.height.male << "\n"; + }*/ + + + std::string smodel = "meshes\\base_anim.nif"; + if(isBeast) + smodel = "meshes\\base_animkna.nif"; + + mInsert = ptr.getRefData().getBaseNode(); + assert(mInsert); + + NifOgre::NIFLoader::load(smodel); + + mBase = mRend.getScene()->createEntity(smodel); + + mBase->setVisibilityFlags(RV_Actors); bool transparent = false; - for (unsigned int i=0; igetNumSubEntities(); ++i) + for (unsigned int i=0; igetNumSubEntities(); ++i) { - Ogre::MaterialPtr mat = base->getSubEntity(i)->getMaterial(); + Ogre::MaterialPtr mat = mBase->getSubEntity(i)->getMaterial(); Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); while (techIt.hasMoreElements()) { @@ -140,625 +139,694 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere } } } - base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + mBase->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - base->setSkipAnimationStateUpdate(true); //Magical line of code, this makes the bones + mBase->setSkipAnimationStateUpdate(true); //Magical line of code, this makes the bones //stay in the same place when we skipanim, or open a gui window + if((mTransformations = (NIFLoader::getSingletonPtr())->getAnim(smodel))) + { - if((transformations = (NIFLoader::getSingletonPtr())->getAnim(smodel))){ - - for(unsigned int init = 0; init < transformations->size(); init++){ - rindexI.push_back(0); - tindexI.push_back(0); - } + for(unsigned int init = 0; init < mTransformations->size(); init++) + { + mRindexI.push_back(0); + mTindexI.push_back(0); + } - stopTime = transformations->begin()->getStopTime(); - startTime = transformations->begin()->getStartTime(); + mStopTime = mTransformations->begin()->getStopTime(); + mStartTime = mTransformations->begin()->getStartTime(); } - textmappings = NIFLoader::getSingletonPtr()->getTextIndices(smodel); - insert->attachObject(base); + mTextmappings = NIFLoader::getSingletonPtr()->getTextIndices(smodel); + mInsert->attachObject(mBase); - if(isFemale) - insert->scale(race->data.height.female, race->data.height.female, race->data.height.female); - else - insert->scale(race->data.height.male, race->data.height.male, race->data.height.male); - updateParts(); + if(isFemale) + mInsert->scale(race->data.height.female, race->data.height.female, race->data.height.female); + else + mInsert->scale(race->data.height.male, race->data.height.male, race->data.height.male); + updateParts(); } -void NpcAnimation::updateParts(){ +void NpcAnimation::updateParts() +{ + + bool apparelChanged = false; - bool apparelChanged = false; + //mInv.getSlot(MWWorld::InventoryStore::Slot_Robe); + if(robe != mInv.getSlot(MWWorld::InventoryStore::Slot_Robe)) + { + //A robe was added or removed + removePartGroup(MWWorld::InventoryStore::Slot_Robe); + robe = mInv.getSlot(MWWorld::InventoryStore::Slot_Robe); + apparelChanged = true; + } + if(skirtiter != mInv.getSlot(MWWorld::InventoryStore::Slot_Skirt)) + { + //A robe was added or removed + removePartGroup(MWWorld::InventoryStore::Slot_Skirt); + skirtiter = mInv.getSlot(MWWorld::InventoryStore::Slot_Skirt); + apparelChanged = true; + } + if(helmet != mInv.getSlot(MWWorld::InventoryStore::Slot_Helmet)) + { + apparelChanged = true; + helmet = mInv.getSlot(MWWorld::InventoryStore::Slot_Helmet); + removePartGroup(MWWorld::InventoryStore::Slot_Helmet); + + } + if(cuirass != mInv.getSlot(MWWorld::InventoryStore::Slot_Cuirass)) + { + cuirass = mInv.getSlot(MWWorld::InventoryStore::Slot_Cuirass); + removePartGroup(MWWorld::InventoryStore::Slot_Cuirass); + apparelChanged = true; - //inv.getSlot(MWWorld::InventoryStore::Slot_Robe); - if(robe != inv.getSlot(MWWorld::InventoryStore::Slot_Robe)){ - //A robe was added or removed - removePartGroup(MWWorld::InventoryStore::Slot_Robe); - robe = inv.getSlot(MWWorld::InventoryStore::Slot_Robe); - apparelChanged = true; - } - if(skirtiter != inv.getSlot(MWWorld::InventoryStore::Slot_Skirt)){ - //A robe was added or removed - removePartGroup(MWWorld::InventoryStore::Slot_Skirt); - skirtiter = inv.getSlot(MWWorld::InventoryStore::Slot_Skirt); - apparelChanged = true; - } - if(helmet != inv.getSlot(MWWorld::InventoryStore::Slot_Helmet)){ - apparelChanged = true; - helmet = inv.getSlot(MWWorld::InventoryStore::Slot_Helmet); - removePartGroup(MWWorld::InventoryStore::Slot_Helmet); + } + if(greaves != mInv.getSlot(MWWorld::InventoryStore::Slot_Greaves)) + { + greaves = mInv.getSlot(MWWorld::InventoryStore::Slot_Greaves); + removePartGroup(MWWorld::InventoryStore::Slot_Greaves); + apparelChanged = true; + } + if(leftpauldron != mInv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron)) + { + leftpauldron = mInv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron); + removePartGroup(MWWorld::InventoryStore::Slot_LeftPauldron); + apparelChanged = true; - } - if(cuirass != inv.getSlot(MWWorld::InventoryStore::Slot_Cuirass)){ - cuirass = inv.getSlot(MWWorld::InventoryStore::Slot_Cuirass); - removePartGroup(MWWorld::InventoryStore::Slot_Cuirass); - apparelChanged = true; + } + if(rightpauldron != mInv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron)) + { + rightpauldron = mInv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron); + removePartGroup(MWWorld::InventoryStore::Slot_RightPauldron); + apparelChanged = true; - } - if(greaves != inv.getSlot(MWWorld::InventoryStore::Slot_Greaves)){ - greaves = inv.getSlot(MWWorld::InventoryStore::Slot_Greaves); - removePartGroup(MWWorld::InventoryStore::Slot_Greaves); - apparelChanged = true; - } - if(leftpauldron != inv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron)){ - leftpauldron = inv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron); - removePartGroup(MWWorld::InventoryStore::Slot_LeftPauldron); - apparelChanged = true; + } + if(!isBeast && boots != mInv.getSlot(MWWorld::InventoryStore::Slot_Boots)) + { + boots = mInv.getSlot(MWWorld::InventoryStore::Slot_Boots); + removePartGroup(MWWorld::InventoryStore::Slot_Boots); + apparelChanged = true; - } - if(rightpauldron != inv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron)){ - rightpauldron = inv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron); - removePartGroup(MWWorld::InventoryStore::Slot_RightPauldron); - apparelChanged = true; + } + if(leftglove != mInv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet)) + { + leftglove = mInv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet); + removePartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet); + apparelChanged = true; - } - if(!isBeast && boots != inv.getSlot(MWWorld::InventoryStore::Slot_Boots)){ - boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); - removePartGroup(MWWorld::InventoryStore::Slot_Boots); - apparelChanged = true; + } + if(rightglove != mInv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet)) + { + rightglove = mInv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet); + removePartGroup(MWWorld::InventoryStore::Slot_RightGauntlet); + apparelChanged = true; - } - if(leftglove != inv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet)){ - leftglove = inv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet); - removePartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet); - apparelChanged = true; + } + if(shirt != mInv.getSlot(MWWorld::InventoryStore::Slot_Shirt)) + { + shirt = mInv.getSlot(MWWorld::InventoryStore::Slot_Shirt); + removePartGroup(MWWorld::InventoryStore::Slot_Shirt); + apparelChanged = true; - } - if(rightglove != inv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet)){ - rightglove = inv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet); - removePartGroup(MWWorld::InventoryStore::Slot_RightGauntlet); - apparelChanged = true; + } + if(pants != mInv.getSlot(MWWorld::InventoryStore::Slot_Pants)) + { + pants = mInv.getSlot(MWWorld::InventoryStore::Slot_Pants); + removePartGroup(MWWorld::InventoryStore::Slot_Pants); + apparelChanged = true; + } + if(apparelChanged) + { + + if(robe != mInv.end()) + { + MWWorld::Ptr ptr = *robe; + + const ESM::Clothing *clothes = (ptr.get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Robe, 5, parts); + reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_Skirt, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RKnee, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LKnee, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RForearm, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LForearm, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_RPauldron, MWWorld::InventoryStore::Slot_Robe, 5); + reserveIndividualPart(ESM::PRT_LPauldron, MWWorld::InventoryStore::Slot_Robe, 5); + } + if(skirtiter != mInv.end()) + { + MWWorld::Ptr ptr = *skirtiter; + + const ESM::Clothing *clothes = (ptr.get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Skirt, 4, parts); + reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Skirt, 4); + reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Skirt, 4); + reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Skirt, 4); } - if(shirt != inv.getSlot(MWWorld::InventoryStore::Slot_Shirt)){ - shirt = inv.getSlot(MWWorld::InventoryStore::Slot_Shirt); - removePartGroup(MWWorld::InventoryStore::Slot_Shirt); - apparelChanged = true; + + if(helmet != mInv.end()) + { + removeIndividualPart(ESM::PRT_Hair); + const ESM::Armor *armor = (helmet->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Helmet, 3, parts); } - if(pants != inv.getSlot(MWWorld::InventoryStore::Slot_Pants)){ - pants = inv.getSlot(MWWorld::InventoryStore::Slot_Pants); - removePartGroup(MWWorld::InventoryStore::Slot_Pants); - apparelChanged = true; + if(cuirass != mInv.end()) + { + const ESM::Armor *armor = (cuirass->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Cuirass, 3, parts); } + if(greaves != mInv.end()) + { + const ESM::Armor *armor = (greaves->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Greaves, 3, parts); + } - if(apparelChanged){ + if(leftpauldron != mInv.end()) + { + const ESM::Armor *armor = (leftpauldron->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_LeftPauldron, 3, parts); - if(robe != inv.end()) - { - MWWorld::Ptr ptr = *robe; + } + if(rightpauldron != mInv.end()) + { + const ESM::Armor *armor = (rightpauldron->get())->base; + std::vector parts = armor->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_RightPauldron, 3, parts); - const ESM::Clothing *clothes = (ptr.get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Robe, 5, parts); - reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_Skirt, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LUpperarm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RKnee, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LKnee, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RForearm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LForearm, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_RPauldron, MWWorld::InventoryStore::Slot_Robe, 5); - reserveIndividualPart(ESM::PRT_LPauldron, MWWorld::InventoryStore::Slot_Robe, 5); - } - if(skirtiter != inv.end()) + } + if(!isBeast && boots != mInv.end()) + { + if(boots->getTypeName() == typeid(ESM::Clothing).name()) { - MWWorld::Ptr ptr = *skirtiter; - - const ESM::Clothing *clothes = (ptr.get())->base; + const ESM::Clothing *clothes = (boots->get())->base; std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Skirt, 4, parts); - reserveIndividualPart(ESM::PRT_Groin, MWWorld::InventoryStore::Slot_Skirt, 4); - reserveIndividualPart(ESM::PRT_RLeg, MWWorld::InventoryStore::Slot_Skirt, 4); - reserveIndividualPart(ESM::PRT_LLeg, MWWorld::InventoryStore::Slot_Skirt, 4); + addPartGroup(MWWorld::InventoryStore::Slot_Boots, 2, parts); } - - if(helmet != inv.end()){ - removeIndividualPart(ESM::PRT_Hair); - const ESM::Armor *armor = (helmet->get())->base; + else if(boots->getTypeName() == typeid(ESM::Armor).name()) + { + const ESM::Armor *armor = (boots->get())->base; std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Helmet, 3, parts); - + addPartGroup(MWWorld::InventoryStore::Slot_Boots, 3, parts); } - if(cuirass != inv.end()){ - const ESM::Armor *armor = (cuirass->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Cuirass, 3, parts); + } + if(leftglove != mInv.end()){ + if(leftglove->getTypeName() == typeid(ESM::Clothing).name()) + { + const ESM::Clothing *clothes = (leftglove->get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 2, parts); } - if(greaves != inv.end()){ - const ESM::Armor *armor = (greaves->get())->base; + else + { + const ESM::Armor *armor = (leftglove->get())->base; std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Greaves, 3, parts); - + addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 3, parts); } - if(leftpauldron != inv.end()){ - const ESM::Armor *armor = (leftpauldron->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_LeftPauldron, 3, parts); - + } + if(rightglove != mInv.end()){ + if(rightglove->getTypeName() == typeid(ESM::Clothing).name()) + { + const ESM::Clothing *clothes = (rightglove->get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 2, parts); } - if(rightpauldron != inv.end()){ - const ESM::Armor *armor = (rightpauldron->get())->base; + else + { + const ESM::Armor *armor = (rightglove->get())->base; std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_RightPauldron, 3, parts); - + addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 3, parts); } - if(!isBeast && boots != inv.end()){ - if(boots->getTypeName() == typeid(ESM::Clothing).name()){ - const ESM::Clothing *clothes = (boots->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Boots, 2, parts); - } - else if(boots->getTypeName() == typeid(ESM::Armor).name()) - { - const ESM::Armor *armor = (boots->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Boots, 3, parts); - } - } - if(leftglove != inv.end()){ - if(leftglove->getTypeName() == typeid(ESM::Clothing).name()){ - const ESM::Clothing *clothes = (leftglove->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 2, parts); - } - else - { - const ESM::Armor *armor = (leftglove->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet, 3, parts); - } - - } - if(rightglove != inv.end()){ - if(rightglove->getTypeName() == typeid(ESM::Clothing).name()){ - const ESM::Clothing *clothes = (rightglove->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 2, parts); - } - else - { - const ESM::Armor *armor = (rightglove->get())->base; - std::vector parts = armor->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_RightGauntlet, 3, parts); - } - - } - - if(shirt != inv.end()){ - const ESM::Clothing *clothes = (shirt->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Shirt, 2, parts); - } - if(pants != inv.end()){ - const ESM::Clothing *clothes = (pants->get())->base; - std::vector parts = clothes->parts.parts; - addPartGroup(MWWorld::InventoryStore::Slot_Pants, 2, parts); - } } - if(partpriorities[ESM::PRT_Head] < 1){ - addOrReplaceIndividualPart(ESM::PRT_Head, -1,1,headModel); - } - if(partpriorities[ESM::PRT_Hair] < 1 && partpriorities[ESM::PRT_Head] <= 1){ - addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1,hairModel); - } - if(partpriorities[ESM::PRT_Neck] < 1){ - const ESM::BodyPart *neckPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "neck"); - if(neckPart) - addOrReplaceIndividualPart(ESM::PRT_Neck, -1,1,"meshes\\" + neckPart->model); - } - if(partpriorities[ESM::PRT_Cuirass] < 1){ - const ESM::BodyPart *chestPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "chest"); - if(chestPart) - addOrReplaceIndividualPart(ESM::PRT_Cuirass, -1,1,"meshes\\" + chestPart->model); - } - - if(partpriorities[ESM::PRT_Groin] < 1){ - const ESM::BodyPart *groinPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "groin"); - if(groinPart) - addOrReplaceIndividualPart(ESM::PRT_Groin, -1,1,"meshes\\" + groinPart->model); - } - if(partpriorities[ESM::PRT_RHand] < 1){ - const ESM::BodyPart *handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hand"); - if(!handPart) - handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hands"); - if(handPart) - addOrReplaceIndividualPart(ESM::PRT_RHand, -1,1,"meshes\\" + handPart->model); - } - if(partpriorities[ESM::PRT_LHand] < 1){ - const ESM::BodyPart *handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hand"); - if(!handPart) - handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hands"); - if(handPart) - addOrReplaceIndividualPart(ESM::PRT_LHand, -1,1,"meshes\\" + handPart->model); - } - - if(partpriorities[ESM::PRT_RWrist] < 1){ - const ESM::BodyPart *wristPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "wrist"); - if(wristPart) - addOrReplaceIndividualPart(ESM::PRT_RWrist, -1,1,"meshes\\" + wristPart->model); - } - if(partpriorities[ESM::PRT_LWrist] < 1){ - const ESM::BodyPart *wristPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "wrist"); - if(wristPart) - addOrReplaceIndividualPart(ESM::PRT_LWrist, -1,1,"meshes\\" + wristPart->model); - } - if(partpriorities[ESM::PRT_RForearm] < 1){ - const ESM::BodyPart *forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "forearm"); - if(bodyRaceID == "b_n_argonian_f_") - forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search ("b_n_argonian_m_forearm"); - if(forearmPart) - addOrReplaceIndividualPart(ESM::PRT_RForearm, -1,1,"meshes\\" + forearmPart->model); - } - if(partpriorities[ESM::PRT_LForearm] < 1){ - const ESM::BodyPart *forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "forearm"); - if(bodyRaceID == "b_n_argonian_f_") - forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search ("b_n_argonian_m_forearm"); - if(forearmPart) - addOrReplaceIndividualPart(ESM::PRT_LForearm, -1,1,"meshes\\" + forearmPart->model); - } - if(partpriorities[ESM::PRT_RUpperarm] < 1){ - const ESM::BodyPart *armPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper arm"); - if(armPart) - addOrReplaceIndividualPart(ESM::PRT_RUpperarm, -1,1,"meshes\\" + armPart->model); - } - if(partpriorities[ESM::PRT_LUpperarm] < 1){ - const ESM::BodyPart *armPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper arm"); - if(armPart) - addOrReplaceIndividualPart(ESM::PRT_LUpperarm, -1,1,"meshes\\" + armPart->model); - } - if(partpriorities[ESM::PRT_RFoot] < 1){ - const ESM::BodyPart *footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "foot"); - if(isBeast && !footPart) - footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "feet"); - if(footPart) - addOrReplaceIndividualPart(ESM::PRT_RFoot, -1,1,"meshes\\" + footPart->model); - } - if(partpriorities[ESM::PRT_LFoot] < 1){ - const ESM::BodyPart *footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "foot"); - if(isBeast && !footPart) - footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "feet"); - if(footPart) - addOrReplaceIndividualPart(ESM::PRT_LFoot, -1,1,"meshes\\" + footPart->model); - } - if(partpriorities[ESM::PRT_RAnkle] < 1){ - const ESM::BodyPart *anklePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "ankle"); - if(anklePart) - addOrReplaceIndividualPart(ESM::PRT_RAnkle, -1,1,"meshes\\" + anklePart->model); - } - if(partpriorities[ESM::PRT_LAnkle] < 1){ - const ESM::BodyPart *anklePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "ankle"); - if(anklePart) - addOrReplaceIndividualPart(ESM::PRT_LAnkle, -1,1,"meshes\\" + anklePart->model); - } - if(partpriorities[ESM::PRT_RKnee] < 1){ - const ESM::BodyPart *kneePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "knee"); - if(kneePart) - addOrReplaceIndividualPart(ESM::PRT_RKnee, -1,1,"meshes\\" + kneePart->model); - } - if(partpriorities[ESM::PRT_LKnee] < 1){ - const ESM::BodyPart *kneePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "knee"); - if(kneePart) - addOrReplaceIndividualPart(ESM::PRT_LKnee, -1,1,"meshes\\" + kneePart->model); - } - if(partpriorities[ESM::PRT_RLeg] < 1){ - const ESM::BodyPart *legPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper leg"); - if(legPart) - addOrReplaceIndividualPart(ESM::PRT_RLeg, -1,1,"meshes\\" + legPart->model); - } - if(partpriorities[ESM::PRT_LLeg] < 1){ - const ESM::BodyPart *legPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper leg"); - if(legPart) - addOrReplaceIndividualPart(ESM::PRT_LLeg, -1,1,"meshes\\" + legPart->model); - } - if(partpriorities[ESM::PRT_Tail] < 1){ - const ESM::BodyPart *tailPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "tail"); - if(tailPart) - addOrReplaceIndividualPart(ESM::PRT_Tail, -1,1,"meshes\\" + tailPart->model); - } - - - - + if(shirt != mInv.end()) + { + const ESM::Clothing *clothes = (shirt->get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Shirt, 2, parts); + } + if(pants != mInv.end()) + { + const ESM::Clothing *clothes = (pants->get())->base; + std::vector parts = clothes->parts.parts; + addPartGroup(MWWorld::InventoryStore::Slot_Pants, 2, parts); + } + } + if(mPartPriorities[ESM::PRT_Head] < 1) + { + addOrReplaceIndividualPart(ESM::PRT_Head, -1,1,headModel); + } + if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1) + { + addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1,hairModel); + } + if(mPartPriorities[ESM::PRT_Neck] < 1) + { + const ESM::BodyPart *neckPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "neck"); + if(neckPart) + addOrReplaceIndividualPart(ESM::PRT_Neck, -1,1,"meshes\\" + neckPart->model); + } + if(mPartPriorities[ESM::PRT_Cuirass] < 1) + { + const ESM::BodyPart *chestPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "chest"); + if(chestPart) + addOrReplaceIndividualPart(ESM::PRT_Cuirass, -1,1,"meshes\\" + chestPart->model); + } + if(mPartPriorities[ESM::PRT_Groin] < 1) + { + const ESM::BodyPart *groinPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "groin"); + if(groinPart) + addOrReplaceIndividualPart(ESM::PRT_Groin, -1,1,"meshes\\" + groinPart->model); + } + if(mPartPriorities[ESM::PRT_RHand] < 1) + { + const ESM::BodyPart *handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hand"); + if(!handPart) + handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hands"); + if(handPart) + addOrReplaceIndividualPart(ESM::PRT_RHand, -1,1,"meshes\\" + handPart->model); + } + if(mPartPriorities[ESM::PRT_LHand] < 1) + { + const ESM::BodyPart *handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hand"); + if(!handPart) + handPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "hands"); + if(handPart) + addOrReplaceIndividualPart(ESM::PRT_LHand, -1,1,"meshes\\" + handPart->model); + } + if(mPartPriorities[ESM::PRT_RWrist] < 1) + { + const ESM::BodyPart *wristPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "wrist"); + if(wristPart) + addOrReplaceIndividualPart(ESM::PRT_RWrist, -1,1,"meshes\\" + wristPart->model); + } + if(mPartPriorities[ESM::PRT_LWrist] < 1) + { + const ESM::BodyPart *wristPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "wrist"); + if(wristPart) + addOrReplaceIndividualPart(ESM::PRT_LWrist, -1,1,"meshes\\" + wristPart->model); + } + if(mPartPriorities[ESM::PRT_RForearm] < 1) + { + const ESM::BodyPart *forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "forearm"); + if(bodyRaceID == "b_n_argonian_f_") + forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search ("b_n_argonian_m_forearm"); + if(forearmPart) + addOrReplaceIndividualPart(ESM::PRT_RForearm, -1,1,"meshes\\" + forearmPart->model); + } + if(mPartPriorities[ESM::PRT_LForearm] < 1) + { + const ESM::BodyPart *forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "forearm"); + if(bodyRaceID == "b_n_argonian_f_") + forearmPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search ("b_n_argonian_m_forearm"); + if(forearmPart) + addOrReplaceIndividualPart(ESM::PRT_LForearm, -1,1,"meshes\\" + forearmPart->model); + } + if(mPartPriorities[ESM::PRT_RUpperarm] < 1) + { + const ESM::BodyPart *armPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper arm"); + if(armPart) + addOrReplaceIndividualPart(ESM::PRT_RUpperarm, -1,1,"meshes\\" + armPart->model); + } + if(mPartPriorities[ESM::PRT_LUpperarm] < 1) + { + const ESM::BodyPart *armPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper arm"); + if(armPart) + addOrReplaceIndividualPart(ESM::PRT_LUpperarm, -1,1,"meshes\\" + armPart->model); + } + if(mPartPriorities[ESM::PRT_RFoot] < 1) + { + const ESM::BodyPart *footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "foot"); + if(isBeast && !footPart) + footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "feet"); + if(footPart) + addOrReplaceIndividualPart(ESM::PRT_RFoot, -1,1,"meshes\\" + footPart->model); + } + if(mPartPriorities[ESM::PRT_LFoot] < 1) + { + const ESM::BodyPart *footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "foot"); + if(isBeast && !footPart) + footPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "feet"); + if(footPart) + addOrReplaceIndividualPart(ESM::PRT_LFoot, -1,1,"meshes\\" + footPart->model); + } + if(mPartPriorities[ESM::PRT_RAnkle] < 1) + { + const ESM::BodyPart *anklePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "ankle"); + if(anklePart) + addOrReplaceIndividualPart(ESM::PRT_RAnkle, -1,1,"meshes\\" + anklePart->model); + } + if(mPartPriorities[ESM::PRT_LAnkle] < 1) + { + const ESM::BodyPart *anklePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "ankle"); + if(anklePart) + addOrReplaceIndividualPart(ESM::PRT_LAnkle, -1,1,"meshes\\" + anklePart->model); + } + if(mPartPriorities[ESM::PRT_RKnee] < 1) + { + const ESM::BodyPart *kneePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "knee"); + if(kneePart) + addOrReplaceIndividualPart(ESM::PRT_RKnee, -1,1,"meshes\\" + kneePart->model); + } + if(mPartPriorities[ESM::PRT_LKnee] < 1) + { + const ESM::BodyPart *kneePart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "knee"); + if(kneePart) + addOrReplaceIndividualPart(ESM::PRT_LKnee, -1,1,"meshes\\" + kneePart->model); + } + if(mPartPriorities[ESM::PRT_RLeg] < 1) + { + const ESM::BodyPart *legPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper leg"); + if(legPart) + addOrReplaceIndividualPart(ESM::PRT_RLeg, -1,1,"meshes\\" + legPart->model); + } + if(mPartPriorities[ESM::PRT_LLeg] < 1) + { + const ESM::BodyPart *legPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "upper leg"); + if(legPart) + addOrReplaceIndividualPart(ESM::PRT_LLeg, -1,1,"meshes\\" + legPart->model); + } + if(mPartPriorities[ESM::PRT_Tail] < 1) + { + const ESM::BodyPart *tailPart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (bodyRaceID + "tail"); + if(tailPart) + addOrReplaceIndividualPart(ESM::PRT_Tail, -1,1,"meshes\\" + tailPart->model); + } } -Ogre::Entity* NpcAnimation::insertBoundedPart(const std::string &mesh, std::string bonename){ - +Ogre::Entity* NpcAnimation::insertBoundedPart(const std::string &mesh, std::string bonename) +{ NIFLoader::load(mesh); Ogre::Entity* part = mRend.getScene()->createEntity(mesh); part->setVisibilityFlags(RV_Actors); - base->attachObjectToBone(bonename, part); + mBase->attachObjectToBone(bonename, part); return part; } -void NpcAnimation::insertFootPart(int type, const std::string &mesh){ +void NpcAnimation::insertFootPart(int type, const std::string &mesh) +{ std::string meshAndSuffix = mesh; if(type == ESM::PRT_LFoot) meshAndSuffix += "*|"; NIFLoader::load(meshAndSuffix); Ogre::Entity* part = mRend.getScene()->createEntity(meshAndSuffix); std::vector* shape = ((NIFLoader::getSingletonPtr())->getShapes(meshAndSuffix)); - if(shape == 0){ - if(type == ESM::PRT_LFoot){ - base->attachObjectToBone("Left Foot", part); + if(shape == 0) + { + if(type == ESM::PRT_LFoot) + { + mBase->attachObjectToBone("Left Foot", part); lfoot = part; } - else if (type == ESM::PRT_RFoot){ - base->attachObjectToBone("Right Foot", part); + else if (type == ESM::PRT_RFoot) + { + mBase->attachObjectToBone("Right Foot", part); rfoot = part; } } - else{ + else + { if(type == ESM::PRT_LFoot) - lFreeFoot = insertFreePart(mesh, "::"); + mLFreeFoot = insertFreePart(mesh, "::"); else if (type == ESM::PRT_RFoot) - rFreeFoot = insertFreePart(mesh, ":<"); + mRFreeFoot = insertFreePart(mesh, ":<"); } - - } -std::pair*> NpcAnimation::insertFreePart(const std::string &mesh, const std::string& suffix){ +std::pair*> NpcAnimation::insertFreePart(const std::string &mesh, const std::string& suffix) +{ std::string meshNumbered = mesh + getUniqueID(mesh + suffix) + suffix; NIFLoader::load(meshNumbered); Ogre::Entity* part = mRend.getScene()->createEntity(meshNumbered); part->setVisibilityFlags(RV_Actors); - insert->attachObject(part); + mInsert->attachObject(part); std::vector* shape = ((NIFLoader::getSingletonPtr())->getShapes(mesh + "0000" + suffix)); - if(shape){ - handleShapes(shape, part, base->getSkeleton()); - } - std::pair*> pair = std::make_pair(part, shape); - return pair; + if(shape) + handleShapes(shape, part, mBase->getSkeleton()); + std::pair*> pair = std::make_pair(part, shape); + return pair; } +void NpcAnimation::runAnimation(float timepassed) +{ + if(timeToChange > .2){ + timeToChange = 0; + updateParts(); + } - -void NpcAnimation::runAnimation(float timepassed){ - - if(timeToChange > .2){ - - timeToChange = 0; - - updateParts(); - } - - timeToChange += timepassed; + timeToChange += timepassed; //1. Add the amount of time passed to time - //2. Handle the animation transforms dependent on time + //2. Handle the animation transforms dependent on time - //3. Handle the shapes dependent on animation transforms - if(animate > 0){ - time += timepassed; + //3. Handle the shapes dependent on animation transforms + if(mAnimate > 0) + { + mTime += timepassed; - if(time > stopTime){ - animate--; + if(mTime > mStopTime) + { + mAnimate--; - if(animate == 0) - time = stopTime; + if(mAnimate == 0) + mTime = mStopTime; else - time = startTime + (time - stopTime); + mTime = mStartTime + (mTime - mStopTime); } - handleAnimationTransforms(); + handleAnimationTransforms(); + mVecRotPos.clear(); - vecRotPos.clear(); + if(mLFreeFoot.first) + handleShapes(mLFreeFoot.second, mLFreeFoot.first, mBase->getSkeleton()); + if(mRFreeFoot.first) + handleShapes(mRFreeFoot.second, mRFreeFoot.first, mBase->getSkeleton()); + if(mChest.first) + handleShapes(mChest.second, mChest.first, mBase->getSkeleton()); + if(mTail.first) + handleShapes(mTail.second, mTail.first, mBase->getSkeleton()); + if(mSkirt.first) + handleShapes(mSkirt.second, mSkirt.first, mBase->getSkeleton()); + if(mLhand.first) + handleShapes(mLhand.second, mLhand.first, mBase->getSkeleton()); + if(mRhand.first) + handleShapes(mRhand.second, mRhand.first, mBase->getSkeleton()); - if(lFreeFoot.first) - handleShapes(lFreeFoot.second, lFreeFoot.first, base->getSkeleton()); - if(rFreeFoot.first) - handleShapes(rFreeFoot.second, rFreeFoot.first, base->getSkeleton()); - - if(chest.first) - handleShapes(chest.second, chest.first, base->getSkeleton()); - if(tail.first) - handleShapes(tail.second, tail.first, base->getSkeleton()); - if(skirt.first){ - handleShapes(skirt.second, skirt.first, base->getSkeleton()); - } - if(lhand.first) - handleShapes(lhand.second, lhand.first, base->getSkeleton()); - if(rhand.first) - handleShapes(rhand.second, rhand.first, base->getSkeleton()); - -} + } } void NpcAnimation::removeIndividualPart(int type){ - partpriorities[type] = 0; - partslots[type] = -1; + mPartPriorities[type] = 0; + mPartslots[type] = -1; - if(type == ESM::PRT_Head && head){ //0 - base->detachObjectFromBone(head); + if(type == ESM::PRT_Head && head) //0 + { + mBase->detachObjectFromBone(head); head = 0; } - else if(type == ESM::PRT_Hair && hair){//1 - base->detachObjectFromBone(hair); + else if(type == ESM::PRT_Hair && hair) //1 + { + mBase->detachObjectFromBone(hair); hair = 0; } - else if(type == ESM::PRT_Neck && neck){//2 - base->detachObjectFromBone(neck); + else if(type == ESM::PRT_Neck && neck) //2 + { + mBase->detachObjectFromBone(neck); neck = 0; } - else if(type == ESM::PRT_Cuirass && chest.first){//3 - insert->detachObject(chest.first); - chest = zero; + else if(type == ESM::PRT_Cuirass && mChest.first) //3 + { + mInsert->detachObject(mChest.first); + mChest = mZero; } - else if(type == ESM::PRT_Groin && groin){//4 - base->detachObjectFromBone(groin); + else if(type == ESM::PRT_Groin && groin) //4 + { + mBase->detachObjectFromBone(groin); groin = 0; } - else if(type == ESM::PRT_Skirt && skirt.first){//5 - insert->detachObject(skirt.first); - skirt = zero; + else if(type == ESM::PRT_Skirt && mSkirt.first) //5 + { + mInsert->detachObject(mSkirt.first); + mSkirt = mZero; } - else if(type == ESM::PRT_RHand && rhand.first){//6 - insert->detachObject(rhand.first); - rhand = zero; + else if(type == ESM::PRT_RHand && mRhand.first) //6 + { + mInsert->detachObject(mRhand.first); + mRhand = mZero; } - else if(type == ESM::PRT_LHand && lhand.first){//7 - insert->detachObject(lhand.first); - lhand = zero; + else if(type == ESM::PRT_LHand && mLhand.first) //7 + { + mInsert->detachObject(mLhand.first); + mLhand = mZero; } - else if(type == ESM::PRT_RWrist && rWrist){//8 - base->detachObjectFromBone(rWrist); + else if(type == ESM::PRT_RWrist && rWrist) //8 + { + mBase->detachObjectFromBone(rWrist); rWrist = 0; } - else if(type == ESM::PRT_LWrist && lWrist){//9 - base->detachObjectFromBone(lWrist); + else if(type == ESM::PRT_LWrist && lWrist) //9 + { + mBase->detachObjectFromBone(lWrist); lWrist = 0; } - else if(type == ESM::PRT_Shield){//10 + else if(type == ESM::PRT_Shield) //10 + { } - else if(type == ESM::PRT_RForearm && rForearm){//11 - base->detachObjectFromBone(rForearm); + else if(type == ESM::PRT_RForearm && rForearm) //11 + { + mBase->detachObjectFromBone(rForearm); rForearm = 0; } - else if(type == ESM::PRT_LForearm && lForearm){//12 - base->detachObjectFromBone(lForearm); + else if(type == ESM::PRT_LForearm && lForearm) //12 + { + mBase->detachObjectFromBone(lForearm); lForearm = 0; } - else if(type == ESM::PRT_RUpperarm && rupperArm){//13 - base->detachObjectFromBone(rupperArm); + else if(type == ESM::PRT_RUpperarm && rupperArm) //13 + { + mBase->detachObjectFromBone(rupperArm); rupperArm = 0; } - else if(type == ESM::PRT_LUpperarm && lupperArm){//14 - base->detachObjectFromBone(lupperArm); + else if(type == ESM::PRT_LUpperarm && lupperArm) //14 + { + mBase->detachObjectFromBone(lupperArm); lupperArm = 0; } - else if(type == ESM::PRT_RFoot){ //15 - if(rfoot){ - base->detachObjectFromBone(rfoot); + else if(type == ESM::PRT_RFoot) //15 + { + if(rfoot) + { + mBase->detachObjectFromBone(rfoot); rfoot = 0; } - else if(rFreeFoot.first){ - insert->detachObject(rFreeFoot.first); - rFreeFoot = zero; + else if(mRFreeFoot.first) + { + mInsert->detachObject(mRFreeFoot.first); + mRFreeFoot = mZero; } } - else if(type == ESM::PRT_LFoot){ //16 - if(lfoot){ - base->detachObjectFromBone(lfoot); + else if(type == ESM::PRT_LFoot) //16 + { + if(lfoot) + { + mBase->detachObjectFromBone(lfoot); lfoot = 0; } - else if(lFreeFoot.first){ - insert->detachObject(lFreeFoot.first); - lFreeFoot = zero; + else if(mLFreeFoot.first) + { + mInsert->detachObject(mLFreeFoot.first); + mLFreeFoot = mZero; } } - else if(type == ESM::PRT_RAnkle && rAnkle){ //17 - base->detachObjectFromBone(rAnkle); + else if(type == ESM::PRT_RAnkle && rAnkle) //17 + { + mBase->detachObjectFromBone(rAnkle); rAnkle = 0; } - else if(type == ESM::PRT_LAnkle && lAnkle){ //18 - base->detachObjectFromBone(lAnkle); + else if(type == ESM::PRT_LAnkle && lAnkle) //18 + { + mBase->detachObjectFromBone(lAnkle); lAnkle = 0; } - else if(type == ESM::PRT_RKnee && rKnee){ //19 - base->detachObjectFromBone(rKnee); + else if(type == ESM::PRT_RKnee && rKnee) //19 + { + mBase->detachObjectFromBone(rKnee); rKnee = 0; } - else if(type == ESM::PRT_LKnee && lKnee){ //20 - base->detachObjectFromBone(lKnee); + else if(type == ESM::PRT_LKnee && lKnee) //20 + { + mBase->detachObjectFromBone(lKnee); lKnee = 0; } - else if(type == ESM::PRT_RLeg && rUpperLeg){ //21 - base->detachObjectFromBone(rUpperLeg); + else if(type == ESM::PRT_RLeg && rUpperLeg) //21 + { + mBase->detachObjectFromBone(rUpperLeg); rUpperLeg = 0; } - else if(type == ESM::PRT_LLeg && lUpperLeg){ //22 - base->detachObjectFromBone(lUpperLeg); + else if(type == ESM::PRT_LLeg && lUpperLeg) //22 + { + mBase->detachObjectFromBone(lUpperLeg); lUpperLeg = 0; } - else if(type == ESM::PRT_RPauldron && rclavicle){ //23 - base->detachObjectFromBone(rclavicle); + else if(type == ESM::PRT_RPauldron && rclavicle) //23 + { + mBase->detachObjectFromBone(rclavicle); rclavicle = 0; } - else if(type == ESM::PRT_LPauldron && lclavicle){ //24 - base->detachObjectFromBone(lclavicle); + else if(type == ESM::PRT_LPauldron && lclavicle) //24 + { + mBase->detachObjectFromBone(lclavicle); lclavicle = 0; } - else if(type == ESM::PRT_Weapon){ //25 + else if(type == ESM::PRT_Weapon) //25 + { } - else if(type == ESM::PRT_Tail && tail.first){ //26 - insert->detachObject(tail.first); - tail = zero; + else if(type == ESM::PRT_Tail && mTail.first) //26 + { + mInsert->detachObject(mTail.first); + mTail = mZero; } - - - } - void NpcAnimation::reserveIndividualPart(int type, int group, int priority){ - if(priority > partpriorities[type]){ + void NpcAnimation::reserveIndividualPart(int type, int group, int priority) + { + if(priority > mPartPriorities[type]) + { removeIndividualPart(type); - partpriorities[type] = priority; - partslots[type] = group; + mPartPriorities[type] = priority; + mPartslots[type] = group; } } - void NpcAnimation::removePartGroup(int group){ - for(int i = 0; i < 27; i++){ - if(partslots[i] == group){ + void NpcAnimation::removePartGroup(int group) + { + for(int i = 0; i < 27; i++) + if(mPartslots[i] == group) removeIndividualPart(i); - } - } } - bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, const std::string &mesh){ - if(priority > partpriorities[type]){ + bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, const std::string &mesh) + { + if(priority > mPartPriorities[type]) + { removeIndividualPart(type); - partslots[type] = group; - partpriorities[type] = priority; - switch(type){ + mPartslots[type] = group; + mPartPriorities[type] = priority; + switch(type) + { case ESM::PRT_Head: //0 head = insertBoundedPart(mesh, "Head"); break; @@ -769,19 +837,19 @@ void NpcAnimation::removeIndividualPart(int type){ neck = insertBoundedPart(mesh, "Neck"); break; case ESM::PRT_Cuirass: //3 - chest = insertFreePart(mesh, ":\""); + mChest = insertFreePart(mesh, ":\""); break; case ESM::PRT_Groin: //4 groin = insertBoundedPart(mesh, "Groin"); break; case ESM::PRT_Skirt: //5 - skirt = insertFreePart(mesh, ":|"); + mSkirt = insertFreePart(mesh, ":|"); break; case ESM::PRT_RHand: //6 - rhand = insertFreePart(mesh, ":?"); + mRhand = insertFreePart(mesh, ":?"); break; case ESM::PRT_LHand: //7 - lhand = insertFreePart(mesh, ":>"); + mLhand = insertFreePart(mesh, ":>"); break; case ESM::PRT_RWrist: //8 rWrist = insertBoundedPart(mesh, "Right Wrist"); @@ -836,33 +904,30 @@ void NpcAnimation::removeIndividualPart(int type){ case ESM::PRT_Weapon: //25 break; case ESM::PRT_Tail: //26 - tail = insertFreePart(mesh, ":*"); + mTail = insertFreePart(mesh, ":*"); break; - - } return true; } return false; } - void NpcAnimation::addPartGroup(int group, int priority, std::vector& parts){ + void NpcAnimation::addPartGroup(int group, int priority, std::vector& parts) + { for(std::size_t i = 0; i < parts.size(); i++) - { - ESM::PartReference part = parts[i]; - - const ESM::BodyPart *bodypart = 0; - - if(isFemale) - bodypart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (part.female); - if(!bodypart) - bodypart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (part.male); - if(bodypart){ - addOrReplaceIndividualPart(part.part, group,priority,"meshes\\" + bodypart->model); - } - else - reserveIndividualPart(part.part, group, priority); - - } + { + ESM::PartReference part = parts[i]; + + const ESM::BodyPart *bodypart = 0; + + if(isFemale) + bodypart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (part.female); + if(!bodypart) + bodypart = MWBase::Environment::get().getWorld()->getStore().bodyParts.search (part.male); + if(bodypart) + addOrReplaceIndividualPart(part.part, group,priority,"meshes\\" + bodypart->model); + else + reserveIndividualPart(part.part, group, priority); + } } } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 8f4f8181d..973549619 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -20,54 +20,52 @@ namespace MWRender{ class NpcAnimation: public Animation{ private: - MWWorld::InventoryStore& inv; - int mStateID; - //Free Parts - std::pair*> chest; - std::pair*> skirt; - std::pair*> lhand; - std::pair*> rhand; - std::pair*> tail; - std::pair*> lFreeFoot; - std::pair*> rFreeFoot; + MWWorld::InventoryStore& mInv; + int mStateID; + //Free Parts + std::pair*> mChest; + std::pair*> mSkirt; + std::pair*> mLhand; + std::pair*> mRhand; + std::pair*> mTail; + std::pair*> mLFreeFoot; + std::pair*> mRFreeFoot; - int partslots[27]; //Each part slot is taken by clothing, armor, or is empty - int partpriorities[27]; - std::pair*> zero; + int mPartslots[27]; //Each part slot is taken by clothing, armor, or is empty + int mPartPriorities[27]; + std::pair*> mZero; + //Bounded Parts + Ogre::Entity* lclavicle; + Ogre::Entity* rclavicle; + Ogre::Entity* rupperArm; + Ogre::Entity* lupperArm; + Ogre::Entity* rUpperLeg; + Ogre::Entity* lUpperLeg; + Ogre::Entity* lForearm; + Ogre::Entity* rForearm; + Ogre::Entity* lWrist; + Ogre::Entity* rWrist; + Ogre::Entity* rKnee; + Ogre::Entity* lKnee; + Ogre::Entity* neck; + Ogre::Entity* rAnkle; + Ogre::Entity* lAnkle; + Ogre::Entity* groin; + Ogre::Entity* lfoot; + Ogre::Entity* rfoot; + Ogre::Entity* hair; + Ogre::Entity* head; - - //Bounded Parts - Ogre::Entity* lclavicle; - Ogre::Entity* rclavicle; - Ogre::Entity* rupperArm; - Ogre::Entity* lupperArm; - Ogre::Entity* rUpperLeg; - Ogre::Entity* lUpperLeg; - Ogre::Entity* lForearm; - Ogre::Entity* rForearm; - Ogre::Entity* lWrist; - Ogre::Entity* rWrist; - Ogre::Entity* rKnee; - Ogre::Entity* lKnee; - Ogre::Entity* neck; - Ogre::Entity* rAnkle; - Ogre::Entity* lAnkle; - Ogre::Entity* groin; - Ogre::Entity* lfoot; - Ogre::Entity* rfoot; - Ogre::Entity* hair; - Ogre::Entity* head; - - Ogre::SceneNode* insert; + Ogre::SceneNode* insert; bool isBeast; bool isFemale; - std::string headModel; - std::string hairModel; - std::string npcName; - std::string bodyRaceID; - float timeToChange; - MWWorld::ContainerStoreIterator robe; + std::string headModel; + std::string hairModel; + std::string npcName; + std::string bodyRaceID; + float timeToChange; + MWWorld::ContainerStoreIterator robe; MWWorld::ContainerStoreIterator helmet; MWWorld::ContainerStoreIterator shirt; MWWorld::ContainerStoreIterator cuirass; @@ -80,22 +78,21 @@ private: MWWorld::ContainerStoreIterator rightglove; MWWorld::ContainerStoreIterator skirtiter; - public: - NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv); - virtual ~NpcAnimation(); +public: + NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv); + virtual ~NpcAnimation(); Ogre::Entity* insertBoundedPart(const std::string &mesh, std::string bonename); - std::pair*> insertFreePart(const std::string &mesh, const std::string& suffix); - void insertFootPart(int type, const std::string &mesh); - virtual void runAnimation(float timepassed); - void updateParts(); + std::pair*> insertFreePart(const std::string &mesh, const std::string& suffix); + void insertFootPart(int type, const std::string &mesh); + virtual void runAnimation(float timepassed); + void updateParts(); void removeIndividualPart(int type); void reserveIndividualPart(int type, int group, int priority); bool addOrReplaceIndividualPart(int type, int group, int priority, const std::string &mesh); - void removePartGroup(int group); + void removePartGroup(int group); void addPartGroup(int group, int priority, std::vector& parts); - }; } #endif From 182017b8e92c6259bed6ceab4f47b098152a671f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 13 Jul 2012 13:01:30 +0200 Subject: [PATCH 089/298] Issue #314: Moved ingredients and potions to a different type of record list --- apps/esmtool/esmtool.cpp | 2 +- components/esm/loadalch.cpp | 4 +++- components/esm/loadalch.hpp | 4 +++- components/esm/loadingr.cpp | 4 +++- components/esm/loadingr.hpp | 4 +++- components/esm_store/store.hpp | 4 ++-- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index f417d5c60..4888d3ceb 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -187,7 +187,7 @@ int main(int argc, char**argv) case REC_ALCH: { Potion p; - p.load(esm); + p.load(esm, id); if(quiet) break; cout << " Name: " << p.name << endl; break; diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index d3bc36a77..cd36887b0 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -2,8 +2,10 @@ namespace ESM { -void Potion::load(ESMReader &esm) +void Potion::load(ESMReader &esm, const std::string& id) { + mId = id; + model = esm.getHNString("MODL"); icon = esm.getHNOString("TEXT"); // not ITEX here for some reason script = esm.getHNOString("SCRI"); diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index c21e5dea0..6917fb448 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -24,7 +24,9 @@ struct Potion std::string name, model, icon, script; EffectList effects; - void load(ESMReader &esm); + std::string mId; + + void load(ESMReader &esm, const std::string& id); }; } #endif diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 471f71780..a745ff669 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -3,8 +3,10 @@ namespace ESM { -void Ingredient::load(ESMReader &esm) +void Ingredient::load(ESMReader &esm, const std::string& id) { + mId = id; + model = esm.getHNString("MODL"); name = esm.getHNString("FNAM"); esm.getHNT(data, "IRDT", 56); diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index af9599ed0..5c1c3bd75 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -24,7 +24,9 @@ struct Ingredient IRDTstruct data; std::string name, model, icon, script; - void load(ESMReader &esm); + std::string mId; + + void load(ESMReader &esm, const std::string& id); }; } #endif diff --git a/components/esm_store/store.hpp b/components/esm_store/store.hpp index 507196a86..991925bd4 100644 --- a/components/esm_store/store.hpp +++ b/components/esm_store/store.hpp @@ -30,7 +30,7 @@ namespace ESMS // Each individual list RecListT activators; - RecListT potions; + RecListWithIDT potions; RecListT appas; RecListT armors; RecListT bodyParts; @@ -47,7 +47,7 @@ namespace ESMS RecListT enchants; RecListT factions; RecListT globals; - RecListT ingreds; + RecListWithIDT ingreds; RecListT creatureLists; RecListT itemLists; RecListT lights; From 782d417d5bd17a45ed202756f9dac795dd6f446a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 13 Jul 2012 13:09:22 +0200 Subject: [PATCH 090/298] Issue #314: potion usage --- apps/openmw/mwclass/potion.cpp | 12 ++++++++++++ apps/openmw/mwclass/potion.hpp | 3 +++ 2 files changed, 15 insertions(+) diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 5b446fbe9..d3d615262 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -8,6 +8,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/actionapply.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" @@ -144,4 +145,15 @@ namespace MWClass return info; } + + boost::shared_ptr Potion::use (const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + ptr.getRefData().setCount (ptr.getRefData().getCount()-1); + + return boost::shared_ptr ( + new MWWorld::ActionApply (ptr, ref->base->mId, ptr)); + } } diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 74779864a..ac5d85f23 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -34,6 +34,9 @@ namespace MWClass virtual int getValue (const MWWorld::Ptr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. + virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; + ///< Generate action for using via inventory menu + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; From 573d7e7e45c2b823a3f471157c917b712e1301a5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 13 Jul 2012 15:51:57 +0200 Subject: [PATCH 091/298] Issue #314: fix to potion use function (was mixing up potion with potion user) --- apps/openmw/mwclass/potion.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index d3d615262..45cb07840 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -11,6 +11,7 @@ #include "../mwworld/actionapply.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/player.hpp" #include "../mwgui/window_manager.hpp" #include "../mwgui/tooltips.hpp" @@ -153,7 +154,9 @@ namespace MWClass ptr.getRefData().setCount (ptr.getRefData().getCount()-1); + MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + return boost::shared_ptr ( - new MWWorld::ActionApply (ptr, ref->base->mId, ptr)); + new MWWorld::ActionApply (actor, ref->base->mId, actor)); } } From 9436ca4b0cea93fcd53cba5884d360518cfe1bf9 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 13 Jul 2012 09:32:58 -0700 Subject: [PATCH 092/298] Use vectors for ShapeData properties --- components/nif/data.hpp | 16 ++++++++----- components/nif/nif_file.hpp | 26 ++++++++++++++++++++++ components/nifbullet/bullet_nif_loader.cpp | 17 ++++++-------- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 208337f35..d08ba7b32 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -96,7 +96,9 @@ public: class ShapeData : public Record { public: - std::vector vertices, normals, colors, uvlist; + std::vector vertices, normals; + std::vector colors; + std::vector< std::vector > uvlist; Ogre::Vector3 center; float radius; @@ -105,16 +107,16 @@ public: int verts = nif->getUShort(); if(nif->getInt()) - nif->getFloats(vertices, verts*3); + nif->getVector3s(vertices, verts); if(nif->getInt()) - nif->getFloats(normals, verts*3); + nif->getVector3s(normals, verts); center = nif->getVector3(); radius = nif->getFloat(); if(nif->getInt()) - nif->getFloats(colors, verts*4); + nif->getVector4s(colors, verts); // Only the first 6 bits are used as a count. I think the rest are // flags of some sort. @@ -122,7 +124,11 @@ public: uvs &= 0x3f; if(nif->getInt()) - nif->getFloats(uvlist, uvs*verts*2); + { + uvlist.resize(uvs); + for(int i = 0;i < uvs;i++) + nif->getVector2s(uvlist[i], verts); + } } }; diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index c33790742..f6c23ec9a 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -142,6 +143,13 @@ public: unsigned short getUShort() { return read_le16(); } int getInt() { return read_le32(); } float getFloat() { return read_le32f(); } + Ogre::Vector2 getVector2() + { + float a[2]; + for(size_t i = 0;i < 2;i++) + a[i] = getFloat(); + return Ogre::Vector2(a); + } Ogre::Vector3 getVector3() { float a[3]; @@ -208,6 +216,24 @@ public: for(size_t i = 0;i < vec.size();i++) vec[i] = getFloat(); } + void getVector2s(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getVector2(); + } + void getVector3s(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getVector3(); + } + void getVector4s(std::vector &vec, size_t size) + { + vec.resize(size); + for(size_t i = 0;i < vec.size();i++) + vec[i] = getVector4(); + } }; diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index 5f562504b..bdf508f6c 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -271,19 +271,16 @@ void ManualBulletShapeLoader::handleNiTriShape(Nif::NiTriShape *shape, int flags Nif::NiTriShapeData *data = shape->data.getPtr(); - float* vertices = &data->vertices[0]; - short* triangles = &data->triangles[0]; + const std::vector &vertices = data->vertices; const Ogre::Matrix3 &rot = shape->trafo.rotation; const Ogre::Vector3 &pos = shape->trafo.pos; - float scale = shape->trafo.scale; - for(unsigned int i=0; i < data->triangles.size(); i = i+3) + float scale = shape->trafo.scale * parentScale; + short* triangles = &data->triangles[0]; + for(size_t i = 0;i < data->triangles.size();i+=3) { - Ogre::Vector3 b1(vertices[triangles[i+0]*3]*parentScale,vertices[triangles[i+0]*3+1]*parentScale,vertices[triangles[i+0]*3+2]*parentScale); - Ogre::Vector3 b2(vertices[triangles[i+1]*3]*parentScale,vertices[triangles[i+1]*3+1]*parentScale,vertices[triangles[i+1]*3+2]*parentScale); - Ogre::Vector3 b3(vertices[triangles[i+2]*3]*parentScale,vertices[triangles[i+2]*3+1]*parentScale,vertices[triangles[i+2]*3+2]*parentScale); - b1 = pos + rot*b1*scale; - b2 = pos + rot*b2*scale; - b3 = pos + rot*b3*scale; + Ogre::Vector3 b1 = pos + rot*vertices[triangles[i+0]]*scale; + Ogre::Vector3 b2 = pos + rot*vertices[triangles[i+1]]*scale; + Ogre::Vector3 b3 = pos + rot*vertices[triangles[i+2]]*scale; mTriMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); } } From fdfe40a55a3bf0744cb7fb3a6d548c0eb35e34e5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 13 Jul 2012 13:41:08 -0700 Subject: [PATCH 093/298] Use a different loader object for each NIF mesh --- components/nifogre/ogre_nif_loader.cpp | 6 ++++-- components/nifogre/ogre_nif_loader.hpp | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 883cf7fce..efe9ddea6 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -128,6 +128,8 @@ public: }; +NIFLoader::LoaderMap NIFLoader::sLoaders; + void NIFLoader::warn(const std::string &msg) { std::cerr << "NIFLoader: Warn:" << msg << "\n"; @@ -331,8 +333,8 @@ Ogre::MeshPtr NIFLoader::load(const std::string &name, const std::string &group) Ogre::MeshPtr themesh = meshMgr.getByName(name, group); if(themesh.isNull()) { - static NIFLoader loader; - themesh = meshMgr.createManual(name, group, &loader); + NIFLoader *loader = &sLoaders[name]; + themesh = meshMgr.createManual(name, group, loader); } return themesh; } diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index bc1ef304c..985c64e0d 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -41,7 +41,7 @@ #include #include -#include +#include // For warning messages #include using namespace boost::algorithm; @@ -90,11 +90,11 @@ public: const std::string &group="General"); private: - NIFLoader() {} - NIFLoader(NIFLoader& n) {} - void warn(const std::string &msg); void fail(const std::string &msg); + + typedef std::map LoaderMap; + static LoaderMap sLoaders; }; } From 939d0d2fc5b4a1a8d78e708ed7c519a2ca5416e2 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 13 Jul 2012 18:25:35 -0700 Subject: [PATCH 094/298] Return a list of meshes and the skeleton from NIFLoader::load --- apps/openmw/mwrender/creatureanimation.cpp | 5 +- apps/openmw/mwrender/npcanimation.cpp | 12 +- apps/openmw/mwrender/objects.cpp | 5 +- apps/openmw/mwrender/sky.cpp | 206 +++++++++++---------- apps/openmw/mwrender/sky.hpp | 5 +- components/nifogre/ogre_nif_loader.cpp | 12 +- components/nifogre/ogre_nif_loader.hpp | 20 +- 7 files changed, 146 insertions(+), 119 deletions(-) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 6bdd58ac3..0690a7be7 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -26,8 +26,9 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O { std::string mesh = "meshes\\" + ref->base->model; - NifOgre::NIFLoader::load(mesh); - base = mRend.getScene()->createEntity(mesh); + // FIXME: There can be more than one! + NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load(mesh); + base = mRend.getScene()->createEntity(meshes[0].first->getName()); base->setVisibilityFlags(RV_Actors); bool transparent = false; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index fa33d18ff..d6600bc1b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -82,8 +82,10 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere assert(insert); std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); - NifOgre::NIFLoader::load(smodel); - base = mRend.getScene()->createEntity(smodel); + + // FIXME: There can be more than one! + NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load(smodel); + base = mRend.getScene()->createEntity(meshes[0].first->getName()); base->setVisibilityFlags(RV_Actors); bool transparent = false; @@ -382,9 +384,9 @@ void NpcAnimation::updateParts() Ogre::Entity* NpcAnimation::insertBoundedPart(const std::string &mesh, const std::string &bonename) { - - NIFLoader::load(mesh); - Ogre::Entity* part = mRend.getScene()->createEntity(mesh); + // FIXME: There can be more than one! + NifOgre::MeshPairList meshes = NIFLoader::load(mesh); + Ogre::Entity* part = mRend.getScene()->createEntity(meshes[0].first->getName()); part->setVisibilityFlags(RV_Actors); base->attachObjectToBone(bonename, part); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index fb2bfb3c5..ea58ec716 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -92,8 +92,9 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) Ogre::SceneNode* insert = ptr.getRefData().getBaseNode(); assert(insert); - NifOgre::NIFLoader::load(mesh); - Ogre::Entity *ent = mRenderer.getScene()->createEntity(mesh); + // FIXME: There can be more than one! + NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load(mesh); + Ogre::Entity *ent = mRenderer.getScene()->createEntity(meshes[0].first->getName()); Ogre::Vector3 extents = ent->getBoundingBox().getSize(); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 45e48240b..8f49e03df 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -453,16 +453,6 @@ void SkyManager::create() HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); // Stars - /// \todo sky_night_02.nif (available in Bloodmoon) - MeshPtr mesh = NifOgre::NIFLoader::load("meshes\\sky_night_01.nif"); - Entity* night1_ent = mSceneMgr->createEntity("meshes\\sky_night_01.nif"); - night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); - night1_ent->setVisibilityFlags(RV_Sky); - night1_ent->setCastShadows(false); - - mAtmosphereNight = mRootNode->createChildSceneNode(); - mAtmosphereNight->attachObject(night1_ent); - // Stars vertex shader HighLevelGpuProgramPtr stars_vp = mgr.createProgram("Stars_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_VERTEX_PROGRAM); @@ -517,35 +507,35 @@ void SkyManager::create() stars_fp->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); stars_fp->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); - for (unsigned int i=0; igetNumSubEntities(); ++i) + /// \todo sky_night_02.nif (available in Bloodmoon) + mAtmosphereNight = mRootNode->createChildSceneNode(); + NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load("meshes\\sky_night_01.nif"); + for(size_t i = 0;i < meshes.size();i++) { - MaterialPtr mp = night1_ent->getSubEntity(i)->getMaterial(); - mp->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); - mp->getTechnique(0)->getPass(0)->setAmbient(0.0, 0.0, 0.0); - mp->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, 1.0); - mp->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mp->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - mp->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); - mp->getTechnique(0)->getPass(0)->setVertexProgram(stars_vp->getName()); - mp->getTechnique(0)->getPass(0)->setFragmentProgram(stars_fp->getName()); - mp->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); - mStarsMaterials[i] = mp; - } + Entity* night1_ent = mSceneMgr->createEntity(meshes[i].first->getName()); + night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); + night1_ent->setVisibilityFlags(RV_Sky); + night1_ent->setCastShadows(false); - // Atmosphere (day) - mesh = NifOgre::NIFLoader::load("meshes\\sky_atmosphere.nif"); - Entity* atmosphere_ent = mSceneMgr->createEntity("meshes\\sky_atmosphere.nif"); - atmosphere_ent->setCastShadows(false); - - ModVertexAlpha(atmosphere_ent, 0); + mAtmosphereNight->attachObject(night1_ent); - atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly); - atmosphere_ent->setVisibilityFlags(RV_Sky); - mAtmosphereDay = mRootNode->createChildSceneNode(); - mAtmosphereDay->attachObject(atmosphere_ent); - mAtmosphereMaterial = atmosphere_ent->getSubEntity(0)->getMaterial(); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); + for (unsigned int i=0; igetNumSubEntities(); ++i) + { + MaterialPtr mp = night1_ent->getSubEntity(i)->getMaterial(); + mp->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); + mp->getTechnique(0)->getPass(0)->setAmbient(0.0, 0.0, 0.0); + mp->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, 1.0); + mp->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + mp->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + mp->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); + mp->getTechnique(0)->getPass(0)->setVertexProgram(stars_vp->getName()); + mp->getTechnique(0)->getPass(0)->setFragmentProgram(stars_fp->getName()); + mp->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); + mStarsMaterials.push_back(mp); + } + } + // Atmosphere (day) // Atmosphere shader HighLevelGpuProgramPtr vshader = mgr.createProgram("Atmosphere_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_VERTEX_PROGRAM); @@ -555,22 +545,21 @@ void SkyManager::create() StringUtil::StrStreamType outStream; outStream << - "void main_vp( \n" - " float4 position : POSITION, \n" - " in float4 color : COLOR, \n" - " out float4 oPosition : POSITION, \n" - " out float4 oVertexColor : TEXCOORD0, \n" - " uniform float4x4 worldViewProj \n" - ") \n" - "{ \n" - " oPosition = mul( worldViewProj, position ); \n" + "void main_vp( \n" + " float4 position : POSITION, \n" + " in float4 color : COLOR, \n" + " out float4 oPosition : POSITION, \n" + " out float4 oVertexColor : TEXCOORD0, \n" + " uniform float4x4 worldViewProj \n" + ") \n" + "{ \n" + " oPosition = mul( worldViewProj, position ); \n" " oVertexColor = color; \n" "}"; vshader->setSource(outStream.str()); vshader->load(); vshader->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName()); HighLevelGpuProgramPtr fshader = mgr.createProgram("Atmosphere_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_FRAGMENT_PROGRAM); @@ -580,15 +569,15 @@ void SkyManager::create() StringUtil::StrStreamType _outStream; _outStream << - "void main_fp( \n" - " in float4 iVertexColor : TEXCOORD0, \n" - " out float4 oColor : COLOR, \n"; + "void main_fp( \n" + " in float4 iVertexColor : TEXCOORD0, \n" + " out float4 oColor : COLOR, \n"; if (RenderingManager::useMRT()) _outStream << " out float4 oColor1 : COLOR1, \n"; _outStream << " uniform float4 emissive \n" - ") \n" - "{ \n" + ") \n" + "{ \n" " oColor = iVertexColor * emissive; \n"; if (RenderingManager::useMRT()) _outStream << " oColor1 = float4(1, 0, 0, 1); \n"; @@ -598,19 +587,36 @@ void SkyManager::create() fshader->load(); fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); - // Clouds - NifOgre::NIFLoader::load("meshes\\sky_clouds_01.nif"); - Entity* clouds_ent = mSceneMgr->createEntity("meshes\\sky_clouds_01.nif"); - clouds_ent->setVisibilityFlags(RV_Sky); - clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); - SceneNode* clouds_node = mRootNode->createChildSceneNode(); - clouds_node->attachObject(clouds_ent); - mCloudMaterial = clouds_ent->getSubEntity(0)->getMaterial(); - mCloudMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); - clouds_ent->setCastShadows(false); + mAtmosphereDay = mRootNode->createChildSceneNode(); + meshes = NifOgre::NIFLoader::load("meshes\\sky_atmosphere.nif"); + for(size_t i = 0;i < meshes.size();i++) + { + Entity* atmosphere_ent = mSceneMgr->createEntity(meshes[i].first->getName()); + atmosphere_ent->setCastShadows(false); + + ModVertexAlpha(atmosphere_ent, 0); + + atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly); + atmosphere_ent->setVisibilityFlags(RV_Sky); + mAtmosphereDay->attachObject(atmosphere_ent); + + mAtmosphereMaterial = atmosphere_ent->getSubEntity(0)->getMaterial(); + mAtmosphereMaterial = mAtmosphereMaterial->clone("Atmosphere"); + atmosphere_ent->getSubEntity(0)->setMaterial(mAtmosphereMaterial); + + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName()); + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(fshader->getName()); + + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, 0.0); + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setAmbient(0.0, 0.0, 0.0); + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); + } + // Clouds // Clouds vertex shader HighLevelGpuProgramPtr vshader2 = mgr.createProgram("Clouds_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, "cg", GPT_VERTEX_PROGRAM); @@ -618,24 +624,23 @@ void SkyManager::create() vshader2->setParameter("entry_point", "main_vp"); StringUtil::StrStreamType outStream3; outStream3 << - "void main_vp( \n" - " float4 position : POSITION, \n" - " in float4 color : COLOR, \n" + "void main_vp( \n" + " float4 position : POSITION, \n" + " in float4 color : COLOR, \n" " out float4 oColor : TEXCOORD1, \n" " in float2 uv : TEXCOORD0, \n" " out float2 oUV : TEXCOORD0, \n" - " out float4 oPosition : POSITION, \n" - " uniform float4x4 worldViewProj \n" - ") \n" - "{ \n" + " out float4 oPosition : POSITION, \n" + " uniform float4x4 worldViewProj \n" + ") \n" + "{ \n" " oUV = uv; \n" " oColor = color; \n" - " oPosition = mul( worldViewProj, position ); \n" + " oPosition = mul( worldViewProj, position ); \n" "}"; vshader2->setSource(outStream3.str()); vshader2->load(); vshader2->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); - mCloudMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader2->getName()); // Clouds fragment shader mCloudFragmentShader = mgr.createProgram("Clouds_FP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, @@ -644,10 +649,10 @@ void SkyManager::create() mCloudFragmentShader->setParameter("entry_point", "main_fp"); StringUtil::StrStreamType outStream2; outStream2 << - "void main_fp( \n" + "void main_fp( \n" " in float2 uv : TEXCOORD0, \n" " in float4 color : TEXCOORD1, \n" - " out float4 oColor : COLOR, \n"; + " out float4 oColor : COLOR, \n"; if (RenderingManager::useMRT()) outStream2 << " out float4 oColor1 : COLOR1, \n"; outStream2 << @@ -658,8 +663,8 @@ void SkyManager::create() " uniform float speed, \n" " uniform float opacity, \n" " uniform float4 emissive \n" - ") \n" - "{ \n" + ") \n" + "{ \n" " uv += float2(0,1) * time * speed * 0.003; \n" // Scroll in y direction " float4 tex = lerp(tex2D(texture, uv), tex2D(secondTexture, uv), transitionFactor); \n" " oColor = color * float4(emissive.xyz,1) * tex * float4(1,1,1,opacity); \n"; @@ -670,30 +675,35 @@ void SkyManager::create() mCloudFragmentShader->setSource(outStream2.str()); mCloudFragmentShader->load(); mCloudFragmentShader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); - mCloudMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(mCloudFragmentShader->getName()); - setCloudsOpacity(0.75); - - ModVertexAlpha(clouds_ent, 1); - // I'm not sure if the materials are being used by any other objects - // Make a unique "modifiable" copy of the materials to be sure - mCloudMaterial = mCloudMaterial->clone("Clouds"); - clouds_ent->getSubEntity(0)->setMaterial(mCloudMaterial); - mAtmosphereMaterial = mAtmosphereMaterial->clone("Atmosphere"); - atmosphere_ent->getSubEntity(0)->setMaterial(mAtmosphereMaterial); - - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, 0.0); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setAmbient(0.0, 0.0, 0.0); - mCloudMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); - mCloudMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mAtmosphereMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); - mCloudMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); - - mCloudMaterial->getTechnique(0)->getPass(0)->removeAllTextureUnitStates(); - mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("textures\\tx_sky_cloudy.dds"); - mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); + SceneNode* clouds_node = mRootNode->createChildSceneNode(); + meshes = NifOgre::NIFLoader::load("meshes\\sky_clouds_01.nif"); + for(size_t i = 0;i < meshes.size();i++) + { + Entity* clouds_ent = mSceneMgr->createEntity(meshes[i].first->getName()); + clouds_ent->setVisibilityFlags(RV_Sky); + clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); + clouds_node->attachObject(clouds_ent); + + mCloudMaterial = clouds_ent->getSubEntity(0)->getMaterial(); + mCloudMaterial = mCloudMaterial->clone("Clouds"); + clouds_ent->getSubEntity(0)->setMaterial(mCloudMaterial); + + mCloudMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); + clouds_ent->setCastShadows(false); + + mCloudMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader2->getName()); + mCloudMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(mCloudFragmentShader->getName()); + ModVertexAlpha(clouds_ent, 1); + + mCloudMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(1.0, 1.0, 1.0); + mCloudMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + mCloudMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); + mCloudMaterial->getTechnique(0)->getPass(0)->removeAllTextureUnitStates(); + mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("textures\\tx_sky_cloudy.dds"); + mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); + } + setCloudsOpacity(0.75); mCreated = true; } @@ -851,7 +861,7 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) else { mAtmosphereNight->setVisible(true); - for (int i=0; i<7; ++i) + for (size_t i=0; igetTechnique(0)->getPass(0)->setDiffuse(0.0, 0.0, 0.0, weather.mNightFade); mStarsOpacity = weather.mNightFade; } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index e11745e82..7b7cc2d16 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -1,13 +1,14 @@ #ifndef _GAME_RENDER_SKY_H #define _GAME_RENDER_SKY_H +#include + #include #include #include #include #include -#include "sky.hpp" #include "../mwworld/weather.hpp" namespace Ogre @@ -195,7 +196,7 @@ namespace MWRender Ogre::MaterialPtr mCloudMaterial; Ogre::MaterialPtr mAtmosphereMaterial; - Ogre::MaterialPtr mStarsMaterials[7]; + std::vector mStarsMaterials; Ogre::HighLevelGpuProgramPtr mCloudFragmentShader; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index efe9ddea6..a479e78a5 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -322,12 +322,16 @@ void NIFLoader::createMaterial(const Ogre::String &name, void NIFLoader::loadResource(Ogre::Resource *resource) { - warn("Found no records in NIF."); + warn("Found no records in NIF for "+resource->getName()); } -Ogre::MeshPtr NIFLoader::load(const std::string &name, const std::string &group) +MeshPairList NIFLoader::load(const std::string &name, Ogre::SkeletonPtr *skel, const std::string &group) { Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); + MeshPairList ret; + + if(skel != NULL) + skel->setNull(); // Check if the resource already exists Ogre::MeshPtr themesh = meshMgr.getByName(name, group); @@ -336,7 +340,9 @@ Ogre::MeshPtr NIFLoader::load(const std::string &name, const std::string &group) NIFLoader *loader = &sLoaders[name]; themesh = meshMgr.createManual(name, group, loader); } - return themesh; + ret.push_back(std::make_pair(themesh, std::string())); + + return ret; } diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 985c64e0d..1eef7eb37 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -67,6 +68,12 @@ namespace Nif namespace NifOgre { + +/** This holds a list of meshes along with the names of their parent nodes + */ +typedef std::vector< std::pair > MeshPairList; + + /** Manual resource loader for NIF meshes. This is the main class responsible for translating the internal NIF mesh structure into something Ogre can use. @@ -75,19 +82,18 @@ namespace NifOgre NIFLoader::load("somemesh.nif"); - Afterwards, you can use the mesh name "somemesh.nif" normally to - create entities and so on. The mesh isn't loaded from disk until - OGRE needs it for rendering. Thus the above load() command is not - very resource intensive, and can safely be done for a large number - of meshes at load time. + This returns a list of meshes used by the model, as well as the names of + their parent nodes (as they pertain to the skeleton, which is optionally + returned in the second argument if it exists). */ class NIFLoader : Ogre::ManualResourceLoader { public: virtual void loadResource(Ogre::Resource *resource); - static Ogre::MeshPtr load(const std::string &name, - const std::string &group="General"); + static MeshPairList load(const std::string &name, + Ogre::SkeletonPtr *skel=NULL, + const std::string &group="General"); private: void warn(const std::string &msg); From 16c2ea3a751500733bb69b7571c1a6061e6396b3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jul 2012 11:13:38 +0200 Subject: [PATCH 095/298] terrain colourmap --- apps/openmw/mwrender/terrain.cpp | 3 +++ apps/openmw/mwrender/terrainmaterial.cpp | 20 +++++++++++++++---- apps/openmw/mwrender/terrainmaterial.hpp | 9 +++++---- extern/shiny | 2 +- files/materials/terrain.shader | 25 +++++++++++++++++++++++- 5 files changed, 49 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index 67cee435c..b1d9dee12 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -191,6 +191,9 @@ namespace MWRender y*(mLandSize-1), mLandSize); + mActiveProfile->setGlobalColourMapEnabled(true); + mActiveProfile->setGlobalColourMap (terrain, vertex->getName()); + //this is a hack to get around the fact that Ogre seems to //corrupt the global colour map leading to rendering errors //MaterialPtr mat = terrain->getMaterial(); diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index ae04306f6..f8551a3fd 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -31,6 +31,7 @@ namespace MWRender TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc) : Ogre::TerrainMaterialGenerator::Profile(parent, name, desc) + , mGlobalColourMap(false) { } @@ -56,11 +57,17 @@ namespace MWRender createPass(); return Ogre::MaterialManager::getSingleton().getByName(matName); + } - /* - Ogre::MaterialPtr m = Ogre::MaterialManager::getSingleton().create(matName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - return m; - */ + void TerrainMaterial::Profile::setGlobalColourMapEnabled (bool enabled) + { + mGlobalColourMap = enabled; + mParent->_markChanged(); + } + + void TerrainMaterial::Profile::setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name) + { + sh::Factory::getInstance ().setTextureAlias (terrain->getMaterialName () + "_colourMap", name); } int TerrainMaterial::Profile::getLayersPerPass () const @@ -76,6 +83,11 @@ namespace MWRender p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); + + p->mShaderProperties.setProperty ("colour_map", sh::makeProperty(new sh::BooleanValue(mGlobalColourMap))); + + sh::MaterialInstanceTextureUnit* colourMap = p->createTextureUnit ("colourMap"); + colourMap->setProperty ("texture_alias", sh::makeProperty(mMaterial->getName() + "_colourMap")); } Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain) diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp index 73bbd7e07..2ee0224fd 100644 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -47,10 +47,6 @@ namespace MWRender class Profile : public Ogre::TerrainMaterialGenerator::Profile { - protected: - Ogre::TerrainMaterialGenerator* mParent; - Ogre::String mName; - Ogre::String mDesc; public: Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc); virtual ~Profile(); @@ -69,9 +65,14 @@ namespace MWRender virtual void requestOptions(Ogre::Terrain* terrain); + void setGlobalColourMapEnabled(bool enabled); + void setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name); + private: sh::MaterialInstance* mMaterial; + bool mGlobalColourMap; + void createPass (int index=0); int getLayersPerPass () const; diff --git a/extern/shiny b/extern/shiny index 1c25aca08..5cf02ac0c 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 1c25aca082214beddd05fa9b8adf481c7299cf0e +Subproject commit 5cf02ac0c39115fcf7f69eea7a79c9c6ad7dbd71 diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 8b186def3..bb776d691 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -3,8 +3,11 @@ #define FOG @shGlobalSettingBool(fog) #define MRT @shGlobalSettingBool(mrt_output) +#define COLOUR_MAP @shPropertyBool(colour_map) + @shAllocatePassthrough(1, depth) +@shAllocatePassthrough(2, UV) #ifdef SH_VERTEX_SHADER @@ -26,6 +29,7 @@ shOutputPosition = shMatrixMult(viewProjMatrix, worldPos); @shPassthroughAssign(depth, shOutputPosition.z); + @shPassthroughAssign(UV, uv0); } @@ -34,11 +38,20 @@ SH_BEGIN_PROGRAM +#if COLOUR_MAP + shSampler2D(colourMap) +#endif + +#if FOG + shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) + shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) +#endif @shPassthroughFragmentInputs #if MRT shDeclareMrtOutput(1) + shUniform(float, far) @shAutoConstant(far, far_clip_distance) #endif @@ -47,12 +60,22 @@ { float depth = @shPassthroughReceive(depth); + float2 UV = @shPassthroughReceive(UV); shOutputColour(0) = float4(1,0,0,1); + +#if COLOUR_MAP + shOutputColour(0).rgb *= shSample(colourMap, UV).rgb; +#endif + +#if FOG + float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); +#endif #if MRT - //shOutputColour(1) = float4(1,1,1,1); + shOutputColour(1) = float4(depth / far,1,1,1); #endif } From d41050fb79a60f62458620f10c0363c3d7e78811 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jul 2012 11:21:40 +0200 Subject: [PATCH 096/298] merge --- components/nifogre/ogre_nif_loader.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index e7c677f83..bde948970 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -212,23 +212,23 @@ void NIFLoader::createMaterial(const Ogre::String &name, const Ogre::Vector3 &emissive, float glossiness, float alpha, int alphaFlags, float alphaTest, - const String &texName, bool vertexColor) + const Ogre::String &texName, bool vertexColor) { if (texName.empty()) return; sh::MaterialInstance* instance = sh::Factory::getInstance ().createMaterialInstance (name, "openmw_objects_base"); instance->setProperty ("ambient", sh::makeProperty ( - new sh::Vector3(ambient.array[0], ambient.array[1], ambient.array[2]))); + new sh::Vector3(ambient.x, ambient.y, ambient.z))); instance->setProperty ("diffuse", sh::makeProperty ( - new sh::Vector4(diffuse.array[0], diffuse.array[1], diffuse.array[2], alpha))); + new sh::Vector4(diffuse.x, diffuse.y, diffuse.z, alpha))); instance->setProperty ("specular", sh::makeProperty ( - new sh::Vector4(specular.array[0], specular.array[1], specular.array[2], glossiness))); + new sh::Vector4(specular.x, specular.y, specular.z, glossiness))); instance->setProperty ("emissive", sh::makeProperty ( - new sh::Vector3(emissive.array[0], emissive.array[1], emissive.array[2]))); + new sh::Vector3(emissive.x, emissive.y, emissive.z))); instance->setProperty ("diffuseMap", sh::makeProperty(texName)); @@ -679,7 +679,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou { //std::cout << "new"; createMaterial(material, d->ambient, d->diffuse, d->specular, d->emissive, - d->glossiness, d->alpha, alphaFlags, alphaTest, texName, shape->data->colors.length != 0); + d->glossiness, d->alpha, alphaFlags, alphaTest, texName, shape->data->colors.size() != 0); MaterialMap.insert(std::make_pair(texName,material)); } } @@ -689,7 +689,7 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou // material for it. const Ogre::Vector3 zero(0.0f), one(1.0f); createMaterial(material, one, one, zero, zero, 0.0f, 1.0f, - alphaFlags, alphaTest, texName, shape->data->colors.length != 0); + alphaFlags, alphaTest, texName, shape->data->colors.size() != 0); } } } // End of material block, if(!hidden) ... From 5a381006e52e6815f4f86f8eeec09f6c1e8c15eb Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 14 Jul 2012 09:20:09 -0700 Subject: [PATCH 097/298] Fix parsing of some key lists It seems some still want you to read the interpolation type even when there's no keys. --- components/nif/data.hpp | 4 ++-- components/nif/nif_file.hpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index d08ba7b32..63df23b27 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -394,9 +394,9 @@ struct NiMorphData : public Record nif->getChar(); mMorphs.resize(morphCount); - for(int i=0; igetInt(); - if(count == 0) return; + if(count == 0 && !force) + return; mInterpolationType = nif->getInt(); mKeys.resize(count); From 93c641efa7ffe3dd4bd67bde62cae898c37cc79d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jul 2012 18:25:43 +0200 Subject: [PATCH 098/298] terrain albedo --- apps/openmw/mwrender/terrainmaterial.cpp | 58 ++++++++++++++++++++++-- apps/openmw/mwrender/terrainmaterial.hpp | 2 +- extern/shiny | 2 +- files/materials/terrain.shader | 48 +++++++++++++++++++- 4 files changed, 102 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index f8551a3fd..d7c42efee 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -4,6 +4,21 @@ #include +namespace +{ + Ogre::String getComponent (int num) + { + if (num == 0) + return "x"; + else if (num == 1) + return "y"; + else if (num == 2) + return "z"; + else + return "w"; + } +} + namespace MWRender { @@ -54,7 +69,7 @@ namespace MWRender mMaterial->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); - createPass(); + createPass(0, terrain); return Ogre::MaterialManager::getSingleton().getByName(matName); } @@ -72,10 +87,10 @@ namespace MWRender int TerrainMaterial::Profile::getLayersPerPass () const { - return 10; + return 12; } - void TerrainMaterial::Profile::createPass (int index) + void TerrainMaterial::Profile::createPass (int index, const Ogre::Terrain* terrain) { int layerOffset = index * getLayersPerPass(); @@ -86,8 +101,41 @@ namespace MWRender p->mShaderProperties.setProperty ("colour_map", sh::makeProperty(new sh::BooleanValue(mGlobalColourMap))); + // global colour map sh::MaterialInstanceTextureUnit* colourMap = p->createTextureUnit ("colourMap"); - colourMap->setProperty ("texture_alias", sh::makeProperty(mMaterial->getName() + "_colourMap")); + colourMap->setProperty ("texture_alias", sh::makeProperty (new sh::StringValue(mMaterial->getName() + "_colourMap"))); + colourMap->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); + + // global normal map + sh::MaterialInstanceTextureUnit* normalMap = p->createTextureUnit ("normalMap"); + normalMap->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getTerrainNormalMap ()->getName()))); + normalMap->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); + + uint maxLayers = getMaxLayers(terrain); + uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + + p->mShaderProperties.setProperty ("num_layers", sh::makeProperty(new sh::StringValue(Ogre::StringConverter::toString(numLayers)))); + p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty(new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); + + // blend maps + for (uint i = 0; i < numBlendTextures; ++i) + { + sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i)); + blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getBlendTextureName(i)))); + blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); + } + + // layer maps + for (uint i = 0; i < numLayers; ++i) + { + sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); + diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getLayerTextureName(i, 0)))); + p->mShaderProperties.setProperty ("blendmap_index_" + Ogre::StringConverter::toString(i), + sh::makeProperty(new sh::StringValue(Ogre::StringConverter::toString(int((i-1) / 4))))); + p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), + sh::makeProperty(new sh::StringValue(getComponent(int((i-1) % 4))))); + } } Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain) @@ -111,7 +159,7 @@ namespace MWRender void TerrainMaterial::Profile::requestOptions(Ogre::Terrain* terrain) { terrain->_setMorphRequired(true); - terrain->_setNormalMapRequired(false); + terrain->_setNormalMapRequired(true); // global normal map terrain->_setLightMapRequired(false); terrain->_setCompositeMapRequired(false); } diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp index 2ee0224fd..a9f957442 100644 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -73,7 +73,7 @@ namespace MWRender bool mGlobalColourMap; - void createPass (int index=0); + void createPass (int index, const Ogre::Terrain* terrain); int getLayersPerPass () const; diff --git a/extern/shiny b/extern/shiny index 5cf02ac0c..a83d479c4 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 5cf02ac0c39115fcf7f69eea7a79c9c6ad7dbd71 +Subproject commit a83d479c461feead0356946f841c2c474760f420 diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index bb776d691..34b52caf6 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -5,6 +5,17 @@ #define COLOUR_MAP @shPropertyBool(colour_map) +#define NUM_LAYERS @shPropertyString(num_layers) + + +#define COMPONENT_0 x +#define COMPONENT_1 y +#define COMPONENT_2 z +#define COMPONENT_3 w + +#define IS_FIRST_PASS 1 + + @shAllocatePassthrough(1, depth) @shAllocatePassthrough(2, UV) @@ -41,6 +52,16 @@ #if COLOUR_MAP shSampler2D(colourMap) #endif + + shSampler2D(normalMap) // global normal map + +@shForeach(@shPropertyString(num_blendmaps)) + shSampler2D(blendMap@shIterator) +@shEndForeach + +@shForeach(@shPropertyString(num_layers)) + shSampler2D(diffuseMap@shIterator) +@shEndForeach #if FOG shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) @@ -62,11 +83,36 @@ float depth = @shPassthroughReceive(depth); float2 UV = @shPassthroughReceive(UV); - shOutputColour(0) = float4(1,0,0,1); + float3 normal = shSample(normalMap, UV).rgb * 2 - 1; + + // fetch blendmaps +@shForeach(@shPropertyString(num_blendmaps)) + float4 blendValues@shIterator = shSample(blendMap@shIterator, UV); +@shEndForeach + + float blendAmount; + float3 albedo; +@shForeach(@shPropertyString(num_layers)) + + +#if IS_FIRST_PASS == 1 && @shIterator == 0 + // first layer of first pass doesn't need a blend map + albedo = shSample(diffuseMap0, UV * 5).rgb; +#else + blendAmount = blendValues@shPropertyString(blendmap_index_@shIterator).@shPropertyString(blendmap_component_@shIterator); + + albedo += shSample(diffuseMap@shIterator, UV * 5).rgb * blendAmount; +#endif +@shEndForeach + + shOutputColour(0) = float4(1,1,1,1); #if COLOUR_MAP shOutputColour(0).rgb *= shSample(colourMap, UV).rgb; #endif + + shOutputColour(0).rgb *= albedo; + #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); From 29f91753f7b624b094ef3e9f0f237d560832b514 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jul 2012 18:44:03 +0200 Subject: [PATCH 099/298] correction --- apps/openmw/mwrender/terrainmaterial.cpp | 2 +- files/materials/terrain.shader | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index d7c42efee..4c8f06030 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -87,7 +87,7 @@ namespace MWRender int TerrainMaterial::Profile::getLayersPerPass () const { - return 12; + return 11; } void TerrainMaterial::Profile::createPass (int index, const Ogre::Terrain* terrain) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 34b52caf6..47f81d05d 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -90,18 +90,17 @@ float4 blendValues@shIterator = shSample(blendMap@shIterator, UV); @shEndForeach - float blendAmount; float3 albedo; @shForeach(@shPropertyString(num_layers)) #if IS_FIRST_PASS == 1 && @shIterator == 0 // first layer of first pass doesn't need a blend map - albedo = shSample(diffuseMap0, UV * 5).rgb; + albedo = shSample(diffuseMap0, UV * 10).rgb; #else - blendAmount = blendValues@shPropertyString(blendmap_index_@shIterator).@shPropertyString(blendmap_component_@shIterator); + #define BLEND_AMOUNT blendValues@shPropertyString(blendmap_index_@shIterator).@shPropertyString(blendmap_component_@shIterator) - albedo += shSample(diffuseMap@shIterator, UV * 5).rgb * blendAmount; + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, UV * 10).rgb, BLEND_AMOUNT); #endif @shEndForeach From 5345d4eeefdc909c687e5f249944bc7e2cd8454c Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jul 2012 18:45:03 +0200 Subject: [PATCH 100/298] fix a warning --- components/nif/nif_file.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index a21882c6d..42e312b7f 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -83,7 +83,7 @@ class NIFFile float read_le32f() { union { - int i; + uint32_t i; float f; } u = { read_le32() }; return u.f; From 32e14907a218ab4223747e7f12b8b904b20d4ea3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jul 2012 19:09:35 +0200 Subject: [PATCH 101/298] add a default value for CMAKE_BUILD_TYPE, resolves error when it is not set --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b561815ca..6c822256f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,13 @@ set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") +# Add a sensible build type default and warning because empty means no optimization and no debug info. +if(NOT CMAKE_BUILD_TYPE) + message("WARNING: CMAKE_BUILD_TYPE is not defined!\n Defaulting to CMAKE_BUILD_TYPE=RelWithDebInfo. Use ccmake to set a proper value.") + set(CMAKE_BUILD_TYPE RelWithDebInfo + CACHE STRING "Type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE) +endif(NOT CMAKE_BUILD_TYPE) + # Debug suffix for plugins set(DEBUG_SUFFIX "") if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") From 94c3fb81d11e916c7517c7ddaa9979dbf1a4cec4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jul 2012 21:36:42 +0200 Subject: [PATCH 102/298] check if CMAKE_BUILD_TYPE is defined instead of defining it by default --- CMakeLists.txt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c822256f..a8a8ad18b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,17 +20,12 @@ set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") -# Add a sensible build type default and warning because empty means no optimization and no debug info. -if(NOT CMAKE_BUILD_TYPE) - message("WARNING: CMAKE_BUILD_TYPE is not defined!\n Defaulting to CMAKE_BUILD_TYPE=RelWithDebInfo. Use ccmake to set a proper value.") - set(CMAKE_BUILD_TYPE RelWithDebInfo - CACHE STRING "Type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE) -endif(NOT CMAKE_BUILD_TYPE) - # Debug suffix for plugins set(DEBUG_SUFFIX "") -if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") - set(DEBUG_SUFFIX "_d") +if (DEFINED CMAKE_BUILD_TYPE) + if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(DEBUG_SUFFIX "_d") + endif() endif() # doxygen main page From 778e59ee37919ab3320ec3374b5d3f8a9c06039a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jul 2012 08:21:43 +0200 Subject: [PATCH 103/298] terrain as it was before, with about 4x less code --- apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwrender/terrainmaterial.cpp | 63 +++---- apps/openmw/mwrender/terrainmaterial.hpp | 4 - extern/shiny | 2 +- files/materials/objects.shader | 33 ++-- files/materials/terrain.shader | 194 ++++++++++++++++++++-- files/materials/terrain.shaderset | 4 +- 7 files changed, 239 insertions(+), 62 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d8aefaaa0..15660ade5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -107,6 +107,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); sh::Factory::getInstance ().setGlobalSetting ("fog", "true"); sh::Factory::getInstance ().setGlobalSetting ("lighting", "true"); + sh::Factory::getInstance ().setGlobalSetting ("num_lights", Settings::Manager::getString ("num lights", "Objects")); applyCompositors(); diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index 4c8f06030..89d895358 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -69,31 +69,6 @@ namespace MWRender mMaterial->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); - createPass(0, terrain); - - return Ogre::MaterialManager::getSingleton().getByName(matName); - } - - void TerrainMaterial::Profile::setGlobalColourMapEnabled (bool enabled) - { - mGlobalColourMap = enabled; - mParent->_markChanged(); - } - - void TerrainMaterial::Profile::setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name) - { - sh::Factory::getInstance ().setTextureAlias (terrain->getMaterialName () + "_colourMap", name); - } - - int TerrainMaterial::Profile::getLayersPerPass () const - { - return 11; - } - - void TerrainMaterial::Profile::createPass (int index, const Ogre::Terrain* terrain) - { - int layerOffset = index * getLayersPerPass(); - sh::MaterialInstancePass* p = mMaterial->createPass (); p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); @@ -131,11 +106,32 @@ namespace MWRender { sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getLayerTextureName(i, 0)))); - p->mShaderProperties.setProperty ("blendmap_index_" + Ogre::StringConverter::toString(i), - sh::makeProperty(new sh::StringValue(Ogre::StringConverter::toString(int((i-1) / 4))))); p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty(new sh::StringValue(getComponent(int((i-1) % 4))))); + sh::makeProperty(new sh::StringValue(Ogre::StringConverter::toString(int((i-1) / 4)) + "." + getComponent(int((i-1) % 4))))); } + + // shadow + for (uint i = 0; i < 3; ++i) + { + sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); + shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); + } + + p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty(new sh::StringValue( + Ogre::StringConverter::toString(numBlendTextures + numLayers + 2)))); + + return Ogre::MaterialManager::getSingleton().getByName(matName); + } + + void TerrainMaterial::Profile::setGlobalColourMapEnabled (bool enabled) + { + mGlobalColourMap = enabled; + mParent->_markChanged(); + } + + void TerrainMaterial::Profile::setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name) + { + sh::Factory::getInstance ().setTextureAlias (terrain->getMaterialName () + "_colourMap", name); } Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain) @@ -145,7 +141,16 @@ namespace MWRender Ogre::uint8 TerrainMaterial::Profile::getMaxLayers(const Ogre::Terrain* terrain) const { - return 32; + // count the texture units free + Ogre::uint8 freeTextureUnits = 16; + // normalmap + --freeTextureUnits; + // colourmap + --freeTextureUnits; + freeTextureUnits -= 3; // shadow PSSM + + // each layer needs 1.25 units (1xdiffusespec, 0.25xblend) + return static_cast(freeTextureUnits / (1.25f)); } void TerrainMaterial::Profile::updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain) diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp index a9f957442..3e31b2a58 100644 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -73,10 +73,6 @@ namespace MWRender bool mGlobalColourMap; - void createPass (int index, const Ogre::Terrain* terrain); - - int getLayersPerPass () const; - }; TerrainMaterial(); diff --git a/extern/shiny b/extern/shiny index a83d479c4..5a9bda601 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit a83d479c461feead0356946f841c2c474760f420 +Subproject commit 5a9bda6010413555736479ef03103f764fecb91d diff --git a/files/materials/objects.shader b/files/materials/objects.shader index c343a32b0..eabdb8ca3 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -1,7 +1,5 @@ #include "core.h" -#include "shadows.h" - #define FOG @shGlobalSettingBool(fog) #define MRT @shPropertyNotBool(is_transparent) && @shGlobalSettingBool(mrt_output) @@ -10,6 +8,10 @@ #define SHADOWS_PSSM LIGHTING && @shGlobalSettingBool(shadows_pssm) #define SHADOWS LIGHTING && @shGlobalSettingBool(shadows) +#if SHADOWS || SHADOWS_PSSM +#include "shadows.h" +#endif + #if FOG || MRT || SHADOWS_PSSM #define NEED_DEPTH #endif @@ -110,7 +112,7 @@ shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) - @shForeach(8) + @shForeach(@shGlobalSettingString(num_lights)) shUniform(float4, lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) shUniform(float4, lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) shUniform(float4, lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) @@ -149,32 +151,33 @@ #if LIGHTING float3 normal = normalize(normalPassthrough); - float3 lightDir, diffuse; + float3 lightDir; + float3 diffuse = float3(0,0,0); float d; float3 ambient = materialAmbient.xyz * lightAmbient.xyz; - @shForeach(8) - // shadows only for the first (directional) light -#if @shIterator == 0 - #if SHADOWS + // shadows only for the first (directional) light +#if SHADOWS float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0); - #endif - #if SHADOWS_PSSM +#endif +#if SHADOWS_PSSM float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depthPassthrough, pssmSplitPoints); - #endif +#endif - #if SHADOWS || SHADOWS_PSSM +#if SHADOWS || SHADOWS_PSSM float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; float fade = 1-((depthPassthrough - shadowFar_fadeStart.y) / fadeRange); shadow = (depthPassthrough > shadowFar_fadeStart.x) ? 1 : ((depthPassthrough > shadowFar_fadeStart.y) ? 1-((1-shadow)*fade) : shadow); - #endif +#endif - #if !SHADOWS && !SHADOWS_PSSM +#if !SHADOWS && !SHADOWS_PSSM float shadow = 1.0; - #endif #endif + @shForeach(@shGlobalSettingString(num_lights)) + + lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePositionPassthrough.xyz * lightPosObjSpace@shIterator.w); d = length(lightDir); diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 47f81d05d..2b7091838 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -1,32 +1,70 @@ #include "core.h" +#define IS_FIRST_PASS 1 + #define FOG @shGlobalSettingBool(fog) #define MRT @shGlobalSettingBool(mrt_output) -#define COLOUR_MAP @shPropertyBool(colour_map) +#define LIGHTING @shGlobalSettingBool(lighting) -#define NUM_LAYERS @shPropertyString(num_layers) +#define SHADOWS_PSSM LIGHTING && @shGlobalSettingBool(shadows_pssm) +#define SHADOWS LIGHTING && @shGlobalSettingBool(shadows) +#if SHADOWS || SHADOWS_PSSM +#include "shadows.h" +#endif -#define COMPONENT_0 x -#define COMPONENT_1 y -#define COMPONENT_2 z -#define COMPONENT_3 w +#define COLOUR_MAP @shPropertyBool(colour_map) -#define IS_FIRST_PASS 1 +#define NUM_LAYERS @shPropertyString(num_layers) +#if MRT || FOG || SHADOWS_PSSM +#define NEED_DEPTH 1 +#endif +#if NEED_DEPTH @shAllocatePassthrough(1, depth) +#endif + @shAllocatePassthrough(2, UV) +#if LIGHTING +@shAllocatePassthrough(3, objSpacePosition) +#endif + +#if SHADOWS +@shAllocatePassthrough(4, lightSpacePos0) +#endif +#if SHADOWS_PSSM +@shForeach(3) + @shAllocatePassthrough(4, lightSpacePos@shIterator) +@shEndForeach +#endif + #ifdef SH_VERTEX_SHADER + // ------------------------------------- VERTEX --------------------------------------- + SH_BEGIN_PROGRAM shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) shUniform(float4x4, viewProjMatrix) @shAutoConstant(viewProjMatrix, viewproj_matrix) + shUniform(float2, lodMorph) @shAutoConstant(lodMorph, custom, 1001) + shInput(float2, uv0) + shInput(float2, delta) // lodDelta, lodThreshold + +#if SHADOWS + shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) +#endif + +#if SHADOWS_PSSM + @shForeach(3) + shUniform(float4x4, texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator) + @shEndForeach +#endif + @shPassthroughVertexOutputs @@ -36,16 +74,54 @@ float4 worldPos = shMatrixMult(worldMatrix, shInputPosition); + // determine whether to apply the LOD morph to this vertex + // we store the deltas against all vertices so we only want to apply + // the morph to the ones which would disappear. The target LOD which is + // being morphed to is stored in lodMorph.y, and the LOD at which + // the vertex should be morphed is stored in uv.w. If we subtract + // the former from the latter, and arrange to only morph if the + // result is negative (it will only be -1 in fact, since after that + // the vertex will never be indexed), we will achieve our aim. + // sign(vertexLOD - targetLOD) == -1 is to morph + float toMorph = -min(0, sign(delta.y - lodMorph.y)); + + // morph + // this assumes XZ terrain alignment + worldPos.y += delta.x * toMorph * lodMorph.x; + shOutputPosition = shMatrixMult(viewProjMatrix, worldPos); +#if NEED_DEPTH @shPassthroughAssign(depth, shOutputPosition.z); +#endif + @shPassthroughAssign(UV, uv0); + +#if LIGHTING + @shPassthroughAssign(objSpacePosition, shInputPosition.xyz); +#endif + +#if SHADOWS + float4 lightSpacePos = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); + @shPassthroughAssign(lightSpacePos0, lightSpacePos); +#endif +#if SHADOWS_PSSM + float4 wPos = shMatrixMult(worldMatrix, shInputPosition); + + float4 lightSpacePos; + @shForeach(3) + lightSpacePos = shMatrixMult(texViewProjMatrix@shIterator, wPos); + @shPassthroughAssign(lightSpacePos@shIterator, lightSpacePos); + @shEndForeach +#endif } #else + // ----------------------------------- FRAGMENT ------------------------------------------ + SH_BEGIN_PROGRAM @@ -55,10 +131,11 @@ shSampler2D(normalMap) // global normal map + @shForeach(@shPropertyString(num_blendmaps)) shSampler2D(blendMap@shIterator) @shEndForeach - + @shForeach(@shPropertyString(num_layers)) shSampler2D(diffuseMap@shIterator) @shEndForeach @@ -76,21 +153,57 @@ #endif +#if LIGHTING + shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) + @shForeach(@shGlobalSettingString(num_lights)) + shUniform(float4, lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) + shUniform(float4, lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) + shUniform(float4, lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) + @shEndForeach +#endif + + +#if SHADOWS + shSampler2D(shadowMap0) + shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, @shPropertyString(shadowtexture_offset)) +#endif +#if SHADOWS_PSSM + @shForeach(3) + shSampler2D(shadowMap@shIterator) + shUniform(float2, invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(@shPropertyString(shadowtexture_offset))) + @shEndForeach + shUniform(float3, pssmSplitPoints) @shSharedParameter(pssmSplitPoints) +#endif + +#if SHADOWS || SHADOWS_PSSM + shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) +#endif + SH_START_PROGRAM { +#if NEED_DEPTH float depth = @shPassthroughReceive(depth); +#endif + float2 UV = @shPassthroughReceive(UV); +#if LIGHTING + float3 objSpacePosition = @shPassthroughReceive(objSpacePosition); + float3 normal = shSample(normalMap, UV).rgb * 2 - 1; + normal = normalize(normal); +#endif + - // fetch blendmaps + + // Layer calculations @shForeach(@shPropertyString(num_blendmaps)) float4 blendValues@shIterator = shSample(blendMap@shIterator, UV); @shEndForeach - float3 albedo; + float3 albedo = float3(0,0,0); @shForeach(@shPropertyString(num_layers)) @@ -98,9 +211,11 @@ // first layer of first pass doesn't need a blend map albedo = shSample(diffuseMap0, UV * 10).rgb; #else - #define BLEND_AMOUNT blendValues@shPropertyString(blendmap_index_@shIterator).@shPropertyString(blendmap_component_@shIterator) + #define BLEND_AMOUNT blendValues@shPropertyString(blendmap_component_@shIterator) + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, UV * 10).rgb, BLEND_AMOUNT); + #endif @shEndForeach @@ -113,6 +228,63 @@ shOutputColour(0).rgb *= albedo; + + + + + // Lighting + +#if LIGHTING + // shadows only for the first (directional) light +#if SHADOWS + float4 lightSpacePos0 = @shPassthroughReceive(lightSpacePos0); + float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0); +#endif +#if SHADOWS_PSSM + @shForeach(3) + float4 lightSpacePos@shIterator = @shPassthroughReceive(lightSpacePos@shIterator); + @shEndForeach + + float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depth, pssmSplitPoints); +#endif + +#if SHADOWS || SHADOWS_PSSM + float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; + float fade = 1-((depth - shadowFar_fadeStart.y) / fadeRange); + shadow = (depth > shadowFar_fadeStart.x) ? 1 : ((depth > shadowFar_fadeStart.y) ? 1-((1-shadow)*fade) : shadow); +#endif + +#if !SHADOWS && !SHADOWS_PSSM + float shadow = 1.0; +#endif + + + + float3 lightDir; + float3 diffuse = float3(0,0,0); + float d; + + @shForeach(@shGlobalSettingString(num_lights)) + + lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePosition.xyz * lightPosObjSpace@shIterator.w); + d = length(lightDir); + + + lightDir = normalize(lightDir); + +#if @shIterator == 0 && (SHADOWS || SHADOWS_PSSM) + diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; +#else + diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); +#endif + @shEndForeach + + shOutputColour(0).xyz *= (lightAmbient.xyz + diffuse); +#endif + + + + #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); diff --git a/files/materials/terrain.shaderset b/files/materials/terrain.shaderset index d31429dbb..4132b8e9c 100644 --- a/files/materials/terrain.shaderset +++ b/files/materials/terrain.shaderset @@ -2,7 +2,7 @@ shader_set terrain_vertex { source terrain.shader type vertex - profiles_cg vs_2_0 arbvp1 + profiles_cg vs_2_0 vp40 arbvp1 profiles_hlsl vs_2_0 } @@ -10,6 +10,6 @@ shader_set terrain_fragment { source terrain.shader type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 + profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 profiles_hlsl ps_2_0 } From 9ed2f1df6716e2d1a065970ba8122df301da43fc Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jul 2012 08:39:34 +0200 Subject: [PATCH 104/298] boost wave not needed anymore --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 05287439b..d5ea17657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,7 +195,7 @@ endif() find_package(OGRE REQUIRED) find_package(MyGUI REQUIRED) -find_package(Boost REQUIRED COMPONENTS system filesystem program_options thread wave) +find_package(Boost REQUIRED COMPONENTS system filesystem program_options thread) find_package(OIS REQUIRED) find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) From 6a447c88fb8349e17846dde2ef0d83c2fe24be58 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 06:45:21 -0700 Subject: [PATCH 105/298] Create meshes from the NiTriShapes in the NIF. This doesn't actually load them yet. It's also very slow for certain NIFs. --- components/nifogre/ogre_nif_loader.cpp | 72 ++++++++++++++++++++++---- components/nifogre/ogre_nif_loader.hpp | 9 +++- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index a479e78a5..40740ac56 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -132,13 +132,13 @@ NIFLoader::LoaderMap NIFLoader::sLoaders; void NIFLoader::warn(const std::string &msg) { - std::cerr << "NIFLoader: Warn:" << msg << "\n"; + std::cerr << "NIFLoader: Warn: " << msg << std::endl; } void NIFLoader::fail(const std::string &msg) { std::cerr << "NIFLoader: Fail: "<< msg << std::endl; - assert(1); + abort(); } @@ -325,24 +325,74 @@ void NIFLoader::loadResource(Ogre::Resource *resource) warn("Found no records in NIF for "+resource->getName()); } +void NIFLoader::createMeshes(const std::string &name, const std::string &group, Nif::Node *node, MeshPairList &meshes, int flags) +{ + flags |= node->flags; + + // TODO: Check for extra data + + if(node->recType == Nif::RC_NiTriShape) + { + Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); + std::string fullname = name+"@"+node->name; + + Ogre::MeshPtr mesh = meshMgr.getByName(fullname); + if(mesh.isNull()) + { + NIFLoader *loader = &sLoaders[fullname]; + loader->mName = name; + loader->mShapeName = node->name; + + mesh = meshMgr.createManual(fullname, group, loader); + } + + meshes.push_back(std::make_pair(mesh, (node->parent ? node->parent->name : std::string()))); + } + else if(node->recType != Nif::RC_NiNode && node->recType != Nif::RC_RootCollisionNode && + node->recType != Nif::RC_NiRotatingParticles) + warn("Unhandled mesh node type: "+node->recName); + + Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) + { + Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();i++) + { + if(!children[i].empty()) + createMeshes(name, group, children[i].getPtr(), meshes, flags); + } + } +} + MeshPairList NIFLoader::load(const std::string &name, Ogre::SkeletonPtr *skel, const std::string &group) { - Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); - MeshPairList ret; + MeshPairList meshes; if(skel != NULL) skel->setNull(); - // Check if the resource already exists - Ogre::MeshPtr themesh = meshMgr.getByName(name, group); - if(themesh.isNull()) + Nif::NIFFile nif(name); + if (nif.numRecords() < 1) { - NIFLoader *loader = &sLoaders[name]; - themesh = meshMgr.createManual(name, group, loader); + nif.warn("Found no records in NIF."); + return meshes; } - ret.push_back(std::make_pair(themesh, std::string())); - return ret; + // The first record is assumed to be the root node + Nif::Record *r = nif.getRecord(0); + assert(r != NULL); + + Nif::Node *node = dynamic_cast(r); + if(node == NULL) + { + nif.warn("First record in file was not a node, but a "+ + r->recName+". Skipping file."); + return meshes; + } + + createMeshes(name, group, node, meshes); + + return meshes; } diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 1eef7eb37..092cc23a8 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -96,8 +96,13 @@ public: const std::string &group="General"); private: - void warn(const std::string &msg); - void fail(const std::string &msg); + std::string mName; + std::string mShapeName; + + static void warn(const std::string &msg); + static void fail(const std::string &msg); + + static void createMeshes(const std::string &name, const std::string &group, Nif::Node *node, MeshPairList &meshes, int flags=0); typedef std::map LoaderMap; static LoaderMap sLoaders; From a8ebb39883fba74f7e1cb64529f50ba0e81f65d5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 07:41:19 -0700 Subject: [PATCH 106/298] Avoid Mangle for BSA accesses The way it was set up was not very efficient, and we're using Ogre for resource management anyway, so it's best to just use that. --- components/bsa/bsa_archive.cpp | 7 +- components/bsa/bsa_file.cpp | 328 ++++++++++++++++++++------------- components/bsa/bsa_file.hpp | 178 +++++++++--------- 3 files changed, 282 insertions(+), 231 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index e9ce3f615..07921da69 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -28,13 +28,11 @@ #include #include #include "bsa_file.hpp" -#include namespace { using namespace Ogre; -using namespace Mangle::Stream; using namespace Bsa; struct ciLessBoost : std::binary_function @@ -239,10 +237,7 @@ public: // Open the file - StreamPtr strm = narc->getFile(passed.c_str()); - - // Wrap it into an Ogre::DataStream. - return DataStreamPtr(new Mangle2OgreStream(strm)); + return narc->getFile(passed.c_str()); } bool exists(const String& filename) { diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index f19606703..b5145a4e4 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -23,171 +23,235 @@ #include "bsa_file.hpp" -#include -#include - #include #include #include +#include + using namespace std; -using namespace Mangle::Stream; using namespace Bsa; +class ConstrainedDataStream : public Ogre::DataStream { + std::ifstream mStream; + const size_t mStart; + size_t mPos; + bool mIsEOF; + +public: + ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length) + : mStream(fname.c_str(), std::ios_base::binary), mStart(start), mPos(0), mIsEOF(false) + { + mSize = length; + if(!mStream.seekg(mStart, std::ios_base::beg)) + throw std::runtime_error("Error seeking to start of BSA entry"); + } + + ConstrainedDataStream(const Ogre::String &name, const Ogre::String &fname, + size_t start, size_t length) + : Ogre::DataStream(name), mStream(fname.c_str(), std::ios_base::binary), + mStart(start), mPos(0), mIsEOF(false) + { + mSize = length; + if(!mStream.seekg(mStart, std::ios_base::beg)) + throw std::runtime_error("Error seeking to start of BSA entry"); + } + + + virtual size_t read(void *buf, size_t count) + { + mStream.clear(); + + if(count > mSize-mPos) + { + count = mSize-mPos; + mIsEOF = true; + } + mStream.read(reinterpret_cast(buf), count); + + count = mStream.gcount(); + mPos += count; + return count; + } + + virtual void skip(long count) + { + if((count >= 0 && (size_t)count <= mSize-mPos) || + (count < 0 && (size_t)-count <= mPos)) + { + mStream.clear(); + if(mStream.seekg(count, std::ios_base::cur)) + { + mPos += count; + mIsEOF = false; + } + } + } + + virtual void seek(size_t pos) + { + if(pos < mSize) + { + mStream.clear(); + if(mStream.seekg(pos+mStart, std::ios_base::beg)) + { + mPos = pos; + mIsEOF = false; + } + } + } + + virtual size_t tell() const + { return mPos; } + + virtual bool eof() const + { return mIsEOF; } + + virtual void close() + { mStream.close(); } +}; + + /// Error handling void BSAFile::fail(const string &msg) { - throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + filename); + throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + filename); } /// Read header information from the input source void BSAFile::readHeader() { - /* - * The layout of a BSA archive is as follows: - * - * - 12 bytes header, contains 3 ints: - * id number - equal to 0x100 - * dirsize - size of the directory block (see below) - * numfiles - number of files - * - * ---------- start of directory block ----------- - * - * - 8 bytes*numfiles, each record contains: - * fileSize - * offset into data buffer (see below) - * - * - 4 bytes*numfiles, each record is an offset into the following name buffer - * - * - name buffer, indexed by the previous table, each string is - * null-terminated. Size is (dirsize - 12*numfiles). - * - * ---------- end of directory block ------------- - * - * - 8*filenum - hash table block, we currently ignore this - * - * ----------- start of data buffer -------------- - * - * - The rest of the archive is file data, indexed by the - * offsets in the directory block. The offsets start at 0 at - * the beginning of this buffer. - * - */ - assert(!isLoaded); - assert(input); - assert(input->hasSize); - assert(input->hasPosition); - assert(input->isSeekable); - - // Total archive size - size_t fsize = input->size(); - - if( fsize < 12 ) - fail("File too small to be a valid BSA archive"); - - // Get essential header numbers - size_t dirsize, filenum; - - { - // First 12 bytes - uint32_t head[3]; - - input->read(head, 12); - - if(head[0] != 0x100) - fail("Unrecognized BSA header"); - - // Total number of bytes used in size/offset-table + filename - // sections. - dirsize = head[1]; - - // Number of files - filenum = head[2]; - } - - // Each file must take up at least 21 bytes of data in the bsa. So - // if files*21 overflows the file size then we are guaranteed that - // the archive is corrupt. - if( (filenum*21 > fsize -12) || - (dirsize+8*filenum > fsize -12) ) - fail("Directory information larger than entire archive"); - - // Read the offset info into a temporary buffer - vector offsets(3*filenum); - input->read(&offsets[0], 12*filenum); - - // Read the string table - stringBuf.resize(dirsize-12*filenum); - input->read(&stringBuf[0], stringBuf.size()); - - // Check our position - assert(input->tell() == 12+dirsize); - - // Calculate the offset of the data buffer. All file offsets are - // relative to this. 12 header bytes + directory + hash table - // (skipped) - size_t fileDataOffset = 12 + dirsize + 8*filenum; - - // Set up the the FileStruct table - files.resize(filenum); - for(size_t i=0;i(head), 12); + + if(head[0] != 0x100) + fail("Unrecognized BSA header"); + + // Total number of bytes used in size/offset-table + filename + // sections. + dirsize = head[1]; + + // Number of files + filenum = head[2]; + } + + // Each file must take up at least 21 bytes of data in the bsa. So + // if files*21 overflows the file size then we are guaranteed that + // the archive is corrupt. + if((filenum*21 > fsize -12) || (dirsize+8*filenum > fsize -12) ) + fail("Directory information larger than entire archive"); + + // Read the offset info into a temporary buffer + vector offsets(3*filenum); + input.read(reinterpret_cast(&offsets[0]), 12*filenum); - if(fs.offset + fs.fileSize > fsize) - fail("Archive contains offsets outside itself"); + // Read the string table + stringBuf.resize(dirsize-12*filenum); + input.read(&stringBuf[0], stringBuf.size()); - // Add the file name to the lookup - lookup[fs.name] = i; + // Check our position + assert(input.tellg() == 12+dirsize); + + // Calculate the offset of the data buffer. All file offsets are + // relative to this. 12 header bytes + directory + hash table + // (skipped) + size_t fileDataOffset = 12 + dirsize + 8*filenum; + + // Set up the the FileStruct table + files.resize(filenum); + for(size_t i=0;i fsize) + fail("Archive contains offsets outside itself"); + + // Add the file name to the lookup + lookup[fs.name] = i; } - isLoaded = true; + isLoaded = true; } /// Get the index of a given file name, or -1 if not found int BSAFile::getIndex(const char *str) const { - Lookup::const_iterator it; - it = lookup.find(str); + Lookup::const_iterator it = lookup.find(str); + if(it == lookup.end()) + return -1; - if(it == lookup.end()) return -1; - else - { - int res = it->second; - assert(res >= 0 && res < static_cast (files.size())); - return res; - } + int res = it->second; + assert(res >= 0 && (size_t)res < files.size()); + return res; } /// Open an archive file. void BSAFile::open(const string &file) { - filename = file; - input = StreamPtr(new FileStream(file)); - readHeader(); + filename = file; + readHeader(); } -/** Open an archive from a generic stream. The 'name' parameter is - used for error messages. -*/ -void BSAFile::open(StreamPtr inp, const string &name) +Ogre::DataStreamPtr BSAFile::getFile(const char *file) { - filename = name; - input = inp; - readHeader(); -} - -StreamPtr BSAFile::getFile(const char *file) -{ - assert(file); - int i = getIndex(file); - if(i == -1) - fail("File not found: " + string(file)); - - FileStruct &fs = files[i]; + assert(file); + int i = getIndex(file); + if(i == -1) + fail("File not found: " + string(file)); - return StreamPtr(new SliceStream(input, fs.offset, fs.fileSize)); + const FileStruct &fs = files[i]; + return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, fs.offset, fs.fileSize)); } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 95fac0f4d..afe0c739c 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -24,13 +24,15 @@ #ifndef BSA_BSA_FILE_H #define BSA_BSA_FILE_H -#include #include #include #include #include #include +#include + + namespace Bsa { @@ -39,98 +41,88 @@ namespace Bsa */ class BSAFile { - public: - - /// Represents one file entry in the archive - struct FileStruct - { - // File size and offset in file. We store the offset from the - // beginning of the file, not the offset into the data buffer - // (which is what is stored in the archive.) - uint32_t fileSize, offset; - - // Zero-terminated file name - char* name; - }; - - typedef std::vector FileList; - - private: - - /// The archive source - Mangle::Stream::StreamPtr input; - - /// Table of files in this archive - FileList files; - - /// Filename string buffer - std::vector stringBuf; - - /// True when an archive has been loaded - bool isLoaded; - - /// Used for error messages - std::string filename; - - /// Case insensitive string comparison - struct iltstr - { - bool operator()(const char *s1, const char *s2) const - { return strcasecmp(s1,s2) < 0; } - }; - - /** A map used for fast file name lookup. The value is the index into - the files[] vector above. The iltstr ensures that file name - checks are case insensitive. - */ - typedef std::map Lookup; - Lookup lookup; - - /// Error handling - void fail(const std::string &msg); - - /// Read header information from the input source - void readHeader(); - - /// Get the index of a given file name, or -1 if not found - int getIndex(const char *str) const; - - public: - - /* ----------------------------------- - * BSA management methods - * ----------------------------------- - */ - - BSAFile() - : input(), isLoaded(false) {} - - /// Open an archive file. - void open(const std::string &file); - - /** Open an archive from a generic stream. The 'name' parameter is - used for error messages. - */ - void open(Mangle::Stream::StreamPtr inp, const std::string &name); - - /* ----------------------------------- - * Archive file routines - * ----------------------------------- - */ - - /// Check if a file exists - bool exists(const char *file) const { return getIndex(file) != -1; } - - /** Open a file contained in the archive. Throws an exception if the - file doesn't exist. - - NOTE: All files opened from one archive will share a common file - handle. This is NOT thread safe. - */ - Mangle::Stream::StreamPtr getFile(const char *file); - - /// Get a list of all files - const FileList &getList() const +public: + /// Represents one file entry in the archive + struct FileStruct + { + // File size and offset in file. We store the offset from the + // beginning of the file, not the offset into the data buffer + // (which is what is stored in the archive.) + uint32_t fileSize, offset; + + // Zero-terminated file name + const char *name; + }; + typedef std::vector FileList; + +private: + /// Table of files in this archive + FileList files; + + /// Filename string buffer + std::vector stringBuf; + + /// True when an archive has been loaded + bool isLoaded; + + /// Used for error messages + std::string filename; + + /// Case insensitive string comparison + struct iltstr + { + bool operator()(const char *s1, const char *s2) const + { return strcasecmp(s1,s2) < 0; } + }; + + /** A map used for fast file name lookup. The value is the index into + the files[] vector above. The iltstr ensures that file name + checks are case insensitive. + */ + typedef std::map Lookup; + Lookup lookup; + + /// Error handling + void fail(const std::string &msg); + + /// Read header information from the input source + void readHeader(); + + /// Get the index of a given file name, or -1 if not found + int getIndex(const char *str) const; + +public: + /* ----------------------------------- + * BSA management methods + * ----------------------------------- + */ + + BSAFile() + : isLoaded(false) + { } + + /// Open an archive file. + void open(const std::string &file); + + /* ----------------------------------- + * Archive file routines + * ----------------------------------- + */ + + /// Check if a file exists + bool exists(const char *file) const + { return getIndex(file) != -1; } + + /** Open a file contained in the archive. Throws an exception if the + file doesn't exist. + + NOTE: All files opened from one archive will share a common file + handle. This is NOT thread safe. + */ + Ogre::DataStreamPtr getFile(const char *file); + + /// Get a list of all files + const FileList &getList() const { return files; } }; From 7734771245a58dec8ce7c1d312a7f3b268141427 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 08:31:03 -0700 Subject: [PATCH 107/298] Use Ogre to load ESM data instead of Mangle --- components/esm/esm_reader.cpp | 18 ++++++++++-------- components/esm/esm_reader.hpp | 10 +++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/components/esm/esm_reader.cpp b/components/esm/esm_reader.cpp index e9bfcf5ee..6b8409b45 100644 --- a/components/esm/esm_reader.cpp +++ b/components/esm/esm_reader.cpp @@ -27,7 +27,7 @@ void ESMReader::restoreContext(const ESM_Context &rc) void ESMReader::close() { - mEsm.reset(); + mEsm.setNull(); mCtx.filename.clear(); mCtx.leftFile = 0; mCtx.leftRec = 0; @@ -37,7 +37,7 @@ void ESMReader::close() mCtx.subName.val = 0; } -void ESMReader::openRaw(Mangle::Stream::StreamPtr _esm, const std::string &name) +void ESMReader::openRaw(Ogre::DataStreamPtr _esm, const std::string &name) { close(); mEsm = _esm; @@ -57,7 +57,7 @@ void ESMReader::openRaw(Mangle::Stream::StreamPtr _esm, const std::string &name) mSpf = SF_Other; } -void ESMReader::open(Mangle::Stream::StreamPtr _esm, const std::string &name) +void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name) { openRaw(_esm, name); @@ -107,14 +107,16 @@ void ESMReader::open(Mangle::Stream::StreamPtr _esm, const std::string &name) void ESMReader::open(const std::string &file) { - using namespace Mangle::Stream; - open(StreamPtr(new FileStream(file)), file); + std::ifstream *stream = new std::ifstream(file.c_str(), std::ios_base::binary); + // Ogre will delete the stream for us + open(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), file); } void ESMReader::openRaw(const std::string &file) { - using namespace Mangle::Stream; - openRaw(StreamPtr(new FileStream(file)), file); + std::ifstream *stream = new std::ifstream(file.c_str(), std::ios_base::binary); + // Ogre will delete the stream for us + openRaw(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), file); } int64_t ESMReader::getHNLong(const char *name) @@ -339,7 +341,7 @@ void ESMReader::fail(const std::string &msg) ss << "\n File: " << mCtx.filename; ss << "\n Record: " << mCtx.recName.toString(); ss << "\n Subrecord: " << mCtx.subName.toString(); - if (mEsm != NULL) + if (!mEsm.isNull()) ss << "\n Offset: 0x" << hex << mEsm->tell(); throw std::runtime_error(ss.str()); } diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index 340482891..17cca7a91 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -11,8 +11,8 @@ #include #include -#include -#include +#include + #include #include @@ -183,11 +183,11 @@ public: /// Raw opening. Opens the file and sets everything up but doesn't /// parse the header. - void openRaw(Mangle::Stream::StreamPtr _esm, const std::string &name); + void openRaw(Ogre::DataStreamPtr _esm, const std::string &name); /// Load ES file from a new stream, parses the header. Closes the /// currently open file first, if any. - void open(Mangle::Stream::StreamPtr _esm, const std::string &name); + void open(Ogre::DataStreamPtr _esm, const std::string &name); void open(const std::string &file); @@ -354,7 +354,7 @@ public: void setEncoding(const std::string& encoding); private: - Mangle::Stream::StreamPtr mEsm; + Ogre::DataStreamPtr mEsm; ESM_Context mCtx; From 2a3ce5ee6df8da1ca3a98dcf7080b834b4691692 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 08:40:18 -0700 Subject: [PATCH 108/298] Remove Mangle::Stream The base Stream class is left because some part of the terrain esm land factory inherits from it, though it's largely unused (few of the methods work, and none actually do anything). --- CMakeLists.txt | 3 +- libs/mangle/stream/clients/audiere_file.cpp | 32 --- libs/mangle/stream/clients/audiere_file.hpp | 38 --- libs/mangle/stream/clients/io_stream.cpp | 221 ------------------ libs/mangle/stream/clients/io_stream.hpp | 43 ---- .../mangle/stream/clients/ogre_datastream.hpp | 68 ------ libs/mangle/stream/filters/buffer_stream.hpp | 74 ------ libs/mangle/stream/filters/pure_filter.hpp | 46 ---- libs/mangle/stream/filters/slice_stream.hpp | 101 -------- libs/mangle/stream/servers/file_stream.hpp | 32 --- libs/mangle/stream/servers/memory_stream.hpp | 116 --------- .../mangle/stream/servers/ogre_datastream.hpp | 37 --- libs/mangle/stream/servers/outfile_stream.hpp | 41 ---- libs/mangle/stream/servers/phys_stream.hpp | 36 --- libs/mangle/stream/servers/std_ostream.hpp | 78 ------- libs/mangle/stream/servers/std_stream.hpp | 70 ------ 16 files changed, 1 insertion(+), 1035 deletions(-) delete mode 100644 libs/mangle/stream/clients/audiere_file.cpp delete mode 100644 libs/mangle/stream/clients/audiere_file.hpp delete mode 100644 libs/mangle/stream/clients/io_stream.cpp delete mode 100644 libs/mangle/stream/clients/io_stream.hpp delete mode 100644 libs/mangle/stream/clients/ogre_datastream.hpp delete mode 100644 libs/mangle/stream/filters/buffer_stream.hpp delete mode 100644 libs/mangle/stream/filters/pure_filter.hpp delete mode 100644 libs/mangle/stream/filters/slice_stream.hpp delete mode 100644 libs/mangle/stream/servers/file_stream.hpp delete mode 100644 libs/mangle/stream/servers/memory_stream.hpp delete mode 100644 libs/mangle/stream/servers/ogre_datastream.hpp delete mode 100644 libs/mangle/stream/servers/outfile_stream.hpp delete mode 100644 libs/mangle/stream/servers/phys_stream.hpp delete mode 100644 libs/mangle/stream/servers/std_ostream.hpp delete mode 100644 libs/mangle/stream/servers/std_stream.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a8a8ad18b..e3175fa7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,9 +101,8 @@ ENDIF() set(LIBDIR ${CMAKE_SOURCE_DIR}/libs) -set(MANGLE_VFS ${LIBDIR}/mangle/vfs/servers/ogre_vfs.cpp) set(MANGLE_INPUT ${LIBDIR}/mangle/input/servers/ois_driver.cpp) -set(MANGLE_ALL ${MANGLE_VFS} ${MANGLE_INPUT}) +set(MANGLE_ALL ${MANGLE_INPUT}) source_group(libs\\mangle FILES ${MANGLE_ALL}) set(OENGINE_OGRE diff --git a/libs/mangle/stream/clients/audiere_file.cpp b/libs/mangle/stream/clients/audiere_file.cpp deleted file mode 100644 index 16bc7891a..000000000 --- a/libs/mangle/stream/clients/audiere_file.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "audiere_file.hpp" - -using namespace audiere; -using namespace Mangle::Stream; - -bool AudiereFile::seek(int pos, SeekMode mode) -{ - assert(inp->isSeekable); - assert(inp->hasPosition); - - size_t newPos; - - switch(mode) - { - case BEGIN: newPos = pos; break; - case CURRENT: newPos = pos+tell(); break; - case END: - // Seeking from the end. This requires that we're able to get - // the entire size of the stream. The pos also has to be - // non-positive. - assert(inp->hasSize); - assert(pos <= 0); - newPos = inp->size() + pos; - break; - default: - assert(0 && "invalid seek mode"); - } - - inp->seek(newPos); - return inp->tell() == newPos; - -} diff --git a/libs/mangle/stream/clients/audiere_file.hpp b/libs/mangle/stream/clients/audiere_file.hpp deleted file mode 100644 index 61e26f21b..000000000 --- a/libs/mangle/stream/clients/audiere_file.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef MANGLE_STREAM_AUDIERECLIENT_H -#define MANGLE_STREAM_AUDIERECLIENT_H - -#include -#include - -#include "../stream.hpp" - -namespace Mangle { -namespace Stream { - -/** @brief An Audiere::File that wraps a Mangle::Stream input. - - This lets Audiere read sound files from any generic archive or - file manager that supports Mangle streams. - */ -class AudiereFile : public audiere::RefImplementation -{ - StreamPtr inp; - - public: - AudiereFile(StreamPtr _inp) - : inp(_inp) {} - - /// Read 'count' bytes, return bytes successfully read - int ADR_CALL read(void *buf, int count) - { return inp->read(buf,count); } - - /// Seek, relative to specified seek mode. Returns true if successful. - bool ADR_CALL seek(int pos, audiere::File::SeekMode mode); - - /// Get current position - int ADR_CALL tell() - { assert(inp->hasPosition); return inp->tell(); } -}; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/clients/io_stream.cpp b/libs/mangle/stream/clients/io_stream.cpp deleted file mode 100644 index 5f1edc221..000000000 --- a/libs/mangle/stream/clients/io_stream.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "io_stream.hpp" - -// This seems to work -#ifndef EOF -#define EOF -1 -#endif - -using namespace Mangle::Stream; - -#define BSIZE 1024 - -// Streambuf for normal stream reading -class _istreambuf : public std::streambuf -{ - StreamPtr client; - char buf[BSIZE]; - -public: - _istreambuf(StreamPtr strm) : client(strm) - { - // Make sure we picked the right class - assert(client->isReadable); - assert(!client->hasPtr); - - // Tell streambuf to delegate reading operations to underflow() - setg(NULL,NULL,NULL); - - // Disallow writing - setp(NULL,NULL); - } - - /* Underflow is called when there is no more info to read in the - input buffer. We need to refill buf with new data (if any), and - set up the internal pointers with setg() to reflect the new - state. - */ - int underflow() - { - // Read some more data - size_t read = client->read(buf, BSIZE); - assert(read <= BSIZE); - - // If we're out of data, then EOF - if(read == 0) - return EOF; - - // Otherwise, set up input buffer - setg(buf, buf, buf+read); - - // Return the first char - return *((unsigned char*)buf); - } - - // Seek stream, if the source supports it. Ignores the second - // parameter as Mangle doesn't separate input and output pointers. - std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in) - { - // Does this stream know how to seek? - if(!client->isSeekable || !client->hasPosition) - // If not, signal an error. - return -1; - - // Set stream position and reset the buffer. - client->seek(pos); - setg(NULL,NULL,NULL); - - return client->tell(); - } -}; - -// Streambuf optimized for pointer-based input streams -class _ptrstreambuf : public std::streambuf -{ - StreamPtr client; - -public: - _ptrstreambuf(StreamPtr strm) : client(strm) - { - // Make sure we picked the right class - assert(client->isReadable); - assert(client->hasPtr); - - // seekpos() does all the work - seekpos(0); - } - - // Underflow is only called when we're at the end of the file - int underflow() { return EOF; } - - // Seek to a new position within the memory stream. This bypasses - // client->seek() entirely so isSeekable doesn't have to be set. - std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in) - { - // All pointer streams need a size - assert(client->hasSize); - - // Figure out how much will be left of the stream after seeking - size_t size = client->size() - pos; - - // Get a pointer - char* ptr = (char*)client->getPtr(pos,size); - - // And use it - setg(ptr,ptr,ptr+size); - - return pos; - } -}; - -// Streambuf for stream writing -class _ostreambuf : public std::streambuf -{ - StreamPtr client; - char buf[BSIZE]; - -public: - _ostreambuf(StreamPtr strm) : client(strm) - { - // Make sure we picked the right class - assert(client->isWritable); - - // Inform streambuf about our nice buffer - setp(buf, buf+BSIZE); - - // Disallow reading - setg(NULL,NULL,NULL); - } - - /* Sync means to flush (write) all current data to the output - stream. It will also set up the entire output buffer to be usable - again. - */ - int sync() - { - // Get the number of bytes that streambuf wants us to write - int num = pptr() - pbase(); - assert(num >= 0); - - // Is there any work to do? - if(num == 0) return 0; - - if((int)client->write(pbase(), num) != num) - // Inform caller that writing failed - return -1; - - // Reset output buffer pointers - setp(buf, buf+BSIZE); - - // No error - return 0; - } - - /* Called whenever the output buffer is full. - */ - int overflow(int c) - { - // First, write all existing data - if(sync()) return EOF; - - // Put the requested character in the next round of output - if(c != EOF) - { - *pptr() = c; - pbump(1); - } - - // No error - return 0; - } - - // Seek stream, if the source supports it. - std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::out) - { - if(!client->isSeekable || !client->hasPosition) - return -1; - - // Flush data and reset buffers - sync(); - - // Set stream position - client->seek(pos); - - return client->tell(); - } -}; - -MangleIStream::MangleIStream(StreamPtr inp) - : std::istream(NULL) -{ - assert(inp->isReadable); - - // Pick the right streambuf implementation based on whether the - // input supports pointers or not. - if(inp->hasPtr) - buf = new _ptrstreambuf(inp); - else - buf = new _istreambuf(inp); - - rdbuf(buf); -} - -MangleIStream::~MangleIStream() -{ - delete buf; -} - -MangleOStream::MangleOStream(StreamPtr out) - : std::ostream(NULL) -{ - assert(out->isWritable); - buf = new _ostreambuf(out); - - rdbuf(buf); -} - -MangleOStream::~MangleOStream() -{ - // Make sure we don't have lingering data on exit - flush(); - delete buf; -} diff --git a/libs/mangle/stream/clients/io_stream.hpp b/libs/mangle/stream/clients/io_stream.hpp deleted file mode 100644 index 98c6252ed..000000000 --- a/libs/mangle/stream/clients/io_stream.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef MANGLE_STREAM_IOSTREAM_H -#define MANGLE_STREAM_IOSTREAM_H - -#include -#include "../stream.hpp" -#include - -namespace Mangle { -namespace Stream { - - /** This file contains classes for wrapping an std::istream or - std::ostream around a Mangle::Stream. - - This allows you to use Mangle streams in places that require std - streams. - - This is much easier than trying to make your own custom streams - into iostreams. The std::iostream interface is horrible and NOT - designed for easy subclassing. Create a Mangle::Stream instead, - and use this wrapper. - */ - - // An istream wrapping a readable Mangle::Stream. Has extra - // optimizations for pointer-based streams. - class MangleIStream : public std::istream - { - std::streambuf *buf; - public: - MangleIStream(StreamPtr inp); - ~MangleIStream(); - }; - - // An ostream wrapping a writable Mangle::Stream. - class MangleOStream : public std::ostream - { - std::streambuf *buf; - public: - MangleOStream(StreamPtr inp); - ~MangleOStream(); - }; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/clients/ogre_datastream.hpp b/libs/mangle/stream/clients/ogre_datastream.hpp deleted file mode 100644 index 76a6f20cf..000000000 --- a/libs/mangle/stream/clients/ogre_datastream.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef MANGLE_STREAM_OGRECLIENT_H -#define MANGLE_STREAM_OGRECLIENT_H - -#include -#include -#include "../stream.hpp" - -namespace Mangle { -namespace Stream { - -/** An OGRE DataStream that wraps a Mangle::Stream input. - - This has been built and tested against OGRE 1.6.2. You might have - to make your own modifications if you're working with newer (or - older) versions. - */ -class Mangle2OgreStream : public Ogre::DataStream -{ - StreamPtr inp; - - void init() - { - // Get the size, if possible - if(inp->hasSize) - mSize = inp->size(); - - // Allow writing if inp supports it - if(inp->isWritable) - mAccess |= Ogre::DataStream::WRITE; - } - - public: - /// Constructor without name - Mangle2OgreStream(StreamPtr _inp) - : inp(_inp) { init(); } - - /// Constructor for a named data stream - Mangle2OgreStream(const Ogre::String &name, StreamPtr _inp) - : Ogre::DataStream(name), inp(_inp) { init(); } - - // Only implement the DataStream functions we have to implement - - size_t read(void *buf, size_t count) - { return inp->read(buf,count); } - - size_t write(const void *buf, size_t count) - { assert(inp->isWritable); return inp->write(buf,count); } - - void skip(long count) - { - assert(inp->isSeekable && inp->hasPosition); - inp->seek(inp->tell() + count); - } - - void seek(size_t pos) - { assert(inp->isSeekable); inp->seek(pos); } - - size_t tell() const - { assert(inp->hasPosition); return inp->tell(); } - - bool eof() const { return inp->eof(); } - - /// Does nothing - void close() {} -}; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/filters/buffer_stream.hpp b/libs/mangle/stream/filters/buffer_stream.hpp deleted file mode 100644 index f037212a3..000000000 --- a/libs/mangle/stream/filters/buffer_stream.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef MANGLE_STREAM_BUFFER_H -#define MANGLE_STREAM_BUFFER_H - -#include "../servers/memory_stream.hpp" -#include - -namespace Mangle { -namespace Stream { - -/** A Stream that reads another Stream into a buffer, and serves it as - a MemoryStream. Might be expanded with other capabilities later. - */ - -class BufferStream : public MemoryStream -{ - std::vector buffer; - - public: - /* - input = stream to copy - ADD = each read increment (for streams without size()) - */ - BufferStream(StreamPtr input, size_t ADD = 32*1024) - { - assert(input); - - // Allocate memory, read the stream into it. Then call set() - if(input->hasSize) - { - // We assume that we can get the position as well - assert(input->hasPosition); - - // Calculate how much is left of the stream - size_t left = input->size() - input->tell(); - - // Allocate the buffer and fill it - buffer.resize(left); - input->read(&buffer[0], left); - } - else - { - // We DON'T know how big the stream is. We'll have to read - // it in increments. - size_t len=0, newlen; - - while(!input->eof()) - { - // Read one block - newlen = len + ADD; - buffer.resize(newlen); - size_t read = input->read(&buffer[len], ADD); - - // Increase the total length - len += read; - - // If we read less than expected, we should be at the - // end of the stream - assert(read == ADD || (read < ADD && input->eof())); - } - - // Downsize to match the real length - buffer.resize(len); - } - - // After the buffer has been filled, set up our MemoryStream - // ancestor to reference it. - set(&buffer[0], buffer.size()); - } -}; - -typedef boost::shared_ptr BufferStreamPtr; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/filters/pure_filter.hpp b/libs/mangle/stream/filters/pure_filter.hpp deleted file mode 100644 index f0ce91f87..000000000 --- a/libs/mangle/stream/filters/pure_filter.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef MANGLE_STREAM_FILTER_H -#define MANGLE_STREAM_FILTER_H - -#include "../stream.hpp" - -namespace Mangle { -namespace Stream { - -/** A stream that filters another stream with no changes. Intended as - a base class for other filters. - */ -class PureFilter : public Stream -{ - protected: - StreamPtr src; - - public: - PureFilter() {} - PureFilter(StreamPtr _src) - { setStream(_src); } - - void setStream(StreamPtr _src) - { - src = _src; - isSeekable = src->isSeekable; - isWritable = src->isWritable; - hasPosition = src->hasPosition; - hasSize = src->hasSize; - hasPtr = src->hasPtr; - } - - size_t read(void *buf, size_t count) { return src->read(buf, count); } - size_t write(const void *buf, size_t count) { return src->write(buf,count); } - void flush() { src->flush(); } - void seek(size_t pos) { src->seek(pos); } - size_t tell() const { return src->tell(); } - size_t size() const { return src->size(); } - bool eof() const { return src->eof(); } - const void *getPtr() { return src->getPtr(); } - const void *getPtr(size_t size) { return src->getPtr(size); } - const void *getPtr(size_t pos, size_t size) - { return src->getPtr(pos, size); } -}; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/filters/slice_stream.hpp b/libs/mangle/stream/filters/slice_stream.hpp deleted file mode 100644 index 6337b9d57..000000000 --- a/libs/mangle/stream/filters/slice_stream.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef MANGLE_STREAM_SLICE_H -#define MANGLE_STREAM_SLICE_H - -#include "../stream.hpp" - -namespace Mangle { -namespace Stream { - -/** A Stream that represents a subset (called a slice) of another stream. - */ -class SliceStream : public Stream -{ - StreamPtr src; - size_t offset, length, pos; - - public: - SliceStream(StreamPtr _src, size_t _offset, size_t _length) - : src(_src), offset(_offset), length(_length), pos(0) - { - assert(src->hasSize); - assert(src->isSeekable); - - // Make sure we can actually fit inside the source stream - assert(src->size() >= offset+length); - - isSeekable = true; - hasPosition = true; - hasSize = true; - hasPtr = src->hasPtr; - isWritable = src->isWritable; - } - - size_t read(void *buf, size_t count) - { - // Check that we're not reading past our slice - if(count > length-pos) - count = length-pos; - - // Seek into place and start reading - src->seek(offset+pos); - count = src->read(buf, count); - - pos += count; - assert(pos <= length); - return count; - } - - // Note that writing to a slice does not allow you to append data, - // you may only overwrite existing data. - size_t write(const void *buf, size_t count) - { - assert(isWritable); - // Check that we're not reading past our slice - if(count > length-pos) - count = length-pos; - - // Seek into place and action - src->seek(offset+pos); - count = src->write(buf, count); - - pos += count; - assert(pos <= length); - return count; - } - - void seek(size_t _pos) - { - pos = _pos; - if(pos > length) pos = length; - } - - bool eof() const { return pos == length; } - size_t tell() const { return pos; } - size_t size() const { return length; } - void flush() { src->flush(); } - - const void *getPtr() { return getPtr(0, length); } - const void *getPtr(size_t size) - { - const void *ptr = getPtr(pos, size); - seek(pos+size); - return ptr; - } - const void *getPtr(size_t pos, size_t size) - { - // Boundry checks on pos and size. Bounding the size is - // important even when getting pointers, as the source stream - // may use the size parameter for something (such as memory - // mapping or buffering.) - if(pos > length) pos = length; - if(pos+size > length) size = length-pos; - - // Ask the source to kindly give us a pointer - return src->getPtr(offset+pos, size); - } -}; - -typedef boost::shared_ptr SliceStreamPtr; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/servers/file_stream.hpp b/libs/mangle/stream/servers/file_stream.hpp deleted file mode 100644 index 314a49642..000000000 --- a/libs/mangle/stream/servers/file_stream.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef MANGLE_STREAM_FILESERVER_H -#define MANGLE_STREAM_FILESERVER_H - -#include "std_stream.hpp" -#include -#include - -namespace Mangle { -namespace Stream { - -/** Very simple file input stream, based on std::ifstream - */ -class FileStream : public StdStream -{ - std::ifstream file; - - public: - FileStream(const std::string &name) - : StdStream(&file) - { - file.open(name.c_str(), std::ios::binary); - - if(file.fail()) - throw std::runtime_error("FileStream: failed to open file " + name); - } - ~FileStream() { file.close(); } -}; - -typedef boost::shared_ptr FileStreamPtr; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/servers/memory_stream.hpp b/libs/mangle/stream/servers/memory_stream.hpp deleted file mode 100644 index 0849e4a3c..000000000 --- a/libs/mangle/stream/servers/memory_stream.hpp +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef MANGLE_STREAM_MEMSERVER_H -#define MANGLE_STREAM_MEMSERVER_H - -#include -#include "../stream.hpp" -#include - -namespace Mangle { -namespace Stream { - -// Do this before the class declaration, since the class itself -// uses it. -class MemoryStream; -typedef boost::shared_ptr MemoryStreamPtr; - -/** A Stream wrapping a memory buffer - - This will create a fully seekable stream out of any pointer/length - pair you give it. - */ -class MemoryStream : public Stream -{ - const void *data; - size_t length, pos; - - public: - MemoryStream(const void *ptr, size_t len) - : data(ptr), length(len), pos(0) - { - isSeekable = true; - hasPosition = true; - hasSize = true; - hasPtr = true; - } - - MemoryStream() - : data(NULL), length(0), pos(0) - { - isSeekable = true; - hasPosition = true; - hasSize = true; - hasPtr = true; - } - - size_t read(void *buf, size_t count) - { - assert(data != NULL); - assert(pos <= length); - - // Don't read more than we have - if(count > (length - pos)) - count = length - pos; - - // Copy data - if(count) - memcpy(buf, ((char*)data)+pos, count); - - // aaand remember to increase the count - pos += count; - - return count; - } - - void seek(size_t _pos) - { - pos = _pos; - if(pos > length) - pos = length; - } - - size_t tell() const { return pos; } - size_t size() const { return length; } - bool eof() const { return pos == length; } - - const void *getPtr() { return data; } - const void *getPtr(size_t size) - { - // This variant of getPtr must move the position pointer - size_t opos = pos; - pos += size; - if(pos > length) pos = length; - return ((char*)data)+opos; - } - const void *getPtr(size_t pos, size_t size) - { - if(pos > length) pos = length; - return ((char*)data)+pos; - } - - // New members in MemoryStream: - - /// Set a new buffer and length. This will rewind the position to zero. - void set(const void* _data, size_t _length) - { - data = _data; - length = _length; - pos = 0; - } - - /// Clone this memory stream - /** Make a new stream of the same buffer. If setPos is true, we also - set the clone's position to be the same as ours. - - No memory is copied during this operation, the new stream is - just another 'view' into the same shared memory buffer. - */ - MemoryStreamPtr clone(bool setPos=false) const - { - MemoryStreamPtr res(new MemoryStream(data, length)); - if(setPos) res->seek(pos); - return res; - } -}; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/servers/ogre_datastream.hpp b/libs/mangle/stream/servers/ogre_datastream.hpp deleted file mode 100644 index a5be98c84..000000000 --- a/libs/mangle/stream/servers/ogre_datastream.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef MANGLE_STREAM_OGRESERVER_H -#define MANGLE_STREAM_OGRESERVER_H - -#include - -namespace Mangle { -namespace Stream { - -/** A Stream wrapping an OGRE DataStream. - - This has been built and tested against OGRE 1.6.2. You might have - to make your own modifications if you're working with newer (or - older) versions. - */ -class OgreStream : public Stream -{ - Ogre::DataStreamPtr inp; - - public: - OgreStream(Ogre::DataStreamPtr _inp) : inp(_inp) - { - isSeekable = true; - hasPosition = true; - hasSize = true; - } - - size_t read(void *buf, size_t count) { return inp->read(buf,count); } - void seek(size_t pos) { inp->seek(pos); } - size_t tell() const { return inp->tell(); } - size_t size() const { return inp->size(); } - bool eof() const { return inp->eof(); } -}; - -typedef boost::shared_ptr OgreStreamPtr; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/servers/outfile_stream.hpp b/libs/mangle/stream/servers/outfile_stream.hpp deleted file mode 100644 index 8d953d904..000000000 --- a/libs/mangle/stream/servers/outfile_stream.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef MANGLE_OSTREAM_FILESERVER_H -#define MANGLE_OSTREAM_FILESERVER_H - -#include "std_ostream.hpp" -#include - -namespace Mangle { -namespace Stream { - -/** File stream based on std::ofstream, only supports writing. - */ -class OutFileStream : public StdOStream -{ - std::ofstream file; - - public: - /** - By default we overwrite the file. If append=true, then we will - open an existing file and seek to the end instead. - */ - OutFileStream(const std::string &name, bool append=false) - : StdOStream(&file) - { - std::ios::openmode mode = std::ios::binary; - if(append) - mode |= std::ios::app; - else - mode |= std::ios::trunc; - - file.open(name.c_str(), mode); - - if(file.fail()) - throw std::runtime_error("OutFileStream: failed to open file " + name); - } - ~OutFileStream() { file.close(); } -}; - -typedef boost::shared_ptr OutFileStreamPtr; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/servers/phys_stream.hpp b/libs/mangle/stream/servers/phys_stream.hpp deleted file mode 100644 index 4312ac041..000000000 --- a/libs/mangle/stream/servers/phys_stream.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef MANGLE_STREAM_OGRESERVER_H -#define MANGLE_STREAM_OGRESERVER_H - -#include - -namespace Mangle { -namespace Stream { - -/// A Stream wrapping a PHYSFS_file stream from the PhysFS library. -class PhysFile : public Stream -{ - PHYSFS_file *file; - - public: - PhysFile(PHYSFS_file *inp) : file(inp) - { - isSeekable = true; - hasPosition = true; - hasSize = true; - } - - ~PhysFile() { PHYSFS_close(file); } - - size_t read(void *buf, size_t count) - { return PHYSFS_read(file, buf, 1, count); } - - void seek(size_t pos) { PHYSFS_seek(file, pos); } - size_t tell() const { return PHYSFS_tell(file); } - size_t size() const { return PHYSFS_fileLength(file); } - bool eof() const { return PHYSFS_eof(file); } -}; - -typedef boost::shared_ptr PhysFilePtr; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/servers/std_ostream.hpp b/libs/mangle/stream/servers/std_ostream.hpp deleted file mode 100644 index f406e1a93..000000000 --- a/libs/mangle/stream/servers/std_ostream.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef MANGLE_OSTREAM_STDIOSERVER_H -#define MANGLE_OSTREAM_STDIOSERVER_H - -#include "../stream.hpp" -#include -#include - -namespace Mangle { -namespace Stream { - -/** Simple wrapper for std::ostream, only supports output. - */ -class StdOStream : public Stream -{ - std::ostream *inf; - - static void fail(const std::string &msg) - { throw std::runtime_error("StdOStream: " + msg); } - - public: - StdOStream(std::ostream *_inf) - : inf(_inf) - { - isSeekable = true; - hasPosition = true; - hasSize = true; - isWritable = true; - isReadable = false; - } - - size_t write(const void* buf, size_t len) - { - inf->write((const char*)buf, len); - if(inf->fail()) - fail("error writing to stream"); - // Just return len, but that is ok. The only cases where we would - // return less than len is when an error occured. - return len; - } - - void flush() - { - inf->flush(); - } - - void seek(size_t pos) - { - inf->seekp(pos); - if(inf->fail()) - fail("seek error"); - } - - size_t tell() const - // Hack around the fact that ifstream->tellp() isn't const - { return ((StdOStream*)this)->inf->tellp(); } - - size_t size() const - { - // Use the standard iostream size hack, terrible as it is. - std::streampos pos = inf->tellp(); - inf->seekp(0, std::ios::end); - size_t res = inf->tellp(); - inf->seekp(pos); - - if(inf->fail()) - fail("could not get stream size"); - - return res; - } - - bool eof() const - { return inf->eof(); } -}; - -typedef boost::shared_ptr StdOStreamPtr; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/servers/std_stream.hpp b/libs/mangle/stream/servers/std_stream.hpp deleted file mode 100644 index 163f023f6..000000000 --- a/libs/mangle/stream/servers/std_stream.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef MANGLE_STREAM_STDIOSERVER_H -#define MANGLE_STREAM_STDIOSERVER_H - -#include "../stream.hpp" -#include -#include - -namespace Mangle { -namespace Stream { - -/** Simple wrapper for std::istream. - */ -class StdStream : public Stream -{ - std::istream *inf; - - static void fail(const std::string &msg) - { throw std::runtime_error("StdStream: " + msg); } - - public: - StdStream(std::istream *_inf) - : inf(_inf) - { - isSeekable = true; - hasPosition = true; - hasSize = true; - } - - size_t read(void* buf, size_t len) - { - inf->read((char*)buf, len); - if(inf->bad()) - fail("error reading from stream"); - return inf->gcount(); - } - - void seek(size_t pos) - { - inf->clear(); - inf->seekg(pos); - if(inf->fail()) - fail("seek error"); - } - - size_t tell() const - // Hack around the fact that ifstream->tellg() isn't const - { return ((StdStream*)this)->inf->tellg(); } - - size_t size() const - { - // Use the standard iostream size hack, terrible as it is. - std::streampos pos = inf->tellg(); - inf->seekg(0, std::ios::end); - size_t res = inf->tellg(); - inf->seekg(pos); - - if(inf->fail()) - fail("could not get stream size"); - - return res; - } - - bool eof() const - { return inf->eof(); } -}; - -typedef boost::shared_ptr StdStreamPtr; - -}} // namespaces -#endif From bd68f7bd3380521393c9f05ee92b1fbb374bf195 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 08:51:51 -0700 Subject: [PATCH 109/298] Remove final direct uses of Mangle::Stream --- components/nifogre/ogre_nif_loader.hpp | 1 - components/terrain/esm_land_factory.cpp | 34 ---- components/terrain/esm_land_factory.hpp | 4 - components/terrain/land_factory.hpp | 12 -- libs/mangle/stream/stream.hpp | 103 ---------- libs/mangle/stream/tests/.gitignore | 2 - libs/mangle/stream/tests/Makefile | 34 ---- .../stream/tests/audiere_client_test.cpp | 34 ---- .../stream/tests/buffer_filter_test.cpp | 41 ---- libs/mangle/stream/tests/file_server_test.cpp | 18 -- libs/mangle/stream/tests/file_write_test.cpp | 41 ---- libs/mangle/stream/tests/iostream_test.cpp | 176 ------------------ .../stream/tests/memory_server_test.cpp | 42 ----- libs/mangle/stream/tests/ogre_client_test.cpp | 22 --- .../tests/output/audiere_client_test.out | 9 - .../tests/output/buffer_filter_test.out | 22 --- .../stream/tests/output/file_server_test.out | 3 - .../stream/tests/output/file_write_test.out | 12 -- .../stream/tests/output/iostream_test.out | 32 ---- .../tests/output/memory_server_test.out | 23 --- .../stream/tests/output/ogre_client_test.out | 5 - .../stream/tests/output/slice_filter_test.out | 36 ---- .../mangle/stream/tests/slice_filter_test.cpp | 42 ----- libs/mangle/stream/tests/test.sh | 18 -- 24 files changed, 766 deletions(-) delete mode 100644 libs/mangle/stream/stream.hpp delete mode 100644 libs/mangle/stream/tests/.gitignore delete mode 100644 libs/mangle/stream/tests/Makefile delete mode 100644 libs/mangle/stream/tests/audiere_client_test.cpp delete mode 100644 libs/mangle/stream/tests/buffer_filter_test.cpp delete mode 100644 libs/mangle/stream/tests/file_server_test.cpp delete mode 100644 libs/mangle/stream/tests/file_write_test.cpp delete mode 100644 libs/mangle/stream/tests/iostream_test.cpp delete mode 100644 libs/mangle/stream/tests/memory_server_test.cpp delete mode 100644 libs/mangle/stream/tests/ogre_client_test.cpp delete mode 100644 libs/mangle/stream/tests/output/audiere_client_test.out delete mode 100644 libs/mangle/stream/tests/output/buffer_filter_test.out delete mode 100644 libs/mangle/stream/tests/output/file_server_test.out delete mode 100644 libs/mangle/stream/tests/output/file_write_test.out delete mode 100644 libs/mangle/stream/tests/output/iostream_test.out delete mode 100644 libs/mangle/stream/tests/output/memory_server_test.out delete mode 100644 libs/mangle/stream/tests/output/ogre_client_test.out delete mode 100644 libs/mangle/stream/tests/output/slice_filter_test.out delete mode 100644 libs/mangle/stream/tests/slice_filter_test.cpp delete mode 100755 libs/mangle/stream/tests/test.sh diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 64efc70c7..0620ddf49 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -31,7 +31,6 @@ #include #include -#include #include "../nif/nif_file.hpp" #include "../nif/node.hpp" #include "../nif/data.hpp" diff --git a/components/terrain/esm_land_factory.cpp b/components/terrain/esm_land_factory.cpp index 315188234..a6335c6dc 100644 --- a/components/terrain/esm_land_factory.cpp +++ b/components/terrain/esm_land_factory.cpp @@ -8,41 +8,7 @@ using namespace Terrain; -static class ESMLandStream : public Mangle::Stream -{ -public: - ESMLandStream(ESM::Land *l, ESM::ESMReader &r) - { - } -}; - bool ESMLandFactory::has(int x, int y) { return store.landscapes.has(x,y); } - -LandDataPtr get(int x, int y, LandInfo &info) -{ - assert(has(x,y)); - - // Set up the info - info.grid = LGT_Quadratic; - info.data = LDT_Float; - - const float SIZE = 8192; // CHECK - - info.xsize = SIZE; - info.ysize = SIZE; - info.numx = 65; - info.numy = 65; - info.xoffset = SIZE*x; - info.yoffset = SIZE*y; - - // Get the Land struct from store - ESM::Land* land = store.landscapes.find(x,y); - assert(land->hasData); - - // Create a stream for the data and return it. - LandDataPtr ptr(new ESMLandStream(land, reader)); - return ptr; -} diff --git a/components/terrain/esm_land_factory.hpp b/components/terrain/esm_land_factory.hpp index 4fd95caa9..bb1f9a8c6 100644 --- a/components/terrain/esm_land_factory.hpp +++ b/components/terrain/esm_land_factory.hpp @@ -32,10 +32,6 @@ namespace Terrain // True if this factory has any data for the given grid cell. bool has(int x, int y); - - // Return stream to data for this cell. Additional data about the - // landscape is returned through the LandInfo struct. - LandDataPtr get(int x, int y, LandInfo &info); }; } #endif diff --git a/components/terrain/land_factory.hpp b/components/terrain/land_factory.hpp index f41946b49..c4d160443 100644 --- a/components/terrain/land_factory.hpp +++ b/components/terrain/land_factory.hpp @@ -1,8 +1,6 @@ #ifndef TERRAIN_LAND_FACTORY_H #define TERRAIN_LAND_FACTORY_H -#include - namespace Terrain { enum LandInfoGridType @@ -32,12 +30,6 @@ namespace Terrain float xoffset, yoffset; }; - /* We use normal streams for data. This allows us to just pass (for - example) a file stream as height map input later, with no extra - fuzz. - */ - typedef Mangle::Stream::StreamPtr LandDataPtr; - /* Factory class that provides streams to land data cells. Each "cell" has a unique integer coordinate in the plane. @@ -46,10 +38,6 @@ namespace Terrain { // True if this factory has any data for the given grid cell. virtual bool has(int x, int y) = 0; - - // Return stream to data for this cell. Additional data about the - // landscape is returned through the LandInfo struct. - virtual LandDataPtr get(int x, int y, LandInfo &info) = 0; }; } #endif diff --git a/libs/mangle/stream/stream.hpp b/libs/mangle/stream/stream.hpp deleted file mode 100644 index 2ee4fcbd8..000000000 --- a/libs/mangle/stream/stream.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef MANGLE_STREAM_INPUT_H -#define MANGLE_STREAM_INPUT_H - -#include -#include "../tools/shared_ptr.hpp" -#include - -namespace Mangle { -namespace Stream { - -/// An abstract interface for a stream data. -class Stream -{ - public: - // Feature options. These should be set in the constructor. - - /// If true, seek() works - bool isSeekable; - - /// If true, tell() works - bool hasPosition; - - /// If true, size() works - bool hasSize; - - /// If true, write() works. Writing through pointer operations is - /// not (yet) supported. - bool isWritable; - - /// If true, read() and eof() works. - bool isReadable; - - /// If true, the getPtr() functions work - bool hasPtr; - - /// Initialize all bools to false by default, except isReadable. - Stream() : - isSeekable(false), hasPosition(false), hasSize(false), - isWritable(false), isReadable(true), hasPtr(false) {} - - /// Virtual destructor - virtual ~Stream() {} - - /** Read a given number of bytes from the stream. Returns the actual - number read. If the return value is less than count, then the - stream is empty or an error occured. Only required for readable - streams. - */ - virtual size_t read(void* buf, size_t count) { assert(0); return 0; } - - /** Write a given number of bytes from the stream. Semantics is - similar to read(). Only valid if isWritable is true. - - The returned value is the number of bytes written. However in - most cases, unlike for read(), a write-count less than requested - usually indicates an error. The implementation should throw such - errors as exceptions rather than expect the caller to handle - them. - - Since most implementations do NOT support writing we default to - an assert(0) here. - */ - virtual size_t write(const void *buf, size_t count) { assert(0); return 0; } - - /// Flush an output stream. Does nothing for non-writing streams. - virtual void flush() {} - - /// Seek to an absolute position in this stream. Not all streams are - /// seekable. - virtual void seek(size_t pos) { assert(0); } - - /// Get the current position in the stream. Non-seekable streams are - /// not required to keep track of this. - virtual size_t tell() const { assert(0); return 0; } - - /// Return the total size of the stream. For streams hasSize is - /// false, size() should fail in some way, since it is an error to - /// call it in those cases. - virtual size_t size() const { assert(0); return 0; } - - /// Returns true if the stream is empty. Required for readable - /// streams. - virtual bool eof() const { assert(0); return 0; } - - /// Return a pointer to the entire stream. This function (and the - /// other getPtr() variants below) should only be implemented for - /// memory-based streams where using them would be an optimization. - virtual const void *getPtr() { assert(0); return NULL; } - - /// Get a pointer to a memory region of 'size' bytes starting from - /// position 'pos' - virtual const void *getPtr(size_t pos, size_t size) { assert(0); return NULL; } - - /// Get a pointer to a memory region of 'size' bytes from the - /// current position. Unlike the two other getPtr variants, this - /// will advance the position past the returned area. - virtual const void *getPtr(size_t size) { assert(0); return NULL; } -}; - -typedef boost::shared_ptr StreamPtr; - -}} // namespaces -#endif diff --git a/libs/mangle/stream/tests/.gitignore b/libs/mangle/stream/tests/.gitignore deleted file mode 100644 index 9dfd618e2..000000000 --- a/libs/mangle/stream/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*_test -test.file diff --git a/libs/mangle/stream/tests/Makefile b/libs/mangle/stream/tests/Makefile deleted file mode 100644 index 5f4397819..000000000 --- a/libs/mangle/stream/tests/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -GCC=g++ -I../ -Wall -Werror - -all: ogre_client_test audiere_client_test memory_server_test buffer_filter_test file_server_test slice_filter_test file_write_test iostream_test - -I_OGRE=$(shell pkg-config --cflags OGRE) -L_OGRE=$(shell pkg-config --libs OGRE) -L_AUDIERE=-laudiere - -ogre_client_test: ogre_client_test.cpp ../stream.hpp ../clients/ogre_datastream.hpp - $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) - -audiere_client_test: audiere_client_test.cpp ../stream.hpp ../clients/audiere_file.hpp ../clients/audiere_file.cpp - $(GCC) $< -o $@ ../clients/audiere_file.cpp $(L_AUDIERE) - -iostream_test: iostream_test.cpp ../clients/io_stream.cpp - $(GCC) $^ -o $@ - -file_server_test: file_server_test.cpp ../stream.hpp ../servers/file_stream.hpp ../servers/std_stream.hpp - $(GCC) $< -o $@ - -file_write_test: file_write_test.cpp ../stream.hpp ../servers/outfile_stream.hpp ../servers/std_ostream.hpp - $(GCC) $< -o $@ - -memory_server_test: memory_server_test.cpp ../stream.hpp ../servers/memory_stream.hpp - $(GCC) $< -o $@ - -buffer_filter_test: buffer_filter_test.cpp ../stream.hpp ../servers/memory_stream.hpp ../filters/buffer_stream.hpp - $(GCC) $< -o $@ - -slice_filter_test: slice_filter_test.cpp ../stream.hpp ../servers/memory_stream.hpp ../filters/slice_stream.hpp - $(GCC) $< -o $@ - -clean: - rm *_test diff --git a/libs/mangle/stream/tests/audiere_client_test.cpp b/libs/mangle/stream/tests/audiere_client_test.cpp deleted file mode 100644 index 82be569cc..000000000 --- a/libs/mangle/stream/tests/audiere_client_test.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "../servers/memory_stream.hpp" -#include "../clients/audiere_file.hpp" -#include -#include - -using namespace Mangle::Stream; -using namespace audiere; -using namespace std; - -int main() -{ - char str[12]; - memset(str, 0, 12); - StreamPtr inp(new MemoryStream("hello world", 11)); - FilePtr p(new AudiereFile(inp)); - cout << "pos=" << p->tell() << endl; - p->read(str, 2); - cout << "2 bytes: " << str << endl; - cout << "pos=" << p->tell() << endl; - p->seek(4, File::BEGIN); - cout << "pos=" << p->tell() << endl; - p->read(str, 3); - cout << "3 bytes: " << str << endl; - p->seek(-1, File::CURRENT); - cout << "pos=" << p->tell() << endl; - p->seek(-4, File::END); - cout << "pos=" << p->tell() << endl; - p->read(str, 4); - cout << "last 4 bytes: " << str << endl; - p->seek(0, File::BEGIN); - p->read(str, 11); - cout << "entire stream: " << str << endl; - return 0; -} diff --git a/libs/mangle/stream/tests/buffer_filter_test.cpp b/libs/mangle/stream/tests/buffer_filter_test.cpp deleted file mode 100644 index e53bda651..000000000 --- a/libs/mangle/stream/tests/buffer_filter_test.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include - -#include "../filters/buffer_stream.hpp" - -using namespace Mangle::Stream; -using namespace std; - -int main() -{ - StreamPtr orig (new MemoryStream("hello world", 11)); - StreamPtr inp (new BufferStream(orig)); - - cout << "Size: " << inp->size() << endl; - cout << "Pos: " << inp->tell() << "\nSeeking...\n"; - inp->seek(3); - cout << "Pos: " << inp->tell() << endl; - char data[12]; - memset(data, 0, 12); - cout << "Reading: " << inp->read(data, 4) << endl; - cout << "Four bytes: " << data << endl; - cout << "Eof: " << inp->eof() << endl; - cout << "Pos: " << inp->tell() << "\nSeeking again...\n"; - inp->seek(33); - cout << "Pos: " << inp->tell() << endl; - cout << "Eof: " << inp->eof() << "\nSeek to 6\n"; - inp->seek(6); - cout << "Eof: " << inp->eof() << endl; - cout << "Pos: " << inp->tell() << endl; - cout << "Over-reading: " << inp->read(data, 200) << endl; - cout << "Result: " << data << endl; - cout << "Eof: " << inp->eof() << endl; - cout << "Pos: " << inp->tell() << endl; - inp->seek(0); - cout << "Finally, reading the entire string: " << inp->read(data,11) << endl; - cout << "Result: " << data << endl; - cout << "Eof: " << inp->eof() << endl; - cout << "Pos: " << inp->tell() << endl; - - return 0; -} diff --git a/libs/mangle/stream/tests/file_server_test.cpp b/libs/mangle/stream/tests/file_server_test.cpp deleted file mode 100644 index 3e1e3cfa5..000000000 --- a/libs/mangle/stream/tests/file_server_test.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "../servers/file_stream.hpp" -#include - -using namespace Mangle::Stream; -using namespace std; - -int main() -{ - StreamPtr inp(new FileStream("file_server_test.cpp")); - - char buf[21]; - buf[20] = 0; - cout << "pos=" << inp->tell() << " eof=" << inp->eof() << endl; - inp->read(buf, 20); - cout << "First 20 bytes: " << buf << endl; - cout << "pos=" << inp->tell() << " eof=" << inp->eof() << endl; - return 0; -} diff --git a/libs/mangle/stream/tests/file_write_test.cpp b/libs/mangle/stream/tests/file_write_test.cpp deleted file mode 100644 index c6c61fcca..000000000 --- a/libs/mangle/stream/tests/file_write_test.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "../servers/outfile_stream.hpp" -#include - -using namespace Mangle::Stream; -using namespace std; - -void print(Stream &str) -{ - cout << "size=" << str.size() - << " pos=" << str.tell() - << " eof=" << str.eof() - << endl; -} - -int main() -{ - { - cout << "\nCreating file\n"; - OutFileStream out("test.file"); - print(out); - out.write("hello",5); - print(out); - } - - { - cout << "\nAppending to file\n"; - OutFileStream out("test.file", true); - print(out); - out.write(" again\n",7); - print(out); - } - - { - cout << "\nOverwriting file\n"; - OutFileStream out("test.file"); - print(out); - out.write("overwrite!\n",11); - print(out); - } - return 0; -} diff --git a/libs/mangle/stream/tests/iostream_test.cpp b/libs/mangle/stream/tests/iostream_test.cpp deleted file mode 100644 index 60648c6c5..000000000 --- a/libs/mangle/stream/tests/iostream_test.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include -#include "../clients/io_stream.hpp" -#include "../servers/memory_stream.hpp" - -using namespace Mangle::Stream; -using namespace std; - -void test1() -{ - cout << "Testing ASCII reading from memory:\n"; - StreamPtr input(new MemoryStream("hello you world you", 19)); - MangleIStream inp(input); - - string str; - while(!inp.eof()) - { - inp >> str; - cout << "Got: " << str << endl; - } -} - -class Dummy : public Stream -{ - int count; - -public: - - Dummy() : count(0) - { - } - - size_t read(void *ptr, size_t num) - { - char *p = (char*)ptr; - char *start = p; - for(; (count < 2560) && (p-start < (int)num); count++) - { - *p = count / 10; - p++; - } - return p-start; - } - - bool eof() const { return count == 2560; } -}; - -void test2() -{ - cout << "\nTesting binary reading from non-memory:\n"; - - StreamPtr input(new Dummy); - MangleIStream inp(input); - - int x = 0; - while(!inp.eof()) - { - unsigned char buf[5]; - inp.read((char*)buf,5); - - // istream doesn't set eof() until we read _beyond_ the end of - // the stream, so we need an extra check. - if(inp.gcount() == 0) break; - - /* - for(int i=0;i<5;i++) - cout << (int)buf[i] << " "; - cout << endl; - */ - - assert(buf[4] == buf[0]); - assert(buf[0] == x/2); - x++; - } - cout << " Done\n"; -} - -struct Dummy2 : Stream -{ - Dummy2() - { - isWritable = true; - isReadable = false; - } - - size_t write(const void *ptr, size_t num) - { - const char *p = (const char*)ptr; - cout << " Got: "; - for(unsigned i=0;iwrite("testing", 7); - - cout << " Running through MangleOStream:\n"; - MangleOStream out(output); - out << "hello"; - out << " - are you ok?"; - cout << " Flushing:\n"; - out.flush(); - - cout << " Writing a hell of a lot of characters:\n"; - for(int i=0; i<127; i++) - out << "xxxxxxxx"; // 127 * 8 = 1016 - out << "fffffff"; // +7 = 1023 - cout << " Just one more:\n"; - out << "y"; - cout << " And oooone more:\n"; - out << "z"; - - cout << " Flushing again:\n"; - out.flush(); - cout << " Writing some more and exiting:\n"; - out << "blah bleh blob"; -} - -struct Dummy3 : Stream -{ - int pos; - - Dummy3() : pos(0) - { - hasPosition = true; - isSeekable = true; - } - - size_t read(void*, size_t num) - { - cout << " Reading " << num << " bytes from " << pos << endl; - pos += num; - return num; - } - - void seek(size_t npos) { pos = npos; } - size_t tell() const { return pos; } -}; - -void test4() -{ - cout << "\nTesting seeking;\n"; - StreamPtr input(new Dummy3); - - cout << " Direct reading:\n"; - input->read(0,10); - input->read(0,5); - - MangleIStream inp(input); - - cout << " Reading from istream:\n"; - char buf[20]; - inp.read(buf, 20); - inp.read(buf, 20); - inp.read(buf, 20); - - cout << " Seeking to 30 and reading again:\n"; - inp.seekg(30); - inp.read(buf, 20); -} - -int main() -{ - test1(); - test2(); - test3(); - test4(); - return 0; -} diff --git a/libs/mangle/stream/tests/memory_server_test.cpp b/libs/mangle/stream/tests/memory_server_test.cpp deleted file mode 100644 index 24d3bb17e..000000000 --- a/libs/mangle/stream/tests/memory_server_test.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include - -#include "../servers/memory_stream.hpp" - -using namespace Mangle::Stream; -using namespace std; - -int main() -{ - Stream* inp = new MemoryStream("hello world\0", 12); - - cout << "Size: " << inp->size() << endl; - cout << "Pos: " << inp->tell() << "\nSeeking...\n"; - inp->seek(3); - cout << "Pos: " << inp->tell() << endl; - char data[12]; - memset(data, 0, 12); - cout << "Reading: " << inp->read(data, 4) << endl; - cout << "Four bytes: " << data << endl; - cout << "Eof: " << inp->eof() << endl; - cout << "Pos: " << inp->tell() << "\nSeeking again...\n"; - inp->seek(33); - cout << "Pos: " << inp->tell() << endl; - cout << "Eof: " << inp->eof() << "\nSeek to 6\n"; - inp->seek(6); - cout << "Eof: " << inp->eof() << endl; - cout << "Pos: " << inp->tell() << endl; - cout << "Over-reading: " << inp->read(data, 200) << endl; - cout << "Result: " << data << endl; - cout << "Eof: " << inp->eof() << endl; - cout << "Pos: " << inp->tell() << endl; - inp->seek(0); - cout << "Finally, reading the entire string: " << inp->read(data,11) << endl; - cout << "Result: " << data << endl; - cout << "Eof: " << inp->eof() << endl; - cout << "Pos: " << inp->tell() << endl; - - cout << "Entire stream from pointer: " << (char*)inp->getPtr() << endl; - - return 0; -} diff --git a/libs/mangle/stream/tests/ogre_client_test.cpp b/libs/mangle/stream/tests/ogre_client_test.cpp deleted file mode 100644 index c8d0442c0..000000000 --- a/libs/mangle/stream/tests/ogre_client_test.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "../servers/memory_stream.hpp" -#include "../clients/ogre_datastream.hpp" -#include - -using namespace Mangle::Stream; -using namespace Ogre; -using namespace std; - -int main() -{ - StreamPtr inp(new MemoryStream("hello world", 11)); - DataStreamPtr p(new Mangle2OgreStream("hello", inp)); - cout << "Name: " << p->getName() << endl; - cout << "As string: " << p->getAsString() << endl; - cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; - p->seek(0); - cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; - p->skip(5); - p->skip(-2); - cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; - return 0; -} diff --git a/libs/mangle/stream/tests/output/audiere_client_test.out b/libs/mangle/stream/tests/output/audiere_client_test.out deleted file mode 100644 index 2130db9fd..000000000 --- a/libs/mangle/stream/tests/output/audiere_client_test.out +++ /dev/null @@ -1,9 +0,0 @@ -pos=0 -2 bytes: he -pos=2 -pos=4 -3 bytes: o w -pos=6 -pos=7 -last 4 bytes: orld -entire stream: hello world diff --git a/libs/mangle/stream/tests/output/buffer_filter_test.out b/libs/mangle/stream/tests/output/buffer_filter_test.out deleted file mode 100644 index 0ca252f25..000000000 --- a/libs/mangle/stream/tests/output/buffer_filter_test.out +++ /dev/null @@ -1,22 +0,0 @@ -Size: 11 -Pos: 0 -Seeking... -Pos: 3 -Reading: 4 -Four bytes: lo w -Eof: 0 -Pos: 7 -Seeking again... -Pos: 11 -Eof: 1 -Seek to 6 -Eof: 0 -Pos: 6 -Over-reading: 5 -Result: world -Eof: 1 -Pos: 11 -Finally, reading the entire string: 11 -Result: hello world -Eof: 1 -Pos: 11 diff --git a/libs/mangle/stream/tests/output/file_server_test.out b/libs/mangle/stream/tests/output/file_server_test.out deleted file mode 100644 index 240f066a3..000000000 --- a/libs/mangle/stream/tests/output/file_server_test.out +++ /dev/null @@ -1,3 +0,0 @@ -pos=0 eof=0 -First 20 bytes: #include "../servers -pos=20 eof=0 diff --git a/libs/mangle/stream/tests/output/file_write_test.out b/libs/mangle/stream/tests/output/file_write_test.out deleted file mode 100644 index 34b07c49f..000000000 --- a/libs/mangle/stream/tests/output/file_write_test.out +++ /dev/null @@ -1,12 +0,0 @@ - -Creating file -size=0 pos=0 eof=0 -size=5 pos=5 eof=0 - -Appending to file -size=5 pos=5 eof=0 -size=12 pos=12 eof=0 - -Overwriting file -size=0 pos=0 eof=0 -size=11 pos=11 eof=0 diff --git a/libs/mangle/stream/tests/output/iostream_test.out b/libs/mangle/stream/tests/output/iostream_test.out deleted file mode 100644 index b6da80c80..000000000 --- a/libs/mangle/stream/tests/output/iostream_test.out +++ /dev/null @@ -1,32 +0,0 @@ -Testing ASCII reading from memory: -Got: hello -Got: you -Got: world -Got: you - -Testing binary reading from non-memory: - Done - -Writing to dummy stream: - Pure dummy test: - Got: t e s t i n g - Running through MangleOStream: - Flushing: - Got: h e l l o - a r e y o u o k ? - Writing a hell of a lot of characters: - Just one more: - And oooone more: - Got: x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x f f f f f f f y - Flushing again: - Got: z - Writing some more and exiting: - Got: b l a h b l e h b l o b - -Testing seeking; - Direct reading: - Reading 10 bytes from 0 - Reading 5 bytes from 10 - Reading from istream: - Reading 1024 bytes from 15 - Seeking to 30 and reading again: - Reading 1024 bytes from 30 diff --git a/libs/mangle/stream/tests/output/memory_server_test.out b/libs/mangle/stream/tests/output/memory_server_test.out deleted file mode 100644 index 7cd9533e7..000000000 --- a/libs/mangle/stream/tests/output/memory_server_test.out +++ /dev/null @@ -1,23 +0,0 @@ -Size: 12 -Pos: 0 -Seeking... -Pos: 3 -Reading: 4 -Four bytes: lo w -Eof: 0 -Pos: 7 -Seeking again... -Pos: 12 -Eof: 1 -Seek to 6 -Eof: 0 -Pos: 6 -Over-reading: 6 -Result: world -Eof: 1 -Pos: 12 -Finally, reading the entire string: 11 -Result: hello world -Eof: 0 -Pos: 11 -Entire stream from pointer: hello world diff --git a/libs/mangle/stream/tests/output/ogre_client_test.out b/libs/mangle/stream/tests/output/ogre_client_test.out deleted file mode 100644 index 62cd14604..000000000 --- a/libs/mangle/stream/tests/output/ogre_client_test.out +++ /dev/null @@ -1,5 +0,0 @@ -Name: hello -As string: hello world -pos=11 eof=1 -pos=0 eof=0 -pos=3 eof=0 diff --git a/libs/mangle/stream/tests/output/slice_filter_test.out b/libs/mangle/stream/tests/output/slice_filter_test.out deleted file mode 100644 index 6d84704a7..000000000 --- a/libs/mangle/stream/tests/output/slice_filter_test.out +++ /dev/null @@ -1,36 +0,0 @@ - -Slice 1: --------- -Size: 6 -Pos: 0 -Seeking... -Reading 6 bytes -Result: hello -Pos: 6 -Eof: 1 -Seeking: -Pos: 2 -Eof: 0 -Reading 4 bytes -Result: llo -Pos: 6 -Eof: 1 -Entire stream as pointer: hello - -Slice 2: --------- -Size: 6 -Pos: 0 -Seeking... -Reading 6 bytes -Result: world -Pos: 6 -Eof: 1 -Seeking: -Pos: 2 -Eof: 0 -Reading 4 bytes -Result: rld -Pos: 6 -Eof: 1 -Entire stream as pointer: world diff --git a/libs/mangle/stream/tests/slice_filter_test.cpp b/libs/mangle/stream/tests/slice_filter_test.cpp deleted file mode 100644 index da90e24ad..000000000 --- a/libs/mangle/stream/tests/slice_filter_test.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include - -#include "../filters/slice_stream.hpp" -#include "../servers/memory_stream.hpp" - -using namespace Mangle::Stream; -using namespace std; - -void test(StreamPtr inp) -{ - cout << "Size: " << inp->size() << endl; - cout << "Pos: " << inp->tell() << "\nSeeking...\n"; - char data[6]; - memset(data, 0, 6); - cout << "Reading " << inp->read(data, 6) << " bytes\n"; - cout << "Result: " << data << endl; - cout << "Pos: " << inp->tell() << endl; - cout << "Eof: " << inp->eof() << endl; - inp->seek(2); - cout << "Seeking:\nPos: " << inp->tell() << endl; - cout << "Eof: " << inp->eof() << endl; - cout << "Reading " << inp->read(data, 6) << " bytes\n"; - cout << "Result: " << data << endl; - cout << "Pos: " << inp->tell() << endl; - cout << "Eof: " << inp->eof() << endl; - cout << "Entire stream as pointer: " << (char*)inp->getPtr() << endl; -} - -int main() -{ - StreamPtr orig (new MemoryStream("hello\0world\0", 12)); - StreamPtr slice1 (new SliceStream(orig,0,6)); - StreamPtr slice2 (new SliceStream(orig,6,6)); - - cout << "\nSlice 1:\n--------\n"; - test(slice1); - cout << "\nSlice 2:\n--------\n"; - test(slice2); - - return 0; -} diff --git a/libs/mangle/stream/tests/test.sh b/libs/mangle/stream/tests/test.sh deleted file mode 100755 index 2d07708ad..000000000 --- a/libs/mangle/stream/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -make || exit - -mkdir -p output - -PROGS=*_test - -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done From b353cfd457e94ecfda4a78c1de2e0b473bd19b26 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 08:54:00 -0700 Subject: [PATCH 110/298] Remove unused Mangle::VFS --- libs/mangle/vfs/clients/ogre_archive.cpp | 85 ------------- libs/mangle/vfs/clients/ogre_archive.hpp | 59 --------- libs/mangle/vfs/servers/ogre_vfs.cpp | 61 --------- libs/mangle/vfs/servers/ogre_vfs.hpp | 70 ----------- libs/mangle/vfs/servers/physfs_vfs.hpp | 71 ----------- libs/mangle/vfs/tests/.gitignore | 1 - libs/mangle/vfs/tests/Makefile | 25 ---- libs/mangle/vfs/tests/dummy_test.cpp | 42 ------- libs/mangle/vfs/tests/dummy_vfs.cpp | 117 ------------------ libs/mangle/vfs/tests/ogre_client_test.cpp | 39 ------ libs/mangle/vfs/tests/ogre_resource_test.cpp | 61 --------- libs/mangle/vfs/tests/ogre_server_test.cpp | 38 ------ libs/mangle/vfs/tests/output/dummy_test.out | 31 ----- .../vfs/tests/output/ogre_client_test.out | 12 -- .../vfs/tests/output/ogre_resource_test.out | 20 --- .../vfs/tests/output/ogre_server_test.out | 11 -- .../vfs/tests/output/physfs_server_test.out | 11 -- libs/mangle/vfs/tests/physfs_server_test.cpp | 21 ---- libs/mangle/vfs/tests/server_common.cpp | 33 ----- libs/mangle/vfs/tests/test.sh | 18 --- libs/mangle/vfs/tests/test.zip | Bin 169 -> 0 bytes libs/mangle/vfs/vfs.hpp | 87 ------------- 22 files changed, 913 deletions(-) delete mode 100644 libs/mangle/vfs/clients/ogre_archive.cpp delete mode 100644 libs/mangle/vfs/clients/ogre_archive.hpp delete mode 100644 libs/mangle/vfs/servers/ogre_vfs.cpp delete mode 100644 libs/mangle/vfs/servers/ogre_vfs.hpp delete mode 100644 libs/mangle/vfs/servers/physfs_vfs.hpp delete mode 100644 libs/mangle/vfs/tests/.gitignore delete mode 100644 libs/mangle/vfs/tests/Makefile delete mode 100644 libs/mangle/vfs/tests/dummy_test.cpp delete mode 100644 libs/mangle/vfs/tests/dummy_vfs.cpp delete mode 100644 libs/mangle/vfs/tests/ogre_client_test.cpp delete mode 100644 libs/mangle/vfs/tests/ogre_resource_test.cpp delete mode 100644 libs/mangle/vfs/tests/ogre_server_test.cpp delete mode 100644 libs/mangle/vfs/tests/output/dummy_test.out delete mode 100644 libs/mangle/vfs/tests/output/ogre_client_test.out delete mode 100644 libs/mangle/vfs/tests/output/ogre_resource_test.out delete mode 100644 libs/mangle/vfs/tests/output/ogre_server_test.out delete mode 100644 libs/mangle/vfs/tests/output/physfs_server_test.out delete mode 100644 libs/mangle/vfs/tests/physfs_server_test.cpp delete mode 100644 libs/mangle/vfs/tests/server_common.cpp delete mode 100755 libs/mangle/vfs/tests/test.sh delete mode 100644 libs/mangle/vfs/tests/test.zip delete mode 100644 libs/mangle/vfs/vfs.hpp diff --git a/libs/mangle/vfs/clients/ogre_archive.cpp b/libs/mangle/vfs/clients/ogre_archive.cpp deleted file mode 100644 index 2d3f7c520..000000000 --- a/libs/mangle/vfs/clients/ogre_archive.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "ogre_archive.hpp" - -#include "../../stream/clients/ogre_datastream.hpp" - -using namespace Mangle::VFS; -using namespace Mangle::Stream; - -Ogre::DataStreamPtr MangleArchive::open(const Ogre::String& filename) const -{ - return Ogre::DataStreamPtr(new Mangle2OgreStream - (filename, vfs->open(filename))); -} - -static void fill(Ogre::FileInfoList &out, FileInfoList &in) -{ - int size = in.size(); - out.resize(size); - - for(int i=0; ihasList); - FileInfoListPtr lst = vfs->list("", recursive, dirs); - Ogre::StringVector *res = new Ogre::StringVector; - - fill(*res, *lst); - - return Ogre::StringVectorPtr(res); -} - -Ogre::FileInfoListPtr MangleArchive::listFileInfo(bool recursive, bool dirs) -{ - assert(vfs->hasList); - FileInfoListPtr lst = vfs->list("", recursive, dirs); - Ogre::FileInfoList *res = new Ogre::FileInfoList; - - fill(*res, *lst); - - return Ogre::FileInfoListPtr(res); -} - -// Find functions will only work if vfs->hasFind is set. -Ogre::StringVectorPtr MangleArchive::find(const Ogre::String& pattern, - bool recursive, - bool dirs) -{ - assert(vfs->hasFind); - FileInfoListPtr lst = vfs->find(pattern, recursive, dirs); - Ogre::StringVector *res = new Ogre::StringVector; - - fill(*res, *lst); - - return Ogre::StringVectorPtr(res); -} - -Ogre::FileInfoListPtr MangleArchive::findFileInfo(const Ogre::String& pattern, - bool recursive, - bool dirs) -{ - assert(vfs->hasFind); - FileInfoListPtr lst = vfs->find(pattern, recursive, dirs); - Ogre::FileInfoList *res = new Ogre::FileInfoList; - - fill(*res, *lst); - - return Ogre::FileInfoListPtr(res); -} diff --git a/libs/mangle/vfs/clients/ogre_archive.hpp b/libs/mangle/vfs/clients/ogre_archive.hpp deleted file mode 100644 index 7b371c6ef..000000000 --- a/libs/mangle/vfs/clients/ogre_archive.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef MANGLE_VFS_OGRECLIENT_H -#define MANGLE_VFS_OGRECLIENT_H - -#include -#include -#include "../vfs.hpp" - -namespace Mangle { -namespace VFS { - -/** An OGRE Archive implementation that wraps a Mangle::VFS - filesystem. - - This has been built and tested against OGRE 1.6.2, and has been - extended for OGRE 1.7. You might have to make your own - modifications if you're working with newer (or older) versions. - */ -class MangleArchive : public Ogre::Archive -{ - VFSPtr vfs; - - public: - MangleArchive(VFSPtr _vfs, const std::string &name, - const std::string &archType = "Mangle") - : Ogre::Archive(name, archType) - , vfs(_vfs) - {} - - bool isCaseSensitive() const { return vfs->isCaseSensitive; } - - // These do nothing. You have to load / unload the archive in the - // constructor/destructor. - void load() {} - void unload() {} - - bool exists(const Ogre::String& filename) - { return vfs->isFile(filename); } - - time_t getModifiedTime(const Ogre::String& filename) - { return vfs->stat(filename)->time; } - - // With both these we support both OGRE 1.6 and 1.7 - Ogre::DataStreamPtr open(const Ogre::String& filename) const; - Ogre::DataStreamPtr open(const Ogre::String& filename, bool readOnly) const - { return open(filename); } - - Ogre::StringVectorPtr list(bool recursive = true, bool dirs = false); - Ogre::FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false); - - // Find functions will only work if vfs->hasFind is set. - Ogre::StringVectorPtr find(const Ogre::String& pattern, bool recursive = true, - bool dirs = false); - Ogre::FileInfoListPtr findFileInfo(const Ogre::String& pattern, - bool recursive = true, - bool dirs = false); -}; - -}} // namespaces -#endif diff --git a/libs/mangle/vfs/servers/ogre_vfs.cpp b/libs/mangle/vfs/servers/ogre_vfs.cpp deleted file mode 100644 index 7f728c1b0..000000000 --- a/libs/mangle/vfs/servers/ogre_vfs.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "ogre_vfs.hpp" -#include "../../stream/servers/ogre_datastream.hpp" - -using namespace Mangle::VFS; -using namespace Mangle::Stream; - -OgreVFS::OgreVFS(const std::string &_group) - : group(_group) -{ - hasList = true; - hasFind = true; - isCaseSensitive = true; - - // Get the group manager once - gm = Ogre::ResourceGroupManager::getSingletonPtr(); - - // Use the default group if none was specified - if(group.empty()) - group = gm->getWorldResourceGroupName(); -} - -StreamPtr OgreVFS::open(const std::string &name) -{ - Ogre::DataStreamPtr data = gm->openResource(name, group); - return StreamPtr(new OgreStream(data)); -} - -static void fill(FileInfoList &out, Ogre::FileInfoList &in, bool dirs) -{ - int size = in.size(); - out.resize(size); - - for(int i=0; ilistResourceFileInfo(group, dirs); - FileInfoListPtr res(new FileInfoList); - fill(*res, *olist, dirs); - return res; -} - -FileInfoListPtr OgreVFS::find(const std::string& pattern, - bool recursive, - bool dirs) const -{ - Ogre::FileInfoListPtr olist = gm->findResourceFileInfo(group, pattern, dirs); - FileInfoListPtr res(new FileInfoList); - fill(*res, *olist, dirs); - return res; -} diff --git a/libs/mangle/vfs/servers/ogre_vfs.hpp b/libs/mangle/vfs/servers/ogre_vfs.hpp deleted file mode 100644 index 7ab64169d..000000000 --- a/libs/mangle/vfs/servers/ogre_vfs.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef MANGLE_VFS_OGRESERVER_H -#define MANGLE_VFS_OGRESERVER_H - -#include "../vfs.hpp" -#include - -namespace Mangle { -namespace VFS { - -/** @brief An interface into the OGRE VFS system. - - This class does NOT wrap a single Ogre::Archive, but rather an - entire resource group in Ogre. You can use this class to tap into - all paths, Zip files, custom archives on so on that have been - inserted into Ogre as resource locations. - - This has been built and tested against OGRE 1.6.2. You might have - to make your own modifications if you're working with newer (or - older) versions. - */ -class OgreVFS : public VFS -{ - std::string group; - Ogre::ResourceGroupManager *gm; - - public: - /** @brief Constructor - - OGRE must be initialized (ie. you must have created an - Ogre::Root object somewhere) before calling this. - - @param group Optional resource group name. If none is given, - OGRE's default (or 'World') resource group is used. - */ - OgreVFS(const std::string &_group = ""); - - /// Open a new data stream. Deleting the object should be enough to - /// close it. - virtual Stream::StreamPtr open(const std::string &name); - - /// Check for the existence of a file - virtual bool isFile(const std::string &name) const - { return gm->resourceExists(group, name); } - - /// This doesn't work, always returns false. - virtual bool isDir(const std::string &name) const - { return false; } - - /// This doesn't work. - virtual FileInfoPtr stat(const std::string &name) const - { return FileInfoPtr(); } - - /// List all entries in a given directory. A blank dir should be - /// interpreted as a the root/current directory of the archive. If - /// dirs is true, list directories instead of files. OGRE note: The - /// ogre resource systemd does not support recursive listing of - /// files. We might make a separate filter for this later. - virtual FileInfoListPtr list(const std::string& dir = "", - bool recurse=true, - bool dirs=false) const; - - /// Find files after a given pattern. Wildcards (*) are - /// supported. - virtual FileInfoListPtr find(const std::string& pattern, - bool recursive=true, - bool dirs=false) const; -}; - -}} // namespaces -#endif diff --git a/libs/mangle/vfs/servers/physfs_vfs.hpp b/libs/mangle/vfs/servers/physfs_vfs.hpp deleted file mode 100644 index 8535088e0..000000000 --- a/libs/mangle/vfs/servers/physfs_vfs.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef MANGLE_VFS_PHYSFS_SERVER_H -#define MANGLE_VFS_PHYSFS_SERVER_H - -#include "../vfs.hpp" -#include "../../stream/servers/phys_stream.hpp" - -#include -#include - -namespace Mangle { -namespace VFS { - -/** @brief An interface into the PhysFS virtual file system library - - You have to set up PhysFS on your own before using this class. - */ -class PhysVFS : public VFS -{ - public: - PhysVFS() - { - hasList = true; - hasFind = false; - isCaseSensitive = true; - } - - /// Open a new data stream. Deleting the object should be enough to - /// close it. - virtual Stream::StreamPtr open(const std::string &name) - { return Stream::StreamPtr(new Stream::PhysFile(PHYSFS_openRead(name.c_str()))); } - - /// Check for the existence of a file - virtual bool isFile(const std::string &name) const - { return PHYSFS_exists(name.c_str()); } - - /// Checks for a directory - virtual bool isDir(const std::string &name) const - { return PHYSFS_isDirectory(name.c_str()); } - - /// This doesn't work - virtual FileInfoPtr stat(const std::string &name) const - { assert(0); return FileInfoPtr(); } - - virtual FileInfoListPtr list(const std::string& dir = "", - bool recurse=true, - bool dirs=false) const - { - char **files = PHYSFS_enumerateFiles(dir.c_str()); - FileInfoListPtr lst(new FileInfoList); - - // Add all teh files - int i = 0; - while(files[i] != NULL) - { - FileInfo fi; - fi.name = files[i]; - fi.isDir = false; - - lst->push_back(fi); - } - return lst; - } - - virtual FileInfoListPtr find(const std::string& pattern, - bool recursive=true, - bool dirs=false) const - { assert(0); } -}; - -}} // namespaces -#endif diff --git a/libs/mangle/vfs/tests/.gitignore b/libs/mangle/vfs/tests/.gitignore deleted file mode 100644 index 814490404..000000000 --- a/libs/mangle/vfs/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_test diff --git a/libs/mangle/vfs/tests/Makefile b/libs/mangle/vfs/tests/Makefile deleted file mode 100644 index a5d1d1712..000000000 --- a/libs/mangle/vfs/tests/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -GCC=g++ -I../ -Wall - -all: dummy_test ogre_client_test ogre_resource_test ogre_server_test physfs_server_test - -I_OGRE=$(shell pkg-config --cflags OGRE) -L_OGRE=$(shell pkg-config --libs OGRE) -L_PHYSFS=-lphysfs - -ogre_client_test: ogre_client_test.cpp dummy_vfs.cpp ../vfs.hpp ../clients/ogre_archive.hpp ../clients/ogre_archive.cpp - $(GCC) $< ../clients/ogre_archive.cpp -o $@ $(I_OGRE) $(L_OGRE) - -ogre_resource_test: ogre_resource_test.cpp - $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) - -ogre_server_test: ogre_server_test.cpp ../vfs.hpp ../servers/ogre_vfs.hpp ../servers/ogre_vfs.cpp - $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) ../servers/ogre_vfs.cpp - -physfs_server_test: physfs_server_test.cpp ../vfs.hpp ../servers/physfs_vfs.hpp - $(GCC) $< -o $@ $(L_PHYSFS) - -dummy_test: dummy_test.cpp dummy_vfs.cpp ../vfs.hpp - $(GCC) $< -o $@ - -clean: - rm *_test diff --git a/libs/mangle/vfs/tests/dummy_test.cpp b/libs/mangle/vfs/tests/dummy_test.cpp deleted file mode 100644 index 541010c6a..000000000 --- a/libs/mangle/vfs/tests/dummy_test.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "dummy_vfs.cpp" - -#include -#include - -using namespace std; - -void print(FileInfo &inf) -{ - cout << "name: " << inf.name << endl; - cout << "basename: " << inf.basename << endl; - cout << "isDir: " << inf.isDir << endl; - cout << "size: " << inf.size << endl; - cout << "time: " << inf.time << endl; -} -void print(FileInfoPtr inf) { print(*inf); } - -void print(FileInfoList &lst) -{ - for(unsigned i=0; isize() << endl; - - return 0; -} diff --git a/libs/mangle/vfs/tests/dummy_vfs.cpp b/libs/mangle/vfs/tests/dummy_vfs.cpp deleted file mode 100644 index 0448e96a5..000000000 --- a/libs/mangle/vfs/tests/dummy_vfs.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// This file is shared between several test programs -#include "vfs.hpp" -#include -#include - -#include "../../stream/servers/memory_stream.hpp" - -using namespace Mangle::VFS; -using namespace Mangle::Stream; - -class DummyVFS : public VFS -{ -public: - DummyVFS() - { - hasFind = false; - hasList = true; - isCaseSensitive = true; - } - - // We only support opening 'file1' at the moment. - StreamPtr open(const std::string &name) - { - assert(name == "file1"); - return StreamPtr(new MemoryStream("hello world", 11)); - } - - bool isFile(const std::string &name) const - { - return (name == "file1" || - name == "dir/file2"); - } - - bool isDir(const std::string &name) const - { - return name == "dir"; - } - - /// Get info about a single file - FileInfoPtr stat(const std::string &name) const - { - FileInfoPtr fi(new FileInfo); - fi->name = name; - fi->time = 0; - - if(isFile(name)) - { - if(name == "dir/file2") - { - fi->basename = "file2"; - fi->size = 2; - } - else - { - fi->basename = "file1"; - fi->size = 1; - } - fi->isDir = false; - } - else if(isDir(name)) - { - fi->basename = "dir"; - fi->isDir = true; - fi->size = 0; - } - else assert(0); - - return fi; - } - - /// List all entries in a given directory. A blank dir should be - /// interpreted as a the root/current directory of the archive. If - /// dirs is true, list directories instead of files. - virtual FileInfoListPtr list(const std::string& dir = "", - bool recurse=true, - bool dirs=false) const - { - assert(dir == ""); - - FileInfoListPtr fl(new FileInfoList); - - FileInfo fi; - - if(!dirs) - { - fi.name = "file1"; - fi.basename = "file1"; - fi.isDir = false; - fi.size = 1; - fi.time = 0; - fl->push_back(fi); - - if(recurse) - { - fi.name = "dir/file2"; - fi.basename = "file2"; - fi.size = 2; - fl->push_back(fi); - } - } - else - { - fi.name = "dir"; - fi.basename = "dir"; - fi.isDir = true; - fi.size = 0; - fi.time = 0; - fl->push_back(fi); - } - return fl; - } - - FileInfoListPtr find(const std::string& pattern, - bool recursive=true, - bool dirs=false) const - { assert(0); } -}; diff --git a/libs/mangle/vfs/tests/ogre_client_test.cpp b/libs/mangle/vfs/tests/ogre_client_test.cpp deleted file mode 100644 index 8e1269b56..000000000 --- a/libs/mangle/vfs/tests/ogre_client_test.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "dummy_vfs.cpp" -#include "../clients/ogre_archive.hpp" -#include - -using namespace Ogre; -using namespace std; - -void print(StringVectorPtr lst) -{ - int s = lst->size(); - - for(int i=0; isize() << endl; - cout << "contents: " << file->getAsString() << endl; - - return 0; -} diff --git a/libs/mangle/vfs/tests/ogre_resource_test.cpp b/libs/mangle/vfs/tests/ogre_resource_test.cpp deleted file mode 100644 index 8965adaa1..000000000 --- a/libs/mangle/vfs/tests/ogre_resource_test.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include -#include - -/* - This isn't really a test of our implementation, but a test of using - the Ogre resource system to find files. If the Ogre interface - changes and you have to change this test, you will have to change - the servers/ogre_vfs.cpp implementation equivalently. - - */ - -using namespace std; -using namespace Ogre; - -ResourceGroupManager *gm; -String group; - -void find(const std::string &fileName) -{ - cout << "\nFile: " << fileName << endl; - - if(!gm->resourceExists(group, fileName)) - { - cout << "Does not exist\n"; - return; - } - - DataStreamPtr data = gm->openResource(fileName, group); - - cout << "Size: " << data->size() << endl; - cout << "First line: " << data->getLine() << "\n"; -} - -int main() -{ - // Disable logging - new LogManager; - Log *log = LogManager::getSingleton().createLog(""); - log->setDebugOutputEnabled(false); - - // Set up Ogre - Root root("","",""); - - root.addResourceLocation("./", "FileSystem", "General"); - - gm = ResourceGroupManager::getSingletonPtr(); - group = gm->getWorldResourceGroupName(); - - find("Makefile"); - find("ogre_resource_test.cpp"); - find("bleh"); - - cout << "\nAll source files:\n"; - FileInfoListPtr list = gm->findResourceFileInfo(group, "*.cpp"); - FileInfoList::iterator it, end; - it = list->begin(); - end = list->end(); - for(; it != end; it++) - cout << " " << it->filename << endl; -} diff --git a/libs/mangle/vfs/tests/ogre_server_test.cpp b/libs/mangle/vfs/tests/ogre_server_test.cpp deleted file mode 100644 index b846eef96..000000000 --- a/libs/mangle/vfs/tests/ogre_server_test.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../servers/ogre_vfs.hpp" - -#include - -#include "server_common.cpp" - -Ogre::Root *root; - -void setupOgre() -{ - using namespace Ogre; - - // Disable logging - new LogManager; - Log *log = LogManager::getSingleton().createLog(""); - log->setDebugOutputEnabled(false); - - // Set up Root - root = new Root("","",""); - - // Add a zip file and the current directory - root->addResourceLocation("test.zip", "Zip", "General"); - root->addResourceLocation("./", "FileSystem", "General"); -} - -int main() -{ - // Set up the engine - setupOgre(); - - // This is our entry point into the resource file system - OgreVFS vfs("General"); - - // Run the test - testAll(vfs); - - return 0; -} diff --git a/libs/mangle/vfs/tests/output/dummy_test.out b/libs/mangle/vfs/tests/output/dummy_test.out deleted file mode 100644 index 61f1fda80..000000000 --- a/libs/mangle/vfs/tests/output/dummy_test.out +++ /dev/null @@ -1,31 +0,0 @@ -Listing all files: -name: file1 -basename: file1 -isDir: 0 -size: 1 -time: 0 -name: dir/file2 -basename: file2 -isDir: 0 -size: 2 -time: 0 - -Stat for single files: -name: file1 -basename: file1 -isDir: 0 -size: 1 -time: 0 - -name: dir/file2 -basename: file2 -isDir: 0 -size: 2 -time: 0 - -name: dir -basename: dir -isDir: 1 -size: 0 -time: 0 -filesize: 11 diff --git a/libs/mangle/vfs/tests/output/ogre_client_test.out b/libs/mangle/vfs/tests/output/ogre_client_test.out deleted file mode 100644 index bc10b1e5c..000000000 --- a/libs/mangle/vfs/tests/output/ogre_client_test.out +++ /dev/null @@ -1,12 +0,0 @@ -Case: 1 -Name: dummy -Type: Mangle -All files: - file1 - dir/file2 -Non-recursive: - file1 -Dirs: - dir -filesize: 11 -contents: hello world diff --git a/libs/mangle/vfs/tests/output/ogre_resource_test.out b/libs/mangle/vfs/tests/output/ogre_resource_test.out deleted file mode 100644 index 9bfc4ff5b..000000000 --- a/libs/mangle/vfs/tests/output/ogre_resource_test.out +++ /dev/null @@ -1,20 +0,0 @@ - -File: Makefile -Size: 828 -First line: GCC=g++ -I../ - -File: ogre_resource_test.cpp -Size: 1437 -First line: #include - -File: bleh -Does not exist - -All source files: - physfs_server_test.cpp - dummy_test.cpp - ogre_resource_test.cpp - server_common.cpp - dummy_vfs.cpp - ogre_client_test.cpp - ogre_server_test.cpp diff --git a/libs/mangle/vfs/tests/output/ogre_server_test.out b/libs/mangle/vfs/tests/output/ogre_server_test.out deleted file mode 100644 index 74f350844..000000000 --- a/libs/mangle/vfs/tests/output/ogre_server_test.out +++ /dev/null @@ -1,11 +0,0 @@ - -File: Makefile -Size: 828 -First 12 bytes: GCC=g++ -I.. - -File: testfile.txt -Size: 13 -First 12 bytes: hello world! - -File: blah_bleh -File doesn't exist diff --git a/libs/mangle/vfs/tests/output/physfs_server_test.out b/libs/mangle/vfs/tests/output/physfs_server_test.out deleted file mode 100644 index 74f350844..000000000 --- a/libs/mangle/vfs/tests/output/physfs_server_test.out +++ /dev/null @@ -1,11 +0,0 @@ - -File: Makefile -Size: 828 -First 12 bytes: GCC=g++ -I.. - -File: testfile.txt -Size: 13 -First 12 bytes: hello world! - -File: blah_bleh -File doesn't exist diff --git a/libs/mangle/vfs/tests/physfs_server_test.cpp b/libs/mangle/vfs/tests/physfs_server_test.cpp deleted file mode 100644 index 9e9d088cd..000000000 --- a/libs/mangle/vfs/tests/physfs_server_test.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "../servers/physfs_vfs.hpp" - -#include "server_common.cpp" - -#include - -int main() -{ - // Set up the library and paths - PHYSFS_init("blah"); - PHYSFS_addToSearchPath("test.zip", 1); - PHYSFS_addToSearchPath("./", 1); - - // Create our interface - PhysVFS vfs; - - // Run the test - testAll(vfs); - - return 0; -} diff --git a/libs/mangle/vfs/tests/server_common.cpp b/libs/mangle/vfs/tests/server_common.cpp deleted file mode 100644 index 1834bc25a..000000000 --- a/libs/mangle/vfs/tests/server_common.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include - -using namespace Mangle::VFS; -using namespace Mangle::Stream; -using namespace std; - -void find(VFS &vfs, const std::string &file) -{ - cout << "\nFile: " << file << endl; - - if(!vfs.isFile(file)) - { - cout << "File doesn't exist\n"; - return; - } - - StreamPtr data = vfs.open(file); - - cout << "Size: " << data->size() << endl; - - char buf[13]; - buf[12] = 0; - data->read(buf, 12); - - cout << "First 12 bytes: " << buf << "\n"; -} - -void testAll(VFS &vfs) -{ - find(vfs, "Makefile"); // From the file system - find(vfs, "testfile.txt"); // From the zip - find(vfs, "blah_bleh"); // Doesn't exist -} diff --git a/libs/mangle/vfs/tests/test.sh b/libs/mangle/vfs/tests/test.sh deleted file mode 100755 index 2d07708ad..000000000 --- a/libs/mangle/vfs/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -make || exit - -mkdir -p output - -PROGS=*_test - -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done diff --git a/libs/mangle/vfs/tests/test.zip b/libs/mangle/vfs/tests/test.zip deleted file mode 100644 index ec82f8bc6be46902264847237d30818665e89072..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169 zcmWIWW@h1H0D;ZXv#htQzs|@DWP>mdgD68uYH>+gW=^VJNkvI$2qy#cq^G9dGk`d> zf`#D)^9$yT)SR4rh4TEOoD@Z_0B=Snab{emfy`uJUcwofH({Q*9Ik` diff --git a/libs/mangle/vfs/vfs.hpp b/libs/mangle/vfs/vfs.hpp deleted file mode 100644 index 258a6b0a0..000000000 --- a/libs/mangle/vfs/vfs.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef MANGLE_VFS_H -#define MANGLE_VFS_H - -#include "../stream/stream.hpp" -#include -#include - -namespace Mangle { -namespace VFS { - -/// Generic file info structure -struct FileInfo -{ - /// Full name, including path - std::string name; - - /// Base name, not including path - std::string basename; - - /// Is this a directory? - bool isDir; - - /// File size - size_t size; - - /// Last modification date - time_t time; -}; - -typedef std::vector FileInfoList; - -typedef boost::shared_ptr FileInfoPtr; -typedef boost::shared_ptr FileInfoListPtr; - -/** An interface to any file system or other provider of named data - streams -*/ -class VFS -{ - public: - // Feature options. These should be set in the constructor. - - /// If true, the list() function work - bool hasList; - - /// If true, the find() function work - bool hasFind; - - /// If true, the file system is case sensitive - bool isCaseSensitive; - - /// Virtual destructor - virtual ~VFS() {} - - /// Open a new data stream. Deleting the object (letting all the - /// pointers to it go out of scope) should be enough to close it. - virtual Stream::StreamPtr open(const std::string &name) = 0; - - /// Check for the existence of a file - virtual bool isFile(const std::string &name) const = 0; - - /// Check for the existence of a directory - virtual bool isDir(const std::string &name) const = 0; - - /// Get info about a single file - virtual FileInfoPtr stat(const std::string &name) const = 0; - - /// List all entries in a given directory. A blank dir should be - /// interpreted as a the root/current directory of the archive. If - /// dirs is true, list directories instead of files. - virtual FileInfoListPtr list(const std::string& dir = "", - bool recurse=true, - bool dirs=false) const = 0; - - /// Find files after a given pattern. Wildcards (*) are - /// supported. Only valid if 'hasFind' is true. Don't implement your - /// own pattern matching here if the backend doesn't support it - /// natively; use a filter instead. - virtual FileInfoListPtr find(const std::string& pattern, - bool recursive=true, - bool dirs=false) const = 0; -}; - -typedef boost::shared_ptr VFSPtr; - -}} // namespaces -#endif From bc0a6bffcff90bc12c54ec465d9057aa507af43c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 09:03:35 -0700 Subject: [PATCH 111/298] Remove outdated comment --- components/bsa/bsa_file.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index afe0c739c..6f3ab3bce 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -115,9 +115,6 @@ public: /** Open a file contained in the archive. Throws an exception if the file doesn't exist. - - NOTE: All files opened from one archive will share a common file - handle. This is NOT thread safe. */ Ogre::DataStreamPtr getFile(const char *file); From 871b1d1c9bd3ceab8be326253f3d1d665915f3b4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Jul 2012 19:20:59 +0200 Subject: [PATCH 112/298] silenced a warning --- components/bsa/bsa_file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index b5145a4e4..8f605b8ed 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -200,7 +200,7 @@ void BSAFile::readHeader() input.read(&stringBuf[0], stringBuf.size()); // Check our position - assert(input.tellg() == 12+dirsize); + assert(input.tellg() == static_cast (12+dirsize)); // Calculate the offset of the data buffer. All file offsets are // relative to this. 12 header bytes + directory + hash table From da916cecfb1f17e0d073572ce8d6dabafd9f40f7 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Jul 2012 19:29:09 +0200 Subject: [PATCH 113/298] fixed a bug in a cmake script that resulted in some files being compiled twice --- apps/openmw/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b12c58f0d..a66cda71e 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -79,7 +79,6 @@ ENDIF(WIN32) ENDIF(OGRE_STATIC) add_executable(openmw ${OPENMW_LIBS} ${OPENMW_LIBS_HEADER} - ${COMPONENT_FILES} ${OPENMW_FILES} ${GAME} ${GAME_HEADER} ${APPLE_BUNDLE_RESOURCES} From 300730a834d49b25f52d7002b737062070e88b93 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 11:11:49 -0700 Subject: [PATCH 114/298] Create the skeleton resource from NIFs Note they are not loaded yet. --- components/nifogre/ogre_nif_loader.cpp | 64 ++++++++++++++++++++++++-- components/nifogre/ogre_nif_loader.hpp | 4 +- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 40740ac56..99ccb7fb1 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -128,6 +128,60 @@ public: }; +struct NIFSkeletonLoader : public Ogre::ManualResourceLoader { + +static void warn(const std::string &msg) +{ + std::cerr << "NIFSkeletonLoader: Warn: " << msg << std::endl; +} + +static void fail(const std::string &msg) +{ + std::cerr << "NIFSkeletonLoader: Fail: "<< msg << std::endl; + abort(); +} + +void loadResource(Ogre::Resource *resource) +{ + warn("Found no records in NIF for "+resource->getName()); +} + +static bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node, Ogre::SkeletonPtr *skel) +{ + if(node->boneTrafo != NULL) + { + Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton(); + + Ogre::SkeletonPtr tmp = skelMgr.getByName(name); + if(tmp.isNull()) + { + static NIFSkeletonLoader loader; + tmp = skelMgr.create(name, group, true, &loader); + } + + if(skel) *skel = tmp; + return true; + } + + Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) + { + Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();i++) + { + if(!children[i].empty()) + { + if(createSkeleton(name, group, children[i].getPtr(), skel)) + return true; + } + } + } + return false; +} + +}; + + NIFLoader::LoaderMap NIFLoader::sLoaders; void NIFLoader::warn(const std::string &msg) @@ -325,7 +379,8 @@ void NIFLoader::loadResource(Ogre::Resource *resource) warn("Found no records in NIF for "+resource->getName()); } -void NIFLoader::createMeshes(const std::string &name, const std::string &group, Nif::Node *node, MeshPairList &meshes, int flags) + +void NIFLoader::createMeshes(const std::string &name, const std::string &group, bool hasSkel, Nif::Node *node, MeshPairList &meshes, int flags) { flags |= node->flags; @@ -342,6 +397,7 @@ void NIFLoader::createMeshes(const std::string &name, const std::string &group, NIFLoader *loader = &sLoaders[fullname]; loader->mName = name; loader->mShapeName = node->name; + loader->mHasSkel = hasSkel; mesh = meshMgr.createManual(fullname, group, loader); } @@ -359,11 +415,12 @@ void NIFLoader::createMeshes(const std::string &name, const std::string &group, for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) - createMeshes(name, group, children[i].getPtr(), meshes, flags); + createMeshes(name, group, hasSkel, children[i].getPtr(), meshes, flags); } } } + MeshPairList NIFLoader::load(const std::string &name, Ogre::SkeletonPtr *skel, const std::string &group) { MeshPairList meshes; @@ -390,7 +447,8 @@ MeshPairList NIFLoader::load(const std::string &name, Ogre::SkeletonPtr *skel, c return meshes; } - createMeshes(name, group, node, meshes); + bool hasSkel = NIFSkeletonLoader::createSkeleton(name, group, node, skel); + createMeshes(name, group, hasSkel, node, meshes); return meshes; } diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 092cc23a8..c90d65395 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -98,11 +98,13 @@ public: private: std::string mName; std::string mShapeName; + bool mHasSkel; static void warn(const std::string &msg); static void fail(const std::string &msg); - static void createMeshes(const std::string &name, const std::string &group, Nif::Node *node, MeshPairList &meshes, int flags=0); + static void createMeshes(const std::string &name, const std::string &group, bool hasSkel, + Nif::Node *node, MeshPairList &meshes, int flags=0); typedef std::map LoaderMap; static LoaderMap sLoaders; From 69ed73399ab367c445372d6a1610785e3e122502 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 11:37:36 -0700 Subject: [PATCH 115/298] Avoid exposing the NIF mesh resource loading class --- components/nifogre/ogre_nif_loader.cpp | 110 ++++++++++++++----------- components/nifogre/ogre_nif_loader.hpp | 18 +--- 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 99ccb7fb1..079e6425b 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -182,20 +182,6 @@ static bool createSkeleton(const std::string &name, const std::string &group, Ni }; -NIFLoader::LoaderMap NIFLoader::sLoaders; - -void NIFLoader::warn(const std::string &msg) -{ - std::cerr << "NIFLoader: Warn: " << msg << std::endl; -} - -void NIFLoader::fail(const std::string &msg) -{ - std::cerr << "NIFLoader: Fail: "<< msg << std::endl; - abort(); -} - - // Conversion of blend / test mode from NIF -> OGRE. // Not in use yet, so let's comment it out. /* @@ -374,51 +360,81 @@ void NIFLoader::createMaterial(const Ogre::String &name, #endif -void NIFLoader::loadResource(Ogre::Resource *resource) +class NIFMeshLoader : Ogre::ManualResourceLoader { - warn("Found no records in NIF for "+resource->getName()); -} + std::string mName; + std::string mGroup; + std::string mShapeName; + bool mHasSkel; + void warn(const std::string &msg) + { + std::cerr << "NIFMeshLoader: Warn: " << msg << std::endl; + } + + void fail(const std::string &msg) + { + std::cerr << "NIFMeshLoader: Fail: "<< msg << std::endl; + abort(); + } -void NIFLoader::createMeshes(const std::string &name, const std::string &group, bool hasSkel, Nif::Node *node, MeshPairList &meshes, int flags) -{ - flags |= node->flags; - // TODO: Check for extra data + typedef std::map LoaderMap; + static LoaderMap sLoaders; - if(node->recType == Nif::RC_NiTriShape) +public: + NIFMeshLoader() + : mHasSkel(false) + { } + NIFMeshLoader(const std::string &name, const std::string &group, bool hasSkel) + : mName(name), mGroup(group), mHasSkel(hasSkel) + { } + + virtual void loadResource(Ogre::Resource *resource) { - Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); - std::string fullname = name+"@"+node->name; + warn("Found no records in NIF for "+resource->getName()); + } - Ogre::MeshPtr mesh = meshMgr.getByName(fullname); - if(mesh.isNull()) + void createMeshes(Nif::Node *node, MeshPairList &meshes, int flags=0) + { + flags |= node->flags; + + // TODO: Check for extra data + + if(node->recType == Nif::RC_NiTriShape) { - NIFLoader *loader = &sLoaders[fullname]; - loader->mName = name; - loader->mShapeName = node->name; - loader->mHasSkel = hasSkel; + Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); + std::string fullname = mName+"@"+node->name; - mesh = meshMgr.createManual(fullname, group, loader); - } + Ogre::MeshPtr mesh = meshMgr.getByName(fullname); + if(mesh.isNull()) + { + NIFMeshLoader *loader = &sLoaders[fullname]; + *loader = *this; + loader->mShapeName = node->name; - meshes.push_back(std::make_pair(mesh, (node->parent ? node->parent->name : std::string()))); - } - else if(node->recType != Nif::RC_NiNode && node->recType != Nif::RC_RootCollisionNode && - node->recType != Nif::RC_NiRotatingParticles) - warn("Unhandled mesh node type: "+node->recName); + mesh = meshMgr.createManual(fullname, mGroup, loader); + } - Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) - { - Nif::NodeList &children = ninode->children; - for(size_t i = 0;i < children.length();i++) + meshes.push_back(std::make_pair(mesh, (node->parent ? node->parent->name : std::string()))); + } + else if(node->recType != Nif::RC_NiNode && node->recType != Nif::RC_RootCollisionNode && + node->recType != Nif::RC_NiRotatingParticles) + warn("Unhandled mesh node type: "+node->recName); + + Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) { - if(!children[i].empty()) - createMeshes(name, group, hasSkel, children[i].getPtr(), meshes, flags); + Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();i++) + { + if(!children[i].empty()) + createMeshes(children[i].getPtr(), meshes, flags); + } } } -} +}; +NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders; MeshPairList NIFLoader::load(const std::string &name, Ogre::SkeletonPtr *skel, const std::string &group) @@ -448,7 +464,9 @@ MeshPairList NIFLoader::load(const std::string &name, Ogre::SkeletonPtr *skel, c } bool hasSkel = NIFSkeletonLoader::createSkeleton(name, group, node, skel); - createMeshes(name, group, hasSkel, node, meshes); + + NIFMeshLoader meshldr(name, group, hasSkel); + meshldr.createMeshes(node, meshes); return meshes; } diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index c90d65395..431af1163 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -86,28 +86,12 @@ typedef std::vector< std::pair > MeshPairList; their parent nodes (as they pertain to the skeleton, which is optionally returned in the second argument if it exists). */ -class NIFLoader : Ogre::ManualResourceLoader +class NIFLoader { public: - virtual void loadResource(Ogre::Resource *resource); - static MeshPairList load(const std::string &name, Ogre::SkeletonPtr *skel=NULL, const std::string &group="General"); - -private: - std::string mName; - std::string mShapeName; - bool mHasSkel; - - static void warn(const std::string &msg); - static void fail(const std::string &msg); - - static void createMeshes(const std::string &name, const std::string &group, bool hasSkel, - Nif::Node *node, MeshPairList &meshes, int flags=0); - - typedef std::map LoaderMap; - static LoaderMap sLoaders; }; } From 9028cfe83c707a95c0c1e9a8582eb05936d931d8 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 12:02:47 -0700 Subject: [PATCH 116/298] Look for the NiTriShape when the mesh resource wants to load It's still not loaded yet. --- components/nifogre/ogre_nif_loader.cpp | 32 +++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 079e6425b..6896ac8fb 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -379,6 +379,31 @@ class NIFMeshLoader : Ogre::ManualResourceLoader } + bool findTriShape(Ogre::Mesh *mesh, Nif::Node *node) + { + if(node->recType == Nif::RC_NiTriShape && mShapeName == node->name) + { + warn("Not loading shape \""+mShapeName+"\" in "+mName); + return true; + } + + Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) + { + Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();i++) + { + if(!children[i].empty()) + { + if(findTriShape(mesh, children[i].getPtr())) + return true; + } + } + } + return false; + } + + typedef std::map LoaderMap; static LoaderMap sLoaders; @@ -392,7 +417,12 @@ public: virtual void loadResource(Ogre::Resource *resource) { - warn("Found no records in NIF for "+resource->getName()); + Ogre::Mesh *mesh = dynamic_cast(resource); + assert(mesh && "Attempting to load a mesh into a non-mesh resource!"); + + Nif::NIFFile nif(mName); + Nif::Node *node = dynamic_cast(nif.getRecord(0)); + findTriShape(mesh, node); } void createMeshes(Nif::Node *node, MeshPairList &meshes, int flags=0) From fb1f8082d2191b0194f856016870f5a974e805bc Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jul 2012 21:27:57 +0200 Subject: [PATCH 117/298] fix link error with recent glibc versions --- components/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 284ca3cce..efeb69cae 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -72,5 +72,11 @@ add_library(components STATIC ${COMPONENT_FILES}) target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES}) +# Fix for not visible pthreads functions for linker with glibc 2.15 +if (UNIX AND NOT APPLE) +target_link_libraries(components ${CMAKE_THREAD_LIBS_INIT}) +endif() + + # Make the variable accessible for other subdirectories set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) From 3029c221efb8cc6070196eef806712e8e538c388 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 12:59:39 -0700 Subject: [PATCH 118/298] Create materials when creating meshes --- components/nifogre/ogre_nif_loader.cpp | 146 ++++++++++++++++++++++--- 1 file changed, 130 insertions(+), 16 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 6896ac8fb..b0e92d82f 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -226,26 +226,131 @@ static CompareFunction getTestMode(int mode) } */ -#if 0 -void NIFLoader::createMaterial(const Ogre::String &name, - const Ogre::Vector3 &ambient, - const Ogre::Vector3 &diffuse, - const Ogre::Vector3 &specular, - const Ogre::Vector3 &emissive, - float glossiness, float alpha, - int alphaFlags, float alphaTest, - const Ogre::String &texName) + +class NIFMaterialLoader { + +static std::multimap MaterialMap; + +static void warn(const std::string &msg) { - Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().create(name, resourceGroup); + std::cerr << "NIFMeshLoader: Warn: " << msg << std::endl; +} + +static void fail(const std::string &msg) +{ + std::cerr << "NIFMeshLoader: Fail: "<< msg << std::endl; + abort(); +} + +public: +static Ogre::String getMaterial(const NiTriShape *shape, const Ogre::String &name, const Ogre::String &group) +{ + Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); + Ogre::MaterialPtr material = matMgr.getByName(name); + if(!material.isNull()) + return name; + + Ogre::Vector3 ambient(1.0f); + Ogre::Vector3 diffuse(1.0f); + Ogre::Vector3 specular(0.0f); + Ogre::Vector3 emissive(0.0f); + float glossiness = 0.0f; + float alpha = 1.0f; + int alphaFlags = -1; + ubyte alphaTest = 0; + Ogre::String texName; + + // These are set below if present + const NiTexturingProperty *t = NULL; + const NiMaterialProperty *m = NULL; + const NiAlphaProperty *a = NULL; + + // Scan the property list for material information + const PropertyList &list = shape->props; + for (size_t i = 0;i < list.length();i++) + { + // Entries may be empty + if (list[i].empty()) continue; + + const Property *pr = list[i].getPtr(); + if (pr->recType == RC_NiTexturingProperty) + t = static_cast(pr); + else if (pr->recType == RC_NiMaterialProperty) + m = static_cast(pr); + else if (pr->recType == RC_NiAlphaProperty) + a = static_cast(pr); + else + warn("Skipped property type: "+pr->recName); + } + + // Texture + if (t && t->textures[0].inUse) + { + NiSourceTexture *st = t->textures[0].texture.getPtr(); + if (st->external) + { + /* Bethesda at some at some point converted all their BSA + * textures from tga to dds for increased load speed, but all + * texture file name references were kept as .tga. + */ + texName = "textures\\" + st->filename; + if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texName)) + { + Ogre::String::size_type pos = texName.rfind('.'); + texName.replace(pos, texName.length(), ".dds"); + } + } + else warn("Found internal texture, ignoring."); + } + // Alpha modifiers + if (a) + { + alphaFlags = a->flags; + alphaTest = a->data.threshold; + } + + // Material + if(m) + { + ambient = m->data.ambient; + diffuse = m->data.diffuse; + specular = m->data.specular; + emissive = m->data.emissive; + glossiness = m->data.glossiness; + alpha = m->data.alpha; + } + + Ogre::String matname = name; + if (m || !texName.empty()) + { + // If we're here, then this mesh has a material. Thus we + // need to calculate a snappy material name. It should + // contain the mesh name (mesh->getName()) but also has to + // be unique. One mesh may use many materials. + std::multimap::iterator itr = MaterialMap.find(texName); + std::multimap::iterator lastElement; + lastElement = MaterialMap.upper_bound(texName); + if (itr != MaterialMap.end()) + { + for ( ; itr != lastElement; ++itr) + { + //std::cout << "OK!"; + //MaterialPtr mat = MaterialManager::getSingleton().getByName(itr->second,recourceGroup); + return itr->second; + //if( mat->getA + } + } + } + + // No existing material like this. Create a new one. + material = matMgr.create(matname, group, true); // This assigns the texture to this material. If the texture name is // a file name, and this file exists (in a resource directory), it // will automatically be loaded when needed. If not (such as for // internal NIF textures that we might support later), we should // already have inserted a manual loader for the texture. - - if (!texName.empty()) { Ogre::Pass *pass = material->getTechnique(0)->getPass(0); @@ -356,8 +461,13 @@ void NIFLoader::createMaterial(const Ogre::String &name, material->setSpecular(specular[0], specular[1], specular[2], alpha); material->setSelfIllumination(emissive[0], emissive[1], emissive[2]); material->setShininess(glossiness); + + MaterialMap.insert(std::make_pair(texName, matname)); + return matname; } -#endif + +}; +std::multimap NIFMaterialLoader::MaterialMap; class NIFMeshLoader : Ogre::ManualResourceLoader @@ -365,6 +475,7 @@ class NIFMeshLoader : Ogre::ManualResourceLoader std::string mName; std::string mGroup; std::string mShapeName; + std::string mMaterialName; bool mHasSkel; void warn(const std::string &msg) @@ -433,20 +544,23 @@ public: if(node->recType == Nif::RC_NiTriShape) { + NiTriShape *shape = dynamic_cast(node); + Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); - std::string fullname = mName+"@"+node->name; + std::string fullname = mName+"@"+shape->name; Ogre::MeshPtr mesh = meshMgr.getByName(fullname); if(mesh.isNull()) { NIFMeshLoader *loader = &sLoaders[fullname]; *loader = *this; - loader->mShapeName = node->name; + loader->mShapeName = shape->name; + loader->mMaterialName = NIFMaterialLoader::getMaterial(shape, fullname, mGroup); mesh = meshMgr.createManual(fullname, mGroup, loader); } - meshes.push_back(std::make_pair(mesh, (node->parent ? node->parent->name : std::string()))); + meshes.push_back(std::make_pair(mesh, (shape->parent ? shape->parent->name : std::string()))); } else if(node->recType != Nif::RC_NiNode && node->recType != Nif::RC_RootCollisionNode && node->recType != Nif::RC_NiRotatingParticles) From 441a5c2da20f595da6fd5d8ad0b98c2f6c4b9f07 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 14:12:12 -0700 Subject: [PATCH 119/298] Load NiTriShapes into Ogre meshes --- components/nifogre/ogre_nif_loader.cpp | 173 ++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index b0e92d82f..884d8f64e 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -490,11 +490,182 @@ class NIFMeshLoader : Ogre::ManualResourceLoader } + // Convert NiTriShape to Ogre::SubMesh + void handleNiTriShape(Ogre::Mesh *mesh, Nif::NiTriShape *shape) + { + const Nif::NiTriShapeData *data = shape->data.getPtr(); + const Nif::NiSkinInstance *skin = (shape->skin.empty() ? NULL : shape->skin.getPtr()); + std::vector srcVerts = data->vertices; + std::vector srcNorms = data->normals; + if(skin != NULL) + { +#if 0 + // Convert vertices and normals back to bone space + std::vector newVerts(srcVerts.size(), Vector3(0,0,0)); + std::vector newNorms(srcNorms.size(), Vector3(0,0,0)); + + NiSkinDataRef data = skin->GetSkinData(); + const std::vector &bones = skin->GetBones(); + for(size_t b = 0;b < bones.size();b++) + { + Matrix44 mat = data->GetBoneTransform(b) * bones[b]->GetWorldTransform(); + + const std::vector &weights = data->GetBoneWeights(b); + for(size_t i = 0;i < weights.size();i++) + { + size_t index = weights[i].index; + float weight = weights[i].weight; + + newVerts.at(index) += (mat*srcVerts[index]) * weight; + if(newNorms.size() > index) + { + for(size_t j = 0;j < 3;j++) + { + newNorms[index][j] += mat[0][j]*srcNorms[index][0] * weight; + newNorms[index][j] += mat[1][j]*srcNorms[index][1] * weight; + newNorms[index][j] += mat[2][j]*srcNorms[index][2] * weight; + } + } + } + } + + srcVerts = newVerts; + srcNorms = newNorms; +#endif + } + + // Set the bounding box first + BoundsFinder bounds; + bounds.add(&srcVerts[0][0], srcVerts.size()); + mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX(), bounds.minY(), bounds.minZ(), + bounds.maxX(), bounds.maxY(), bounds.maxZ())); + mesh->_setBoundingSphereRadius(bounds.getRadius()); + + // This function is just one long stream of Ogre-barf, but it works + // great. + Ogre::HardwareBufferManager *hwBufMgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareVertexBufferSharedPtr vbuf; + Ogre::HardwareIndexBufferSharedPtr ibuf; + Ogre::VertexBufferBinding *bind; + Ogre::VertexDeclaration *decl; + int nextBuf = 0; + + Ogre::SubMesh *sub = mesh->createSubMesh(shape->name); + + // Add vertices + sub->useSharedVertices = false; + sub->vertexData = new Ogre::VertexData(); + sub->vertexData->vertexStart = 0; + sub->vertexData->vertexCount = srcVerts.size(); + + decl = sub->vertexData->vertexDeclaration; + bind = sub->vertexData->vertexBufferBinding; + if(srcVerts.size()) + { + vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + srcVerts.size(), Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, + true); + vbuf->writeData(0, vbuf->getSizeInBytes(), &srcVerts[0][0], true); + + decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); + bind->setBinding(nextBuf++, vbuf); + } + + // Vertex normals + if(srcNorms.size()) + { + vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + srcNorms.size(), Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, + true); + vbuf->writeData(0, vbuf->getSizeInBytes(), &srcNorms[0][0], true); + + decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); + bind->setBinding(nextBuf++, vbuf); + } + + // Vertex colors + const std::vector &colors = data->colors; + if(colors.size()) + { + Ogre::RenderSystem* rs = Ogre::Root::getSingleton().getRenderSystem(); + std::vector colorsRGB(colors.size()); + for(size_t i = 0;i < colorsRGB.size();i++) + { + Ogre::ColourValue clr(colors[i][0], colors[i][1], colors[i][2], colors[i][3]); + rs->convertColourValue(clr, &colorsRGB[i]); + } + vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), + colorsRGB.size(), Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, + true); + vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB[0], true); + decl->addElement(nextBuf, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); + bind->setBinding(nextBuf++, vbuf); + } + + // Texture UV coordinates + size_t numUVs = data->uvlist.size(); + if(numUVs) + { + size_t elemSize = Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2); + vbuf = hwBufMgr->createVertexBuffer(elemSize, srcVerts.size()*numUVs, + Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, true); + for(size_t i = 0;i < numUVs;i++) + { + const std::vector &uvlist = data->uvlist[i]; + vbuf->writeData(i*srcVerts.size()*elemSize, elemSize*srcVerts.size(), &uvlist[0], true); + decl->addElement(nextBuf, i*srcVerts.size()*elemSize, Ogre::VET_FLOAT2, + Ogre::VES_TEXTURE_COORDINATES, i); + } + bind->setBinding(nextBuf++, vbuf); + } + + // Triangle faces + const std::vector &srcIdx = data->triangles; + if(srcIdx.size()) + { + ibuf = hwBufMgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, srcIdx.size(), + Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY); + ibuf->writeData(0, ibuf->getSizeInBytes(), &srcIdx[0], true); + sub->indexData->indexBuffer = ibuf; + sub->indexData->indexCount = srcIdx.size(); + sub->indexData->indexStart = 0; + } + + // Assign bone weights for this TriShape +#if 0 + if(skin != NULL) + { + // Get the skeleton resource, so weights can be applied + Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); + Ogre::SkeletonPtr skel = skelMgr->getByName(mesh->getSkeletonName()); + + NiSkinDataRef data = skin->GetSkinData(); + const std::vector &bones = skin->GetBones(); + for(size_t i = 0;i < bones.size();i++) + { + Ogre::VertexBoneAssignment boneInf; + boneInf.boneIndex = skel->getBone(bones[i]->GetName())->getHandle(); + + const std::vector &weights = data->GetBoneWeights(i); + for(size_t j = 0;j < weights.size();j++) + { + boneInf.vertexIndex = weights[j].index; + boneInf.weight = weights[j].weight; + sub->addBoneAssignment(boneInf); + } + } + } +#endif + + if(mMaterialName.length() > 0) + sub->setMaterialName(mMaterialName); + } + bool findTriShape(Ogre::Mesh *mesh, Nif::Node *node) { if(node->recType == Nif::RC_NiTriShape && mShapeName == node->name) { - warn("Not loading shape \""+mShapeName+"\" in "+mName); + handleNiTriShape(mesh, dynamic_cast(node)); return true; } From 61f32eca77bcf93e25d609987cddb72cc2bbc0f8 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 15:23:49 -0700 Subject: [PATCH 120/298] Add methods to get a node's local and full transform as a 4x4 matrix --- components/nif/nif_file.cpp | 14 ++++++++++++++ components/nif/node.hpp | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 231349302..789bae5e3 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -210,3 +210,17 @@ void NiSkinInstance::post(NIFFile *nif) bones[i]->makeBone(i, data->bones[i]); } } + +Ogre::Matrix4 Node::getLocalTransform() +{ + Ogre::Matrix4 mat4; + mat4.makeTransform(trafo.pos, Ogre::Vector3(trafo.scale), Ogre::Quaternion(trafo.rotation)); + return mat4; +} + +Ogre::Matrix4 Node::getWorldTransform() +{ + if(parent != NULL) + return getLocalTransform() * parent->getWorldTransform(); + return getLocalTransform(); +} diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 1f1b91a46..f7d3c6e96 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -24,6 +24,8 @@ #ifndef _NIF_NODE_H_ #define _NIF_NODE_H_ +#include + #include "controlled.hpp" #include "data.hpp" #include "property.hpp" @@ -108,6 +110,9 @@ public: boneTrafo = &bi.trafo; boneIndex = ind; } + + Ogre::Matrix4 getLocalTransform(); + Ogre::Matrix4 getWorldTransform(); }; struct NiNode : Node From b1f7fd9f7b39b9728839b84693ab8617be739fd5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 17:00:27 -0700 Subject: [PATCH 121/298] Pre-transform the mesh vertices when there's no skinning or skeleton --- components/nif/nif_file.cpp | 2 +- components/nifogre/ogre_nif_loader.cpp | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 789bae5e3..308196f32 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -213,7 +213,7 @@ void NiSkinInstance::post(NIFFile *nif) Ogre::Matrix4 Node::getLocalTransform() { - Ogre::Matrix4 mat4; + Ogre::Matrix4 mat4(Ogre::Matrix4::IDENTITY); mat4.makeTransform(trafo.pos, Ogre::Vector3(trafo.scale), Ogre::Quaternion(trafo.rotation)); return mat4; } diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 884d8f64e..01551a702 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -533,12 +533,33 @@ class NIFMeshLoader : Ogre::ManualResourceLoader srcNorms = newNorms; #endif } + else if(!mHasSkel) + { + // No skinning and no skeleton, so just transform the vertices and + // normals into position. + Ogre::Matrix4 mat4 = shape->getWorldTransform(); + for(size_t i = 0;i < srcVerts.size();i++) + { + Ogre::Vector4 vec4(srcVerts[i].x, srcVerts[i].y, srcVerts[i].z, 1.0f); + vec4 = mat4*vec4; + srcVerts[i] = Ogre::Vector3(&vec4[0]); + } + for(size_t i = 0;i < srcNorms.size();i++) + { + Ogre::Vector4 vec4(srcNorms[i].x, srcNorms[i].y, srcNorms[i].z, 0.0f); + vec4 = mat4*vec4; + srcNorms[i] = Ogre::Vector3(&vec4[0]); + } + } // Set the bounding box first BoundsFinder bounds; bounds.add(&srcVerts[0][0], srcVerts.size()); - mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX(), bounds.minY(), bounds.minZ(), - bounds.maxX(), bounds.maxY(), bounds.maxZ())); + // No idea why this offset is needed. It works fine without it if the + // vertices weren't transformed first, but otherwise it fails later on + // when the object is being inserted into the scene. + mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX()-0.5f, bounds.minY()-0.5f, bounds.minZ()-0.5f, + bounds.maxX()+0.5f, bounds.maxY()+0.5f, bounds.maxZ()+0.5f)); mesh->_setBoundingSphereRadius(bounds.getRadius()); // This function is just one long stream of Ogre-barf, but it works From ad75b47472dbf3856120fe2ee8b65d352395b34c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 15 Jul 2012 19:07:31 -0700 Subject: [PATCH 122/298] Build and set up a skeleton for meshes --- components/nifogre/ogre_nif_loader.cpp | 74 ++++++++++++++++++-------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 01551a702..1db0b92a5 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -141,9 +141,35 @@ static void fail(const std::string &msg) abort(); } + +void buildBones(Ogre::Skeleton *skel, Nif::NiNode *node, Ogre::Bone *parent=NULL) +{ + Ogre::Bone *bone = skel->createBone(node->name); + if(parent) parent->addChild(bone); + + bone->setOrientation(node->trafo.rotation); + bone->setPosition(node->trafo.pos); + bone->setScale(Ogre::Vector3(node->trafo.scale)); + bone->setBindingPose(); + bone->setInitialState(); + + const Nif::NodeList &children = node->children; + for(size_t i = 0;i < children.length();i++) + { + Nif::NiNode *next; + if(!children[i].empty() && (next=dynamic_cast(children[i].getPtr()))) + buildBones(skel, next, bone); + } +} + void loadResource(Ogre::Resource *resource) { - warn("Found no records in NIF for "+resource->getName()); + Ogre::Skeleton *skel = dynamic_cast(resource); + OgreAssert(skel, "Attempting to load a skeleton into a non-skeleton resource!"); + + Nif::NIFFile nif(skel->getName()); + Nif::NiNode *node = dynamic_cast(nif.getRecord(0)); + buildBones(skel, node); } static bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node, Ogre::SkeletonPtr *skel) @@ -499,21 +525,29 @@ class NIFMeshLoader : Ogre::ManualResourceLoader std::vector srcNorms = data->normals; if(skin != NULL) { -#if 0 - // Convert vertices and normals back to bone space - std::vector newVerts(srcVerts.size(), Vector3(0,0,0)); - std::vector newNorms(srcNorms.size(), Vector3(0,0,0)); - - NiSkinDataRef data = skin->GetSkinData(); - const std::vector &bones = skin->GetBones(); - for(size_t b = 0;b < bones.size();b++) + // Only set a skeleton when skinning. Unskinned meshes with a skeleton will be + // explicitly attached later. + mesh->setSkeletonName(mName); + + // Convert vertices and normals to bone space from bind position. It would be + // better to transform the bones into bind position, but there doesn't seem to + // be a reliable way to do that. + std::vector newVerts(srcVerts.size(), Ogre::Vector3(0.0f)); + std::vector newNorms(srcNorms.size(), Ogre::Vector3(1.0f)); + + const Nif::NiSkinData *data = skin->data.getPtr(); + const Nif::NodeList &bones = skin->bones; + for(size_t b = 0;b < bones.length();b++) { - Matrix44 mat = data->GetBoneTransform(b) * bones[b]->GetWorldTransform(); + Ogre::Matrix4 mat(Ogre::Matrix4::IDENTITY); + mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale), + Ogre::Quaternion(data->bones[b].trafo.rotation)); + mat = mat * bones[b]->getWorldTransform(); - const std::vector &weights = data->GetBoneWeights(b); + const std::vector &weights = data->bones[b].weights; for(size_t i = 0;i < weights.size();i++) { - size_t index = weights[i].index; + size_t index = weights[i].vertex; float weight = weights[i].weight; newVerts.at(index) += (mat*srcVerts[index]) * weight; @@ -531,7 +565,6 @@ class NIFMeshLoader : Ogre::ManualResourceLoader srcVerts = newVerts; srcNorms = newNorms; -#endif } else if(!mHasSkel) { @@ -653,30 +686,29 @@ class NIFMeshLoader : Ogre::ManualResourceLoader } // Assign bone weights for this TriShape -#if 0 if(skin != NULL) { // Get the skeleton resource, so weights can be applied Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); Ogre::SkeletonPtr skel = skelMgr->getByName(mesh->getSkeletonName()); + skel->touch(); - NiSkinDataRef data = skin->GetSkinData(); - const std::vector &bones = skin->GetBones(); - for(size_t i = 0;i < bones.size();i++) + const Nif::NiSkinData *data = skin->data.getPtr(); + const Nif::NodeList &bones = skin->bones; + for(size_t i = 0;i < bones.length();i++) { Ogre::VertexBoneAssignment boneInf; - boneInf.boneIndex = skel->getBone(bones[i]->GetName())->getHandle(); + boneInf.boneIndex = skel->getBone(bones[i]->name)->getHandle(); - const std::vector &weights = data->GetBoneWeights(i); + const std::vector &weights = data->bones[i].weights; for(size_t j = 0;j < weights.size();j++) { - boneInf.vertexIndex = weights[j].index; + boneInf.vertexIndex = weights[j].vertex; boneInf.weight = weights[j].weight; sub->addBoneAssignment(boneInf); } } } -#endif if(mMaterialName.length() > 0) sub->setMaterialName(mMaterialName); From e862b6b5a5cc7a8a001e3aa5df2983bfc3ef406b Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Mon, 16 Jul 2012 15:53:02 +0400 Subject: [PATCH 123/298] Fix comparison in cmake --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3175fa7d..9cc741c67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VE # Debug suffix for plugins set(DEBUG_SUFFIX "") if (DEFINED CMAKE_BUILD_TYPE) - if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") + if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(DEBUG_SUFFIX "_d") endif() endif() From a539e98274971f9495075b7092a5ba264c3e7994 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 16 Jul 2012 10:48:48 -0700 Subject: [PATCH 124/298] Handle all meshes when inserting objects into the scene --- apps/openmw/mwrender/objects.cpp | 69 ++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index ea58ec716..2ff9de9a4 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -92,12 +92,19 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) Ogre::SceneNode* insert = ptr.getRefData().getBaseNode(); assert(insert); - // FIXME: There can be more than one! + Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load(mesh); - Ogre::Entity *ent = mRenderer.getScene()->createEntity(meshes[0].first->getName()); - + std::vector entities(meshes.size()); + for(size_t i = 0;i < meshes.size();i++) + { + entities[i] = mRenderer.getScene()->createEntity(meshes[i].first->getName()); - Ogre::Vector3 extents = ent->getBoundingBox().getSize(); + const Ogre::AxisAlignedBox &tmp = entities[i]->getBoundingBox(); + bounds.merge(Ogre::AxisAlignedBox(insert->_getDerivedPosition() + tmp.getMinimum(), + insert->_getDerivedPosition() + tmp.getMaximum()) + ); + } + Ogre::Vector3 extents = bounds.getSize(); extents *= insert->getScale(); float size = std::max(std::max(extents.x, extents.y), extents.z); @@ -109,42 +116,42 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) if (mBounds.find(ptr.getCell()) == mBounds.end()) mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; - - Ogre::AxisAlignedBox bounds = ent->getBoundingBox(); - bounds = Ogre::AxisAlignedBox( - insert->_getDerivedPosition() + bounds.getMinimum(), - insert->_getDerivedPosition() + bounds.getMaximum() - ); - - bounds.scale(insert->getScale()); mBounds[ptr.getCell()].merge(bounds); bool transparent = false; - for (unsigned int i=0; igetNumSubEntities(); ++i) + for(size_t i = 0;i < entities.size();i++) { - Ogre::MaterialPtr mat = ent->getSubEntity(i)->getMaterial(); - Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); - while (techIt.hasMoreElements()) + Ogre::Entity *ent = entities[i]; + for (unsigned int i=0; igetNumSubEntities(); ++i) { - Ogre::Technique* tech = techIt.getNext(); - Ogre::Technique::PassIterator passIt = tech->getPassIterator(); - while (passIt.hasMoreElements()) + Ogre::MaterialPtr mat = ent->getSubEntity(i)->getMaterial(); + Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); + while (techIt.hasMoreElements()) { - Ogre::Pass* pass = passIt.getNext(); + Ogre::Technique* tech = techIt.getNext(); + Ogre::Technique::PassIterator passIt = tech->getPassIterator(); + while (passIt.hasMoreElements()) + { + Ogre::Pass* pass = passIt.getNext(); - if (pass->getDepthWriteEnabled() == false) - transparent = true; + if (pass->getDepthWriteEnabled() == false) + transparent = true; + } } } } if(!mIsStatic || !Settings::Manager::getBool("use static geometry", "Objects") || transparent) { - insert->attachObject(ent); + for(size_t i = 0;i < entities.size();i++) + { + Ogre::Entity *ent = entities[i]; + insert->attachObject(ent); - ent->setRenderingDistance(small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0); - ent->setVisibilityFlags(mIsStatic ? (small ? RV_StaticsSmall : RV_Statics) : RV_Misc); - ent->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + ent->setRenderingDistance(small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0); + ent->setVisibilityFlags(mIsStatic ? (small ? RV_StaticsSmall : RV_Statics) : RV_Misc); + ent->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + } } else { @@ -184,15 +191,19 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) // - there will be too many batches. sg->setRegionDimensions(Ogre::Vector3(2500,2500,2500)); - sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); - sg->setVisibilityFlags(small ? RV_StaticsSmall : RV_Statics); sg->setCastShadows(true); sg->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - mRenderer.getScene()->destroyEntity(ent); + for(size_t i = 0;i < entities.size();i++) + { + Ogre::Entity *ent = entities[i]; + sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); + + mRenderer.getScene()->destroyEntity(ent); + } } } From 75ce10c580cdf673586e8122f060b3c7876380d9 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 16 Jul 2012 11:31:45 -0700 Subject: [PATCH 125/298] Don't load data for hidden meshes --- components/nifogre/ogre_nif_loader.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 1db0b92a5..044c9cd72 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -755,6 +755,13 @@ public: Ogre::Mesh *mesh = dynamic_cast(resource); assert(mesh && "Attempting to load a mesh into a non-mesh resource!"); + if(!mShapeName.length()) + { + if(mHasSkel) + mesh->setSkeletonName(mName); + return; + } + Nif::NIFFile nif(mName); Nif::Node *node = dynamic_cast(nif.getRecord(0)); findTriShape(mesh, node); @@ -778,8 +785,11 @@ public: { NIFMeshLoader *loader = &sLoaders[fullname]; *loader = *this; - loader->mShapeName = shape->name; - loader->mMaterialName = NIFMaterialLoader::getMaterial(shape, fullname, mGroup); + if(!(flags&0x01)) // Not hidden + { + loader->mShapeName = shape->name; + loader->mMaterialName = NIFMaterialLoader::getMaterial(shape, fullname, mGroup); + } mesh = meshMgr.createManual(fullname, mGroup, loader); } From 12f17858828b6134a8716fb9971bd97d49aa57ae Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 16 Jul 2012 11:43:16 -0700 Subject: [PATCH 126/298] Use default bone names for duplicate names --- components/nifogre/ogre_nif_loader.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 044c9cd72..9c0e2e10f 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -144,7 +144,11 @@ static void fail(const std::string &msg) void buildBones(Ogre::Skeleton *skel, Nif::NiNode *node, Ogre::Bone *parent=NULL) { - Ogre::Bone *bone = skel->createBone(node->name); + Ogre::Bone *bone; + if(!skel->hasBone(node->name)) + bone = skel->createBone(node->name); + else + bone = skel->createBone(); if(parent) parent->addChild(bone); bone->setOrientation(node->trafo.rotation); From 1c53add6c4d3d349b32ac7bb94d9a396142b2c1f Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Mon, 16 Jul 2012 23:53:33 +0400 Subject: [PATCH 127/298] Include boost/shared_ptr.hpp for boost:shared_ptr --- apps/openmw/mwsound/soundmanager.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index ff360122b..83195390c 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include From 0e934a52ca765c097b822440cd0d0635ca8e0c8b Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Mon, 16 Jul 2012 23:54:04 +0400 Subject: [PATCH 128/298] Include soundmanager.hpp for Play_Normal enum --- apps/openmw/mwsound/sound.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index a33892548..0cba6abca 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -3,6 +3,8 @@ #include +#include "soundmanager.hpp" + namespace MWSound { class Sound From d6bf2b7d294d7298b691677e84ac051c86030397 Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Tue, 17 Jul 2012 00:11:56 +0400 Subject: [PATCH 129/298] Proper way to find and use libtbb --- CMakeLists.txt | 1 + apps/esmtool/CMakeLists.txt | 1 + apps/launcher/CMakeLists.txt | 3 ++- apps/openmw/CMakeLists.txt | 1 + cmake/FindTBB.cmake | 14 ++++++++++++++ 5 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 cmake/FindTBB.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cc741c67..49684fd7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,6 +200,7 @@ find_package(Boost REQUIRED COMPONENTS system filesystem program_options thread) find_package(OIS REQUIRED) find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) +find_package(TBB REQUIRED) IF(OGRE_STATIC) find_package(Cg REQUIRED) IF(WIN32) diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index af3dc090e..eb74aa992 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable(esmtool target_link_libraries(esmtool ${Boost_LIBRARIES} + ${TBB_LIBRARY} components ) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index ed3559fdc..57b7fb71a 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -75,8 +75,9 @@ add_executable(omwlauncher target_link_libraries(omwlauncher ${Boost_LIBRARIES} ${OGRE_LIBRARIES} - ${OGRE_STATIC_PLUGINS} + ${OGRE_STATIC_PLUGINS} ${QT_LIBRARIES} + ${TBB_LIBRARY} components ) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a66cda71e..feefa4fd2 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -100,6 +100,7 @@ target_link_libraries(openmw ${BULLET_LIBRARIES} ${MYGUI_LIBRARIES} ${MYGUI_PLATFORM_LIBRARIES} + ${TBB_LIBRARY} components ) diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake new file mode 100644 index 000000000..f84f81f1c --- /dev/null +++ b/cmake/FindTBB.cmake @@ -0,0 +1,14 @@ +# Locate TBB +# This module defines +# TBB_LIBRARY +# TBB_FOUND, if false, do not try to link to TBB +# TBB_INCLUDE_DIR, where to find the headers + +FIND_PATH(TBB_INCLUDE_DIR tbb/tbb.h) + +FIND_LIBRARY(TBB_LIBRARY NAMES tbb) + +SET(TBB_FOUND "NO") +IF(TBB_LIBRARY AND TBB_INCLUDE_DIR) + SET(TBB_FOUND "YES") +ENDIF(TBB_LIBRARY AND TBB_INCLUDE_DIR) From 63e40d6e92e4b55b7c05984a10e1ae59a5f0e8ed Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 16 Jul 2012 13:16:11 -0700 Subject: [PATCH 130/298] Fix world transform calculation --- components/nif/nif_file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 308196f32..3313d89ab 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -221,6 +221,6 @@ Ogre::Matrix4 Node::getLocalTransform() Ogre::Matrix4 Node::getWorldTransform() { if(parent != NULL) - return getLocalTransform() * parent->getWorldTransform(); + return parent->getWorldTransform() * getLocalTransform(); return getLocalTransform(); } From df76c324a4af9681c39d95b187562fc2220c2024 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 16 Jul 2012 13:34:56 -0700 Subject: [PATCH 131/298] Handle the MRK text string marker --- components/nifogre/ogre_nif_loader.cpp | 28 +++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 9c0e2e10f..7a7b9e572 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -771,15 +771,33 @@ public: findTriShape(mesh, node); } - void createMeshes(Nif::Node *node, MeshPairList &meshes, int flags=0) + void createMeshes(const Nif::Node *node, MeshPairList &meshes, int flags=0) { flags |= node->flags; - // TODO: Check for extra data + Nif::ExtraPtr e = node->extra; + while(!e.empty()) + { + Nif::NiStringExtraData *sd = dynamic_cast(e.getPtr()); + if(sd != NULL) + { + // String markers may contain important information + // affecting the entire subtree of this obj + if(sd->string == "MRK") + { + // Marker objects. These are only visible in the + // editor. + flags |= 0x01; + } + } + else + warn("Unhandled extra data type "+e->recType); + e = e->extra; + } if(node->recType == Nif::RC_NiTriShape) { - NiTriShape *shape = dynamic_cast(node); + const NiTriShape *shape = dynamic_cast(node); Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); std::string fullname = mName+"@"+shape->name; @@ -804,10 +822,10 @@ public: node->recType != Nif::RC_NiRotatingParticles) warn("Unhandled mesh node type: "+node->recName); - Nif::NiNode *ninode = dynamic_cast(node); + const Nif::NiNode *ninode = dynamic_cast(node); if(ninode) { - Nif::NodeList &children = ninode->children; + const Nif::NodeList &children = ninode->children; for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) From bf26f029f92cac2857c886c246b3df11de75d773 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 16 Jul 2012 13:47:19 -0700 Subject: [PATCH 132/298] Fix some skinning-related transformations --- components/nifogre/ogre_nif_loader.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 7a7b9e572..cffab724e 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -546,7 +546,7 @@ class NIFMeshLoader : Ogre::ManualResourceLoader Ogre::Matrix4 mat(Ogre::Matrix4::IDENTITY); mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale), Ogre::Quaternion(data->bones[b].trafo.rotation)); - mat = mat * bones[b]->getWorldTransform(); + mat = bones[b]->getWorldTransform() * mat; const std::vector &weights = data->bones[b].weights; for(size_t i = 0;i < weights.size();i++) @@ -559,9 +559,9 @@ class NIFMeshLoader : Ogre::ManualResourceLoader { for(size_t j = 0;j < 3;j++) { - newNorms[index][j] += mat[0][j]*srcNorms[index][0] * weight; - newNorms[index][j] += mat[1][j]*srcNorms[index][1] * weight; - newNorms[index][j] += mat[2][j]*srcNorms[index][2] * weight; + newNorms[index][j] += mat[j][0]*srcNorms[index][0] * weight; + newNorms[index][j] += mat[j][1]*srcNorms[index][1] * weight; + newNorms[index][j] += mat[j][2]*srcNorms[index][2] * weight; } } } From fefbf86531f84c07766bb4b61f95f6345e8c3c73 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 16 Jul 2012 14:30:23 -0700 Subject: [PATCH 133/298] Use Ogre's matrix ops to transform normals --- components/nifogre/ogre_nif_loader.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index cffab724e..29ead0461 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -557,12 +557,9 @@ class NIFMeshLoader : Ogre::ManualResourceLoader newVerts.at(index) += (mat*srcVerts[index]) * weight; if(newNorms.size() > index) { - for(size_t j = 0;j < 3;j++) - { - newNorms[index][j] += mat[j][0]*srcNorms[index][0] * weight; - newNorms[index][j] += mat[j][1]*srcNorms[index][1] * weight; - newNorms[index][j] += mat[j][2]*srcNorms[index][2] * weight; - } + Ogre::Vector4 vec4(srcNorms[index][0], srcNorms[index][1], srcNorms[index][2], 0.0f); + vec4 = mat*vec4 * weight; + newNorms[index] += Ogre::Vector3(&vec4[0]); } } } From ada88596dc65457a8e4b10e4c1adfc1a45be0b45 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 16 Jul 2012 15:30:09 -0700 Subject: [PATCH 134/298] Fix an abort at shutdown Ogre uses a special method to delete the stream object, so it needs to be allocated properly. --- components/esm/esm_reader.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/esm/esm_reader.cpp b/components/esm/esm_reader.cpp index 6b8409b45..a2cf69ddc 100644 --- a/components/esm/esm_reader.cpp +++ b/components/esm/esm_reader.cpp @@ -107,15 +107,15 @@ void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name) void ESMReader::open(const std::string &file) { - std::ifstream *stream = new std::ifstream(file.c_str(), std::ios_base::binary); - // Ogre will delete the stream for us + std::ifstream *stream = OGRE_NEW_T(std::ifstream, Ogre::MEMCATEGORY_GENERAL)(file.c_str(), std::ios_base::binary); + // Ogre will delete the stream for us open(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), file); } void ESMReader::openRaw(const std::string &file) { - std::ifstream *stream = new std::ifstream(file.c_str(), std::ios_base::binary); - // Ogre will delete the stream for us + std::ifstream *stream = OGRE_NEW_T(std::ifstream, Ogre::MEMCATEGORY_GENERAL)(file.c_str(), std::ios_base::binary); + // Ogre will delete the stream for us openRaw(Ogre::DataStreamPtr(new Ogre::FileStreamDataStream(stream)), file); } From 65c20f128f8c6552da1707c072c23698a40d8364 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 16 Jul 2012 21:18:33 -0700 Subject: [PATCH 135/298] Build bones for non-NiNode nodes (NiTriShapes, etc) --- components/nifogre/ogre_nif_loader.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 29ead0461..c0f3c25d0 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -142,7 +142,7 @@ static void fail(const std::string &msg) } -void buildBones(Ogre::Skeleton *skel, Nif::NiNode *node, Ogre::Bone *parent=NULL) +void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent=NULL) { Ogre::Bone *bone; if(!skel->hasBone(node->name)) @@ -157,12 +157,15 @@ void buildBones(Ogre::Skeleton *skel, Nif::NiNode *node, Ogre::Bone *parent=NULL bone->setBindingPose(); bone->setInitialState(); - const Nif::NodeList &children = node->children; - for(size_t i = 0;i < children.length();i++) + const Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) { - Nif::NiNode *next; - if(!children[i].empty() && (next=dynamic_cast(children[i].getPtr()))) - buildBones(skel, next, bone); + const Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();i++) + { + if(!children[i].empty()) + buildBones(skel, children[i].getPtr(), bone); + } } } @@ -172,7 +175,7 @@ void loadResource(Ogre::Resource *resource) OgreAssert(skel, "Attempting to load a skeleton into a non-skeleton resource!"); Nif::NIFFile nif(skel->getName()); - Nif::NiNode *node = dynamic_cast(nif.getRecord(0)); + const Nif::Node *node = dynamic_cast(nif.getRecord(0)); buildBones(skel, node); } From 0549e949ba6cab8fb06919ebcc0ee4dee558ef59 Mon Sep 17 00:00:00 2001 From: guidoj Date: Tue, 17 Jul 2012 09:27:12 +0200 Subject: [PATCH 136/298] Mostly removal of unnecessary #include's and a little clean up --- .gitignore | 1 + apps/openmw/engine.cpp | 19 ----------------- apps/openmw/engine.hpp | 4 ---- apps/openmw/main.cpp | 15 -------------- apps/openmw/mwbase/world.hpp | 2 -- apps/openmw/mwclass/creature.hpp | 1 - apps/openmw/mwgui/class.cpp | 1 - apps/openmw/mwgui/dialogue.cpp | 1 - apps/openmw/mwgui/dialogue_history.cpp | 1 - apps/openmw/mwgui/race.cpp | 1 - apps/openmw/mwgui/window_manager.hpp | 4 ---- apps/openmw/mwrender/actors.hpp | 10 --------- apps/openmw/mwrender/animation.hpp | 6 +----- apps/openmw/mwrender/creatureanimation.hpp | 4 ---- apps/openmw/mwrender/npcanimation.hpp | 10 +-------- apps/openmw/mwrender/renderingmanager.hpp | 12 ----------- apps/openmw/mwscript/scriptmanager.hpp | 1 - apps/openmw/mwworld/cells.cpp | 4 ---- apps/openmw/mwworld/cellstore.hpp | 3 --- apps/openmw/mwworld/containerstore.hpp | 2 -- apps/openmw/mwworld/physicssystem.hpp | 2 -- apps/openmw/mwworld/ptr.hpp | 4 ---- apps/openmw/mwworld/refdata.hpp | 2 -- apps/openmw/mwworld/scene.cpp | 2 -- apps/openmw/mwworld/scene.hpp | 8 -------- apps/openmw/mwworld/worldimp.cpp | 9 -------- apps/openmw/mwworld/worldimp.hpp | 15 -------------- components/compiler/streamerrorhandler.cpp | 3 ++- components/esm/esm_reader.hpp | 6 +----- components/files/collections.hpp | 2 -- components/files/configurationmanager.hpp | 1 - components/misc/tests/slice_test.cpp | 2 -- components/nif/nif_file.hpp | 3 +-- components/nifbullet/bullet_nif_loader.hpp | 8 +------- components/nifogre/ogre_nif_loader.hpp | 24 +++++++--------------- components/nifogre/tests/ogre_common.cpp | 1 - libs/openengine/gui/layout.hpp | 1 - libs/openengine/ogre/fader.cpp | 2 -- 38 files changed, 15 insertions(+), 182 deletions(-) diff --git a/.gitignore b/.gitignore index b3bb8d82d..26ba80e1a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ cmake_install.cmake Makefile makefile data +*.kdev4 diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 45b4ab514..7966639a6 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,25 +1,13 @@ #include "engine.hpp" #include "components/esm/loadcell.hpp" -#include - -#include -#include - #include #include #include -#include -#include - -#include #include -#include -#include #include -#include #include #include @@ -31,16 +19,12 @@ #include "mwgui/cursorreplace.hpp" #include "mwscript/scriptmanager.hpp" -#include "mwscript/compilercontext.hpp" -#include "mwscript/interpretercontext.hpp" #include "mwscript/extensions.hpp" -#include "mwscript/globalscripts.hpp" #include "mwsound/soundmanager.hpp" #include "mwworld/class.hpp" #include "mwworld/player.hpp" -#include "mwworld/cellstore.hpp" #include "mwworld/worldimp.hpp" #include "mwclass/classes.hpp" @@ -50,9 +34,6 @@ #include "mwmechanics/mechanicsmanager.hpp" -#include "mwbase/environment.hpp" -#include "mwbase/world.hpp" - void OMW::Engine::executeLocalScripts() { diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index cf1ef3b9c..031cae551 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -1,10 +1,6 @@ #ifndef ENGINE_H #define ENGINE_H -#include - -#include - #include #include diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 68aa12fb3..fc905b79d 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -1,13 +1,5 @@ #include -#include -#include - -#include - -#include -#include -#include #include #include "engine.hpp" @@ -16,11 +8,6 @@ #include #include -# if !defined(_DEBUG) -# include -# include -# endif - // For OutputDebugString #include // makes __argc and __argv available on windows @@ -52,8 +39,6 @@ inline boost::filesystem::path lexical_cast mMap; }; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 51d0a4f8e..6937cbf3b 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -1,8 +1,6 @@ #ifndef GAME_MWBASE_WORLD_H #define GAME_MWBASE_WORLD_H -#include -#include #include #include diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 237f54e82..1274be09a 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -1,7 +1,6 @@ #ifndef GAME_MWCLASS_CREATURE_H #define GAME_MWCLASS_CREATURE_H -#include "../mwworld/class.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/actors.hpp" diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index d0f21f945..9f291bae7 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -1,6 +1,5 @@ #include "class.hpp" -#include #include #include diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index acf0bf130..018fbb178 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -1,6 +1,5 @@ #include "dialogue.hpp" -#include #include #include diff --git a/apps/openmw/mwgui/dialogue_history.cpp b/apps/openmw/mwgui/dialogue_history.cpp index cd34ee119..009f42044 100644 --- a/apps/openmw/mwgui/dialogue_history.cpp +++ b/apps/openmw/mwgui/dialogue_history.cpp @@ -3,7 +3,6 @@ #include "widgets.hpp" #include "components/esm_store/store.hpp" -#include #include #include diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 9ae453016..9f4a4965d 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -1,6 +1,5 @@ #include "race.hpp" -#include #include #include diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index 03ffa6b59..9d1516ccf 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -10,10 +10,6 @@ this class. **/ -#include -#include -#include - #include "MyGUI_UString.h" #include diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp index 63cd3baa1..4b0b2e572 100644 --- a/apps/openmw/mwrender/actors.hpp +++ b/apps/openmw/mwrender/actors.hpp @@ -1,18 +1,8 @@ #ifndef _GAME_RENDER_ACTORS_H #define _GAME_RENDER_ACTORS_H -#include -#include - -#include -#include "components/nifogre/ogre_nif_loader.hpp" - -#include "../mwworld/refdata.hpp" -#include "../mwworld/actiontalk.hpp" - #include "npcanimation.hpp" #include "creatureanimation.hpp" -#include namespace MWWorld { diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 4ab60cff4..28ef9d706 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -1,17 +1,13 @@ #ifndef _GAME_RENDER_ANIMATION_H #define _GAME_RENDER_ANIMATION_H -#include + #include -#include "../mwworld/refdata.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" #include -#include #include - namespace MWRender{ struct PosAndRot{ diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index f50b7904b..4fea1a9d9 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -2,11 +2,7 @@ #define _GAME_RENDER_CREATUREANIMATION_H #include "animation.hpp" -#include - -#include "../mwworld/refdata.hpp" -#include "../mwworld/ptr.hpp" #include "components/nifogre/ogre_nif_loader.hpp" diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 8f4f8181d..4233d2803 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -1,20 +1,12 @@ #ifndef _GAME_RENDER_NPCANIMATION_H #define _GAME_RENDER_NPCANIMATION_H + #include "animation.hpp" -#include -#include -#include -#include -#include -#include -#include "../mwworld/refdata.hpp" -#include "../mwworld/ptr.hpp" #include "components/nifogre/ogre_nif_loader.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwclass/npc.hpp" #include "../mwworld/containerstore.hpp" -#include "components/esm/loadarmo.hpp" namespace MWRender{ diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 9aaba3803..c30f52979 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -1,25 +1,14 @@ #ifndef _GAME_RENDERING_MANAGER_H #define _GAME_RENDERING_MANAGER_H - #include "sky.hpp" #include "terrain.hpp" #include "debugging.hpp" -#include "../mwworld/class.hpp" - -#include - -#include -#include #include -#include #include -#include -#include - #include #include "renderinginterface.hpp" @@ -45,7 +34,6 @@ namespace MWWorld namespace MWRender { - class Shadows; class ShaderHelper; class LocalMap; diff --git a/apps/openmw/mwscript/scriptmanager.hpp b/apps/openmw/mwscript/scriptmanager.hpp index 34cc0defe..a466f903d 100644 --- a/apps/openmw/mwscript/scriptmanager.hpp +++ b/apps/openmw/mwscript/scriptmanager.hpp @@ -2,7 +2,6 @@ #define GAME_SCRIPT_SCRIPTMANAGER_H #include -#include #include #include diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index cd7ebf79a..cffaf70ea 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -1,9 +1,5 @@ #include "cells.hpp" -#include - -#include - #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 8253f3f0b..de3ac12ae 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -4,9 +4,6 @@ #include #include -#include -#include -#include #include #include "refdata.hpp" diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index f71b493bd..ae27fad3d 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -3,8 +3,6 @@ #include -#include "cellstore.hpp" -#include "refdata.hpp" #include "ptr.hpp" namespace ESM diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index b46ce117b..a6b679833 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -1,9 +1,7 @@ #ifndef GAME_MWWORLD_PHYSICSSYSTEM_H #define GAME_MWWORLD_PHYSICSSYSTEM_H -#include #include -#include #include "ptr.hpp" #include diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index b7469b8f5..f74fdd3ef 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -1,12 +1,8 @@ #ifndef GAME_MWWORLD_PTR_H #define GAME_MWWORLD_PTR_H -#include - #include -#include - #include "cellstore.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 41521e47a..3a6e0fc9f 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -1,8 +1,6 @@ #ifndef GAME_MWWORLD_REFDATA_H #define GAME_MWWORLD_REFDATA_H -#include - #include #include "../mwscript/locals.hpp" diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 39496def3..33c67aad8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -11,9 +11,7 @@ #include "../mwworld/manualref.hpp" /// FIXME -#include "ptr.hpp" #include "player.hpp" -#include "class.hpp" #include "localscripts.hpp" #include "cellfunctors.hpp" diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 64a8c9f8e..c0b93796a 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -1,15 +1,7 @@ #ifndef GAME_MWWORLD_SCENE_H #define GAME_MWWORLD_SCENE_H -#include -#include - -#include - -#include - #include "../mwrender/renderingmanager.hpp" -#include "../mwrender/renderinginterface.hpp" #include "physicssystem.hpp" #include "globals.hpp" diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 24baac144..a68f08e34 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,8 +1,5 @@ #include "worldimp.hpp" -#include -#include - #include #include @@ -17,16 +14,10 @@ #include "../mwgui/window_manager.hpp" -#include "ptr.hpp" -#include "class.hpp" #include "player.hpp" -#include "weather.hpp" #include "manualref.hpp" -#include "refdata.hpp" -#include "globals.hpp" #include "cellfunctors.hpp" -#include using namespace Ogre; namespace diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 8b39a78f1..43b178fe3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -1,32 +1,17 @@ #ifndef GAME_MWWORLD_WORLDIMP_H #define GAME_MWWORLD_WORLDIMP_H -#include -#include - -#include - #include -#include - #include "../mwrender/debugging.hpp" -#include "../mwrender/renderingmanager.hpp" -#include "refdata.hpp" #include "ptr.hpp" -#include "globals.hpp" #include "scene.hpp" #include "physicssystem.hpp" #include "cells.hpp" #include "localscripts.hpp" #include "timestamp.hpp" -#include -#include - -#include - #include "../mwbase/world.hpp" namespace Ogre diff --git a/components/compiler/streamerrorhandler.cpp b/components/compiler/streamerrorhandler.cpp index a1d12b708..8a74ad086 100644 --- a/components/compiler/streamerrorhandler.cpp +++ b/components/compiler/streamerrorhandler.cpp @@ -35,4 +35,5 @@ namespace Compiler << " " << message << std::endl; } - StreamErrorHandler::StreamErrorHandler (std::ostream& ErrorStream) : mStream (ErrorStream) {}} + StreamErrorHandler::StreamErrorHandler (std::ostream& ErrorStream) : mStream (ErrorStream) {} +} diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index 17cca7a91..13f1f4a01 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -1,15 +1,11 @@ #ifndef _ESM_READER_H #define _ESM_READER_H -#include - -#include #include #include -#include +#include #include #include -#include #include diff --git a/components/files/collections.hpp b/components/files/collections.hpp index 70aaec55e..ed4aafa13 100644 --- a/components/files/collections.hpp +++ b/components/files/collections.hpp @@ -1,8 +1,6 @@ #ifndef COMPONENTS_FILES_COLLECTION_HPP #define COMPONENTS_FILES_COLLECTION_HPP -#include -#include #include #include "multidircollection.hpp" diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index af9d02b91..0c22c6f7d 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -8,7 +8,6 @@ #endif #include -#include #include #include diff --git a/components/misc/tests/slice_test.cpp b/components/misc/tests/slice_test.cpp index a0ea55311..0d9d7b4ab 100644 --- a/components/misc/tests/slice_test.cpp +++ b/components/misc/tests/slice_test.cpp @@ -2,8 +2,6 @@ using namespace std; -#include - #include "../slice_array.hpp" int main() diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 42e312b7f..4072b4307 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -32,8 +32,7 @@ #include #include -#include -#include +#include #include "record.hpp" #include "nif_types.hpp" diff --git a/components/nifbullet/bullet_nif_loader.hpp b/components/nifbullet/bullet_nif_loader.hpp index b2f023301..88e1ab189 100644 --- a/components/nifbullet/bullet_nif_loader.hpp +++ b/components/nifbullet/bullet_nif_loader.hpp @@ -25,22 +25,16 @@ #define _BULLET_NIF_LOADER_H_ #include -#include +#include #include #include #include #include -#include #include "openengine/bullet/BulletShapeLoader.h" -#include -#include // For warning messages #include -// float infinity -#include - namespace Nif { class Node; diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 0620ddf49..7b32d24d1 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -28,31 +28,20 @@ #include #include -#include #include -#include "../nif/nif_file.hpp" #include "../nif/node.hpp" -#include "../nif/data.hpp" -#include "../nif/property.hpp" -#include "../nif/controller.hpp" -#include "../nif/extra.hpp" -#include - -#include -#include -// For warning messages -#include -using namespace boost::algorithm; +#include class BoundsFinder; struct ciLessBoost : std::binary_function { - bool operator() (const std::string & s1, const std::string & s2) const { - //case insensitive version of is_less - return lexicographical_compare(s1, s2, is_iless()); + bool operator() (const std::string & s1, const std::string & s2) const + { + //case insensitive version of is_less + return boost::algorithm::lexicographical_compare(s1, s2, boost::algorithm::is_iless()); } }; @@ -63,7 +52,6 @@ namespace Nif class NiTriShape; } - namespace NifOgre { @@ -177,3 +165,5 @@ class NIFLoader : Ogre::ManualResourceLoader } #endif + + diff --git a/components/nifogre/tests/ogre_common.cpp b/components/nifogre/tests/ogre_common.cpp index 949c91c4f..657913f30 100644 --- a/components/nifogre/tests/ogre_common.cpp +++ b/components/nifogre/tests/ogre_common.cpp @@ -1,6 +1,5 @@ #include #include -#include using namespace std; using namespace Ogre; diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp index e6feb3d0e..9040dfb90 100644 --- a/libs/openengine/gui/layout.hpp +++ b/libs/openengine/gui/layout.hpp @@ -1,7 +1,6 @@ #ifndef OENGINE_MYGUI_LAYOUT_H #define OENGINE_MYGUI_LAYOUT_H -#include #include namespace OEngine { diff --git a/libs/openengine/ogre/fader.cpp b/libs/openengine/ogre/fader.cpp index 062559e00..41b7773ea 100644 --- a/libs/openengine/ogre/fader.cpp +++ b/libs/openengine/ogre/fader.cpp @@ -8,8 +8,6 @@ #include #include -#include - #define FADE_OVERLAY_NAME "FadeInOutOverlay" #define FADE_OVERLAY_PANEL_NAME "FadeInOutOverlayPanel" #define FADE_MATERIAL_NAME "FadeInOutMaterial" From a021165d9f2cc002a991f4f5abac014d06e229be Mon Sep 17 00:00:00 2001 From: guidoj Date: Tue, 17 Jul 2012 09:44:24 +0200 Subject: [PATCH 137/298] Changed standard C lib includes to C++ format --- apps/openmw/main.cpp | 2 +- components/bsa/bsa_file.cpp | 4 ++-- components/bsa/tests/bsatool_cmd.c | 4 ++-- components/bsa/tests/bsatool_cmd.h | 2 +- components/esm_store/reclists.hpp | 2 +- components/misc/tests/strops_test.cpp | 2 +- components/nifbullet/bullet_nif_loader.cpp | 2 +- components/terrain/heightmapbuf.hpp | 2 +- components/terrain/triangulator.hpp | 2 +- components/to_utf8/gen_iconv.cpp | 2 +- components/to_utf8/to_utf8.cpp | 2 +- libs/mangle/input/servers/ois_driver.cpp | 2 +- libs/mangle/rend2d/servers/sdl_driver.cpp | 2 +- libs/mangle/rend2d/servers/sdl_gl_driver.cpp | 2 +- libs/openengine/gui/events.cpp | 2 +- libs/openengine/gui/manager.cpp | 2 +- libs/openengine/input/dispatch_map.hpp | 2 +- libs/openengine/input/func_binder.hpp | 2 +- libs/openengine/misc/list.hpp | 2 +- libs/openengine/ogre/renderer.cpp | 2 +- 20 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index fc905b79d..993ec6623 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -11,7 +11,7 @@ // For OutputDebugString #include // makes __argc and __argv available on windows -#include +#include #endif diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 8f605b8ed..053191c9b 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -24,8 +24,8 @@ #include "bsa_file.hpp" #include -#include -#include +#include +#include #include diff --git a/components/bsa/tests/bsatool_cmd.c b/components/bsa/tests/bsatool_cmd.c index f6e10d793..caa8cd720 100644 --- a/components/bsa/tests/bsatool_cmd.c +++ b/components/bsa/tests/bsatool_cmd.c @@ -13,8 +13,8 @@ #include "config.h" #endif -#include -#include +#include +#include #include #ifndef FIX_UNUSED diff --git a/components/bsa/tests/bsatool_cmd.h b/components/bsa/tests/bsatool_cmd.h index 65ebc3e96..98fe2633f 100644 --- a/components/bsa/tests/bsatool_cmd.h +++ b/components/bsa/tests/bsatool_cmd.h @@ -13,7 +13,7 @@ #include "config.h" #endif -#include /* for FILE */ +#include /* for FILE */ #ifdef __cplusplus extern "C" { diff --git a/components/esm_store/reclists.hpp b/components/esm_store/reclists.hpp index 48bf050cd..ffecfc8de 100644 --- a/components/esm_store/reclists.hpp +++ b/components/esm_store/reclists.hpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/components/misc/tests/strops_test.cpp b/components/misc/tests/strops_test.cpp index 2a1fdd77d..24ab8a298 100644 --- a/components/misc/tests/strops_test.cpp +++ b/components/misc/tests/strops_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include "../stringops.hpp" diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index 4105c4c79..ea94e7758 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -23,7 +23,7 @@ http://www.gnu.org/licenses/ . #include "bullet_nif_loader.hpp" #include -#include +#include #include "../nif/nif_file.hpp" #include "../nif/node.hpp" diff --git a/components/terrain/heightmapbuf.hpp b/components/terrain/heightmapbuf.hpp index 82154ce88..d147e6015 100644 --- a/components/terrain/heightmapbuf.hpp +++ b/components/terrain/heightmapbuf.hpp @@ -8,7 +8,7 @@ #include "heightmap.hpp" #include "land_factory.hpp" #include -#include +#include namespace Terrain { diff --git a/components/terrain/triangulator.hpp b/components/terrain/triangulator.hpp index cedf0c6a2..c5c0e699b 100644 --- a/components/terrain/triangulator.hpp +++ b/components/terrain/triangulator.hpp @@ -28,7 +28,7 @@ terrains of the same size, once instance can usually be shared. */ -#include +#include namespace Terrain { diff --git a/components/to_utf8/gen_iconv.cpp b/components/to_utf8/gen_iconv.cpp index b7298e304..cc7cc191a 100644 --- a/components/to_utf8/gen_iconv.cpp +++ b/components/to_utf8/gen_iconv.cpp @@ -5,7 +5,7 @@ using namespace std; #include -#include +#include void tab() { cout << " "; } diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 3fbbeb733..6bcbbd0e6 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -1,7 +1,7 @@ #include "to_utf8.hpp" #include -#include +#include /* This file contains the code to translate from WINDOWS-1252 (native charset used in English version of Morrowind) to UTF-8. The library diff --git a/libs/mangle/input/servers/ois_driver.cpp b/libs/mangle/input/servers/ois_driver.cpp index b8e4f5eb9..07ba3e83a 100644 --- a/libs/mangle/input/servers/ois_driver.cpp +++ b/libs/mangle/input/servers/ois_driver.cpp @@ -1,6 +1,6 @@ #include "ois_driver.hpp" -#include +#include #include #include #include diff --git a/libs/mangle/rend2d/servers/sdl_driver.cpp b/libs/mangle/rend2d/servers/sdl_driver.cpp index aa1ff6c6d..84a17933f 100644 --- a/libs/mangle/rend2d/servers/sdl_driver.cpp +++ b/libs/mangle/rend2d/servers/sdl_driver.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include using namespace Mangle::Rend2D; diff --git a/libs/mangle/rend2d/servers/sdl_gl_driver.cpp b/libs/mangle/rend2d/servers/sdl_gl_driver.cpp index 2bcb1d677..db519e091 100644 --- a/libs/mangle/rend2d/servers/sdl_gl_driver.cpp +++ b/libs/mangle/rend2d/servers/sdl_gl_driver.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include using namespace Mangle::Rend2D; diff --git a/libs/openengine/gui/events.cpp b/libs/openengine/gui/events.cpp index bce70704b..35b01158b 100644 --- a/libs/openengine/gui/events.cpp +++ b/libs/openengine/gui/events.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include "events.hpp" diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp index 022c5efb5..9c6ca37eb 100644 --- a/libs/openengine/gui/manager.cpp +++ b/libs/openengine/gui/manager.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include "manager.hpp" diff --git a/libs/openengine/input/dispatch_map.hpp b/libs/openengine/input/dispatch_map.hpp index f0d4cabe9..be13e7f01 100644 --- a/libs/openengine/input/dispatch_map.hpp +++ b/libs/openengine/input/dispatch_map.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace OEngine { namespace Input { diff --git a/libs/openengine/input/func_binder.hpp b/libs/openengine/input/func_binder.hpp index 7aa733edf..a815ba0ce 100644 --- a/libs/openengine/input/func_binder.hpp +++ b/libs/openengine/input/func_binder.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include namespace OEngine { namespace Input { diff --git a/libs/openengine/misc/list.hpp b/libs/openengine/misc/list.hpp index b08b57494..bda9cb8de 100644 --- a/libs/openengine/misc/list.hpp +++ b/libs/openengine/misc/list.hpp @@ -1,7 +1,7 @@ #ifndef MISC_LIST_H #define MISC_LIST_H -#include +#include namespace Misc{ diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 275b7385a..f2f4b4c81 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -9,7 +9,7 @@ #include "OgreTexture.h" #include "OgreHardwarePixelBuffer.h" -#include +#include #include using namespace Ogre; From e9b95d55cdc0d78c861869c92f31846c047cdc0b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 17 Jul 2012 09:49:52 +0200 Subject: [PATCH 138/298] Revert "Proper way to find and use libtbb" This reverts commit d6bf2b7d294d7298b691677e84ac051c86030397. --- CMakeLists.txt | 1 - apps/esmtool/CMakeLists.txt | 1 - apps/launcher/CMakeLists.txt | 3 +-- apps/openmw/CMakeLists.txt | 1 - cmake/FindTBB.cmake | 14 -------------- 5 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 cmake/FindTBB.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 49684fd7c..9cc741c67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,7 +200,6 @@ find_package(Boost REQUIRED COMPONENTS system filesystem program_options thread) find_package(OIS REQUIRED) find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) -find_package(TBB REQUIRED) IF(OGRE_STATIC) find_package(Cg REQUIRED) IF(WIN32) diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index eb74aa992..af3dc090e 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -10,7 +10,6 @@ add_executable(esmtool target_link_libraries(esmtool ${Boost_LIBRARIES} - ${TBB_LIBRARY} components ) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 57b7fb71a..ed3559fdc 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -75,9 +75,8 @@ add_executable(omwlauncher target_link_libraries(omwlauncher ${Boost_LIBRARIES} ${OGRE_LIBRARIES} - ${OGRE_STATIC_PLUGINS} + ${OGRE_STATIC_PLUGINS} ${QT_LIBRARIES} - ${TBB_LIBRARY} components ) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index feefa4fd2..a66cda71e 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -100,7 +100,6 @@ target_link_libraries(openmw ${BULLET_LIBRARIES} ${MYGUI_LIBRARIES} ${MYGUI_PLATFORM_LIBRARIES} - ${TBB_LIBRARY} components ) diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake deleted file mode 100644 index f84f81f1c..000000000 --- a/cmake/FindTBB.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# Locate TBB -# This module defines -# TBB_LIBRARY -# TBB_FOUND, if false, do not try to link to TBB -# TBB_INCLUDE_DIR, where to find the headers - -FIND_PATH(TBB_INCLUDE_DIR tbb/tbb.h) - -FIND_LIBRARY(TBB_LIBRARY NAMES tbb) - -SET(TBB_FOUND "NO") -IF(TBB_LIBRARY AND TBB_INCLUDE_DIR) - SET(TBB_FOUND "YES") -ENDIF(TBB_LIBRARY AND TBB_INCLUDE_DIR) From a3652f16ce4fba2cb0bfe304717f5c380e6cb6b2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 17 Jul 2012 12:18:43 +0200 Subject: [PATCH 139/298] Issue #342: factored out dynamic stats calculation into a separate function --- apps/openmw/mwmechanics/actors.cpp | 35 ++++++++++++++++-------------- apps/openmw/mwmechanics/actors.hpp | 2 ++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 95747dbbf..60c96e5b9 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -18,22 +18,7 @@ namespace MWMechanics { // magic effects adjustMagicEffects (ptr); - - CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - - // calculate dynamic stats - int strength = creatureStats.mAttributes[0].getBase(); - int intelligence = creatureStats.mAttributes[1].getBase(); - int willpower = creatureStats.mAttributes[2].getBase(); - int agility = creatureStats.mAttributes[3].getBase(); - int endurance = creatureStats.mAttributes[5].getBase(); - - double magickaFactor = creatureStats.mMagicEffects.get (EffectKey (84)).mMagnitude*0.1 + 0.5; - - creatureStats.mDynamic[0].setBase (static_cast (0.5 * (strength + endurance))); - creatureStats.mDynamic[1].setBase (static_cast (intelligence + - magickaFactor * intelligence)); - creatureStats.mDynamic[2].setBase (strength+willpower+agility+endurance); + calculateDynamicStats (ptr); } void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) @@ -64,6 +49,24 @@ namespace MWMechanics // TODO apply diff to other stats } + void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr) + { + CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + + int strength = creatureStats.mAttributes[0].getBase(); + int intelligence = creatureStats.mAttributes[1].getBase(); + int willpower = creatureStats.mAttributes[2].getBase(); + int agility = creatureStats.mAttributes[3].getBase(); + int endurance = creatureStats.mAttributes[5].getBase(); + + double magickaFactor = creatureStats.mMagicEffects.get (EffectKey (84)).mMagnitude*0.1 + 0.5; + + creatureStats.mDynamic[0].setBase (static_cast (0.5 * (strength + endurance))); + creatureStats.mDynamic[1].setBase (static_cast (intelligence + + magickaFactor * intelligence)); + creatureStats.mDynamic[2].setBase (strength+willpower+agility+endurance); + } + Actors::Actors() : mDuration (0) {} void Actors::addActor (const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 82f8a943c..7458562e1 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -27,6 +27,8 @@ namespace MWMechanics void adjustMagicEffects (const MWWorld::Ptr& creature); + void calculateDynamicStats (const MWWorld::Ptr& ptr); + public: Actors(); From 76de2f73605cf8cf686b95d7cf1536342209766e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 17 Jul 2012 15:49:37 +0200 Subject: [PATCH 140/298] Issue #342: handle magic effects 79-82 and 17-20 --- apps/openmw/mwmechanics/actors.cpp | 24 ++++++++++++++++++++++++ apps/openmw/mwmechanics/actors.hpp | 2 ++ apps/openmw/mwmechanics/stat.hpp | 9 ++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 60c96e5b9..d4dc5ea00 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -19,6 +19,7 @@ namespace MWMechanics // magic effects adjustMagicEffects (ptr); calculateDynamicStats (ptr); + calculateCreatureStatModifiers (ptr); } void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) @@ -67,6 +68,29 @@ namespace MWMechanics creatureStats.mDynamic[2].setBase (strength+willpower+agility+endurance); } + void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr) + { + CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + + // attributes + for (int i=0; i<5; ++i) + { + int modifier = creatureStats.mMagicEffects.get (EffectKey (79, i)).mMagnitude + - creatureStats.mMagicEffects.get (EffectKey (17, i)).mMagnitude; + + creatureStats.mAttributes[0].setModifier (modifier); + } + + // dynamic stats + for (int i=0; i<3; ++i) + { + int modifier = creatureStats.mMagicEffects.get (EffectKey (80+i)).mMagnitude + - creatureStats.mMagicEffects.get (EffectKey (18+i)).mMagnitude; + + creatureStats.mDynamic[0].setModifier (modifier); + } + } + Actors::Actors() : mDuration (0) {} void Actors::addActor (const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 7458562e1..d5dcef487 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -29,6 +29,8 @@ namespace MWMechanics void calculateDynamicStats (const MWWorld::Ptr& ptr); + void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr); + public: Actors(); diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 996036fc1..d576020c5 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -147,7 +147,7 @@ namespace MWMechanics void modify (const T& diff) { mStatic.modify (diff); - modifyCurrent (diff); + setCurrent (getCurrent()+diff); } void setCurrent (const T& value) @@ -159,6 +159,13 @@ namespace MWMechanics else if (mCurrent>getModified()) mCurrent = getModified(); } + + void setModifier (const T& modifier) + { + T diff = modifier - mStatic.getModifier(); + mStatic.setModifier (modifier); + setCurrent (getCurrent()+diff); + } }; template From beb18282bb76f34be8d5113c591dc582e7e7b704 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 17 Jul 2012 16:44:55 +0200 Subject: [PATCH 141/298] Issue #342: various fixes --- apps/openmw/mwmechanics/actors.cpp | 6 +++--- apps/openmw/mwworld/actionapply.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d4dc5ea00..72f1be7a8 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -18,8 +18,8 @@ namespace MWMechanics { // magic effects adjustMagicEffects (ptr); - calculateDynamicStats (ptr); calculateCreatureStatModifiers (ptr); + calculateDynamicStats (ptr); } void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) @@ -78,7 +78,7 @@ namespace MWMechanics int modifier = creatureStats.mMagicEffects.get (EffectKey (79, i)).mMagnitude - creatureStats.mMagicEffects.get (EffectKey (17, i)).mMagnitude; - creatureStats.mAttributes[0].setModifier (modifier); + creatureStats.mAttributes[i].setModifier (modifier); } // dynamic stats @@ -87,7 +87,7 @@ namespace MWMechanics int modifier = creatureStats.mMagicEffects.get (EffectKey (80+i)).mMagnitude - creatureStats.mMagicEffects.get (EffectKey (18+i)).mMagnitude; - creatureStats.mDynamic[0].setModifier (modifier); + creatureStats.mDynamic[i].setModifier (modifier); } } diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index c5228d798..b330a70e7 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -22,7 +22,7 @@ namespace MWWorld void ActionApplyWithSkill::execute() { - if (MWWorld::Class::get (mTarget).apply (mTarget, mId, mActor)) + if (MWWorld::Class::get (mTarget).apply (mTarget, mId, mActor) && mUsageType!=-1) MWWorld::Class::get (mTarget).skillUsageSucceeded (mActor, mSkillIndex, mUsageType); } } From 67443756363ef853f6ebf812f06037cd56ad60cf Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 17 Jul 2012 18:35:01 +0200 Subject: [PATCH 142/298] fixed a logic error in the auto equip code --- apps/openmw/mwworld/inventorystore.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 2fd702f6d..9e4381f07 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -148,7 +148,7 @@ void MWWorld::InventoryStore::autoEquip (const MWMechanics::NpcStats& stats) bool use = false; if (slots.at (*iter2)==end()) - use = true; // slot was empty before -> skill all further checks + use = true; // slot was empty before -> skip all further checks else { Ptr old = *slots.at (*iter2); @@ -159,7 +159,9 @@ void MWWorld::InventoryStore::autoEquip (const MWMechanics::NpcStats& stats) int oldSkill = MWWorld::Class::get (old).getEquipmentSkill (old); - if (testSkill!=-1 || oldSkill!=-1 || testSkill!=oldSkill) + if (testSkill!=-1 && oldSkill==-1) + use = true; + else if (testSkill!=-1 && oldSkill!=-1 && testSkill!=oldSkill) { if (stats.getSkill (oldSkill).getModified()>stats.getSkill (testSkill).getModified()) continue; // rejected, because old item better matched the NPC's skills. From 483b125aad586b3165537be7a6c55470503fc47c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 17 Jul 2012 18:37:20 +0200 Subject: [PATCH 143/298] avoid locking up in case actor updates repeatedly throw exceptions --- apps/openmw/mwmechanics/actors.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 72f1be7a8..ff3e91da8 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -126,15 +126,16 @@ namespace MWMechanics if (mDuration>=0.25) { + float totalDuration = mDuration; + mDuration = 0; + for (std::set::iterator iter (mActors.begin()); iter!=mActors.end(); ++iter) { - updateActor (*iter, mDuration); + updateActor (*iter, totalDuration); if (iter->getTypeName()==typeid (ESM::NPC).name()) - updateNpc (*iter, mDuration, paused); + updateNpc (*iter, totalDuration, paused); } - - mDuration = 0; } for (std::set::iterator iter (mActors.begin()); iter!=mActors.end(); From 93f0043afc098b7384b1b02e389c478052bc47bc Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 10:04:21 -0700 Subject: [PATCH 144/298] Set the mesh's attach point as the NiTriShape's node --- components/nifogre/ogre_nif_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index c0f3c25d0..fef7054f7 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -816,7 +816,7 @@ public: mesh = meshMgr.createManual(fullname, mGroup, loader); } - meshes.push_back(std::make_pair(mesh, (shape->parent ? shape->parent->name : std::string()))); + meshes.push_back(std::make_pair(mesh, shape->name)); } else if(node->recType != Nif::RC_NiNode && node->recType != Nif::RC_RootCollisionNode && node->recType != Nif::RC_NiRotatingParticles) From 0a4a141f2e94925f24f60b09e85e4a365ef336d5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 11:23:34 -0700 Subject: [PATCH 145/298] Support multiple meshes for creatures --- apps/openmw/mwrender/animation.cpp | 1 - apps/openmw/mwrender/animation.hpp | 4 ++- apps/openmw/mwrender/creatureanimation.cpp | 41 ++++++++++++---------- apps/openmw/mwrender/npcanimation.hpp | 1 + 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 08176077c..17c3f62ac 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -23,7 +23,6 @@ Animation::Animation(OEngine::Render::OgreRenderer& _rend) , mShapeIndexI() , mTransformations(NULL) , mTextmappings(NULL) - , mBase(NULL) { } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 0e96b4d4d..ea18865da 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -1,6 +1,8 @@ #ifndef _GAME_RENDER_ANIMATION_H #define _GAME_RENDER_ANIMATION_H +#include + #include #include "../mwworld/actiontalk.hpp" #include @@ -37,7 +39,7 @@ protected: std::vector* mTransformations; std::map* mTextmappings; - Ogre::Entity* mBase; + std::vector mBase; void handleAnimationTransforms(); bool timeIndex( float time, const std::vector & times, int & i, int & j, float & x ); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 99800f06c..b42feec68 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -26,32 +26,35 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O { std::string mesh = "meshes\\" + ref->base->model; - // FIXME: There can be more than one! NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load(mesh); - mBase = mRend.getScene()->createEntity(meshes[0].first->getName()); - mBase->setVisibilityFlags(RV_Actors); - - bool transparent = false; - for (unsigned int i=0; i < mBase->getNumSubEntities(); ++i) + for(size_t i = 0;i < meshes.size();i++) { - Ogre::MaterialPtr mat = mBase->getSubEntity(i)->getMaterial(); - Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); - while (techIt.hasMoreElements()) + mBase.push_back(mRend.getScene()->createEntity(meshes[i].first->getName())); + Ogre::Entity *base = mBase.back(); + base->setVisibilityFlags(RV_Actors); + + bool transparent = false; + for (unsigned int j=0;j < base->getNumSubEntities() && !transparent; ++j) { - Ogre::Technique* tech = techIt.getNext(); - Ogre::Technique::PassIterator passIt = tech->getPassIterator(); - while (passIt.hasMoreElements()) + Ogre::MaterialPtr mat = base->getSubEntity(j)->getMaterial(); + Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); + while (techIt.hasMoreElements() && !transparent) { - Ogre::Pass* pass = passIt.getNext(); - - if (pass->getDepthWriteEnabled() == false) - transparent = true; + Ogre::Technique* tech = techIt.getNext(); + Ogre::Technique::PassIterator passIt = tech->getPassIterator(); + while (passIt.hasMoreElements() && !transparent) + { + Ogre::Pass* pass = passIt.getNext(); + + if (pass->getDepthWriteEnabled() == false) + transparent = true; + } } } - } - mBase->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - mInsert->attachObject(mBase); + mInsert->attachObject(base); + } } } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 82d847091..651f0ffe2 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -41,6 +41,7 @@ private: Ogre::Entity* head; Ogre::SceneNode* mInsert; + Ogre::Entity *mBase; // FIXME: Temporary! bool isBeast; bool isFemale; std::string headModel; From 89cfe778f04df460a03fa3d3f4e00eccec54ee19 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 11:38:20 -0700 Subject: [PATCH 146/298] Support multiple entities for the NPC base --- apps/openmw/mwrender/npcanimation.cpp | 91 ++++++++++++++------------- apps/openmw/mwrender/npcanimation.hpp | 2 +- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index cede30c96..e8e24473c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -45,7 +45,8 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere lAnkle(0), groin(0), lfoot(0), - rfoot(0) + rfoot(0), + mSkelBase(0) { MWWorld::LiveCellRef *ref = ptr.get(); @@ -82,33 +83,39 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); - // FIXME: There can be more than one! NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load(smodel); - mBase = mRend.getScene()->createEntity(meshes[0].first->getName()); - - mBase->setVisibilityFlags(RV_Actors); - bool transparent = false; - for (unsigned int i=0; igetNumSubEntities(); ++i) + for(size_t i = 0;i < meshes.size();i++) { - Ogre::MaterialPtr mat = mBase->getSubEntity(i)->getMaterial(); - Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); - while (techIt.hasMoreElements()) + mBase.push_back(mRend.getScene()->createEntity(meshes[i].first->getName())); + Ogre::Entity *base = mBase.back(); + + if(!mSkelBase && base->hasSkeleton()) + mSkelBase = base; + + base->setVisibilityFlags(RV_Actors); + bool transparent = false; + for(unsigned int j=0;j < base->getNumSubEntities();++j) { - Ogre::Technique* tech = techIt.getNext(); - Ogre::Technique::PassIterator passIt = tech->getPassIterator(); - while (passIt.hasMoreElements()) + Ogre::MaterialPtr mat = base->getSubEntity(j)->getMaterial(); + Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); + while (techIt.hasMoreElements()) { - Ogre::Pass* pass = passIt.getNext(); - if (pass->getDepthWriteEnabled() == false) - transparent = true; + Ogre::Technique* tech = techIt.getNext(); + Ogre::Technique::PassIterator passIt = tech->getPassIterator(); + while (passIt.hasMoreElements()) + { + Ogre::Pass* pass = passIt.getNext(); + if (pass->getDepthWriteEnabled() == false) + transparent = true; + } } } - } - mBase->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - mBase->setSkipAnimationStateUpdate(true); //Magical line of code, this makes the bones - //stay in the same place when we skipanim, or open a gui window + base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + base->setSkipAnimationStateUpdate(true); //Magical line of code, this makes the bones + //stay in the same place when we skipanim, or open a gui window - mInsert->attachObject(mBase); + mInsert->attachObject(base); + } if(isFemale) mInsert->scale(race->data.height.female, race->data.height.female, race->data.height.female); @@ -388,7 +395,7 @@ Ogre::Entity* NpcAnimation::insertBoundedPart(const std::string &mesh, const std Ogre::Entity* part = mRend.getScene()->createEntity(meshes[0].first->getName()); part->setVisibilityFlags(RV_Actors); - mBase->attachObjectToBone(bonename, part); + mSkelBase->attachObjectToBone(bonename, part); return part; } @@ -430,32 +437,32 @@ void NpcAnimation::removeIndividualPart(int type) if(type == ESM::PRT_Head && head) //0 { - mBase->detachObjectFromBone(head); + mSkelBase->detachObjectFromBone(head); head = 0; } else if(type == ESM::PRT_Hair && hair) //1 { - mBase->detachObjectFromBone(hair); + mSkelBase->detachObjectFromBone(hair); hair = 0; } else if(type == ESM::PRT_Neck && neck) //2 { - mBase->detachObjectFromBone(neck); + mSkelBase->detachObjectFromBone(neck); neck = 0; } else if(type == ESM::PRT_Groin && groin)//4 { - mBase->detachObjectFromBone(groin); + mSkelBase->detachObjectFromBone(groin); groin = 0; } else if(type == ESM::PRT_RWrist && rWrist)//8 { - mBase->detachObjectFromBone(rWrist); + mSkelBase->detachObjectFromBone(rWrist); rWrist = 0; } else if(type == ESM::PRT_LWrist && lWrist) //9 { - mBase->detachObjectFromBone(lWrist); + mSkelBase->detachObjectFromBone(lWrist); lWrist = 0; } else if(type == ESM::PRT_Shield) //10 @@ -463,72 +470,72 @@ void NpcAnimation::removeIndividualPart(int type) } else if(type == ESM::PRT_RForearm && rForearm) //11 { - mBase->detachObjectFromBone(rForearm); + mSkelBase->detachObjectFromBone(rForearm); rForearm = 0; } else if(type == ESM::PRT_LForearm && lForearm) //12 { - mBase->detachObjectFromBone(lForearm); + mSkelBase->detachObjectFromBone(lForearm); lForearm = 0; } else if(type == ESM::PRT_RUpperarm && rupperArm) //13 { - mBase->detachObjectFromBone(rupperArm); + mSkelBase->detachObjectFromBone(rupperArm); rupperArm = 0; } else if(type == ESM::PRT_LUpperarm && lupperArm) //14 { - mBase->detachObjectFromBone(lupperArm); + mSkelBase->detachObjectFromBone(lupperArm); lupperArm = 0; } else if(type == ESM::PRT_RFoot && rfoot) //15 { - mBase->detachObjectFromBone(rfoot); + mSkelBase->detachObjectFromBone(rfoot); rfoot = 0; } else if(type == ESM::PRT_LFoot && lfoot) //16 { - mBase->detachObjectFromBone(lfoot); + mSkelBase->detachObjectFromBone(lfoot); lfoot = 0; } else if(type == ESM::PRT_RAnkle && rAnkle) //17 { - mBase->detachObjectFromBone(rAnkle); + mSkelBase->detachObjectFromBone(rAnkle); rAnkle = 0; } else if(type == ESM::PRT_LAnkle && lAnkle) //18 { - mBase->detachObjectFromBone(lAnkle); + mSkelBase->detachObjectFromBone(lAnkle); lAnkle = 0; } else if(type == ESM::PRT_RKnee && rKnee) //19 { - mBase->detachObjectFromBone(rKnee); + mSkelBase->detachObjectFromBone(rKnee); rKnee = 0; } else if(type == ESM::PRT_LKnee && lKnee) //20 { - mBase->detachObjectFromBone(lKnee); + mSkelBase->detachObjectFromBone(lKnee); lKnee = 0; } else if(type == ESM::PRT_RLeg && rUpperLeg) //21 { - mBase->detachObjectFromBone(rUpperLeg); + mSkelBase->detachObjectFromBone(rUpperLeg); rUpperLeg = 0; } else if(type == ESM::PRT_LLeg && lUpperLeg) //22 { - mBase->detachObjectFromBone(lUpperLeg); + mSkelBase->detachObjectFromBone(lUpperLeg); lUpperLeg = 0; } else if(type == ESM::PRT_RPauldron && rclavicle) //23 { - mBase->detachObjectFromBone(rclavicle); + mSkelBase->detachObjectFromBone(rclavicle); rclavicle = 0; } else if(type == ESM::PRT_LPauldron && lclavicle) //24 { - mBase->detachObjectFromBone(lclavicle); + mSkelBase->detachObjectFromBone(lclavicle); lclavicle = 0; } else if(type == ESM::PRT_Weapon) //25 diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 651f0ffe2..a60c8d7fe 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -41,7 +41,7 @@ private: Ogre::Entity* head; Ogre::SceneNode* mInsert; - Ogre::Entity *mBase; // FIXME: Temporary! + Ogre::Entity *mSkelBase; // Entity with the base skeleton (temporary) bool isBeast; bool isFemale; std::string headModel; From c6cc82a51a6df78113dbc2a6e838dfc9231b5704 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 12:19:50 -0700 Subject: [PATCH 147/298] Handle multiple entities per NPC part --- apps/openmw/mwrender/npcanimation.cpp | 181 ++++++++------------------ apps/openmw/mwrender/npcanimation.hpp | 43 +++--- 2 files changed, 79 insertions(+), 145 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e8e24473c..ce22da779 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -28,24 +28,6 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere boots(mInv.end()), leftglove(mInv.end()), rightglove(mInv.end()), skirtiter(mInv.end()), pants(mInv.end()), - lclavicle(0), - rclavicle(0), - rupperArm(0), - lupperArm(0), - rUpperLeg(0), - lUpperLeg(0), - lForearm(0), - rForearm(0), - lWrist(0), - rWrist(0), - rKnee(0), - lKnee(0), - neck(0), - rAnkle(0), - lAnkle(0), - groin(0), - lfoot(0), - rfoot(0), mSkelBase(0) { MWWorld::LiveCellRef *ref = ptr.get(); @@ -388,15 +370,19 @@ void NpcAnimation::updateParts() } } -Ogre::Entity* NpcAnimation::insertBoundedPart(const std::string &mesh, const std::string &bonename) +std::vector NpcAnimation::insertBoundedPart(const std::string &mesh, const std::string &bonename) { - // FIXME: There can be more than one! NifOgre::MeshPairList meshes = NIFLoader::load(mesh); - Ogre::Entity* part = mRend.getScene()->createEntity(meshes[0].first->getName()); - part->setVisibilityFlags(RV_Actors); + std::vector parts; + for(size_t i = 0;i < meshes.size();i++) + { + parts.push_back(mRend.getScene()->createEntity(meshes[i].first->getName())); + Ogre::Entity *part = parts.back(); - mSkelBase->attachObjectToBone(bonename, part); - return part; + part->setVisibilityFlags(RV_Actors); + mSkelBase->attachObjectToBone(bonename, part); + } + return parts; } void NpcAnimation::runAnimation(float timepassed) @@ -430,114 +416,61 @@ void NpcAnimation::runAnimation(float timepassed) } } +void NpcAnimation::removeEntities(std::vector &entities) +{ + for(size_t i = 0;i < entities.size();i++) + mSkelBase->detachObjectFromBone(entities[i]); + entities.clear(); +} + void NpcAnimation::removeIndividualPart(int type) { mPartPriorities[type] = 0; mPartslots[type] = -1; - if(type == ESM::PRT_Head && head) //0 - { - mSkelBase->detachObjectFromBone(head); - head = 0; - } - else if(type == ESM::PRT_Hair && hair) //1 - { - mSkelBase->detachObjectFromBone(hair); - hair = 0; - } - else if(type == ESM::PRT_Neck && neck) //2 - { - mSkelBase->detachObjectFromBone(neck); - neck = 0; - } - else if(type == ESM::PRT_Groin && groin)//4 - { - mSkelBase->detachObjectFromBone(groin); - groin = 0; - } - else if(type == ESM::PRT_RWrist && rWrist)//8 - { - mSkelBase->detachObjectFromBone(rWrist); - rWrist = 0; - } - else if(type == ESM::PRT_LWrist && lWrist) //9 - { - mSkelBase->detachObjectFromBone(lWrist); - lWrist = 0; - } + if(type == ESM::PRT_Head) //0 + removeEntities(head); + else if(type == ESM::PRT_Hair) //1 + removeEntities(hair); + else if(type == ESM::PRT_Neck) //2 + removeEntities(neck); + else if(type == ESM::PRT_Groin)//4 + removeEntities(groin); + else if(type == ESM::PRT_RWrist)//8 + removeEntities(rWrist); + else if(type == ESM::PRT_LWrist) //9 + removeEntities(lWrist); else if(type == ESM::PRT_Shield) //10 { } - else if(type == ESM::PRT_RForearm && rForearm) //11 - { - mSkelBase->detachObjectFromBone(rForearm); - rForearm = 0; - } - else if(type == ESM::PRT_LForearm && lForearm) //12 - { - mSkelBase->detachObjectFromBone(lForearm); - lForearm = 0; - } - else if(type == ESM::PRT_RUpperarm && rupperArm) //13 - { - mSkelBase->detachObjectFromBone(rupperArm); - rupperArm = 0; - } - else if(type == ESM::PRT_LUpperarm && lupperArm) //14 - { - mSkelBase->detachObjectFromBone(lupperArm); - lupperArm = 0; - } - else if(type == ESM::PRT_RFoot && rfoot) //15 - { - mSkelBase->detachObjectFromBone(rfoot); - rfoot = 0; - } - else if(type == ESM::PRT_LFoot && lfoot) //16 - { - mSkelBase->detachObjectFromBone(lfoot); - lfoot = 0; - } - else if(type == ESM::PRT_RAnkle && rAnkle) //17 - { - mSkelBase->detachObjectFromBone(rAnkle); - rAnkle = 0; - } - else if(type == ESM::PRT_LAnkle && lAnkle) //18 - { - mSkelBase->detachObjectFromBone(lAnkle); - lAnkle = 0; - } - else if(type == ESM::PRT_RKnee && rKnee) //19 - { - mSkelBase->detachObjectFromBone(rKnee); - rKnee = 0; - } - else if(type == ESM::PRT_LKnee && lKnee) //20 - { - mSkelBase->detachObjectFromBone(lKnee); - lKnee = 0; - } - else if(type == ESM::PRT_RLeg && rUpperLeg) //21 - { - mSkelBase->detachObjectFromBone(rUpperLeg); - rUpperLeg = 0; - } - else if(type == ESM::PRT_LLeg && lUpperLeg) //22 - { - mSkelBase->detachObjectFromBone(lUpperLeg); - lUpperLeg = 0; - } - else if(type == ESM::PRT_RPauldron && rclavicle) //23 - { - mSkelBase->detachObjectFromBone(rclavicle); - rclavicle = 0; - } - else if(type == ESM::PRT_LPauldron && lclavicle) //24 - { - mSkelBase->detachObjectFromBone(lclavicle); - lclavicle = 0; - } + else if(type == ESM::PRT_RForearm) //11 + removeEntities(rForearm); + else if(type == ESM::PRT_LForearm) //12 + removeEntities(lForearm); + else if(type == ESM::PRT_RUpperarm) //13 + removeEntities(rupperArm); + else if(type == ESM::PRT_LUpperarm) //14 + removeEntities(lupperArm); + else if(type == ESM::PRT_RFoot) //15 + removeEntities(rfoot); + else if(type == ESM::PRT_LFoot) //16 + removeEntities(lfoot); + else if(type == ESM::PRT_RAnkle) //17 + removeEntities(rAnkle); + else if(type == ESM::PRT_LAnkle) //18 + removeEntities(lAnkle); + else if(type == ESM::PRT_RKnee) //19 + removeEntities(rKnee); + else if(type == ESM::PRT_LKnee) //20 + removeEntities(lKnee); + else if(type == ESM::PRT_RLeg) //21 + removeEntities(rUpperLeg); + else if(type == ESM::PRT_LLeg) //22 + removeEntities(lUpperLeg); + else if(type == ESM::PRT_RPauldron) //23 + removeEntities(rclavicle); + else if(type == ESM::PRT_LPauldron) //24 + removeEntities(lclavicle); else if(type == ESM::PRT_Weapon) //25 { } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index a60c8d7fe..7830b68cc 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -19,26 +19,26 @@ private: int mPartPriorities[27]; //Bounded Parts - Ogre::Entity* lclavicle; - Ogre::Entity* rclavicle; - Ogre::Entity* rupperArm; - Ogre::Entity* lupperArm; - Ogre::Entity* rUpperLeg; - Ogre::Entity* lUpperLeg; - Ogre::Entity* lForearm; - Ogre::Entity* rForearm; - Ogre::Entity* lWrist; - Ogre::Entity* rWrist; - Ogre::Entity* rKnee; - Ogre::Entity* lKnee; - Ogre::Entity* neck; - Ogre::Entity* rAnkle; - Ogre::Entity* lAnkle; - Ogre::Entity* groin; - Ogre::Entity* lfoot; - Ogre::Entity* rfoot; - Ogre::Entity* hair; - Ogre::Entity* head; + std::vector lclavicle; + std::vector rclavicle; + std::vector rupperArm; + std::vector lupperArm; + std::vector rUpperLeg; + std::vector lUpperLeg; + std::vector lForearm; + std::vector rForearm; + std::vector lWrist; + std::vector rWrist; + std::vector rKnee; + std::vector lKnee; + std::vector neck; + std::vector rAnkle; + std::vector lAnkle; + std::vector groin; + std::vector lfoot; + std::vector rfoot; + std::vector hair; + std::vector head; Ogre::SceneNode* mInsert; Ogre::Entity *mSkelBase; // Entity with the base skeleton (temporary) @@ -65,9 +65,10 @@ private: public: NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv); virtual ~NpcAnimation(); - Ogre::Entity* insertBoundedPart(const std::string &mesh, const std::string &bonename); + std::vector insertBoundedPart(const std::string &mesh, const std::string &bonename); virtual void runAnimation(float timepassed); void updateParts(); + void removeEntities(std::vector &entities); void removeIndividualPart(int type); void reserveIndividualPart(int type, int group, int priority); From 3dedac5cb14623911a43de2027c8f86077586e8d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 13:40:03 -0700 Subject: [PATCH 148/298] Create mesh entities for objects when loading the NIF --- apps/openmw/mwrender/objects.cpp | 23 +++++++-------- components/nifogre/ogre_nif_loader.cpp | 39 ++++++++++++++++++++++++++ components/nifogre/ogre_nif_loader.hpp | 12 ++++++++ 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 2ff9de9a4..bc1a4a521 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -93,13 +93,10 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) assert(insert); Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load(mesh); - std::vector entities(meshes.size()); - for(size_t i = 0;i < meshes.size();i++) + NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(insert, mesh); + for(size_t i = 0;i < entities.mEntities.size();i++) { - entities[i] = mRenderer.getScene()->createEntity(meshes[i].first->getName()); - - const Ogre::AxisAlignedBox &tmp = entities[i]->getBoundingBox(); + const Ogre::AxisAlignedBox &tmp = entities.mEntities[i]->getBoundingBox(); bounds.merge(Ogre::AxisAlignedBox(insert->_getDerivedPosition() + tmp.getMinimum(), insert->_getDerivedPosition() + tmp.getMaximum()) ); @@ -119,9 +116,9 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) mBounds[ptr.getCell()].merge(bounds); bool transparent = false; - for(size_t i = 0;i < entities.size();i++) + for(size_t i = 0;i < entities.mEntities.size();i++) { - Ogre::Entity *ent = entities[i]; + Ogre::Entity *ent = entities.mEntities[i]; for (unsigned int i=0; igetNumSubEntities(); ++i) { Ogre::MaterialPtr mat = ent->getSubEntity(i)->getMaterial(); @@ -143,10 +140,9 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) if(!mIsStatic || !Settings::Manager::getBool("use static geometry", "Objects") || transparent) { - for(size_t i = 0;i < entities.size();i++) + for(size_t i = 0;i < entities.mEntities.size();i++) { - Ogre::Entity *ent = entities[i]; - insert->attachObject(ent); + Ogre::Entity *ent = entities.mEntities[i]; ent->setRenderingDistance(small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0); ent->setVisibilityFlags(mIsStatic ? (small ? RV_StaticsSmall : RV_Statics) : RV_Misc); @@ -197,9 +193,10 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) sg->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - for(size_t i = 0;i < entities.size();i++) + for(size_t i = 0;i < entities.mEntities.size();i++) { - Ogre::Entity *ent = entities[i]; + Ogre::Entity *ent = entities.mEntities[i]; + insert->detachObject(ent); sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); mRenderer.getScene()->destroyEntity(ent); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index fef7054f7..c90083619 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -871,6 +872,44 @@ MeshPairList NIFLoader::load(const std::string &name, Ogre::SkeletonPtr *skel, c return meshes; } +EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string &name, const std::string &group) +{ + EntityList entitylist; + + MeshPairList meshes = load(name, NULL, group); + if(meshes.size() == 0) + return entitylist; + + Ogre::SceneManager *sceneMgr = parent->getCreator(); + for(size_t i = 0;i < meshes.size();i++) + { + entitylist.mEntities.push_back(sceneMgr->createEntity(meshes[i].first->getName())); + Ogre::Entity *entity = entitylist.mEntities.back(); + if(!entitylist.mSkelBase && entity->hasSkeleton()) + entitylist.mSkelBase = entity; + } + + if(entitylist.mSkelBase) + { + parent->attachObject(entitylist.mSkelBase); + for(size_t i = 0;i < entitylist.mEntities.size();i++) + { + Ogre::Entity *entity = entitylist.mEntities[i]; + if(entity != entitylist.mSkelBase && entity->hasSkeleton()) + entity->shareSkeletonInstanceWith(entitylist.mSkelBase); + else if(entity != entitylist.mSkelBase) + entitylist.mSkelBase->attachObjectToBone(meshes[i].second, entity); + } + } + else + { + for(size_t i = 0;i < entitylist.mEntities.size();i++) + parent->attachObject(entitylist.mEntities[i]); + } + + return entitylist; +} + /* More code currently not in use, from the old D source. This was used in the first attempt at loading NIF meshes, where each submesh diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 4e9ffc31e..bd6f99b2f 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -58,6 +58,14 @@ namespace Nif namespace NifOgre { +struct EntityList { + std::vector mEntities; + Ogre::Entity *mSkelBase; + + EntityList() : mSkelBase(0) + { } +}; + /** This holds a list of meshes along with the names of their parent nodes */ typedef std::vector< std::pair > MeshPairList; @@ -81,6 +89,10 @@ public: static MeshPairList load(const std::string &name, Ogre::SkeletonPtr *skel=NULL, const std::string &group="General"); + + static EntityList createEntities(Ogre::SceneNode *parent, + const std::string &name, + const std::string &group="General"); }; } From 3efd2030e206099f613d99aadee3ddce3e959a6b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 14:11:07 -0700 Subject: [PATCH 149/298] Create entities when loading NIFs for creatures --- apps/openmw/mwrender/creatureanimation.cpp | 17 +++++++---------- components/nifogre/ogre_nif_loader.cpp | 3 +++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index b42feec68..92930e8d4 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -26,17 +26,16 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O { std::string mesh = "meshes\\" + ref->base->model; - NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load(mesh); - for(size_t i = 0;i < meshes.size();i++) + NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(mInsert, mesh); + mBase = entities.mEntities; + for(size_t i = 0;i < mBase.size();i++) { - mBase.push_back(mRend.getScene()->createEntity(meshes[i].first->getName())); - Ogre::Entity *base = mBase.back(); - base->setVisibilityFlags(RV_Actors); + mBase[i]->setVisibilityFlags(RV_Actors); bool transparent = false; - for (unsigned int j=0;j < base->getNumSubEntities() && !transparent; ++j) + for (unsigned int j=0;j < mBase[i]->getNumSubEntities() && !transparent; ++j) { - Ogre::MaterialPtr mat = base->getSubEntity(j)->getMaterial(); + Ogre::MaterialPtr mat = mBase[i]->getSubEntity(j)->getMaterial(); Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); while (techIt.hasMoreElements() && !transparent) { @@ -51,9 +50,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O } } } - base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - - mInsert->attachObject(base); + mBase[i]->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); } } } diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index c90083619..79eaa6680 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -896,7 +896,10 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string { Ogre::Entity *entity = entitylist.mEntities[i]; if(entity != entitylist.mSkelBase && entity->hasSkeleton()) + { entity->shareSkeletonInstanceWith(entitylist.mSkelBase); + parent->attachObject(entity); + } else if(entity != entitylist.mSkelBase) entitylist.mSkelBase->attachObjectToBone(meshes[i].second, entity); } From a590db2cf46da03e5fd8642f629424a039c3434a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 15:43:40 -0700 Subject: [PATCH 150/298] Create entities when loading NIFs for the NPC base --- apps/openmw/mwrender/npcanimation.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index ce22da779..86a58c849 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -65,14 +65,12 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); - NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load(smodel); - for(size_t i = 0;i < meshes.size();i++) + NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(mInsert, smodel); + mBase = entities.mEntities; + mSkelBase = entities.mSkelBase; + for(size_t i = 0;i < mBase.size();i++) { - mBase.push_back(mRend.getScene()->createEntity(meshes[i].first->getName())); - Ogre::Entity *base = mBase.back(); - - if(!mSkelBase && base->hasSkeleton()) - mSkelBase = base; + Ogre::Entity *base = mBase[i]; base->setVisibilityFlags(RV_Actors); bool transparent = false; @@ -95,8 +93,6 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); base->setSkipAnimationStateUpdate(true); //Magical line of code, this makes the bones //stay in the same place when we skipanim, or open a gui window - - mInsert->attachObject(base); } if(isFemale) From 94f3e7a6c069cc9d4de5959a343bc06d0d66b320 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 16:00:03 -0700 Subject: [PATCH 151/298] Store the entity list in the object --- apps/openmw/mwrender/animation.hpp | 3 ++- apps/openmw/mwrender/creatureanimation.cpp | 14 +++++++------- apps/openmw/mwrender/npcanimation.cpp | 15 ++++++--------- apps/openmw/mwrender/npcanimation.hpp | 1 - 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index ea18865da..def7f226c 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -3,6 +3,7 @@ #include +#include #include #include "../mwworld/actiontalk.hpp" #include @@ -39,7 +40,7 @@ protected: std::vector* mTransformations; std::map* mTextmappings; - std::vector mBase; + NifOgre::EntityList mEntityList; void handleAnimationTransforms(); bool timeIndex( float time, const std::vector & times, int & i, int & j, float & x ); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 92930e8d4..fd2855154 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -26,16 +26,16 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O { std::string mesh = "meshes\\" + ref->base->model; - NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(mInsert, mesh); - mBase = entities.mEntities; - for(size_t i = 0;i < mBase.size();i++) + mEntityList = NifOgre::NIFLoader::createEntities(mInsert, mesh); + for(size_t i = 0;i < mEntityList.mEntities.size();i++) { - mBase[i]->setVisibilityFlags(RV_Actors); + Ogre::Entity *ent = mEntityList.mEntities[i]; + ent->setVisibilityFlags(RV_Actors); bool transparent = false; - for (unsigned int j=0;j < mBase[i]->getNumSubEntities() && !transparent; ++j) + for (unsigned int j=0;j < ent->getNumSubEntities() && !transparent; ++j) { - Ogre::MaterialPtr mat = mBase[i]->getSubEntity(j)->getMaterial(); + Ogre::MaterialPtr mat = ent->getSubEntity(j)->getMaterial(); Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator(); while (techIt.hasMoreElements() && !transparent) { @@ -50,7 +50,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O } } } - mBase[i]->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); + ent->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); } } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 86a58c849..cdef5ce8b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -27,8 +27,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere leftpauldron(mInv.end()), rightpauldron(mInv.end()), boots(mInv.end()), leftglove(mInv.end()), rightglove(mInv.end()), skirtiter(mInv.end()), - pants(mInv.end()), - mSkelBase(0) + pants(mInv.end()) { MWWorld::LiveCellRef *ref = ptr.get(); @@ -65,12 +64,10 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); - NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(mInsert, smodel); - mBase = entities.mEntities; - mSkelBase = entities.mSkelBase; - for(size_t i = 0;i < mBase.size();i++) + mEntityList = NifOgre::NIFLoader::createEntities(mInsert, smodel); + for(size_t i = 0;i < mEntityList.mEntities.size();i++) { - Ogre::Entity *base = mBase[i]; + Ogre::Entity *base = mEntityList.mEntities[i]; base->setVisibilityFlags(RV_Actors); bool transparent = false; @@ -376,7 +373,7 @@ std::vector NpcAnimation::insertBoundedPart(const std::string &me Ogre::Entity *part = parts.back(); part->setVisibilityFlags(RV_Actors); - mSkelBase->attachObjectToBone(bonename, part); + mEntityList.mSkelBase->attachObjectToBone(bonename, part); } return parts; } @@ -415,7 +412,7 @@ void NpcAnimation::runAnimation(float timepassed) void NpcAnimation::removeEntities(std::vector &entities) { for(size_t i = 0;i < entities.size();i++) - mSkelBase->detachObjectFromBone(entities[i]); + mEntityList.mSkelBase->detachObjectFromBone(entities[i]); entities.clear(); } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 7830b68cc..f27642720 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -41,7 +41,6 @@ private: std::vector head; Ogre::SceneNode* mInsert; - Ogre::Entity *mSkelBase; // Entity with the base skeleton (temporary) bool isBeast; bool isFemale; std::string headModel; From 496343b714e9b78dbb3297307122775cac57db03 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 16:36:43 -0700 Subject: [PATCH 152/298] Use the proper member for the NIF type string --- components/nifogre/ogre_nif_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 79eaa6680..305e66df1 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -792,7 +792,7 @@ public: } } else - warn("Unhandled extra data type "+e->recType); + warn("Unhandled extra data type "+e->recName); e = e->extra; } From 1c544682d5bf12b152cb3e868b90e22085806af3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 16:45:01 -0700 Subject: [PATCH 153/298] Stub handling for NiTextKeyExtraData to suppress some spam --- components/nifogre/ogre_nif_loader.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 305e66df1..761924488 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -779,8 +779,9 @@ public: Nif::ExtraPtr e = node->extra; while(!e.empty()) { - Nif::NiStringExtraData *sd = dynamic_cast(e.getPtr()); - if(sd != NULL) + Nif::NiStringExtraData *sd; + Nif::NiTextKeyExtraData *td; + if((sd=dynamic_cast(e.getPtr())) != NULL) { // String markers may contain important information // affecting the entire subtree of this obj @@ -791,6 +792,10 @@ public: flags |= 0x01; } } + else if((td=dynamic_cast(e.getPtr())) != NULL) + { + // TODO: Read and store text keys somewhere + } else warn("Unhandled extra data type "+e->recName); e = e->extra; From 92546ca18d034300b12b179dc099e1cee37c005f Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 20:23:09 -0700 Subject: [PATCH 154/298] Move the last bits of code to createEntities --- apps/openmw/mwrender/npcanimation.cpp | 14 ++++---------- apps/openmw/mwrender/sky.cpp | 22 +++++++++------------- components/nifogre/ogre_nif_loader.hpp | 2 +- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index cdef5ce8b..439f6fa2d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -365,16 +365,10 @@ void NpcAnimation::updateParts() std::vector NpcAnimation::insertBoundedPart(const std::string &mesh, const std::string &bonename) { - NifOgre::MeshPairList meshes = NIFLoader::load(mesh); - std::vector parts; - for(size_t i = 0;i < meshes.size();i++) - { - parts.push_back(mRend.getScene()->createEntity(meshes[i].first->getName())); - Ogre::Entity *part = parts.back(); - - part->setVisibilityFlags(RV_Actors); - mEntityList.mSkelBase->attachObjectToBone(bonename, part); - } + NifOgre::EntityList entities = NIFLoader::createEntities(mInsert, mesh); + std::vector &parts = entities.mEntities; + for(size_t i = 0;i < parts.size();i++) + parts[i]->setVisibilityFlags(RV_Actors); return parts; } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 8f49e03df..682a96419 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -509,16 +509,14 @@ void SkyManager::create() /// \todo sky_night_02.nif (available in Bloodmoon) mAtmosphereNight = mRootNode->createChildSceneNode(); - NifOgre::MeshPairList meshes = NifOgre::NIFLoader::load("meshes\\sky_night_01.nif"); - for(size_t i = 0;i < meshes.size();i++) + NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(mAtmosphereNight, "meshes\\sky_night_01.nif"); + for(size_t i = 0;i < entities.mEntities.size();i++) { - Entity* night1_ent = mSceneMgr->createEntity(meshes[i].first->getName()); + Entity* night1_ent = entities.mEntities[i]; night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); night1_ent->setVisibilityFlags(RV_Sky); night1_ent->setCastShadows(false); - mAtmosphereNight->attachObject(night1_ent); - for (unsigned int i=0; igetNumSubEntities(); ++i) { MaterialPtr mp = night1_ent->getSubEntity(i)->getMaterial(); @@ -589,17 +587,16 @@ void SkyManager::create() fshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); mAtmosphereDay = mRootNode->createChildSceneNode(); - meshes = NifOgre::NIFLoader::load("meshes\\sky_atmosphere.nif"); - for(size_t i = 0;i < meshes.size();i++) + entities = NifOgre::NIFLoader::createEntities(mAtmosphereDay, "meshes\\sky_atmosphere.nif"); + for(size_t i = 0;i < entities.mEntities.size();i++) { - Entity* atmosphere_ent = mSceneMgr->createEntity(meshes[i].first->getName()); + Entity* atmosphere_ent = entities.mEntities[i]; atmosphere_ent->setCastShadows(false); ModVertexAlpha(atmosphere_ent, 0); atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly); atmosphere_ent->setVisibilityFlags(RV_Sky); - mAtmosphereDay->attachObject(atmosphere_ent); mAtmosphereMaterial = atmosphere_ent->getSubEntity(0)->getMaterial(); mAtmosphereMaterial = mAtmosphereMaterial->clone("Atmosphere"); @@ -677,13 +674,12 @@ void SkyManager::create() mCloudFragmentShader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); SceneNode* clouds_node = mRootNode->createChildSceneNode(); - meshes = NifOgre::NIFLoader::load("meshes\\sky_clouds_01.nif"); - for(size_t i = 0;i < meshes.size();i++) + entities = NifOgre::NIFLoader::createEntities(clouds_node, "meshes\\sky_clouds_01.nif"); + for(size_t i = 0;i < entities.mEntities.size();i++) { - Entity* clouds_ent = mSceneMgr->createEntity(meshes[i].first->getName()); + Entity* clouds_ent = entities.mEntities[i]; clouds_ent->setVisibilityFlags(RV_Sky); clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); - clouds_node->attachObject(clouds_ent); mCloudMaterial = clouds_ent->getSubEntity(0)->getMaterial(); mCloudMaterial = mCloudMaterial->clone("Clouds"); diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index bd6f99b2f..f21069b75 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -85,11 +85,11 @@ typedef std::vector< std::pair > MeshPairList; */ class NIFLoader { -public: static MeshPairList load(const std::string &name, Ogre::SkeletonPtr *skel=NULL, const std::string &group="General"); +public: static EntityList createEntities(Ogre::SceneNode *parent, const std::string &name, const std::string &group="General"); From b04c3cbcac64988ec3a964a1b323946c6d56abbe Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 21:42:47 -0700 Subject: [PATCH 155/298] Store the entities' root node in the EntityList --- components/nifogre/ogre_nif_loader.cpp | 1 + components/nifogre/ogre_nif_loader.hpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 761924488..c74f1fe0d 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -885,6 +885,7 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string if(meshes.size() == 0) return entitylist; + entitylist.mRootNode = parent; Ogre::SceneManager *sceneMgr = parent->getCreator(); for(size_t i = 0;i < meshes.size();i++) { diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index f21069b75..70f467295 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -61,8 +61,9 @@ namespace NifOgre struct EntityList { std::vector mEntities; Ogre::Entity *mSkelBase; + Ogre::SceneNode *mRootNode; - EntityList() : mSkelBase(0) + EntityList() : mSkelBase(0), mRootNode(0) { } }; From a32740cf5ee1712299be24d00d8ba67d90fe2e29 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 22:41:26 -0700 Subject: [PATCH 156/298] Remove an unused parameter --- components/nifogre/ogre_nif_loader.cpp | 20 ++++++++------------ components/nifogre/ogre_nif_loader.hpp | 4 +--- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index c74f1fe0d..9c294f8c3 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -180,20 +180,19 @@ void loadResource(Ogre::Resource *resource) buildBones(skel, node); } -static bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node, Ogre::SkeletonPtr *skel) +static bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node) { if(node->boneTrafo != NULL) { Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton(); - Ogre::SkeletonPtr tmp = skelMgr.getByName(name); - if(tmp.isNull()) + Ogre::SkeletonPtr skel = skelMgr.getByName(name); + if(skel.isNull()) { static NIFSkeletonLoader loader; - tmp = skelMgr.create(name, group, true, &loader); + skel = skelMgr.create(name, group, true, &loader); } - if(skel) *skel = tmp; return true; } @@ -205,7 +204,7 @@ static bool createSkeleton(const std::string &name, const std::string &group, Ni { if(!children[i].empty()) { - if(createSkeleton(name, group, children[i].getPtr(), skel)) + if(createSkeleton(name, group, children[i].getPtr())) return true; } } @@ -843,13 +842,10 @@ public: NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders; -MeshPairList NIFLoader::load(const std::string &name, Ogre::SkeletonPtr *skel, const std::string &group) +MeshPairList NIFLoader::load(const std::string &name, const std::string &group) { MeshPairList meshes; - if(skel != NULL) - skel->setNull(); - Nif::NIFFile nif(name); if (nif.numRecords() < 1) { @@ -869,7 +865,7 @@ MeshPairList NIFLoader::load(const std::string &name, Ogre::SkeletonPtr *skel, c return meshes; } - bool hasSkel = NIFSkeletonLoader::createSkeleton(name, group, node, skel); + bool hasSkel = NIFSkeletonLoader::createSkeleton(name, group, node); NIFMeshLoader meshldr(name, group, hasSkel); meshldr.createMeshes(node, meshes); @@ -881,7 +877,7 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string { EntityList entitylist; - MeshPairList meshes = load(name, NULL, group); + MeshPairList meshes = load(name, group); if(meshes.size() == 0) return entitylist; diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 70f467295..3f58097c0 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -86,9 +86,7 @@ typedef std::vector< std::pair > MeshPairList; */ class NIFLoader { - static MeshPairList load(const std::string &name, - Ogre::SkeletonPtr *skel=NULL, - const std::string &group="General"); + static MeshPairList load(const std::string &name, const std::string &group); public: static EntityList createEntities(Ogre::SceneNode *parent, From 4109d0d9232b13813f53a74e78afcffb7f772b45 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 17 Jul 2012 22:47:56 -0700 Subject: [PATCH 157/298] Destroy entities removed from the NPC --- apps/openmw/mwrender/npcanimation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 439f6fa2d..d740dd7db 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -405,8 +405,12 @@ void NpcAnimation::runAnimation(float timepassed) void NpcAnimation::removeEntities(std::vector &entities) { + Ogre::SceneManager *sceneMgr = mInsert->getCreator(); for(size_t i = 0;i < entities.size();i++) + { mEntityList.mSkelBase->detachObjectFromBone(entities[i]); + sceneMgr->destroyEntity(entities[i]); + } entities.clear(); } From 02d39080c8e3f66d7ff77d5cc9617d8ba4174767 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 00:17:39 -0700 Subject: [PATCH 158/298] Destroy entities when they're done with. --- apps/openmw/mwrender/animation.cpp | 5 +++++ apps/openmw/mwrender/npcanimation.cpp | 20 ++++++++++++++++++++ apps/openmw/mwrender/npcanimation.hpp | 1 - 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 17c3f62ac..fddfe7b8a 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace MWRender { @@ -28,6 +29,10 @@ Animation::Animation(OEngine::Render::OgreRenderer& _rend) Animation::~Animation() { + Ogre::SceneManager *sceneMgr = mInsert->getCreator(); + for(size_t i = 0;i < mEntityList.mEntities.size();i++) + sceneMgr->destroyEntity(mEntityList.mEntities[i]); + mEntityList.mEntities.clear(); } void Animation::startScript(std::string groupname, int mode, int loops) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d740dd7db..51bad3d0e 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -17,6 +17,26 @@ using namespace NifOgre; namespace MWRender{ NpcAnimation::~NpcAnimation() { + removeEntities(head); + removeEntities(hair); + removeEntities(neck); + removeEntities(groin); + removeEntities(rWrist); + removeEntities(lWrist); + removeEntities(rForearm); + removeEntities(lForearm); + removeEntities(rupperArm); + removeEntities(lupperArm); + removeEntities(rfoot); + removeEntities(lfoot); + removeEntities(rAnkle); + removeEntities(lAnkle); + removeEntities(rKnee); + removeEntities(lKnee); + removeEntities(rUpperLeg); + removeEntities(lUpperLeg); + removeEntities(rclavicle); + removeEntities(lclavicle); } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index f27642720..6fb549408 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -40,7 +40,6 @@ private: std::vector hair; std::vector head; - Ogre::SceneNode* mInsert; bool isBeast; bool isFemale; std::string headModel; From 6611b0b317986430bac19cb57058cefb1d8c4af4 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 10:09:16 -0700 Subject: [PATCH 159/298] Use an array instead of a bunch of nearly-identical if blocks --- apps/openmw/mwrender/npcanimation.cpp | 100 ++++++++------------------ 1 file changed, 28 insertions(+), 72 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 51bad3d0e..eb5fceb0e 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -123,79 +123,35 @@ void NpcAnimation::updateParts() { bool apparelChanged = false; - //mInv.getSlot(MWWorld::InventoryStore::Slot_Robe); - if(robe != mInv.getSlot(MWWorld::InventoryStore::Slot_Robe)) - { - // A robe was added or removed - robe = mInv.getSlot(MWWorld::InventoryStore::Slot_Robe); - removePartGroup(MWWorld::InventoryStore::Slot_Robe); - apparelChanged = true; - } - if(skirtiter != mInv.getSlot(MWWorld::InventoryStore::Slot_Skirt)) - { - skirtiter = mInv.getSlot(MWWorld::InventoryStore::Slot_Skirt); - removePartGroup(MWWorld::InventoryStore::Slot_Skirt); - apparelChanged = true; - } - if(helmet != mInv.getSlot(MWWorld::InventoryStore::Slot_Helmet)) - { - helmet = mInv.getSlot(MWWorld::InventoryStore::Slot_Helmet); - removePartGroup(MWWorld::InventoryStore::Slot_Helmet); - apparelChanged = true; - } - if(cuirass != mInv.getSlot(MWWorld::InventoryStore::Slot_Cuirass)) - { - cuirass = mInv.getSlot(MWWorld::InventoryStore::Slot_Cuirass); - removePartGroup(MWWorld::InventoryStore::Slot_Cuirass); - apparelChanged = true; - } - if(greaves != mInv.getSlot(MWWorld::InventoryStore::Slot_Greaves)) - { - greaves = mInv.getSlot(MWWorld::InventoryStore::Slot_Greaves); - removePartGroup(MWWorld::InventoryStore::Slot_Greaves); - apparelChanged = true; - } - if(leftpauldron != mInv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron)) - { - leftpauldron = mInv.getSlot(MWWorld::InventoryStore::Slot_LeftPauldron); - removePartGroup(MWWorld::InventoryStore::Slot_LeftPauldron); - apparelChanged = true; - } - if(rightpauldron != mInv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron)) - { - rightpauldron = mInv.getSlot(MWWorld::InventoryStore::Slot_RightPauldron); - removePartGroup(MWWorld::InventoryStore::Slot_RightPauldron); - apparelChanged = true; - } - if(!isBeast && boots != mInv.getSlot(MWWorld::InventoryStore::Slot_Boots)) - { - boots = mInv.getSlot(MWWorld::InventoryStore::Slot_Boots); - removePartGroup(MWWorld::InventoryStore::Slot_Boots); - apparelChanged = true; - } - if(leftglove != mInv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet)) - { - leftglove = mInv.getSlot(MWWorld::InventoryStore::Slot_LeftGauntlet); - removePartGroup(MWWorld::InventoryStore::Slot_LeftGauntlet); - apparelChanged = true; - } - if(rightglove != mInv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet)) - { - rightglove = mInv.getSlot(MWWorld::InventoryStore::Slot_RightGauntlet); - removePartGroup(MWWorld::InventoryStore::Slot_RightGauntlet); - apparelChanged = true; - } - if(shirt != mInv.getSlot(MWWorld::InventoryStore::Slot_Shirt)) - { - shirt = mInv.getSlot(MWWorld::InventoryStore::Slot_Shirt); - removePartGroup(MWWorld::InventoryStore::Slot_Shirt); - apparelChanged = true; - } - if(pants != mInv.getSlot(MWWorld::InventoryStore::Slot_Pants)) + const struct { + MWWorld::ContainerStoreIterator *iter; + int slot; + } slotlist[] = { + { &robe, MWWorld::InventoryStore::Slot_Robe }, + { &skirtiter, MWWorld::InventoryStore::Slot_Skirt }, + { &helmet, MWWorld::InventoryStore::Slot_Helmet }, + { &cuirass, MWWorld::InventoryStore::Slot_Cuirass }, + { &greaves, MWWorld::InventoryStore::Slot_Greaves }, + { &leftpauldron, MWWorld::InventoryStore::Slot_LeftPauldron }, + { &rightpauldron, MWWorld::InventoryStore::Slot_RightPauldron }, + { &boots, MWWorld::InventoryStore::Slot_Boots }, // !isBeast + { &leftglove, MWWorld::InventoryStore::Slot_LeftGauntlet }, + { &rightglove, MWWorld::InventoryStore::Slot_RightGauntlet }, + { &shirt, MWWorld::InventoryStore::Slot_Shirt }, + { &pants, MWWorld::InventoryStore::Slot_Pants }, + }; + for(size_t i = 0;i < sizeof(slotlist)/sizeof(slotlist[0]);i++) { - pants = mInv.getSlot(MWWorld::InventoryStore::Slot_Pants); - removePartGroup(MWWorld::InventoryStore::Slot_Pants); - apparelChanged = true; + if(slotlist[i].iter == &boots && isBeast) + continue; + + MWWorld::ContainerStoreIterator iter = mInv.getSlot(slotlist[i].slot); + if(*slotlist[i].iter != iter) + { + *slotlist[i].iter = iter; + removePartGroup(slotlist[i].slot); + apparelChanged = true; + } } if(apparelChanged) From 04e496a6ca4e1329c95b084b77e14572b0c61455 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 10:26:25 -0700 Subject: [PATCH 160/298] Store the entity lists for NPC parts --- apps/openmw/mwrender/npcanimation.cpp | 18 ++++++----- apps/openmw/mwrender/npcanimation.hpp | 44 +++++++++++++-------------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index eb5fceb0e..b4e39ff15 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -339,13 +339,13 @@ void NpcAnimation::updateParts() } } -std::vector NpcAnimation::insertBoundedPart(const std::string &mesh, const std::string &bonename) +NifOgre::EntityList NpcAnimation::insertBoundedPart(const std::string &mesh, const std::string &bonename) { NifOgre::EntityList entities = NIFLoader::createEntities(mInsert, mesh); std::vector &parts = entities.mEntities; for(size_t i = 0;i < parts.size();i++) parts[i]->setVisibilityFlags(RV_Actors); - return parts; + return entities; } void NpcAnimation::runAnimation(float timepassed) @@ -379,15 +379,19 @@ void NpcAnimation::runAnimation(float timepassed) } } -void NpcAnimation::removeEntities(std::vector &entities) +void NpcAnimation::removeEntities(NifOgre::EntityList &entities) { + assert(&entities != &mEntityList); + Ogre::SceneManager *sceneMgr = mInsert->getCreator(); - for(size_t i = 0;i < entities.size();i++) + for(size_t i = 0;i < entities.mEntities.size();i++) { - mEntityList.mSkelBase->detachObjectFromBone(entities[i]); - sceneMgr->destroyEntity(entities[i]); + mEntityList.mSkelBase->detachObjectFromBone(entities.mEntities[i]); + sceneMgr->destroyEntity(entities.mEntities[i]); } - entities.clear(); + entities.mEntities.clear(); + entities.mSkelBase = NULL; + entities.mRootNode = NULL; } void NpcAnimation::removeIndividualPart(int type) diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 6fb549408..1eec1294f 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -19,26 +19,26 @@ private: int mPartPriorities[27]; //Bounded Parts - std::vector lclavicle; - std::vector rclavicle; - std::vector rupperArm; - std::vector lupperArm; - std::vector rUpperLeg; - std::vector lUpperLeg; - std::vector lForearm; - std::vector rForearm; - std::vector lWrist; - std::vector rWrist; - std::vector rKnee; - std::vector lKnee; - std::vector neck; - std::vector rAnkle; - std::vector lAnkle; - std::vector groin; - std::vector lfoot; - std::vector rfoot; - std::vector hair; - std::vector head; + NifOgre::EntityList lclavicle; + NifOgre::EntityList rclavicle; + NifOgre::EntityList rupperArm; + NifOgre::EntityList lupperArm; + NifOgre::EntityList rUpperLeg; + NifOgre::EntityList lUpperLeg; + NifOgre::EntityList lForearm; + NifOgre::EntityList rForearm; + NifOgre::EntityList lWrist; + NifOgre::EntityList rWrist; + NifOgre::EntityList rKnee; + NifOgre::EntityList lKnee; + NifOgre::EntityList neck; + NifOgre::EntityList rAnkle; + NifOgre::EntityList lAnkle; + NifOgre::EntityList groin; + NifOgre::EntityList lfoot; + NifOgre::EntityList rfoot; + NifOgre::EntityList hair; + NifOgre::EntityList head; bool isBeast; bool isFemale; @@ -63,10 +63,10 @@ private: public: NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv); virtual ~NpcAnimation(); - std::vector insertBoundedPart(const std::string &mesh, const std::string &bonename); + NifOgre::EntityList insertBoundedPart(const std::string &mesh, const std::string &bonename); virtual void runAnimation(float timepassed); void updateParts(); - void removeEntities(std::vector &entities); + void removeEntities(NifOgre::EntityList &entities); void removeIndividualPart(int type); void reserveIndividualPart(int type, int group, int priority); From db948969c98cd032915a7aae450f1d522ab48bba Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 11:14:13 -0700 Subject: [PATCH 161/298] Attach NPC parts to the proper bone --- apps/openmw/mwrender/npcanimation.cpp | 3 +- components/nifogre/ogre_nif_loader.cpp | 43 ++++++++++++++++++++++++++ components/nifogre/ogre_nif_loader.hpp | 6 ++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b4e39ff15..991ef827e 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -341,7 +341,8 @@ void NpcAnimation::updateParts() NifOgre::EntityList NpcAnimation::insertBoundedPart(const std::string &mesh, const std::string &bonename) { - NifOgre::EntityList entities = NIFLoader::createEntities(mInsert, mesh); + NifOgre::EntityList entities = NIFLoader::createEntities(mEntityList.mSkelBase, bonename, + mInsert->getCreator(), mesh); std::vector &parts = entities.mEntities; for(size_t i = 0;i < parts.size();i++) parts[i]->setVisibilityFlags(RV_Actors); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 9c294f8c3..44992eea4 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -915,6 +915,49 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string return entitylist; } +EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bonename, + Ogre::SceneManager *sceneMgr, + const std::string &name, + const std::string &group) +{ + EntityList entitylist; + + MeshPairList meshes = load(name, group); + if(meshes.size() == 0) + return entitylist; + + for(size_t i = 0;i < meshes.size();i++) + { + entitylist.mEntities.push_back(sceneMgr->createEntity(meshes[i].first->getName())); + Ogre::Entity *entity = entitylist.mEntities.back(); + if(!entitylist.mSkelBase && entity->hasSkeleton()) + entitylist.mSkelBase = entity; + } + + if(entitylist.mSkelBase) + { + parent->attachObjectToBone(bonename, entitylist.mSkelBase); + for(size_t i = 0;i < entitylist.mEntities.size();i++) + { + Ogre::Entity *entity = entitylist.mEntities[i]; + if(entity != entitylist.mSkelBase && entity->hasSkeleton()) + { + entity->shareSkeletonInstanceWith(entitylist.mSkelBase); + parent->attachObjectToBone(bonename, entity); + } + else if(entity != entitylist.mSkelBase) + entitylist.mSkelBase->attachObjectToBone(meshes[i].second, entity); + } + } + else + { + for(size_t i = 0;i < entitylist.mEntities.size();i++) + parent->attachObjectToBone(bonename, entitylist.mEntities[i]); + } + + return entitylist; +} + /* More code currently not in use, from the old D source. This was used in the first attempt at loading NIF meshes, where each submesh diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 3f58097c0..2348a9b10 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -58,6 +58,7 @@ namespace Nif namespace NifOgre { +// FIXME: This should not be in NifOgre, it works agnostic of what model format is used struct EntityList { std::vector mEntities; Ogre::Entity *mSkelBase; @@ -89,6 +90,11 @@ class NIFLoader static MeshPairList load(const std::string &name, const std::string &group); public: + static EntityList createEntities(Ogre::Entity *parent, const std::string &bonename, + Ogre::SceneManager *sceneMgr, + const std::string &name, + const std::string &group="General"); + static EntityList createEntities(Ogre::SceneNode *parent, const std::string &name, const std::string &group="General"); From 3b29d280b91c31113cde0860722fc4484769a164 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 12:47:16 -0700 Subject: [PATCH 162/298] Filter out skinned shapes that don't match the bone name --- components/nifogre/ogre_nif_loader.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 44992eea4..d72c45abd 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -926,12 +926,23 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo if(meshes.size() == 0) return entitylist; + std::string filter = "Tri "+bonename; for(size_t i = 0;i < meshes.size();i++) { - entitylist.mEntities.push_back(sceneMgr->createEntity(meshes[i].first->getName())); - Ogre::Entity *entity = entitylist.mEntities.back(); - if(!entitylist.mSkelBase && entity->hasSkeleton()) - entitylist.mSkelBase = entity; + Ogre::Entity *ent = sceneMgr->createEntity(meshes[i].first->getName()); + if(ent->hasSkeleton()) + { + if(meshes[i].second.length() < filter.length() || filter.compare(meshes[i].second) != 0) + { + sceneMgr->destroyEntity(ent); + meshes.erase(meshes.begin()+i); + i--; + continue; + } + if(!entitylist.mSkelBase) + entitylist.mSkelBase = ent; + } + entitylist.mEntities.push_back(ent); } if(entitylist.mSkelBase) @@ -946,7 +957,7 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo parent->attachObjectToBone(bonename, entity); } else if(entity != entitylist.mSkelBase) - entitylist.mSkelBase->attachObjectToBone(meshes[i].second, entity); + parent->attachObjectToBone(bonename, entity); } } else From e8ff304562ff6d87bd9781c3a6a6e6b1fd07963f Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 12:53:45 -0700 Subject: [PATCH 163/298] Fix the initial normal vector for vertex fixups --- components/nifogre/ogre_nif_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index d72c45abd..23e1e99d5 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -540,7 +540,7 @@ class NIFMeshLoader : Ogre::ManualResourceLoader // better to transform the bones into bind position, but there doesn't seem to // be a reliable way to do that. std::vector newVerts(srcVerts.size(), Ogre::Vector3(0.0f)); - std::vector newNorms(srcNorms.size(), Ogre::Vector3(1.0f)); + std::vector newNorms(srcNorms.size(), Ogre::Vector3(0.0f)); const Nif::NiSkinData *data = skin->data.getPtr(); const Nif::NodeList &bones = skin->bones; From 2890904fb543c8555578fcb2e486d9812279a332 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 15:27:33 -0700 Subject: [PATCH 164/298] Use lowercase names for the mesh and skeleton resources To reduce the risk of duplicates due to different capitalizations. --- components/nifogre/ogre_nif_loader.cpp | 5 ++++- components/nifogre/ogre_nif_loader.hpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 23e1e99d5..6e2aa1663 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -807,6 +807,7 @@ public: Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); std::string fullname = mName+"@"+shape->name; + std::transform(fullname.begin(), fullname.end(), fullname.begin(), ::tolower); Ogre::MeshPtr mesh = meshMgr.getByName(fullname); if(mesh.isNull()) { @@ -842,10 +843,12 @@ public: NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders; -MeshPairList NIFLoader::load(const std::string &name, const std::string &group) +MeshPairList NIFLoader::load(std::string name, const std::string &group) { MeshPairList meshes; + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + Nif::NIFFile nif(name); if (nif.numRecords() < 1) { diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 2348a9b10..b0b1ce27b 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -87,7 +87,7 @@ typedef std::vector< std::pair > MeshPairList; */ class NIFLoader { - static MeshPairList load(const std::string &name, const std::string &group); + static MeshPairList load(std::string name, const std::string &group); public: static EntityList createEntities(Ogre::Entity *parent, const std::string &bonename, From 04b244cf9e63225e69ed707d44b9a71746471a6c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 17:18:21 -0700 Subject: [PATCH 165/298] Use the mesh's skeleton to transform shapes into "bind pose" instead of the NIF nodes --- components/nifogre/ogre_nif_loader.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 6e2aa1663..9fd3d44f1 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -526,6 +526,7 @@ class NIFMeshLoader : Ogre::ManualResourceLoader // Convert NiTriShape to Ogre::SubMesh void handleNiTriShape(Ogre::Mesh *mesh, Nif::NiTriShape *shape) { + Ogre::SkeletonPtr skel; const Nif::NiTriShapeData *data = shape->data.getPtr(); const Nif::NiSkinInstance *skin = (shape->skin.empty() ? NULL : shape->skin.getPtr()); std::vector srcVerts = data->vertices; @@ -536,6 +537,11 @@ class NIFMeshLoader : Ogre::ManualResourceLoader // explicitly attached later. mesh->setSkeletonName(mName); + // Get the skeleton resource, so vertices can be transformed into the bones' initial state. + Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); + skel = skelMgr->getByName(mName); + skel->touch(); + // Convert vertices and normals to bone space from bind position. It would be // better to transform the bones into bind position, but there doesn't seem to // be a reliable way to do that. @@ -546,10 +552,13 @@ class NIFMeshLoader : Ogre::ManualResourceLoader const Nif::NodeList &bones = skin->bones; for(size_t b = 0;b < bones.length();b++) { - Ogre::Matrix4 mat(Ogre::Matrix4::IDENTITY); + Ogre::Bone *bone = skel->getBone(bones[b]->name); + Ogre::Matrix4 mat, mat2; mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale), Ogre::Quaternion(data->bones[b].trafo.rotation)); - mat = bones[b]->getWorldTransform() * mat; + mat2.makeTransform(bone->_getDerivedPosition(), bone->_getDerivedScale(), + bone->_getDerivedOrientation()); + mat = mat2 * mat; const std::vector &weights = data->bones[b].weights; for(size_t i = 0;i < weights.size();i++) @@ -692,11 +701,6 @@ class NIFMeshLoader : Ogre::ManualResourceLoader // Assign bone weights for this TriShape if(skin != NULL) { - // Get the skeleton resource, so weights can be applied - Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); - Ogre::SkeletonPtr skel = skelMgr->getByName(mesh->getSkeletonName()); - skel->touch(); - const Nif::NiSkinData *data = skin->data.getPtr(); const Nif::NodeList &bones = skin->bones; for(size_t i = 0;i < bones.length();i++) From 626dcd54dc1da12647be1316a6010fe30e689da3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 17:26:51 -0700 Subject: [PATCH 166/298] Store the skeleton name with the mesh resource loader instead of a flag --- components/nifogre/ogre_nif_loader.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 9fd3d44f1..c4e1398f9 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -509,7 +509,7 @@ class NIFMeshLoader : Ogre::ManualResourceLoader std::string mGroup; std::string mShapeName; std::string mMaterialName; - bool mHasSkel; + std::string mSkelName; void warn(const std::string &msg) { @@ -535,7 +535,7 @@ class NIFMeshLoader : Ogre::ManualResourceLoader { // Only set a skeleton when skinning. Unskinned meshes with a skeleton will be // explicitly attached later. - mesh->setSkeletonName(mName); + mesh->setSkeletonName(mSkelName); // Get the skeleton resource, so vertices can be transformed into the bones' initial state. Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); @@ -579,7 +579,7 @@ class NIFMeshLoader : Ogre::ManualResourceLoader srcVerts = newVerts; srcNorms = newNorms; } - else if(!mHasSkel) + else if(mSkelName.length() == 0) { // No skinning and no skeleton, so just transform the vertices and // normals into position. @@ -752,10 +752,9 @@ class NIFMeshLoader : Ogre::ManualResourceLoader public: NIFMeshLoader() - : mHasSkel(false) { } - NIFMeshLoader(const std::string &name, const std::string &group, bool hasSkel) - : mName(name), mGroup(group), mHasSkel(hasSkel) + NIFMeshLoader(const std::string &name, const std::string &group, const std::string skelName) + : mName(name), mGroup(group), mSkelName(skelName) { } virtual void loadResource(Ogre::Resource *resource) @@ -765,8 +764,8 @@ public: if(!mShapeName.length()) { - if(mHasSkel) - mesh->setSkeletonName(mName); + if(mSkelName.length() > 0) + mesh->setSkeletonName(mSkelName); return; } @@ -874,7 +873,7 @@ MeshPairList NIFLoader::load(std::string name, const std::string &group) bool hasSkel = NIFSkeletonLoader::createSkeleton(name, group, node); - NIFMeshLoader meshldr(name, group, hasSkel); + NIFMeshLoader meshldr(name, group, (hasSkel ? name : std::string())); meshldr.createMeshes(node, meshes); return meshes; From 5154188110d7bcf223c4c73198b370de19d556bc Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 18:29:25 -0700 Subject: [PATCH 167/298] Allow specifying an alternate skeleton for mesh skinning --- components/nifogre/ogre_nif_loader.cpp | 18 +++++++++++------- components/nifogre/ogre_nif_loader.hpp | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index c4e1398f9..8e73c2503 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -539,7 +539,7 @@ class NIFMeshLoader : Ogre::ManualResourceLoader // Get the skeleton resource, so vertices can be transformed into the bones' initial state. Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); - skel = skelMgr->getByName(mName); + skel = skelMgr->getByName(mSkelName); skel->touch(); // Convert vertices and normals to bone space from bind position. It would be @@ -808,7 +808,9 @@ public: const NiTriShape *shape = dynamic_cast(node); Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); - std::string fullname = mName+"@"+shape->name; + std::string fullname = mName+"@shape="+shape->name; + if(mSkelName.length() > 0 && mName != mSkelName) + fullname += "@skel="+mSkelName; std::transform(fullname.begin(), fullname.end(), fullname.begin(), ::tolower); Ogre::MeshPtr mesh = meshMgr.getByName(fullname); @@ -846,11 +848,12 @@ public: NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders; -MeshPairList NIFLoader::load(std::string name, const std::string &group) +MeshPairList NIFLoader::load(std::string name, std::string skelName, const std::string &group) { MeshPairList meshes; std::transform(name.begin(), name.end(), name.begin(), ::tolower); + std::transform(skelName.begin(), skelName.end(), skelName.begin(), ::tolower); Nif::NIFFile nif(name); if (nif.numRecords() < 1) @@ -873,7 +876,7 @@ MeshPairList NIFLoader::load(std::string name, const std::string &group) bool hasSkel = NIFSkeletonLoader::createSkeleton(name, group, node); - NIFMeshLoader meshldr(name, group, (hasSkel ? name : std::string())); + NIFMeshLoader meshldr(name, group, (hasSkel ? skelName : std::string())); meshldr.createMeshes(node, meshes); return meshes; @@ -883,7 +886,7 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string { EntityList entitylist; - MeshPairList meshes = load(name, group); + MeshPairList meshes = load(name, name, group); if(meshes.size() == 0) return entitylist; @@ -928,7 +931,7 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo { EntityList entitylist; - MeshPairList meshes = load(name, group); + MeshPairList meshes = load(name, parent->getMesh()->getSkeletonName(), group); if(meshes.size() == 0) return entitylist; @@ -953,13 +956,14 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo if(entitylist.mSkelBase) { + entitylist.mSkelBase->shareSkeletonInstanceWith(parent); parent->attachObjectToBone(bonename, entitylist.mSkelBase); for(size_t i = 0;i < entitylist.mEntities.size();i++) { Ogre::Entity *entity = entitylist.mEntities[i]; if(entity != entitylist.mSkelBase && entity->hasSkeleton()) { - entity->shareSkeletonInstanceWith(entitylist.mSkelBase); + entity->shareSkeletonInstanceWith(parent); parent->attachObjectToBone(bonename, entity); } else if(entity != entitylist.mSkelBase) diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index b0b1ce27b..49d19fdb8 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -87,7 +87,7 @@ typedef std::vector< std::pair > MeshPairList; */ class NIFLoader { - static MeshPairList load(std::string name, const std::string &group); + static MeshPairList load(std::string name, std::string skelName, const std::string &group); public: static EntityList createEntities(Ogre::Entity *parent, const std::string &bonename, From d9b64b77ec170935744d57db669d354c9a39dba5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 18:38:55 -0700 Subject: [PATCH 168/298] Attach skinned parts to the scene node instead of the named bone --- apps/openmw/mwrender/npcanimation.cpp | 2 +- components/nifogre/ogre_nif_loader.cpp | 7 ++++--- components/nifogre/ogre_nif_loader.hpp | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 991ef827e..00583d427 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -342,7 +342,7 @@ void NpcAnimation::updateParts() NifOgre::EntityList NpcAnimation::insertBoundedPart(const std::string &mesh, const std::string &bonename) { NifOgre::EntityList entities = NIFLoader::createEntities(mEntityList.mSkelBase, bonename, - mInsert->getCreator(), mesh); + mInsert, mesh); std::vector &parts = entities.mEntities; for(size_t i = 0;i < parts.size();i++) parts[i]->setVisibilityFlags(RV_Actors); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 8e73c2503..646ec6c0b 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -925,7 +925,7 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string } EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bonename, - Ogre::SceneManager *sceneMgr, + Ogre::SceneNode *parentNode, const std::string &name, const std::string &group) { @@ -935,6 +935,7 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo if(meshes.size() == 0) return entitylist; + Ogre::SceneManager *sceneMgr = parentNode->getCreator(); std::string filter = "Tri "+bonename; for(size_t i = 0;i < meshes.size();i++) { @@ -957,14 +958,14 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo if(entitylist.mSkelBase) { entitylist.mSkelBase->shareSkeletonInstanceWith(parent); - parent->attachObjectToBone(bonename, entitylist.mSkelBase); + parentNode->attachObject(entitylist.mSkelBase); for(size_t i = 0;i < entitylist.mEntities.size();i++) { Ogre::Entity *entity = entitylist.mEntities[i]; if(entity != entitylist.mSkelBase && entity->hasSkeleton()) { entity->shareSkeletonInstanceWith(parent); - parent->attachObjectToBone(bonename, entity); + parentNode->attachObject(entity); } else if(entity != entitylist.mSkelBase) parent->attachObjectToBone(bonename, entity); diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 49d19fdb8..76e94975c 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -91,7 +91,7 @@ class NIFLoader public: static EntityList createEntities(Ogre::Entity *parent, const std::string &bonename, - Ogre::SceneManager *sceneMgr, + Ogre::SceneNode *parentNode, const std::string &name, const std::string &group="General"); From 4af1bce659722bd06c89a492bfb291f231891d06 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 20:14:23 -0700 Subject: [PATCH 169/298] Restore and fix some missing parts --- apps/openmw/mwrender/npcanimation.cpp | 16 ++++++++++++++++ apps/openmw/mwrender/npcanimation.hpp | 4 ++++ components/nifogre/ogre_nif_loader.cpp | 3 ++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 00583d427..62e9fd580 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -20,7 +20,10 @@ NpcAnimation::~NpcAnimation() removeEntities(head); removeEntities(hair); removeEntities(neck); + removeEntities(chest); removeEntities(groin); + removeEntities(rHand); + removeEntities(lHand); removeEntities(rWrist); removeEntities(lWrist); removeEntities(rForearm); @@ -37,6 +40,7 @@ NpcAnimation::~NpcAnimation() removeEntities(lUpperLeg); removeEntities(rclavicle); removeEntities(lclavicle); + removeEntities(tail); } @@ -406,8 +410,14 @@ void NpcAnimation::removeIndividualPart(int type) removeEntities(hair); else if(type == ESM::PRT_Neck) //2 removeEntities(neck); + else if(type == ESM::PRT_Cuirass)//3 + removeEntities(chest); else if(type == ESM::PRT_Groin)//4 removeEntities(groin); + else if(type == ESM::PRT_RHand)//6 + removeEntities(rHand); + else if(type == ESM::PRT_LHand)//7 + removeEntities(lHand); else if(type == ESM::PRT_RWrist)//8 removeEntities(rWrist); else if(type == ESM::PRT_LWrist) //9 @@ -446,6 +456,8 @@ void NpcAnimation::removeIndividualPart(int type) else if(type == ESM::PRT_Weapon) //25 { } + else if(type == ESM::PRT_Tail) //26 + removeEntities(tail); } void NpcAnimation::reserveIndividualPart(int type, int group, int priority) @@ -487,6 +499,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, neck = insertBoundedPart(mesh, "Neck"); break; case ESM::PRT_Cuirass: //3 + chest = insertBoundedPart(mesh, "Chest"); break; case ESM::PRT_Groin: //4 groin = insertBoundedPart(mesh, "Groin"); @@ -494,8 +507,10 @@ bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, case ESM::PRT_Skirt: //5 break; case ESM::PRT_RHand: //6 + rHand = insertBoundedPart(mesh, "Right Hand"); break; case ESM::PRT_LHand: //7 + lHand = insertBoundedPart(mesh, "Left Hand"); break; case ESM::PRT_RWrist: //8 rWrist = insertBoundedPart(mesh, "Right Wrist"); @@ -550,6 +565,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, case ESM::PRT_Weapon: //25 break; case ESM::PRT_Tail: //26 + tail = insertBoundedPart(mesh, "Tail"); break; } return true; diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 1eec1294f..9dda169e2 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -38,7 +38,11 @@ private: NifOgre::EntityList lfoot; NifOgre::EntityList rfoot; NifOgre::EntityList hair; + NifOgre::EntityList rHand; + NifOgre::EntityList lHand; NifOgre::EntityList head; + NifOgre::EntityList chest; + NifOgre::EntityList tail; bool isBeast; bool isFemale; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 646ec6c0b..e8bc6133a 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -942,7 +942,8 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo Ogre::Entity *ent = sceneMgr->createEntity(meshes[i].first->getName()); if(ent->hasSkeleton()) { - if(meshes[i].second.length() < filter.length() || filter.compare(meshes[i].second) != 0) + if(meshes[i].second.length() < filter.length() || + meshes[i].second.compare(0, filter.length(), filter) != 0) { sceneMgr->destroyEntity(ent); meshes.erase(meshes.begin()+i); From bd74ab027a16801da7db778f62c9f4a898517cda Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 21:21:49 -0700 Subject: [PATCH 170/298] Mirror left-sided parts --- components/nifogre/ogre_nif_loader.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index e8bc6133a..c470363b0 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -956,6 +957,10 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo entitylist.mEntities.push_back(ent); } + Ogre::Vector3 scale(1.0f); + if(bonename.find("Left") != std::string::npos) + scale.x *= -1.0f; + if(entitylist.mSkelBase) { entitylist.mSkelBase->shareSkeletonInstanceWith(parent); @@ -969,13 +974,19 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo parentNode->attachObject(entity); } else if(entity != entitylist.mSkelBase) - parent->attachObjectToBone(bonename, entity); + { + Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entity); + tag->setScale(scale); + } } } else { for(size_t i = 0;i < entitylist.mEntities.size();i++) - parent->attachObjectToBone(bonename, entitylist.mEntities[i]); + { + Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entitylist.mEntities[i]); + tag->setScale(scale); + } } return entitylist; From b505d4ace0977522aab257b86caca4cad1d320af Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 21:39:50 -0700 Subject: [PATCH 171/298] Fix feet and entity part detachment --- apps/openmw/mwrender/npcanimation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 62e9fd580..138a8eb86 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -391,7 +391,7 @@ void NpcAnimation::removeEntities(NifOgre::EntityList &entities) Ogre::SceneManager *sceneMgr = mInsert->getCreator(); for(size_t i = 0;i < entities.mEntities.size();i++) { - mEntityList.mSkelBase->detachObjectFromBone(entities.mEntities[i]); + entities.mEntities[i]->detachFromParent(); sceneMgr->destroyEntity(entities.mEntities[i]); } entities.mEntities.clear(); @@ -533,10 +533,10 @@ bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, lupperArm = insertBoundedPart(mesh, "Left Upper Arm"); break; case ESM::PRT_RFoot: //15 - lupperArm = insertBoundedPart(mesh, "Right Foot"); + rfoot = insertBoundedPart(mesh, "Right Foot"); break; case ESM::PRT_LFoot: //16 - lupperArm = insertBoundedPart(mesh, "Left Foot"); + lfoot = insertBoundedPart(mesh, "Left Foot"); break; case ESM::PRT_RAnkle: //17 rAnkle = insertBoundedPart(mesh, "Right Ankle"); From c9b1f72d81b00f9e809b278885795d5300557a16 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 22:31:07 -0700 Subject: [PATCH 172/298] Use a case-insensitive compare for the part filter --- components/nifogre/ogre_nif_loader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index c470363b0..639ebaa12 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -944,7 +944,8 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo if(ent->hasSkeleton()) { if(meshes[i].second.length() < filter.length() || - meshes[i].second.compare(0, filter.length(), filter) != 0) + !boost::algorithm::lexicographical_compare(meshes[i].second.substr(0, filter.length()), + filter, boost::algorithm::is_iequal())) { sceneMgr->destroyEntity(ent); meshes.erase(meshes.begin()+i); From 6caa39629d85f22320eff0abd5def679a043a0c4 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 18 Jul 2012 22:32:26 -0700 Subject: [PATCH 173/298] Reimplement the skirt part --- apps/openmw/mwrender/npcanimation.cpp | 4 ++++ apps/openmw/mwrender/npcanimation.hpp | 1 + 2 files changed, 5 insertions(+) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 138a8eb86..9f28dbf13 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -22,6 +22,7 @@ NpcAnimation::~NpcAnimation() removeEntities(neck); removeEntities(chest); removeEntities(groin); + removeEntities(skirt); removeEntities(rHand); removeEntities(lHand); removeEntities(rWrist); @@ -414,6 +415,8 @@ void NpcAnimation::removeIndividualPart(int type) removeEntities(chest); else if(type == ESM::PRT_Groin)//4 removeEntities(groin); + else if(type == ESM::PRT_Skirt)//5 + removeEntities(skirt); else if(type == ESM::PRT_RHand)//6 removeEntities(rHand); else if(type == ESM::PRT_LHand)//7 @@ -505,6 +508,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, groin = insertBoundedPart(mesh, "Groin"); break; case ESM::PRT_Skirt: //5 + skirt = insertBoundedPart(mesh, "Groin"); break; case ESM::PRT_RHand: //6 rHand = insertBoundedPart(mesh, "Right Hand"); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 9dda169e2..d4b2a5b9e 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -35,6 +35,7 @@ private: NifOgre::EntityList rAnkle; NifOgre::EntityList lAnkle; NifOgre::EntityList groin; + NifOgre::EntityList skirt; NifOgre::EntityList lfoot; NifOgre::EntityList rfoot; NifOgre::EntityList hair; From 739455e6f81c53f16e6cde4087996fac862775cf Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jul 2012 16:23:30 +0200 Subject: [PATCH 174/298] new water WIP, caustics, chromatic abberation, accurate fresnel, underwater reflection, etc --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/main.cpp | 6 +- apps/openmw/mwrender/renderconst.hpp | 10 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/terrain.cpp | 38 +--- apps/openmw/mwrender/terrainmaterial.cpp | 6 + apps/openmw/mwrender/water.cpp | 92 +++++---- apps/openmw/mwrender/water.hpp | 8 +- extern/shiny | 2 +- files/gbuffer/gbuffer.compositor | 4 +- files/materials/caustics.h | 117 +++++++++++ files/materials/core.h | 2 +- files/materials/objects.mat | 5 + files/materials/objects.shader | 52 ++++- files/materials/terrain.shader | 46 ++++- files/materials/water.mat | 13 +- files/materials/water.shader | 228 ++++++++++++++++++++++ files/materials/water.shaderset | 15 ++ files/water/underwater_dome.mesh | Bin 0 -> 22585 bytes files/water/water.compositor | 1 - files/water/water.material | 2 +- files/water/water_nm.png | Bin 0 -> 24405 bytes 22 files changed, 554 insertions(+), 96 deletions(-) create mode 100644 files/materials/caustics.h create mode 100644 files/water/underwater_dome.mesh create mode 100644 files/water/water_nm.png diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 32faafcc8..8e424ad56 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -102,6 +102,7 @@ target_link_libraries(openmw ${MYGUI_PLATFORM_LIBRARIES} "shiny" "shiny.OgrePlatform" + components ) # Fix for not visible pthreads functions for linker with glibc 2.15 diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 993ec6623..86584cc22 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -261,7 +261,7 @@ int main(int argc, char**argv) boost::filesystem::current_path(bundlePath); #endif - try + //try { Files::ConfigurationManager cfgMgr; OMW::Engine engine(cfgMgr); @@ -271,11 +271,11 @@ int main(int argc, char**argv) engine.go(); } } - catch (std::exception &e) + /*catch (std::exception &e) { std::cout << "\nERROR: " << e.what() << std::endl; return 1; - } + }*/ return 0; } diff --git a/apps/openmw/mwrender/renderconst.hpp b/apps/openmw/mwrender/renderconst.hpp index c4aa093c0..457b6e601 100644 --- a/apps/openmw/mwrender/renderconst.hpp +++ b/apps/openmw/mwrender/renderconst.hpp @@ -14,13 +14,13 @@ enum RenderQueueGroups RQG_Main = Ogre::RENDER_QUEUE_MAIN, - RQG_Water = Ogre::RENDER_QUEUE_7+1, + RQG_Alpha = Ogre::RENDER_QUEUE_MAIN+1, - RQG_Alpha = Ogre::RENDER_QUEUE_MAIN, + RQG_OcclusionQuery = Ogre::RENDER_QUEUE_6, - RQG_UnderWater = Ogre::RENDER_QUEUE_7+1, + RQG_UnderWater = Ogre::RENDER_QUEUE_7, - RQG_OcclusionQuery = Ogre::RENDER_QUEUE_8, + RQG_Water = Ogre::RENDER_QUEUE_7+1, // Sky late (sun & sun flare) RQG_SkiesLate = Ogre::RENDER_QUEUE_SKIES_LATE @@ -54,7 +54,7 @@ enum VisibilityFlags RV_OcclusionQuery = 256, - RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water, + RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water /// \todo markers (normally hidden) }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 15660ade5..916ccb0c0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -264,7 +264,7 @@ void RenderingManager::update (float duration){ checkUnderwater(); - mWater->update(); + mWater->update(duration); } void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store){ if(store->cell->data.flags & store->cell->HasWater diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index b1d9dee12..691e7c4af 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -44,7 +44,6 @@ namespace MWRender mTerrainGlobals->setMaxPixelError(8); mTerrainGlobals->setLayerBlendMapSize(32); - mTerrainGlobals->setDefaultGlobalColourMapSize(65); //10 (default) didn't seem to be quite enough mTerrainGlobals->setSkirtSize(128); @@ -53,28 +52,6 @@ namespace MWRender //this seemed the distance where it wasn't too noticeable mTerrainGlobals->setCompositeMapDistance(mWorldSize*2); - /* - mActiveProfile->setLightmapEnabled(false); - mActiveProfile->setLayerSpecularMappingEnabled(false); - mActiveProfile->setLayerNormalMappingEnabled(false); - mActiveProfile->setLayerParallaxMappingEnabled(false); - - bool shadows = Settings::Manager::getBool("enabled", "Shadows"); - mActiveProfile->setReceiveDynamicShadowsEnabled(shadows); - mActiveProfile->setReceiveDynamicShadowsDepth(shadows); - if (Settings::Manager::getBool("split", "Shadows")) - mActiveProfile->setReceiveDynamicShadowsPSSM(mRendering->getShadows()->getPSSMSetup()); - else - mActiveProfile->setReceiveDynamicShadowsPSSM(0); - - mActiveProfile->setShadowFar(mRendering->getShadows()->getShadowFar()); - mActiveProfile->setShadowFadeStart(mRendering->getShadows()->getFadeStart()); - - //composite maps lead to a drastic increase in loading time so are - //disabled - mActiveProfile->setCompositeMapEnabled(false); - */ - mTerrainGroup.setOrigin(Vector3(mWorldSize/2, 0, -mWorldSize/2)); @@ -181,10 +158,9 @@ namespace MWRender terrain->setVisibilityFlags(RV_Terrain); terrain->setRenderQueueGroup(RQG_Main); + // disable or enable global colour map (depends on available vertex colours) if ( land->landData->usingColours ) { - // disable or enable global colour map (depends on available vertex colours) - //mActiveProfile->setGlobalColourMapEnabled(true); TexturePtr vertex = getVertexColours(land, cellX, cellY, x*(mLandSize-1), @@ -193,17 +169,9 @@ namespace MWRender mActiveProfile->setGlobalColourMapEnabled(true); mActiveProfile->setGlobalColourMap (terrain, vertex->getName()); - - //this is a hack to get around the fact that Ogre seems to - //corrupt the global colour map leading to rendering errors - //MaterialPtr mat = terrain->getMaterial(); - /// \todo - //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); - - - //mat = terrain->_getCompositeMapMaterial(); - //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); } + else + mActiveProfile->setGlobalColourMapEnabled (false); } } } diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index 89d895358..1a2d96a14 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -117,6 +117,10 @@ namespace MWRender shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); } + // caustics + sh::MaterialInstanceTextureUnit* caustics = p->createTextureUnit ("causticMap"); + caustics->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("water_nm.png"))); + p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty(new sh::StringValue( Ogre::StringConverter::toString(numBlendTextures + numLayers + 2)))); @@ -149,6 +153,8 @@ namespace MWRender --freeTextureUnits; freeTextureUnits -= 3; // shadow PSSM + --freeTextureUnits; // caustics + // each layer needs 1.25 units (1xdiffusespec, 0.25xblend) return static_cast(freeTextureUnits / (1.25f)); } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b8642a754..90967c886 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -8,11 +8,16 @@ #include #include #include +#include +#include +#include #include "sky.hpp" #include "renderingmanager.hpp" #include "compositors.hpp" +#include + using namespace Ogre; namespace MWRender @@ -22,10 +27,16 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mCamera (camera), mSceneManager (camera->getSceneManager()), mIsUnderwater(false), mVisibilityFlags(0), mReflectionTarget(0), mActive(1), mToggled(1), - mReflectionRenderActive(false), mRendering(rend) + mReflectionRenderActive(false), mRendering(rend), + mOldFarClip(0), + mWaterTimer(0.f) { mSky = rend->getSkyManager(); + sh::Factory::getInstance ().setSharedParameter ("windDir_windSpeed", sh::makeProperty(new sh::Vector3(0.5, -0.8, 0.2))); + sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(0))); + sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(1, 0.6))); + mTop = cell->water; mIsUnderwater = false; @@ -40,7 +51,6 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mWater->setCastShadows(false); mWaterNode = mSceneManager->getRootSceneNode()->createChildSceneNode(); - mWaterNode->setPosition(0, mTop, 0); mReflectionCamera = mSceneManager->createCamera("ReflectionCamera"); @@ -59,21 +69,30 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mUnderwaterEffect = Settings::Manager::getBool("underwater effect", "Water"); + Ogre::Entity* underwaterDome = mSceneManager->createEntity ("underwater_dome.mesh"); + underwaterDome->setRenderQueueGroup (RQG_UnderWater); + mUnderwaterDome = mSceneManager->getRootSceneNode ()->createChildSceneNode (); + mUnderwaterDome->attachObject (underwaterDome); + mUnderwaterDome->setScale(100,100,100); + mUnderwaterDome->setVisible(false); + mSceneManager->addRenderQueueListener(this); assignTextures(); + setHeight(mTop); + // ---------------------------------------------------------------------------------------------- // ---------------------------------- reflection debug overlay ---------------------------------- // ---------------------------------------------------------------------------------------------- - /* +/* if (Settings::Manager::getBool("shader", "Water")) { OverlayManager& mgr = OverlayManager::getSingleton(); Overlay* overlay; // destroy if already exists - if (overlay = mgr.getByName("ReflectionDebugOverlay")) + if ((overlay = mgr.getByName("ReflectionDebugOverlay"))) mgr.destroy(overlay); overlay = mgr.create("ReflectionDebugOverlay"); @@ -85,18 +104,17 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false); - TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(tex->getName()); - t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(mReflectionTexture->getName()); OverlayContainer* debugPanel; // destroy container if exists try { - if (debugPanel = + if ((debugPanel = static_cast( mgr.getOverlayElement("Ogre/ReflectionDebugTexPanel" - ))) + )))) mgr.destroyOverlayElement(debugPanel); } catch (Ogre::Exception&) {} @@ -110,7 +128,7 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel overlay->add2D(debugPanel); overlay->show(); } - */ +*/ } void Water::setActive(bool active) @@ -146,6 +164,7 @@ void Water::setHeight(const float height) mTop = height; mWaterPlane = Plane(Vector3::UNIT_Y, height); mWaterNode->setPosition(0, height, 0); + sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty(new sh::FloatValue(height))); } void Water::toggle() @@ -164,13 +183,15 @@ void Water::checkUnderwater(float y) if ((mIsUnderwater && y > mTop) || !mWater->isVisible() || mCamera->getPolygonMode() != Ogre::PM_SOLID) { - mRendering->getCompositors()->setCompositorEnabled(mCompositorName, false); + //mRendering->getCompositors()->setCompositorEnabled(mCompositorName, false); // tell the shader we are not underwater + +/* Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); if (pass->hasFragmentProgram() && pass->getFragmentProgramParameters()->_findNamedConstantDefinition("isUnderwater", false)) pass->getFragmentProgramParameters()->setNamedConstant("isUnderwater", Real(0)); - +*/ mWater->setRenderQueueGroup(RQG_Water); mIsUnderwater = false; @@ -178,15 +199,16 @@ void Water::checkUnderwater(float y) if (!mIsUnderwater && y < mTop && mWater->isVisible() && mCamera->getPolygonMode() == Ogre::PM_SOLID) { - if (mUnderwaterEffect) - mRendering->getCompositors()->setCompositorEnabled(mCompositorName, true); + //if (mUnderwaterEffect) + //mRendering->getCompositors()->setCompositorEnabled(mCompositorName, true); // tell the shader we are underwater +/* Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); if (pass->hasFragmentProgram() && pass->getFragmentProgramParameters()->_findNamedConstantDefinition("isUnderwater", false)) pass->getFragmentProgramParameters()->setNamedConstant("isUnderwater", Real(1)); - - mWater->setRenderQueueGroup(RQG_UnderWater); +*/ + //mWater->setRenderQueueGroup(RQG_UnderWater); mIsUnderwater = true; } @@ -211,12 +233,11 @@ void Water::preRenderTargetUpdate(const RenderTargetEvent& evt) mReflectionCamera->setFOVy(mCamera->getFOVy()); mReflectionRenderActive = true; - /// \todo For some reason this camera is delayed for 1 frame, which causes ugly sky reflection behaviour.. - /// to circumvent this we just scale the sky up, so it's not that noticable + /// \todo the reflection render (and probably all renderingmanager-updates) lag behind 1 camera frame for some reason Vector3 pos = mCamera->getRealPosition(); pos.y = mTop*2 - pos.y; mSky->setSkyPosition(pos); - mSky->scaleSky(mCamera->getFarClipDistance() / 5000.f); + mSky->scaleSky(mCamera->getFarClipDistance() / 50.f); mReflectionCamera->enableReflection(mWaterPlane); } } @@ -242,7 +263,7 @@ void Water::createMaterial() else { mMaterial = MaterialManager::getSingleton().getByName("Water"); - mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTexture(mReflectionTexture); + sh::Factory::getInstance ().setTextureAlias ("WaterReflection", mReflectionTexture->getName()); } // these have to be set in code @@ -251,30 +272,23 @@ void Water::createMaterial() { textureNames[i] = "textures\\water\\water" + StringConverter::toString(i, 2, '0') + ".dds"; } - Ogre::Technique* tech; - if (mReflectionTarget == 0) - tech = mMaterial->getTechnique(0); - else - tech = mMaterial->getTechnique(1); - tech->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); + if (mReflectionTarget == 0) + mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); } void Water::assignTextures() { if (Settings::Manager::getBool("shader", "Water")) { + CompositorInstance* compositor = CompositorManager::getSingleton().getCompositorChain(mRendering->getViewport())->getCompositor("gbuffer"); TexturePtr colorTexture = compositor->getTextureInstance("mrt_output", 0); - TextureUnitState* tus = mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState("refractionMap"); - if (tus != 0) - tus->setTexture(colorTexture); + sh::Factory::getInstance ().setTextureAlias ("WaterRefraction", colorTexture->getName()); TexturePtr depthTexture = compositor->getTextureInstance("mrt_output", 1); - tus = mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState("depthMap"); - if (tus != 0) - tus->setTexture(depthTexture); + sh::Factory::getInstance ().setTextureAlias ("SceneDepth", depthTexture->getName()); } } @@ -288,7 +302,7 @@ void Water::updateVisible() { mWater->setVisible(mToggled && mActive); if (mReflectionTarget) - mReflectionTarget->setActive(mToggled && mActive && !mIsUnderwater); + mReflectionTarget->setActive(mToggled && mActive); } void Water::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation) @@ -296,7 +310,9 @@ void Water::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &in // We don't want the sky to get clipped by custom near clip plane (the water plane) if (queueGroupId < 20 && mReflectionRenderActive) { + mOldFarClip = mReflectionCamera->getFarClipDistance (); mReflectionCamera->disableCustomNearClipPlane(); + mReflectionCamera->setFarClipDistance (1000000000); Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mReflectionCamera->getProjectionMatrixRS()); } } @@ -305,13 +321,21 @@ void Water::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invo { if (queueGroupId < 20 && mReflectionRenderActive) { - mReflectionCamera->enableCustomNearClipPlane(mWaterPlane); + mReflectionCamera->setFarClipDistance (mOldFarClip); + if (!mIsUnderwater) + mReflectionCamera->enableCustomNearClipPlane(mWaterPlane); Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mReflectionCamera->getProjectionMatrixRS()); } } -void Water::update() +void Water::update(float dt) { + Ogre::Vector3 pos = mCamera->getDerivedPosition (); + pos.y = -mWaterPlane.d; + mUnderwaterDome->setPosition (pos); + + mWaterTimer += dt; + sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(mWaterTimer))); } void Water::applyRTT() diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 71f261f2b..ac837ca0d 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -39,11 +39,17 @@ namespace MWRender { Ogre::SceneNode *mWaterNode; Ogre::Entity *mWater; + Ogre::SceneNode* mUnderwaterDome; + bool mIsUnderwater; bool mActive; bool mToggled; int mTop; + int mOldFarClip; + + float mWaterTimer; + bool mReflectionRenderActive; Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY); @@ -83,7 +89,7 @@ namespace MWRender { void setActive(bool active); void toggle(); - void update(); + void update(float dt); void assignTextures(); diff --git a/extern/shiny b/extern/shiny index 5a9bda601..bf003238a 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 5a9bda6010413555736479ef03103f764fecb91d +Subproject commit bf003238a27d94be43724e6774d25c38b4d578c8 diff --git a/files/gbuffer/gbuffer.compositor b/files/gbuffer/gbuffer.compositor index 6ca35df87..19b890fec 100644 --- a/files/gbuffer/gbuffer.compositor +++ b/files/gbuffer/gbuffer.compositor @@ -18,7 +18,7 @@ compositor gbuffer { // Renders everything except water first_render_queue 0 - last_render_queue 70 + last_render_queue 50 } } @@ -66,7 +66,7 @@ compositor gbufferFinalizer } pass render_scene { - first_render_queue 71 + first_render_queue 51 last_render_queue 100 } } diff --git a/files/materials/caustics.h b/files/materials/caustics.h new file mode 100644 index 000000000..d6be680f0 --- /dev/null +++ b/files/materials/caustics.h @@ -0,0 +1,117 @@ +#define VISIBILITY 1500.0 // how far you can look through water + +#define BIG_WAVES_X 0.3 // strength of big waves +#define BIG_WAVES_Y 0.3 + +#define MID_WAVES_X 0.3 // strength of middle sized waves +#define MID_WAVES_Y 0.15 + +#define SMALL_WAVES_X 0.15 // strength of small waves +#define SMALL_WAVES_Y 0.1 + +#define WAVE_CHOPPYNESS 0.15 // wave choppyness +#define WAVE_SCALE 0.01 // overall wave scale + +#define ABBERATION 0.001 // chromatic abberation amount + +float3 intercept(float3 lineP, + float3 lineN, + float3 planeN, + float planeD) +{ + + float distance = (planeD - dot(planeN, lineP)) / dot(lineN, planeN); + return lineP + lineN * distance; +} + +float3 perturb1(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) +{ + float2 nCoord = float2(0.0); + bend *= WAVE_CHOPPYNESS; + nCoord = coords * (WAVE_SCALE * 0.05) + windDir * timer * (windSpeed*0.04); + float3 normal0 = 2.0 * shSample(tex, nCoord + float2(-timer*0.015,-timer*0.05)).rgb - 1.0; + nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.08)-normal0.xy*bend; + float3 normal1 = 2.0 * shSample(tex, nCoord + float2(+timer*0.020,+timer*0.015)).rgb - 1.0; + + nCoord = coords * (WAVE_SCALE * 0.25) + windDir * timer * (windSpeed*0.07)-normal1.xy*bend; + float3 normal2 = 2.0 * shSample(tex, nCoord + float2(-timer*0.04,-timer*0.03)).rgb - 1.0; + nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.09)-normal2.xy*bend; + float3 normal3 = 2.0 * shSample(tex, nCoord + float2(+timer*0.03,+timer*0.04)).rgb - 1.0; + + nCoord = coords * (WAVE_SCALE* 1.0) + windDir * timer * (windSpeed*0.4)-normal3.xy*bend; + float3 normal4 = 2.0 * shSample(tex, nCoord + float2(-timer*0.2,+timer*0.1)).rgb - 1.0; + nCoord = coords * (WAVE_SCALE * 2.0) + windDir * timer * (windSpeed*0.7)-normal4.xy*bend; + float3 normal5 = 2.0 * shSample(tex, nCoord + float2(+timer*0.1,-timer*0.06)).rgb - 1.0; + + + float3 normal = normalize(normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + + normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); + return normal; +} + +float3 perturb(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) +{ + bend *= WAVE_CHOPPYNESS; + float3 col = float3(0.0); + float2 nCoord = float2(0.0); //normal coords + + nCoord = coords * (WAVE_SCALE * 0.025) + windDir * timer * (windSpeed*0.03); + col += shSample(tex,nCoord + float2(-timer*0.005,-timer*0.01)).rgb*0.20; + nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.05)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(+timer*0.01,+timer*0.005)).rgb*0.20; + + nCoord = coords * (WAVE_SCALE * 0.2) + windDir * timer * (windSpeed*0.1)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(-timer*0.02,-timer*0.03)).rgb*0.20; + nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.2)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(+timer*0.03,+timer*0.02)).rgb*0.15; + + nCoord = coords * (WAVE_SCALE* 0.8) + windDir * timer * (windSpeed*1.0)-(col.xy/col.zz)*bend; + col += shSample(tex, nCoord + float2(-timer*0.06,+timer*0.08)).rgb*0.15; + nCoord = coords * (WAVE_SCALE * 1.0) + windDir * timer * (windSpeed*1.3)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(+timer*0.08,-timer*0.06)).rgb*0.10; + + return col; +} + + +float3 getCaustics (shTexture2D causticMap, float3 worldPos, float3 eyePosWS, float3 worldNormal, float3 lightDirectionWS0, float waterLevel, float waterTimer, float3 windDir_windSpeed) +{ + float3 waterEyePos = intercept(worldPos.xyz, eyePosWS - worldPos, float3(0,1,0), waterLevel); + float waterDepth = shSaturate((waterEyePos.y - worldPos.y) / 50.0); + + float3 causticPos = intercept(worldPos.xyz, lightDirectionWS0.xyz, float3(0,1,0), waterLevel); + + ///\ todo clean this up + float causticdepth = length(causticPos-worldPos.xyz); + causticdepth = 1.0-shSaturate(causticdepth / VISIBILITY); + causticdepth = shSaturate(causticdepth); + + // NOTE: the original shader calculated a tangent space basis here, + // but using only the world normal is cheaper and i couldn't see a visual difference + // also, if this effect gets moved to screen-space some day, it's unlikely to have tangent information + float3 causticNorm = worldNormal.xyz * perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).xzy * 2 - 1; + + //float fresnel = pow(clamp(dot(LV,causticnorm),0.0,1.0),2.0); + + float NdotL = max(dot(worldNormal.xyz, lightDirectionWS0.xyz),0.0); + + float causticR = 1.0-perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + + /// \todo sunFade + + // float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*sunFade*causticdepth; + float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*causticdepth; + float causticG = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + float causticB = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION*2.0, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + //caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*sunFade*causticdepth; + caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*causticdepth; + + caustics *= 3; + + // shore transition + caustics = shLerp (float3(1,1,1), caustics, waterDepth); + + return caustics; +} + diff --git a/files/materials/core.h b/files/materials/core.h index 34095f93d..c4d287761 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -69,7 +69,7 @@ #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name) - #define shMatrixMult(m, v) m * v + #define shMatrixMult(m, v) (m * v) // automatically recognized by ogre when the input name equals this #define shInputPosition vertex diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 96c6f8422..9b0357f01 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -61,5 +61,10 @@ material openmw_objects_base tex_address_mode clamp filtering none } + + texture_unit causticMap + { + direct_texture water_nm.png + } } } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index eabdb8ca3..0c1867d66 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -9,13 +9,17 @@ #define SHADOWS LIGHTING && @shGlobalSettingBool(shadows) #if SHADOWS || SHADOWS_PSSM -#include "shadows.h" + #include "shadows.h" #endif #if FOG || MRT || SHADOWS_PSSM #define NEED_DEPTH #endif + +#define UNDERWATER LIGHTING + + #define HAS_VERTEXCOLOR @shPropertyBool(has_vertex_colour) #ifdef SH_VERTEX_SHADER @@ -89,6 +93,10 @@ // ----------------------------------- FRAGMENT ------------------------------------------ +#if UNDERWATER + #include "caustics.h" +#endif + SH_BEGIN_PROGRAM shSampler2D(diffuseMap) shInput(float2, UV) @@ -145,6 +153,20 @@ #if SHADOWS || SHADOWS_PSSM shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) #endif + +#if UNDERWATER + shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) + shUniform(float, waterLevel) @shSharedParameter(waterLevel) + shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) + shUniform(float4, lightDirectionWS0) @shAutoConstant(lightDirectionWS0, light_position, 0) + + shSampler2D(causticMap) + + shUniform(float, waterTimer) @shSharedParameter(waterTimer) + + shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) +#endif + SH_START_PROGRAM { shOutputColour(0) = shSample(diffuseMap, UV); @@ -174,20 +196,42 @@ #if !SHADOWS && !SHADOWS_PSSM float shadow = 1.0; #endif + + + + float3 caustics = float3(1,1,1); +#if UNDERWATER + float4 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)); + if (worldPos.y < waterLevel) + { + float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); + caustics = getCaustics(causticMap, worldPos.xyz, cameraPos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); + } +#endif + @shForeach(@shGlobalSettingString(num_lights)) - + /// \todo use the array auto params for lights, and use a real for-loop with auto param "light_count" iterations lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePositionPassthrough.xyz * lightPosObjSpace@shIterator.w); d = length(lightDir); lightDir = normalize(lightDir); -#if @shIterator == 0 && (SHADOWS || SHADOWS_PSSM) - diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; +#if @shIterator == 0 + + #if (SHADOWS || SHADOWS_PSSM) + diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow * caustics; + + #else + diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * caustics; + + #endif + #else diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); #endif + @shEndForeach #if HAS_VERTEXCOLOR diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 2b7091838..722108a58 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -22,6 +22,8 @@ #define NEED_DEPTH 1 #endif +#define UNDERWATER LIGHTING + #if NEED_DEPTH @shAllocatePassthrough(1, depth) @@ -122,6 +124,10 @@ // ----------------------------------- FRAGMENT ------------------------------------------ +#if UNDERWATER + #include "caustics.h" +#endif + SH_BEGIN_PROGRAM @@ -180,6 +186,20 @@ #endif +#if UNDERWATER + shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) + shUniform(float, waterLevel) @shSharedParameter(waterLevel) + shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) + shUniform(float4, lightDirectionWS0) @shAutoConstant(lightDirectionWS0, light_position, 0) + + shSampler2D(causticMap) + + shUniform(float, waterTimer) @shSharedParameter(waterTimer) + + shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) +#endif + + SH_START_PROGRAM { @@ -198,6 +218,19 @@ + float3 caustics = float3(1,1,1); +#if UNDERWATER + + float4 worldPos = shMatrixMult(worldMatrix, float4(objSpacePosition,1)); + if (worldPos.y < waterLevel) + { + float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); + caustics = getCaustics(causticMap, worldPos.xyz, cameraPos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); + } + +#endif + + // Layer calculations @shForeach(@shPropertyString(num_blendmaps)) float4 blendValues@shIterator = shSample(blendMap@shIterator, UV); @@ -272,11 +305,20 @@ lightDir = normalize(lightDir); -#if @shIterator == 0 && (SHADOWS || SHADOWS_PSSM) - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; +#if @shIterator == 0 + + #if (SHADOWS || SHADOWS_PSSM) + diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow * caustics; + + #else + diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * caustics; + + #endif + #else diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); #endif + @shEndForeach shOutputColour(0).xyz *= (lightAmbient.xyz + diffuse); diff --git a/files/materials/water.mat b/files/materials/water.mat index c701e5ffe..d74305c73 100644 --- a/files/materials/water.mat +++ b/files/materials/water.mat @@ -1,30 +1,33 @@ -// note: the fixed function water is created manually, not here - -material openmw_water +material Water { pass { vertex_program water_vertex fragment_program water_fragment + cull_hardware none + texture_unit reflectionMap { texture_alias WaterReflection + tex_address_mode clamp } texture_unit refractionMap { texture_alias WaterRefraction + tex_address_mode clamp } texture_unit depthMap { - + texture_alias SceneDepth + tex_address_mode clamp } texture_unit normalMap { - texture + direct_texture water_nm.png } } } diff --git a/files/materials/water.shader b/files/materials/water.shader index e69de29bb..4945770a5 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -0,0 +1,228 @@ +#include "core.h" + +// Inspired by Blender GLSL Water by martinsh ( http://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shInput(float2, uv0) + shOutput(float2, UV) + + shOutput(float3, screenCoordsPassthrough) + shOutput(float4, position) + shOutput(float, depthPassthrough) + + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + + + #if !SH_GLSL + float4x4 scalemat = float4x4( 0.5, 0, 0, 0.5, + 0, -0.5, 0, 0.5, + 0, 0, 0.5, 0.5, + 0, 0, 0, 1 ); + #else + mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, -0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + #endif + + float4 texcoordProj = shMatrixMult(scalemat, shOutputPosition); + screenCoordsPassthrough = float3(texcoordProj.x, texcoordProj.y, texcoordProj.w); + + position = shInputPosition; + + depthPassthrough = shOutputPosition.z; + } + +#else + + // tweakables ---------------------------------------------------- + + #define BIG_WAVES_X 0.3 // strength of big waves + #define BIG_WAVES_Y 0.3 + + #define MID_WAVES_X 0.3 // strength of middle sized waves + #define MID_WAVES_Y 0.15 + + #define SMALL_WAVES_X 0.15 // strength of small waves + #define SMALL_WAVES_Y 0.1 + + #define WAVE_CHOPPYNESS 0.15 // wave choppyness + #define WAVE_SCALE 150 // overall wave scale + + #define ABBERATION 0.001 // chromatic abberation amount + #define BUMP 1.5 // overall water surface bumpiness + #define REFL_BUMP 0.11 // reflection distortion amount + #define REFR_BUMP 0.08 // refraction distortion amount + + #define SCATTER_AMOUNT 3.0 // amount of sunlight scattering + #define SCATTER_COLOUR float3(0.0,1.0,0.95) // colour of sunlight scattering + + #define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction + + #define SPEC_HARDNESS 256 // specular highlights hardness + + + // --------------------------------------------------------------- + + + + float fresnel_dielectric(float3 Incoming, float3 Normal, float eta) + { + /* compute fresnel reflectance without explicitly computing + the refracted direction */ + float c = abs(dot(Incoming, Normal)); + float g = eta * eta - 1.0 + c * c; + float result; + + if(g > 0.0) { + g = sqrt(g); + float A =(g - c)/(g + c); + float B =(c *(g + c)- 1.0)/(c *(g - c)+ 1.0); + result = 0.5 * A * A *(1.0 + B * B); + } + else + result = 1.0; /* TIR (no refracted component) */ + + return result; + } + + SH_BEGIN_PROGRAM + shInput(float2, UV) + shInput(float3, screenCoordsPassthrough) + shInput(float4, position) + shInput(float, depthPassthrough) + + shUniform(float, far) @shAutoConstant(far, far_clip_distance) + + shSampler2D(reflectionMap) + shSampler2D(refractionMap) + shSampler2D(depthMap) + shSampler2D(normalMap) + + shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) + #define WIND_SPEED windDir_windSpeed.z + #define WIND_DIR windDir_windSpeed.xy + + shUniform(float, waterTimer) @shSharedParameter(waterTimer) + shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight) + + shUniform(float4, sunPosition) @shAutoConstant(sunPosition, light_position, 0) + shUniform(float4, sunSpecular) @shAutoConstant(sunSpecular, light_specular_colour, 0) + + + + shUniform(float, renderTargetFlipping) @shAutoConstant(renderTargetFlipping, render_target_flipping) + + + shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) + shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) + + shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position_object_space) + + + SH_START_PROGRAM + { + + float2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; + screenCoords.y = (1-shSaturate(renderTargetFlipping))+renderTargetFlipping*screenCoords.y; + + float depth = shSample(depthMap, screenCoords).x * far - depthPassthrough; + float shoreFade = shSaturate(depth / 50.0); + + float2 nCoord = float2(0.0); + + nCoord = UV * (WAVE_SCALE * 0.05) + WIND_DIR * waterTimer * (WIND_SPEED*0.04); + float3 normal0 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.015,-waterTimer*0.005)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 0.1) + WIND_DIR * waterTimer * (WIND_SPEED*0.08)-(normal0.xy/normal0.zz)*WAVE_CHOPPYNESS; + float3 normal1 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.020,+waterTimer*0.015)).rgb - 1.0; + + nCoord = UV * (WAVE_SCALE * 0.25) + WIND_DIR * waterTimer * (WIND_SPEED*0.07)-(normal1.xy/normal1.zz)*WAVE_CHOPPYNESS; + float3 normal2 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.04,-waterTimer*0.03)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 0.5) + WIND_DIR * waterTimer * (WIND_SPEED*0.09)-(normal2.xy/normal2.z)*WAVE_CHOPPYNESS; + float3 normal3 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.03,+waterTimer*0.04)).rgb - 1.0; + + nCoord = UV * (WAVE_SCALE* 1.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.4)-(normal3.xy/normal3.zz)*WAVE_CHOPPYNESS; + float3 normal4 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.02,+waterTimer*0.1)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 2.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.7)-(normal4.xy/normal4.zz)*WAVE_CHOPPYNESS; + float3 normal5 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.1,-waterTimer*0.06)).rgb - 1.0; + + + + float3 normal = (normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + + normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y).xzy; + + normal = normalize(float3(normal.x * BUMP, normal.y, normal.z * BUMP)); + + // normal for sunlight scattering + float3 lNormal = (normal0 * BIG_WAVES_X*0.5 + normal1 * BIG_WAVES_Y*0.5 + + normal2 * MID_WAVES_X*0.2 + normal3 * MID_WAVES_Y*0.2 + + normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1).xzy; + lNormal = normalize(float3(lNormal.x * BUMP, lNormal.y, lNormal.z * BUMP)); + + + float3 lVec = normalize(sunPosition.xyz); + float3 vVec = normalize(position.xyz - cameraPos.xyz); + + + float isUnderwater = (cameraPos.y > 0) ? 0.0 : 1.0; + + // sunlight scattering + float3 pNormal = float3(0,1,0); + vec3 lR = reflect(lVec, lNormal); + vec3 llR = reflect(lVec, pNormal); + + float s = shSaturate(dot(lR, vVec)*2.0-1.2); + float lightScatter = shSaturate(dot(-lVec,lNormal)*0.7+0.3) * s * SCATTER_AMOUNT * waterSunFade_sunHeight.x * shSaturate(1.0-exp(-waterSunFade_sunHeight.y)); + float3 scatterColour = shLerp(vec3(SCATTER_COLOUR)*vec3(1.0,0.4,0.0), SCATTER_COLOUR, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + + // fresnel + float ior = (cameraPos.y>0)?(1.333/1.0):(1.0/1.333); //air to water; water to air + float fresnel = fresnel_dielectric(-vVec, normal, ior); + + fresnel = shSaturate(fresnel); + + // reflection + float3 reflection = shSample(reflectionMap, screenCoords+(normal.xz*REFL_BUMP)).rgb; + + // refraction + float3 R = reflect(vVec, normal); + + float3 refraction = float3(0,0,0); + refraction.r = shSample(refractionMap, (screenCoords-(shoreFade * normal.xz*REFR_BUMP))*1.0).r; + refraction.g = shSample(refractionMap, (screenCoords-(shoreFade * normal.xz*REFR_BUMP))*1.0-(R.xy*ABBERATION)).g; + refraction.b = shSample(refractionMap, (screenCoords-(shoreFade * normal.xz*REFR_BUMP))*1.0-(R.xy*ABBERATION*2.0)).b; + + // brighten up the refraction underwater + refraction = (cameraPos.y < 0) ? shSaturate(refraction * 1.5) : refraction; + + float waterSunGradient = dot(-vVec, -lVec); + waterSunGradient = shSaturate(pow(waterSunGradient*0.7+0.3,2.0)); + float3 waterSunColour = float3(0.0,1.0,0.85)*waterSunGradient * 0.5; + + float waterGradient = dot(-vVec, float3(0.0,-1.0,0.0)); + waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); + float3 watercolour = (float3(0.0078, 0.5176, 0.700)+waterSunColour)*waterGradient*2.0; + float3 waterext = float3(0.6, 0.9, 1.0);//water extinction + watercolour = mix(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + + // specular + float specular = pow(max(dot(R, lVec), 0.0),SPEC_HARDNESS); + + shOutputColour(0).xyz = shLerp( shLerp(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpecular.xyz; + + // smooth transition to shore (above water only) + shOutputColour(0).xyz = shLerp(shOutputColour(0).xyz, refraction, (1-shoreFade) * (1-isUnderwater)); + + // fog + float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); + } + +#endif diff --git a/files/materials/water.shaderset b/files/materials/water.shaderset index e69de29bb..754ac8e49 100644 --- a/files/materials/water.shaderset +++ b/files/materials/water.shaderset @@ -0,0 +1,15 @@ +shader_set water_vertex +{ + source water.shader + type vertex + profiles_cg vs_2_0 vp40 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set water_fragment +{ + source water.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 + profiles_hlsl ps_2_0 +} diff --git a/files/water/underwater_dome.mesh b/files/water/underwater_dome.mesh new file mode 100644 index 0000000000000000000000000000000000000000..64ca569c22a04110e7913505c427f0087cef51c4 GIT binary patch literal 22585 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 literal 0 HcmV?d00001 diff --git a/files/water/water.compositor b/files/water/water.compositor index 8d9c3cb39..94b778773 100644 --- a/files/water/water.compositor +++ b/files/water/water.compositor @@ -39,7 +39,6 @@ compositor Underwater { material Water/Compositor input 0 rt0 - input 3 scene 1 } } } diff --git a/files/water/water.material b/files/water/water.material index d1f7fcf49..7376d10cc 100644 --- a/files/water/water.material +++ b/files/water/water.material @@ -44,7 +44,7 @@ fragment_program Water_FP cg profiles ps_2_x arbfp1 } -material Water +material __Water { technique { diff --git a/files/water/water_nm.png b/files/water/water_nm.png new file mode 100644 index 0000000000000000000000000000000000000000..361431a0efc35882b56bab8ea407d245f27c879d GIT binary patch literal 24405 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 literal 0 HcmV?d00001 From a86ed46ec495329b5fafc08e5893bdaa43afac2b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 19 Jul 2012 11:00:26 -0700 Subject: [PATCH 175/298] Use the race ID specified in the ref base's record instead of the race record The latter is localized and doesn't match with international versions. --- apps/openmw/mwrender/npcanimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 9f28dbf13..a693a6b46 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -74,7 +74,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere isFemale = !!(ref->base->flags&ESM::NPC::Female); isBeast = !!(race->data.flags&ESM::Race::Beast); - bodyRaceID = "b_n_"+race->name; + bodyRaceID = "b_n_"+ref->base->race; std::transform(bodyRaceID.begin(), bodyRaceID.end(), bodyRaceID.begin(), ::tolower); /*std::cout << "Race: " << ref->base->race ; From 9d7470e14cb2e57c6300ce9e633ed7db635e0b7c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jul 2012 20:18:34 +0200 Subject: [PATCH 176/298] temp commit --- extern/shiny | 2 +- files/materials/objects.shader | 46 ++++++++++--- files/materials/terrain.shader | 10 +-- files/materials/underwater.h | 116 +++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 15 deletions(-) create mode 100644 files/materials/underwater.h diff --git a/extern/shiny b/extern/shiny index bf003238a..4853ea735 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit bf003238a27d94be43724e6774d25c38b4d578c8 +Subproject commit 4853ea7351edced75a48662da5f3e857961e0b47 diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 0c1867d66..fdf2b3676 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -42,7 +42,7 @@ #if HAS_VERTEXCOLOR shColourInput(float4) - shOutput(float4, colorPassthrough) + shOutput(float4, colourPassthrough) #endif #if SHADOWS @@ -75,7 +75,7 @@ #endif #if HAS_VERTEXCOLOR - colorPassthrough = colour; + colourPassthrough = colour; #endif #if SHADOWS @@ -128,12 +128,12 @@ #endif #if FOG - shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) + shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour) shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) #endif #ifdef HAS_VERTEXCOLOR - shInput(float4, colorPassthrough) + shInput(float4, colourPassthrough) #endif #if SHADOWS @@ -201,11 +201,13 @@ float3 caustics = float3(1,1,1); #if UNDERWATER - float4 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)); + float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)).xyz; + float3 waterEyePos = float3(1,1,1); if (worldPos.y < waterLevel) { float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); - caustics = getCaustics(causticMap, worldPos.xyz, cameraPos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); + waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); + caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); } #endif @@ -235,7 +237,7 @@ @shEndForeach #if HAS_VERTEXCOLOR - ambient *= colorPassthrough.xyz; + ambient *= colourPassthrough.xyz; #endif shOutputColour(0).xyz *= (ambient + diffuse + materialEmissive.xyz); @@ -243,16 +245,40 @@ #if HAS_VERTEXCOLOR && !LIGHTING - shOutputColour(0).xyz *= colorPassthrough.xyz; + shOutputColour(0).xyz *= colourPassthrough.xyz; #endif #if FOG float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); + + #if UNDERWATER + // regular fog only if fragment is above water + if (worldPos.y > waterLevel) + #endif + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); #endif - // prevent negative color output (for example with negative lights) + // prevent negative colour output (for example with negative lights) shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0)); + +#if UNDERWATER + float fogAmount = (worldPos.y > waterLevel) + ? shSaturate(length(waterEyePos-worldPos) / VISIBILITY) + : shSaturate(length(cameraPos.xyz-worldPos)/ VISIBILITY); + + float3 eyeVec = normalize(cameraPos.xyz-worldPos); + float waterSunGradient = dot(eyeVec, -normalize(lightDirectionWS0.xyz)); + waterSunGradient = clamp(pow(waterSunGradient*0.7+0.3,2.0),0.0,1.0); + + float waterGradient = dot(eyeVec, vec3(0.0,-1.0,0.0)); + waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); + + float3 waterSunColour = vec3(0.0,1.0,0.85)*waterSunGradient; + waterSunColour = (cameraPos.z < waterLevel) ? waterSunColour*0.5:waterSunColour*0.25;//below or above water? + + float3 waterColour = (float3(0.0078, 0.5176, 0.700)+waterSunColour)*waterGradient*2.0; + shOutputColour(0).xyz = waterColour; +#endif #if MRT shOutputColour(1) = float4(depthPassthrough / far,1,1,1); diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 722108a58..77ba01494 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -147,7 +147,7 @@ @shEndForeach #if FOG - shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) + shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour) shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) #endif @@ -221,11 +221,13 @@ float3 caustics = float3(1,1,1); #if UNDERWATER - float4 worldPos = shMatrixMult(worldMatrix, float4(objSpacePosition,1)); + float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePosition,1)).xyz; + float3 waterEyePos = float3(1,1,1); if (worldPos.y < waterLevel) { float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); - caustics = getCaustics(causticMap, worldPos.xyz, cameraPos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); + waterEyePos = intercept(worldPos, eyePosWS - worldPos, float3(0,1,0), waterLevel); + caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); } #endif @@ -329,7 +331,7 @@ #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); #endif diff --git a/files/materials/underwater.h b/files/materials/underwater.h new file mode 100644 index 000000000..fa744ac39 --- /dev/null +++ b/files/materials/underwater.h @@ -0,0 +1,116 @@ +#define VISIBILITY 1500.0 // how far you can look through water + +#define BIG_WAVES_X 0.3 // strength of big waves +#define BIG_WAVES_Y 0.3 + +#define MID_WAVES_X 0.3 // strength of middle sized waves +#define MID_WAVES_Y 0.15 + +#define SMALL_WAVES_X 0.15 // strength of small waves +#define SMALL_WAVES_Y 0.1 + +#define WAVE_CHOPPYNESS 0.15 // wave choppyness +#define WAVE_SCALE 0.01 // overall wave scale + +#define ABBERATION 0.001 // chromatic abberation amount + +float3 intercept(float3 lineP, + float3 lineN, + float3 planeN, + float planeD) +{ + + float distance = (planeD - dot(planeN, lineP)) / dot(lineN, planeN); + return lineP + lineN * distance; +} + +float3 perturb1(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) +{ + float2 nCoord = float2(0.0); + bend *= WAVE_CHOPPYNESS; + nCoord = coords * (WAVE_SCALE * 0.05) + windDir * timer * (windSpeed*0.04); + float3 normal0 = 2.0 * shSample(tex, nCoord + float2(-timer*0.015,-timer*0.05)).rgb - 1.0; + nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.08)-normal0.xy*bend; + float3 normal1 = 2.0 * shSample(tex, nCoord + float2(+timer*0.020,+timer*0.015)).rgb - 1.0; + + nCoord = coords * (WAVE_SCALE * 0.25) + windDir * timer * (windSpeed*0.07)-normal1.xy*bend; + float3 normal2 = 2.0 * shSample(tex, nCoord + float2(-timer*0.04,-timer*0.03)).rgb - 1.0; + nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.09)-normal2.xy*bend; + float3 normal3 = 2.0 * shSample(tex, nCoord + float2(+timer*0.03,+timer*0.04)).rgb - 1.0; + + nCoord = coords * (WAVE_SCALE* 1.0) + windDir * timer * (windSpeed*0.4)-normal3.xy*bend; + float3 normal4 = 2.0 * shSample(tex, nCoord + float2(-timer*0.2,+timer*0.1)).rgb - 1.0; + nCoord = coords * (WAVE_SCALE * 2.0) + windDir * timer * (windSpeed*0.7)-normal4.xy*bend; + float3 normal5 = 2.0 * shSample(tex, nCoord + float2(+timer*0.1,-timer*0.06)).rgb - 1.0; + + + float3 normal = normalize(normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + + normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); + return normal; +} + +float3 perturb(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) +{ + bend *= WAVE_CHOPPYNESS; + float3 col = float3(0.0); + float2 nCoord = float2(0.0); //normal coords + + nCoord = coords * (WAVE_SCALE * 0.025) + windDir * timer * (windSpeed*0.03); + col += shSample(tex,nCoord + float2(-timer*0.005,-timer*0.01)).rgb*0.20; + nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.05)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(+timer*0.01,+timer*0.005)).rgb*0.20; + + nCoord = coords * (WAVE_SCALE * 0.2) + windDir * timer * (windSpeed*0.1)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(-timer*0.02,-timer*0.03)).rgb*0.20; + nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.2)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(+timer*0.03,+timer*0.02)).rgb*0.15; + + nCoord = coords * (WAVE_SCALE* 0.8) + windDir * timer * (windSpeed*1.0)-(col.xy/col.zz)*bend; + col += shSample(tex, nCoord + float2(-timer*0.06,+timer*0.08)).rgb*0.15; + nCoord = coords * (WAVE_SCALE * 1.0) + windDir * timer * (windSpeed*1.3)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(+timer*0.08,-timer*0.06)).rgb*0.10; + + return col; +} + + +float3 getCaustics (shTexture2D causticMap, float3 worldPos, float3 waterEyePos, float3 worldNormal, float3 lightDirectionWS0, float waterLevel, float waterTimer, float3 windDir_windSpeed) +{ + float waterDepth = shSaturate((waterEyePos.y - worldPos.y) / 50.0); + + float3 causticPos = intercept(worldPos.xyz, lightDirectionWS0.xyz, float3(0,1,0), waterLevel); + + ///\ todo clean this up + float causticdepth = length(causticPos-worldPos.xyz); + causticdepth = 1.0-shSaturate(causticdepth / VISIBILITY); + causticdepth = shSaturate(causticdepth); + + // NOTE: the original shader calculated a tangent space basis here, + // but using only the world normal is cheaper and i couldn't see a visual difference + // also, if this effect gets moved to screen-space some day, it's unlikely to have tangent information + float3 causticNorm = worldNormal.xyz * perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).xzy * 2 - 1; + + //float fresnel = pow(clamp(dot(LV,causticnorm),0.0,1.0),2.0); + + float NdotL = max(dot(worldNormal.xyz, lightDirectionWS0.xyz),0.0); + + float causticR = 1.0-perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + + /// \todo sunFade + + // float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*sunFade*causticdepth; + float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*causticdepth; + float causticG = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + float causticB = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION*2.0, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + //caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*sunFade*causticdepth; + caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*causticdepth; + + caustics *= 3; + + // shore transition + caustics = shLerp (float3(1,1,1), caustics, waterDepth); + + return caustics; +} + From e7602199530baab8c74664af037ff71564b24733 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 19 Jul 2012 11:36:36 -0700 Subject: [PATCH 177/298] Use a unique loader for each skeleton resource --- components/nifogre/ogre_nif_loader.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 639ebaa12..66b711737 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -130,7 +130,7 @@ public: }; -struct NIFSkeletonLoader : public Ogre::ManualResourceLoader { +class NIFSkeletonLoader : public Ogre::ManualResourceLoader { static void warn(const std::string &msg) { @@ -171,6 +171,11 @@ void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent= } } + +typedef std::map LoaderMap; +static LoaderMap sLoaders; + +public: void loadResource(Ogre::Resource *resource) { Ogre::Skeleton *skel = dynamic_cast(resource); @@ -181,7 +186,7 @@ void loadResource(Ogre::Resource *resource) buildBones(skel, node); } -static bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node) +bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node) { if(node->boneTrafo != NULL) { @@ -190,8 +195,8 @@ static bool createSkeleton(const std::string &name, const std::string &group, Ni Ogre::SkeletonPtr skel = skelMgr.getByName(name); if(skel.isNull()) { - static NIFSkeletonLoader loader; - skel = skelMgr.create(name, group, true, &loader); + NIFSkeletonLoader *loader = &sLoaders[name]; + skel = skelMgr.create(name, group, true, loader); } return true; @@ -214,6 +219,7 @@ static bool createSkeleton(const std::string &name, const std::string &group, Ni } }; +NIFSkeletonLoader::LoaderMap NIFSkeletonLoader::sLoaders; // Conversion of blend / test mode from NIF -> OGRE. @@ -875,7 +881,8 @@ MeshPairList NIFLoader::load(std::string name, std::string skelName, const std:: return meshes; } - bool hasSkel = NIFSkeletonLoader::createSkeleton(name, group, node); + NIFSkeletonLoader skelldr; + bool hasSkel = skelldr.createSkeleton(name, group, node); NIFMeshLoader meshldr(name, group, (hasSkel ? skelName : std::string())); meshldr.createMeshes(node, meshes); From f6c837468f1c250d75f2dba1a2fc297c518899fc Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 19 Jul 2012 11:44:08 -0700 Subject: [PATCH 178/298] Load the proper NIF skeleton --- components/nifogre/ogre_nif_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 66b711737..8795efbdf 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -882,7 +882,7 @@ MeshPairList NIFLoader::load(std::string name, std::string skelName, const std:: } NIFSkeletonLoader skelldr; - bool hasSkel = skelldr.createSkeleton(name, group, node); + bool hasSkel = skelldr.createSkeleton(skelName, group, node); NIFMeshLoader meshldr(name, group, (hasSkel ? skelName : std::string())); meshldr.createMeshes(node, meshes); From e077814a8c4563afdf92f21788d4a913c1bba319 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jul 2012 22:23:07 +0200 Subject: [PATCH 179/298] water --- apps/openmw/main.cpp | 6 +- apps/openmw/mwgui/settingswindow.cpp | 2 +- apps/openmw/mwrender/renderconst.hpp | 6 +- apps/openmw/mwrender/renderingmanager.cpp | 5 - apps/openmw/mwrender/sky.cpp | 15 ++- apps/openmw/mwrender/sky.hpp | 1 + apps/openmw/mwrender/water.cpp | 55 +++++----- apps/openmw/mwrender/water.hpp | 3 + files/gbuffer/gbuffer.cg | 18 ---- files/gbuffer/gbuffer.compositor | 6 +- files/gbuffer/gbuffer.material | 63 ----------- files/materials/caustics.h | 117 --------------------- files/materials/objects.mat | 3 - files/materials/objects.shader | 31 ++++-- files/materials/quad.mat | 22 ++++ files/materials/quad.shader | 25 +++++ files/materials/quad.shaderset | 15 +++ files/materials/terrain.shader | 42 +++++++- files/materials/underwater.h | 2 + files/materials/water.mat | 8 ++ files/materials/water.shader | 45 +++++--- files/water/WaterNormal2.tga | Bin 196652 -> 0 bytes files/water/caustic_0.png | Bin 35147 -> 0 bytes files/water/perlinvolume.dds | Bin 2097280 -> 0 bytes files/water/underwater.cg | 61 ----------- files/water/water.cg | 121 ---------------------- files/water/water.compositor | 45 -------- 27 files changed, 213 insertions(+), 504 deletions(-) delete mode 100644 files/gbuffer/gbuffer.cg delete mode 100644 files/gbuffer/gbuffer.material delete mode 100644 files/materials/caustics.h create mode 100644 files/materials/quad.mat create mode 100644 files/materials/quad.shader create mode 100644 files/materials/quad.shaderset delete mode 100644 files/water/WaterNormal2.tga delete mode 100644 files/water/caustic_0.png delete mode 100644 files/water/perlinvolume.dds delete mode 100644 files/water/underwater.cg delete mode 100644 files/water/water.cg delete mode 100644 files/water/water.compositor diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 86584cc22..993ec6623 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -261,7 +261,7 @@ int main(int argc, char**argv) boost::filesystem::current_path(bundlePath); #endif - //try + try { Files::ConfigurationManager cfgMgr; OMW::Engine engine(cfgMgr); @@ -271,11 +271,11 @@ int main(int argc, char**argv) engine.go(); } } - /*catch (std::exception &e) + catch (std::exception &e) { std::cout << "\nERROR: " << e.what() << std::endl; return 1; - }*/ + } return 0; } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 3ef89d422..96cdef4cc 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -209,7 +209,7 @@ namespace MWGui mVoiceVolumeSlider->setScrollPosition(Settings::Manager::getFloat("voice volume", "Sound") * (mVoiceVolumeSlider->getScrollRange()-1)); mWaterShaderButton->setCaptionWithReplacing(Settings::Manager::getBool("shader", "Water") ? "#{sOn}" : "#{sOff}"); - mReflectObjectsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect objects", "Water") ? "#{sOn}" : "#{sOff}"); + mReflectObjectsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect statics", "Water") ? "#{sOn}" : "#{sOff}"); mReflectActorsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect actors", "Water") ? "#{sOn}" : "#{sOff}"); mReflectTerrainButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect terrain", "Water") ? "#{sOn}" : "#{sOff}"); diff --git a/apps/openmw/mwrender/renderconst.hpp b/apps/openmw/mwrender/renderconst.hpp index 457b6e601..9f57833bb 100644 --- a/apps/openmw/mwrender/renderconst.hpp +++ b/apps/openmw/mwrender/renderconst.hpp @@ -18,7 +18,7 @@ enum RenderQueueGroups RQG_OcclusionQuery = Ogre::RENDER_QUEUE_6, - RQG_UnderWater = Ogre::RENDER_QUEUE_7, + RQG_UnderWater = Ogre::RENDER_QUEUE_4, RQG_Water = Ogre::RENDER_QUEUE_7+1, @@ -49,8 +49,8 @@ enum VisibilityFlags RV_Sky = 64, - // Sun glare (not visible in reflection) - RV_Glare = 128, + // not visible in reflection + RV_NoReflection = 128, RV_OcclusionQuery = 256, diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 916ccb0c0..e1ff91a24 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -725,14 +725,9 @@ void RenderingManager::applyCompositors() { mCompositors->addCompositor("gbuffer", 0); mCompositors->setCompositorEnabled("gbuffer", true); - mCompositors->addCompositor("Underwater", 1); mCompositors->addCompositor("gbufferFinalizer", 2); mCompositors->setCompositorEnabled("gbufferFinalizer", true); } - else - { - mCompositors->addCompositor("UnderwaterNoMRT", 0); - } if (mWater) mWater->assignTextures(); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index f84c12b4e..25928cf3f 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -307,7 +307,7 @@ void SkyManager::create() mSun->setRenderQueue(RQG_SkiesEarly+4); mSunGlare = new BillboardObject("textures\\tx_sun_flash_grey_05.dds", 3, Vector3(0.4, 0.4, 0.4), mRootNode, "openmw_sun"); mSunGlare->setRenderQueue(RQG_SkiesLate); - mSunGlare->setVisibilityFlags(RV_Glare); + mSunGlare->setVisibilityFlags(RV_NoReflection); // Stars MeshPtr mesh = NifOgre::NIFLoader::load("meshes\\sky_night_01.nif"); @@ -389,7 +389,6 @@ void SkyManager::update(float duration) sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", sh::makeProperty(new sh::FloatValue(mCloudAnimationTimer))); - /// \todo improve this mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); mSecunda->setPhase ( static_cast( (int) ((mDay % 32)/4.f)) ); @@ -559,6 +558,10 @@ void SkyManager::setSunDirection(const Vector3& direction) if (!mCreated) return; mSun->setPosition(direction); mSunGlare->setPosition(direction); + + float height = direction.z; + float fade = ( height > 0.5) ? 1.0 : height * 2; + sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(fade, height))); } void SkyManager::setMasserDirection(const Vector3& direction) @@ -607,6 +610,7 @@ void SkyManager::setLightningStrength(const float factor) void SkyManager::setLightningDirection(const Ogre::Vector3& dir) { + if (!mCreated) return; mLightning->setDirection (dir); } @@ -653,3 +657,10 @@ void SkyManager::scaleSky(float scale) { mRootNode->setScale(scale, scale, scale); } + +void SkyManager::setGlareEnabled (bool enabled) +{ + if (!mCreated) + return; + mSunGlare->setVisible (mSunEnabled && enabled); +} diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 4a0a7b36b..d785c6eb6 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -164,6 +164,7 @@ namespace MWRender void setLightningDirection(const Ogre::Vector3& dir); void setGlare(const float glare); + void setGlareEnabled(bool enabled); Ogre::Vector3 getRealSunPos(); void setSkyPosition(const Ogre::Vector3& position); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 90967c886..bdf61e96d 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -28,7 +28,7 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mIsUnderwater(false), mVisibilityFlags(0), mReflectionTarget(0), mActive(1), mToggled(1), mReflectionRenderActive(false), mRendering(rend), - mOldFarClip(0), + mOldFarClip(0), mOldFarClip2(0), mWaterTimer(0.f) { mSky = rend->getSkyManager(); @@ -73,8 +73,9 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel underwaterDome->setRenderQueueGroup (RQG_UnderWater); mUnderwaterDome = mSceneManager->getRootSceneNode ()->createChildSceneNode (); mUnderwaterDome->attachObject (underwaterDome); - mUnderwaterDome->setScale(100,100,100); + mUnderwaterDome->setScale(10000,10000,10000); mUnderwaterDome->setVisible(false); + underwaterDome->setMaterialName("Underwater_Dome"); mSceneManager->addRenderQueueListener(this); @@ -162,7 +163,12 @@ void Water::changeCell(const ESM::Cell* cell) void Water::setHeight(const float height) { mTop = height; + mWaterPlane = Plane(Vector3::UNIT_Y, height); + + // small error due to reflection texture size & reflection distortion + mErrorPlane = Plane(Vector3::UNIT_Y, height - 5); + mWaterNode->setPosition(0, height, 0); sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty(new sh::FloatValue(height))); } @@ -177,39 +183,16 @@ void Water::checkUnderwater(float y) { if (!mActive) { - mRendering->getCompositors()->setCompositorEnabled(mCompositorName, false); return; } if ((mIsUnderwater && y > mTop) || !mWater->isVisible() || mCamera->getPolygonMode() != Ogre::PM_SOLID) { - //mRendering->getCompositors()->setCompositorEnabled(mCompositorName, false); - - // tell the shader we are not underwater - -/* - Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); - if (pass->hasFragmentProgram() && pass->getFragmentProgramParameters()->_findNamedConstantDefinition("isUnderwater", false)) - pass->getFragmentProgramParameters()->setNamedConstant("isUnderwater", Real(0)); -*/ - mWater->setRenderQueueGroup(RQG_Water); - mIsUnderwater = false; } if (!mIsUnderwater && y < mTop && mWater->isVisible() && mCamera->getPolygonMode() == Ogre::PM_SOLID) { - //if (mUnderwaterEffect) - //mRendering->getCompositors()->setCompositorEnabled(mCompositorName, true); - - // tell the shader we are underwater -/* - Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); - if (pass->hasFragmentProgram() && pass->getFragmentProgramParameters()->_findNamedConstantDefinition("isUnderwater", false)) - pass->getFragmentProgramParameters()->setNamedConstant("isUnderwater", Real(1)); -*/ - //mWater->setRenderQueueGroup(RQG_UnderWater); - mIsUnderwater = true; } @@ -308,24 +291,36 @@ void Water::updateVisible() void Water::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation) { // We don't want the sky to get clipped by custom near clip plane (the water plane) - if (queueGroupId < 20 && mReflectionRenderActive) + if (((queueGroupId < 20) || queueGroupId == RQG_UnderWater) && mReflectionRenderActive) { mOldFarClip = mReflectionCamera->getFarClipDistance (); mReflectionCamera->disableCustomNearClipPlane(); mReflectionCamera->setFarClipDistance (1000000000); Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mReflectionCamera->getProjectionMatrixRS()); } + else if (queueGroupId == RQG_UnderWater) + {/* + mOldFarClip2 = mCamera->getFarClipDistance (); + mCamera->setFarClipDistance (1000000000); + Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); + */} } void Water::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation) { - if (queueGroupId < 20 && mReflectionRenderActive) + if (((queueGroupId < 20) || queueGroupId == RQG_UnderWater) && mReflectionRenderActive) { mReflectionCamera->setFarClipDistance (mOldFarClip); if (!mIsUnderwater) - mReflectionCamera->enableCustomNearClipPlane(mWaterPlane); + mReflectionCamera->enableCustomNearClipPlane(mErrorPlane); Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mReflectionCamera->getProjectionMatrixRS()); } + if (queueGroupId == RQG_UnderWater) + { + /* + mCamera->setFarClipDistance (mOldFarClip2); + Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); + */} } void Water::update(float dt) @@ -336,6 +331,8 @@ void Water::update(float dt) mWaterTimer += dt; sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(mWaterTimer))); + + mRendering->getSkyManager ()->setGlareEnabled (!mIsUnderwater); } void Water::applyRTT() @@ -366,8 +363,6 @@ void Water::applyRTT() mReflectionTarget = rtt; } - - mCompositorName = RenderingManager::useMRT() ? "Underwater" : "UnderwaterNoMRT"; } void Water::applyVisibilityMask() diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index ac837ca0d..6d0001119 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -36,6 +36,8 @@ namespace MWRender { Ogre::SceneManager *mSceneManager; Ogre::Plane mWaterPlane; + Ogre::Plane mErrorPlane; + Ogre::SceneNode *mWaterNode; Ogre::Entity *mWater; @@ -47,6 +49,7 @@ namespace MWRender { int mTop; int mOldFarClip; + int mOldFarClip2; float mWaterTimer; diff --git a/files/gbuffer/gbuffer.cg b/files/gbuffer/gbuffer.cg deleted file mode 100644 index c7f2fe678..000000000 --- a/files/gbuffer/gbuffer.cg +++ /dev/null @@ -1,18 +0,0 @@ -void RenderScene_vs(in float4 position : POSITION - ,in float2 uv :TEXCOORD0 - ,uniform float4x4 wvp - ,out float4 oPosition : POSITION - ,out float2 oUV :TEXCOORD0) -{ - oPosition = mul(wvp, position); - oUV = uv; -} - -void RenderScene_ps(in float4 position : POSITION - ,in float2 uv :TEXCOORD0 - ,uniform sampler2D tex1 : TEXUNIT0 - ,out float4 oColor : COLOR) -{ - float4 scene =tex2D(tex1, uv); - oColor= scene; -} diff --git a/files/gbuffer/gbuffer.compositor b/files/gbuffer/gbuffer.compositor index 19b890fec..b3d80e9d5 100644 --- a/files/gbuffer/gbuffer.compositor +++ b/files/gbuffer/gbuffer.compositor @@ -29,7 +29,7 @@ compositor gbuffer pass render_quad { - material RenderScene + material quad input 0 mrt_output 0 } } @@ -61,7 +61,7 @@ compositor gbufferFinalizer } pass render_quad { - material RenderSceneNoDepth + material quad_noDepthWrite input 0 previousscene } pass render_scene @@ -78,7 +78,7 @@ compositor gbufferFinalizer } pass render_quad { - material RenderSceneNoDepth + material quad_noDepthWrite input 0 no_mrt_output } } diff --git a/files/gbuffer/gbuffer.material b/files/gbuffer/gbuffer.material deleted file mode 100644 index faa8dd498..000000000 --- a/files/gbuffer/gbuffer.material +++ /dev/null @@ -1,63 +0,0 @@ -vertex_program RenderGBuffer_vs cg -{ - source gbuffer.cg - profiles vs_4_0 vs_1_1 arbvp1 - entry_point RenderScene_vs - default_params - { - param_named_auto wvp worldviewproj_matrix - } -} -fragment_program RenderGBuffer_ps cg -{ - source gbuffer.cg - entry_point RenderScene_ps - profiles ps_4_0 ps_2_x arbfp1 - default_params - { - } -} -material RenderScene -{ - technique - { - pass - { - vertex_program_ref RenderGBuffer_vs - { - } - - fragment_program_ref RenderGBuffer_ps - { - } - - texture_unit tex1 - { - //scenebuffer - } - } - } -} - -material RenderSceneNoDepth -{ - technique - { - pass - { - depth_write off - vertex_program_ref RenderGBuffer_vs - { - } - - fragment_program_ref RenderGBuffer_ps - { - } - - texture_unit tex1 - { - //scenebuffer - } - } - } -} diff --git a/files/materials/caustics.h b/files/materials/caustics.h deleted file mode 100644 index d6be680f0..000000000 --- a/files/materials/caustics.h +++ /dev/null @@ -1,117 +0,0 @@ -#define VISIBILITY 1500.0 // how far you can look through water - -#define BIG_WAVES_X 0.3 // strength of big waves -#define BIG_WAVES_Y 0.3 - -#define MID_WAVES_X 0.3 // strength of middle sized waves -#define MID_WAVES_Y 0.15 - -#define SMALL_WAVES_X 0.15 // strength of small waves -#define SMALL_WAVES_Y 0.1 - -#define WAVE_CHOPPYNESS 0.15 // wave choppyness -#define WAVE_SCALE 0.01 // overall wave scale - -#define ABBERATION 0.001 // chromatic abberation amount - -float3 intercept(float3 lineP, - float3 lineN, - float3 planeN, - float planeD) -{ - - float distance = (planeD - dot(planeN, lineP)) / dot(lineN, planeN); - return lineP + lineN * distance; -} - -float3 perturb1(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) -{ - float2 nCoord = float2(0.0); - bend *= WAVE_CHOPPYNESS; - nCoord = coords * (WAVE_SCALE * 0.05) + windDir * timer * (windSpeed*0.04); - float3 normal0 = 2.0 * shSample(tex, nCoord + float2(-timer*0.015,-timer*0.05)).rgb - 1.0; - nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.08)-normal0.xy*bend; - float3 normal1 = 2.0 * shSample(tex, nCoord + float2(+timer*0.020,+timer*0.015)).rgb - 1.0; - - nCoord = coords * (WAVE_SCALE * 0.25) + windDir * timer * (windSpeed*0.07)-normal1.xy*bend; - float3 normal2 = 2.0 * shSample(tex, nCoord + float2(-timer*0.04,-timer*0.03)).rgb - 1.0; - nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.09)-normal2.xy*bend; - float3 normal3 = 2.0 * shSample(tex, nCoord + float2(+timer*0.03,+timer*0.04)).rgb - 1.0; - - nCoord = coords * (WAVE_SCALE* 1.0) + windDir * timer * (windSpeed*0.4)-normal3.xy*bend; - float3 normal4 = 2.0 * shSample(tex, nCoord + float2(-timer*0.2,+timer*0.1)).rgb - 1.0; - nCoord = coords * (WAVE_SCALE * 2.0) + windDir * timer * (windSpeed*0.7)-normal4.xy*bend; - float3 normal5 = 2.0 * shSample(tex, nCoord + float2(+timer*0.1,-timer*0.06)).rgb - 1.0; - - - float3 normal = normalize(normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + - normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + - normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); - return normal; -} - -float3 perturb(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) -{ - bend *= WAVE_CHOPPYNESS; - float3 col = float3(0.0); - float2 nCoord = float2(0.0); //normal coords - - nCoord = coords * (WAVE_SCALE * 0.025) + windDir * timer * (windSpeed*0.03); - col += shSample(tex,nCoord + float2(-timer*0.005,-timer*0.01)).rgb*0.20; - nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.05)-(col.xy/col.zz)*bend; - col += shSample(tex,nCoord + float2(+timer*0.01,+timer*0.005)).rgb*0.20; - - nCoord = coords * (WAVE_SCALE * 0.2) + windDir * timer * (windSpeed*0.1)-(col.xy/col.zz)*bend; - col += shSample(tex,nCoord + float2(-timer*0.02,-timer*0.03)).rgb*0.20; - nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.2)-(col.xy/col.zz)*bend; - col += shSample(tex,nCoord + float2(+timer*0.03,+timer*0.02)).rgb*0.15; - - nCoord = coords * (WAVE_SCALE* 0.8) + windDir * timer * (windSpeed*1.0)-(col.xy/col.zz)*bend; - col += shSample(tex, nCoord + float2(-timer*0.06,+timer*0.08)).rgb*0.15; - nCoord = coords * (WAVE_SCALE * 1.0) + windDir * timer * (windSpeed*1.3)-(col.xy/col.zz)*bend; - col += shSample(tex,nCoord + float2(+timer*0.08,-timer*0.06)).rgb*0.10; - - return col; -} - - -float3 getCaustics (shTexture2D causticMap, float3 worldPos, float3 eyePosWS, float3 worldNormal, float3 lightDirectionWS0, float waterLevel, float waterTimer, float3 windDir_windSpeed) -{ - float3 waterEyePos = intercept(worldPos.xyz, eyePosWS - worldPos, float3(0,1,0), waterLevel); - float waterDepth = shSaturate((waterEyePos.y - worldPos.y) / 50.0); - - float3 causticPos = intercept(worldPos.xyz, lightDirectionWS0.xyz, float3(0,1,0), waterLevel); - - ///\ todo clean this up - float causticdepth = length(causticPos-worldPos.xyz); - causticdepth = 1.0-shSaturate(causticdepth / VISIBILITY); - causticdepth = shSaturate(causticdepth); - - // NOTE: the original shader calculated a tangent space basis here, - // but using only the world normal is cheaper and i couldn't see a visual difference - // also, if this effect gets moved to screen-space some day, it's unlikely to have tangent information - float3 causticNorm = worldNormal.xyz * perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).xzy * 2 - 1; - - //float fresnel = pow(clamp(dot(LV,causticnorm),0.0,1.0),2.0); - - float NdotL = max(dot(worldNormal.xyz, lightDirectionWS0.xyz),0.0); - - float causticR = 1.0-perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; - - /// \todo sunFade - - // float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*sunFade*causticdepth; - float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*causticdepth; - float causticG = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; - float causticB = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION*2.0, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; - //caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*sunFade*causticdepth; - caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*causticdepth; - - caustics *= 3; - - // shore transition - caustics = shLerp (float3(1,1,1), caustics, waterDepth); - - return caustics; -} - diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 9b0357f01..f1198b4a2 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -11,9 +11,6 @@ material openmw_objects_base scene_blend default depth_write default alpha_rejection default - shadow_transparency true // use diffuse alpha as mask for shadow - - shadow_caster_material openmw_shadowcaster pass { diff --git a/files/materials/objects.shader b/files/materials/objects.shader index fdf2b3676..f40094fa7 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -94,7 +94,7 @@ // ----------------------------------- FRAGMENT ------------------------------------------ #if UNDERWATER - #include "caustics.h" + #include "underwater.h" #endif SH_BEGIN_PROGRAM @@ -163,7 +163,8 @@ shSampler2D(causticMap) shUniform(float, waterTimer) @shSharedParameter(waterTimer) - + shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight) + shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) #endif @@ -262,22 +263,30 @@ shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0)); #if UNDERWATER - float fogAmount = (worldPos.y > waterLevel) + float fogAmount = (cameraPos.y > waterLevel) ? shSaturate(length(waterEyePos-worldPos) / VISIBILITY) : shSaturate(length(cameraPos.xyz-worldPos)/ VISIBILITY); float3 eyeVec = normalize(cameraPos.xyz-worldPos); + float waterSunGradient = dot(eyeVec, -normalize(lightDirectionWS0.xyz)); - waterSunGradient = clamp(pow(waterSunGradient*0.7+0.3,2.0),0.0,1.0); + waterSunGradient = shSaturate(pow(waterSunGradient*0.7+0.3,2.0)); + float3 waterSunColour = float3(0.0,1.0,0.85)*waterSunGradient * 0.5; - float waterGradient = dot(eyeVec, vec3(0.0,-1.0,0.0)); + float waterGradient = dot(eyeVec, float3(0.0,-1.0,0.0)); waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); - - float3 waterSunColour = vec3(0.0,1.0,0.85)*waterSunGradient; - waterSunColour = (cameraPos.z < waterLevel) ? waterSunColour*0.5:waterSunColour*0.25;//below or above water? - - float3 waterColour = (float3(0.0078, 0.5176, 0.700)+waterSunColour)*waterGradient*2.0; - shOutputColour(0).xyz = waterColour; + float3 watercolour = (float3(0.0078, 0.5176, 0.700)+waterSunColour)*waterGradient*2.0; + float3 waterext = float3(0.6, 0.9, 1.0);//water extinction + watercolour = shLerp(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + watercolour = (cameraPos.y <= waterLevel) ? watercolour : watercolour*0.3; + + + float darkness = VISIBILITY*2.0; + darkness = clamp((waterEyePos.y - waterLevel + darkness)/darkness,0.2,1.0); + watercolour *= darkness; + + float isUnderwater = (worldPos.y < waterLevel) ? 1.0 : 0.0; + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, watercolour, fogAmount * isUnderwater); #endif #if MRT diff --git a/files/materials/quad.mat b/files/materials/quad.mat new file mode 100644 index 000000000..1ada37bc4 --- /dev/null +++ b/files/materials/quad.mat @@ -0,0 +1,22 @@ +material quad +{ + depth_write on + + pass + { + vertex_program quad_vertex + fragment_program quad_fragment + + depth_write $depth_write + + texture_unit SceneBuffer + { + } + } +} + +material quad_noDepthWrite +{ + parent quad + depth_write off +} diff --git a/files/materials/quad.shader b/files/materials/quad.shader new file mode 100644 index 000000000..4fa38cac9 --- /dev/null +++ b/files/materials/quad.shader @@ -0,0 +1,25 @@ +#include "core.h" + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shInput(float2, uv0) + shOutput(float2, UV) + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + } + +#else + + SH_BEGIN_PROGRAM + shInput(float2, UV) + shSampler2D(SceneBuffer) + SH_START_PROGRAM + { + shOutputColour(0) = shSample(SceneBuffer, UV); + } + +#endif diff --git a/files/materials/quad.shaderset b/files/materials/quad.shaderset new file mode 100644 index 000000000..a1252b15c --- /dev/null +++ b/files/materials/quad.shaderset @@ -0,0 +1,15 @@ +shader_set quad_vertex +{ + source quad.shader + type vertex + profiles_cg vs_2_0 vp40 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set quad_fragment +{ + source quad.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 + profiles_hlsl ps_2_0 +} diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 77ba01494..919857401 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -125,7 +125,7 @@ // ----------------------------------- FRAGMENT ------------------------------------------ #if UNDERWATER - #include "caustics.h" + #include "underwater.h" #endif SH_BEGIN_PROGRAM @@ -195,7 +195,8 @@ shSampler2D(causticMap) shUniform(float, waterTimer) @shSharedParameter(waterTimer) - + shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight) + shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) #endif @@ -226,7 +227,7 @@ if (worldPos.y < waterLevel) { float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); - waterEyePos = intercept(worldPos, eyePosWS - worldPos, float3(0,1,0), waterLevel); + waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); } @@ -331,9 +332,44 @@ #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); + + #if UNDERWATER + // regular fog only if fragment is above water + if (worldPos.y > waterLevel) + #endif shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); #endif + // prevent negative colour output (for example with negative lights) + shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0)); + +#if UNDERWATER + float fogAmount = (cameraPos.y > waterLevel) + ? shSaturate(length(waterEyePos-worldPos) / VISIBILITY) + : shSaturate(length(cameraPos.xyz-worldPos)/ VISIBILITY); + + float3 eyeVec = normalize(cameraPos.xyz-worldPos); + + float waterSunGradient = dot(eyeVec, -normalize(lightDirectionWS0.xyz)); + waterSunGradient = shSaturate(pow(waterSunGradient*0.7+0.3,2.0)); + float3 waterSunColour = float3(0.0,1.0,0.85)*waterSunGradient * 0.5; + + float waterGradient = dot(eyeVec, float3(0.0,-1.0,0.0)); + waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); + float3 watercolour = (float3(0.0078, 0.5176, 0.700)+waterSunColour)*waterGradient*2.0; + float3 waterext = float3(0.6, 0.9, 1.0);//water extinction + watercolour = shLerp(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + watercolour = (cameraPos.y <= waterLevel) ? watercolour : watercolour*0.3; + + + float darkness = VISIBILITY*2.0; + darkness = clamp((waterEyePos.y - waterLevel + darkness)/darkness,0.2,1.0); + watercolour *= darkness; + + float isUnderwater = (worldPos.y < waterLevel) ? 1.0 : 0.0; + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, watercolour, fogAmount * isUnderwater); +#endif + #if MRT shOutputColour(1) = float4(depth / far,1,1,1); diff --git a/files/materials/underwater.h b/files/materials/underwater.h index fa744ac39..fe19ff93b 100644 --- a/files/materials/underwater.h +++ b/files/materials/underwater.h @@ -14,6 +14,8 @@ #define ABBERATION 0.001 // chromatic abberation amount +#define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction + float3 intercept(float3 lineP, float3 lineN, float3 planeN, diff --git a/files/materials/water.mat b/files/materials/water.mat index d74305c73..2ae561d77 100644 --- a/files/materials/water.mat +++ b/files/materials/water.mat @@ -31,3 +31,11 @@ material Water } } } + + +material Underwater_Dome +{ + parent openmw_objects_base + + depth_write off +} diff --git a/files/materials/water.shader b/files/materials/water.shader index 4945770a5..4bfc32421 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -43,6 +43,8 @@ // tweakables ---------------------------------------------------- + #define VISIBILITY 1500.0 // how far you can look through water + #define BIG_WAVES_X 0.3 // strength of big waves #define BIG_WAVES_Y 0.3 @@ -57,15 +59,15 @@ #define ABBERATION 0.001 // chromatic abberation amount #define BUMP 1.5 // overall water surface bumpiness - #define REFL_BUMP 0.11 // reflection distortion amount - #define REFR_BUMP 0.08 // refraction distortion amount + #define REFL_BUMP 0.08 // reflection distortion amount + #define REFR_BUMP 0.06 // refraction distortion amount #define SCATTER_AMOUNT 3.0 // amount of sunlight scattering #define SCATTER_COLOUR float3(0.0,1.0,0.95) // colour of sunlight scattering #define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction - #define SPEC_HARDNESS 256 // specular highlights hardness + #define SPEC_HARDNESS 256 // specular highlights hardness // --------------------------------------------------------------- @@ -201,16 +203,6 @@ // brighten up the refraction underwater refraction = (cameraPos.y < 0) ? shSaturate(refraction * 1.5) : refraction; - - float waterSunGradient = dot(-vVec, -lVec); - waterSunGradient = shSaturate(pow(waterSunGradient*0.7+0.3,2.0)); - float3 waterSunColour = float3(0.0,1.0,0.85)*waterSunGradient * 0.5; - - float waterGradient = dot(-vVec, float3(0.0,-1.0,0.0)); - waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); - float3 watercolour = (float3(0.0078, 0.5176, 0.700)+waterSunColour)*waterGradient*2.0; - float3 waterext = float3(0.6, 0.9, 1.0);//water extinction - watercolour = mix(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); // specular float specular = pow(max(dot(R, lVec), 0.0),SPEC_HARDNESS); @@ -221,8 +213,31 @@ shOutputColour(0).xyz = shLerp(shOutputColour(0).xyz, refraction, (1-shoreFade) * (1-isUnderwater)); // fog - float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); + if (isUnderwater == 1) + { + float waterSunGradient = dot(-vVec, -lVec); + waterSunGradient = shSaturate(pow(waterSunGradient*0.7+0.3,2.0)); + float3 waterSunColour = float3(0.0,1.0,0.85)*waterSunGradient * 0.5; + + float waterGradient = dot(-vVec, float3(0.0,-1.0,0.0)); + waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); + float3 watercolour = (float3(0.0078, 0.5176, 0.700)+waterSunColour)*waterGradient*2.0; + float3 waterext = float3(0.6, 0.9, 1.0);//water extinction + watercolour = mix(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + + float darkness = VISIBILITY*2.0; + darkness = clamp((cameraPos.y+darkness)/darkness,0.2,1.0); + + + float fog = shSaturate(length(cameraPos.xyz-position.xyz) / VISIBILITY); + shOutputColour(0).xyz = shLerp(shOutputColour(0).xyz, watercolour * darkness, shSaturate(fog / waterext)); + } + else + { + float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); + } + } #endif diff --git a/files/water/WaterNormal2.tga b/files/water/WaterNormal2.tga deleted file mode 100644 index 771d15041922130687cc943d8350165c7edadc0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196652 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

GK8 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 K>GQw)JO3X(c8^d1 diff --git a/files/water/caustic_0.png b/files/water/caustic_0.png deleted file mode 100644 index fee464860ea42100b844a4d8dfaa101c3bb80a88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35147 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Ⓢ+p>caiAE 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�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⩔jXf~T)t@b>`XZ1}WXzmgvic~p|ZvqVA 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

3X6KRrMiKbQ@^|Ag;R0leCt%5#7~ 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%0A}9&@$#GffZoEiQ0$R6dA|3w27HBm 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 z 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

N+W*Q_>{H&2lwvZn*zEswczQ!I5ARL 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 AApigX diff --git a/files/water/underwater.cg b/files/water/underwater.cg deleted file mode 100644 index 829d34347..000000000 --- a/files/water/underwater.cg +++ /dev/null @@ -1,61 +0,0 @@ -void main_vp -( - in float4 inPos : POSITION, - - out float4 pos : POSITION, - out float2 uv0 : TEXCOORD0, - out float4 noiseCoord : TEXCOORD1, - - uniform float4x4 worldViewProj, - uniform float timeVal, - uniform float scale -) -{ - // Use standardise transform, so work accord with render system specific (RS depth, requires texture flipping, etc) - pos = mul(worldViewProj, inPos); - - // The input positions adjusted by texel offsets, so clean up inaccuracies - inPos.xy = sign(inPos.xy); - - // Convert to image-space - uv0 = (float2(inPos.x, -inPos.y) + 1.0f) * 0.5f; - noiseCoord = (pos + timeVal) * scale; -} - - - -float4 main_fp_nomrt (float2 iTexCoord : TEXCOORD0, - float3 noiseCoord : TEXCOORD1, - uniform sampler2D RT : register(s0), - uniform sampler2D NormalMap : register(s1), - uniform sampler2D CausticMap : register(s2)) : COLOR -{ - float4 normal = tex2D(NormalMap, noiseCoord) * 2 - 1; - - float4 col = tex2D(RT, iTexCoord + normal.xy * 0.015) + - (tex2D(CausticMap, noiseCoord) / 5); - col.xyz = lerp(col.xyz, float3(0.15, 0.40, 0.40), 0.4); - return col; - -} - - -float4 main_fp (float2 iTexCoord : TEXCOORD0, - float3 noiseCoord : TEXCOORD1, - uniform float far, - uniform sampler2D RT : register(s0), - uniform sampler2D NormalMap : register(s1), - uniform sampler2D CausticMap : register(s2), - uniform sampler2D DepthMap : register(s3)) : COLOR -{ - float4 normal = tex2D(NormalMap, noiseCoord) * 2 - 1; - - float depth = tex2D(DepthMap, iTexCoord + normal.xy * 0.015).r * far; - depth = saturate(depth / 2000.f); - - float4 color = tex2D(RT, iTexCoord + normal.xy * 0.015) + - (tex2D(CausticMap, noiseCoord) / 5); - color.xyz = lerp(color.xyz, float3(0.15, 0.40, 0.40), 0.4); - - return lerp(color, float4(0.15, 0.40, 0.40, 1), depth); -} diff --git a/files/water/water.cg b/files/water/water.cg deleted file mode 100644 index ad0ff57f7..000000000 --- a/files/water/water.cg +++ /dev/null @@ -1,121 +0,0 @@ -void main_vp -( - in float4 iPos : POSITION - , in float2 iUv : TEXCOORD0 - - , out float4 oPos : POSITION - , out float3 oScreenCoords : TEXCOORD0 - , out float2 oUv : TEXCOORD1 - , out float oDepth : TEXCOORD2 - , out float4 oEyeVector : TEXCOORD3 - - , uniform float4x4 wvpMat - , uniform float4 camPosObjSpace -) -{ - oPos = mul(wvpMat, iPos); - - oUv = iUv * 10; // uv scale - oDepth = oPos.z; - - float4x4 scalemat = float4x4( 0.5, 0, 0, 0.5, - 0, -0.5, 0, 0.5, - 0, 0, 0.5, 0.5, - 0, 0, 0, 1 ); - float4 texcoordProj = mul(scalemat, oPos); - oScreenCoords = float3(texcoordProj.x, texcoordProj.y, texcoordProj.w); - - oEyeVector = camPosObjSpace - iPos; -} - -void main_fp -( - out float4 oColor : COLOR - - , in float3 iScreenCoords : TEXCOORD0 - , in float2 iUv : TEXCOORD1 - , in float iDepth : TEXCOORD2 - , in float4 iEyeVector : TEXCOORD3 - , uniform float renderTargetFlipping - , uniform float4 lightPosObjSpace0 - , uniform float4 lightSpecularColour0 - - , uniform sampler2D reflectionMap : register(s0) - , uniform sampler2D refractionMap : register(s1) - , uniform sampler2D depthMap : register(s2) - , uniform sampler2D normalMap : register(s3) - , uniform float time - , uniform float far - , uniform float4 fogParams - , uniform float4 fogColour - , uniform float isUnderwater -) -{ - - float2 screenCoords = iScreenCoords.xy / iScreenCoords.z; - screenCoords.y = (1-saturate(renderTargetFlipping))+renderTargetFlipping*screenCoords.y; - - // No need for transparency since we are using a refraction map - oColor.a = 1; - - // Sample screen-space depth map and subtract pixel depth to get the real water depth - float depthTex = tex2D(depthMap, screenCoords).r; - float depth1 = depthTex * far - iDepth; - depth1 = saturate(depth1 / 500.f); - - // Simple wave effect. to be replaced by something better - float2 uv1 = iUv + time * float2(0.5, 0); - float2 uv2 = iUv + time * float2(0, 0.5); - float2 uv3 = iUv + time * float2(-0.5, 0); - float2 uv4 = iUv + time * float2(0, -0.5); - float4 normal = tex2D(normalMap, uv1) + tex2D(normalMap, uv2) + tex2D(normalMap, uv3) + tex2D(normalMap, uv4); - normal = normal / 4.f; - normal = 2*normal - 1; - - float2 screenCoords_reflect = screenCoords + normal.yx * 0.05; - float2 screenCoords_refract = screenCoords + normal.yx * 0.05 * depth1; - - // Sample depth again with the refracted coordinates - depthTex = tex2D(depthMap, screenCoords_refract).r; - float depth2 = (depthTex * far - iDepth) / 500.f; - depth2 = (depthTex == 0 ? 1 : depth2); - // if depth2 is less than 0, this means we would refract something which is above water, - // which we don't want to - so in that case, don't refract - if (depth2 < 0.25) // delta due to inaccuracies - { - screenCoords_refract = screenCoords; - depth2 = depth1; - } - depth2 = saturate(depth2); - - float4 reflection = tex2D(reflectionMap, screenCoords_reflect); - float4 refraction = tex2D(refractionMap, screenCoords_refract); - - // tangent to object space - normal.xyz = normal.xzy; - - iEyeVector.xyz = normalize(iEyeVector.xyz); - - // fresnel - float facing = 1.0 - max(abs(dot(iEyeVector.xyz, normal.xyz)), 0); - float reflectionFactor = saturate(0.35 + 0.65 * pow(facing, 2)); - - // specular - float3 lightDir = normalize(lightPosObjSpace0.xyz); // assumes that light 0 is a directional light - float3 halfVector = normalize(iEyeVector + lightDir); - float specular = pow(max(dot(normal.xyz, halfVector.xyz), 0), 64); - - float opacity = depth2 * saturate(reflectionFactor + specular); - opacity *= (1-isUnderwater); - - reflection.xyz += lightSpecularColour0.xyz * specular; - - oColor.xyz = lerp(refraction.xyz, reflection.xyz, opacity); - - oColor.xyz = lerp(oColor.xyz, float3(0.15, 0.40, 0.40), isUnderwater*0.6); // underwater tint color - oColor.xyz = lerp(oColor.xyz, float3(0.15, 0.40, 0.40), saturate(isUnderwater * (iDepth / 2000.f))); // underwater fog - - // add fog - //float fogValue = saturate((iDepth - fogParams.y) * fogParams.w); - //oColor.xyz = lerp(oColor.xyz, fogColour, fogValue); -} diff --git a/files/water/water.compositor b/files/water/water.compositor deleted file mode 100644 index 94b778773..000000000 --- a/files/water/water.compositor +++ /dev/null @@ -1,45 +0,0 @@ -compositor UnderwaterNoMRT -{ - technique - { - texture rt0 target_width target_height PF_R8G8B8 - - target rt0 { input previous } - - target_output - { - // Start with clear output - input none - - pass render_quad - { - material Water/CompositorNoMRT - input 0 rt0 - } - } - } -} - - -compositor Underwater -{ - technique - { - texture_ref scene gbuffer mrt_output - texture rt0 target_width target_height PF_R8G8B8 - - target rt0 { input previous } - - target_output - { - // Start with clear output - input none - - pass render_quad - { - material Water/Compositor - input 0 rt0 - } - } - } -} From f5ffea4d4b247cc83d0821e6cde2090651215526 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jul 2012 23:30:41 +0200 Subject: [PATCH 180/298] new button, water timescale --- apps/openmw/mwgui/settingswindow.cpp | 7 +++++++ apps/openmw/mwgui/settingswindow.hpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 6 ++++++ apps/openmw/mwrender/water.cpp | 7 ++++--- files/materials/objects.shader | 2 +- files/materials/terrain.shader | 2 +- files/mygui/openmw_settings_window.layout | 5 +++++ 7 files changed, 25 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 96cdef4cc..4f8ad77c9 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -119,8 +119,10 @@ namespace MWGui getWidget(mStaticsShadows, "StaticsShadows"); getWidget(mMiscShadows, "MiscShadows"); getWidget(mShadowsDebug, "ShadowsDebug"); + getWidget(mUnderwaterButton, "UnderwaterButton"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); + mUnderwaterButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mShadersButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadersToggled); mFullscreenButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mWaterShaderButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); @@ -212,6 +214,7 @@ namespace MWGui mReflectObjectsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect statics", "Water") ? "#{sOn}" : "#{sOff}"); mReflectActorsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect actors", "Water") ? "#{sOn}" : "#{sOff}"); mReflectTerrainButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect terrain", "Water") ? "#{sOn}" : "#{sOff}"); + mUnderwaterButton->setCaptionWithReplacing(Settings::Manager::getBool("underwater effect", "Water") ? "#{sOn}" : "#{sOff}"); mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); mShadowsLargeDistance->setCaptionWithReplacing(Settings::Manager::getBool("split", "Shadows") ? "#{sOn}" : "#{sOff}"); @@ -351,6 +354,10 @@ namespace MWGui { if (_sender == mWaterShaderButton) Settings::Manager::setBool("shader", "Water", newState); + else if (_sender == mUnderwaterButton) + { + Settings::Manager::setBool("underwater effect", "Water", newState); + } else if (_sender == mReflectObjectsButton) { Settings::Manager::setBool("reflect misc", "Water", newState); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 840d266f1..63fbed46b 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -43,6 +43,7 @@ namespace MWGui MyGUI::Button* mReflectActorsButton; MyGUI::Button* mReflectTerrainButton; MyGUI::Button* mShadersButton; + MyGUI::Button* mUnderwaterButton; MyGUI::Button* mShadowsEnabledButton; MyGUI::Button* mShadowsLargeDistance; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e1ff91a24..23d222384 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -108,6 +108,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const sh::Factory::getInstance ().setGlobalSetting ("fog", "true"); sh::Factory::getInstance ().setGlobalSetting ("lighting", "true"); sh::Factory::getInstance ().setGlobalSetting ("num_lights", Settings::Manager::getString ("num lights", "Objects")); + sh::Factory::getInstance ().setGlobalSetting ("underwater_effects", Settings::Manager::getString("underwater effect", "Water")); applyCompositors(); @@ -639,6 +640,11 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); mObjects.rebuildStaticGeometry (); } + else if (it->second == "underwater effect" && it->first == "Water") + { + sh::Factory::getInstance ().setGlobalSetting ("underwater_effects", Settings::Manager::getString("underwater effect", "Water")); + mObjects.rebuildStaticGeometry (); + } else if (it->second == "shaders" && it->first == "Objects") { sh::Factory::getInstance ().setShadersEnabled (Settings::Manager::getBool("shaders", "Objects")); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index bdf61e96d..7bbe107ed 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -18,6 +18,9 @@ #include +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + using namespace Ogre; namespace MWRender @@ -67,8 +70,6 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel createMaterial(); mWater->setMaterial(mMaterial); - mUnderwaterEffect = Settings::Manager::getBool("underwater effect", "Water"); - Ogre::Entity* underwaterDome = mSceneManager->createEntity ("underwater_dome.mesh"); underwaterDome->setRenderQueueGroup (RQG_UnderWater); mUnderwaterDome = mSceneManager->getRootSceneNode ()->createChildSceneNode (); @@ -329,7 +330,7 @@ void Water::update(float dt) pos.y = -mWaterPlane.d; mUnderwaterDome->setPosition (pos); - mWaterTimer += dt; + mWaterTimer += dt / 30.0 * MWBase::Environment::get().getWorld()->getTimeScaleFactor(); sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(mWaterTimer))); mRendering->getSkyManager ()->setGlareEnabled (!mIsUnderwater); diff --git a/files/materials/objects.shader b/files/materials/objects.shader index f40094fa7..90d58da60 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -17,7 +17,7 @@ #endif -#define UNDERWATER LIGHTING +#define UNDERWATER @shGlobalSettingBool(underwater_effects) && LIGHTING #define HAS_VERTEXCOLOR @shPropertyBool(has_vertex_colour) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 919857401..24771ca41 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -22,7 +22,7 @@ #define NEED_DEPTH 1 #endif -#define UNDERWATER LIGHTING +#define UNDERWATER @shGlobalSettingBool(underwater_effects) && LIGHTING #if NEED_DEPTH diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index d8fbca69c..07c307324 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -188,6 +188,11 @@ + + + + + From c2acf47d880d2adfd20a9f6b3eca680a2ec0e31b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 19 Jul 2012 20:30:10 -0700 Subject: [PATCH 181/298] Store the list of keyframe controllers when building the bones --- components/nifogre/ogre_nif_loader.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 8795efbdf..8ba5c9752 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -144,7 +144,7 @@ static void fail(const std::string &msg) } -void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent=NULL) +void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, std::vector &ctrls, Ogre::Bone *parent=NULL) { Ogre::Bone *bone; if(!skel->hasBone(node->name)) @@ -159,6 +159,14 @@ void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent= bone->setBindingPose(); bone->setInitialState(); + Nif::ControllerPtr ctrl = node->controller; + while(!ctrl.empty()) + { + if(ctrl->recType == Nif::RC_NiKeyframeController) + ctrls.push_back(static_cast(ctrl.getPtr())); + ctrl = ctrl->next; + } + const Nif::NiNode *ninode = dynamic_cast(node); if(ninode) { @@ -166,7 +174,7 @@ void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent= for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) - buildBones(skel, children[i].getPtr(), bone); + buildBones(skel, children[i].getPtr(), ctrls, bone); } } } @@ -183,7 +191,13 @@ void loadResource(Ogre::Resource *resource) Nif::NIFFile nif(skel->getName()); const Nif::Node *node = dynamic_cast(nif.getRecord(0)); - buildBones(skel, node); + + std::vector ctrls; + buildBones(skel, node, ctrls); + + // TODO: If ctrls.size() == 0, check for a .kf file sharing the name of the .nif file + if(ctrls.size() == 0) // No animations? Then we're done. + return; } bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node) From 0986cd5962d8dfa3279d761d7352ee441e11ae61 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 19 Jul 2012 20:48:12 -0700 Subject: [PATCH 182/298] Get the animation controller target names --- components/nifogre/ogre_nif_loader.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 8ba5c9752..8cdb2e99b 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -195,9 +195,27 @@ void loadResource(Ogre::Resource *resource) std::vector ctrls; buildBones(skel, node, ctrls); + std::vector targets; // TODO: If ctrls.size() == 0, check for a .kf file sharing the name of the .nif file if(ctrls.size() == 0) // No animations? Then we're done. return; + + float maxtime = 0.0f; + for(size_t i = 0;i < ctrls.size();i++) + { + Nif::NiKeyframeController *ctrl = ctrls[i]; + maxtime = std::max(maxtime, ctrl->timeStop); + Nif::Named *target = dynamic_cast(ctrl->target.getPtr()); + if(target != NULL) + targets.push_back(target->name); + } + + if(targets.size() != ctrls.size()) + { + warn("Target size mismatch ("+Ogre::StringConverter::toString(targets.size())+" targets, "+ + Ogre::StringConverter::toString(ctrls.size())+" controllers)"); + return; + } } bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node) From 4210880c061ea7698e0a96cd0ef7292b55a06c14 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 19 Jul 2012 21:46:16 -0700 Subject: [PATCH 183/298] Load the animation tracks into Ogre --- components/nifogre/ogre_nif_loader.cpp | 97 ++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 8cdb2e99b..eea53e963 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -180,6 +180,15 @@ void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, std::vector vectors */ +template +struct KeyTimeSort +{ + bool operator()(const Nif::KeyT &lhs, const Nif::KeyT &rhs) const + { return lhs.mTime < rhs.mTime; } +}; + + typedef std::map LoaderMap; static LoaderMap sLoaders; @@ -216,6 +225,94 @@ void loadResource(Ogre::Resource *resource) Ogre::StringConverter::toString(ctrls.size())+" controllers)"); return; } + + Ogre::Animation *anim = skel->createAnimation("default", maxtime); + /* HACK: Pre-create the node tracks by matching the track IDs with the + * bone IDs. Otherwise, Ogre animates the wrong bones. */ + size_t bonecount = skel->getNumBones(); + for(size_t i = 0;i < bonecount;i++) + anim->createNodeTrack(i, skel->getBone(i)); + + for(size_t i = 0;i < ctrls.size();i++) + { + Nif::NiKeyframeController *kfc = ctrls[i]; + Nif::NiKeyframeData *kf = kfc->data.getPtr(); + + /* Get the keyframes and make sure they're sorted first to last */ + QuaternionKeyList quatkeys = kf->mRotations; + Vector3KeyList trankeys = kf->mTranslations; + FloatKeyList scalekeys = kf->mScales; + std::sort(quatkeys.mKeys.begin(), quatkeys.mKeys.end(), KeyTimeSort()); + std::sort(trankeys.mKeys.begin(), trankeys.mKeys.end(), KeyTimeSort()); + std::sort(scalekeys.mKeys.begin(), scalekeys.mKeys.end(), KeyTimeSort()); + + QuaternionKeyList::VecType::const_iterator quatiter = quatkeys.mKeys.begin(); + Vector3KeyList::VecType::const_iterator traniter = trankeys.mKeys.begin(); + FloatKeyList::VecType::const_iterator scaleiter = scalekeys.mKeys.begin(); + + Ogre::Bone *bone = skel->getBone(targets[i]); + const Ogre::Quaternion startquat = bone->getInitialOrientation(); + const Ogre::Vector3 starttrans = bone->getInitialPosition(); + const Ogre::Vector3 startscale = bone->getInitialScale(); + Ogre::NodeAnimationTrack *nodetrack = anim->getNodeTrack(bone->getHandle()); + + Ogre::Quaternion lastquat, curquat; + Ogre::Vector3 lasttrans(0.0f), curtrans(0.0f); + Ogre::Vector3 lastscale(1.0f), curscale(1.0f); + if(quatiter != quatkeys.mKeys.end()) + lastquat = curquat = startquat.Inverse() * quatiter->mValue; + if(traniter != trankeys.mKeys.end()) + lasttrans = curtrans = traniter->mValue - starttrans; + if(scaleiter != scalekeys.mKeys.end()) + lastscale = curscale = Ogre::Vector3(scaleiter->mValue) / startscale; + bool didlast = false; + while(!didlast) + { + float curtime = kfc->timeStop; + if(quatiter != quatkeys.mKeys.end()) + curtime = std::min(curtime, quatiter->mTime); + if(traniter != trankeys.mKeys.end()) + curtime = std::min(curtime, traniter->mTime); + if(scaleiter != scalekeys.mKeys.end()) + curtime = std::min(curtime, scaleiter->mTime); + + curtime = std::max(curtime, kfc->timeStart); + if(curtime >= kfc->timeStop) + { + didlast = true; + curtime = kfc->timeStop; + } + + // Get the latest quaternion, translation, and scale for the + // current time + while(quatiter != quatkeys.mKeys.end() && curtime >= quatiter->mTime) + { + lastquat = curquat; + curquat = startquat.Inverse() * quatiter->mValue; + quatiter++; + } + while(traniter != trankeys.mKeys.end() && curtime >= traniter->mTime) + { + lasttrans = curtrans; + curtrans = traniter->mValue - starttrans; + traniter++; + } + while(scaleiter != scalekeys.mKeys.end() && curtime >= scaleiter->mTime) + { + lastscale = curscale; + curscale = Ogre::Vector3(scaleiter->mValue) / startscale; + scaleiter++; + } + + Ogre::TransformKeyFrame *kframe; + kframe = nodetrack->createNodeKeyFrame(curtime); + // FIXME: These should be interpolated since they don't all fall on the same time + kframe->setRotation(curquat); + kframe->setTranslate(curtrans); + kframe->setScale(curscale); + } + } + anim->optimise(); } bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node) From 8b5b74f9ee3ec4b291e87d92fb01bfd78b177638 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 19 Jul 2012 22:34:26 -0700 Subject: [PATCH 184/298] Add a quick hack to let "playgroup all" work on creatures and NPCs --- apps/openmw/mwrender/creatureanimation.cpp | 42 +++++++++++++++------- apps/openmw/mwrender/npcanimation.cpp | 42 +++++++++++++++------- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index fd2855154..492749553 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -52,6 +52,18 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O } ent->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); } + + if(mEntityList.mSkelBase) + { + Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); + while(as.hasMoreElements()) + { + Ogre::AnimationState *state = as.getNext(); + state->setEnabled(true); + state->setLoop(false); + } + } } } @@ -59,20 +71,26 @@ void CreatureAnimation::runAnimation(float timepassed) { if(mAnimate > 0) { - //Add the amount of time passed to time - - //Handle the animation transforms dependent on time - - //Handle the shapes dependent on animation transforms mTime += timepassed; - if(mTime >= mStopTime) + + if(mEntityList.mSkelBase) { - mAnimate--; - //std::cout << "Stopping the animation\n"; - if(mAnimate == 0) - mTime = mStopTime; - else - mTime = mStartTime + (mTime - mStopTime); + Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); + while(as.hasMoreElements()) + { + Ogre::AnimationState *state = as.getNext(); + state->setTimePosition(mTime); + if(state->getTimePosition() >= state->getLength()) + { + mAnimate--; + //std::cout << "Stopping the animation\n"; + if(mAnimate == 0) + mTime = state->getLength(); + else + mTime = mTime - state->getLength(); + } + } } handleAnimationTransforms(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index a693a6b46..8c34acba2 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -117,6 +117,18 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere //stay in the same place when we skipanim, or open a gui window } + if(mEntityList.mSkelBase) + { + Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); + while(as.hasMoreElements()) + { + Ogre::AnimationState *state = as.getNext(); + state->setEnabled(true); + state->setLoop(false); + } + } + if(isFemale) mInsert->scale(race->data.height.female, race->data.height.female, race->data.height.female); else @@ -361,24 +373,30 @@ void NpcAnimation::runAnimation(float timepassed) timeToChange = 0; updateParts(); } - timeToChange += timepassed; - //1. Add the amount of time passed to time - - //2. Handle the animation transforms dependent on time - - //3. Handle the shapes dependent on animation transforms if(mAnimate > 0) { mTime += timepassed; - if(mTime > mStopTime) + + if(mEntityList.mSkelBase) { - mAnimate--; - if(mAnimate == 0) - mTime = mStopTime; - else - mTime = mStartTime + (mTime - mStopTime); + Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); + while(as.hasMoreElements()) + { + Ogre::AnimationState *state = as.getNext(); + state->setTimePosition(mTime); + if(state->getTimePosition() >= state->getLength()) + { + mAnimate--; + //std::cout << "Stopping the animation\n"; + if(mAnimate == 0) + mTime = state->getLength(); + else + mTime = mTime - state->getLength(); + } + } } handleAnimationTransforms(); From 66860825cfd4cc74154eb9fb147ed3e73506211c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 20 Jul 2012 00:29:22 -0700 Subject: [PATCH 185/298] Remove some unused and unneeded bits from the Animation class --- apps/openmw/mwrender/animation.cpp | 137 +-------------------- apps/openmw/mwrender/animation.hpp | 14 --- apps/openmw/mwrender/creatureanimation.cpp | 2 - apps/openmw/mwrender/npcanimation.cpp | 2 - 4 files changed, 1 insertion(+), 154 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index fddfe7b8a..06fd34e3c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -15,15 +15,7 @@ Animation::Animation(OEngine::Render::OgreRenderer& _rend) : mInsert(NULL) , mRend(_rend) , mTime(0.0f) - , mStartTime(0.0f) - , mStopTime(0.0f) , mAnimate(0) - , mRindexI() - , mTindexI() - , mShapeNumber(0) - , mShapeIndexI() - , mTransformations(NULL) - , mTextmappings(NULL) { } @@ -37,79 +29,11 @@ Animation::~Animation() void Animation::startScript(std::string groupname, int mode, int loops) { - //If groupname is recognized set animate to true - //Set the start time and stop time - //How many times to loop if(groupname == "all") { mAnimate = loops; - mTime = mStartTime; + mTime = 0.0f; } - else if(mTextmappings) - { - std::string startName = groupname + ": loop start"; - std::string stopName = groupname + ": loop stop"; - - bool first = false; - - if(loops > 1) - { - startName = groupname + ": loop start"; - stopName = groupname + ": loop stop"; - - for(std::map::iterator iter = mTextmappings->begin(); iter != mTextmappings->end(); iter++) - { - std::string current = iter->first.substr(0, startName.size()); - std::transform(current.begin(), current.end(), current.begin(), ::tolower); - std::string current2 = iter->first.substr(0, stopName.size()); - std::transform(current2.begin(), current2.end(), current2.begin(), ::tolower); - - if(current == startName) - { - mStartTime = iter->second; - mAnimate = loops; - mTime = mStartTime; - first = true; - } - if(current2 == stopName) - { - mStopTime = iter->second; - if(first) - break; - } - } - } - - if(!first) - { - startName = groupname + ": start"; - stopName = groupname + ": stop"; - - for(std::map::iterator iter = mTextmappings->begin(); iter != mTextmappings->end(); iter++) - { - std::string current = iter->first.substr(0, startName.size()); - std::transform(current.begin(), current.end(), current.begin(), ::tolower); - std::string current2 = iter->first.substr(0, stopName.size()); - std::transform(current2.begin(), current2.end(), current2.begin(), ::tolower); - - if(current == startName) - { - mStartTime = iter->second; - mAnimate = loops; - mTime = mStartTime; - first = true; - } - if(current2 == stopName) - { - mStopTime = iter->second; - if(first) - break; - } - } - } - - } - } @@ -118,63 +42,4 @@ void Animation::stopScript() mAnimate = 0; } - -bool Animation::timeIndex(float time, const std::vector ×, int &i, int &j, float &x) -{ - size_t count; - if((count=times.size()) == 0) - return false; - - if(time <= times[0]) - { - i = j = 0; - x = 0.0; - return true; - } - if(time >= times[count-1]) - { - i = j = count - 1; - x = 0.0; - return true; - } - - if(i < 0 || (size_t)i >= count) - i = 0; - - float tI = times[i]; - if(time > tI) - { - j = i + 1; - float tJ; - while(time >= (tJ=times[j])) - { - i = j++; - tI = tJ; - } - x = (time-tI) / (tJ-tI); - return true; - } - - if(time < tI) - { - j = i - 1; - float tJ; - while(time <= (tJ=times[j])) - { - i = j--; - tI = tJ; - } - x = (time-tI) / (tJ-tI); - return true; - } - - j = i; - x = 0.0; - return true; -} - -void Animation::handleAnimationTransforms() -{ -} - } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index def7f226c..bed02f85f 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -26,23 +26,9 @@ protected: static std::map sUniqueIDs; float mTime; - float mStartTime; - float mStopTime; int mAnimate; - //Represents a rotation index for each bone - std::vectormRindexI; - //Represents a translation index for each bone - std::vectormTindexI; - //Only shapes with morphing data will use a shape number - int mShapeNumber; - std::vector > mShapeIndexI; - - std::vector* mTransformations; - std::map* mTextmappings; NifOgre::EntityList mEntityList; - void handleAnimationTransforms(); - bool timeIndex( float time, const std::vector & times, int & i, int & j, float & x ); public: Animation(OEngine::Render::OgreRenderer& _rend); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 492749553..ee1fd45ba 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -92,8 +92,6 @@ void CreatureAnimation::runAnimation(float timepassed) } } } - - handleAnimationTransforms(); } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 8c34acba2..4d2ca557d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -398,8 +398,6 @@ void NpcAnimation::runAnimation(float timepassed) } } } - - handleAnimationTransforms(); } } From 2db80a1504576d15df11b9988fb5beabf3f4bf8e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 20 Jul 2012 00:53:12 -0700 Subject: [PATCH 186/298] Rename a couple methods to match their scripting counterparts --- apps/openmw/mwrender/actors.cpp | 8 +++----- apps/openmw/mwrender/animation.cpp | 4 ++-- apps/openmw/mwrender/animation.hpp | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index b37921b0c..a64397d49 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -122,15 +122,13 @@ void Actors::removeCell(MWWorld::Ptr::CellStore* store){ void Actors::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number){ if(mAllActors.find(ptr) != mAllActors.end()) - mAllActors[ptr]->startScript(groupName, mode, number); + mAllActors[ptr]->playGroup(groupName, mode, number); } void Actors::skipAnimation (const MWWorld::Ptr& ptr){ if(mAllActors.find(ptr) != mAllActors.end()) - mAllActors[ptr]->stopScript(); + mAllActors[ptr]->skipAnim(); } void Actors::update (float duration){ for(std::map::iterator iter = mAllActors.begin(); iter != mAllActors.end(); iter++) - { - (iter->second)->runAnimation(duration); - } + iter->second->runAnimation(duration); } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 06fd34e3c..1f0a99518 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -27,7 +27,7 @@ Animation::~Animation() mEntityList.mEntities.clear(); } -void Animation::startScript(std::string groupname, int mode, int loops) +void Animation::playGroup(std::string groupname, int mode, int loops) { if(groupname == "all") { @@ -37,7 +37,7 @@ void Animation::startScript(std::string groupname, int mode, int loops) } -void Animation::stopScript() +void Animation::skipAnim() { mAnimate = 0; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index bed02f85f..0ed4545f2 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -32,11 +32,11 @@ protected: public: Animation(OEngine::Render::OgreRenderer& _rend); - virtual void runAnimation(float timepassed) = 0; - void startScript(std::string groupname, int mode, int loops); - void stopScript(); - virtual ~Animation(); + + void playGroup(std::string groupname, int mode, int loops); + void skipAnim(); + virtual void runAnimation(float timepassed) = 0; }; } From 21728020f6c6b017024f98595f43f29cad3e1e44 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 20 Jul 2012 14:45:42 +0200 Subject: [PATCH 187/298] fixed the water on local map --- apps/openmw/mwrender/renderingmanager.cpp | 2 + apps/openmw/mwrender/water.cpp | 63 +++--- apps/openmw/mwrender/water.hpp | 8 +- extern/shiny | 2 +- files/materials/openmw.configuration | 1 + files/materials/water.mat | 14 ++ files/materials/water.shader | 64 +++++++ files/water/water.material | 221 ---------------------- 8 files changed, 122 insertions(+), 253 deletions(-) delete mode 100644 files/water/water.material diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 23d222384..d0060c243 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -109,6 +109,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const sh::Factory::getInstance ().setGlobalSetting ("lighting", "true"); sh::Factory::getInstance ().setGlobalSetting ("num_lights", Settings::Manager::getString ("num lights", "Objects")); sh::Factory::getInstance ().setGlobalSetting ("underwater_effects", Settings::Manager::getString("underwater effect", "Water")); + sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); applyCompositors(); @@ -638,6 +639,7 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec { applyCompositors(); sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); + sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); mObjects.rebuildStaticGeometry (); } else if (it->second == "underwater effect" && it->first == "Water") diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 7bbe107ed..a886eac93 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -17,6 +17,7 @@ #include "compositors.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -40,6 +41,8 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(0))); sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(1, 0.6))); + mMaterial = MaterialManager::getSingleton().getByName("Water"); + mTop = cell->water; mIsUnderwater = false; @@ -66,8 +69,6 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel applyRTT(); applyVisibilityMask(); - - createMaterial(); mWater->setMaterial(mMaterial); Ogre::Entity* underwaterDome = mSceneManager->createEntity ("underwater_dome.mesh"); @@ -84,6 +85,9 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel setHeight(mTop); + sh::MaterialInstance* m = sh::Factory::getInstance ().getMaterialInstance ("Water"); + m->setListener (this); + // ---------------------------------------------------------------------------------------------- // ---------------------------------- reflection debug overlay ---------------------------------- @@ -106,7 +110,7 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false); - TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(mReflectionTexture->getName()); + debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(mReflectionTexture->getName()); OverlayContainer* debugPanel; @@ -238,29 +242,6 @@ void Water::postRenderTargetUpdate(const RenderTargetEvent& evt) } } -void Water::createMaterial() -{ - if (mReflectionTarget == 0) - { - mMaterial = MaterialManager::getSingleton().getByName("Water_Fallback"); - } - else - { - mMaterial = MaterialManager::getSingleton().getByName("Water"); - sh::Factory::getInstance ().setTextureAlias ("WaterReflection", mReflectionTexture->getName()); - } - - // these have to be set in code - std::string textureNames[32]; - for (int i=0; i<32; ++i) - { - textureNames[i] = "textures\\water\\water" + StringConverter::toString(i, 2, '0') + ".dds"; - } - - if (mReflectionTarget == 0) - mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); -} - void Water::assignTextures() { if (Settings::Manager::getBool("shader", "Water")) @@ -292,7 +273,7 @@ void Water::updateVisible() void Water::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation) { // We don't want the sky to get clipped by custom near clip plane (the water plane) - if (((queueGroupId < 20) || queueGroupId == RQG_UnderWater) && mReflectionRenderActive) + if (queueGroupId < 20 && mReflectionRenderActive) { mOldFarClip = mReflectionCamera->getFarClipDistance (); mReflectionCamera->disableCustomNearClipPlane(); @@ -309,7 +290,7 @@ void Water::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &in void Water::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation) { - if (((queueGroupId < 20) || queueGroupId == RQG_UnderWater) && mReflectionRenderActive) + if (queueGroupId < 20 && mReflectionRenderActive) { mReflectionCamera->setFarClipDistance (mOldFarClip); if (!mIsUnderwater) @@ -363,6 +344,8 @@ void Water::applyRTT() rtt->setActive(true); mReflectionTarget = rtt; + + sh::Factory::getInstance ().setTextureAlias ("WaterReflection", mReflectionTexture->getName()); } } @@ -406,7 +389,6 @@ void Water::processChangedSettings(const Settings::CategorySettingVector& settin { applyRTT(); applyVisibilityMask(); - createMaterial(); mWater->setMaterial(mMaterial); assignTextures(); } @@ -414,4 +396,27 @@ void Water::processChangedSettings(const Settings::CategorySettingVector& settin applyVisibilityMask(); } +void Water::requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration) +{ +} + +void Water::createdConfiguration (sh::MaterialInstance* m, const std::string& configuration) +{ + if (configuration == "local_map" || !Settings::Manager::getBool("shader", "Water")) + { + // for simple water, set animated texture names + // these have to be set in code + std::string textureNames[32]; + for (int i=0; i<32; ++i) + { + textureNames[i] = "textures\\water\\water" + StringConverter::toString(i, 2, '0') + ".dds"; + } + + Ogre::Technique* t = static_cast(m->getMaterial())->getOgreTechniqueForConfiguration(configuration); + t->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); + t->getPass(0)->setDepthWriteEnabled (false); + t->getPass(0)->setSceneBlending (Ogre::SBT_TRANSPARENT_ALPHA); + } +} + } // namespace diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 6d0001119..60e39c496 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -13,6 +13,8 @@ #include "renderconst.hpp" +#include + namespace Ogre { class Camera; @@ -29,7 +31,7 @@ namespace MWRender { class RenderingManager; /// Water rendering - class Water : public Ogre::RenderTargetListener, public Ogre::RenderQueueListener + class Water : public Ogre::RenderTargetListener, public Ogre::RenderQueueListener, public sh::MaterialInstanceListener { static const int CELL_SIZE = 8192; Ogre::Camera *mCamera; @@ -74,7 +76,6 @@ namespace MWRender { std::string mCompositorName; - void createMaterial(); Ogre::MaterialPtr mMaterial; Ogre::Camera* mReflectionCamera; @@ -104,6 +105,9 @@ namespace MWRender { void changeCell(const ESM::Cell* cell); void setHeight(const float height); + virtual void requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration); + virtual void createdConfiguration (sh::MaterialInstance* m, const std::string& configuration); + }; } diff --git a/extern/shiny b/extern/shiny index 4853ea735..7af50bf44 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 4853ea7351edced75a48662da5f3e857961e0b47 +Subproject commit 7af50bf4463a828ee0e8cb72c93445c793864bf9 diff --git a/files/materials/openmw.configuration b/files/materials/openmw.configuration index cddf49a03..ee97451d3 100644 --- a/files/materials/openmw.configuration +++ b/files/materials/openmw.configuration @@ -13,4 +13,5 @@ configuration local_map lighting false shadows false shadows_pssm false + simple_water true } diff --git a/files/materials/water.mat b/files/materials/water.mat index 2ae561d77..dcea5a0d0 100644 --- a/files/materials/water.mat +++ b/files/materials/water.mat @@ -2,6 +2,11 @@ material Water { pass { + emissive 0.6 0.7 1.0 + ambient 0 0 0 + diffuse 0 0 0 1 + specular 0 0 0 32 + vertex_program water_vertex fragment_program water_fragment @@ -29,6 +34,15 @@ material Water { direct_texture water_nm.png } + + + // for simple_water + texture_unit animatedTexture + { + create_in_ffp true + scale 0.1 0.1 + alpha_op_ex source1 src_manual src_current 0.7 + } } } diff --git a/files/materials/water.shader b/files/materials/water.shader index 4bfc32421..a5eb9b189 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -1,7 +1,68 @@ #include "core.h" + +#define SIMPLE_WATER @shGlobalSettingBool(simple_water) + + +#if SIMPLE_WATER + // --------------------------------------- SIMPLE WATER --------------------------------------------------- + + + #define MRT @shGlobalSettingBool(mrt_output) + + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shInput(float2, uv0) + shOutput(float2, UV) + shOutput(float, depth) + + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + depth = shOutputPosition.z; + } + +#else + + SH_BEGIN_PROGRAM + shSampler2D(animatedTexture) + shInput(float2, UV) + shInput(float, depth) +#if MRT + shDeclareMrtOutput(1) +#endif + + shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) + shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) + + + SH_START_PROGRAM + { + shOutputColour(0).xyz = shSample(animatedTexture, UV * 15).xyz * float3(0.6, 0.7, 1.0); + shOutputColour(0).w = 0.7; + + float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); + +#if MRT + shOutputColour(1) = float4(1,1,1,1); +#endif + } + +#endif + +#else + + + // Inspired by Blender GLSL Water by martinsh ( http://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) + + #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM @@ -241,3 +302,6 @@ } #endif + + +#endif diff --git a/files/water/water.material b/files/water/water.material deleted file mode 100644 index 7376d10cc..000000000 --- a/files/water/water.material +++ /dev/null @@ -1,221 +0,0 @@ -vertex_program UnderwaterEffectVP cg -{ - source underwater.cg - entry_point main_vp - profiles vs_1_1 arbvp1 - - default_params - { - param_named_auto worldViewProj worldviewproj_matrix - } -} - - -fragment_program UnderwaterEffectFP_NoMRT cg -{ - source underwater.cg - entry_point main_fp_nomrt - profiles ps_2_0 arbfp1 -} - -fragment_program UnderwaterEffectFP cg -{ - source underwater.cg - entry_point main_fp - profiles ps_2_0 arbfp1 -} - -vertex_program Water_VP cg -{ - source water.cg - entry_point main_vp - profiles vs_2_x arbvp1 - - default_params - { - param_named_auto wvpMat worldviewproj_matrix - } -} - -fragment_program Water_FP cg -{ - source water.cg - entry_point main_fp - profiles ps_2_x arbfp1 -} - -material __Water -{ - technique - { - pass - { - cull_hardware none - - vertex_program_ref Water_VP - { - param_named_auto camPosObjSpace camera_position_object_space - } - fragment_program_ref Water_FP - { - param_named_auto time time 0.1 - //param_named_auto fogColour fog_colour - //param_named_auto fogParams fog_params - param_named_auto renderTargetFlipping render_target_flipping - param_named_auto far far_clip_distance - param_named_auto lightPosObjSpace0 light_position_object_space 0 - param_named_auto lightSpecularColour0 light_specular_colour 0 - param_named isUnderwater float 0 - } - - texture_unit reflectionMap - { - texture WaterReflection - tex_address_mode clamp - } - - texture_unit refractionMap - { - tex_address_mode clamp - } - - texture_unit depthMap - { - tex_address_mode clamp - } - - texture_unit normalMap - { - texture WaterNormal2.tga - } - } - } - - technique - { - scheme Fallback - pass - { - cull_hardware none - scene_blend alpha_blend - depth_write off - diffuse 0 0 0 1 - emissive 0.6 0.7 1.0 - ambient 0 0 0 - texture_unit - { - // texture names set via code - scale 0.1 0.1 - alpha_op_ex source1 src_manual src_current 0.7 - } - } - } - -} - -material Water_Fallback -{ - technique - { - scheme Fallback - pass - { - cull_hardware none - scene_blend alpha_blend - depth_write off - diffuse 0 0 0 1 - emissive 0.6 0.7 1.0 - ambient 0 0 0 - texture_unit - { - // texture names set via code - scale 0.1 0.1 - alpha_op_ex source1 src_manual src_current 0.7 - } - } - } -} - -material Water/CompositorNoMRT -{ - technique - { - pass - { - depth_check off - vertex_program_ref UnderwaterEffectVP - { - param_named_auto timeVal time 0.25 - param_named scale float 0.1 - } - - fragment_program_ref UnderwaterEffectFP_NoMRT - { - } - - texture_unit RT - { - tex_coord_set 0 - tex_address_mode clamp - filtering linear linear linear - } - - texture_unit - { - texture WaterNormal2.tga 2d - tex_coord_set 1 - //tex_address_mode clamp - filtering linear linear linear - } - texture_unit - { - texture caustic_0.png 2d - tex_coord_set 2 - //tex_address_mode clamp - filtering linear linear linear - } - } - } -} - -material Water/Compositor -{ - technique - { - pass - { - depth_check off - vertex_program_ref UnderwaterEffectVP - { - param_named_auto timeVal time 0.25 - param_named scale float 0.1 - } - - fragment_program_ref UnderwaterEffectFP - { - param_named_auto far far_clip_distance - } - - texture_unit RT - { - tex_coord_set 0 - tex_address_mode clamp - } - - texture_unit - { - texture WaterNormal2.tga 2d - tex_coord_set 2 - } - texture_unit - { - texture caustic_0.png 2d - tex_coord_set 3 - } - - texture_unit DepthMap - { - } - } - } -} From 014396e80c376663b8bba9ac598f1828d569ea49 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 20 Jul 2012 16:44:03 +0200 Subject: [PATCH 188/298] remove the plugins.cfg files, do not enforce CG plugin --- CMakeLists.txt | 39 ++++++++++------------- apps/launcher/graphicspage.cpp | 35 +++++++++++++++++--- apps/openmw/engine.cpp | 1 - apps/openmw/mwrender/renderingmanager.cpp | 11 ++++--- apps/openmw/mwrender/water.cpp | 4 +++ components/files/configurationmanager.cpp | 17 ---------- components/files/configurationmanager.hpp | 2 -- files/gbuffer/gbuffer.compositor | 7 +++- files/materials/atmosphere.shader | 2 ++ files/materials/clouds.shader | 2 +- files/materials/objects.shader | 5 +-- files/materials/quad.mat | 13 ++++++++ files/materials/quad.shaderset | 16 ++++++++++ files/materials/water.shader | 2 +- files/plugins.cfg.linux | 11 ------- files/plugins.cfg.mac | 12 ------- files/plugins.cfg.win32 | 13 -------- libs/openengine/ogre/renderer.cpp | 35 ++++++++++++++++++-- libs/openengine/ogre/renderer.hpp | 1 - 19 files changed, 131 insertions(+), 97 deletions(-) delete mode 100644 files/plugins.cfg.linux delete mode 100644 files/plugins.cfg.mac delete mode 100644 files/plugins.cfg.win32 diff --git a/CMakeLists.txt b/CMakeLists.txt index 269fe456b..c7e23a98d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,14 +20,6 @@ set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") -# Debug suffix for plugins -set(DEBUG_SUFFIX "") -if (DEFINED CMAKE_BUILD_TYPE) - if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(DEBUG_SUFFIX "_d") - endif() -endif() - # doxygen main page configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp") @@ -230,6 +222,22 @@ if (APPLE) ${OGRE_Plugin_ParticleFX_LIBRARY_REL}) endif (APPLE) + +# Set up Ogre plugin folder & debug suffix +set(DEBUG_SUFFIX "") +if (DEFINED CMAKE_BUILD_TYPE) + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(DEBUG_SUFFIX "_d") + add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") + else() + add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="") + endif() +endif() +add_definitions(-DOGRE_PLUGIN_DIR_REL="${OGRE_PLUGIN_DIR_REL}") +add_definitions(-DOGRE_PLUGIN_DIR_DBG="${OGRE_PLUGIN_DIR_DBG}") +add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}") + + add_subdirectory(files/) add_subdirectory(files/mygui) @@ -254,15 +262,8 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg "${OpenMW_BINARY_DIR}/openmw.cfg.install") -if (WIN32) - configure_file(${OpenMW_SOURCE_DIR}/files/plugins.cfg.win32 - "${OpenMW_BINARY_DIR}/plugins.cfg" COPYONLY) -endif (WIN32) if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") - configure_file(${OpenMW_SOURCE_DIR}/files/plugins.cfg.linux - "${OpenMW_BINARY_DIR}/plugins.cfg") - configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop "${OpenMW_BINARY_DIR}/openmw.desktop") endif() @@ -284,13 +285,8 @@ if (APPLE) set(OGRE_PLUGIN_DIR "${OGRE_PLUGIN_DIR}/") - configure_file(${OpenMW_SOURCE_DIR}/files/plugins.cfg.mac - "${OpenMW_BINARY_DIR}/plugins.cfg") - set(OGRE_PLUGIN_DIR_2 ${OGRE_PLUGIN_DIR}) set(OGRE_PLUGIN_DIR "") - configure_file(${OpenMW_SOURCE_DIR}/files/plugins.cfg.mac - "${OpenMW_BINARY_DIR}/plugins.cfg.install") set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_2}) configure_file(${OpenMW_SOURCE_DIR}/files/mac/Info.plist @@ -337,7 +333,6 @@ if(DPKG_PROGRAM) INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "../etc/openmw/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "../etc/openmw/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "../etc/openmw/" RENAME "openmw.cfg" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") - INSTALL(FILES "${OpenMW_BINARY_DIR}/plugins.cfg" DESTINATION "../etc/openmw/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") #Install resources INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "share/games/openmw/" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") @@ -375,7 +370,6 @@ if(WIN32) INSTALL(FILES ${dll_files} DESTINATION ".") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") INSTALL(FILES - "${OpenMW_BINARY_DIR}/plugins.cfg" "${OpenMW_SOURCE_DIR}/readme.txt" "${OpenMW_SOURCE_DIR}/GPL3.txt" "${OpenMW_SOURCE_DIR}/OFL.txt" @@ -547,7 +541,6 @@ if (APPLE) install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/plugins.cfg.install" RENAME "plugins.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/launcher.qss" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index fb798fee8..8caa2b550 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -1,6 +1,9 @@ #include +#include + #include +#include #include #include @@ -70,9 +73,6 @@ GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, QWidget *parent) bool GraphicsPage::setupOgre() { - QString pluginCfg = mCfgMgr.getPluginsConfigPath().string().c_str(); - QFile file(pluginCfg); - // Create a log manager so we can surpress debug text to stdout/stderr Ogre::LogManager* logMgr = OGRE_NEW Ogre::LogManager; logMgr->createLog((mCfgMgr.getLogPath().string() + "/launcherOgre.log"), true, false, false); @@ -82,7 +82,7 @@ bool GraphicsPage::setupOgre() #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) mOgre = new Ogre::Root("", "", "./launcherOgre.log"); #else - mOgre = new Ogre::Root(pluginCfg.toStdString(), "", "./launcherOgre.log"); + mOgre = new Ogre::Root("", "", "./launcherOgre.log"); #endif } catch(Ogre::Exception &ex) @@ -93,7 +93,6 @@ bool GraphicsPage::setupOgre() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("
Failed to create the Ogre::Root object

\ - Make sure the plugins.cfg is present and valid.

\ Press \"Show Details...\" for more information.
")); msgBox.setDetailedText(ogreError); msgBox.exec(); @@ -102,6 +101,32 @@ bool GraphicsPage::setupOgre() return false; } + + std::string pluginDir; + const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); + if (pluginEnv) + pluginDir = pluginEnv; + else + { +#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + pluginDir = ".\\"; +#endif +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + pluginDir = OGRE_PLUGIN_DIR; +#endif +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX + pluginDir = OGRE_PLUGIN_DIR_REL; +#endif + } + + std::string glPlugin = std::string(pluginDir) + "/RenderSystem_GL" + OGRE_PLUGIN_DEBUG_SUFFIX; + if (boost::filesystem::exists(glPlugin + ".so") || boost::filesystem::exists(glPlugin + ".dll")) + mOgre->loadPlugin (glPlugin); + + std::string dxPlugin = std::string(pluginDir) + "/RenderSystem_Direct3D9" + OGRE_PLUGIN_DEBUG_SUFFIX; + if (boost::filesystem::exists(dxPlugin + ".so") || boost::filesystem::exists(dxPlugin + ".dll")) + mOgre->loadPlugin (dxPlugin); + #ifdef ENABLE_PLUGIN_GL mGLPlugin = new Ogre::GLPlugin(); mOgre->installPlugin(mGLPlugin); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7966639a6..96fbeb9e2 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -292,7 +292,6 @@ void OMW::Engine::go() } mOgre->configure( mCfgMgr.getLogPath().string(), - mCfgMgr.getPluginsConfigPath().string(), renderSystem, false); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d0060c243..852288d14 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -111,6 +111,9 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const sh::Factory::getInstance ().setGlobalSetting ("underwater_effects", Settings::Manager::getString("underwater effect", "Water")); sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); + sh::Factory::getInstance ().setSharedParameter ("viewportBackground", sh::makeProperty (new sh::Vector3(0,0,0))); + sh::Factory::getInstance ().setSharedParameter ("waterEnabled", sh::makeProperty (new sh::FloatValue(0.0))); + applyCompositors(); // Turn the entire scene (represented by the 'root' node) -90 @@ -379,11 +382,9 @@ void RenderingManager::configureFog(const float density, const Ogre::ColourValue mRendering.getCamera()->setFarClipDistance ( max / density ); mRendering.getViewport()->setBackgroundColour (colour); - CompositorInstance* inst = CompositorManager::getSingleton().getCompositorChain(mRendering.getViewport())->getCompositor("gbuffer"); - if (inst != 0) - inst->getCompositor()->getTechnique(0)->getTargetPass(0)->getPass(0)->setClearColour(colour); - if (mWater) - mWater->setViewportBackground(colour); + sh::Factory::getInstance ().setSharedParameter ("viewportBackground", + sh::makeProperty (new sh::Vector3(colour.r, colour.g, colour.b))); + } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index a886eac93..1c0afab67 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -141,6 +141,8 @@ void Water::setActive(bool active) { mActive = active; updateVisible(); + + sh::Factory::getInstance ().setSharedParameter ("waterEnabled", sh::makeProperty (new sh::FloatValue(active ? 1.0 : 0.0))); } Water::~Water() @@ -413,6 +415,8 @@ void Water::createdConfiguration (sh::MaterialInstance* m, const std::string& co } Ogre::Technique* t = static_cast(m->getMaterial())->getOgreTechniqueForConfiguration(configuration); + if (t->getPass(0)->getNumTextureUnitStates () == 0) + return; t->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); t->getPass(0)->setDepthWriteEnabled (false); t->getPass(0)->setSceneBlending (Ogre::SBT_TRANSPARENT_ALPHA); diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 150a4fcd8..af057ef97 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -15,7 +15,6 @@ namespace Files { static const char* const openmwCfgFile = "openmw.cfg"; -static const char* const pluginsCfgFile = "plugins.cfg"; const char* const mwToken = "?mw?"; const char* const localToken = "?local?"; @@ -27,17 +26,6 @@ ConfigurationManager::ConfigurationManager() { setupTokensMapping(); - mPluginsCfgPath = mFixedPath.getLocalPath() / pluginsCfgFile; - if (!boost::filesystem::is_regular_file(mPluginsCfgPath)) - { - mPluginsCfgPath = mFixedPath.getGlobalPath() / pluginsCfgFile; - if (!boost::filesystem::is_regular_file(mPluginsCfgPath)) - { - std::cerr << "Failed to find " << pluginsCfgFile << " file!" << std::endl; - mPluginsCfgPath.clear(); - } - } - mLogPath = mFixedPath.getUserPath(); } @@ -162,11 +150,6 @@ const boost::filesystem::path& ConfigurationManager::getInstallPath() const return mFixedPath.getInstallPath(); } -const boost::filesystem::path& ConfigurationManager::getPluginsConfigPath() const -{ - return mPluginsCfgPath; -} - const boost::filesystem::path& ConfigurationManager::getLogPath() const { return mLogPath; diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 0c22c6f7d..ecbfac664 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -40,7 +40,6 @@ struct ConfigurationManager const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getInstallPath() const; - const boost::filesystem::path& getPluginsConfigPath() const; const boost::filesystem::path& getLogPath() const; private: @@ -57,7 +56,6 @@ struct ConfigurationManager FixedPathType mFixedPath; - boost::filesystem::path mPluginsCfgPath; boost::filesystem::path mLogPath; TokensMappingContainer mTokensMapping; diff --git a/files/gbuffer/gbuffer.compositor b/files/gbuffer/gbuffer.compositor index b3d80e9d5..04600ce9b 100644 --- a/files/gbuffer/gbuffer.compositor +++ b/files/gbuffer/gbuffer.compositor @@ -9,11 +9,16 @@ compositor gbuffer target mrt_output { input none + pass clear { - // make sure to set this to the viewport background color from outside colour_value 0 0 0 1 } + pass render_quad + { + // this makes sure the depth for background is set to 1 + material openmw_viewport_init + } pass render_scene { // Renders everything except water diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader index 295fa9376..6e22b7ac3 100644 --- a/files/materials/atmosphere.shader +++ b/files/materials/atmosphere.shader @@ -28,6 +28,8 @@ SH_START_PROGRAM { shOutputColour(0) = colourPassthrough * atmosphereColour; + + shOutputColour(0) = float3(0,0,0,1); #if MRT shOutputColour(1) = float4(1,1,1,1); diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index 7677ecd95..aa53e6051 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -43,7 +43,7 @@ float4 albedo = shSample(diffuseMap1, scrolledUV) * (1-cloudBlendFactor) + shSample(diffuseMap2, scrolledUV) * cloudBlendFactor; shOutputColour(0) = colourPassthrough * float4(cloudColour, 1) * albedo * float4(1,1,1, cloudOpacity); - + shOutputColour(0) = float3(0,0,0,1); #if MRT shOutputColour(1) = float4(1,1,1,1); #endif diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 90d58da60..eafce7af6 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -164,6 +164,7 @@ shUniform(float, waterTimer) @shSharedParameter(waterTimer) shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight) + shUniform(float, waterEnabled) @shSharedParameter(waterEnabled) shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) #endif @@ -204,7 +205,7 @@ #if UNDERWATER float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)).xyz; float3 waterEyePos = float3(1,1,1); - if (worldPos.y < waterLevel) + if (worldPos.y < waterLevel && waterEnabled == 1) { float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); @@ -286,7 +287,7 @@ watercolour *= darkness; float isUnderwater = (worldPos.y < waterLevel) ? 1.0 : 0.0; - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, watercolour, fogAmount * isUnderwater); + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, watercolour, fogAmount * isUnderwater * waterEnabled); #endif #if MRT diff --git a/files/materials/quad.mat b/files/materials/quad.mat index 1ada37bc4..afb7f5111 100644 --- a/files/materials/quad.mat +++ b/files/materials/quad.mat @@ -20,3 +20,16 @@ material quad_noDepthWrite parent quad depth_write off } + +material openmw_viewport_init +{ + pass + { + vertex_program viewport_init_vertex + fragment_program viewport_init_fragment + + depth_write off + depth_check off + scene_blend add + } +} diff --git a/files/materials/quad.shaderset b/files/materials/quad.shaderset index a1252b15c..c61497503 100644 --- a/files/materials/quad.shaderset +++ b/files/materials/quad.shaderset @@ -13,3 +13,19 @@ shader_set quad_fragment profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 profiles_hlsl ps_2_0 } + +shader_set viewport_init_vertex +{ + source quad2.shader + type vertex + profiles_cg vs_2_0 vp40 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set viewport_init_fragment +{ + source quad2.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 + profiles_hlsl ps_2_0 +} diff --git a/files/materials/water.shader b/files/materials/water.shader index a5eb9b189..805cf7b81 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -116,7 +116,7 @@ #define SMALL_WAVES_Y 0.1 #define WAVE_CHOPPYNESS 0.15 // wave choppyness - #define WAVE_SCALE 150 // overall wave scale + #define WAVE_SCALE 75 // overall wave scale #define ABBERATION 0.001 // chromatic abberation amount #define BUMP 1.5 // overall water surface bumpiness diff --git a/files/plugins.cfg.linux b/files/plugins.cfg.linux deleted file mode 100644 index 7b8d99e8f..000000000 --- a/files/plugins.cfg.linux +++ /dev/null @@ -1,11 +0,0 @@ -# Defines plugins to load - -# Define plugin folder -PluginFolder=${OGRE_PLUGIN_DIR_REL} - -# Define plugins -Plugin=RenderSystem_GL${DEBUG_SUFFIX} -Plugin=Plugin_ParticleFX${DEBUG_SUFFIX} -Plugin=Plugin_OctreeSceneManager${DEBUG_SUFFIX} -Plugin=Plugin_CgProgramManager${DEBUG_SUFFIX} - diff --git a/files/plugins.cfg.mac b/files/plugins.cfg.mac deleted file mode 100644 index fac18dc8f..000000000 --- a/files/plugins.cfg.mac +++ /dev/null @@ -1,12 +0,0 @@ -# Defines plugins to load - -# Define plugin folder -PluginFolder=${OGRE_PLUGIN_DIR} - -# Define plugins -Plugin=RenderSystem_GL${DEBUG_SUFFIX}.1.8.0 -Plugin=Plugin_ParticleFX${DEBUG_SUFFIX}.1.8.0 -Plugin=Plugin_OctreeSceneManager${DEBUG_SUFFIX}.1.8.0 -Plugin=Plugin_CgProgramManager${DEBUG_SUFFIX}.1.8.0 - - diff --git a/files/plugins.cfg.win32 b/files/plugins.cfg.win32 deleted file mode 100644 index 6b4e9ef9d..000000000 --- a/files/plugins.cfg.win32 +++ /dev/null @@ -1,13 +0,0 @@ -# Defines plugins to load - -# Define plugin folder -PluginFolder=.\ - -# Define plugins -Plugin=RenderSystem_Direct3D9${DEBUG_SUFFIX} -Plugin=RenderSystem_GL${DEBUG_SUFFIX} -Plugin=Plugin_ParticleFX${DEBUG_SUFFIX} -Plugin=Plugin_OctreeSceneManager${DEBUG_SUFFIX} -Plugin=Plugin_CgProgramManager${DEBUG_SUFFIX} - - diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index f2f4b4c81..7e609faa8 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -9,7 +9,10 @@ #include "OgreTexture.h" #include "OgreHardwarePixelBuffer.h" +#include + #include +#include #include using namespace Ogre; @@ -70,7 +73,6 @@ float OgreRenderer::getFPS() } void OgreRenderer::configure(const std::string &logPath, - const std::string &pluginCfg, const std::string& renderSystem, bool _logging) { @@ -90,9 +92,38 @@ void OgreRenderer::configure(const std::string &logPath, mRoot = new Root("", "", ""); loadPlugins(); #else - mRoot = new Root(pluginCfg, "", ""); + mRoot = new Root("", "", ""); #endif + std::string pluginDir; + const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); + if (pluginEnv) + pluginDir = pluginEnv; + else + { +#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + pluginDir = ".\\"; +#endif +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + pluginDir = OGRE_PLUGIN_DIR; +#endif +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX + pluginDir = OGRE_PLUGIN_DIR_REL; +#endif + } + + std::string glPlugin = std::string(pluginDir) + "/RenderSystem_GL" + OGRE_PLUGIN_DEBUG_SUFFIX; + if (boost::filesystem::exists(glPlugin + ".so") || boost::filesystem::exists(glPlugin + ".dll")) + mRoot->loadPlugin (glPlugin); + + std::string dxPlugin = std::string(pluginDir) + "/RenderSystem_Direct3D9" + OGRE_PLUGIN_DEBUG_SUFFIX; + if (boost::filesystem::exists(dxPlugin + ".so") || boost::filesystem::exists(dxPlugin + ".dll")) + mRoot->loadPlugin (dxPlugin); + + std::string cgPlugin = std::string(pluginDir) + "/Plugin_CgProgramManager" + OGRE_PLUGIN_DEBUG_SUFFIX; + if (boost::filesystem::exists(cgPlugin + ".so") || boost::filesystem::exists(cgPlugin + ".dll")) + mRoot->loadPlugin (cgPlugin); + RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); if (rs == 0) throw std::runtime_error ("RenderSystem with name " + renderSystem + " not found, make sure the plugins are loaded"); diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index c7c30c8d4..247c8f95a 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -110,7 +110,6 @@ namespace OEngine set up the Root and logging classes. */ void configure( const std::string &logPath, // Path to directory where to store log files - const std::string &pluginCfg, // plugin.cfg file const std::string &renderSystem, bool _logging); // Enable or disable logging From 41791ccaa2eb337bc374034d9ad5fac7bdc08607 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 20 Jul 2012 16:44:40 +0200 Subject: [PATCH 189/298] add file --- files/materials/quad2.shader | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 files/materials/quad2.shader diff --git a/files/materials/quad2.shader b/files/materials/quad2.shader new file mode 100644 index 000000000..e54d83ef4 --- /dev/null +++ b/files/materials/quad2.shader @@ -0,0 +1,23 @@ +#include "core.h" + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + } + +#else + + SH_BEGIN_PROGRAM + shUniform(float3, viewportBackground) @shSharedParameter(viewportBackground) + shDeclareMrtOutput(1) + SH_START_PROGRAM + { + shOutputColour(0) = float4(viewportBackground, 1); + shOutputColour(1) = float4(1,1,1,1); + } + +#endif From acc5c3bbbf3e59baf55c536e189394d36511da01 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 20 Jul 2012 17:08:15 +0200 Subject: [PATCH 190/298] some fixes --- apps/openmw/mwrender/sky.cpp | 15 ++++++++++++++- apps/openmw/mwrender/sky.hpp | 8 ++++++-- apps/openmw/mwworld/physicssystem.cpp | 11 +++++------ files/materials/atmosphere.shader | 2 -- files/materials/clouds.shader | 2 +- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 25928cf3f..2a2df7943 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -57,6 +57,8 @@ BillboardObject::BillboardObject( const String& textureName, mMaterial = sh::Factory::getInstance().createMaterialInstance ("BillboardMaterial"+StringConverter::toString(bodyCount), material); mMaterial->setProperty("texture", sh::makeProperty(new sh::StringValue(textureName))); + sh::Factory::getInstance().getMaterialInstance ("BillboardMaterial"+StringConverter::toString(bodyCount))->setListener(this); + mBBSet->setMaterialName("BillboardMaterial"+StringConverter::toString(bodyCount)); bodyCount++; @@ -66,6 +68,16 @@ BillboardObject::BillboardObject() { } +void BillboardObject::requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration) +{ +} + +void BillboardObject::createdConfiguration (sh::MaterialInstance* m, const std::string& configuration) +{ + setVisibility(mVisibility); + setColour(mColour); +} + void BillboardObject::setVisible(const bool visible) { mBBSet->setVisible(visible); @@ -78,6 +90,7 @@ void BillboardObject::setSize(const float size) void BillboardObject::setVisibility(const float visibility) { + mVisibility = visibility; Ogre::MaterialPtr m = static_cast(mMaterial->getMaterial ())->getOgreMaterial (); for (int i=0; igetNumTechniques(); ++i) { @@ -110,6 +123,7 @@ void BillboardObject::setVisibilityFlags(int flags) void BillboardObject::setColour(const ColourValue& pColour) { + mColour = pColour; Ogre::MaterialPtr m = static_cast(mMaterial->getMaterial ())->getOgreMaterial (); for (int i=0; igetNumTechniques(); ++i) { @@ -117,7 +131,6 @@ void BillboardObject::setColour(const ColourValue& pColour) if (t->getNumPasses ()) t->getPass(0)->setSelfIllumination (pColour); } - //mMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(pColour); } void BillboardObject::setRenderQueue(unsigned int id) diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index d785c6eb6..9fdff3a01 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -27,7 +27,7 @@ namespace Ogre namespace MWRender { - class BillboardObject + class BillboardObject : public sh::MaterialInstanceListener { public: BillboardObject( const Ogre::String& textureName, @@ -38,6 +38,9 @@ namespace MWRender ); BillboardObject(); + void requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration); + void createdConfiguration (sh::MaterialInstance* m, const std::string& configuration); + virtual ~BillboardObject() {} void setColour(const Ogre::ColourValue& pColour); @@ -53,7 +56,8 @@ namespace MWRender Ogre::SceneNode* getNode(); protected: - + float mVisibility; + Ogre::ColourValue mColour; Ogre::SceneNode* mNode; sh::MaterialInstance* mMaterial; Ogre::BillboardSet* mBBSet; diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 105995aca..bf5c001db 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -181,16 +181,15 @@ namespace MWWorld playerphysics->ps.viewangles.y = yawQuat.getYaw().valueDegrees() *-1 + 90; - Ogre::Quaternion quat = yawNode->getOrientation(); - Ogre::Vector3 dir1(iter->second.x,iter->second.z,-iter->second.y); + Ogre::Vector3 dir1(iter->second.x,iter->second.z,-iter->second.y); - pm_ref.rightmove = -iter->second.x; - pm_ref.forwardmove = -iter->second.y; - pm_ref.upmove = iter->second.z; + pm_ref.rightmove = -iter->second.x; + pm_ref.forwardmove = -iter->second.y; + pm_ref.upmove = iter->second.z; - } + } diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader index 6e22b7ac3..295fa9376 100644 --- a/files/materials/atmosphere.shader +++ b/files/materials/atmosphere.shader @@ -28,8 +28,6 @@ SH_START_PROGRAM { shOutputColour(0) = colourPassthrough * atmosphereColour; - - shOutputColour(0) = float3(0,0,0,1); #if MRT shOutputColour(1) = float4(1,1,1,1); diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index aa53e6051..7677ecd95 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -43,7 +43,7 @@ float4 albedo = shSample(diffuseMap1, scrolledUV) * (1-cloudBlendFactor) + shSample(diffuseMap2, scrolledUV) * cloudBlendFactor; shOutputColour(0) = colourPassthrough * float4(cloudColour, 1) * albedo * float4(1,1,1, cloudOpacity); - shOutputColour(0) = float3(0,0,0,1); + #if MRT shOutputColour(1) = float4(1,1,1,1); #endif From 4bc93ecd1a9c851e5bff3d3670f0dd74e7d61201 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 20 Jul 2012 11:09:05 -0700 Subject: [PATCH 191/298] Use the skeleton name for the main animation --- components/nifogre/ogre_nif_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index eea53e963..2aa7cd433 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -226,7 +226,7 @@ void loadResource(Ogre::Resource *resource) return; } - Ogre::Animation *anim = skel->createAnimation("default", maxtime); + Ogre::Animation *anim = skel->createAnimation(skel->getName(), maxtime); /* HACK: Pre-create the node tracks by matching the track IDs with the * bone IDs. Otherwise, Ogre animates the wrong bones. */ size_t bonecount = skel->getNumBones(); From 17a5c22c8ff25dd29d5a29ca333962269723f882 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 20 Jul 2012 23:31:49 +0200 Subject: [PATCH 192/298] don't use globbing --- cmake/OpenMWMacros.cmake | 7 ++-- files/CMakeLists.txt | 49 ++++++++++++++++++++++++--- files/mygui/CMakeLists.txt | 68 +++++++++++++++++++++++++++++++++++++- 3 files changed, 115 insertions(+), 9 deletions(-) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index c2567830d..e6f45fdb1 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -23,10 +23,9 @@ endforeach (u) source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_dir) -macro (copy_all_files file_expression destination_dir) -file (GLOB ALL "${file_expression}") -foreach (f ${ALL}) +macro (copy_all_files source_dir destination_dir files) +foreach (f ${files}) get_filename_component(filename ${f} NAME) -configure_file(${f} ${destination_dir}/${filename} COPYONLY) +configure_file(${source_dir}/${f} ${destination_dir}/${filename} COPYONLY) endforeach (f) endmacro (copy_all_files) diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index ce8bc9fbd..3e99f5745 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -1,9 +1,50 @@ project(resources) -copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/water/* "${OpenMW_BINARY_DIR}/resources/water/") +set(WATER_FILES + underwater_dome.mesh + water_nm.png +) -copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer/* "${OpenMW_BINARY_DIR}/resources/gbuffer/") +set(GBUFFER_FILES + gbuffer.compositor +) -copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/shadows/* "${OpenMW_BINARY_DIR}/resources/shadows/") +set(MATERIAL_FILES + atmosphere.shader + atmosphere.shaderset + clouds.shader + clouds.shaderset + core.h + moon.shader + moon.shaderset + objects.mat + objects.shader + objects.shaderset + openmw.configuration + quad2.shader + quad.mat + quad.shader + quad.shaderset + shadowcaster.mat + shadowcaster.shader + shadowcaster.shaderset + shadows.h + sky.mat + stars.shader + stars.shaderset + sun.shader + sun.shaderset + terrain.shader + terrain.shaderset + underwater.h + water.mat + water.shader + water.shaderset -copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/materials/* "${OpenMW_BINARY_DIR}/resources/materials/") +) + +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/water "${OpenMW_BINARY_DIR}/resources/water/" "${WATER_FILES}") + +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/gbuffer "${OpenMW_BINARY_DIR}/resources/gbuffer/" "${GBUFFER_FILES}") + +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/materials "${OpenMW_BINARY_DIR}/resources/materials/" "${MATERIAL_FILES}") diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index b8a04a31c..ae007f023 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -3,4 +3,70 @@ set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) -copy_all_files(${SDIR}/* ${DDIR}) +set(MYGUI_FILES + atlas1.cfg + bigbars.png + black.png + core.skin + core.xml + EBGaramond-Regular.ttf + mwgui.png + Obliviontt.zip + openmw_alchemy_window.layout + openmw_book.layout + openmw_box.skin.xml + openmw_button.skin.xml + openmw_chargen_birth.layout + openmw_chargen_class_description.layout + openmw_chargen_class.layout + openmw_chargen_create_class.layout + openmw_chargen_generate_class_result.layout + openmw_chargen_race.layout + openmw_chargen_review.layout + openmw_chargen_select_attribute.layout + openmw_chargen_select_skill.layout + openmw_chargen_select_specialization.layout + openmw_confirmation_dialog.layout + openmw_console.layout + openmw_console.skin.xml + openmw_container_window.layout + openmw_count_window.layout + openmw_dialogue_window.layout + openmw_dialogue_window_skin.xml + openmw_edit.skin.xml + openmw.font.xml + openmw_hud_box.skin.xml + openmw_hud_energybar.skin.xml + openmw_hud.layout + openmw_infobox.layout + openmw_interactive_messagebox.layout + openmw_inventory_window.layout + openmw_journal.layout + openmw_journal_skin.xml + openmw_layers.xml + openmw_list.skin.xml + openmw_mainmenu.layout + openmw_mainmenu_skin.xml + openmw_map_window.layout + openmw_map_window_skin.xml + openmw_messagebox.layout + openmw.pointer.xml + openmw_progress.skin.xml + openmw_resources.xml + openmw_scroll.layout + openmw_scroll_skin.xml + openmw_settings_window.layout + openmw_settings.xml + openmw_spell_window.layout + openmw_stats_window.layout + openmw_text_input.layout + openmw_text.skin.xml + openmw_tooltips.layout + openmw_trade_window.layout + openmw_windows.skin.xml + smallbars.png + VeraMono.ttf +) + + +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${MYGUI_FILES}") From e7ab3544acc3fb5047998c7cdb13e15b56b2fce1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 21 Jul 2012 13:52:12 +0200 Subject: [PATCH 193/298] removed some redundant code --- apps/launcher/graphicspage.cpp | 4 ---- libs/openengine/ogre/renderer.cpp | 5 ++--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 8caa2b550..3c1d76f3d 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -79,11 +79,7 @@ bool GraphicsPage::setupOgre() try { -#if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) mOgre = new Ogre::Root("", "", "./launcherOgre.log"); -#else - mOgre = new Ogre::Root("", "", "./launcherOgre.log"); -#endif } catch(Ogre::Exception &ex) { diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 7e609faa8..e40bdf708 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -88,11 +88,10 @@ void OgreRenderer::configure(const std::string &logPath, // Disable logging log->setDebugOutputEnabled(false); - #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) mRoot = new Root("", "", ""); + + #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) loadPlugins(); - #else - mRoot = new Root("", "", ""); #endif std::string pluginDir; From fcaa8aae06d791c37daab0897f4fd19f6b2129b5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 21 Jul 2012 11:26:09 -0700 Subject: [PATCH 194/298] Don't skip animation state updates for NPCs --- apps/openmw/mwrender/npcanimation.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 4d2ca557d..f66ab8403 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -113,8 +113,6 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere } } base->setRenderQueueGroup(transparent ? RQG_Alpha : RQG_Main); - base->setSkipAnimationStateUpdate(true); //Magical line of code, this makes the bones - //stay in the same place when we skipanim, or open a gui window } if(mEntityList.mSkelBase) From 81ce8dbe122db57110a32996975356bb057e0e91 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 21 Jul 2012 14:41:26 -0700 Subject: [PATCH 195/298] Combine animation handling into the base class --- apps/openmw/mwrender/animation.cpp | 29 ++++++++++++++++++++++ apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwrender/creatureanimation.cpp | 25 ++----------------- apps/openmw/mwrender/npcanimation.cpp | 25 +------------------ 4 files changed, 33 insertions(+), 48 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1f0a99518..88f6cebe6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -42,4 +42,33 @@ void Animation::skipAnim() mAnimate = 0; } +void Animation::runAnimation(float timepassed) +{ + if(mAnimate != 0) + { + mTime += timepassed; + + if(mEntityList.mSkelBase) + { + Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); + while(as.hasMoreElements()) + { + Ogre::AnimationState *state = as.getNext(); + state->setTimePosition(mTime); + if(mTime >= state->getLength()) + { + if(mAnimate != -1) + mAnimate--; + //std::cout << "Stopping the animation\n"; + if(mAnimate == 0) + mTime = state->getLength(); + else + mTime = mTime - state->getLength(); + } + } + } + } +} + } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 0ed4545f2..5de314df1 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -36,7 +36,7 @@ public: void playGroup(std::string groupname, int mode, int loops); void skipAnim(); - virtual void runAnimation(float timepassed) = 0; + virtual void runAnimation(float timepassed); }; } diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index ee1fd45ba..9d2a58a1e 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -69,30 +69,9 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O void CreatureAnimation::runAnimation(float timepassed) { - if(mAnimate > 0) - { - mTime += timepassed; + // Placeholder - if(mEntityList.mSkelBase) - { - Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); - Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); - while(as.hasMoreElements()) - { - Ogre::AnimationState *state = as.getNext(); - state->setTimePosition(mTime); - if(state->getTimePosition() >= state->getLength()) - { - mAnimate--; - //std::cout << "Stopping the animation\n"; - if(mAnimate == 0) - mTime = state->getLength(); - else - mTime = mTime - state->getLength(); - } - } - } - } + Animation::runAnimation(timepassed); } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f66ab8403..3cca110e3 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -373,30 +373,7 @@ void NpcAnimation::runAnimation(float timepassed) } timeToChange += timepassed; - if(mAnimate > 0) - { - mTime += timepassed; - - if(mEntityList.mSkelBase) - { - Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); - Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); - while(as.hasMoreElements()) - { - Ogre::AnimationState *state = as.getNext(); - state->setTimePosition(mTime); - if(state->getTimePosition() >= state->getLength()) - { - mAnimate--; - //std::cout << "Stopping the animation\n"; - if(mAnimate == 0) - mTime = state->getLength(); - else - mTime = mTime - state->getLength(); - } - } - } - } + Animation::runAnimation(timepassed); } void NpcAnimation::removeEntities(NifOgre::EntityList &entities) From c5b9098517714827d575166de0163aaed26e1398 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 21 Jul 2012 17:09:16 -0700 Subject: [PATCH 196/298] Remove an unused field from EntityList --- apps/openmw/mwrender/npcanimation.cpp | 1 - components/nifogre/ogre_nif_loader.cpp | 1 - components/nifogre/ogre_nif_loader.hpp | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 3cca110e3..e89122e4b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -388,7 +388,6 @@ void NpcAnimation::removeEntities(NifOgre::EntityList &entities) } entities.mEntities.clear(); entities.mSkelBase = NULL; - entities.mRootNode = NULL; } void NpcAnimation::removeIndividualPart(int type) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 2aa7cd433..2c5b7e324 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -1027,7 +1027,6 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string if(meshes.size() == 0) return entitylist; - entitylist.mRootNode = parent; Ogre::SceneManager *sceneMgr = parent->getCreator(); for(size_t i = 0;i < meshes.size();i++) { diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 76e94975c..a9195f2fb 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -62,9 +62,8 @@ namespace NifOgre struct EntityList { std::vector mEntities; Ogre::Entity *mSkelBase; - Ogre::SceneNode *mRootNode; - EntityList() : mSkelBase(0), mRootNode(0) + EntityList() : mSkelBase(0) { } }; From e81fc42daaf0adc5ae296b7781aea4270f299b9d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 21 Jul 2012 17:12:41 -0700 Subject: [PATCH 197/298] Remove the beast-race special cases from updateParts The special handling should happen at a much lower level, and prevent the objects from being equipped in the first place. --- apps/openmw/mwrender/npcanimation.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e89122e4b..415de5859 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -149,7 +149,7 @@ void NpcAnimation::updateParts() { &greaves, MWWorld::InventoryStore::Slot_Greaves }, { &leftpauldron, MWWorld::InventoryStore::Slot_LeftPauldron }, { &rightpauldron, MWWorld::InventoryStore::Slot_RightPauldron }, - { &boots, MWWorld::InventoryStore::Slot_Boots }, // !isBeast + { &boots, MWWorld::InventoryStore::Slot_Boots }, { &leftglove, MWWorld::InventoryStore::Slot_LeftGauntlet }, { &rightglove, MWWorld::InventoryStore::Slot_RightGauntlet }, { &shirt, MWWorld::InventoryStore::Slot_Shirt }, @@ -157,9 +157,6 @@ void NpcAnimation::updateParts() }; for(size_t i = 0;i < sizeof(slotlist)/sizeof(slotlist[0]);i++) { - if(slotlist[i].iter == &boots && isBeast) - continue; - MWWorld::ContainerStoreIterator iter = mInv.getSlot(slotlist[i].slot); if(*slotlist[i].iter != iter) { @@ -235,7 +232,7 @@ void NpcAnimation::updateParts() std::vector parts = armor->parts.parts; addPartGroup(MWWorld::InventoryStore::Slot_RightPauldron, 3, parts); } - if(!isBeast && boots != mInv.end()) + if(boots != mInv.end()) { if(boots->getTypeName() == typeid(ESM::Clothing).name()) { From 77446a0d58fde458f1ba01788f2854e1e2b1b558 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 21 Jul 2012 17:39:57 -0700 Subject: [PATCH 198/298] Fix skipAnim, only skip one animation update --- apps/openmw/mwrender/animation.cpp | 6 ++++-- apps/openmw/mwrender/animation.hpp | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 88f6cebe6..653a506e8 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -16,6 +16,7 @@ Animation::Animation(OEngine::Render::OgreRenderer& _rend) , mRend(_rend) , mTime(0.0f) , mAnimate(0) + , mSkipFrame(false) { } @@ -39,12 +40,12 @@ void Animation::playGroup(std::string groupname, int mode, int loops) void Animation::skipAnim() { - mAnimate = 0; + mSkipFrame = true; } void Animation::runAnimation(float timepassed) { - if(mAnimate != 0) + if(mAnimate != 0 && !mSkipFrame) { mTime += timepassed; @@ -69,6 +70,7 @@ void Animation::runAnimation(float timepassed) } } } + mSkipFrame = false; } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 5de314df1..ae1477666 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -27,6 +27,7 @@ protected: float mTime; int mAnimate; + bool mSkipFrame; NifOgre::EntityList mEntityList; From 6bfcf2bc1b84e888e11742c33547e001e5835036 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 22 Jul 2012 02:45:39 +0200 Subject: [PATCH 199/298] - exchanged the preprocessor again, no warnings now - disable line directives for now, causing some trouble --- extern/shiny | 2 +- files/materials/objects.shader | 2 +- files/materials/terrain.shader | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/extern/shiny b/extern/shiny index 7af50bf44..34c098c63 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 7af50bf4463a828ee0e8cb72c93445c793864bf9 +Subproject commit 34c098c636659b63e0ce85d371f4933c27191753 diff --git a/files/materials/objects.shader b/files/materials/objects.shader index eafce7af6..39c9b42e0 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -132,7 +132,7 @@ shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) #endif -#ifdef HAS_VERTEXCOLOR +#if HAS_VERTEXCOLOR shInput(float4, colourPassthrough) #endif diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 24771ca41..e0187959f 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -247,10 +247,7 @@ // first layer of first pass doesn't need a blend map albedo = shSample(diffuseMap0, UV * 10).rgb; #else - #define BLEND_AMOUNT blendValues@shPropertyString(blendmap_component_@shIterator) - - - albedo = shLerp(albedo, shSample(diffuseMap@shIterator, UV * 10).rgb, BLEND_AMOUNT); + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, UV * 10).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); #endif @shEndForeach From d8cb6855437813e1145f213c3cb83f43b0580923 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 21 Jul 2012 18:03:01 -0700 Subject: [PATCH 200/298] Interpolate keyframes when creating them Probably not fully correct, but better than nothing. --- components/nifogre/ogre_nif_loader.cpp | 28 ++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 2c5b7e324..e695cd959 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -306,10 +306,30 @@ void loadResource(Ogre::Resource *resource) Ogre::TransformKeyFrame *kframe; kframe = nodetrack->createNodeKeyFrame(curtime); - // FIXME: These should be interpolated since they don't all fall on the same time - kframe->setRotation(curquat); - kframe->setTranslate(curtrans); - kframe->setScale(curscale); + if(quatiter == quatkeys.mKeys.end() || quatiter == quatkeys.mKeys.begin()) + kframe->setRotation(curquat); + else + { + QuaternionKeyList::VecType::const_iterator last = quatiter-1; + float diff = (curtime-last->mTime) / (quatiter->mTime-last->mTime); + kframe->setRotation(Ogre::Quaternion::nlerp(diff, lastquat, curquat)); + } + if(traniter == trankeys.mKeys.end() || traniter == trankeys.mKeys.begin()) + kframe->setTranslate(curtrans); + else + { + Vector3KeyList::VecType::const_iterator last = traniter-1; + float diff = (curtime-last->mTime) / (traniter->mTime-last->mTime); + kframe->setTranslate(lasttrans + ((curtrans-lasttrans)*diff)); + } + if(scaleiter == scalekeys.mKeys.end() || scaleiter == scalekeys.mKeys.begin()) + kframe->setScale(curscale); + else + { + FloatKeyList::VecType::const_iterator last = scaleiter-1; + float diff = (curtime-last->mTime) / (scaleiter->mTime-last->mTime); + kframe->setScale(lastscale + ((curscale-lastscale)*diff)); + } } } anim->optimise(); From 4035d7370e5288898d1e590030b1aa4f853bad28 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 21 Jul 2012 22:04:05 -0700 Subject: [PATCH 201/298] Fix name/filter comparison --- components/nifogre/ogre_nif_loader.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index e695cd959..c34f69404 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -25,6 +25,8 @@ #include "ogre_nif_loader.hpp" +#include + #include #include #include @@ -1080,6 +1082,11 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string return entitylist; } +static bool checklow(const char &a, const char &b) +{ + return ::tolower(a) == ::tolower(b); +} + EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bonename, Ogre::SceneNode *parentNode, const std::string &name, @@ -1099,8 +1106,7 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo if(ent->hasSkeleton()) { if(meshes[i].second.length() < filter.length() || - !boost::algorithm::lexicographical_compare(meshes[i].second.substr(0, filter.length()), - filter, boost::algorithm::is_iequal())) + std::mismatch(filter.begin(), filter.end(), meshes[i].second.begin(), checklow).first != filter.end()) { sceneMgr->destroyEntity(ent); meshes.erase(meshes.begin()+i); From 1dde806addefa39bd4779b7e3c4954b355fc7c69 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 22 Jul 2012 14:52:55 +0300 Subject: [PATCH 202/298] Fixes #313: openmw without a ~/.config/openmw folder segfault. Added creation of $HOME/.config/openmw directory. Signed-off-by: Lukasz Gromanowski --- components/files/configurationmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 150a4fcd8..dee51b0c1 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -27,6 +27,8 @@ ConfigurationManager::ConfigurationManager() { setupTokensMapping(); + boost::filesystem::create_directories(mFixedPath.getUserPath()); + mPluginsCfgPath = mFixedPath.getLocalPath() / pluginsCfgFile; if (!boost::filesystem::is_regular_file(mPluginsCfgPath)) { From b2dcf5adcdcfc73c8b61e782e7153facc03a6b77 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 22 Jul 2012 14:41:23 +0200 Subject: [PATCH 203/298] support system install for boost wave --- CMakeLists.txt | 13 ++++++++++++- extern/shiny | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7e23a98d..c0b5bf451 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,9 +186,20 @@ if (UNIX AND NOT APPLE) find_package (Threads) endif() +set(BOOST_COMPONENTS system filesystem program_options thread) + +if (Boost_VERSION LESS 104900) + set(SHINY_USE_WAVE_SYSTEM_INSTALL TRUE) + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} wave) +else() + set(SHINY_USE_WAVE_SYSTEM_INSTALL FALSE) +endif() + +MESSAGE(STATUS ${BOOST_COMPONENTS}) + find_package(OGRE REQUIRED) find_package(MyGUI REQUIRED) -find_package(Boost REQUIRED COMPONENTS system filesystem program_options thread) +find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) find_package(OIS REQUIRED) find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) diff --git a/extern/shiny b/extern/shiny index 34c098c63..fdc92a424 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 34c098c636659b63e0ce85d371f4933c27191753 +Subproject commit fdc92a4243c98f5d86a33cbdf2ed0fbb94dad3f6 From 32b1350b63e7fe8ee99d316f822df3cc8ffd978e Mon Sep 17 00:00:00 2001 From: greye Date: Sun, 22 Jul 2012 18:29:54 +0400 Subject: [PATCH 204/298] CreatureStats class --- apps/openmw/mwclass/creature.cpp | 42 +-- apps/openmw/mwclass/npc.cpp | 42 +-- apps/openmw/mwgui/spellwindow.cpp | 8 +- apps/openmw/mwmechanics/actors.cpp | 54 ++-- apps/openmw/mwmechanics/creaturestats.cpp | 48 ++++ apps/openmw/mwmechanics/creaturestats.hpp | 257 ++++++++++++++++++- apps/openmw/mwmechanics/mechanicsmanager.cpp | 64 ++--- apps/openmw/mwmechanics/spellsuccess.hpp | 10 +- apps/openmw/mwscript/aiextensions.cpp | 8 +- apps/openmw/mwscript/statsextensions.cpp | 73 +++--- 10 files changed, 465 insertions(+), 141 deletions(-) create mode 100644 apps/openmw/mwmechanics/creaturestats.cpp diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a5a4f337a..83370478f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -47,24 +47,24 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); // creature stats - data->mCreatureStats.mAttributes[0].set (ref->base->data.strength); - data->mCreatureStats.mAttributes[1].set (ref->base->data.intelligence); - data->mCreatureStats.mAttributes[2].set (ref->base->data.willpower); - data->mCreatureStats.mAttributes[3].set (ref->base->data.agility); - data->mCreatureStats.mAttributes[4].set (ref->base->data.speed); - data->mCreatureStats.mAttributes[5].set (ref->base->data.endurance); - data->mCreatureStats.mAttributes[6].set (ref->base->data.personality); - data->mCreatureStats.mAttributes[7].set (ref->base->data.luck); - data->mCreatureStats.mDynamic[0].set (ref->base->data.health); - data->mCreatureStats.mDynamic[1].set (ref->base->data.mana); - data->mCreatureStats.mDynamic[2].set (ref->base->data.fatigue); - - data->mCreatureStats.mLevel = ref->base->data.level; - - data->mCreatureStats.mHello = ref->base->AI.hello; - data->mCreatureStats.mFight = ref->base->AI.fight; - data->mCreatureStats.mFlee = ref->base->AI.flee; - data->mCreatureStats.mAlarm = ref->base->AI.alarm; + data->mCreatureStats.getAttribute(0).set (ref->base->data.strength); + data->mCreatureStats.getAttribute(1).set (ref->base->data.intelligence); + data->mCreatureStats.getAttribute(2).set (ref->base->data.willpower); + data->mCreatureStats.getAttribute(3).set (ref->base->data.agility); + data->mCreatureStats.getAttribute(4).set (ref->base->data.speed); + data->mCreatureStats.getAttribute(5).set (ref->base->data.endurance); + data->mCreatureStats.getAttribute(6).set (ref->base->data.personality); + data->mCreatureStats.getAttribute(7).set (ref->base->data.luck); + data->mCreatureStats.getHealth().set (ref->base->data.health); + data->mCreatureStats.getMagicka().set (ref->base->data.mana); + data->mCreatureStats.getFatigue().set (ref->base->data.fatigue); + + data->mCreatureStats.setLevel(ref->base->data.level); + + data->mCreatureStats.setHello(ref->base->AI.hello); + data->mCreatureStats.setFight(ref->base->AI.fight); + data->mCreatureStats.setFlee(ref->base->AI.flee); + data->mCreatureStats.setAlarm(ref->base->AI.alarm); // store ptr.getRefData().setCustomData (data.release()); @@ -169,7 +169,7 @@ namespace MWClass float Creature::getCapacity (const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - return stats.mAttributes[0].getModified()*5; + return stats.getAttribute(0).getModified()*5; } float Creature::getEncumbrance (const MWWorld::Ptr& ptr) const @@ -178,9 +178,9 @@ namespace MWClass const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - weight -= stats.mMagicEffects.get (MWMechanics::EffectKey (8)).mMagnitude; // feather + weight -= stats.getMagicEffects().get (MWMechanics::EffectKey (8)).mMagnitude; // feather - weight += stats.mMagicEffects.get (MWMechanics::EffectKey (7)).mMagnitude; // burden + weight += stats.getMagicEffects().get (MWMechanics::EffectKey (7)).mMagnitude; // burden if (weight<0) weight = 0; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index ffe7dec61..80bff73fa 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -81,29 +81,29 @@ namespace MWClass for (int i=0; i<27; ++i) data->mNpcStats.getSkill (i).setBase (ref->base->npdt52.skills[i]); - data->mCreatureStats.mAttributes[0].set (ref->base->npdt52.strength); - data->mCreatureStats.mAttributes[1].set (ref->base->npdt52.intelligence); - data->mCreatureStats.mAttributes[2].set (ref->base->npdt52.willpower); - data->mCreatureStats.mAttributes[3].set (ref->base->npdt52.agility); - data->mCreatureStats.mAttributes[4].set (ref->base->npdt52.speed); - data->mCreatureStats.mAttributes[5].set (ref->base->npdt52.endurance); - data->mCreatureStats.mAttributes[6].set (ref->base->npdt52.personality); - data->mCreatureStats.mAttributes[7].set (ref->base->npdt52.luck); - data->mCreatureStats.mDynamic[0].set (ref->base->npdt52.health); - data->mCreatureStats.mDynamic[1].set (ref->base->npdt52.mana); - data->mCreatureStats.mDynamic[2].set (ref->base->npdt52.fatigue); - - data->mCreatureStats.mLevel = ref->base->npdt52.level; + data->mCreatureStats.getAttribute(0).set (ref->base->npdt52.strength); + data->mCreatureStats.getAttribute(1).set (ref->base->npdt52.intelligence); + data->mCreatureStats.getAttribute(2).set (ref->base->npdt52.willpower); + data->mCreatureStats.getAttribute(3).set (ref->base->npdt52.agility); + data->mCreatureStats.getAttribute(4).set (ref->base->npdt52.speed); + data->mCreatureStats.getAttribute(5).set (ref->base->npdt52.endurance); + data->mCreatureStats.getAttribute(6).set (ref->base->npdt52.personality); + data->mCreatureStats.getAttribute(7).set (ref->base->npdt52.luck); + data->mCreatureStats.getHealth().set (ref->base->npdt52.health); + data->mCreatureStats.getMagicka().set (ref->base->npdt52.mana); + data->mCreatureStats.getFatigue().set (ref->base->npdt52.fatigue); + + data->mCreatureStats.setLevel(ref->base->npdt52.level); } else { /// \todo do something with npdt12 maybe:p } - data->mCreatureStats.mHello = ref->base->AI.hello; - data->mCreatureStats.mFight = ref->base->AI.fight; - data->mCreatureStats.mFlee = ref->base->AI.flee; - data->mCreatureStats.mAlarm = ref->base->AI.alarm; + data->mCreatureStats.setHello(ref->base->AI.hello); + data->mCreatureStats.setFight(ref->base->AI.fight); + data->mCreatureStats.setFlee(ref->base->AI.flee); + data->mCreatureStats.setAlarm(ref->base->AI.alarm); // store ptr.getRefData().setCustomData (data.release()); @@ -330,7 +330,7 @@ namespace MWClass float Npc::getCapacity (const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - return stats.mAttributes[0].getModified()*5; + return stats.getAttribute(0).getModified()*5; } float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const @@ -339,9 +339,9 @@ namespace MWClass const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - weight -= stats.mMagicEffects.get (MWMechanics::EffectKey (8)).mMagnitude; // feather + weight -= stats.getMagicEffects().get (MWMechanics::EffectKey (8)).mMagnitude; // feather - weight += stats.mMagicEffects.get (MWMechanics::EffectKey (7)).mMagnitude; // burden + weight += stats.getMagicEffects().get (MWMechanics::EffectKey (7)).mMagnitude; // burden if (weight<0) weight = 0; @@ -356,7 +356,7 @@ namespace MWClass /// \todo consider instant effects - return stats.mActiveSpells.addSpell (id); + return stats.getActiveSpells().addSpell (id); } void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index f6e7d3b77..8f81a1761 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -81,7 +81,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - MWMechanics::Spells& spells = stats.mSpells; + MWMechanics::Spells& spells = stats.getSpells(); // the following code switches between selected enchanted item and selected spell (only one of these // can be active at a time) @@ -333,7 +333,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::Spells& spells = stats.mSpells; + MWMechanics::Spells& spells = stats.getSpells(); MWWorld::Ptr item = *_sender->getUserData(); // retrieve ContainerStoreIterator to the item @@ -397,7 +397,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::Spells& spells = stats.mSpells; + MWMechanics::Spells& spells = stats.getSpells(); if (MyGUI::InputManager::getInstance().isShiftPressed()) { @@ -451,7 +451,7 @@ namespace MWGui { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - MWMechanics::Spells& spells = stats.mSpells; + MWMechanics::Spells& spells = stats.getSpells(); if (spells.getSelectedSpell() == mSpellToDelete) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ff3e91da8..e1c5f855f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -33,7 +33,7 @@ namespace MWMechanics { CreatureStats& creatureStats = MWWorld::Class::get (creature).getCreatureStats (creature); - MagicEffects now = creatureStats.mSpells.getMagicEffects(); + MagicEffects now = creatureStats.getSpells().getMagicEffects(); if (creature.getTypeName()==typeid (ESM::NPC).name()) { @@ -41,11 +41,11 @@ namespace MWMechanics now += store.getMagicEffects(); } - now += creatureStats.mActiveSpells.getMagicEffects(); + now += creatureStats.getActiveSpells().getMagicEffects(); - MagicEffects diff = MagicEffects::diff (creatureStats.mMagicEffects, now); + MagicEffects diff = MagicEffects::diff (creatureStats.getMagicEffects(), now); - creatureStats.mMagicEffects = now; + creatureStats.setMagicEffects(now); // TODO apply diff to other stats } @@ -54,18 +54,22 @@ namespace MWMechanics { CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - int strength = creatureStats.mAttributes[0].getBase(); - int intelligence = creatureStats.mAttributes[1].getBase(); - int willpower = creatureStats.mAttributes[2].getBase(); - int agility = creatureStats.mAttributes[3].getBase(); - int endurance = creatureStats.mAttributes[5].getBase(); + int strength = creatureStats.getAttribute(0).getBase(); + int intelligence = creatureStats.getAttribute(1).getBase(); + int willpower = creatureStats.getAttribute(2).getBase(); + int agility = creatureStats.getAttribute(3).getBase(); + int endurance = creatureStats.getAttribute(5).getBase(); - double magickaFactor = creatureStats.mMagicEffects.get (EffectKey (84)).mMagnitude*0.1 + 0.5; + double magickaFactor = + creatureStats.getMagicEffects().get (EffectKey (84)).mMagnitude * 0.1 + 0.5; - creatureStats.mDynamic[0].setBase (static_cast (0.5 * (strength + endurance))); - creatureStats.mDynamic[1].setBase (static_cast (intelligence + - magickaFactor * intelligence)); - creatureStats.mDynamic[2].setBase (strength+willpower+agility+endurance); + creatureStats.getHealth().setBase( + static_cast (0.5 * (strength + endurance))); + + creatureStats.getMagicka().setBase( + static_cast (intelligence + magickaFactor * intelligence)); + + creatureStats.getFatigue().setBase(strength+willpower+agility+endurance); } void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr) @@ -75,20 +79,24 @@ namespace MWMechanics // attributes for (int i=0; i<5; ++i) { - int modifier = creatureStats.mMagicEffects.get (EffectKey (79, i)).mMagnitude - - creatureStats.mMagicEffects.get (EffectKey (17, i)).mMagnitude; + int modifier = + creatureStats.getMagicEffects().get (EffectKey (79, i)).mMagnitude; + + modifier -= creatureStats.getMagicEffects().get (EffectKey (17, i)).mMagnitude; - creatureStats.mAttributes[i].setModifier (modifier); + creatureStats.getAttribute(i).setModifier (modifier); } // dynamic stats - for (int i=0; i<3; ++i) - { - int modifier = creatureStats.mMagicEffects.get (EffectKey (80+i)).mMagnitude - - creatureStats.mMagicEffects.get (EffectKey (18+i)).mMagnitude; + MagicEffects effects = creatureStats.getMagicEffects(); + creatureStats.getHealth().setModifier( + effects.get(EffectKey(80)).mMagnitude - effects.get(EffectKey(18)).mMagnitude); - creatureStats.mDynamic[i].setModifier (modifier); - } + creatureStats.getMagicka().setModifier( + effects.get(EffectKey(81)).mMagnitude - effects.get(EffectKey(19)).mMagnitude); + + creatureStats.getFatigue().setModifier( + effects.get(EffectKey(82)).mMagnitude - effects.get(EffectKey(20)).mMagnitude); } Actors::Actors() : mDuration (0) {} diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp new file mode 100644 index 000000000..38d2442fa --- /dev/null +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -0,0 +1,48 @@ +#include "creaturestats.hpp" + +namespace MWMechanics +{ + CreatureStats::CreatureStats() + {} + + // Can't use all benefits of members initialization because of + // lack of copy constructors + CreatureStats::CreatureStats(const CreatureStats &orig) + : mLevel(orig.mLevel), mHello(orig.mHello), mFight(orig.mFight), + mFlee(orig.mFlee), mAlarm(orig.mAlarm) + { + for (int i = 0; i < 8; ++i) { + mAttributes[i] = orig.mAttributes[i]; + } + for (int i = 0; i < 3; ++i) { + mDynamic[i] = orig.mDynamic[i]; + } + mSpells = orig.mSpells; + mActiveSpells = orig.mActiveSpells; + mMagicEffects = orig.mMagicEffects; + } + + CreatureStats::~CreatureStats() + {} + + const CreatureStats & + CreatureStats::operator=(const CreatureStats &orig) + { + for (int i = 0; i < 8; ++i) { + mAttributes[i] = orig.mAttributes[i]; + } + for (int i = 0; i < 3; ++i) { + mDynamic[i] = orig.mDynamic[i]; + } + mLevel = orig.mLevel; + mSpells = orig.mSpells; + mActiveSpells = orig.mActiveSpells; + mMagicEffects = orig.mMagicEffects; + mHello = orig.mHello; + mFight = orig.mFight; + mFlee = orig.mFlee; + mAlarm = orig.mAlarm; + + return *this; + } +} diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 8d40e1942..9d69c868f 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "stat.hpp" #include "magiceffects.hpp" @@ -11,7 +12,10 @@ namespace MWMechanics { - struct CreatureStats + /// \brief Common creature stats + /// + /// + class CreatureStats { Stat mAttributes[8]; DynamicStat mDynamic[3]; // health, magicka, fatigue @@ -23,7 +27,258 @@ namespace MWMechanics int mFight; int mFlee; int mAlarm; + + public: + CreatureStats(); + CreatureStats(const CreatureStats &); + virtual ~CreatureStats(); + + const CreatureStats & operator=(const CreatureStats &); + + const Stat & getAttribute(int index) const; + + const DynamicStat & getHealth() const; + + const DynamicStat & getMagicka() const; + + const DynamicStat & getFatigue() const; + + const Spells & getSpells() const; + + const ActiveSpells & getActiveSpells() const; + + const MagicEffects & getMagicEffects() const; + + int getLevel() const; + + int getHello() const; + + int getFight() const; + + int getFlee() const; + + int getAlarm() const; + + + Stat & getAttribute(int index); + + DynamicStat & getHealth(); + + DynamicStat & getMagicka(); + + DynamicStat & getFatigue(); + + DynamicStat & getDynamic(int index); + + Spells & getSpells(); + + ActiveSpells & getActiveSpells(); + + MagicEffects & getMagicEffects(); + + + void setAttribute(int index, const Stat &value); + + void setHealth(const DynamicStat &value); + + void setMagicka(const DynamicStat &value); + + void setFatigue(const DynamicStat &value); + + void setSpells(const Spells &spells); + + void setActiveSpells(const ActiveSpells &active); + + void setMagicEffects(const MagicEffects &effects); + + void setLevel(int level); + + void setHello(int value); + + void setFight(int value); + + void setFlee(int value); + + void setAlarm(int value); }; + + // Inline const getters + + inline const Stat & + CreatureStats::getAttribute(int index) const { + if (index < 0 || index > 7) { + throw std::runtime_error("attribute index is out of range"); + } + return mAttributes[index]; + } + + inline const DynamicStat & + CreatureStats::getHealth() const { + return mDynamic[0]; + } + + inline const DynamicStat & + CreatureStats::getMagicka() const { + return mDynamic[1]; + } + + inline const DynamicStat & + CreatureStats::getFatigue() const { + return mDynamic[2]; + } + + inline const Spells & + CreatureStats::getSpells() const { + return mSpells; + } + + inline const ActiveSpells & + CreatureStats::getActiveSpells() const { + return mActiveSpells; + } + + inline const MagicEffects & + CreatureStats::getMagicEffects() const { + return mMagicEffects; + } + + inline int + CreatureStats::getLevel() const { + return mLevel; + } + + inline int + CreatureStats::getHello() const { + return mHello; + } + + inline int + CreatureStats::getFight() const { + return mFight; + } + + inline int + CreatureStats::getFlee() const { + return mFlee; + } + + inline int + CreatureStats::getAlarm() const { + return mAlarm; + } + + // Inline non-const getters + + inline Stat & + CreatureStats::getAttribute(int index) { + if (index < 0 || index > 7) { + throw std::runtime_error("attribute index is out of range"); + } + return mAttributes[index]; + } + + inline DynamicStat & + CreatureStats::getHealth() { + return mDynamic[0]; + } + + inline DynamicStat & + CreatureStats::getMagicka() { + return mDynamic[1]; + } + + inline DynamicStat & + CreatureStats::getFatigue() { + return mDynamic[2]; + } + + inline DynamicStat & + CreatureStats::getDynamic(int index) { + if (index < 0 || index > 2) { + throw std::runtime_error("dynamic stat index is out of range"); + } + return mDynamic[index]; + } + + inline Spells & + CreatureStats::getSpells() { + return mSpells; + } + + inline void + CreatureStats::setSpells(const Spells &spells) { + mSpells = spells; + } + + inline ActiveSpells & + CreatureStats::getActiveSpells() { + return mActiveSpells; + } + + inline MagicEffects & + CreatureStats::getMagicEffects() { + return mMagicEffects; + } + + // Inline setters + + inline void + CreatureStats::setAttribute(int index, const Stat &value) { + if (index < 0 || index > 7) { + throw std::runtime_error("attribute index is out of range"); + } + mAttributes[index] = value; + } + + inline void + CreatureStats::setHealth(const DynamicStat &value) { + mDynamic[0] = value; + } + + inline void + CreatureStats::setMagicka(const DynamicStat &value) { + mDynamic[1] = value; + } + + inline void + CreatureStats::setFatigue(const DynamicStat &value) { + mDynamic[2] = value; + } + + inline void + CreatureStats::setLevel(int level) { + mLevel = level; + } + + inline void + CreatureStats::setActiveSpells(const ActiveSpells &active) { + mActiveSpells = active; + } + + inline void + CreatureStats::setMagicEffects(const MagicEffects &effects) { + mMagicEffects = effects; + } + + inline void + CreatureStats::setHello(int value) { + mHello = value; + } + + inline void + CreatureStats::setFight(int value) { + mFight = value; + } + + inline void + CreatureStats::setFlee(int value) { + mFlee = value; + } + + inline void + CreatureStats::setAlarm(int value) { + mAlarm = value; + } } #endif diff --git a/apps/openmw/mwmechanics/mechanicsmanager.cpp b/apps/openmw/mwmechanics/mechanicsmanager.cpp index ada05a11b..fe5485d61 100644 --- a/apps/openmw/mwmechanics/mechanicsmanager.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanager.cpp @@ -23,21 +23,21 @@ namespace MWMechanics const ESM::NPC *player = ptr.get()->base; // reset - creatureStats.mLevel = player->npdt52.level; - creatureStats.mSpells.clear(); - creatureStats.mMagicEffects = MagicEffects(); + creatureStats.setLevel(player->npdt52.level); + creatureStats.getSpells().clear(); + creatureStats.setMagicEffects(MagicEffects()); for (int i=0; i<27; ++i) npcStats.getSkill (i).setBase (player->npdt52.skills[i]); - creatureStats.mAttributes[0].setBase (player->npdt52.strength); - creatureStats.mAttributes[1].setBase (player->npdt52.intelligence); - creatureStats.mAttributes[2].setBase (player->npdt52.willpower); - creatureStats.mAttributes[3].setBase (player->npdt52.agility); - creatureStats.mAttributes[4].setBase (player->npdt52.speed); - creatureStats.mAttributes[5].setBase (player->npdt52.endurance); - creatureStats.mAttributes[6].setBase (player->npdt52.personality); - creatureStats.mAttributes[7].setBase (player->npdt52.luck); + creatureStats.getAttribute(0).setBase (player->npdt52.strength); + creatureStats.getAttribute(1).setBase (player->npdt52.intelligence); + creatureStats.getAttribute(2).setBase (player->npdt52.willpower); + creatureStats.getAttribute(3).setBase (player->npdt52.agility); + creatureStats.getAttribute(4).setBase (player->npdt52.speed); + creatureStats.getAttribute(5).setBase (player->npdt52.endurance); + creatureStats.getAttribute(6).setBase (player->npdt52.personality); + creatureStats.getAttribute(7).setBase (player->npdt52.luck); // race if (mRaceSelected) @@ -63,7 +63,7 @@ namespace MWMechanics case 7: attribute = &race->data.luck; break; } - creatureStats.mAttributes[i].setBase ( + creatureStats.getAttribute(i).setBase ( static_cast (male ? attribute->male : attribute->female)); } @@ -81,7 +81,7 @@ namespace MWMechanics for (std::vector::const_iterator iter (race->powers.list.begin()); iter!=race->powers.list.end(); ++iter) { - creatureStats.mSpells.add (*iter); + creatureStats.getSpells().add (*iter); } } @@ -95,7 +95,7 @@ namespace MWMechanics for (std::vector::const_iterator iter (sign->powers.list.begin()); iter!=sign->powers.list.end(); ++iter) { - creatureStats.mSpells.add (*iter); + creatureStats.getSpells().add (*iter); } } @@ -109,8 +109,8 @@ namespace MWMechanics int attribute = class_.data.attribute[i]; if (attribute>=0 && attribute<8) { - creatureStats.mAttributes[attribute].setBase ( - creatureStats.mAttributes[attribute].getBase() + 10); + creatureStats.getAttribute(attribute).setBase ( + creatureStats.getAttribute(attribute).getBase() + 10); } } @@ -151,8 +151,9 @@ namespace MWMechanics // forced update and current value adjustments mActors.updateActor (ptr, 0); - for (int i=0; i<3; ++i) - creatureStats.mDynamic[i].setCurrent (creatureStats.mDynamic[i].getModified()); + creatureStats.getHealth().setCurrent(creatureStats.getHealth().getModified()); + creatureStats.getMagicka().setCurrent(creatureStats.getMagicka().getModified()); + creatureStats.getFatigue().setCurrent(creatureStats.getFatigue().getModified()); } @@ -213,22 +214,25 @@ namespace MWMechanics for (int i=0; i<8; ++i) { - if (stats.mAttributes[i]!=mWatchedCreature.mAttributes[i]) + if (stats.getAttribute(i)!=mWatchedCreature.getAttribute(i)) { - mWatchedCreature.mAttributes[i] = stats.mAttributes[i]; + mWatchedCreature.setAttribute(i, stats.getAttribute(i)); - MWBase::Environment::get().getWindowManager()->setValue (attributeNames[i], stats.mAttributes[i]); + MWBase::Environment::get().getWindowManager()->setValue (attributeNames[i], stats.getAttribute(i)); } } - for (int i=0; i<3; ++i) - { - if (stats.mDynamic[i]!=mWatchedCreature.mDynamic[i]) - { - mWatchedCreature.mDynamic[i] = stats.mDynamic[i]; - - MWBase::Environment::get().getWindowManager()->setValue (dynamicNames[i], stats.mDynamic[i]); - } + if (stats.getHealth() != mWatchedCreature.getHealth()) { + mWatchedCreature.setHealth(stats.getHealth()); + MWBase::Environment::get().getWindowManager()->setValue(dynamicNames[0], stats.getHealth()); + } + if (stats.getMagicka() != mWatchedCreature.getMagicka()) { + mWatchedCreature.setMagicka(stats.getMagicka()); + MWBase::Environment::get().getWindowManager()->setValue(dynamicNames[1], stats.getMagicka()); + } + if (stats.getFatigue() != mWatchedCreature.getFatigue()) { + mWatchedCreature.setFatigue(stats.getFatigue()); + MWBase::Environment::get().getWindowManager()->setValue(dynamicNames[2], stats.getFatigue()); } bool update = false; @@ -247,7 +251,7 @@ namespace MWMechanics if (update) MWBase::Environment::get().getWindowManager()->updateSkillArea(); - MWBase::Environment::get().getWindowManager()->setValue ("level", stats.mLevel); + MWBase::Environment::get().getWindowManager()->setValue ("level", stats.getLevel()); } if (mUpdatePlayer) diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp index 42a7d5bba..1ab1bb11f 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -81,12 +81,12 @@ namespace MWMechanics int skillLevel = stats.getSkill (getSpellSchool(spellId, actor)).getModified(); // Sound magic effect (reduces spell casting chance) - int soundMagnitude = creatureStats.mMagicEffects.get (MWMechanics::EffectKey (48)).mMagnitude; + int soundMagnitude = creatureStats.getMagicEffects().get (MWMechanics::EffectKey (48)).mMagnitude; - int willpower = creatureStats.mAttributes[ESM::Attribute::Willpower].getModified(); - int luck = creatureStats.mAttributes[ESM::Attribute::Luck].getModified(); - int currentFatigue = creatureStats.mDynamic[2].getCurrent(); - int maxFatigue = creatureStats.mDynamic[2].getModified(); + int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified(); + int luck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified(); + int currentFatigue = creatureStats.getFatigue().getCurrent(); + int maxFatigue = creatureStats.getFatigue().getModified(); int spellCost = spell->data.cost; // There we go, all needed variables are there, lets go diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 924a0e9dd..9d70c28bd 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -130,7 +130,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).mHello = value; + MWWorld::Class::get (ptr).getCreatureStats (ptr).setHello(value); } }; @@ -146,7 +146,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).mFight = value; + MWWorld::Class::get (ptr).getCreatureStats (ptr).setFight(value); } }; @@ -162,7 +162,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).mFlee = value; + MWWorld::Class::get (ptr).getCreatureStats (ptr).setFlee(value); } }; @@ -178,7 +178,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).mAlarm = value; + MWWorld::Class::get (ptr).getCreatureStats (ptr).setAlarm(value); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 2edd7925e..0cbbe8398 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -46,8 +46,10 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = - MWWorld::Class::get (ptr).getCreatureStats (ptr).mAttributes[mIndex]. - getModified(); + MWWorld::Class::get (ptr) + .getCreatureStats (ptr) + .getAttribute(mIndex) + .getModified(); runtime.push (value); } @@ -69,8 +71,10 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).mAttributes[mIndex]. - setModified (value, 0); + MWWorld::Class::get(ptr) + .getCreatureStats(ptr) + .getAttribute(mIndex) + .setModified (value, 0); } }; @@ -90,11 +94,16 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - value += MWWorld::Class::get (ptr).getCreatureStats (ptr).mAttributes[mIndex]. - getModified(); + value += + MWWorld::Class::get(ptr) + .getCreatureStats(ptr) + .getAttribute(mIndex) + .getModified(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).mAttributes[mIndex]. - setModified (value, 0, 100); + MWWorld::Class::get(ptr) + .getCreatureStats(ptr) + .getAttribute(mIndex) + .setModified (value, 0, 100); } }; @@ -110,21 +119,19 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value; if (mIndex==0 && MWWorld::Class::get (ptr).hasItemHealth (ptr)) { // health is a special case - Interpreter::Type_Integer value = - MWWorld::Class::get (ptr).getItemMaxHealth (ptr); - runtime.push (value); - - return; + value = MWWorld::Class::get (ptr).getItemMaxHealth (ptr); + } else { + value = + MWWorld::Class::get(ptr) + .getCreatureStats(ptr) + .getDynamic(mIndex) + .getCurrent(); } - - Interpreter::Type_Integer value = - MWWorld::Class::get (ptr).getCreatureStats (ptr).mDynamic[mIndex]. - getCurrent(); - runtime.push (value); } }; @@ -145,8 +152,10 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).mDynamic[mIndex]. - setModified (value, 0); + MWWorld::Class::get(ptr) + .getCreatureStats(ptr) + .getDynamic(mIndex) + .setModified(value, 0); } }; @@ -168,12 +177,12 @@ namespace MWScript MWMechanics::CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - Interpreter::Type_Integer current = stats.mDynamic[mIndex].getCurrent(); + Interpreter::Type_Integer current = stats.getDynamic(mIndex).getCurrent(); - stats.mDynamic[mIndex].setModified ( - diff + stats.mDynamic[mIndex].getModified(), 0); + stats.getDynamic(mIndex).setModified( + diff + stats.getDynamic(mIndex).getModified(), 0); - stats.mDynamic[mIndex].setCurrent (diff + current); + stats.getDynamic(mIndex).setCurrent(diff + current); } }; @@ -195,9 +204,9 @@ namespace MWScript MWMechanics::CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - Interpreter::Type_Integer current = stats.mDynamic[mIndex].getCurrent(); + Interpreter::Type_Integer current = stats.getDynamic(mIndex).getCurrent(); - stats.mDynamic[mIndex].setCurrent (diff + current); + stats.getDynamic(mIndex).setCurrent (diff + current); } }; @@ -218,10 +227,10 @@ namespace MWScript Interpreter::Type_Float value = 0; - Interpreter::Type_Float max = stats.mDynamic[mIndex].getModified(); + Interpreter::Type_Float max = stats.getDynamic(mIndex).getModified(); if (max>0) - value = stats.mDynamic[mIndex].getCurrent() / max; + value = stats.getDynamic(mIndex).getCurrent() / max; runtime.push (value); } @@ -335,7 +344,7 @@ namespace MWScript // make sure a spell with this ID actually exists. MWBase::Environment::get().getWorld()->getStore().spells.find (id); - MWWorld::Class::get (ptr).getCreatureStats (ptr).mSpells.add (id); + MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().add (id); } }; @@ -351,7 +360,7 @@ namespace MWScript std::string id = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).mSpells.remove (id); + MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().remove (id); } }; @@ -371,8 +380,8 @@ namespace MWScript Interpreter::Type_Integer value = 0; for (MWMechanics::Spells::TIterator iter ( - MWWorld::Class::get (ptr).getCreatureStats (ptr).mSpells.begin()); - iter!=MWWorld::Class::get (ptr).getCreatureStats (ptr).mSpells.end(); ++iter) + MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().begin()); + iter!=MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().end(); ++iter) if (*iter==id) { value = 1; From df70b2dc9f180db935bf25faf75ce3f577ec858a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 22 Jul 2012 21:25:37 +0200 Subject: [PATCH 205/298] updated credits.txt --- credits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/credits.txt b/credits.txt index 16b41d56a..3a942bfd8 100644 --- a/credits.txt +++ b/credits.txt @@ -3,6 +3,7 @@ CREDITS Current Developers: Aleksandar Jovanov Alexander “Ace” Olofsson +Artem “greye” Kotsynyak athile BrotherBrick Cris “Mirceam” Mihalache From f0b3142966ecaf8a8c9df68eae29dd3a4ac43823 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 02:54:50 +0200 Subject: [PATCH 206/298] switch to glsl 1.2 --- extern/shiny | 2 +- files/materials/clouds.shader | 2 +- files/materials/core.h | 58 +++++++++++++++++++++++++++-- files/materials/moon.shader | 2 +- files/materials/objects.shader | 3 +- files/materials/quad.shader | 2 +- files/materials/shadowcaster.shader | 2 +- files/materials/stars.shader | 2 +- files/materials/sun.shader | 2 +- files/materials/terrain.shader | 5 ++- files/materials/water.shader | 4 +- 11 files changed, 68 insertions(+), 16 deletions(-) diff --git a/extern/shiny b/extern/shiny index fdc92a424..2408fda3e 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit fdc92a4243c98f5d86a33cbdf2ed0fbb94dad3f6 +Subproject commit 2408fda3e226dda1a1aa7bafd07a769324419b37 diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index 7677ecd95..f4258bf5d 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -6,7 +6,7 @@ SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shInput(float2, uv0) + shVertexInput(float2, uv0) shOutput(float2, UV) shColourInput(float4) shOutput(float4, colourPassthrough) diff --git a/files/materials/core.h b/files/materials/core.h index c4d287761..306073a77 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -11,6 +11,7 @@ #define shUniform(type, name) , uniform type name + #define shVertexInput(type, name) , in type name : TEXCOORD@shCounter(1) #define shInput(type, name) , in type name : TEXCOORD@shCounter(1) #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2) @@ -52,7 +53,8 @@ #endif #if SH_GLSL == 1 - @version 130 + + @version 120 #define float2 vec2 #define float3 vec3 @@ -61,7 +63,7 @@ #define int3 ivec3 #define int4 ivec4/ #define shTexture2D sampler2D - #define shSample(tex, coord) texture(tex, coord) + #define shSample(tex, coord) texture2D(tex, coord) #define shLerp(a, b, t) mix(a, b, t) #define shSaturate(a) clamp(a, 0.0, 1.0) @@ -71,13 +73,19 @@ #define shMatrixMult(m, v) (m * v) + #define shOutputPosition gl_Position + + #define float4x4 mat4 + + // GLSL 1.3 + #if 0 + // automatically recognized by ogre when the input name equals this #define shInputPosition vertex - #define shOutputPosition gl_Position #define shOutputColour(num) oColor##num - #define float4x4 mat4 + #define shInput(type, name) in type name; #define shOutput(type, name) out type name; @@ -105,5 +113,47 @@ void main(void) + #endif + + #endif + + // GLSL 1.2 + + #if 1 + + // automatically recognized by ogre when the input name equals this + #define shInputPosition gl_Vertex + + #define shOutputColour(num) gl_FragData[num] + + #define shVertexInput(type, name) attribute type name; + #define shInput(type, name) varying type name; + #define shOutput(type, name) varying type name; + + // automatically recognized by ogre when the input name equals this + #define shNormalInput(type) attribute type normal; + #define shColourInput(type) attribute type colour; + + #ifdef SH_VERTEX_SHADER + + #define SH_BEGIN_PROGRAM + + #define SH_START_PROGRAM \ + void main(void) + + #endif + + #ifdef SH_FRAGMENT_SHADER + + #define shDeclareMrtOutput(num) + + #define SH_BEGIN_PROGRAM + + #define SH_START_PROGRAM \ + void main(void) + + + #endif + #endif #endif diff --git a/files/materials/moon.shader b/files/materials/moon.shader index 4a4eaf0b4..7640563ce 100644 --- a/files/materials/moon.shader +++ b/files/materials/moon.shader @@ -7,7 +7,7 @@ SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shInput(float2, uv0) + shVertexInput(float2, uv0) shOutput(float2, UV) SH_START_PROGRAM diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 39c9b42e0..238ccdcf6 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -28,7 +28,7 @@ SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shInput(float2, uv0) + shVertexInput(float2, uv0) shOutput(float2, UV) shNormalInput(float4) #ifdef NEED_DEPTH @@ -207,6 +207,7 @@ float3 waterEyePos = float3(1,1,1); if (worldPos.y < waterLevel && waterEnabled == 1) { + // NOTE: this calculation would be wrong for non-uniform scaling float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); diff --git a/files/materials/quad.shader b/files/materials/quad.shader index 4fa38cac9..4620588c3 100644 --- a/files/materials/quad.shader +++ b/files/materials/quad.shader @@ -3,7 +3,7 @@ #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM - shInput(float2, uv0) + shVertexInput(float2, uv0) shOutput(float2, UV) shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) SH_START_PROGRAM diff --git a/files/materials/shadowcaster.shader b/files/materials/shadowcaster.shader index a5551509d..b312d414c 100644 --- a/files/materials/shadowcaster.shader +++ b/files/materials/shadowcaster.shader @@ -6,7 +6,7 @@ SH_BEGIN_PROGRAM #if ALPHA - shInput(float2, uv0) + shVertexInput(float2, uv0) shOutput(float2, UV) #endif shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) diff --git a/files/materials/stars.shader b/files/materials/stars.shader index 75592fc72..5a55d171e 100644 --- a/files/materials/stars.shader +++ b/files/materials/stars.shader @@ -7,7 +7,7 @@ SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shInput(float2, uv0) + shVertexInput(float2, uv0) shOutput(float2, UV) shOutput(float, fade) diff --git a/files/materials/sun.shader b/files/materials/sun.shader index ceab60565..45cd2f24b 100644 --- a/files/materials/sun.shader +++ b/files/materials/sun.shader @@ -7,7 +7,7 @@ SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shInput(float2, uv0) + shVertexInput(float2, uv0) shOutput(float2, UV) SH_START_PROGRAM diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index e0187959f..a562d1ec3 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -54,8 +54,8 @@ shUniform(float2, lodMorph) @shAutoConstant(lodMorph, custom, 1001) - shInput(float2, uv0) - shInput(float2, delta) // lodDelta, lodThreshold + shVertexInput(float2, uv0) + shVertexInput(float2, delta) // lodDelta, lodThreshold #if SHADOWS shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) @@ -226,6 +226,7 @@ float3 waterEyePos = float3(1,1,1); if (worldPos.y < waterLevel) { + // NOTE: this calculation would be wrong for non-uniform scaling float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); diff --git a/files/materials/water.shader b/files/materials/water.shader index 805cf7b81..2145919b0 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -15,7 +15,7 @@ SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shInput(float2, uv0) + shVertexInput(float2, uv0) shOutput(float2, UV) shOutput(float, depth) @@ -67,7 +67,7 @@ SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shInput(float2, uv0) + shVertexInput(float2, uv0) shOutput(float2, UV) shOutput(float3, screenCoordsPassthrough) From 268987835dc90105fbadc6d5540ac6c9c7cd9c18 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 12:19:53 +0200 Subject: [PATCH 207/298] fix the crash --- extern/shiny | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/shiny b/extern/shiny index 2408fda3e..41245d136 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 2408fda3e226dda1a1aa7bafd07a769324419b37 +Subproject commit 41245d1361bc0242e5d2c2313caffb711207e5fc From bc7876e980fb4093a1274e953e12981f6d66ace9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 13:04:48 +0200 Subject: [PATCH 208/298] use alpha map for moons --- files/materials/moon.shader | 9 ++++----- files/materials/sky.mat | 5 +++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/files/materials/moon.shader b/files/materials/moon.shader index 7640563ce..02f3d8001 100644 --- a/files/materials/moon.shader +++ b/files/materials/moon.shader @@ -20,6 +20,7 @@ SH_BEGIN_PROGRAM shSampler2D(diffuseMap) + shSampler2D(alphaMap) shInput(float2, UV) #if MRT shDeclareMrtOutput(1) @@ -36,17 +37,15 @@ shOutputColour(0) = float4(materialEmissive.xyz, 1) * tex; - // use a circle for the alpha (compute UV distance to center) - // looks a bit bad because it's not filtered on the edges, - // but cheaper than a seperate alpha texture. - float sqrUVdist = pow(UV.x-0.5,2) + pow(UV.y-0.5, 2); - shOutputColour(0).a = materialDiffuse.a * (sqrUVdist >= 0.24 ? 0 : 1); + shOutputColour(0).a = shSample(alphaMap, UV).a * materialDiffuse.a; + shOutputColour(0).rgb += (1-tex.a) * shOutputColour(0).a * atmosphereColour.rgb; //fill dark side of moon with atmosphereColour shOutputColour(0).rgb += (1-materialDiffuse.a) * atmosphereColour.rgb; //fade bump #if MRT shOutputColour(1) = float4(1,1,1,1); #endif + } #endif diff --git a/files/materials/sky.mat b/files/materials/sky.mat index 17da7fc13..4af90a170 100644 --- a/files/materials/sky.mat +++ b/files/materials/sky.mat @@ -15,6 +15,11 @@ material openmw_moon { texture_alias $texture } + + texture_unit alphaMap + { + direct_texture textures\tx_secunda_full.dds + } } } From afe7c4172906c39e4f2e9ad9b48141da5ad026ce Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 13:47:51 +0200 Subject: [PATCH 209/298] removed some unused code --- apps/openmw/mwrender/water.cpp | 4 ++++ apps/openmw/mwrender/water.hpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 1c0afab67..3aff334d1 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -71,6 +71,7 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mWater->setMaterial(mMaterial); + /* Ogre::Entity* underwaterDome = mSceneManager->createEntity ("underwater_dome.mesh"); underwaterDome->setRenderQueueGroup (RQG_UnderWater); mUnderwaterDome = mSceneManager->getRootSceneNode ()->createChildSceneNode (); @@ -78,6 +79,7 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mUnderwaterDome->setScale(10000,10000,10000); mUnderwaterDome->setVisible(false); underwaterDome->setMaterialName("Underwater_Dome"); + */ mSceneManager->addRenderQueueListener(this); @@ -309,9 +311,11 @@ void Water::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invo void Water::update(float dt) { + /* Ogre::Vector3 pos = mCamera->getDerivedPosition (); pos.y = -mWaterPlane.d; mUnderwaterDome->setPosition (pos); + */ mWaterTimer += dt / 30.0 * MWBase::Environment::get().getWorld()->getTimeScaleFactor(); sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(mWaterTimer))); diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 60e39c496..d0a5a4352 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -43,7 +43,7 @@ namespace MWRender { Ogre::SceneNode *mWaterNode; Ogre::Entity *mWater; - Ogre::SceneNode* mUnderwaterDome; + //Ogre::SceneNode* mUnderwaterDome; bool mIsUnderwater; bool mActive; From 2eca27bba9b7649f939acaf66bbcd0755bea81e6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 14:21:40 +0200 Subject: [PATCH 210/298] fix horizon color of the sky reflection --- apps/openmw/mwrender/renderingmanager.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 852288d14..b535cb6ae 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -368,6 +368,9 @@ void RenderingManager::configureFog(MWWorld::Ptr::CellStore &mCell) color.setAsABGR (mCell.cell->ambi.fog); configureFog(mCell.cell->ambi.fogDensity, color); + + if (mWater) + mWater->setViewportBackground (Ogre::ColourValue(0.8f, 0.9f, 1.0f)); } void RenderingManager::configureFog(const float density, const Ogre::ColourValue& colour) @@ -382,6 +385,9 @@ void RenderingManager::configureFog(const float density, const Ogre::ColourValue mRendering.getCamera()->setFarClipDistance ( max / density ); mRendering.getViewport()->setBackgroundColour (colour); + if (mWater) + mWater->setViewportBackground (colour); + sh::Factory::getInstance ().setSharedParameter ("viewportBackground", sh::makeProperty (new sh::Vector3(colour.r, colour.g, colour.b))); From ac5bd38df9522da88932725e62d9859720f8fc75 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 14:59:57 +0200 Subject: [PATCH 211/298] some cmake fixes --- CMakeLists.txt | 6 +----- extern/shiny | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c0b5bf451..f124f1383 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,14 +189,10 @@ endif() set(BOOST_COMPONENTS system filesystem program_options thread) if (Boost_VERSION LESS 104900) - set(SHINY_USE_WAVE_SYSTEM_INSTALL TRUE) + set(SHINY_USE_WAVE_SYSTEM_INSTALL "TRUE") set(BOOST_COMPONENTS ${BOOST_COMPONENTS} wave) -else() - set(SHINY_USE_WAVE_SYSTEM_INSTALL FALSE) endif() -MESSAGE(STATUS ${BOOST_COMPONENTS}) - find_package(OGRE REQUIRED) find_package(MyGUI REQUIRED) find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) diff --git a/extern/shiny b/extern/shiny index 41245d136..73ddc737c 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 41245d1361bc0242e5d2c2313caffb711207e5fc +Subproject commit 73ddc737ce6334b264649fd4b0a16109fd8b8a99 From 1fef0860887fd41a1b775efb9f7f278f9b30fd3f Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 15:36:12 +0200 Subject: [PATCH 212/298] Revert "Merge remote-tracking branch 'mark76/multiple_esm_esp' into nif-cleanup" This reverts commit 546b640022e3ec111b5c795c3fc1d067c3b1a684, reversing changes made to fcaa8aae06d791c37daab0897f4fd19f6b2129b5. --- apps/openmw/engine.cpp | 30 +++++-------------- apps/openmw/engine.hpp | 8 ++--- apps/openmw/main.cpp | 24 +++++++-------- apps/openmw/mwrender/terrain.cpp | 24 ++++----------- apps/openmw/mwrender/terrain.hpp | 5 +--- apps/openmw/mwworld/worldimp.cpp | 36 ++++++---------------- apps/openmw/mwworld/worldimp.hpp | 3 +- components/esm/esm_reader.hpp | 8 ----- components/esm/loadland.cpp | 1 - components/esm/loadland.hpp | 1 - components/esm_store/reclists.hpp | 50 +++++++------------------------ components/esm_store/store.cpp | 7 ----- 12 files changed, 48 insertions(+), 149 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f41d453b5..ab5b63071 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -207,32 +207,18 @@ void OMW::Engine::setCell (const std::string& cellName) // Set master file (esm) // - If the given name does not have an extension, ".esm" is added automatically +// - Currently OpenMW only supports one master at the same time. void OMW::Engine::addMaster (const std::string& master) { - mMaster.push_back(master); - std::string &str = mMaster.back(); + assert (mMaster.empty()); + mMaster = master; - // Append .esm if not already there - std::string::size_type sep = str.find_last_of ("."); + // Append .esm if not already there + std::string::size_type sep = mMaster.find_last_of ("."); if (sep == std::string::npos) { - str += ".esm"; - } -} - -// Add plugin file (esp) - -void OMW::Engine::addPlugin (const std::string& plugin) -{ - mPlugins.push_back(plugin); - std::string &str = mPlugins.back(); - - // Append .esp if not already there - std::string::size_type sep = str.find_last_of ("."); - if (sep == std::string::npos) - { - str += ".esp"; + mMaster += ".esm"; } } @@ -333,8 +319,8 @@ void OMW::Engine::go() MWGui::CursorReplace replacer; // Create the world - mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins, - mResDir, mNewGame, mEncoding, mFallbackMap) ); + mEnvironment.setWorld (new MWWorld::World (*mOgre, mFileCollections, mMaster, + mResDir, mNewGame, mEncoding, mFallbackMap)); // Create window manager - this manages all the MW-specific GUI windows MWScript::registerExtensions (mExtensions); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 0582800c9..031cae551 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -64,8 +64,7 @@ namespace OMW boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; std::string mCellName; - std::vector mMaster; - std::vector mPlugins; + std::string mMaster; int mFpsLevel; bool mDebug; bool mVerboseScripts; @@ -121,12 +120,9 @@ namespace OMW /// Set master file (esm) /// - If the given name does not have an extension, ".esm" is added automatically + /// - Currently OpenMW only supports one master at the same time. void addMaster(const std::string& master); - /// Same as "addMaster", but for plugin files (esp) - /// - If the given name does not have an extension, ".esp" is added automatically - void addPlugin(const std::string& plugin); - /// Enable fps counter void showFPS(int level); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 9fe333a28..993ec6623 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -224,23 +224,19 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat master.push_back("Morrowind"); } - StringsVector plugin = variables["plugin"].as(); - int cnt = master.size() + plugin.size(); - if (cnt > 255) - { - std::cerr - << "Error: Trying to load more than 255 master and plugin files! This will break all combaibility and is not supported!" + if (master.size() > 1) + { + std::cout + << "Ignoring all but the first master file (multiple master files not yet supported)." << std::endl; - return false; } - for (std::vector::size_type i = 0; i < master.size(); i++) + engine.addMaster(master[0]); + + StringsVector plugin = variables["plugin"].as(); + if (!plugin.empty()) { - engine.addMaster(master[i]); - } - for (std::vector::size_type i = 0; i < plugin.size(); i++) - { - engine.addPlugin(plugin[i]); - } + std::cout << "Ignoring plugin files (plugins not yet supported)." << std::endl; + } // startup-settings engine.setCell(variables["start"].as()); diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index 909836659..691e7c4af 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -141,7 +141,7 @@ namespace MWRender std::map indexes; initTerrainTextures(&terrainData, cellX, cellY, x * numTextures, y * numTextures, - numTextures, indexes, land->plugin); + numTextures, indexes); if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL) { @@ -200,14 +200,8 @@ namespace MWRender void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, int cellX, int cellY, int fromX, int fromY, int size, - std::map& indexes, - size_t plugin) + std::map& indexes) { - // FIXME: In a multiple esm configuration, we have multiple palettes. Since this code - // crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need - // to adopt the following code for a dynamic palette. And this is evil - the current design - // does not work well for this task... - assert(terrainData != NULL && "Must have valid terrain data"); assert(fromX >= 0 && fromY >= 0 && "Can't get a terrain texture on terrain outside the current cell"); @@ -220,16 +214,12 @@ namespace MWRender // //If we don't sort the ltex indexes, the splatting order may differ between //cells which may lead to inconsistent results when shading between cells - int num = MWBase::Environment::get().getWorld()->getStore().landTexts.getSizePlugin(plugin); std::set ltexIndexes; for ( int y = fromY - 1; y < fromY + size + 1; y++ ) { for ( int x = fromX - 1; x < fromX + size + 1; x++ ) { - int idx = getLtexIndexAt(cellX, cellY, x, y); - if (idx > num) - idx = 0; - ltexIndexes.insert(idx); + ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y)); } } @@ -241,7 +231,7 @@ namespace MWRender iter != ltexIndexes.end(); ++iter ) { - uint16_t ltexIndex = *iter; + const uint16_t ltexIndex = *iter; //this is the base texture, so we can ignore this at present if ( ltexIndex == baseTexture ) { @@ -254,10 +244,8 @@ namespace MWRender { //NB: All vtex ids are +1 compared to the ltex ids - /* - assert( (int)mEnvironment.mWorld->getStore().landTexts.getSizePlugin(plugin) >= (int)ltexIndex - 1 && + assert( (int)MWBase::Environment::get().getWorld()->getStore().landTexts.getSize() >= (int)ltexIndex - 1 && "LAND.VTEX must be within the bounds of the LTEX array"); - */ std::string texture; if ( ltexIndex == 0 ) @@ -266,7 +254,7 @@ namespace MWRender } else { - texture = MWBase::Environment::get().getWorld()->getStore().landTexts.search(ltexIndex-1, plugin)->texture; + texture = MWBase::Environment::get().getWorld()->getStore().landTexts.search(ltexIndex-1)->texture; //TODO this is needed due to MWs messed up texture handling texture = texture.substr(0, texture.rfind(".")) + ".dds"; } diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp index 94072fc40..c83d96cf4 100644 --- a/apps/openmw/mwrender/terrain.hpp +++ b/apps/openmw/mwrender/terrain.hpp @@ -71,14 +71,11 @@ namespace MWRender{ * @param size the size (number of splats) to get * @param indexes a mapping of ltex index to the terrain texture layer that * can be used by initTerrainBlendMaps - * @param plugin the index of the plugin providing the texture list for this - * cell data; required because MW uses texture data on a per-plugin base */ void initTerrainTextures(Ogre::Terrain::ImportData* terrainData, int cellX, int cellY, int fromX, int fromY, int size, - std::map& indexes, - size_t plugin = 0); + std::map& indexes); /** * Creates the blend (splatting maps) for the given terrain from the ltex data. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ae517071e..a68f08e34 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -167,8 +167,7 @@ namespace MWWorld World::World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::vector& master, - const std::vector& plugins, const boost::filesystem::path& resDir, bool newGame, + const std::string& master, const boost::filesystem::path& resDir, bool newGame, const std::string& encoding, std::map fallbackMap) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), mSky (true), mNextDynamicRecord (0), mCells (mStore, mEsm), @@ -181,32 +180,15 @@ namespace MWWorld mWeatherManager = new MWWorld::WeatherManager(mRendering); - int idx = 0; - for (std::vector::size_type i = 0; i < master.size(); i++, idx++) - { - boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master[i])); - - std::cout << "Loading ESM " << masterPath.string() << "\n"; - - // This parses the ESM file and loads a sample cell - mEsm.setEncoding(encoding); - mEsm.open (masterPath.string()); - mEsm.setIndex(idx); - mStore.load (mEsm); - } + boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master)); + + std::cout << "Loading ESM " << masterPath.string() << "\n"; + + // This parses the ESM file and loads a sample cell + mEsm.setEncoding(encoding); + mEsm.open (masterPath.string()); + mStore.load (mEsm); - for (std::vector::size_type i = 0; i < plugins.size(); i++, idx++) - { - boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i])); - - std::cout << "Loading ESP " << pluginPath.string() << "\n"; - - // This parses the ESP file and loads a sample cell - mEsm.setEncoding(encoding); - mEsm.open (pluginPath.string()); - mEsm.setIndex(idx); - mStore.load (mEsm); - } MWRender::Player* play = &(mRendering->getPlayer()); mPlayer = new MWWorld::Player (play, mStore.npcs.find ("player"), *this); mPhysics->addActor (mPlayer->getPlayer().getRefData().getHandle(), "", Ogre::Vector3 (0, 0, 0)); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index af6232aaf..43b178fe3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -93,8 +93,7 @@ namespace MWWorld World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::vector& master, - const std::vector& plugins, const boost::filesystem::path& resDir, bool newGame, + const std::string& master, const boost::filesystem::path& resDir, bool newGame, const std::string& encoding, std::map fallbackMap); virtual ~World(); diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index 0326fdc04..13f1f4a01 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -189,14 +189,6 @@ public: void openRaw(const std::string &file); - // This is a quick hack for multiple esm/esp files. Each plugin introduces its own - // terrain palette, but ESMReader does not pass a reference to the correct plugin - // to the individual load() methods. This hack allows to pass this reference - // indirectly to the load() method. - int idx; - void setIndex(const int index) {idx = index;} - const int getIndex() {return idx;} - /************************************************************************* * * Medium-level reading shortcuts diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 822952c91..96afdf831 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -23,7 +23,6 @@ Land::~Land() void Land::load(ESMReader &esm) { mEsm = &esm; - plugin = mEsm->getIndex(); // Get the grid location esm.getSubNameIs("INTV"); diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index d03012d38..ebc314a28 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -17,7 +17,6 @@ struct Land int flags; // Only first four bits seem to be used, don't know what // they mean. int X, Y; // Map coordinates. - int plugin; // Plugin index, used to reference the correct material palette. // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. diff --git a/components/esm_store/reclists.hpp b/components/esm_store/reclists.hpp index 668c4e58e..ffecfc8de 100644 --- a/components/esm_store/reclists.hpp +++ b/components/esm_store/reclists.hpp @@ -26,7 +26,6 @@ namespace ESMS virtual void load(ESMReader &esm, const std::string &id) = 0; virtual int getSize() = 0; - virtual void remove(const std::string &id) {}; virtual void listIdentifier (std::vector& identifier) const = 0; static std::string toLower (const std::string& name) @@ -58,14 +57,6 @@ namespace ESMS list[id2].load(esm); } - // Delete the given object ID - void remove(const std::string &id) - { - std::string id2 = toLower (id); - - list.erase(id2); - } - // Find the given object ID, or return NULL if not found. const X* search(const std::string &id) const { @@ -277,57 +268,38 @@ namespace ESMS { virtual ~LTexList() {} - // For multiple ESM/ESP files we need one list per file. - typedef std::vector LandTextureList; - std::vector ltex; + // TODO: For multiple ESM/ESP files we need one list per file. + std::vector ltex; LTexList() { - ltex.push_back(LandTextureList()); - LandTextureList <exl = ltex[0]; // More than enough to hold Morrowind.esm. - ltexl.reserve(128); + ltex.reserve(128); } - const LandTexture* search(size_t index, size_t plugin) const + const LandTexture* search(size_t index) const { - assert(plugin < ltex.size()); - const LandTextureList <exl = ltex[plugin]; - - assert(index < ltexl.size()); - return <exl.at(index); + assert(index < ltex.size()); + return <ex.at(index); } int getSize() { return ltex.size(); } int getSize() const { return ltex.size(); } - int getSizePlugin(size_t plugin) { assert(plugin < ltex.size()); return ltex[plugin].size(); } - int getSizePlugin(size_t plugin) const { assert(plugin < ltex.size()); return ltex[plugin].size(); } - - virtual void listIdentifier (std::vector& identifier) const {} + virtual void listIdentifier (std::vector& identifier) const {} - void load(ESMReader &esm, const std::string &id, size_t plugin) + void load(ESMReader &esm, const std::string &id) { LandTexture lt; lt.load(esm); lt.id = id; // Make sure we have room for the structure - if (plugin >= ltex.size()) { - ltex.resize(plugin+1); - } - LandTextureList <exl = ltex[plugin]; - if(lt.index + 1 > (int)ltexl.size()) - ltexl.resize(lt.index+1); + if(lt.index + 1 > (int)ltex.size()) + ltex.resize(lt.index+1); // Store it - ltexl[lt.index] = lt; - } - - void load(ESMReader &esm, const std::string &id) - { - size_t plugin = esm.getIndex(); - load(esm, id, plugin); + ltex[lt.index] = lt; } }; diff --git a/components/esm_store/store.cpp b/components/esm_store/store.cpp index b6972355c..c676601e5 100644 --- a/components/esm_store/store.cpp +++ b/components/esm_store/store.cpp @@ -67,13 +67,6 @@ void ESMStore::load(ESMReader &esm) { // Load it std::string id = esm.getHNOString("NAME"); - // ... unless it got deleted! - if (esm.isNextSub("DELE")) { - esm.skipRecord(); - all.erase(id); - it->second->remove(id); - continue; - } it->second->load(esm, id); if (n.val==ESM::REC_DIAL) From 5572421576351e3140a95942e73ff0a29e2cddcc Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 15:40:10 +0200 Subject: [PATCH 213/298] submodule update for "fix a typo" --- extern/shiny | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/shiny b/extern/shiny index 73ddc737c..9e833c95a 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 73ddc737ce6334b264649fd4b0a16109fd8b8a99 +Subproject commit 9e833c95a1dbd426bc87818ed5dbad02917dd504 From d5384403f3aa8f32e5d567eff807a2e697a118d1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 15:46:18 +0200 Subject: [PATCH 214/298] forgot something else --- extern/shiny | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/shiny b/extern/shiny index 9e833c95a..5546a5bd8 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 9e833c95a1dbd426bc87818ed5dbad02917dd504 +Subproject commit 5546a5bd8474ef328dfedae2df42126cdaf9515f From ab35bfa32cb4642750538c77bf8996419d9bdc7a Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 18:19:34 +0200 Subject: [PATCH 215/298] fixed a sky issue --- apps/openmw/mwrender/sky.cpp | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index d78a3caff..d928995f4 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -287,21 +287,12 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) void SkyManager::create() { + assert(!mCreated); + sh::Factory::getInstance().setSharedParameter ("cloudBlendFactor", sh::makeProperty(new sh::FloatValue(0))); - sh::Factory::getInstance().setSharedParameter ("cloudOpacity", - sh::makeProperty(new sh::FloatValue(1))); - sh::Factory::getInstance().setSharedParameter ("cloudColour", - sh::makeProperty(new sh::Vector3(1,1,1))); - sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", - sh::makeProperty(new sh::FloatValue(0))); - sh::Factory::getInstance().setSharedParameter ("nightFade", - sh::makeProperty(new sh::FloatValue(0))); - sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", ""); - sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", ""); - - // Create overlay used for thunderstorm + // Create light used for thunderstorm mLightning = mSceneMgr->createLight(); mLightning->setType (Ogre::Light::LT_DIRECTIONAL); mLightning->setDirection (Ogre::Vector3(0.3, -0.7, 0.3)); @@ -332,19 +323,15 @@ void SkyManager::create() night1_ent->setVisibilityFlags(RV_Sky); night1_ent->setCastShadows(false); - for (unsigned int i=0; igetNumSubEntities(); ++i) - { - std::string matName = "openmw_stars_" + boost::lexical_cast(i); - sh::MaterialInstance* m = sh::Factory::getInstance ().createMaterialInstance (matName, "openmw_stars"); - - std::string textureName = sh::retrieveValue( - sh::Factory::getInstance().getMaterialInstance(night1_ent->getSubEntity (i)->getMaterialName ())->getProperty("diffuseMap"), NULL).get(); + std::string matName = "openmw_stars_" + boost::lexical_cast(i); + sh::MaterialInstance* m = sh::Factory::getInstance ().createMaterialInstance (matName, "openmw_stars"); - m->setProperty ("texture", sh::makeProperty(new sh::StringValue(textureName))); + std::string textureName = sh::retrieveValue( + sh::Factory::getInstance().getMaterialInstance(night1_ent->getSubEntity (0)->getMaterialName ())->getProperty("diffuseMap"), NULL).get(); - night1_ent->getSubEntity(i)->setMaterialName (matName); + m->setProperty ("texture", sh::makeProperty(new sh::StringValue(textureName))); - } + night1_ent->getSubEntity(0)->setMaterialName (matName); } @@ -370,7 +357,6 @@ void SkyManager::create() Entity* clouds_ent = entities.mEntities[i]; clouds_ent->setVisibilityFlags(RV_Sky); clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); - clouds_node->attachObject(clouds_ent); clouds_ent->getSubEntity(0)->setMaterialName ("openmw_clouds"); clouds_ent->setCastShadows(false); From 9f9183ff73b1a85afff14a7c7f8ec053f30e01f9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 18:25:20 +0200 Subject: [PATCH 216/298] depth bias --- files/materials/shadows.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/files/materials/shadows.h b/files/materials/shadows.h index 9127d28f7..769a4fea7 100644 --- a/files/materials/shadows.h +++ b/files/materials/shadows.h @@ -1,13 +1,15 @@ +#define FIXED_BIAS 0.005 + float depthShadowPCF (shTexture2D shadowMap, float4 shadowMapPos, float2 offset) { shadowMapPos /= shadowMapPos.w; float3 o = float3(offset.xy, -offset.x) * 0.3; //float3 o = float3(0,0,0); - float c = (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy - o.xy).r) ? 1 : 0; // top left - c += (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy + o.xy).r) ? 1 : 0; // bottom right - c += (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy + o.zy).r) ? 1 : 0; // bottom left - c += (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy - o.zy).r) ? 1 : 0; // top right + float c = (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy - o.xy).r) ? 1 : 0; // top left + c += (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy + o.xy).r) ? 1 : 0; // bottom right + c += (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy + o.zy).r) ? 1 : 0; // bottom left + c += (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy - o.zy).r) ? 1 : 0; // top right return c / 4; } From a70f93b0244fde6065157d5a827be30af8e3e1e5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 20:12:42 +0200 Subject: [PATCH 217/298] fix "Crash when running openmw with --start="XYZ", remove HLSL for windows --- apps/openmw/mwgui/settingswindow.cpp | 16 +++++++++------- apps/openmw/mwrender/renderingmanager.cpp | 9 +++++++-- apps/openmw/mwrender/sky.cpp | 1 + apps/openmw/mwrender/water.cpp | 4 ---- files/materials/core.h | 6 +++++- files/materials/objects.shaderset | 2 +- files/materials/terrain.shaderset | 2 +- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 4f8ad77c9..599783e42 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -75,12 +75,9 @@ namespace return boost::lexical_cast(xaspect) + " : " + boost::lexical_cast(yaspect); } - std::string hlslGlsl () + bool hasGLSL () { - if (Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") == std::string::npos) - return "hlsl"; - else - return "glsl"; + return (Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") != std::string::npos); } } @@ -389,8 +386,13 @@ namespace MWGui { std::string val = static_cast(_sender)->getCaption(); if (val == "off") - val = hlslGlsl(); - else if (val == hlslGlsl()) + { + if (hasGLSL ()) + val = "glsl"; + else + val = "cg"; + } + else if (val == "glsl") val = "cg"; else val = "off"; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b535cb6ae..bcf9f2101 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -46,7 +46,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const if (Settings::Manager::getString("shader mode", "General") == "") { if (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") == std::string::npos) - Settings::Manager::setString("shader mode", "General", "hlsl"); + Settings::Manager::setString("shader mode", "General", "cg"); else Settings::Manager::setString("shader mode", "General", "glsl"); } @@ -113,6 +113,10 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const sh::Factory::getInstance ().setSharedParameter ("viewportBackground", sh::makeProperty (new sh::Vector3(0,0,0))); sh::Factory::getInstance ().setSharedParameter ("waterEnabled", sh::makeProperty (new sh::FloatValue(0.0))); + sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty(new sh::FloatValue(0))); + sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(0))); + sh::Factory::getInstance ().setSharedParameter ("windDir_windSpeed", sh::makeProperty(new sh::Vector3(0.5, -0.8, 0.2))); + sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(1, 0.6))); applyCompositors(); @@ -269,7 +273,8 @@ void RenderingManager::update (float duration){ checkUnderwater(); - mWater->update(duration); + if (mWater) + mWater->update(duration); } void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store){ if(store->cell->data.flags & store->cell->HasWater diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2a2df7943..aeefb95d1 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -297,6 +297,7 @@ void SkyManager::create() sh::makeProperty(new sh::FloatValue(0))); sh::Factory::getInstance().setSharedParameter ("nightFade", sh::makeProperty(new sh::FloatValue(0))); + sh::Factory::getInstance().setSharedParameter ("atmosphereColour", sh::makeProperty(new sh::Vector4(0,0,0,1))); sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", ""); sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", ""); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 3aff334d1..92fc97b3b 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -37,10 +37,6 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel { mSky = rend->getSkyManager(); - sh::Factory::getInstance ().setSharedParameter ("windDir_windSpeed", sh::makeProperty(new sh::Vector3(0.5, -0.8, 0.2))); - sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(0))); - sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(1, 0.6))); - mMaterial = MaterialManager::getSingleton().getByName("Water"); mTop = cell->water; diff --git a/files/materials/core.h b/files/materials/core.h index 306073a77..6e27d349c 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -1,4 +1,8 @@ -#if SH_HLSL == 1 || SH_CG == 1 +#if SH_HLSL == 1 + #error "HLSL is unsupported" +#endif + +#if SH_CG == 1 #define shTexture2D sampler2D #define shSample(tex, coord) tex2D(tex, coord) diff --git a/files/materials/objects.shaderset b/files/materials/objects.shaderset index e84368a5b..ccb975fe9 100644 --- a/files/materials/objects.shaderset +++ b/files/materials/objects.shaderset @@ -10,6 +10,6 @@ shader_set openmw_objects_fragment { source objects.shader type fragment - profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 + profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 profiles_hlsl ps_2_0 } diff --git a/files/materials/terrain.shaderset b/files/materials/terrain.shaderset index 4132b8e9c..be8ecd7d8 100644 --- a/files/materials/terrain.shaderset +++ b/files/materials/terrain.shaderset @@ -10,6 +10,6 @@ shader_set terrain_fragment { source terrain.shader type fragment - profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 + profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 profiles_hlsl ps_2_0 } From 3ebc6fd5904f78e10e04fa749a257ae668ef2fbd Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 20:50:56 +0200 Subject: [PATCH 218/298] fixed a settings window bug --- apps/openmw/mwgui/settingswindow.cpp | 13 ++++++++----- files/mygui/openmw_settings_window.layout | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 599783e42..3c8e06dc0 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -419,11 +419,14 @@ namespace MWGui else { // re-enable - mWaterShaderButton->setEnabled(true); - mReflectObjectsButton->setEnabled(true); - mReflectActorsButton->setEnabled(true); - mReflectTerrainButton->setEnabled(true); - mShadowsEnabledButton->setEnabled(true); + if (MWRender::RenderingManager::waterShaderSupported()) + { + mWaterShaderButton->setEnabled(true); + mReflectObjectsButton->setEnabled(true); + mReflectActorsButton->setEnabled(true); + mReflectTerrainButton->setEnabled(true); + mShadowsEnabledButton->setEnabled(true); + } Settings::Manager::setBool("shaders", "Objects", true); Settings::Manager::setString("shader mode", "General", val); diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 07c307324..a14606ade 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -189,10 +189,10 @@
- + - + From 3aa53fea327b061af6208b99fcbe0cb8e39732ea Mon Sep 17 00:00:00 2001 From: greye Date: Mon, 23 Jul 2012 23:06:53 +0400 Subject: [PATCH 219/298] wrong --- apps/openmw/mwworld/scene.cpp | 124 ---------------------------------- 1 file changed, 124 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 33c67aad8..b28a933b1 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -336,130 +336,6 @@ namespace MWWorld /// \todo this whole code needs major clean up, and doesn't belong in this class. - void Scene::insertObject (const Ptr& ptr, CellStore* cell) - { - std::string type = ptr.getTypeName(); - - MWWorld::Ptr newPtr; - - // insert into the correct CellRefList - if (type == typeid(ESM::Potion).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->potions.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->potions.list.back(), cell); - } - else if (type == typeid(ESM::Apparatus).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->appas.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->appas.list.back(), cell); - } - else if (type == typeid(ESM::Armor).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->armors.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->armors.list.back(), cell); - } - else if (type == typeid(ESM::Book).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->books.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->books.list.back(), cell); - } - else if (type == typeid(ESM::Clothing).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->clothes.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->clothes.list.back(), cell); - } - else if (type == typeid(ESM::Ingredient).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->ingreds.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->ingreds.list.back(), cell); - } - else if (type == typeid(ESM::Light).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->lights.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->lights.list.back(), cell); - } - else if (type == typeid(ESM::Tool).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->lockpicks.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->lockpicks.list.back(), cell); - } - else if (type == typeid(ESM::Repair).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->repairs.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->repairs.list.back(), cell); - } - else if (type == typeid(ESM::Probe).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->probes.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->probes.list.back(), cell); - } - else if (type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->weapons.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->weapons.list.back(), cell); - } - else if (type == typeid(ESM::Miscellaneous).name()) - { - - // if this is gold, we need to fetch the correct mesh depending on the amount of gold. - if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) - { - int goldAmount = ptr.getRefData().getCount(); - - std::string base = "Gold_001"; - if (goldAmount >= 100) - base = "Gold_100"; - else if (goldAmount >= 25) - base = "Gold_025"; - else if (goldAmount >= 10) - base = "Gold_010"; - else if (goldAmount >= 5) - base = "Gold_005"; - - MWWorld::ManualRef newRef (MWBase::Environment::get().getWorld()->getStore(), base); - - MWWorld::LiveCellRef* ref = newRef.getPtr().get(); - - cell->miscItems.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); - - ESM::Position& p = newPtr.getRefData().getPosition(); - p.pos[0] = ptr.getRefData().getPosition().pos[0]; - p.pos[1] = ptr.getRefData().getPosition().pos[1]; - p.pos[2] = ptr.getRefData().getPosition().pos[2]; - } - else - { - MWWorld::LiveCellRef* ref = ptr.get(); - - cell->miscItems.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); - } - } - else - throw std::runtime_error("Trying to insert object of unhandled type"); - - - - newPtr.getRefData().setCount(ptr.getRefData().getCount()); - ptr.getRefData().setCount(0); - newPtr.getRefData().enable(); - - mRendering.addObject(newPtr); - MWWorld::Class::get(newPtr).insertObject(newPtr, *mPhysics); - - } void Scene::addObjectToScene (const Ptr& ptr) { From 9a2690f84921696f8894cdaf8b8ee5e2de9843d8 Mon Sep 17 00:00:00 2001 From: greye Date: Mon, 23 Jul 2012 23:07:28 +0400 Subject: [PATCH 220/298] Revert "wrong" This reverts commit 3aa53fea327b061af6208b99fcbe0cb8e39732ea. --- apps/openmw/mwworld/scene.cpp | 124 ++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b28a933b1..33c67aad8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -336,6 +336,130 @@ namespace MWWorld /// \todo this whole code needs major clean up, and doesn't belong in this class. + void Scene::insertObject (const Ptr& ptr, CellStore* cell) + { + std::string type = ptr.getTypeName(); + + MWWorld::Ptr newPtr; + + // insert into the correct CellRefList + if (type == typeid(ESM::Potion).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->potions.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->potions.list.back(), cell); + } + else if (type == typeid(ESM::Apparatus).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->appas.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->appas.list.back(), cell); + } + else if (type == typeid(ESM::Armor).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->armors.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->armors.list.back(), cell); + } + else if (type == typeid(ESM::Book).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->books.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->books.list.back(), cell); + } + else if (type == typeid(ESM::Clothing).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->clothes.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->clothes.list.back(), cell); + } + else if (type == typeid(ESM::Ingredient).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->ingreds.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->ingreds.list.back(), cell); + } + else if (type == typeid(ESM::Light).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->lights.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->lights.list.back(), cell); + } + else if (type == typeid(ESM::Tool).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->lockpicks.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->lockpicks.list.back(), cell); + } + else if (type == typeid(ESM::Repair).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->repairs.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->repairs.list.back(), cell); + } + else if (type == typeid(ESM::Probe).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->probes.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->probes.list.back(), cell); + } + else if (type == typeid(ESM::Weapon).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->weapons.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->weapons.list.back(), cell); + } + else if (type == typeid(ESM::Miscellaneous).name()) + { + + // if this is gold, we need to fetch the correct mesh depending on the amount of gold. + if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) + { + int goldAmount = ptr.getRefData().getCount(); + + std::string base = "Gold_001"; + if (goldAmount >= 100) + base = "Gold_100"; + else if (goldAmount >= 25) + base = "Gold_025"; + else if (goldAmount >= 10) + base = "Gold_010"; + else if (goldAmount >= 5) + base = "Gold_005"; + + MWWorld::ManualRef newRef (MWBase::Environment::get().getWorld()->getStore(), base); + + MWWorld::LiveCellRef* ref = newRef.getPtr().get(); + + cell->miscItems.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); + + ESM::Position& p = newPtr.getRefData().getPosition(); + p.pos[0] = ptr.getRefData().getPosition().pos[0]; + p.pos[1] = ptr.getRefData().getPosition().pos[1]; + p.pos[2] = ptr.getRefData().getPosition().pos[2]; + } + else + { + MWWorld::LiveCellRef* ref = ptr.get(); + + cell->miscItems.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); + } + } + else + throw std::runtime_error("Trying to insert object of unhandled type"); + + + + newPtr.getRefData().setCount(ptr.getRefData().getCount()); + ptr.getRefData().setCount(0); + newPtr.getRefData().enable(); + + mRendering.addObject(newPtr); + MWWorld::Class::get(newPtr).insertObject(newPtr, *mPhysics); + + } void Scene::addObjectToScene (const Ptr& ptr) { From 02a52c0ff1f7f92170ac25e162da9f34a02686a4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jul 2012 21:39:22 +0200 Subject: [PATCH 221/298] fixed some more settings bugs --- apps/openmw/mwgui/settingswindow.cpp | 17 +++++++++++++---- apps/openmw/mwrender/renderingmanager.cpp | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 3c8e06dc0..f20b72d4a 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -238,6 +238,12 @@ namespace MWGui mReflectTerrainButton->setEnabled(false); } + if (shaders == "off") + { + mUnderwaterButton->setEnabled (false); + mShadowsEnabledButton->setEnabled(false); + } + mFullscreenButton->setCaptionWithReplacing(Settings::Manager::getBool("fullscreen", "Video") ? "#{sOn}" : "#{sOff}"); mVSyncButton->setCaptionWithReplacing(Settings::Manager::getBool("vsync", "Video") ? "#{sOn}": "#{sOff}"); mFPSButton->setCaptionWithReplacing(fpsLevelToStr(Settings::Manager::getInt("fps", "HUD"))); @@ -409,7 +415,9 @@ namespace MWGui mReflectObjectsButton->setEnabled(false); mReflectActorsButton->setEnabled(false); mReflectTerrainButton->setEnabled(false); + mUnderwaterButton->setEnabled(false); Settings::Manager::setBool("shader", "Water", false); + Settings::Manager::setBool("underwater effect", "Water", false); // shadows not supported mShadowsEnabledButton->setEnabled(false); @@ -418,6 +426,9 @@ namespace MWGui } else { + Settings::Manager::setBool("shaders", "Objects", true); + Settings::Manager::setString("shader mode", "General", val); +/ // re-enable if (MWRender::RenderingManager::waterShaderSupported()) { @@ -425,11 +436,9 @@ namespace MWGui mReflectObjectsButton->setEnabled(true); mReflectActorsButton->setEnabled(true); mReflectTerrainButton->setEnabled(true); - mShadowsEnabledButton->setEnabled(true); } - - Settings::Manager::setBool("shaders", "Objects", true); - Settings::Manager::setString("shader mode", "General", val); + mUnderwaterButton->setEnabled(true); + mShadowsEnabledButton->setEnabled(true); } apply(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bcf9f2101..c5449cfba 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -104,6 +104,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const if (!Settings::Manager::getBool("shaders", "Objects")) Settings::Manager::setBool("enabled", "Shadows", false); + sh::Factory::getInstance ().setShadersEnabled (Settings::Manager::getBool("shaders", "Objects")); + sh::Factory::getInstance ().setGlobalSetting ("mrt_output", useMRT() ? "true" : "false"); sh::Factory::getInstance ().setGlobalSetting ("fog", "true"); sh::Factory::getInstance ().setGlobalSetting ("lighting", "true"); From 2d080ce4efef7e6596578acd5537a27240c4d3ca Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 23 Jul 2012 21:47:53 +0200 Subject: [PATCH 222/298] compile fix --- apps/openmw/mwgui/settingswindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index f20b72d4a..d8b0c96d7 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -428,7 +428,7 @@ namespace MWGui { Settings::Manager::setBool("shaders", "Objects", true); Settings::Manager::setString("shader mode", "General", val); -/ + // re-enable if (MWRender::RenderingManager::waterShaderSupported()) { From d36d6aacf49741ccee9c6a6138f55b4123fd9b07 Mon Sep 17 00:00:00 2001 From: greye Date: Mon, 23 Jul 2012 23:56:20 +0400 Subject: [PATCH 223/298] move Scene::insertObject to CellStore::insertObject, part 1 --- apps/openmw/mwworld/cellstore.cpp | 107 +++++++++++++++++++++++++ apps/openmw/mwworld/cellstore.hpp | 6 ++ apps/openmw/mwworld/scene.cpp | 126 ------------------------------ 3 files changed, 113 insertions(+), 126 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 60a7eb6e1..51a46ec3c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -121,4 +121,111 @@ namespace MWWorld } } } + + /// \todo this whole code needs major clean up + void CellStore::insertObject(const Ptr &ptr) + { + std::string type = ptr.getTypeName(); + + MWWorld::Ptr newPtr; + + // insert into the correct CellRefList + if (type == typeid(ESM::Potion).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&potions.insert(*ref), cell); + } + else if (type == typeid(ESM::Apparatus).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&appas.insert(*ref), cell); + } + else if (type == typeid(ESM::Armor).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&armors.insert(*ref), cell); + } + else if (type == typeid(ESM::Book).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&books.insert(*ref), cell); + } + else if (type == typeid(ESM::Clothing).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&clothes.insert(*ref), cell); + } + else if (type == typeid(ESM::Ingredient).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&ingreds.insert(*ref), cell); + } + else if (type == typeid(ESM::Light).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&lights.insert(*ref), cell); + } + else if (type == typeid(ESM::Tool).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&lockpicks.insert(*ref), cell); + } + else if (type == typeid(ESM::Repair).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&repairs.insert(*ref), cell); + } + else if (type == typeid(ESM::Probe).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&probes.insert(*ref), cell); + } + else if (type == typeid(ESM::Weapon).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&weapons.insert(*ref), cell); + } + else if (type == typeid(ESM::Miscellaneous).name()) + { + + // if this is gold, we need to fetch the correct mesh depending on the amount of gold. + if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) + { + int goldAmount = ptr.getRefData().getCount(); + + std::string base = "Gold_001"; + if (goldAmount >= 100) + base = "Gold_100"; + else if (goldAmount >= 25) + base = "Gold_025"; + else if (goldAmount >= 10) + base = "Gold_010"; + else if (goldAmount >= 5) + base = "Gold_005"; + + MWWorld::ManualRef newRef (MWBase::Environment::get().getWorld()->getStore(), base); + + MWWorld::LiveCellRef* ref = + newRef.getPtr().get(); + + newPtr = MWWorld::Ptr(&miscItems.insert(*ref), cell); + + ESM::Position& p = newPtr.getRefData().getPosition(); + p.pos[0] = ptr.getRefData().getPosition().pos[0]; + p.pos[1] = ptr.getRefData().getPosition().pos[1]; + p.pos[2] = ptr.getRefData().getPosition().pos[2]; + } + else + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&miscItems.insert(*ref), cell); + } + } + else + throw std::runtime_error("Trying to insert object of unhandled type"); + + newPtr.getRefData().setCount(ptr.getRefData().getCount()); + ptr.getRefData().setCount(0); + newPtr.getRefData().enable(); + } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index de3ac12ae..89724bca4 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -7,6 +7,7 @@ #include #include "refdata.hpp" +#include "ptr.hpp" namespace ESMS { @@ -63,6 +64,11 @@ namespace MWWorld list.push_back(LiveRef(ref, obj)); } + const LiveRef &insert(LiveRef &item) { + list.push_back(item); + return list.back(); + } + LiveRef *find (const std::string& name) { for (typename std::list::iterator iter (list.begin()); iter!=list.end(); ++iter) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 33c67aad8..d2725b00e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -335,132 +335,6 @@ namespace MWWorld } - /// \todo this whole code needs major clean up, and doesn't belong in this class. - void Scene::insertObject (const Ptr& ptr, CellStore* cell) - { - std::string type = ptr.getTypeName(); - - MWWorld::Ptr newPtr; - - // insert into the correct CellRefList - if (type == typeid(ESM::Potion).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->potions.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->potions.list.back(), cell); - } - else if (type == typeid(ESM::Apparatus).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->appas.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->appas.list.back(), cell); - } - else if (type == typeid(ESM::Armor).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->armors.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->armors.list.back(), cell); - } - else if (type == typeid(ESM::Book).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->books.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->books.list.back(), cell); - } - else if (type == typeid(ESM::Clothing).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->clothes.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->clothes.list.back(), cell); - } - else if (type == typeid(ESM::Ingredient).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->ingreds.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->ingreds.list.back(), cell); - } - else if (type == typeid(ESM::Light).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->lights.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->lights.list.back(), cell); - } - else if (type == typeid(ESM::Tool).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->lockpicks.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->lockpicks.list.back(), cell); - } - else if (type == typeid(ESM::Repair).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->repairs.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->repairs.list.back(), cell); - } - else if (type == typeid(ESM::Probe).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->probes.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->probes.list.back(), cell); - } - else if (type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->weapons.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->weapons.list.back(), cell); - } - else if (type == typeid(ESM::Miscellaneous).name()) - { - - // if this is gold, we need to fetch the correct mesh depending on the amount of gold. - if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) - { - int goldAmount = ptr.getRefData().getCount(); - - std::string base = "Gold_001"; - if (goldAmount >= 100) - base = "Gold_100"; - else if (goldAmount >= 25) - base = "Gold_025"; - else if (goldAmount >= 10) - base = "Gold_010"; - else if (goldAmount >= 5) - base = "Gold_005"; - - MWWorld::ManualRef newRef (MWBase::Environment::get().getWorld()->getStore(), base); - - MWWorld::LiveCellRef* ref = newRef.getPtr().get(); - - cell->miscItems.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); - - ESM::Position& p = newPtr.getRefData().getPosition(); - p.pos[0] = ptr.getRefData().getPosition().pos[0]; - p.pos[1] = ptr.getRefData().getPosition().pos[1]; - p.pos[2] = ptr.getRefData().getPosition().pos[2]; - } - else - { - MWWorld::LiveCellRef* ref = ptr.get(); - - cell->miscItems.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); - } - } - else - throw std::runtime_error("Trying to insert object of unhandled type"); - - - - newPtr.getRefData().setCount(ptr.getRefData().getCount()); - ptr.getRefData().setCount(0); - newPtr.getRefData().enable(); - - mRendering.addObject(newPtr); - MWWorld::Class::get(newPtr).insertObject(newPtr, *mPhysics); - - } - void Scene::addObjectToScene (const Ptr& ptr) { mRendering.addObject (ptr); From b76022517935b7d5e16f34100143c0beec3fa253 Mon Sep 17 00:00:00 2001 From: greye Date: Tue, 24 Jul 2012 00:00:10 +0400 Subject: [PATCH 224/298] wrong branch This reverts commit d36d6aacf49741ccee9c6a6138f55b4123fd9b07. --- apps/openmw/mwworld/cellstore.cpp | 107 ------------------------- apps/openmw/mwworld/cellstore.hpp | 6 -- apps/openmw/mwworld/scene.cpp | 126 ++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 113 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 51a46ec3c..60a7eb6e1 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -121,111 +121,4 @@ namespace MWWorld } } } - - /// \todo this whole code needs major clean up - void CellStore::insertObject(const Ptr &ptr) - { - std::string type = ptr.getTypeName(); - - MWWorld::Ptr newPtr; - - // insert into the correct CellRefList - if (type == typeid(ESM::Potion).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&potions.insert(*ref), cell); - } - else if (type == typeid(ESM::Apparatus).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&appas.insert(*ref), cell); - } - else if (type == typeid(ESM::Armor).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&armors.insert(*ref), cell); - } - else if (type == typeid(ESM::Book).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&books.insert(*ref), cell); - } - else if (type == typeid(ESM::Clothing).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&clothes.insert(*ref), cell); - } - else if (type == typeid(ESM::Ingredient).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&ingreds.insert(*ref), cell); - } - else if (type == typeid(ESM::Light).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&lights.insert(*ref), cell); - } - else if (type == typeid(ESM::Tool).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&lockpicks.insert(*ref), cell); - } - else if (type == typeid(ESM::Repair).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&repairs.insert(*ref), cell); - } - else if (type == typeid(ESM::Probe).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&probes.insert(*ref), cell); - } - else if (type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&weapons.insert(*ref), cell); - } - else if (type == typeid(ESM::Miscellaneous).name()) - { - - // if this is gold, we need to fetch the correct mesh depending on the amount of gold. - if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) - { - int goldAmount = ptr.getRefData().getCount(); - - std::string base = "Gold_001"; - if (goldAmount >= 100) - base = "Gold_100"; - else if (goldAmount >= 25) - base = "Gold_025"; - else if (goldAmount >= 10) - base = "Gold_010"; - else if (goldAmount >= 5) - base = "Gold_005"; - - MWWorld::ManualRef newRef (MWBase::Environment::get().getWorld()->getStore(), base); - - MWWorld::LiveCellRef* ref = - newRef.getPtr().get(); - - newPtr = MWWorld::Ptr(&miscItems.insert(*ref), cell); - - ESM::Position& p = newPtr.getRefData().getPosition(); - p.pos[0] = ptr.getRefData().getPosition().pos[0]; - p.pos[1] = ptr.getRefData().getPosition().pos[1]; - p.pos[2] = ptr.getRefData().getPosition().pos[2]; - } - else - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&miscItems.insert(*ref), cell); - } - } - else - throw std::runtime_error("Trying to insert object of unhandled type"); - - newPtr.getRefData().setCount(ptr.getRefData().getCount()); - ptr.getRefData().setCount(0); - newPtr.getRefData().enable(); - } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 89724bca4..de3ac12ae 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -7,7 +7,6 @@ #include #include "refdata.hpp" -#include "ptr.hpp" namespace ESMS { @@ -64,11 +63,6 @@ namespace MWWorld list.push_back(LiveRef(ref, obj)); } - const LiveRef &insert(LiveRef &item) { - list.push_back(item); - return list.back(); - } - LiveRef *find (const std::string& name) { for (typename std::list::iterator iter (list.begin()); iter!=list.end(); ++iter) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index d2725b00e..33c67aad8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -335,6 +335,132 @@ namespace MWWorld } + /// \todo this whole code needs major clean up, and doesn't belong in this class. + void Scene::insertObject (const Ptr& ptr, CellStore* cell) + { + std::string type = ptr.getTypeName(); + + MWWorld::Ptr newPtr; + + // insert into the correct CellRefList + if (type == typeid(ESM::Potion).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->potions.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->potions.list.back(), cell); + } + else if (type == typeid(ESM::Apparatus).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->appas.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->appas.list.back(), cell); + } + else if (type == typeid(ESM::Armor).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->armors.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->armors.list.back(), cell); + } + else if (type == typeid(ESM::Book).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->books.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->books.list.back(), cell); + } + else if (type == typeid(ESM::Clothing).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->clothes.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->clothes.list.back(), cell); + } + else if (type == typeid(ESM::Ingredient).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->ingreds.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->ingreds.list.back(), cell); + } + else if (type == typeid(ESM::Light).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->lights.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->lights.list.back(), cell); + } + else if (type == typeid(ESM::Tool).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->lockpicks.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->lockpicks.list.back(), cell); + } + else if (type == typeid(ESM::Repair).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->repairs.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->repairs.list.back(), cell); + } + else if (type == typeid(ESM::Probe).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->probes.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->probes.list.back(), cell); + } + else if (type == typeid(ESM::Weapon).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + cell->weapons.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->weapons.list.back(), cell); + } + else if (type == typeid(ESM::Miscellaneous).name()) + { + + // if this is gold, we need to fetch the correct mesh depending on the amount of gold. + if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) + { + int goldAmount = ptr.getRefData().getCount(); + + std::string base = "Gold_001"; + if (goldAmount >= 100) + base = "Gold_100"; + else if (goldAmount >= 25) + base = "Gold_025"; + else if (goldAmount >= 10) + base = "Gold_010"; + else if (goldAmount >= 5) + base = "Gold_005"; + + MWWorld::ManualRef newRef (MWBase::Environment::get().getWorld()->getStore(), base); + + MWWorld::LiveCellRef* ref = newRef.getPtr().get(); + + cell->miscItems.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); + + ESM::Position& p = newPtr.getRefData().getPosition(); + p.pos[0] = ptr.getRefData().getPosition().pos[0]; + p.pos[1] = ptr.getRefData().getPosition().pos[1]; + p.pos[2] = ptr.getRefData().getPosition().pos[2]; + } + else + { + MWWorld::LiveCellRef* ref = ptr.get(); + + cell->miscItems.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); + } + } + else + throw std::runtime_error("Trying to insert object of unhandled type"); + + + + newPtr.getRefData().setCount(ptr.getRefData().getCount()); + ptr.getRefData().setCount(0); + newPtr.getRefData().enable(); + + mRendering.addObject(newPtr); + MWWorld::Class::get(newPtr).insertObject(newPtr, *mPhysics); + + } + void Scene::addObjectToScene (const Ptr& ptr) { mRendering.addObject (ptr); From f67983bbee071b886f27682c322fb66628db2b44 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 23 Jul 2012 13:37:05 -0700 Subject: [PATCH 225/298] Don't assume one sub-entity for the night sky entities --- apps/openmw/mwrender/sky.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index d928995f4..3dd038659 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -316,22 +316,25 @@ void SkyManager::create() // Stars mAtmosphereNight = mRootNode->createChildSceneNode(); NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(mAtmosphereNight, "meshes\\sky_night_01.nif"); - for(size_t i = 0;i < entities.mEntities.size();i++) + for(size_t i = 0, matidx = 0;i < entities.mEntities.size();i++) { Entity* night1_ent = entities.mEntities[i]; night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); night1_ent->setVisibilityFlags(RV_Sky); night1_ent->setCastShadows(false); - std::string matName = "openmw_stars_" + boost::lexical_cast(i); - sh::MaterialInstance* m = sh::Factory::getInstance ().createMaterialInstance (matName, "openmw_stars"); + for (unsigned int j=0; jgetNumSubEntities(); ++j) + { + std::string matName = "openmw_stars_" + boost::lexical_cast(matidx++); + sh::MaterialInstance* m = sh::Factory::getInstance().createMaterialInstance(matName, "openmw_stars"); - std::string textureName = sh::retrieveValue( - sh::Factory::getInstance().getMaterialInstance(night1_ent->getSubEntity (0)->getMaterialName ())->getProperty("diffuseMap"), NULL).get(); + std::string textureName = sh::retrieveValue( + sh::Factory::getInstance().getMaterialInstance(night1_ent->getSubEntity(j)->getMaterialName())->getProperty("diffuseMap"), NULL).get(); - m->setProperty ("texture", sh::makeProperty(new sh::StringValue(textureName))); + m->setProperty("texture", sh::makeProperty(new sh::StringValue(textureName))); - night1_ent->getSubEntity(0)->setMaterialName (matName); + night1_ent->getSubEntity(j)->setMaterialName(matName); + } } From 8c8228a15ca76b45b8a059830ea2f49689df3e1c Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 24 Jul 2012 00:08:53 +0200 Subject: [PATCH 226/298] fix terrain num lights setting --- apps/openmw/mwrender/renderingmanager.cpp | 1 + files/materials/terrain.shader | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c5449cfba..bee4bd68a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -110,6 +110,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const sh::Factory::getInstance ().setGlobalSetting ("fog", "true"); sh::Factory::getInstance ().setGlobalSetting ("lighting", "true"); sh::Factory::getInstance ().setGlobalSetting ("num_lights", Settings::Manager::getString ("num lights", "Objects")); + sh::Factory::getInstance ().setGlobalSetting ("terrain_num_lights", Settings::Manager::getString ("num lights", "Terrain")); sh::Factory::getInstance ().setGlobalSetting ("underwater_effects", Settings::Manager::getString("underwater effect", "Water")); sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index a562d1ec3..601f287a0 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -161,7 +161,7 @@ #if LIGHTING shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - @shForeach(@shGlobalSettingString(num_lights)) + @shForeach(@shGlobalSettingString(terrain_num_lights)) shUniform(float4, lightPosObjSpace@shIterator) @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) shUniform(float4, lightAttenuation@shIterator) @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) shUniform(float4, lightDiffuse@shIterator) @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) @@ -298,7 +298,7 @@ float3 diffuse = float3(0,0,0); float d; - @shForeach(@shGlobalSettingString(num_lights)) + @shForeach(@shGlobalSettingString(terrain_num_lights)) lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePosition.xyz * lightPosObjSpace@shIterator.w); d = length(lightDir); From 36be1536d90a5d927854e0c66eca5efbe17c4767 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 23 Jul 2012 17:20:47 -0700 Subject: [PATCH 227/298] Return text keys from NIFs when creating entities --- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwrender/objects.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 6 ++-- components/nifogre/ogre_nif_loader.cpp | 36 ++++++++++++++++------ components/nifogre/ogre_nif_loader.hpp | 8 +++-- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 9d2a58a1e..4086f4f7a 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -26,7 +26,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O { std::string mesh = "meshes\\" + ref->base->model; - mEntityList = NifOgre::NIFLoader::createEntities(mInsert, mesh); + mEntityList = NifOgre::NIFLoader::createEntities(mInsert, NULL, mesh); for(size_t i = 0;i < mEntityList.mEntities.size();i++) { Ogre::Entity *ent = mEntityList.mEntities[i]; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 415de5859..47135b35d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -89,7 +89,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); - mEntityList = NifOgre::NIFLoader::createEntities(mInsert, smodel); + mEntityList = NifOgre::NIFLoader::createEntities(mInsert, NULL, smodel); for(size_t i = 0;i < mEntityList.mEntities.size();i++) { Ogre::Entity *base = mEntityList.mEntities[i]; diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 3af99b469..ecd1328c4 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -93,7 +93,7 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) assert(insert); Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(insert, mesh); + NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(insert, NULL, mesh); for(size_t i = 0;i < entities.mEntities.size();i++) { const Ogre::AxisAlignedBox &tmp = entities.mEntities[i]->getBoundingBox(); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index a87f56ad8..9e551ba2a 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -327,7 +327,7 @@ void SkyManager::create() // Stars mAtmosphereNight = mRootNode->createChildSceneNode(); - NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(mAtmosphereNight, "meshes\\sky_night_01.nif"); + NifOgre::EntityList entities = NifOgre::NIFLoader::createEntities(mAtmosphereNight, NULL, "meshes\\sky_night_01.nif"); for(size_t i = 0, matidx = 0;i < entities.mEntities.size();i++) { Entity* night1_ent = entities.mEntities[i]; @@ -352,7 +352,7 @@ void SkyManager::create() // Atmosphere (day) mAtmosphereDay = mRootNode->createChildSceneNode(); - entities = NifOgre::NIFLoader::createEntities(mAtmosphereDay, "meshes\\sky_atmosphere.nif"); + entities = NifOgre::NIFLoader::createEntities(mAtmosphereDay, NULL, "meshes\\sky_atmosphere.nif"); for(size_t i = 0;i < entities.mEntities.size();i++) { Entity* atmosphere_ent = entities.mEntities[i]; @@ -366,7 +366,7 @@ void SkyManager::create() // Clouds SceneNode* clouds_node = mRootNode->createChildSceneNode(); - entities = NifOgre::NIFLoader::createEntities(clouds_node, "meshes\\sky_clouds_01.nif"); + entities = NifOgre::NIFLoader::createEntities(clouds_node, NULL, "meshes\\sky_clouds_01.nif"); for(size_t i = 0;i < entities.mEntities.size();i++) { Entity* clouds_ent = entities.mEntities[i]; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 3338b8cdd..9df8dd916 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -342,8 +342,23 @@ void loadResource(Ogre::Resource *resource) anim->optimise(); } -bool createSkeleton(const std::string &name, const std::string &group, Nif::Node *node) +bool createSkeleton(const std::string &name, const std::string &group, TextKeyMap *textkeys, const Nif::Node *node) { + if(textkeys) + { + Nif::ExtraPtr e = node->extra; + while(!e.empty()) + { + if(e->recType == Nif::RC_NiTextKeyExtraData) + { + const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); + for(size_t i = 0;i < tk->list.size();i++) + (*textkeys)[tk->list[i].time] = tk->list[i].text; + } + e = e->extra; + } + } + if(node->boneTrafo != NULL) { Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton(); @@ -355,18 +370,19 @@ bool createSkeleton(const std::string &name, const std::string &group, Nif::Node skel = skelMgr.create(name, group, true, loader); } - return true; + if(!textkeys || textkeys->size() > 0) + return true; } - Nif::NiNode *ninode = dynamic_cast(node); + const Nif::NiNode *ninode = dynamic_cast(node); if(ninode) { - Nif::NodeList &children = ninode->children; + const Nif::NodeList &children = ninode->children; for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) { - if(createSkeleton(name, group, children[i].getPtr())) + if(createSkeleton(name, group, textkeys, children[i].getPtr())) return true; } } @@ -965,7 +981,7 @@ public: NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders; -MeshPairList NIFLoader::load(std::string name, std::string skelName, const std::string &group) +MeshPairList NIFLoader::load(std::string name, std::string skelName, TextKeyMap *textkeys, const std::string &group) { MeshPairList meshes; @@ -992,7 +1008,7 @@ MeshPairList NIFLoader::load(std::string name, std::string skelName, const std:: } NIFSkeletonLoader skelldr; - bool hasSkel = skelldr.createSkeleton(skelName, group, node); + bool hasSkel = skelldr.createSkeleton(skelName, group, textkeys, node); NIFMeshLoader meshldr(name, group, (hasSkel ? skelName : std::string())); meshldr.createMeshes(node, meshes); @@ -1000,11 +1016,11 @@ MeshPairList NIFLoader::load(std::string name, std::string skelName, const std:: return meshes; } -EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, const std::string &name, const std::string &group) +EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, TextKeyMap *textkeys, const std::string &name, const std::string &group) { EntityList entitylist; - MeshPairList meshes = load(name, name, group); + MeshPairList meshes = load(name, name, textkeys, group); if(meshes.size() == 0) return entitylist; @@ -1053,7 +1069,7 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo { EntityList entitylist; - MeshPairList meshes = load(name, parent->getMesh()->getSkeletonName(), group); + MeshPairList meshes = load(name, parent->getMesh()->getSkeletonName(), NULL, group); if(meshes.size() == 0) return entitylist; diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index a9195f2fb..b6610d8a7 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -58,7 +58,8 @@ namespace Nif namespace NifOgre { -// FIXME: This should not be in NifOgre, it works agnostic of what model format is used +// FIXME: These should not be in NifOgre, it works agnostic of what model format is used +typedef std::map TextKeyMap; struct EntityList { std::vector mEntities; Ogre::Entity *mSkelBase; @@ -67,11 +68,11 @@ struct EntityList { { } }; + /** This holds a list of meshes along with the names of their parent nodes */ typedef std::vector< std::pair > MeshPairList; - /** Manual resource loader for NIF meshes. This is the main class responsible for translating the internal NIF mesh structure into something Ogre can use. @@ -86,7 +87,7 @@ typedef std::vector< std::pair > MeshPairList; */ class NIFLoader { - static MeshPairList load(std::string name, std::string skelName, const std::string &group); + static MeshPairList load(std::string name, std::string skelName, TextKeyMap *textkeys, const std::string &group); public: static EntityList createEntities(Ogre::Entity *parent, const std::string &bonename, @@ -95,6 +96,7 @@ public: const std::string &group="General"); static EntityList createEntities(Ogre::SceneNode *parent, + TextKeyMap *textkeys, const std::string &name, const std::string &group="General"); }; From f953d7f8c0c0145def6fc81f265d931adfca15bf Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 23 Jul 2012 17:27:35 -0700 Subject: [PATCH 228/298] Store text keys from base NIF animations --- apps/openmw/mwrender/animation.hpp | 1 + apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index ae1477666..dd1fdc95e 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -30,6 +30,7 @@ protected: bool mSkipFrame; NifOgre::EntityList mEntityList; + NifOgre::TextKeyMap mTextKeys; public: Animation(OEngine::Render::OgreRenderer& _rend); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 4086f4f7a..7fb153bd1 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -26,7 +26,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr& ptr, OEngine::Render::O { std::string mesh = "meshes\\" + ref->base->model; - mEntityList = NifOgre::NIFLoader::createEntities(mInsert, NULL, mesh); + mEntityList = NifOgre::NIFLoader::createEntities(mInsert, &mTextKeys, mesh); for(size_t i = 0;i < mEntityList.mEntities.size();i++) { Ogre::Entity *ent = mEntityList.mEntities[i]; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 47135b35d..4f98aebc4 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -89,7 +89,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRendere std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); - mEntityList = NifOgre::NIFLoader::createEntities(mInsert, NULL, smodel); + mEntityList = NifOgre::NIFLoader::createEntities(mInsert, &mTextKeys, smodel); for(size_t i = 0;i < mEntityList.mEntities.size();i++) { Ogre::Entity *base = mEntityList.mEntities[i]; From 94ce95c679d111a69d6cc5e6f4126c8b64feabb8 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Wed, 25 Jul 2012 00:13:33 +0400 Subject: [PATCH 229/298] bug #348: works again on OS X --- CMakeLists.txt | 59 +++++++++++++++---------------- apps/launcher/graphicspage.cpp | 8 +++-- libs/openengine/ogre/renderer.cpp | 12 +++++-- 3 files changed, 44 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f124f1383..05c53e6d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,13 +227,41 @@ if (APPLE) ${OGRE_Plugin_OctreeSceneManager_LIBRARY_REL} ${OGRE_Plugin_CgProgramManager_LIBRARY_REL} ${OGRE_Plugin_ParticleFX_LIBRARY_REL}) + + if (${OGRE_PLUGIN_DIR_REL}}) + set(OGRE_PLUGINS_REL_FOUND TRUE) + endif () + + if (${OGRE_PLUGIN_DIR_DBG}) + set(OGRE_PLUGINS_DBG_FOUND TRUE) + endif () + + if (${OGRE_PLUGINS_REL_FOUND}) + set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL}) + else () + set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_DBG}) + endif () + + set(OGRE_PLUGIN_DIR "${OGRE_PLUGIN_DIR}/") + +# set(OGRE_PLUGIN_DIR_2 ${OGRE_PLUGIN_DIR}) +# set(OGRE_PLUGIN_DIR "") +# set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_2}) + + configure_file(${OpenMW_SOURCE_DIR}/files/mac/Info.plist + "${APP_BUNDLE_DIR}/Contents/Info.plist") + + configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw.icns + "${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY) endif (APPLE) # Set up Ogre plugin folder & debug suffix set(DEBUG_SUFFIX "") +add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="") if (DEFINED CMAKE_BUILD_TYPE) - if (CMAKE_BUILD_TYPE STREQUAL "Debug") + # Ogre on OS X doesn't use "_d" suffix (see Ogre's CMakeLists.txt) + if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT APPLE) set(DEBUG_SUFFIX "_d") add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") else() @@ -275,35 +303,6 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") "${OpenMW_BINARY_DIR}/openmw.desktop") endif() -if (APPLE) - if (${OGRE_PLUGIN_DIR_REL}}) - set(OGRE_PLUGINS_REL_FOUND TRUE) - endif () - - if (${OGRE_PLUGIN_DIR_DBG}) - set(OGRE_PLUGINS_DBG_FOUND TRUE) - endif () - - if (${OGRE_PLUGINS_REL_FOUND}) - set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL}) - else () - set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_DBG}) - endif () - - set(OGRE_PLUGIN_DIR "${OGRE_PLUGIN_DIR}/") - - set(OGRE_PLUGIN_DIR_2 ${OGRE_PLUGIN_DIR}) - set(OGRE_PLUGIN_DIR "") - set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_2}) - - configure_file(${OpenMW_SOURCE_DIR}/files/mac/Info.plist - "${APP_BUNDLE_DIR}/Contents/Info.plist") - - configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw.icns - "${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY) -endif (APPLE) - - # Compiler settings if (CMAKE_COMPILER_IS_GNUCC) add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-reorder) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 3c1d76f3d..296ef2c3e 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -116,11 +116,15 @@ bool GraphicsPage::setupOgre() } std::string glPlugin = std::string(pluginDir) + "/RenderSystem_GL" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(glPlugin + ".so") || boost::filesystem::exists(glPlugin + ".dll")) + if (boost::filesystem::exists(glPlugin + ".so") || + boost::filesystem::exists(glPlugin + ".dll") || + boost::filesystem::exists(glPlugin + ".dylib")) mOgre->loadPlugin (glPlugin); std::string dxPlugin = std::string(pluginDir) + "/RenderSystem_Direct3D9" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(dxPlugin + ".so") || boost::filesystem::exists(dxPlugin + ".dll")) + if (boost::filesystem::exists(dxPlugin + ".so") || + boost::filesystem::exists(dxPlugin + ".dll") || + boost::filesystem::exists(dxPlugin + ".dylib")) mOgre->loadPlugin (dxPlugin); #ifdef ENABLE_PLUGIN_GL diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index e40bdf708..58b363f7f 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -112,15 +112,21 @@ void OgreRenderer::configure(const std::string &logPath, } std::string glPlugin = std::string(pluginDir) + "/RenderSystem_GL" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(glPlugin + ".so") || boost::filesystem::exists(glPlugin + ".dll")) + if (boost::filesystem::exists(glPlugin + ".so") || + boost::filesystem::exists(glPlugin + ".dll") || + boost::filesystem::exists(glPlugin + ".dylib")) mRoot->loadPlugin (glPlugin); std::string dxPlugin = std::string(pluginDir) + "/RenderSystem_Direct3D9" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(dxPlugin + ".so") || boost::filesystem::exists(dxPlugin + ".dll")) + if (boost::filesystem::exists(dxPlugin + ".so") || + boost::filesystem::exists(dxPlugin + ".dll") || + boost::filesystem::exists(dxPlugin + ".dylib")) mRoot->loadPlugin (dxPlugin); std::string cgPlugin = std::string(pluginDir) + "/Plugin_CgProgramManager" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(cgPlugin + ".so") || boost::filesystem::exists(cgPlugin + ".dll")) + if (boost::filesystem::exists(cgPlugin + ".so") || + boost::filesystem::exists(cgPlugin + ".dll") || + boost::filesystem::exists(cgPlugin + ".dylib")) mRoot->loadPlugin (cgPlugin); RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); From 9a7a629d0f82bd224a518901757d3282c23015ad Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 24 Jul 2012 13:51:48 -0700 Subject: [PATCH 230/298] Add support for playing animation groups --- apps/openmw/mwrender/animation.cpp | 131 ++++++++++++++++++++++++++--- apps/openmw/mwrender/animation.hpp | 8 ++ 2 files changed, 126 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 653a506e8..27ab3995a 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -7,6 +7,7 @@ #include #include + namespace MWRender { std::map Animation::sUniqueIDs; @@ -28,15 +29,116 @@ Animation::~Animation() mEntityList.mEntities.clear(); } + +struct checklow { + bool operator()(const char &a, const char &b) const + { + return ::tolower(a) == ::tolower(b); + } +}; + +bool Animation::findGroupTimes(const std::string &groupname, float *starttime, float *stoptime, float *loopstarttime, float *loopstoptime) +{ + const std::string &start = groupname+": start"; + const std::string &startloop = groupname+": loop start"; + const std::string &stop = groupname+": stop"; + const std::string &stoploop = groupname+": loop stop"; + + *starttime = -1.0f; + *stoptime = -1.0f; + *loopstarttime = -1.0f; + *loopstoptime = -1.0f; + + NifOgre::TextKeyMap::const_iterator iter; + for(iter = mTextKeys.begin();iter != mTextKeys.end();iter++) + { + if(*starttime >= 0.0f && *stoptime >= 0.0f && *loopstarttime >= 0.0f && *loopstoptime >= 0.0f) + return true; + + std::string::const_iterator strpos = iter->second.begin(); + std::string::const_iterator strend = iter->second.end(); + + while(strpos != strend) + { + size_t strlen = strend-strpos; + std::string::const_iterator striter; + + if(start.size() <= strlen && + ((striter=std::mismatch(strpos, strend, start.begin(), checklow()).first) == strend || + *striter == '\r' || *striter == '\n')) + { + *starttime = iter->first; + *loopstarttime = iter->first; + } + else if(startloop.size() <= strlen && + ((striter=std::mismatch(strpos, strend, startloop.begin(), checklow()).first) == strend || + *striter == '\r' || *striter == '\n')) + { + *loopstarttime = iter->first; + } + else if(stoploop.size() <= strlen && + ((striter=std::mismatch(strpos, strend, stoploop.begin(), checklow()).first) == strend || + *striter == '\r' || *striter == '\n')) + { + *loopstoptime = iter->first; + } + else if(stop.size() <= strlen && + ((striter=std::mismatch(strpos, strend, stop.begin(), checklow()).first) == strend || + *striter == '\r' || *striter == '\n')) + { + *stoptime = iter->first; + if(*loopstoptime < 0.0f) + *loopstoptime = iter->first; + break; + } + + strpos = std::find(strpos+1, strend, '\n'); + while(strpos != strend && *strpos == '\n') + strpos++; + } + } + + return (*starttime >= 0.0f && *stoptime >= 0.0f && *loopstarttime >= 0.0f && *loopstoptime >= 0.0f); +} + + void Animation::playGroup(std::string groupname, int mode, int loops) { + float start, stop, loopstart, loopstop; + if(groupname == "all") { + mLoopStartTime = mStartTime = 0.0f; + mLoopStopTime = mStopTime = 0.0f; + + if(mEntityList.mSkelBase) + { + Ogre::AnimationStateSet *aset = mEntityList.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateIterator as = aset->getAnimationStateIterator(); + while(as.hasMoreElements()) + { + Ogre::AnimationState *state = as.getNext(); + mLoopStopTime = mStopTime = state->getLength(); + break; + } + } + mAnimate = loops; - mTime = 0.0f; + mTime = mStartTime; } -} + else if(findGroupTimes(groupname, &start, &stop, &loopstart, &loopstop)) + { + mStartTime = start; + mStopTime = stop; + mLoopStartTime = loopstart; + mLoopStopTime = loopstop; + mAnimate = loops; + mTime = mStartTime; + } + else + throw std::runtime_error("Failed to find animation group "+groupname); +} void Animation::skipAnim() { @@ -45,9 +147,22 @@ void Animation::skipAnim() void Animation::runAnimation(float timepassed) { - if(mAnimate != 0 && !mSkipFrame) + if(mAnimate > 0 && !mSkipFrame) { mTime += timepassed; + if(mTime >= mLoopStopTime) + { + if(mAnimate > 1) + { + mAnimate--; + mTime = mTime - mLoopStopTime + mLoopStartTime; + } + else if(mTime >= mStopTime) + { + mAnimate--; + mTime = mStopTime; + } + } if(mEntityList.mSkelBase) { @@ -57,16 +172,6 @@ void Animation::runAnimation(float timepassed) { Ogre::AnimationState *state = as.getNext(); state->setTimePosition(mTime); - if(mTime >= state->getLength()) - { - if(mAnimate != -1) - mAnimate--; - //std::cout << "Stopping the animation\n"; - if(mAnimate == 0) - mTime = state->getLength(); - else - mTime = mTime - state->getLength(); - } } } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index dd1fdc95e..fb9330114 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -26,12 +26,20 @@ protected: static std::map sUniqueIDs; float mTime; + float mStartTime; + float mStopTime; + float mLoopStartTime; + float mLoopStopTime; + int mAnimate; bool mSkipFrame; NifOgre::EntityList mEntityList; NifOgre::TextKeyMap mTextKeys; + bool findGroupTimes(const std::string &groupname, float *starttime, float *stoptime, + float *loopstarttime, float *loopstoptime); + public: Animation(OEngine::Render::OgreRenderer& _rend); virtual ~Animation(); From 20121f3b0a1f5732cb4d76e66cc6e1039d0f048f Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 24 Jul 2012 13:56:28 -0700 Subject: [PATCH 231/298] Remove some unused stuff --- apps/openmw/mwrender/animation.cpp | 1 - apps/openmw/mwrender/animation.hpp | 6 ------ 2 files changed, 7 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 27ab3995a..6a04f62b5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -10,7 +10,6 @@ namespace MWRender { -std::map Animation::sUniqueIDs; Animation::Animation(OEngine::Render::OgreRenderer& _rend) : mInsert(NULL) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index fb9330114..cb0e92367 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -14,16 +14,10 @@ namespace MWRender { -struct PosAndRot { - Ogre::Quaternion vecRot; - Ogre::Vector3 vecPos; -}; - class Animation { protected: Ogre::SceneNode* mInsert; OEngine::Render::OgreRenderer &mRend; - static std::map sUniqueIDs; float mTime; float mStartTime; From fd1e3f6ec55f1aee15fd0ab5514fdeb362370623 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 24 Jul 2012 14:14:32 -0700 Subject: [PATCH 232/298] Add support for playgroup mode 2 --- apps/openmw/mwrender/animation.cpp | 33 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6a04f62b5..f6beeb52f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -107,8 +107,8 @@ void Animation::playGroup(std::string groupname, int mode, int loops) if(groupname == "all") { - mLoopStartTime = mStartTime = 0.0f; - mLoopStopTime = mStopTime = 0.0f; + start = loopstart = 0.0f; + loopstop = stop = 0.0f; if(mEntityList.mSkelBase) { @@ -117,26 +117,25 @@ void Animation::playGroup(std::string groupname, int mode, int loops) while(as.hasMoreElements()) { Ogre::AnimationState *state = as.getNext(); - mLoopStopTime = mStopTime = state->getLength(); + loopstop = stop = state->getLength(); break; } } - - mAnimate = loops; - mTime = mStartTime; - } - else if(findGroupTimes(groupname, &start, &stop, &loopstart, &loopstop)) - { - mStartTime = start; - mStopTime = stop; - mLoopStartTime = loopstart; - mLoopStopTime = loopstop; - - mAnimate = loops; - mTime = mStartTime; } - else + else if(!findGroupTimes(groupname, &start, &stop, &loopstart, &loopstop)) throw std::runtime_error("Failed to find animation group "+groupname); + + // FIXME: mode = 0 not yet supported + if(mode == 0) + mode = 1; + + mStartTime = start; + mStopTime = stop; + mLoopStartTime = loopstart; + mLoopStopTime = loopstop; + + mAnimate = loops; + mTime = ((mode==1) ? mStartTime : mLoopStartTime); } void Animation::skipAnim() From 13ab2baef0967333d8aad0eee6d964edb2a30734 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 24 Jul 2012 14:42:01 -0700 Subject: [PATCH 233/298] Use a struct to hold the current animation times and remaining loop count --- apps/openmw/mwrender/animation.cpp | 61 +++++++++++++----------------- apps/openmw/mwrender/animation.hpp | 23 +++++++---- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f6beeb52f..fe77da52b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -15,7 +15,6 @@ Animation::Animation(OEngine::Render::OgreRenderer& _rend) : mInsert(NULL) , mRend(_rend) , mTime(0.0f) - , mAnimate(0) , mSkipFrame(false) { } @@ -36,22 +35,17 @@ struct checklow { } }; -bool Animation::findGroupTimes(const std::string &groupname, float *starttime, float *stoptime, float *loopstarttime, float *loopstoptime) +bool Animation::findGroupTimes(const std::string &groupname, Animation::GroupTimes *times) { const std::string &start = groupname+": start"; const std::string &startloop = groupname+": loop start"; const std::string &stop = groupname+": stop"; const std::string &stoploop = groupname+": loop stop"; - *starttime = -1.0f; - *stoptime = -1.0f; - *loopstarttime = -1.0f; - *loopstoptime = -1.0f; - NifOgre::TextKeyMap::const_iterator iter; for(iter = mTextKeys.begin();iter != mTextKeys.end();iter++) { - if(*starttime >= 0.0f && *stoptime >= 0.0f && *loopstarttime >= 0.0f && *loopstoptime >= 0.0f) + if(times->mStart >= 0.0f && times->mLoopStart >= 0.0f && times->mLoopStop >= 0.0f && times->mStop >= 0.0f) return true; std::string::const_iterator strpos = iter->second.begin(); @@ -66,28 +60,28 @@ bool Animation::findGroupTimes(const std::string &groupname, float *starttime, f ((striter=std::mismatch(strpos, strend, start.begin(), checklow()).first) == strend || *striter == '\r' || *striter == '\n')) { - *starttime = iter->first; - *loopstarttime = iter->first; + times->mStart = iter->first; + times->mLoopStart = iter->first; } else if(startloop.size() <= strlen && ((striter=std::mismatch(strpos, strend, startloop.begin(), checklow()).first) == strend || *striter == '\r' || *striter == '\n')) { - *loopstarttime = iter->first; + times->mLoopStart = iter->first; } else if(stoploop.size() <= strlen && ((striter=std::mismatch(strpos, strend, stoploop.begin(), checklow()).first) == strend || *striter == '\r' || *striter == '\n')) { - *loopstoptime = iter->first; + times->mLoopStop = iter->first; } else if(stop.size() <= strlen && ((striter=std::mismatch(strpos, strend, stop.begin(), checklow()).first) == strend || *striter == '\r' || *striter == '\n')) { - *stoptime = iter->first; - if(*loopstoptime < 0.0f) - *loopstoptime = iter->first; + times->mStop = iter->first; + if(times->mLoopStop < 0.0f) + times->mLoopStop = iter->first; break; } @@ -97,18 +91,19 @@ bool Animation::findGroupTimes(const std::string &groupname, float *starttime, f } } - return (*starttime >= 0.0f && *stoptime >= 0.0f && *loopstarttime >= 0.0f && *loopstoptime >= 0.0f); + return (times->mStart >= 0.0f && times->mLoopStart >= 0.0f && times->mLoopStop >= 0.0f && times->mStop >= 0.0f); } void Animation::playGroup(std::string groupname, int mode, int loops) { - float start, stop, loopstart, loopstop; + GroupTimes times; + times.mLoops = loops; if(groupname == "all") { - start = loopstart = 0.0f; - loopstop = stop = 0.0f; + times.mStart = times.mLoopStart = 0.0f; + times.mLoopStop = times.mStop = 0.0f; if(mEntityList.mSkelBase) { @@ -117,25 +112,21 @@ void Animation::playGroup(std::string groupname, int mode, int loops) while(as.hasMoreElements()) { Ogre::AnimationState *state = as.getNext(); - loopstop = stop = state->getLength(); + times.mLoopStop = times.mStop = state->getLength(); break; } } } - else if(!findGroupTimes(groupname, &start, &stop, &loopstart, &loopstop)) + else if(!findGroupTimes(groupname, ×)) throw std::runtime_error("Failed to find animation group "+groupname); // FIXME: mode = 0 not yet supported if(mode == 0) mode = 1; - mStartTime = start; - mStopTime = stop; - mLoopStartTime = loopstart; - mLoopStopTime = loopstop; + mCurGroup = times; - mAnimate = loops; - mTime = ((mode==1) ? mStartTime : mLoopStartTime); + mTime = ((mode==1) ? mCurGroup.mStart : mCurGroup.mLoopStart); } void Animation::skipAnim() @@ -145,20 +136,20 @@ void Animation::skipAnim() void Animation::runAnimation(float timepassed) { - if(mAnimate > 0 && !mSkipFrame) + if(mCurGroup.mLoops > 0 && !mSkipFrame) { mTime += timepassed; - if(mTime >= mLoopStopTime) + if(mTime >= mCurGroup.mLoopStop) { - if(mAnimate > 1) + if(mCurGroup.mLoops > 1) { - mAnimate--; - mTime = mTime - mLoopStopTime + mLoopStartTime; + mCurGroup.mLoops--; + mTime = mTime - mCurGroup.mLoopStop + mCurGroup.mLoopStart; } - else if(mTime >= mStopTime) + else if(mTime >= mCurGroup.mStop) { - mAnimate--; - mTime = mStopTime; + mCurGroup.mLoops--; + mTime = mCurGroup.mStop; } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index cb0e92367..c9b34a4e5 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -15,24 +15,33 @@ namespace MWRender { class Animation { + struct GroupTimes { + float mStart; + float mStop; + float mLoopStart; + float mLoopStop; + + size_t mLoops; + + GroupTimes() + : mStart(-1.0f), mStop(-1.0f), mLoopStart(-1.0f), mLoopStop(-1.0f), + mLoops(0) + { } + }; + protected: Ogre::SceneNode* mInsert; OEngine::Render::OgreRenderer &mRend; float mTime; - float mStartTime; - float mStopTime; - float mLoopStartTime; - float mLoopStopTime; + GroupTimes mCurGroup; - int mAnimate; bool mSkipFrame; NifOgre::EntityList mEntityList; NifOgre::TextKeyMap mTextKeys; - bool findGroupTimes(const std::string &groupname, float *starttime, float *stoptime, - float *loopstarttime, float *loopstoptime); + bool findGroupTimes(const std::string &groupname, GroupTimes *times); public: Animation(OEngine::Render::OgreRenderer& _rend); From 9f0c1eeb7b8562ac64206eebe81edd701eaba2e3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 24 Jul 2012 14:54:12 -0700 Subject: [PATCH 234/298] Support playgroup mode 0 --- apps/openmw/mwrender/animation.cpp | 23 ++++++++++++++--------- apps/openmw/mwrender/animation.hpp | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index fe77da52b..46f3bdc0d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -120,13 +120,14 @@ void Animation::playGroup(std::string groupname, int mode, int loops) else if(!findGroupTimes(groupname, ×)) throw std::runtime_error("Failed to find animation group "+groupname); - // FIXME: mode = 0 not yet supported - if(mode == 0) - mode = 1; - - mCurGroup = times; - - mTime = ((mode==1) ? mCurGroup.mStart : mCurGroup.mLoopStart); + if(mode == 0 && mCurGroup.mLoops > 0) + mNextGroup = times; + else + { + mCurGroup = times; + mNextGroup = GroupTimes(); + mTime = ((mode==2) ? mCurGroup.mLoopStart : mCurGroup.mStart); + } } void Animation::skipAnim() @@ -148,8 +149,12 @@ void Animation::runAnimation(float timepassed) } else if(mTime >= mCurGroup.mStop) { - mCurGroup.mLoops--; - mTime = mCurGroup.mStop; + if(mNextGroup.mLoops > 0) + mTime = mTime - mCurGroup.mStop + mNextGroup.mStart; + else + mTime = mCurGroup.mStop; + mCurGroup = mNextGroup; + mNextGroup = GroupTimes(); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index c9b34a4e5..3611d35c0 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -35,6 +35,7 @@ protected: float mTime; GroupTimes mCurGroup; + GroupTimes mNextGroup; bool mSkipFrame; From 4f46c8a8db6cbe532492be39d6369e5360662cea Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 24 Jul 2012 14:59:25 -0700 Subject: [PATCH 235/298] Use a functor for the mismatch compare function --- components/nifogre/ogre_nif_loader.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 9df8dd916..803300282 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -1057,10 +1057,12 @@ EntityList NIFLoader::createEntities(Ogre::SceneNode *parent, TextKeyMap *textke return entitylist; } -static bool checklow(const char &a, const char &b) -{ - return ::tolower(a) == ::tolower(b); -} +struct checklow { + bool operator()(const char &a, const char &b) const + { + return ::tolower(a) == ::tolower(b); + } +}; EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bonename, Ogre::SceneNode *parentNode, @@ -1081,7 +1083,7 @@ EntityList NIFLoader::createEntities(Ogre::Entity *parent, const std::string &bo if(ent->hasSkeleton()) { if(meshes[i].second.length() < filter.length() || - std::mismatch(filter.begin(), filter.end(), meshes[i].second.begin(), checklow).first != filter.end()) + std::mismatch(filter.begin(), filter.end(), meshes[i].second.begin(), checklow()).first != filter.end()) { sceneMgr->destroyEntity(ent); meshes.erase(meshes.begin()+i); From 0f40e6fc65b4a983ef6a159cf6076d07a463324b Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 25 Jul 2012 00:47:08 +0200 Subject: [PATCH 236/298] find boost without components so we can use Boost_VERSION --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f124f1383..ea0c41fe2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,6 +186,9 @@ if (UNIX AND NOT APPLE) find_package (Threads) endif() +# find boost without components so we can use Boost_VERSION +find_package(Boost REQUIRED) + set(BOOST_COMPONENTS system filesystem program_options thread) if (Boost_VERSION LESS 104900) From 61cb012ee7dd1f356af408bccc1ed9a59b2461af Mon Sep 17 00:00:00 2001 From: greye Date: Tue, 24 Jul 2012 11:15:20 +0400 Subject: [PATCH 237/298] moving Scene::insertObject to CellStore::insertObject, part 1 --- apps/openmw/mwworld/cellstore.cpp | 116 ++++++++++++++++++++++++++- apps/openmw/mwworld/cellstore.hpp | 9 +++ apps/openmw/mwworld/scene.cpp | 129 ------------------------------ apps/openmw/mwworld/scene.hpp | 4 - apps/openmw/mwworld/worldimp.cpp | 3 +- 5 files changed, 126 insertions(+), 135 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 60a7eb6e1..22c94cf75 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1,10 +1,12 @@ - #include "cellstore.hpp" #include #include +#include "ptr.hpp" +#include "manualref.hpp" + namespace MWWorld { CellStore::CellStore (const ESM::Cell *cell_) : cell (cell_), mState (State_Unloaded) @@ -121,4 +123,116 @@ namespace MWWorld } } } + + /// \todo this whole code needs major clean up + void CellStore::insertObject (const Ptr& ptr) + { + std::string type = ptr.getTypeName(); + + MWWorld::Ptr newPtr; + + // insert into the correct CellRefList + if (type == typeid(ESM::Potion).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&potions.insert(*ref), this); + } + else if (type == typeid(ESM::Apparatus).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&appas.insert(*ref), this); + } + else if (type == typeid(ESM::Armor).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&armors.insert(*ref), this); + } + else if (type == typeid(ESM::Book).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&books.insert(*ref), this); + } + else if (type == typeid(ESM::Clothing).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&clothes.insert(*ref), this); + } + else if (type == typeid(ESM::Ingredient).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&ingreds.insert(*ref), this); + } + else if (type == typeid(ESM::Light).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&lights.insert(*ref), this); + } + else if (type == typeid(ESM::Tool).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&lockpicks.insert(*ref), this); + } + else if (type == typeid(ESM::Repair).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&repairs.insert(*ref), this); + } + else if (type == typeid(ESM::Probe).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&probes.insert(*ref), this); + } + else if (type == typeid(ESM::Weapon).name()) + { + MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(&weapons.insert(*ref), this); + } + else if (type == typeid(ESM::Miscellaneous).name()) + { + + // if this is gold, we need to fetch the correct mesh depending on the amount of gold. + if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) + { + int goldAmount = ptr.getRefData().getCount(); + + std::string base = "Gold_001"; + if (goldAmount >= 100) + base = "Gold_100"; + else if (goldAmount >= 25) + base = "Gold_025"; + else if (goldAmount >= 10) + base = "Gold_010"; + else if (goldAmount >= 5) + base = "Gold_005"; + + MWWorld::ManualRef newRef( + MWBase::Environment::get().getWorld()->getStore(), + base); + + MWWorld::LiveCellRef* ref = + newRef.getPtr().get(); + + newPtr = MWWorld::Ptr(&miscItems.insert(*ref), this); + + ESM::Position& p = newPtr.getRefData().getPosition(); + p.pos[0] = ptr.getRefData().getPosition().pos[0]; + p.pos[1] = ptr.getRefData().getPosition().pos[1]; + p.pos[2] = ptr.getRefData().getPosition().pos[2]; + } + else + { + MWWorld::LiveCellRef* ref = + ptr.get(); + + newPtr = MWWorld::Ptr(&miscItems.insert(*ref), this); + } + } + else + throw std::runtime_error("Trying to insert object of unhandled type"); + + newPtr.getRefData().setCount(ptr.getRefData().getCount()); + ptr.getRefData().setCount(0); + newPtr.getRefData().enable(); + } + } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index de3ac12ae..3dcde9359 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -15,6 +15,8 @@ namespace ESMS namespace MWWorld { + class Ptr; + /// A reference to one object (of any type) in a cell. /// /// Constructing this with a CellRef instance in the constructor means that @@ -73,6 +75,11 @@ namespace MWWorld return 0; } + + LiveRef &insert(const LiveRef &item) { + list.push_back(item); + return list.back(); + } }; /// A storage struct for one single cell reference. @@ -165,6 +172,8 @@ namespace MWWorld void listRefs(const ESMS::ESMStore &store, ESM::ESMReader &esm); void loadRefs(const ESMS::ESMStore &store, ESM::ESMReader &esm); + + void insertObject(const MWWorld::Ptr &ptr); }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 33c67aad8..9786c24d9 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -9,8 +9,6 @@ #include "../mwgui/window_manager.hpp" -#include "../mwworld/manualref.hpp" /// FIXME - #include "player.hpp" #include "localscripts.hpp" @@ -334,133 +332,6 @@ namespace MWWorld insertCellRefList(mRendering, cell.weapons, cell, *mPhysics); } - - /// \todo this whole code needs major clean up, and doesn't belong in this class. - void Scene::insertObject (const Ptr& ptr, CellStore* cell) - { - std::string type = ptr.getTypeName(); - - MWWorld::Ptr newPtr; - - // insert into the correct CellRefList - if (type == typeid(ESM::Potion).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->potions.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->potions.list.back(), cell); - } - else if (type == typeid(ESM::Apparatus).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->appas.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->appas.list.back(), cell); - } - else if (type == typeid(ESM::Armor).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->armors.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->armors.list.back(), cell); - } - else if (type == typeid(ESM::Book).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->books.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->books.list.back(), cell); - } - else if (type == typeid(ESM::Clothing).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->clothes.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->clothes.list.back(), cell); - } - else if (type == typeid(ESM::Ingredient).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->ingreds.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->ingreds.list.back(), cell); - } - else if (type == typeid(ESM::Light).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->lights.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->lights.list.back(), cell); - } - else if (type == typeid(ESM::Tool).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->lockpicks.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->lockpicks.list.back(), cell); - } - else if (type == typeid(ESM::Repair).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->repairs.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->repairs.list.back(), cell); - } - else if (type == typeid(ESM::Probe).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->probes.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->probes.list.back(), cell); - } - else if (type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - cell->weapons.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->weapons.list.back(), cell); - } - else if (type == typeid(ESM::Miscellaneous).name()) - { - - // if this is gold, we need to fetch the correct mesh depending on the amount of gold. - if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) - { - int goldAmount = ptr.getRefData().getCount(); - - std::string base = "Gold_001"; - if (goldAmount >= 100) - base = "Gold_100"; - else if (goldAmount >= 25) - base = "Gold_025"; - else if (goldAmount >= 10) - base = "Gold_010"; - else if (goldAmount >= 5) - base = "Gold_005"; - - MWWorld::ManualRef newRef (MWBase::Environment::get().getWorld()->getStore(), base); - - MWWorld::LiveCellRef* ref = newRef.getPtr().get(); - - cell->miscItems.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); - - ESM::Position& p = newPtr.getRefData().getPosition(); - p.pos[0] = ptr.getRefData().getPosition().pos[0]; - p.pos[1] = ptr.getRefData().getPosition().pos[1]; - p.pos[2] = ptr.getRefData().getPosition().pos[2]; - } - else - { - MWWorld::LiveCellRef* ref = ptr.get(); - - cell->miscItems.list.push_back( *ref ); - newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); - } - } - else - throw std::runtime_error("Trying to insert object of unhandled type"); - - - - newPtr.getRefData().setCount(ptr.getRefData().getCount()); - ptr.getRefData().setCount(0); - newPtr.getRefData().enable(); - - mRendering.addObject(newPtr); - MWWorld::Class::get(newPtr).insertObject(newPtr, *mPhysics); - - } - void Scene::addObjectToScene (const Ptr& ptr) { mRendering.addObject (ptr); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index c0b93796a..f5f4b640b 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -88,10 +88,6 @@ namespace MWWorld void insertCell (Ptr::CellStore &cell); - /// this method is only meant for dropping objects into the gameworld from a container - /// and thus only handles object types that can be placed in a container - void insertObject (const Ptr& object, CellStore* cell); - void update (float duration); void addObjectToScene (const Ptr& ptr); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a68f08e34..4ffcd33f8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1021,7 +1021,8 @@ namespace MWWorld pos.pos[1] = -result.second[2]; pos.pos[2] = result.second[1]; - mWorldScene->insertObject(object, cell); + cell->insertObject(object); + //TODO mWorldScene->addObjectToScene /// \todo retrieve the bounds of the object and translate it accordingly From 71253c64ab8f11205fef1dc191be78275ff80937 Mon Sep 17 00:00:00 2001 From: greye Date: Tue, 24 Jul 2012 12:30:59 +0400 Subject: [PATCH 238/298] moving part 2, adding position parameter, stable --- apps/openmw/mwworld/cellstore.cpp | 13 +++++++++++-- apps/openmw/mwworld/cellstore.hpp | 4 ++-- apps/openmw/mwworld/worldimp.cpp | 16 +++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 22c94cf75..088c0c623 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -4,8 +4,12 @@ #include +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + #include "ptr.hpp" #include "manualref.hpp" +#include "class.hpp" namespace MWWorld { @@ -125,7 +129,7 @@ namespace MWWorld } /// \todo this whole code needs major clean up - void CellStore::insertObject (const Ptr& ptr) + const MWWorld::Ptr CellStore::insertObject (const Ptr& ptr, const ESM::Position &pos) { std::string type = ptr.getTypeName(); @@ -213,11 +217,12 @@ namespace MWWorld newRef.getPtr().get(); newPtr = MWWorld::Ptr(&miscItems.insert(*ref), this); - + /* ESM::Position& p = newPtr.getRefData().getPosition(); p.pos[0] = ptr.getRefData().getPosition().pos[0]; p.pos[1] = ptr.getRefData().getPosition().pos[1]; p.pos[2] = ptr.getRefData().getPosition().pos[2]; + */ } else { @@ -230,9 +235,13 @@ namespace MWWorld else throw std::runtime_error("Trying to insert object of unhandled type"); + newPtr.getRefData().getPosition() = pos; + newPtr.getRefData().setCount(ptr.getRefData().getCount()); ptr.getRefData().setCount(0); newPtr.getRefData().enable(); + + return newPtr; } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 3dcde9359..1bf233a50 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -155,6 +155,8 @@ namespace MWWorld forEachImp (functor, weapons); } + const MWWorld::Ptr insertObject(const MWWorld::Ptr &ptr, const ESM::Position &pos); + private: template @@ -172,8 +174,6 @@ namespace MWWorld void listRefs(const ESMS::ESMStore &store, ESM::ESMReader &esm); void loadRefs(const ESMS::ESMStore &store, ESM::ESMReader &esm); - - void insertObject(const MWWorld::Ptr &ptr); }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4ffcd33f8..efb17a709 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1016,13 +1016,13 @@ namespace MWWorld else cell = getPlayer().getPlayer().getCell(); - ESM::Position& pos = object.getRefData().getPosition(); + ESM::Position pos = getPlayer().getPlayer().getRefData().getPosition(); pos.pos[0] = result.second[0]; pos.pos[1] = -result.second[2]; pos.pos[2] = result.second[1]; - cell->insertObject(object); - //TODO mWorldScene->addObjectToScene + MWWorld::Ptr dropped = cell->insertObject(object, pos); + mWorldScene->addObjectToScene(dropped); /// \todo retrieve the bounds of the object and translate it accordingly @@ -1044,14 +1044,12 @@ namespace MWWorld { MWWorld::Ptr::CellStore* cell = getPlayer().getPlayer().getCell(); - float* playerPos = getPlayer().getPlayer().getRefData().getPosition().pos; + ESM::Position &pos = + getPlayer().getPlayer().getRefData().getPosition(); - ESM::Position& pos = object.getRefData().getPosition(); - pos.pos[0] = playerPos[0]; - pos.pos[1] = playerPos[1]; - pos.pos[2] = playerPos[2]; + MWWorld::Ptr dropped = cell->insertObject(object, pos); - mWorldScene->insertObject(object, cell); + mWorldScene->addObjectToScene(dropped); } void World::processChangedSettings(const Settings::CategorySettingVector& settings) From 87050e48c8d757afe2c1fe9819c06bb9c7d978e6 Mon Sep 17 00:00:00 2001 From: greye Date: Tue, 24 Jul 2012 18:52:08 +0400 Subject: [PATCH 239/298] physics getObjectHeight(), MWWorld::Class::getModel() definition --- apps/openmw/mwworld/class.cpp | 5 +++++ apps/openmw/mwworld/class.hpp | 2 ++ apps/openmw/mwworld/physicssystem.cpp | 10 ++++++++++ apps/openmw/mwworld/physicssystem.hpp | 2 ++ libs/openengine/bullet/physic.cpp | 21 +++++++++++++++++++++ libs/openengine/bullet/physic.hpp | 2 ++ 6 files changed, 42 insertions(+) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 65fa91666..f973301c5 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -212,4 +212,9 @@ namespace MWWorld void Class::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const { } + + std::string Class::getModel() const + { + return ""; + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 3d3ed71ec..5f16ef593 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -204,6 +204,8 @@ namespace MWWorld virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const; + + virtual std::string getModel() const; }; } diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index bf5c001db..db1db0505 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -14,6 +14,7 @@ #include "../mwbase/world.hpp" // FIXME #include "ptr.hpp" +#include "class.hpp" using namespace Ogre; namespace MWWorld @@ -365,4 +366,13 @@ namespace MWWorld addActor (node->getName(), model, node->getPosition()); } + float PhysicsSystem::getObjectHeight(const MWWorld::Ptr &ptr) { + std::string model = MWWorld::Class::get(ptr).getModel(); + if (model.empty()) { + return 0.0; + } + float scale = ptr.getRefData().getBaseNode()->getScale().x; + return mEngine->getObjectHeight(model, scale); + } + } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index a6b679833..61e2590bd 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -65,6 +65,8 @@ namespace MWWorld void setCurrentWater(bool hasWater, int waterHeight); + float getObjectHeight(const MWWorld::Ptr &ptr); + private: OEngine::Render::OgreRenderer &mRender; OEngine::Physic::PhysicEngine* mEngine; diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 61d0c7b0e..416d0c09d 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -568,4 +568,25 @@ namespace Physic return results2; } + + float PhysicEngine::getObjectHeight(const std::string &mesh, float scale) { + char uniqueID[8]; + sprintf( uniqueID, "%07.3f", scale ); + std::string sid = uniqueID; + std::string outputstring = mesh + uniqueID + "\"|"; + + mShapeLoader->load(outputstring, "General"); + BulletShapeManager::getSingletonPtr()->load(outputstring, "General"); + BulletShapePtr shape = + BulletShapeManager::getSingleton().getByName(outputstring, "General"); + + + btTransform trans; + btVector3 min, max; + + trans.setIdentity(); + shape->Shape->getAabb(trans, min, max); + + return max.z() - min.z(); + } }}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 3988c75a4..22c5fa274 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -221,6 +221,8 @@ namespace Physic bool toggleDebugRendering(); + float getObjectHeight(const std::string &mesh, float scale); + /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). */ From 6a3a728a56f76d2a74277f1b52b77355c649b3e0 Mon Sep 17 00:00:00 2001 From: greye Date: Tue, 24 Jul 2012 20:22:11 +0400 Subject: [PATCH 240/298] Class::getModel implementation --- apps/openmw/mwclass/activator.cpp | 29 ++++++++++++------------ apps/openmw/mwclass/activator.hpp | 2 ++ apps/openmw/mwclass/apparatus.cpp | 31 +++++++++++++------------- apps/openmw/mwclass/apparatus.hpp | 2 ++ apps/openmw/mwclass/armor.cpp | 28 ++++++++++++----------- apps/openmw/mwclass/armor.hpp | 1 + apps/openmw/mwclass/book.cpp | 29 ++++++++++++------------ apps/openmw/mwclass/book.hpp | 2 ++ apps/openmw/mwclass/clothing.cpp | 29 ++++++++++++------------ apps/openmw/mwclass/clothing.hpp | 2 ++ apps/openmw/mwclass/container.cpp | 29 ++++++++++++------------ apps/openmw/mwclass/container.hpp | 2 ++ apps/openmw/mwclass/creature.cpp | 18 ++++++++++----- apps/openmw/mwclass/creature.hpp | 2 ++ apps/openmw/mwclass/door.cpp | 29 +++++++++++++----------- apps/openmw/mwclass/door.hpp | 2 ++ apps/openmw/mwclass/ingredient.cpp | 27 ++++++++++++---------- apps/openmw/mwclass/ingredient.hpp | 2 ++ apps/openmw/mwclass/light.cpp | 23 ++++++++++++++----- apps/openmw/mwclass/light.hpp | 2 ++ apps/openmw/mwclass/lockpick.cpp | 30 ++++++++++++------------- apps/openmw/mwclass/lockpick.hpp | 2 ++ apps/openmw/mwclass/misc.cpp | 29 ++++++++++++------------ apps/openmw/mwclass/misc.hpp | 2 ++ apps/openmw/mwclass/npc.cpp | 32 +++++++++++++++------------ apps/openmw/mwclass/npc.hpp | 2 ++ apps/openmw/mwclass/potion.cpp | 29 ++++++++++++------------ apps/openmw/mwclass/potion.hpp | 2 ++ apps/openmw/mwclass/probe.cpp | 30 ++++++++++++------------- apps/openmw/mwclass/probe.hpp | 2 ++ apps/openmw/mwclass/repair.cpp | 29 ++++++++++++------------ apps/openmw/mwclass/repair.hpp | 2 ++ apps/openmw/mwclass/static.cpp | 28 ++++++++++++----------- apps/openmw/mwclass/static.hpp | 2 ++ apps/openmw/mwclass/weapon.cpp | 29 ++++++++++++------------ apps/openmw/mwclass/weapon.hpp | 1 + apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/class.hpp | 2 +- apps/openmw/mwworld/physicssystem.cpp | 3 +-- 39 files changed, 312 insertions(+), 237 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 81a47ccb0..33baad7ca 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -19,32 +19,33 @@ namespace MWClass { void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Activator::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Activator::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } std::string Activator::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 223dd0a36..2e947a9fc 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -28,6 +28,8 @@ namespace MWClass ///< Return name of the script attached to ptr static void registerSelf(); + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 7e3c3b8f9..dd255f9f8 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -22,34 +22,35 @@ namespace MWClass { - void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Apparatus::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Apparatus::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } std::string Apparatus::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index f33f92e2c..fa77eec93 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -48,6 +48,8 @@ namespace MWClass virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 380c596d7..8bbb198cc 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -27,31 +27,33 @@ namespace MWClass { void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Armor::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Armor::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } std::string Armor::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index a63806162..181a4ce61 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -66,6 +66,7 @@ namespace MWClass const; ///< Generate action for using via inventory menu + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index a37da0fd7..62b6c8204 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -23,32 +23,33 @@ namespace MWClass { void Book::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Book::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Book::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } std::string Book::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index ee3aac8d8..20ea89274 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -50,6 +50,8 @@ namespace MWClass virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 6c34b5e56..e1ec5a61c 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -25,32 +25,33 @@ namespace MWClass { void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Clothing::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Clothing::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } std::string Clothing::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index aba317be0..6b5fe1e96 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -59,6 +59,8 @@ namespace MWClass virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 8dd27db42..7c5719b8e 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -54,32 +54,33 @@ namespace MWClass void Container::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Container::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Container::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } boost::shared_ptr Container::activate (const MWWorld::Ptr& ptr, diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 739c75c77..17a9ac4ea 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -51,6 +51,8 @@ namespace MWClass ///< Unlock object static void registerSelf(); + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 83370478f..178a6f536 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -86,17 +86,25 @@ namespace MWClass } void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()){ + physics.insertActorPhysics(ptr, model); + } + MWBase::Environment::get().getMechanicsManager()->addActor (ptr); + } + + std::string Creature::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); + assert (ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertActorPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - - MWBase::Environment::get().getMechanicsManager()->addActor (ptr); + return ""; } std::string Creature::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 1274be09a..b994e0831 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -54,6 +54,8 @@ namespace MWClass /// effects). Throws an exception, if the object can't hold other objects. static void registerSelf(); + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index b0bba2c03..1274219a9 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -25,30 +25,33 @@ namespace MWClass { void Door::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Door::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const { - MWWorld::LiveCellRef *ref = + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Door::getModel(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } + return ""; } std::string Door::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 63d1c1ab8..2612df41c 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -38,6 +38,8 @@ namespace MWClass ///< Return name of the script attached to ptr static void registerSelf(); + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 01146fe67..a2f14c02c 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -23,30 +23,33 @@ namespace MWClass { void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Ingredient::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Ingredient::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } + return ""; } std::string Ingredient::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 4c45bd69c..ad87dd46c 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -44,6 +44,8 @@ namespace MWClass virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; ///< Return name of inventory icon. + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 15cd89ac2..37d7d1eec 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -28,8 +28,8 @@ namespace MWClass { MWWorld::LiveCellRef *ref = ptr.get(); - assert (ref->base != NULL); + const std::string &model = ref->base->model; MWRender::Objects& objects = renderingInterface.getObjects(); @@ -50,18 +50,29 @@ namespace MWClass { MWWorld::LiveCellRef *ref = ptr.get(); - assert (ref->base != NULL); + const std::string &model = ref->base->model; - if(!model.empty()){ + if(!model.empty()) { physics.insertObjectPhysics(ptr, "meshes\\" + model); } + if (!ref->base->sound.empty()) { + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->base->sound, 1.0, 1.0, MWSound::Play_Loop); + } + } - if (!ref->base->sound.empty()) - { - MWBase::Environment::get().getSoundManager()->playSound3D (ptr, ref->base->sound, 1.0, 1.0, MWSound::Play_Loop); + std::string Light::getModel(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + assert (ref->base != NULL); + + const std::string &model = ref->base->model; + if (!model.empty()) { + return "meshes\\" + model; } + return ""; } std::string Light::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 91193dfdc..ab8854efd 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -52,6 +52,8 @@ namespace MWClass virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index d3d60315f..ae2b5de89 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -25,35 +25,35 @@ namespace MWClass { void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Lockpick::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Lockpick::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } - std::string Lockpick::getName (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 26aab584c..45a02d40d 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -52,6 +52,8 @@ namespace MWClass virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 8484a5dd1..89c1ef25f 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -27,32 +27,33 @@ namespace MWClass { void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Miscellaneous::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } std::string Miscellaneous::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index da5f0df96..23b5e9564 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -44,6 +44,8 @@ namespace MWClass virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; ///< Return name of inventory icon. + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 80bff73fa..a5525fcda 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -125,25 +125,29 @@ namespace MWClass void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const { + physics.insertActorPhysics(ptr, getModel(ptr)); + MWBase::Environment::get().getMechanicsManager()->addActor(ptr); + } + std::string Npc::getModel(const MWWorld::Ptr &ptr) const + { MWWorld::LiveCellRef *ref = ptr.get(); - - assert (ref->base != NULL); - - + assert(ref->base != NULL); std::string headID = ref->base->head; - std::string bodyRaceID = headID.substr(0, headID.find_last_of("head_") - 4); - bool beast = bodyRaceID == "b_n_khajiit_m_" || bodyRaceID == "b_n_khajiit_f_" || bodyRaceID == "b_n_argonian_m_" || bodyRaceID == "b_n_argonian_f_"; - - std::string smodel = "meshes\\base_anim.nif"; - if(beast) - smodel = "meshes\\base_animkna.nif"; - physics.insertActorPhysics(ptr, smodel); - - - MWBase::Environment::get().getMechanicsManager()->addActor (ptr); + int end = headID.find_last_of("head_") - 4; + std::string bodyRaceID = headID.substr(0, end); + + std::string model = "meshes\\base_anim.nif"; + if (bodyRaceID == "b_n_khajiit_m_" || + bodyRaceID == "b_n_khajiit_f_" || + bodyRaceID == "b_n_argonian_m_" || + bodyRaceID == "b_n_argonian_f_") + { + model = "meshes\\base_animkna.nif"; + } + return model; } std::string Npc::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index b32a162a1..0ea45d132 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -88,6 +88,8 @@ namespace MWClass virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const; static void registerSelf(); + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 45cb07840..92a951d03 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -25,32 +25,33 @@ namespace MWClass { void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Potion::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Potion::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } std::string Potion::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 101f4cefa..39edfd760 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -47,6 +47,8 @@ namespace MWClass virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; ///< Return name of inventory icon. + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index f3a8406f5..033eee4c5 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -25,35 +25,35 @@ namespace MWClass { void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Probe::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Probe::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } - std::string Probe::getName (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 51b046fda..ff10eb9d6 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -52,6 +52,8 @@ namespace MWClass virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 464ba1091..4258ad106 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -23,32 +23,33 @@ namespace MWClass { void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Repair::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Repair::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } std::string Repair::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index 1e935e154..689850b90 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -44,6 +44,8 @@ namespace MWClass virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; ///< Return name of inventory icon. + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 9b166b076..a14adc300 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -13,31 +13,33 @@ namespace MWClass { void Static::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), true); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Static::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Static::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); + assert(ref->base != NULL); - assert (ref->base != NULL); const std::string &model = ref->base->model; - - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } + return ""; } std::string Static::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index c223df1ac..3066933e4 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -19,6 +19,8 @@ namespace MWClass /// can return an empty string. static void registerSelf(); + + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 099312d2c..6412a46b1 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -25,32 +25,33 @@ namespace MWClass { void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - assert (ref->base != NULL); - const std::string &model = ref->base->model; - - if (!model.empty()) - { + const std::string model = getModel(ptr); + if (!model.empty()) { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertBegin(ptr, ptr.getRefData().isEnabled(), false); - objects.insertMesh(ptr, "meshes\\" + model); + objects.insertMesh(ptr, model); } } void Weapon::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + { + const std::string model = getModel(ptr); + if(!model.empty()) { + physics.insertObjectPhysics(ptr, model); + } + } + + std::string Weapon::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - + assert(ref->base != NULL); const std::string &model = ref->base->model; - assert (ref->base != NULL); - if(!model.empty()){ - physics.insertObjectPhysics(ptr, "meshes\\" + model); + if (!model.empty()) { + return "meshes\\" + model; } - + return ""; } std::string Weapon::getName (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 92d703b4a..eaf5b60a4 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -66,6 +66,7 @@ namespace MWClass const; ///< Generate action for using via inventory menu + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index f973301c5..729746f6b 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -213,7 +213,7 @@ namespace MWWorld { } - std::string Class::getModel() const + std::string Class::getModel(const MWWorld::Ptr &ptr) const { return ""; } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 5f16ef593..c8b51eeab 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -205,7 +205,7 @@ namespace MWWorld virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const; - virtual std::string getModel() const; + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index db1db0505..1eba439a8 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -367,12 +367,11 @@ namespace MWWorld } float PhysicsSystem::getObjectHeight(const MWWorld::Ptr &ptr) { - std::string model = MWWorld::Class::get(ptr).getModel(); + std::string model = MWWorld::Class::get(ptr).getModel(ptr); if (model.empty()) { return 0.0; } float scale = ptr.getRefData().getBaseNode()->getScale().x; return mEngine->getObjectHeight(model, scale); } - } From e1c7d1f52936988f2093db7a9800093c1cfac927 Mon Sep 17 00:00:00 2001 From: greye Date: Tue, 24 Jul 2012 22:08:23 +0400 Subject: [PATCH 241/298] fixed item sinking --- apps/openmw/mwworld/physicssystem.cpp | 31 ++++++++++++++------------- apps/openmw/mwworld/scene.cpp | 6 ++++++ apps/openmw/mwworld/worldimp.cpp | 4 ++-- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 1eba439a8..2b3d4b0f4 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -349,29 +349,30 @@ namespace MWWorld throw std::logic_error ("can't find player"); } - void PhysicsSystem::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string model){ + void PhysicsSystem::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string model){ - Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); + Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); - // unused - //Ogre::Vector3 objPos = node->getPosition(); - - addObject (node->getName(), model, node->getOrientation(), - node->getScale().x, node->getPosition()); - } + addObject( + node->getName(), + model, + node->getOrientation(), + node->getScale().x, + node->getPosition()); + } - void PhysicsSystem::insertActorPhysics(const MWWorld::Ptr& ptr, const std::string model){ - Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); - // std::cout << "Adding node with name" << node->getName(); - addActor (node->getName(), model, node->getPosition()); - } + void PhysicsSystem::insertActorPhysics(const MWWorld::Ptr& ptr, const std::string model){ + Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); + addActor (node->getName(), model, node->getPosition()); + } - float PhysicsSystem::getObjectHeight(const MWWorld::Ptr &ptr) { + float PhysicsSystem::getObjectHeight(const MWWorld::Ptr &ptr) + { std::string model = MWWorld::Class::get(ptr).getModel(ptr); if (model.empty()) { return 0.0; } float scale = ptr.getRefData().getBaseNode()->getScale().x; return mEngine->getObjectHeight(model, scale); - } + } } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 9786c24d9..b78669ef5 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -335,6 +335,12 @@ namespace MWWorld void Scene::addObjectToScene (const Ptr& ptr) { mRendering.addObject (ptr); + + float *pos = ptr.getRefData().getPosition().pos; + pos[2] += mPhysics->getObjectHeight(ptr) / 2; + + ptr.getRefData().getBaseNode()->setPosition(pos[0], pos[1], pos[2]); + MWWorld::Class::get (ptr).insertObject (ptr, *mPhysics); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index efb17a709..3d409570b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1024,8 +1024,6 @@ namespace MWWorld MWWorld::Ptr dropped = cell->insertObject(object, pos); mWorldScene->addObjectToScene(dropped); - /// \todo retrieve the bounds of the object and translate it accordingly - return true; } @@ -1047,6 +1045,8 @@ namespace MWWorld ESM::Position &pos = getPlayer().getPlayer().getRefData().getPosition(); + /// \todo fix item dropping at player object center position + MWWorld::Ptr dropped = cell->insertObject(object, pos); mWorldScene->addObjectToScene(dropped); From aff05b9d36f102634334219b61ea253a4e7a8dc7 Mon Sep 17 00:00:00 2001 From: greye Date: Wed, 25 Jul 2012 00:03:31 +0400 Subject: [PATCH 242/298] local script registration --- apps/openmw/mwworld/worldimp.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3d409570b..670b3e512 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1023,6 +1023,9 @@ namespace MWWorld MWWorld::Ptr dropped = cell->insertObject(object, pos); mWorldScene->addObjectToScene(dropped); + mLocalScripts.add( + MWWorld::Class::get(dropped).getScript(), + dropped); return true; } @@ -1050,6 +1053,9 @@ namespace MWWorld MWWorld::Ptr dropped = cell->insertObject(object, pos); mWorldScene->addObjectToScene(dropped); + mLocalScripts.add( + MWWorld::Class::get(dropped).getScript(), + dropped); } void World::processChangedSettings(const Settings::CategorySettingVector& settings) From d5fe378a6622445076dea86779a318d6b18e57c5 Mon Sep 17 00:00:00 2001 From: greye Date: Wed, 25 Jul 2012 10:02:43 +0400 Subject: [PATCH 243/298] Revert "local script registration" This reverts commit 0686399a306319a4c49e1009d10215a9c0e65ec2. --- apps/openmw/mwworld/worldimp.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 670b3e512..3d409570b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1023,9 +1023,6 @@ namespace MWWorld MWWorld::Ptr dropped = cell->insertObject(object, pos); mWorldScene->addObjectToScene(dropped); - mLocalScripts.add( - MWWorld::Class::get(dropped).getScript(), - dropped); return true; } @@ -1053,9 +1050,6 @@ namespace MWWorld MWWorld::Ptr dropped = cell->insertObject(object, pos); mWorldScene->addObjectToScene(dropped); - mLocalScripts.add( - MWWorld::Class::get(dropped).getScript(), - dropped); } void World::processChangedSettings(const Settings::CategorySettingVector& settings) From bcc47cd5fb00858c481fff542fad19cac52a79f8 Mon Sep 17 00:00:00 2001 From: greye Date: Wed, 25 Jul 2012 10:47:59 +0400 Subject: [PATCH 244/298] local scripts, move placing to World::placeObject(Ptr, CellStore, Position) --- apps/openmw/mwbase/world.hpp | 8 ++++++++ apps/openmw/mwworld/worldimp.cpp | 22 ++++++++++++++++------ apps/openmw/mwworld/worldimp.hpp | 3 +++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 6937cbf3b..f257b723e 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -54,6 +54,14 @@ namespace MWBase World& operator= (const World&); ///< not implemented + protected: + + virtual void + placeObject( + const MWWorld::Ptr &ptr, + MWWorld::CellStore &cell, + const ESM::Position &pos) = 0; + public: enum RenderMode diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3d409570b..19cdcc334 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1021,8 +1021,7 @@ namespace MWWorld pos.pos[1] = -result.second[2]; pos.pos[2] = result.second[1]; - MWWorld::Ptr dropped = cell->insertObject(object, pos); - mWorldScene->addObjectToScene(dropped); + placeObject(object, *cell, pos); return true; } @@ -1038,6 +1037,20 @@ namespace MWWorld return true; } + void + World::placeObject(const Ptr &object, CellStore &cell, const ESM::Position &pos) + { + mLocalScripts.remove(object); + + MWWorld::Ptr dropped = cell.insertObject(object, pos); + mWorldScene->addObjectToScene(dropped); + + std::string script = MWWorld::Class::get(dropped).getScript(dropped); + if (!script.empty()) { + mLocalScripts.add(script, dropped); + } + } + void World::dropObjectOnGround (const Ptr& object) { MWWorld::Ptr::CellStore* cell = getPlayer().getPlayer().getCell(); @@ -1046,10 +1059,7 @@ namespace MWWorld getPlayer().getPlayer().getRefData().getPosition(); /// \todo fix item dropping at player object center position - - MWWorld::Ptr dropped = cell->insertObject(object, pos); - - mWorldScene->addObjectToScene(dropped); + placeObject(object, *cell, pos); } void World::processChangedSettings(const Settings::CategorySettingVector& settings) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 43b178fe3..d39871c21 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -89,6 +89,9 @@ namespace MWWorld bool moveObjectImp (const Ptr& ptr, float x, float y, float z); ///< @return true if the active cell (cell player is in) changed + virtual void + placeObject(const Ptr &ptr, CellStore &cell, const ESM::Position &pos); + public: World (OEngine::Render::OgreRenderer& renderer, From e7666d3a7fd22a9489ec60244678cd3664532003 Mon Sep 17 00:00:00 2001 From: greye Date: Wed, 25 Jul 2012 17:18:17 +0400 Subject: [PATCH 245/298] move responsibility for cell changing from CellStore::insertObject to Class::moveToCell --- apps/openmw/mwclass/activator.cpp | 10 +++ apps/openmw/mwclass/activator.hpp | 4 + apps/openmw/mwclass/apparatus.cpp | 9 +++ apps/openmw/mwclass/apparatus.hpp | 4 + apps/openmw/mwclass/armor.cpp | 9 +++ apps/openmw/mwclass/armor.hpp | 3 + apps/openmw/mwclass/book.cpp | 8 ++ apps/openmw/mwclass/book.hpp | 3 + apps/openmw/mwclass/clothing.cpp | 9 +++ apps/openmw/mwclass/clothing.hpp | 3 + apps/openmw/mwclass/container.cpp | 9 +++ apps/openmw/mwclass/container.hpp | 4 + apps/openmw/mwclass/creature.cpp | 9 +++ apps/openmw/mwclass/creature.hpp | 3 + apps/openmw/mwclass/door.cpp | 9 +++ apps/openmw/mwclass/door.hpp | 3 + apps/openmw/mwclass/ingredient.cpp | 9 +++ apps/openmw/mwclass/ingredient.hpp | 3 + apps/openmw/mwclass/light.cpp | 9 +++ apps/openmw/mwclass/light.hpp | 3 + apps/openmw/mwclass/lockpick.cpp | 9 +++ apps/openmw/mwclass/lockpick.hpp | 3 + apps/openmw/mwclass/misc.cpp | 36 +++++++++ apps/openmw/mwclass/misc.hpp | 3 + apps/openmw/mwclass/npc.cpp | 9 +++ apps/openmw/mwclass/npc.hpp | 3 + apps/openmw/mwclass/potion.cpp | 10 +++ apps/openmw/mwclass/potion.hpp | 3 + apps/openmw/mwclass/probe.cpp | 10 +++ apps/openmw/mwclass/probe.hpp | 5 +- apps/openmw/mwclass/repair.cpp | 9 +++ apps/openmw/mwclass/repair.hpp | 5 +- apps/openmw/mwclass/static.cpp | 9 +++ apps/openmw/mwclass/static.hpp | 3 + apps/openmw/mwclass/weapon.cpp | 9 +++ apps/openmw/mwclass/weapon.hpp | 3 + apps/openmw/mwworld/cellstore.cpp | 119 ----------------------------- apps/openmw/mwworld/cellstore.hpp | 6 +- apps/openmw/mwworld/class.cpp | 31 ++++++++ apps/openmw/mwworld/class.hpp | 14 ++++ apps/openmw/mwworld/worldimp.cpp | 4 +- 41 files changed, 300 insertions(+), 126 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 33baad7ca..d0f09e80e 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -94,4 +94,14 @@ namespace MWClass return info; } + + MWWorld::Ptr + Activator::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.activators.insert(*ref), &cell); + } } + diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 2e947a9fc..26aafb717 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -7,6 +7,10 @@ namespace MWClass { class Activator : public MWWorld::Class { + + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index dd255f9f8..6322c963e 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -149,4 +149,13 @@ namespace MWClass { return boost::shared_ptr(new MWWorld::ActionAlchemy()); } + + MWWorld::Ptr + Apparatus::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.appas.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index fa77eec93..9352e0617 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -7,6 +7,10 @@ namespace MWClass { class Apparatus : public MWWorld::Class { + + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 8bbb198cc..aeefcd42b 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -276,4 +276,13 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); } + + MWWorld::Ptr + Armor::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.armors.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 181a4ce61..960b6dad5 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Armor : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 62b6c8204..1517f090a 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -157,4 +157,12 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionRead(ptr)); } + MWWorld::Ptr + Book::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.books.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 20ea89274..adbf2d64b 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Book : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index e1ec5a61c..86c031db0 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -227,4 +227,13 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); } + + MWWorld::Ptr + Clothing::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.clothes.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 6b5fe1e96..2c0d0b8a5 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Clothing : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 7c5719b8e..2543f6acd 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -207,4 +207,13 @@ namespace MWClass { ptr.getCellRef().lockLevel = 0; } + + MWWorld::Ptr + Container::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.containers.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 17a9ac4ea..2f15d03d8 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -9,6 +9,10 @@ namespace MWClass { void ensureCustomData (const MWWorld::Ptr& ptr) const; + + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 178a6f536..8f885bf62 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -195,4 +195,13 @@ namespace MWClass return weight; } + + MWWorld::Ptr + Creature::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.creatures.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index b994e0831..38be98533 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -11,6 +11,9 @@ namespace MWClass { void ensureCustomData (const MWWorld::Ptr& ptr) const; + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual std::string getId (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 1274219a9..8eef54725 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -209,4 +209,13 @@ namespace MWClass return info; } + + MWWorld::Ptr + Door::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.doors.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 2612df41c..b89858556 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Door : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index a2f14c02c..02e77fa25 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -156,4 +156,13 @@ namespace MWClass return info; } + + MWWorld::Ptr + Ingredient::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.ingreds.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index ad87dd46c..44ee0ccd0 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Ingredient : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 37d7d1eec..e7b3af2b9 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -196,4 +196,13 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); } + + MWWorld::Ptr + Light::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.lights.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index ab8854efd..953078a35 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Light : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index ae2b5de89..e72e96822 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -165,4 +165,13 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); } + + MWWorld::Ptr + Lockpick::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.lockpicks.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 45a02d40d..216dc10c9 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Lockpick : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 89c1ef25f..37f608ed2 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -12,6 +12,7 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" +#include "../mwworld/manualref.hpp" #include "../mwgui/window_manager.hpp" #include "../mwgui/tooltips.hpp" @@ -183,4 +184,39 @@ namespace MWClass return info; } + + MWWorld::Ptr + Miscellaneous::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::Ptr newPtr; + + const ESMS::ESMStore &store = + MWBase::Environment::get().getWorld()->getStore(); + + if (MWWorld::Class::get(ptr).getName(ptr) == store.gameSettings.search("sGold")->str) { + int goldAmount = ptr.getRefData().getCount(); + + std::string base = "Gold_001"; + if (goldAmount >= 100) + base = "Gold_100"; + else if (goldAmount >= 25) + base = "Gold_025"; + else if (goldAmount >= 10) + base = "Gold_010"; + else if (goldAmount >= 5) + base = "Gold_005"; + + // Really, I have no idea why moving ref out of conditional + // scope causes list::push_back throwing std::bad_alloc + MWWorld::ManualRef newRef(store, base); + MWWorld::LiveCellRef *ref = + newRef.getPtr().get(); + newPtr = MWWorld::Ptr(&cell.miscItems.insert(*ref), &cell); + } else { + MWWorld::LiveCellRef *ref = + ptr.get(); + newPtr = MWWorld::Ptr(&cell.miscItems.insert(*ref), &cell); + } + return newPtr; + } } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 23b5e9564..2a314758f 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Miscellaneous : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index a5525fcda..c81397753 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -380,4 +380,13 @@ namespace MWClass y = 0; x = 0; } + + MWWorld::Ptr + Npc::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.npcs.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 0ea45d132..46fdc9b04 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -9,6 +9,9 @@ namespace MWClass { void ensureCustomData (const MWWorld::Ptr& ptr) const; + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual std::string getId (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 92a951d03..e0f9008c4 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -160,4 +160,14 @@ namespace MWClass return boost::shared_ptr ( new MWWorld::ActionApply (actor, ref->base->mId, actor)); } + + MWWorld::Ptr + Potion::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.potions.insert(*ref), &cell); + } } + diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 39edfd760..0cb40ead0 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Potion : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 033eee4c5..8e5fcfd19 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -164,4 +164,14 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); } + + MWWorld::Ptr + Probe::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.probes.insert(*ref), &cell); + } } + diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index ff10eb9d6..67831d766 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -7,9 +7,12 @@ namespace MWClass { class Probe : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 4258ad106..84d6e3b9e 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -146,4 +146,13 @@ namespace MWClass return info; } + + MWWorld::Ptr + Repair::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.repairs.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index 689850b90..74d1d7378 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -7,9 +7,12 @@ namespace MWClass { class Repair : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index a14adc300..ee8bcfe81 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -53,4 +53,13 @@ namespace MWClass registerClass (typeid (ESM::Static).name(), instance); } + + MWWorld::Ptr + Static::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.statics.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 3066933e4..52b87abcd 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Static : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 6412a46b1..ccc5ff6ea 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -365,4 +365,13 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); } + + MWWorld::Ptr + Weapon::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return MWWorld::Ptr(&cell.weapons.insert(*ref), &cell); + } } diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index eaf5b60a4..b18405488 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -7,6 +7,9 @@ namespace MWClass { class Weapon : public MWWorld::Class { + virtual MWWorld::Ptr + moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 088c0c623..eceb5ddc1 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -8,8 +8,6 @@ #include "../mwbase/world.hpp" #include "ptr.hpp" -#include "manualref.hpp" -#include "class.hpp" namespace MWWorld { @@ -127,121 +125,4 @@ namespace MWWorld } } } - - /// \todo this whole code needs major clean up - const MWWorld::Ptr CellStore::insertObject (const Ptr& ptr, const ESM::Position &pos) - { - std::string type = ptr.getTypeName(); - - MWWorld::Ptr newPtr; - - // insert into the correct CellRefList - if (type == typeid(ESM::Potion).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&potions.insert(*ref), this); - } - else if (type == typeid(ESM::Apparatus).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&appas.insert(*ref), this); - } - else if (type == typeid(ESM::Armor).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&armors.insert(*ref), this); - } - else if (type == typeid(ESM::Book).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&books.insert(*ref), this); - } - else if (type == typeid(ESM::Clothing).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&clothes.insert(*ref), this); - } - else if (type == typeid(ESM::Ingredient).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&ingreds.insert(*ref), this); - } - else if (type == typeid(ESM::Light).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&lights.insert(*ref), this); - } - else if (type == typeid(ESM::Tool).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&lockpicks.insert(*ref), this); - } - else if (type == typeid(ESM::Repair).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&repairs.insert(*ref), this); - } - else if (type == typeid(ESM::Probe).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&probes.insert(*ref), this); - } - else if (type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef* ref = ptr.get(); - newPtr = MWWorld::Ptr(&weapons.insert(*ref), this); - } - else if (type == typeid(ESM::Miscellaneous).name()) - { - - // if this is gold, we need to fetch the correct mesh depending on the amount of gold. - if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) - { - int goldAmount = ptr.getRefData().getCount(); - - std::string base = "Gold_001"; - if (goldAmount >= 100) - base = "Gold_100"; - else if (goldAmount >= 25) - base = "Gold_025"; - else if (goldAmount >= 10) - base = "Gold_010"; - else if (goldAmount >= 5) - base = "Gold_005"; - - MWWorld::ManualRef newRef( - MWBase::Environment::get().getWorld()->getStore(), - base); - - MWWorld::LiveCellRef* ref = - newRef.getPtr().get(); - - newPtr = MWWorld::Ptr(&miscItems.insert(*ref), this); - /* - ESM::Position& p = newPtr.getRefData().getPosition(); - p.pos[0] = ptr.getRefData().getPosition().pos[0]; - p.pos[1] = ptr.getRefData().getPosition().pos[1]; - p.pos[2] = ptr.getRefData().getPosition().pos[2]; - */ - } - else - { - MWWorld::LiveCellRef* ref = - ptr.get(); - - newPtr = MWWorld::Ptr(&miscItems.insert(*ref), this); - } - } - else - throw std::runtime_error("Trying to insert object of unhandled type"); - - newPtr.getRefData().getPosition() = pos; - - newPtr.getRefData().setCount(ptr.getRefData().getCount()); - ptr.getRefData().setCount(0); - newPtr.getRefData().enable(); - - return newPtr; - } - } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 1bf233a50..0be0ef651 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -113,9 +113,9 @@ namespace MWWorld CellRefList ingreds; CellRefList creatureLists; CellRefList itemLists; - CellRefList lights; + CellRefList lights; CellRefList lockpicks; - CellRefList miscItems; + CellRefList miscItems; CellRefList npcs; CellRefList probes; CellRefList repairs; @@ -155,8 +155,6 @@ namespace MWWorld forEachImp (functor, weapons); } - const MWWorld::Ptr insertObject(const MWWorld::Ptr &ptr, const ESM::Position &pos); - private: template diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 729746f6b..6f2306fbe 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -5,7 +5,10 @@ #include +#include + #include "ptr.hpp" +#include "refdata.hpp" #include "nullaction.hpp" #include "containerstore.hpp" @@ -217,4 +220,32 @@ namespace MWWorld { return ""; } + + MWWorld::Ptr + Class::moveToCellImpl(const Ptr &ptr, CellStore &cell) const + { + throw std::runtime_error("unable to move class to cell"); + } + + MWWorld::Ptr + Class::moveToCell(const Ptr &ptr, CellStore &cell) const + { + Ptr newPtr = moveToCellImpl(ptr, cell); + + newPtr.getRefData().setCount(ptr.getRefData().getCount()); + ptr.getRefData().setCount(0); + newPtr.getRefData().enable(); + + return newPtr; + } + + MWWorld::Ptr + Class::moveToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const + { + Ptr newPtr = moveToCell(ptr, cell); + newPtr.getRefData().getPosition() = pos; + + return newPtr; + } } + diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index c8b51eeab..509433433 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -31,12 +31,18 @@ namespace MWGui struct ToolTipInfo; } +namespace ESM +{ + struct Position; +} + namespace MWWorld { class Ptr; class ContainerStore; class InventoryStore; class PhysicsSystem; + class CellStore; /// \brief Base class for referenceable esm records class Class @@ -51,6 +57,8 @@ namespace MWWorld Class(); + virtual Ptr moveToCellImpl(const Ptr &ptr, CellStore &cell) const; + public: /// NPC-stances. @@ -206,6 +214,12 @@ namespace MWWorld virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const; virtual std::string getModel(const MWWorld::Ptr &ptr) const; + + virtual Ptr + moveToCell(const Ptr &ptr, CellStore &cell) const; + + virtual Ptr + moveToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const; }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 19cdcc334..394a9e493 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1042,7 +1042,9 @@ namespace MWWorld { mLocalScripts.remove(object); - MWWorld::Ptr dropped = cell.insertObject(object, pos); + MWWorld::Ptr dropped = + MWWorld::Class::get(object).moveToCell(object, cell, pos); + mWorldScene->addObjectToScene(dropped); std::string script = MWWorld::Class::get(dropped).getScript(dropped); From 49b1d5e1275bfebcd508afb910d4d55644184c20 Mon Sep 17 00:00:00 2001 From: greye Date: Wed, 25 Jul 2012 18:58:55 +0400 Subject: [PATCH 246/298] fix object placing --- apps/openmw/mwworld/physicssystem.cpp | 8 +++++--- apps/openmw/mwworld/physicssystem.hpp | 2 +- apps/openmw/mwworld/scene.cpp | 7 ++++++- libs/openengine/bullet/physic.cpp | 17 +++++++++++++---- libs/openengine/bullet/physic.hpp | 2 +- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 2b3d4b0f4..e4a019b69 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -366,13 +366,15 @@ namespace MWWorld addActor (node->getName(), model, node->getPosition()); } - float PhysicsSystem::getObjectHeight(const MWWorld::Ptr &ptr) + bool PhysicsSystem::getObjectAABB(const MWWorld::Ptr &ptr, float *min, float *max) { std::string model = MWWorld::Class::get(ptr).getModel(ptr); if (model.empty()) { - return 0.0; + return false; } float scale = ptr.getRefData().getBaseNode()->getScale().x; - return mEngine->getObjectHeight(model, scale); + mEngine->getObjectAABB(model, scale, min, max); + + return true; } } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 61e2590bd..f81bcc373 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -65,7 +65,7 @@ namespace MWWorld void setCurrentWater(bool hasWater, int waterHeight); - float getObjectHeight(const MWWorld::Ptr &ptr); + bool getObjectAABB(const MWWorld::Ptr &ptr, float *min, float *max); private: OEngine::Render::OgreRenderer &mRender; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b78669ef5..ce0acfe47 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -337,7 +337,12 @@ namespace MWWorld mRendering.addObject (ptr); float *pos = ptr.getRefData().getPosition().pos; - pos[2] += mPhysics->getObjectHeight(ptr) / 2; + float min[3], max[3]; + if (mPhysics->getObjectAABB(ptr, min, max)) { + pos[0] -= (min[0] + max[0]) / 2; + pos[1] -= (min[1] + max[1]) / 2; + pos[2] -= min[2]; + } ptr.getRefData().getBaseNode()->setPosition(pos[0], pos[1], pos[2]); diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 416d0c09d..611c87d9f 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -12,6 +12,8 @@ #include +#include + #define BIT(x) (1<<(x)) namespace OEngine { @@ -569,7 +571,8 @@ namespace Physic return results2; } - float PhysicEngine::getObjectHeight(const std::string &mesh, float scale) { + void PhysicEngine::getObjectAABB(const std::string &mesh, float scale, float *min, float *max) + { char uniqueID[8]; sprintf( uniqueID, "%07.3f", scale ); std::string sid = uniqueID; @@ -582,11 +585,17 @@ namespace Physic btTransform trans; - btVector3 min, max; + btVector3 btmin, btmax; trans.setIdentity(); - shape->Shape->getAabb(trans, min, max); + shape->Shape->getAabb(trans, btmin, btmax); + + min[0] = btmin.x(); + min[1] = btmin.y(); + min[2] = btmin.z(); - return max.z() - min.z(); + max[0] = btmax.x(); + max[1] = btmax.y(); + max[2] = btmax.z(); } }}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 22c5fa274..8701adc84 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -221,7 +221,7 @@ namespace Physic bool toggleDebugRendering(); - float getObjectHeight(const std::string &mesh, float scale); + void getObjectAABB(const std::string &mesh, float scale, float *min, float *max); /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). From cd04911f3c463ce6e8eb94f62020ee689b661c4f Mon Sep 17 00:00:00 2001 From: greye Date: Wed, 25 Jul 2012 20:25:53 +0400 Subject: [PATCH 247/298] dropping on the ground --- apps/openmw/mwworld/physicssystem.cpp | 16 ++++++++++++++++ apps/openmw/mwworld/physicssystem.hpp | 3 +++ apps/openmw/mwworld/worldimp.cpp | 13 ++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index e4a019b69..27d48fa39 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -122,6 +122,22 @@ namespace MWWorld return !(result.first == ""); } + std::pair + PhysicsSystem::castRay(const Ogre::Vector3 &orig, const Ogre::Vector3 &dir, float len) + { + Ogre::Ray ray = Ogre::Ray(orig, dir); + Ogre::Vector3 to = ray.getPoint(len); + + btVector3 btFrom = btVector3(orig.x, orig.y, orig.z); + btVector3 btTo = btVector3(to.x, to.y, to.z); + + std::pair test = mEngine->rayTest(btFrom, btTo); + if (test.first == "") { + return std::make_pair(false, Ogre::Vector3()); + } + return std::make_pair(true, ray.getPoint(len * test.second)); + } + std::pair PhysicsSystem::castRay(float mouseX, float mouseY) { Ogre::Ray ray = mRender.getCamera()->getCameraToViewportRay( diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index f81bcc373..375a7c9de 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -54,6 +54,9 @@ namespace MWWorld // cast ray, return true if it hit something bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to); + std::pair + castRay(const Ogre::Vector3 &orig, const Ogre::Vector3 &dir, float len); + std::pair castRay(float mouseX, float mouseY); ///< cast ray from the mouse, return true if it hit something and the first result (in OGRE coordinates) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 394a9e493..b9ab6ade2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1057,9 +1057,20 @@ namespace MWWorld { MWWorld::Ptr::CellStore* cell = getPlayer().getPlayer().getCell(); - ESM::Position &pos = + ESM::Position pos = getPlayer().getPlayer().getRefData().getPosition(); + Ogre::Vector3 orig = + Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); + Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1); + + float len = (pos.pos[2] >= 0) ? pos.pos[2] : -pos.pos[2]; + len += 100.0; + + std::pair hit = + mPhysics->castRay(orig, dir, len); + pos.pos[2] = hit.second.z; + /// \todo fix item dropping at player object center position placeObject(object, *cell, pos); } From 3feb4ce61b5c4c402cff2d47d294712d263beefb Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jul 2012 19:33:21 +0200 Subject: [PATCH 248/298] terrain lod morph fix --- files/materials/terrain.shader | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 601f287a0..0458c4db9 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -55,7 +55,7 @@ shUniform(float2, lodMorph) @shAutoConstant(lodMorph, custom, 1001) shVertexInput(float2, uv0) - shVertexInput(float2, delta) // lodDelta, lodThreshold + shVertexInput(float2, uv1) // lodDelta, lodThreshold #if SHADOWS shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) @@ -85,11 +85,11 @@ // result is negative (it will only be -1 in fact, since after that // the vertex will never be indexed), we will achieve our aim. // sign(vertexLOD - targetLOD) == -1 is to morph - float toMorph = -min(0, sign(delta.y - lodMorph.y)); + float toMorph = -min(0, sign(uv1.y - lodMorph.y)); // morph // this assumes XZ terrain alignment - worldPos.y += delta.x * toMorph * lodMorph.x; + worldPos.y += uv1.x * toMorph * lodMorph.x; shOutputPosition = shMatrixMult(viewProjMatrix, worldPos); From 9f813aa26c3ed3a31767f1b75d637027b2fb47b4 Mon Sep 17 00:00:00 2001 From: greye Date: Wed, 25 Jul 2012 23:28:42 +0400 Subject: [PATCH 249/298] update resource naming in getObjectAABB() --- libs/openengine/bullet/physic.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 611c87d9f..d4b63676d 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -12,8 +12,6 @@ #include -#include - #define BIT(x) (1<<(x)) namespace OEngine { @@ -576,14 +574,13 @@ namespace Physic char uniqueID[8]; sprintf( uniqueID, "%07.3f", scale ); std::string sid = uniqueID; - std::string outputstring = mesh + uniqueID + "\"|"; + std::string outputstring = mesh + uniqueID; mShapeLoader->load(outputstring, "General"); BulletShapeManager::getSingletonPtr()->load(outputstring, "General"); BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(outputstring, "General"); - btTransform trans; btVector3 btmin, btmax; From 37c7becb07326240881dc9d4bdfcb35404a68d94 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jul 2012 23:53:06 +0200 Subject: [PATCH 250/298] some potential compability fixes --- files/materials/objects.shader | 15 +++++++-------- files/materials/shadows.h | 10 +++++++--- files/materials/terrain.shader | 17 +++++++++-------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 238ccdcf6..dba239c14 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -192,7 +192,7 @@ #if SHADOWS || SHADOWS_PSSM float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; float fade = 1-((depthPassthrough - shadowFar_fadeStart.y) / fadeRange); - shadow = (depthPassthrough > shadowFar_fadeStart.x) ? 1 : ((depthPassthrough > shadowFar_fadeStart.y) ? 1-((1-shadow)*fade) : shadow); + shadow = (depthPassthrough > shadowFar_fadeStart.x) ? 1.0 : ((depthPassthrough > shadowFar_fadeStart.y) ? 1.0-((1.0-shadow)*fade) : shadow); #endif #if !SHADOWS && !SHADOWS_PSSM @@ -205,13 +205,12 @@ #if UNDERWATER float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)).xyz; float3 waterEyePos = float3(1,1,1); - if (worldPos.y < waterLevel && waterEnabled == 1) - { - // NOTE: this calculation would be wrong for non-uniform scaling - float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); - waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); - caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); - } + // NOTE: this calculation would be wrong for non-uniform scaling + float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); + waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); + caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); + if (worldPos.y >= waterLevel || waterEnabled != 1) + caustics = float3(1,1,1); #endif diff --git a/files/materials/shadows.h b/files/materials/shadows.h index 769a4fea7..b1dc92d21 100644 --- a/files/materials/shadows.h +++ b/files/materials/shadows.h @@ -36,12 +36,16 @@ float pssmDepthShadow ( { float shadow; + float pcf1 = depthShadowPCF(shadowMap0, lightSpacePos0, invShadowmapSize0); + float pcf2 = depthShadowPCF(shadowMap1, lightSpacePos1, invShadowmapSize1); + float pcf3 = depthShadowPCF(shadowMap2, lightSpacePos2, invShadowmapSize2); + if (depth < pssmSplitPoints.x) - shadow = depthShadowPCF(shadowMap0, lightSpacePos0, invShadowmapSize0); + shadow = pcf1; else if (depth < pssmSplitPoints.y) - shadow = depthShadowPCF(shadowMap1, lightSpacePos1, invShadowmapSize1); + shadow = pcf2; else - shadow = depthShadowPCF(shadowMap2, lightSpacePos2, invShadowmapSize2); + shadow = pcf3; return shadow; } diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 0458c4db9..7ef26d035 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -224,13 +224,14 @@ float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePosition,1)).xyz; float3 waterEyePos = float3(1,1,1); - if (worldPos.y < waterLevel) - { - // NOTE: this calculation would be wrong for non-uniform scaling - float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); - waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); - caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); - } + // NOTE: this calculation would be wrong for non-uniform scaling + float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); + waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,1,0), waterLevel); + caustics = getCaustics(causticMap, worldPos, waterEyePos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); + if (worldPos.y >= waterLevel) + caustics = float3(1,1,1); + + #endif @@ -285,7 +286,7 @@ #if SHADOWS || SHADOWS_PSSM float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; float fade = 1-((depth - shadowFar_fadeStart.y) / fadeRange); - shadow = (depth > shadowFar_fadeStart.x) ? 1 : ((depth > shadowFar_fadeStart.y) ? 1-((1-shadow)*fade) : shadow); + shadow = (depth > shadowFar_fadeStart.x) ? 1.0 : ((depth > shadowFar_fadeStart.y) ? 1.0-((1.0-shadow)*fade) : shadow); #endif #if !SHADOWS && !SHADOWS_PSSM From 26595f22f66efcb84c97b3fd7baac9eb03895e24 Mon Sep 17 00:00:00 2001 From: greye Date: Thu, 26 Jul 2012 16:14:11 +0400 Subject: [PATCH 251/298] float* -> Vector3, moveToCell -> copyToCell, fixed placeObject() --- apps/openmw/mwclass/activator.cpp | 2 +- apps/openmw/mwclass/activator.hpp | 2 +- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/apparatus.hpp | 2 +- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/armor.hpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/book.hpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/clothing.hpp | 2 +- apps/openmw/mwclass/container.cpp | 2 +- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/creature.hpp | 2 +- apps/openmw/mwclass/door.cpp | 2 +- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/ingredient.hpp | 2 +- apps/openmw/mwclass/light.cpp | 2 +- apps/openmw/mwclass/light.hpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/lockpick.hpp | 2 +- apps/openmw/mwclass/misc.cpp | 2 +- apps/openmw/mwclass/misc.hpp | 2 +- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwclass/npc.hpp | 2 +- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/potion.hpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/probe.hpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/repair.hpp | 2 +- apps/openmw/mwclass/static.cpp | 2 +- apps/openmw/mwclass/static.hpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwclass/weapon.hpp | 2 +- apps/openmw/mwworld/class.cpp | 11 +++++---- apps/openmw/mwworld/class.hpp | 6 ++--- apps/openmw/mwworld/physicssystem.cpp | 15 ++++++++++--- apps/openmw/mwworld/physicssystem.hpp | 2 +- apps/openmw/mwworld/scene.cpp | 32 +++++++++++++++------------ apps/openmw/mwworld/scene.hpp | 2 ++ apps/openmw/mwworld/worldimp.cpp | 26 +++++++++++++++------- libs/openengine/bullet/physic.cpp | 13 ++--------- libs/openengine/bullet/physic.hpp | 2 +- 45 files changed, 98 insertions(+), 83 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index d0f09e80e..9b0082efc 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -96,7 +96,7 @@ namespace MWClass } MWWorld::Ptr - Activator::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Activator::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 26aafb717..4165fbc08 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -9,7 +9,7 @@ namespace MWClass { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 6322c963e..9814b140c 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -151,7 +151,7 @@ namespace MWClass } MWWorld::Ptr - Apparatus::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Apparatus::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 9352e0617..7045f62d6 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -9,7 +9,7 @@ namespace MWClass { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index aeefcd42b..4624b94d9 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -278,7 +278,7 @@ namespace MWClass } MWWorld::Ptr - Armor::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Armor::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 960b6dad5..51c0ea21c 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -8,7 +8,7 @@ namespace MWClass class Armor : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 1517f090a..d8166347e 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -158,7 +158,7 @@ namespace MWClass } MWWorld::Ptr - Book::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Book::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index adbf2d64b..acb1aac06 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -8,7 +8,7 @@ namespace MWClass class Book : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 86c031db0..f55d6ed88 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -229,7 +229,7 @@ namespace MWClass } MWWorld::Ptr - Clothing::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Clothing::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 2c0d0b8a5..f7801848f 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -8,7 +8,7 @@ namespace MWClass class Clothing : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 2543f6acd..c6d22b3a5 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -209,7 +209,7 @@ namespace MWClass } MWWorld::Ptr - Container::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Container::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 2f15d03d8..006e4bd22 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -11,7 +11,7 @@ namespace MWClass virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 8f885bf62..0f3141f5c 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -197,7 +197,7 @@ namespace MWClass } MWWorld::Ptr - Creature::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Creature::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 38be98533..f7a5e5874 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -12,7 +12,7 @@ namespace MWClass void ensureCustomData (const MWWorld::Ptr& ptr) const; virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 8eef54725..359e50ffb 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -211,7 +211,7 @@ namespace MWClass } MWWorld::Ptr - Door::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Door::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index b89858556..b0f86f12d 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -8,7 +8,7 @@ namespace MWClass class Door : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 02e77fa25..d8c8b4b3b 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -158,7 +158,7 @@ namespace MWClass } MWWorld::Ptr - Ingredient::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Ingredient::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 44ee0ccd0..1365c4a71 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -8,7 +8,7 @@ namespace MWClass class Ingredient : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index e7b3af2b9..f09d3ce36 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -198,7 +198,7 @@ namespace MWClass } MWWorld::Ptr - Light::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Light::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 953078a35..640e1705b 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -8,7 +8,7 @@ namespace MWClass class Light : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index e72e96822..20441b520 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -167,7 +167,7 @@ namespace MWClass } MWWorld::Ptr - Lockpick::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Lockpick::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 216dc10c9..0961b55b2 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -8,7 +8,7 @@ namespace MWClass class Lockpick : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 37f608ed2..cb6d40c43 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -186,7 +186,7 @@ namespace MWClass } MWWorld::Ptr - Miscellaneous::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Miscellaneous::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::Ptr newPtr; diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 2a314758f..a5a79a8f6 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -8,7 +8,7 @@ namespace MWClass class Miscellaneous : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c81397753..81c0c85f5 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -382,7 +382,7 @@ namespace MWClass } MWWorld::Ptr - Npc::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Npc::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 46fdc9b04..e494fbaa7 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -10,7 +10,7 @@ namespace MWClass void ensureCustomData (const MWWorld::Ptr& ptr) const; virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index e0f9008c4..993dac6f6 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -162,7 +162,7 @@ namespace MWClass } MWWorld::Ptr - Potion::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Potion::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 0cb40ead0..d595f7e69 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -8,7 +8,7 @@ namespace MWClass class Potion : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 8e5fcfd19..450016209 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -166,7 +166,7 @@ namespace MWClass } MWWorld::Ptr - Probe::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Probe::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 67831d766..d9f90baf6 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -8,7 +8,7 @@ namespace MWClass class Probe : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 84d6e3b9e..829fe311a 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -148,7 +148,7 @@ namespace MWClass } MWWorld::Ptr - Repair::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Repair::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index 74d1d7378..c58e38f96 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -8,7 +8,7 @@ namespace MWClass class Repair : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index ee8bcfe81..e317b740c 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -55,7 +55,7 @@ namespace MWClass } MWWorld::Ptr - Static::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Static::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 52b87abcd..e36b3d142 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -8,7 +8,7 @@ namespace MWClass class Static : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index ccc5ff6ea..b45953130 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -367,7 +367,7 @@ namespace MWClass } MWWorld::Ptr - Weapon::moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + Weapon::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index b18405488..06cf88c5f 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -8,7 +8,7 @@ namespace MWClass class Weapon : public MWWorld::Class { virtual MWWorld::Ptr - moveToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; public: diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 6f2306fbe..6676ce1cf 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -222,27 +222,26 @@ namespace MWWorld } MWWorld::Ptr - Class::moveToCellImpl(const Ptr &ptr, CellStore &cell) const + Class::copyToCellImpl(const Ptr &ptr, CellStore &cell) const { throw std::runtime_error("unable to move class to cell"); } MWWorld::Ptr - Class::moveToCell(const Ptr &ptr, CellStore &cell) const + Class::copyToCell(const Ptr &ptr, CellStore &cell) const { - Ptr newPtr = moveToCellImpl(ptr, cell); + Ptr newPtr = copyToCellImpl(ptr, cell); newPtr.getRefData().setCount(ptr.getRefData().getCount()); - ptr.getRefData().setCount(0); newPtr.getRefData().enable(); return newPtr; } MWWorld::Ptr - Class::moveToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const + Class::copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const { - Ptr newPtr = moveToCell(ptr, cell); + Ptr newPtr = copyToCell(ptr, cell); newPtr.getRefData().getPosition() = pos; return newPtr; diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 509433433..1bc592798 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -57,7 +57,7 @@ namespace MWWorld Class(); - virtual Ptr moveToCellImpl(const Ptr &ptr, CellStore &cell) const; + virtual Ptr copyToCellImpl(const Ptr &ptr, CellStore &cell) const; public: @@ -216,10 +216,10 @@ namespace MWWorld virtual std::string getModel(const MWWorld::Ptr &ptr) const; virtual Ptr - moveToCell(const Ptr &ptr, CellStore &cell) const; + copyToCell(const Ptr &ptr, CellStore &cell) const; virtual Ptr - moveToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const; + copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const; }; } diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 27d48fa39..45cfdd123 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -382,14 +382,23 @@ namespace MWWorld addActor (node->getName(), model, node->getPosition()); } - bool PhysicsSystem::getObjectAABB(const MWWorld::Ptr &ptr, float *min, float *max) + bool PhysicsSystem::getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max) { std::string model = MWWorld::Class::get(ptr).getModel(ptr); if (model.empty()) { return false; } - float scale = ptr.getRefData().getBaseNode()->getScale().x; - mEngine->getObjectAABB(model, scale, min, max); + btVector3 btMin, btMax; + float scale = ptr.getCellRef().scale; + mEngine->getObjectAABB(model, scale, btMin, btMax); + + min.x = btMin.x(); + min.y = btMin.y(); + min.z = btMin.z(); + + max.x = btMax.x(); + max.y = btMax.y(); + max.z = btMax.z(); return true; } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 375a7c9de..e42fa536b 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -68,7 +68,7 @@ namespace MWWorld void setCurrentWater(bool hasWater, int waterHeight); - bool getObjectAABB(const MWWorld::Ptr &ptr, float *min, float *max); + bool getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max); private: OEngine::Render::OgreRenderer &mRender; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index ce0acfe47..13e5ecb85 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -334,21 +334,10 @@ namespace MWWorld void Scene::addObjectToScene (const Ptr& ptr) { - mRendering.addObject (ptr); - - float *pos = ptr.getRefData().getPosition().pos; - float min[3], max[3]; - if (mPhysics->getObjectAABB(ptr, min, max)) { - pos[0] -= (min[0] + max[0]) / 2; - pos[1] -= (min[1] + max[1]) / 2; - pos[2] -= min[2]; - } - - ptr.getRefData().getBaseNode()->setPosition(pos[0], pos[1], pos[2]); - - MWWorld::Class::get (ptr).insertObject (ptr, *mPhysics); + mRendering.addObject(ptr); + MWWorld::Class::get(ptr).insertObject(ptr, *mPhysics); } - + void Scene::removeObjectFromScene (const Ptr& ptr) { MWBase::Environment::get().getMechanicsManager()->removeActor (ptr); @@ -356,4 +345,19 @@ namespace MWWorld mPhysics->removeObject (ptr.getRefData().getHandle()); mRendering.removeObject (ptr); } + + bool Scene::isCellActive(const CellStore &cell) + { + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active != mActiveCells.end()) { + if ((*active)->cell->name == cell.cell->name && + (*active)->cell->data.gridX == cell.cell->data.gridX && + (*active)->cell->data.gridY == cell.cell->data.gridY) + { + return true; + } + ++active; + } + return false; + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index f5f4b640b..59e13dafe 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -95,6 +95,8 @@ namespace MWWorld void removeObjectFromScene (const Ptr& ptr); ///< Remove an object from the scene, but not from the world model. + + bool isCellActive(const CellStore &cell); }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b9ab6ade2..59f6d7e86 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1022,6 +1022,7 @@ namespace MWWorld pos.pos[2] = result.second[1]; placeObject(object, *cell, pos); + object.getRefData().setCount(0); return true; } @@ -1040,16 +1041,25 @@ namespace MWWorld void World::placeObject(const Ptr &object, CellStore &cell, const ESM::Position &pos) { - mLocalScripts.remove(object); - + /// \todo add searching correct cell for position specified MWWorld::Ptr dropped = - MWWorld::Class::get(object).moveToCell(object, cell, pos); + MWWorld::Class::get(object).copyToCell(object, cell, pos); + + Ogre::Vector3 min, max; + if (mPhysics->getObjectAABB(object, min, max)) { + float *pos = dropped.getRefData().getPosition().pos; + pos[0] -= (min.x + max.x) / 2; + pos[1] -= (min.y + max.y) / 2; + pos[2] -= min.z; + } - mWorldScene->addObjectToScene(dropped); + if (mWorldScene->isCellActive(cell)) { + mWorldScene->addObjectToScene(dropped); - std::string script = MWWorld::Class::get(dropped).getScript(dropped); - if (!script.empty()) { - mLocalScripts.add(script, dropped); + std::string script = MWWorld::Class::get(dropped).getScript(dropped); + if (!script.empty()) { + mLocalScripts.add(script, dropped); + } } } @@ -1071,8 +1081,8 @@ namespace MWWorld mPhysics->castRay(orig, dir, len); pos.pos[2] = hit.second.z; - /// \todo fix item dropping at player object center position placeObject(object, *cell, pos); + object.getRefData().setCount(0); } void World::processChangedSettings(const Settings::CategorySettingVector& settings) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index d4b63676d..089825a9d 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -569,7 +569,7 @@ namespace Physic return results2; } - void PhysicEngine::getObjectAABB(const std::string &mesh, float scale, float *min, float *max) + void PhysicEngine::getObjectAABB(const std::string &mesh, float scale, btVector3 &min, btVector3 &max) { char uniqueID[8]; sprintf( uniqueID, "%07.3f", scale ); @@ -582,17 +582,8 @@ namespace Physic BulletShapeManager::getSingleton().getByName(outputstring, "General"); btTransform trans; - btVector3 btmin, btmax; - trans.setIdentity(); - shape->Shape->getAabb(trans, btmin, btmax); - - min[0] = btmin.x(); - min[1] = btmin.y(); - min[2] = btmin.z(); - max[0] = btmax.x(); - max[1] = btmax.y(); - max[2] = btmax.z(); + shape->Shape->getAabb(trans, min, max); } }}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 8701adc84..9ae8e7607 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -221,7 +221,7 @@ namespace Physic bool toggleDebugRendering(); - void getObjectAABB(const std::string &mesh, float scale, float *min, float *max); + void getObjectAABB(const std::string &mesh, float scale, btVector3 &min, btVector3 &max); /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). From aa5f63ffcc2f3bc52b27c13e84f938f303b98a34 Mon Sep 17 00:00:00 2001 From: greye Date: Thu, 26 Jul 2012 19:06:48 +0400 Subject: [PATCH 252/298] disabled items placing handling --- apps/openmw/mwworld/class.cpp | 6 +++++- apps/openmw/mwworld/worldimp.cpp | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 6676ce1cf..bb9b7d982 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -233,7 +233,11 @@ namespace MWWorld Ptr newPtr = copyToCellImpl(ptr, cell); newPtr.getRefData().setCount(ptr.getRefData().getCount()); - newPtr.getRefData().enable(); + if (ptr.getRefData().isEnabled()) { + newPtr.getRefData().enable(); + } else { + newPtr.getRefData().disable(); + } return newPtr; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 59f6d7e86..67d8a7cec 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1054,8 +1054,9 @@ namespace MWWorld } if (mWorldScene->isCellActive(cell)) { - mWorldScene->addObjectToScene(dropped); - + if (dropped.getRefData().isEnabled()) { + mWorldScene->addObjectToScene(dropped); + } std::string script = MWWorld::Class::get(dropped).getScript(dropped); if (!script.empty()) { mLocalScripts.add(script, dropped); From 620b6bf27b8605d69bf5aae24f8d7bb66c72ef67 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jul 2012 17:08:39 +0200 Subject: [PATCH 253/298] change url to work without publickey --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index f53c97677..d2a4cf0d3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "extern/shiny"] path = extern/shiny - url = git@github.com:scrawl/shiny.git + url = git://github.com/scrawl/shiny.git From d5e63a767ec05360cc7fdd974fe486140a1fa3cf Mon Sep 17 00:00:00 2001 From: greye Date: Thu, 26 Jul 2012 19:38:33 +0400 Subject: [PATCH 254/298] replace sprintf() with boost::format --- libs/openengine/bullet/physic.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 089825a9d..a778aef3a 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -11,6 +11,7 @@ #include "BtOgreExtras.h" #include +#include #define BIT(x) (1<<(x)) @@ -333,10 +334,8 @@ namespace Physic RigidBody* PhysicEngine::createRigidBody(std::string mesh,std::string name,float scale) { - char uniqueID[8]; - sprintf( uniqueID, "%07.3f", scale ); - std::string sid = uniqueID; - std::string outputstring = mesh + uniqueID; + std::string sid = (boost::format("%07.3f") % scale).str(); + std::string outputstring = mesh + sid; //std::cout << "The string" << outputstring << "\n"; //get the shape from the .nif @@ -571,10 +570,8 @@ namespace Physic void PhysicEngine::getObjectAABB(const std::string &mesh, float scale, btVector3 &min, btVector3 &max) { - char uniqueID[8]; - sprintf( uniqueID, "%07.3f", scale ); - std::string sid = uniqueID; - std::string outputstring = mesh + uniqueID; + std::string sid = (boost::format("%07.3f") % scale).str(); + std::string outputstring = mesh + sid; mShapeLoader->load(outputstring, "General"); BulletShapeManager::getSingletonPtr()->load(outputstring, "General"); From 7725190f89dec2f67c3e3ad5412584d575263192 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 26 Jul 2012 19:09:45 +0200 Subject: [PATCH 255/298] removed some redundant code --- apps/openmw/mwworld/class.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index bb9b7d982..5267e368d 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -232,13 +232,6 @@ namespace MWWorld { Ptr newPtr = copyToCellImpl(ptr, cell); - newPtr.getRefData().setCount(ptr.getRefData().getCount()); - if (ptr.getRefData().isEnabled()) { - newPtr.getRefData().enable(); - } else { - newPtr.getRefData().disable(); - } - return newPtr; } @@ -251,4 +244,3 @@ namespace MWWorld return newPtr; } } - From 6077965d27c036b7a1aad512490db253aa4251ce Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jul 2012 23:09:46 +0200 Subject: [PATCH 256/298] fix the directx startup issue on windows --- apps/openmw/mwrender/renderingmanager.cpp | 1 + extern/shiny | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bee4bd68a..ae0e57219 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -72,6 +72,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const else lang = sh::Language_CG; mFactory->setCurrentLanguage (lang); + mFactory->loadAllFiles(); //The fog type must be set before any terrain objects are created as if the //fog type is set to FOG_NONE then the initially created terrain won't have any fog diff --git a/extern/shiny b/extern/shiny index 5546a5bd8..164bc8d3b 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 5546a5bd8474ef328dfedae2df42126cdaf9515f +Subproject commit 164bc8d3bfe860bd16ad89c0bd1b59f465c9bb24 From 281e15f58e0a290b2f8c79135f32d5315fe17ebd Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jul 2012 23:40:55 +0200 Subject: [PATCH 257/298] consider all material properties for nif material sharing, instead of just the texture --- components/nifogre/ogre_nif_loader.cpp | 47 ++++++++++++++++---------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 803300282..4393749fb 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -39,6 +39,7 @@ #include #include +#include #include @@ -441,7 +442,7 @@ static CompareFunction getTestMode(int mode) class NIFMaterialLoader { -static std::multimap MaterialMap; +static std::map MaterialMap; static void warn(const std::string &msg) { @@ -473,6 +474,8 @@ static Ogre::String getMaterial(const NiTriShape *shape, const Ogre::String &nam ubyte alphaTest = 0; Ogre::String texName; + bool vertexColour = (shape->data->colors.size() != 0); + // These are set below if present const NiTexturingProperty *t = NULL; const NiMaterialProperty *m = NULL; @@ -537,23 +540,32 @@ static Ogre::String getMaterial(const NiTriShape *shape, const Ogre::String &nam Ogre::String matname = name; if (m || !texName.empty()) { - // If we're here, then this mesh has a material. Thus we - // need to calculate a snappy material name. It should - // contain the mesh name (mesh->getName()) but also has to - // be unique. One mesh may use many materials. - std::multimap::iterator itr = MaterialMap.find(texName); - std::multimap::iterator lastElement; - lastElement = MaterialMap.upper_bound(texName); + // Generate a hash out of all properties that can affect the material. + size_t h = 0; + boost::hash_combine(h, ambient.x); + boost::hash_combine(h, ambient.y); + boost::hash_combine(h, ambient.z); + boost::hash_combine(h, diffuse.x); + boost::hash_combine(h, diffuse.y); + boost::hash_combine(h, diffuse.z); + boost::hash_combine(h, specular.x); + boost::hash_combine(h, specular.y); + boost::hash_combine(h, specular.z); + boost::hash_combine(h, emissive.x); + boost::hash_combine(h, emissive.y); + boost::hash_combine(h, emissive.z); + boost::hash_combine(h, texName); + boost::hash_combine(h, vertexColour); + boost::hash_combine(h, alphaFlags); + + std::map::iterator itr = MaterialMap.find(h); if (itr != MaterialMap.end()) { - for ( ; itr != lastElement; ++itr) - { - //std::cout << "OK!"; - //MaterialPtr mat = MaterialManager::getSingleton().getByName(itr->second,recourceGroup); - return itr->second; - //if( mat->getA - } + // a suitable material exists already - use it + return itr->second; } + // not found, create a new one + MaterialMap.insert(std::make_pair(h, matname)); } // No existing material like this. Create a new one. @@ -572,7 +584,7 @@ static Ogre::String getMaterial(const NiTriShape *shape, const Ogre::String &nam instance->setProperty ("diffuseMap", sh::makeProperty(texName)); - if (shape->data->colors.size() != 0) + if (vertexColour) instance->setProperty ("has_vertex_colour", sh::makeProperty(new sh::BooleanValue(true))); // Add transparency if NiAlphaProperty was present @@ -628,12 +640,11 @@ static Ogre::String getMaterial(const NiTriShape *shape, const Ogre::String &nam } */ - MaterialMap.insert(std::make_pair(texName, matname)); return matname; } }; -std::multimap NIFMaterialLoader::MaterialMap; +std::map NIFMaterialLoader::MaterialMap; class NIFMeshLoader : Ogre::ManualResourceLoader From 78fe6fdce5a837b6f8c2eb31a42390d99fdc25e5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Jul 2012 12:00:10 +0200 Subject: [PATCH 258/298] Issue #351: Refactoring Action class --- apps/openmw/mwclass/door.cpp | 6 +++--- apps/openmw/mwclass/potion.cpp | 5 ++--- apps/openmw/mwgui/bookwindow.cpp | 4 +++- apps/openmw/mwgui/inventorywindow.cpp | 2 +- apps/openmw/mwgui/scrollwindow.cpp | 4 +++- apps/openmw/mwscript/interpretercontext.cpp | 3 ++- apps/openmw/mwworld/action.cpp | 11 +++++++++++ apps/openmw/mwworld/action.hpp | 10 +++++++--- apps/openmw/mwworld/actionalchemy.cpp | 2 +- apps/openmw/mwworld/actionalchemy.hpp | 3 +-- apps/openmw/mwworld/actionapply.cpp | 18 +++++++++--------- apps/openmw/mwworld/actionapply.hpp | 14 ++++++-------- apps/openmw/mwworld/actionequip.cpp | 2 +- apps/openmw/mwworld/actionequip.hpp | 4 ++-- apps/openmw/mwworld/actionopen.cpp | 2 +- apps/openmw/mwworld/actionopen.hpp | 3 ++- apps/openmw/mwworld/actionread.cpp | 2 +- apps/openmw/mwworld/actionread.hpp | 4 ++-- apps/openmw/mwworld/actiontake.cpp | 2 +- apps/openmw/mwworld/actiontake.hpp | 4 ++-- apps/openmw/mwworld/actiontalk.cpp | 2 +- apps/openmw/mwworld/actiontalk.hpp | 4 ++-- apps/openmw/mwworld/actionteleport.cpp | 4 ++-- apps/openmw/mwworld/actionteleport.hpp | 8 ++++---- apps/openmw/mwworld/nullaction.hpp | 4 +--- 25 files changed, 71 insertions(+), 56 deletions(-) create mode 100644 apps/openmw/mwworld/action.cpp diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 359e50ffb..6939c356e 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -40,7 +40,7 @@ namespace MWClass physics.insertObjectPhysics(ptr, model); } } - + std::string Door::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = @@ -97,18 +97,18 @@ namespace MWClass if (ref->ref.teleport) { // teleport door + /// \todo remove this if clause once ActionTeleport can also support other actors if (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()==actor) { // the player is using the door // The reason this is not 3D is that it would get interrupted when you teleport MWBase::Environment::get().getSoundManager()->playSound(openSound, 1.0, 1.0); return boost::shared_ptr ( - new MWWorld::ActionTeleportPlayer (ref->ref.destCell, ref->ref.doorDest)); + new MWWorld::ActionTeleport (ref->ref.destCell, ref->ref.doorDest)); } else { // another NPC or a creature is using the door - // TODO return action for teleporting other NPC/creature return boost::shared_ptr (new MWWorld::NullAction); } } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 993dac6f6..3d370ac32 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -40,7 +40,7 @@ namespace MWClass physics.insertObjectPhysics(ptr, model); } } - + std::string Potion::getModel(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = @@ -158,7 +158,7 @@ namespace MWClass MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); return boost::shared_ptr ( - new MWWorld::ActionApply (actor, ref->base->mId, actor)); + new MWWorld::ActionApply (actor, ref->base->mId)); } MWWorld::Ptr @@ -170,4 +170,3 @@ namespace MWClass return MWWorld::Ptr(&cell.potions.insert(*ref), &cell); } } - diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 1ea3da839..1e0301d8e 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -3,9 +3,11 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwinput/inputmanager.hpp" #include "../mwsound/soundmanager.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/player.hpp" #include "formatting.hpp" #include "window_manager.hpp" @@ -99,7 +101,7 @@ void BookWindow::onTakeButtonClicked (MyGUI::Widget* _sender) MWBase::Environment::get().getSoundManager()->playSound ("Item Book Up", 1.0, 1.0, MWSound::Play_NoTrack); MWWorld::ActionTake take(mBook); - take.execute(); + take.execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); mWindowManager.removeGuiMode(GM_Book); } diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 23a96b1d3..926c35172 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -175,7 +175,7 @@ namespace MWGui boost::shared_ptr action = MWWorld::Class::get(ptr).use(ptr); - action->execute(); + action->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); // this is necessary for books/scrolls: if they are already in the player's inventory, // the "Take" button should not be visible. diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index e6ff71a14..00e5a01dc 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -1,8 +1,10 @@ #include "scrollwindow.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwinput/inputmanager.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/player.hpp" #include "../mwsound/soundmanager.hpp" #include "formatting.hpp" @@ -63,7 +65,7 @@ void ScrollWindow::onTakeButtonClicked (MyGUI::Widget* _sender) MWBase::Environment::get().getSoundManager()->playSound ("Item Book Up", 1.0, 1.0, MWSound::Play_NoTrack); MWWorld::ActionTake take(mScroll); - take.execute(); + take.execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); mWindowManager.removeGuiMode(GM_Scroll); } diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 5da512778..3a824473e 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -10,6 +10,7 @@ #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "../mwgui/window_manager.hpp" @@ -236,7 +237,7 @@ namespace MWScript if (!mAction.get()) throw std::runtime_error ("activation failed, because no action to perform"); - mAction->execute(); + mAction->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); mActivationHandled = true; } diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp new file mode 100644 index 000000000..0fcbf4a6f --- /dev/null +++ b/apps/openmw/mwworld/action.cpp @@ -0,0 +1,11 @@ + +#include "action.hpp" + +MWWorld::Action::Action() {} + +MWWorld::Action::~Action() {} + +void MWWorld::Action::execute (const Ptr& actor) +{ + executeImp (actor); +} diff --git a/apps/openmw/mwworld/action.hpp b/apps/openmw/mwworld/action.hpp index d5cf12779..c95648135 100644 --- a/apps/openmw/mwworld/action.hpp +++ b/apps/openmw/mwworld/action.hpp @@ -3,6 +3,8 @@ namespace MWWorld { + class Ptr; + /// \brief Abstract base for actions class Action { @@ -10,13 +12,15 @@ namespace MWWorld Action (const Action& action); Action& operator= (const Action& action); + virtual void executeImp (const Ptr& actor) = 0; + public: - Action() {} + Action(); - virtual ~Action() {} + virtual ~Action(); - virtual void execute() = 0; + void execute (const Ptr& actor); }; } diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp index eb91b6946..a7ee4fd0e 100644 --- a/apps/openmw/mwworld/actionalchemy.cpp +++ b/apps/openmw/mwworld/actionalchemy.cpp @@ -5,7 +5,7 @@ namespace MWWorld { - void ActionAlchemy::execute() + void ActionAlchemy::executeImp (const Ptr& actor) { MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Alchemy); } diff --git a/apps/openmw/mwworld/actionalchemy.hpp b/apps/openmw/mwworld/actionalchemy.hpp index 739de419b..e6d1a7976 100644 --- a/apps/openmw/mwworld/actionalchemy.hpp +++ b/apps/openmw/mwworld/actionalchemy.hpp @@ -7,8 +7,7 @@ namespace MWWorld { class ActionAlchemy : public Action { - public: - virtual void execute (); + virtual void executeImp (const Ptr& actor); }; } diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index b330a70e7..595ee6cb3 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -5,24 +5,24 @@ namespace MWWorld { - ActionApply::ActionApply (const Ptr& target, const std::string& id, const Ptr& actor) - : mTarget (target), mId (id), mActor (actor) + ActionApply::ActionApply (const Ptr& target, const std::string& id) + : mTarget (target), mId (id) {} - void ActionApply::execute() + void ActionApply::executeImp (const Ptr& actor) { - MWWorld::Class::get (mTarget).apply (mTarget, mId, mActor); + MWWorld::Class::get (mTarget).apply (mTarget, mId, actor); } ActionApplyWithSkill::ActionApplyWithSkill (const Ptr& target, const std::string& id, - const Ptr& actor, int skillIndex, int usageType) - : mTarget (target), mId (id), mActor (actor), mSkillIndex (skillIndex), mUsageType (usageType) + int skillIndex, int usageType) + : mTarget (target), mId (id), mSkillIndex (skillIndex), mUsageType (usageType) {} - void ActionApplyWithSkill::execute() + void ActionApplyWithSkill::executeImp (const Ptr& actor) { - if (MWWorld::Class::get (mTarget).apply (mTarget, mId, mActor) && mUsageType!=-1) - MWWorld::Class::get (mTarget).skillUsageSucceeded (mActor, mSkillIndex, mUsageType); + if (MWWorld::Class::get (mTarget).apply (mTarget, mId, actor) && mUsageType!=-1) + MWWorld::Class::get (mTarget).skillUsageSucceeded (actor, mSkillIndex, mUsageType); } } diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp index 972417e02..523bf9373 100644 --- a/apps/openmw/mwworld/actionapply.hpp +++ b/apps/openmw/mwworld/actionapply.hpp @@ -13,29 +13,27 @@ namespace MWWorld { Ptr mTarget; std::string mId; - Ptr mActor; - public: + virtual void executeImp (const Ptr& actor); - ActionApply (const Ptr& target, const std::string& id, const Ptr& actor); + public: - virtual void execute(); + ActionApply (const Ptr& target, const std::string& id); }; class ActionApplyWithSkill : public Action { Ptr mTarget; std::string mId; - Ptr mActor; int mSkillIndex; int mUsageType; + virtual void executeImp (const Ptr& actor); + public: - ActionApplyWithSkill (const Ptr& target, const std::string& id, const Ptr& actor, + ActionApplyWithSkill (const Ptr& target, const std::string& id, int skillIndex, int usageType); - - virtual void execute(); }; } diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 52b9437fd..20e0afb68 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -13,7 +13,7 @@ namespace MWWorld { } - void ActionEquip::execute () + void ActionEquip::executeImp (const Ptr& actor) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWWorld::InventoryStore& invStore = MWWorld::Class::get(player).getInventoryStore(player); diff --git a/apps/openmw/mwworld/actionequip.hpp b/apps/openmw/mwworld/actionequip.hpp index 6cf3640f8..5685a294a 100644 --- a/apps/openmw/mwworld/actionequip.hpp +++ b/apps/openmw/mwworld/actionequip.hpp @@ -10,11 +10,11 @@ namespace MWWorld { Ptr mObject; + virtual void executeImp (const Ptr& actor); + public: /// @param item to equip ActionEquip (const Ptr& object); - - virtual void execute (); }; } diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp index a70773af9..c73ef9149 100644 --- a/apps/openmw/mwworld/actionopen.cpp +++ b/apps/openmw/mwworld/actionopen.cpp @@ -15,7 +15,7 @@ namespace MWWorld mContainer = container; } - void ActionOpen::execute () + void ActionOpen::executeImp (const MWWorld::Ptr& actor) { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return; diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index eff26c78c..5666ff293 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -12,10 +12,11 @@ namespace MWWorld { Ptr mContainer; + virtual void executeImp (const MWWorld::Ptr& actor); + public: ActionOpen (const Ptr& container); ///< \param The Container the Player has activated. - virtual void execute (); }; } diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index 1e03230c7..c81d79e03 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -11,7 +11,7 @@ namespace MWWorld { } - void ActionRead::execute () + void ActionRead::executeImp (const MWWorld::Ptr& actor) { LiveCellRef *ref = mObject.get(); diff --git a/apps/openmw/mwworld/actionread.hpp b/apps/openmw/mwworld/actionread.hpp index a4b495f79..9bb74fb88 100644 --- a/apps/openmw/mwworld/actionread.hpp +++ b/apps/openmw/mwworld/actionread.hpp @@ -10,11 +10,11 @@ namespace MWWorld { Ptr mObject; // book or scroll to read + virtual void executeImp (const MWWorld::Ptr& actor); + public: /// @param book or scroll to read ActionRead (const Ptr& object); - - virtual void execute (); }; } diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index 39544b35d..5207f1a10 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -13,7 +13,7 @@ namespace MWWorld { ActionTake::ActionTake (const MWWorld::Ptr& object) : mObject (object) {} - void ActionTake::execute() + void ActionTake::executeImp (const Ptr& actor) { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return; diff --git a/apps/openmw/mwworld/actiontake.hpp b/apps/openmw/mwworld/actiontake.hpp index f495fc3c4..52a114acf 100644 --- a/apps/openmw/mwworld/actiontake.hpp +++ b/apps/openmw/mwworld/actiontake.hpp @@ -10,11 +10,11 @@ namespace MWWorld { MWWorld::Ptr mObject; + virtual void executeImp (const Ptr& actor); + public: ActionTake (const MWWorld::Ptr& object); - - virtual void execute(); }; } diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index a0f9d8c4c..78171bbe5 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -9,7 +9,7 @@ namespace MWWorld { ActionTalk::ActionTalk (const Ptr& actor) : mActor (actor) {} - void ActionTalk::execute() + void ActionTalk::executeImp (const Ptr& actor) { MWBase::Environment::get().getDialogueManager()->startDialogue (mActor); } diff --git a/apps/openmw/mwworld/actiontalk.hpp b/apps/openmw/mwworld/actiontalk.hpp index 1b7b9b6d6..53adf9e53 100644 --- a/apps/openmw/mwworld/actiontalk.hpp +++ b/apps/openmw/mwworld/actiontalk.hpp @@ -10,12 +10,12 @@ namespace MWWorld { Ptr mActor; + virtual void executeImp (const Ptr& actor); + public: ActionTalk (const Ptr& actor); ///< \param actor The actor the player is talking to - - virtual void execute(); }; } diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 8ae3244f8..9c87d37ae 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -6,12 +6,12 @@ namespace MWWorld { - ActionTeleportPlayer::ActionTeleportPlayer (const std::string& cellName, + ActionTeleport::ActionTeleport (const std::string& cellName, const ESM::Position& position) : mCellName (cellName), mPosition (position) {} - void ActionTeleportPlayer::execute() + void ActionTeleport::executeImp (const Ptr& actor) { if (mCellName.empty()) MWBase::Environment::get().getWorld()->changeToExteriorCell (mPosition); diff --git a/apps/openmw/mwworld/actionteleport.hpp b/apps/openmw/mwworld/actionteleport.hpp index 00efdc876..a13cb61b2 100644 --- a/apps/openmw/mwworld/actionteleport.hpp +++ b/apps/openmw/mwworld/actionteleport.hpp @@ -9,17 +9,17 @@ namespace MWWorld { - class ActionTeleportPlayer : public Action + class ActionTeleport : public Action { std::string mCellName; ESM::Position mPosition; + virtual void executeImp (const Ptr& actor); + public: - ActionTeleportPlayer (const std::string& cellName, const ESM::Position& position); + ActionTeleport (const std::string& cellName, const ESM::Position& position); ///< If cellName is empty, an exterior cell is asumed. - - virtual void execute(); }; } diff --git a/apps/openmw/mwworld/nullaction.hpp b/apps/openmw/mwworld/nullaction.hpp index c8e3368e4..7ef8b4a06 100644 --- a/apps/openmw/mwworld/nullaction.hpp +++ b/apps/openmw/mwworld/nullaction.hpp @@ -8,9 +8,7 @@ namespace MWWorld /// \brief Action: do nothing class NullAction : public Action { - public: - - virtual void execute() {} + virtual void executeImp (const Ptr& actor) {} }; } From 8b08928dae542758bdf59eb423cedf8b31e1082f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Jul 2012 12:19:25 +0200 Subject: [PATCH 259/298] Issue #351: Added sound playing to Action --- apps/openmw/mwworld/action.cpp | 13 +++++++++++++ apps/openmw/mwworld/action.hpp | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp index 0fcbf4a6f..0a57d5f67 100644 --- a/apps/openmw/mwworld/action.cpp +++ b/apps/openmw/mwworld/action.cpp @@ -1,11 +1,24 @@ #include "action.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwsound/soundmanager.hpp" + MWWorld::Action::Action() {} MWWorld::Action::~Action() {} void MWWorld::Action::execute (const Ptr& actor) { + if (!mSoundId.empty()) + MWBase::Environment::get().getSoundManager()->playSound3D (actor, mSoundId, 1.0, 1.0, + MWSound::Play_NoTrack); + executeImp (actor); } + +void MWWorld::Action::setSound (const std::string& id) +{ + mSoundId = id; +} diff --git a/apps/openmw/mwworld/action.hpp b/apps/openmw/mwworld/action.hpp index c95648135..a00f67951 100644 --- a/apps/openmw/mwworld/action.hpp +++ b/apps/openmw/mwworld/action.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWWORLD_ACTION_H #define GAME_MWWORLD_ACTION_H +#include + namespace MWWorld { class Ptr; @@ -8,6 +10,8 @@ namespace MWWorld /// \brief Abstract base for actions class Action { + std::string mSoundId; + // not implemented Action (const Action& action); Action& operator= (const Action& action); @@ -21,6 +25,8 @@ namespace MWWorld virtual ~Action(); void execute (const Ptr& actor); + + void setSound (const std::string& id); }; } From 0da3f2cb594abf9bd41698427b1c831d8a6a4dd8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Jul 2012 12:27:22 +0200 Subject: [PATCH 260/298] Issue #314: Playing drink sound when using a potion --- apps/openmw/mwclass/potion.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 3d370ac32..d3f2912ea 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -157,8 +157,12 @@ namespace MWClass MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - return boost::shared_ptr ( + boost::shared_ptr action ( new MWWorld::ActionApply (actor, ref->base->mId)); + + action->setSound ("Drink"); + + return action; } MWWorld::Ptr From fbe9a94568d60b9742ab9216d803de18f2912625 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 28 Jul 2012 01:53:50 +0400 Subject: [PATCH 261/298] bug #348: fixed OS X deployment just enable CMake option "OPENMW_OSX_DEPLOYMENT" and it will search plugins inside application bundle instead of Ogre prefix --- CMakeLists.txt | 17 +++++++------ apps/launcher/graphicspage.cpp | 19 ++++++-------- components/CMakeLists.txt | 4 +++ components/ogreplugin/ogreplugin.cpp | 37 ++++++++++++++++++++++++++++ components/ogreplugin/ogreplugin.h | 12 +++++++++ libs/openengine/ogre/renderer.cpp | 27 ++++++++------------ 6 files changed, 81 insertions(+), 35 deletions(-) create mode 100644 components/ogreplugin/ogreplugin.cpp create mode 100644 components/ogreplugin/ogreplugin.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 05c53e6d6..0bc6344c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,9 @@ option(USE_FFMPEG "use ffmpeg for sound" OFF) option(USE_AUDIERE "use audiere for sound" OFF) option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) +# OS X deployment +option(OPENMW_OSX_DEPLOYMENT OFF) + find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") # Location of morrowind data files @@ -241,12 +244,8 @@ if (APPLE) else () set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_DBG}) endif () - - set(OGRE_PLUGIN_DIR "${OGRE_PLUGIN_DIR}/") - -# set(OGRE_PLUGIN_DIR_2 ${OGRE_PLUGIN_DIR}) -# set(OGRE_PLUGIN_DIR "") -# set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_2}) + + #set(OGRE_PLUGIN_DIR "${OGRE_PLUGIN_DIR}/") configure_file(${OpenMW_SOURCE_DIR}/files/mac/Info.plist "${APP_BUNDLE_DIR}/Contents/Info.plist") @@ -270,7 +269,11 @@ if (DEFINED CMAKE_BUILD_TYPE) endif() add_definitions(-DOGRE_PLUGIN_DIR_REL="${OGRE_PLUGIN_DIR_REL}") add_definitions(-DOGRE_PLUGIN_DIR_DBG="${OGRE_PLUGIN_DIR_DBG}") -add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}") +if (APPLE AND OPENMW_OSX_DEPLOYMENT) + add_definitions(-DOGRE_PLUGIN_DIR="${APP_BUNDLE_NAME}/Contents/Plugins") +else() + add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}") +endif() add_subdirectory(files/) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 296ef2c3e..f9f5c6dda 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "graphicspage.hpp" #include "naturalsort.hpp" @@ -115,17 +116,13 @@ bool GraphicsPage::setupOgre() #endif } - std::string glPlugin = std::string(pluginDir) + "/RenderSystem_GL" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(glPlugin + ".so") || - boost::filesystem::exists(glPlugin + ".dll") || - boost::filesystem::exists(glPlugin + ".dylib")) - mOgre->loadPlugin (glPlugin); - - std::string dxPlugin = std::string(pluginDir) + "/RenderSystem_Direct3D9" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(dxPlugin + ".so") || - boost::filesystem::exists(dxPlugin + ".dll") || - boost::filesystem::exists(dxPlugin + ".dylib")) - mOgre->loadPlugin (dxPlugin); + boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir)); + + pluginDir = absPluginPath.string(); + + loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre); + loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mOgre); + loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre); #ifdef ENABLE_PLUGIN_GL mGLPlugin = new Ogre::GLPlugin(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index efeb69cae..e16a860c4 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -66,6 +66,10 @@ add_component_dir (interpreter miscopcodes opcodes runtime scriptopcodes spatialopcodes types ) +add_component_dir (ogreplugin + ogreplugin +) + include_directories(${BULLET_INCLUDE_DIRS}) add_library(components STATIC ${COMPONENT_FILES}) diff --git a/components/ogreplugin/ogreplugin.cpp b/components/ogreplugin/ogreplugin.cpp new file mode 100644 index 000000000..5516482aa --- /dev/null +++ b/components/ogreplugin/ogreplugin.cpp @@ -0,0 +1,37 @@ +#include "ogreplugin.h" + +#include +#include + +#include + +bool loadOgrePlugin(std::string pluginDir, std::string pluginName, Ogre::Root &ogreRoot) { +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + std::ostringstream verStream; + verStream << "." << OGRE_VERSION_MAJOR << "." << OGRE_VERSION_MINOR << "." << OGRE_VERSION_PATCH; + pluginName = pluginName + OGRE_PLUGIN_DEBUG_SUFFIX + verStream.str(); +#endif + + std::string pluginExt; +#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + pluginExt = ".dll"; +#endif +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + pluginExt = ".dylib"; +#endif +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX + pluginExt = ".so"; +#endif + + std::string pluginPath = pluginDir + "/" + pluginName + pluginExt; + + std::cout << "loading plugin: " << pluginPath << std::endl; + + if (boost::filesystem::exists(pluginPath)) { + ogreRoot.loadPlugin(pluginPath); + return true; + } + else { + return false; + } +} \ No newline at end of file diff --git a/components/ogreplugin/ogreplugin.h b/components/ogreplugin/ogreplugin.h new file mode 100644 index 000000000..bb1ea04e1 --- /dev/null +++ b/components/ogreplugin/ogreplugin.h @@ -0,0 +1,12 @@ +#ifndef OGREPLUGIN_H +#define OGREPLUGIN_H + +#include + +namespace Ogre { + class Root; +} + +extern bool loadOgrePlugin(std::string pluginDir, std::string pluginName, Ogre::Root &ogreRoot); + +#endif \ No newline at end of file diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 58b363f7f..20bb1f40f 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -11,6 +11,8 @@ #include +#include + #include #include #include @@ -94,6 +96,7 @@ void OgreRenderer::configure(const std::string &logPath, loadPlugins(); #endif + std::cout << "current path is: " << boost::filesystem::current_path() << std::endl; std::string pluginDir; const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); if (pluginEnv) @@ -111,23 +114,13 @@ void OgreRenderer::configure(const std::string &logPath, #endif } - std::string glPlugin = std::string(pluginDir) + "/RenderSystem_GL" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(glPlugin + ".so") || - boost::filesystem::exists(glPlugin + ".dll") || - boost::filesystem::exists(glPlugin + ".dylib")) - mRoot->loadPlugin (glPlugin); - - std::string dxPlugin = std::string(pluginDir) + "/RenderSystem_Direct3D9" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(dxPlugin + ".so") || - boost::filesystem::exists(dxPlugin + ".dll") || - boost::filesystem::exists(dxPlugin + ".dylib")) - mRoot->loadPlugin (dxPlugin); - - std::string cgPlugin = std::string(pluginDir) + "/Plugin_CgProgramManager" + OGRE_PLUGIN_DEBUG_SUFFIX; - if (boost::filesystem::exists(cgPlugin + ".so") || - boost::filesystem::exists(cgPlugin + ".dll") || - boost::filesystem::exists(cgPlugin + ".dylib")) - mRoot->loadPlugin (cgPlugin); + boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir)); + + pluginDir = absPluginPath.string(); + + loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); + loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); + loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); if (rs == 0) From e9d4195500287266ee9a6e78006dae4141e3d7ea Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 28 Jul 2012 02:03:34 +0400 Subject: [PATCH 262/298] removed cout spam --- libs/openengine/ogre/renderer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 20bb1f40f..f8fed3899 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -96,7 +96,6 @@ void OgreRenderer::configure(const std::string &logPath, loadPlugins(); #endif - std::cout << "current path is: " << boost::filesystem::current_path() << std::endl; std::string pluginDir; const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); if (pluginEnv) From 0cdb651c5ece1bf5aebf55b9cd8d14ec1fbaf0b4 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 28 Jul 2012 02:05:06 +0400 Subject: [PATCH 263/298] removed excess invocation --- apps/launcher/graphicspage.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index f9f5c6dda..7d25f2df1 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -122,7 +122,6 @@ bool GraphicsPage::setupOgre() loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre); loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mOgre); - loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre); #ifdef ENABLE_PLUGIN_GL mGLPlugin = new Ogre::GLPlugin(); From a0a086f69d7ea7feb35452b3475d96435126a8e7 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 28 Jul 2012 02:11:14 +0400 Subject: [PATCH 264/298] fixed redefining macro --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bc6344c4..cbb895760 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -263,8 +263,6 @@ if (DEFINED CMAKE_BUILD_TYPE) if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT APPLE) set(DEBUG_SUFFIX "_d") add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") - else() - add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="") endif() endif() add_definitions(-DOGRE_PLUGIN_DIR_REL="${OGRE_PLUGIN_DIR_REL}") From 23e44a86c6c7be2b6373df3a0f0afefef8ec7ca7 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 28 Jul 2012 02:13:57 +0400 Subject: [PATCH 265/298] another attempt to fix --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cbb895760..b51ea68ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,12 +257,13 @@ endif (APPLE) # Set up Ogre plugin folder & debug suffix set(DEBUG_SUFFIX "") -add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="") if (DEFINED CMAKE_BUILD_TYPE) # Ogre on OS X doesn't use "_d" suffix (see Ogre's CMakeLists.txt) if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT APPLE) set(DEBUG_SUFFIX "_d") add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") + else() + add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="") endif() endif() add_definitions(-DOGRE_PLUGIN_DIR_REL="${OGRE_PLUGIN_DIR_REL}") From b0b206423285180f0e84a0a2214ccb84d339de43 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 28 Jul 2012 02:31:30 +0400 Subject: [PATCH 266/298] always add debug suffix in plugin loader also removed cout spam --- components/ogreplugin/ogreplugin.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/ogreplugin/ogreplugin.cpp b/components/ogreplugin/ogreplugin.cpp index 5516482aa..ce61f344d 100644 --- a/components/ogreplugin/ogreplugin.cpp +++ b/components/ogreplugin/ogreplugin.cpp @@ -6,10 +6,11 @@ #include bool loadOgrePlugin(std::string pluginDir, std::string pluginName, Ogre::Root &ogreRoot) { + pluginName = pluginName + OGRE_PLUGIN_DEBUG_SUFFIX; #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE std::ostringstream verStream; verStream << "." << OGRE_VERSION_MAJOR << "." << OGRE_VERSION_MINOR << "." << OGRE_VERSION_PATCH; - pluginName = pluginName + OGRE_PLUGIN_DEBUG_SUFFIX + verStream.str(); + pluginName = pluginName + verStream.str(); #endif std::string pluginExt; @@ -24,9 +25,6 @@ bool loadOgrePlugin(std::string pluginDir, std::string pluginName, Ogre::Root &o #endif std::string pluginPath = pluginDir + "/" + pluginName + pluginExt; - - std::cout << "loading plugin: " << pluginPath << std::endl; - if (boost::filesystem::exists(pluginPath)) { ogreRoot.loadPlugin(pluginPath); return true; From 7161361b520fdb895ba42270613e00c3c4457e68 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 28 Jul 2012 02:38:37 +0400 Subject: [PATCH 267/298] cleanup --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b51ea68ea..f2decb8f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,11 +256,9 @@ endif (APPLE) # Set up Ogre plugin folder & debug suffix -set(DEBUG_SUFFIX "") if (DEFINED CMAKE_BUILD_TYPE) # Ogre on OS X doesn't use "_d" suffix (see Ogre's CMakeLists.txt) if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT APPLE) - set(DEBUG_SUFFIX "_d") add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") else() add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="") From a84d8e83cd04e7bfaa25ec7b0e566ef9b0026bd2 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 28 Jul 2012 02:39:49 +0400 Subject: [PATCH 268/298] add const specifier to first argument, also made it reference --- components/ogreplugin/ogreplugin.cpp | 2 +- components/ogreplugin/ogreplugin.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ogreplugin/ogreplugin.cpp b/components/ogreplugin/ogreplugin.cpp index ce61f344d..329cd1ea9 100644 --- a/components/ogreplugin/ogreplugin.cpp +++ b/components/ogreplugin/ogreplugin.cpp @@ -5,7 +5,7 @@ #include -bool loadOgrePlugin(std::string pluginDir, std::string pluginName, Ogre::Root &ogreRoot) { +bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot) { pluginName = pluginName + OGRE_PLUGIN_DEBUG_SUFFIX; #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE std::ostringstream verStream; diff --git a/components/ogreplugin/ogreplugin.h b/components/ogreplugin/ogreplugin.h index bb1ea04e1..d9b00e576 100644 --- a/components/ogreplugin/ogreplugin.h +++ b/components/ogreplugin/ogreplugin.h @@ -7,6 +7,6 @@ namespace Ogre { class Root; } -extern bool loadOgrePlugin(std::string pluginDir, std::string pluginName, Ogre::Root &ogreRoot); +extern bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot); #endif \ No newline at end of file From 90de02b901bde3e62bdf6438998f2365a3704e1f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 30 Jul 2012 11:43:28 +0200 Subject: [PATCH 269/298] Issue #350: console only script instructions --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/engine.cpp | 9 ++- apps/openmw/engine.hpp | 4 + apps/openmw/main.cpp | 10 ++- apps/openmw/mwgui/console.cpp | 10 ++- apps/openmw/mwgui/console.hpp | 5 +- apps/openmw/mwgui/window_manager.cpp | 4 +- apps/openmw/mwgui/window_manager.hpp | 2 +- apps/openmw/mwscript/consoleextensions.cpp | 24 ++++++ apps/openmw/mwscript/consoleextensions.hpp | 25 ++++++ apps/openmw/mwscript/docs/vmformat.txt | 13 +++- apps/openmw/mwscript/extensions.cpp | 18 ++++- apps/openmw/mwscript/extensions.hpp | 8 +- apps/openmw/mwscript/userextensions.cpp | 91 ++++++++++++++++++++++ apps/openmw/mwscript/userextensions.hpp | 25 ++++++ 15 files changed, 230 insertions(+), 20 deletions(-) create mode 100644 apps/openmw/mwscript/consoleextensions.cpp create mode 100644 apps/openmw/mwscript/consoleextensions.hpp create mode 100644 apps/openmw/mwscript/userextensions.cpp create mode 100644 apps/openmw/mwscript/userextensions.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 8e424ad56..556b9a1ba 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -39,7 +39,7 @@ add_openmw_dir (mwscript locals scriptmanager compilercontext interpretercontext cellextensions miscextensions guiextensions soundextensions skyextensions statsextensions containerextensions aiextensions controlextensions extensions globalscripts ref dialogueextensions - animationextensions transformationextensions + animationextensions transformationextensions consoleextensions userextensions ) add_openmw_dir (mwsound diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ab5b63071..d224ab71b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -129,6 +129,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mCompileAll (false) , mScriptContext (0) , mFSStrict (false) + , mScriptConsoleMode (false) , mCfgMgr(configurationManager) { std::srand ( std::time(NULL) ); @@ -326,7 +327,8 @@ void OMW::Engine::go() MWScript::registerExtensions (mExtensions); mEnvironment.setWindowManager (new MWGui::WindowManager( - mExtensions, mFpsLevel, mNewGame, mOgre, mCfgMgr.getLogPath().string() + std::string("/"))); + mExtensions, mFpsLevel, mNewGame, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), + mScriptConsoleMode)); // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); @@ -490,3 +492,8 @@ void OMW::Engine::setFallbackValues(std::map fallbackMa { mFallbackMap = fallbackMap; } + +void OMW::Engine::setScriptConsoleMode (bool enabled) +{ + mScriptConsoleMode = enabled; +} diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 031cae551..01b2dd8d6 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -73,6 +73,7 @@ namespace OMW bool mCompileAll; std::string mFocusName; std::map mFallbackMap; + bool mScriptConsoleMode; Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; @@ -158,6 +159,9 @@ namespace OMW void setFallbackValues(std::map map); + /// Enable console-only script functionality + void setScriptConsoleMode (bool enabled); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 993ec6623..c54c5969b 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -124,12 +124,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("script-verbose", bpo::value()->implicit_value(true) ->default_value(false), "verbose script output") - ("new-game", bpo::value()->implicit_value(true) - ->default_value(false), "activate char gen/new game mechanics") - ("script-all", bpo::value()->implicit_value(true) ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") + ("script-console", bpo::value()->implicit_value(true) + ->default_value(false), "enable console-only script functionality") + + ("new-game", bpo::value()->implicit_value(true) + ->default_value(false), "activate char gen/new game mechanics") + ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") @@ -249,6 +252,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setCompileAll(variables["script-all"].as()); engine.setAnimationVerbose(variables["anim-verbose"].as()); engine.setFallbackValues(variables["fallback"].as().mMap); + engine.setScriptConsoleMode (variables["script-console"].as()); return true; } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 4101165c1..39f36d28a 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -105,9 +105,10 @@ namespace MWGui } } - Console::Console(int w, int h, const Compiler::Extensions& extensions) + Console::Console(int w, int h, bool consoleOnlyScripts) : Layout("openmw_console.layout"), - mCompilerContext (MWScript::CompilerContext::Type_Console) + mCompilerContext (MWScript::CompilerContext::Type_Console), + mConsoleOnlyScripts (consoleOnlyScripts) { setCoord(10,10, w-10, h/2); @@ -126,7 +127,8 @@ namespace MWGui history->setVisibleVScroll(true); // compiler - mCompilerContext.setExtensions (&extensions); + MWScript::registerExtensions (mExtensions, mConsoleOnlyScripts); + mCompilerContext.setExtensions (&mExtensions); } void Console::enable() @@ -246,7 +248,7 @@ namespace MWGui { ConsoleInterpreterContext interpreterContext (*this, mPtr); Interpreter::Interpreter interpreter; - MWScript::installOpcodes (interpreter); + MWScript::installOpcodes (interpreter, mConsoleOnlyScripts); std::vector code; output.getCode (code); interpreter.run (&code[0], code.size(), interpreterContext); diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index eadf4aa4e..b01ebf7fa 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "../mwscript/compilercontext.hpp" @@ -24,8 +25,10 @@ namespace MWGui { private: + Compiler::Extensions mExtensions; MWScript::CompilerContext mCompilerContext; std::vector mNames; + bool mConsoleOnlyScripts; bool compile (const std::string& cmd, Compiler::Output& output); @@ -62,7 +65,7 @@ namespace MWGui StringList::iterator current; std::string editString; - Console(int w, int h, const Compiler::Extensions& extensions); + Console(int w, int h, bool consoleOnlyScripts); void enable(); diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index aa0595b85..25f0fc097 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -41,7 +41,7 @@ using namespace MWGui; WindowManager::WindowManager( - const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath) + const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath, bool consoleOnlyScripts) : mGuiManager(NULL) , mHud(NULL) , mMap(NULL) @@ -113,7 +113,7 @@ WindowManager::WindowManager( mMenu = new MainMenu(w,h); mMap = new MapWindow(*this); mStatsWindow = new StatsWindow(*this); - mConsole = new Console(w,h, extensions); + mConsole = new Console(w,h, consoleOnlyScripts); mJournal = new JournalWindow(*this); mMessageBoxManager = new MessageBoxManager(this); mInventoryWindow = new InventoryWindow(*this,mDragAndDrop); diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index adab79942..51fde071d 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -94,7 +94,7 @@ namespace MWGui typedef std::vector FactionList; typedef std::vector SkillList; - WindowManager(const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath); + WindowManager(const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath, bool consoleOnlyScripts); virtual ~WindowManager(); /** diff --git a/apps/openmw/mwscript/consoleextensions.cpp b/apps/openmw/mwscript/consoleextensions.cpp new file mode 100644 index 000000000..00b4f74e5 --- /dev/null +++ b/apps/openmw/mwscript/consoleextensions.cpp @@ -0,0 +1,24 @@ + +#include "consoleextensions.hpp" + +#include + +#include +#include +#include + +namespace MWScript +{ + namespace Console + { + void registerExtensions (Compiler::Extensions& extensions) + { + + } + + void installOpcodes (Interpreter::Interpreter& interpreter) + { + + } + } +} diff --git a/apps/openmw/mwscript/consoleextensions.hpp b/apps/openmw/mwscript/consoleextensions.hpp new file mode 100644 index 000000000..b10bf06a8 --- /dev/null +++ b/apps/openmw/mwscript/consoleextensions.hpp @@ -0,0 +1,25 @@ +#ifndef GAME_SCRIPT_CONSOLEEXTENSIONS_H +#define GAME_SCRIPT_CONSOLEEXTENSIONS_H + +namespace Compiler +{ + class Extensions; +} + +namespace Interpreter +{ + class Interpreter; +} + +namespace MWScript +{ + /// \brief Script functionality limited to the console + namespace Console + { + void registerExtensions (Compiler::Extensions& extensions); + + void installOpcodes (Interpreter::Interpreter& interpreter); + } +} + +#endif diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index b979420dd..3ca93d4b2 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -169,5 +169,14 @@ op 0x2000164: SetScale op 0x2000165: SetScale, explicit reference op 0x2000166: SetAngle op 0x2000167: SetAngle, explicit reference -opcodes 0x2000168-0x3ffffff unused - +op 0x2000168: GetScale +op 0x2000169: GetScale, explicit reference +op 0x200016a: GetAngle +op 0x200016b: GetAngle, explicit reference +op 0x200016c: user1 (console only, requires --script-console switch) +op 0x200016d: user2 (console only, requires --script-console switch) +op 0x200016e: user3, explicit reference (console only, requires --script-console switch) +op 0x200016f: user3 (implicit reference, console only, requires --script-console switch) +op 0x2000170: user4, explicit reference (console only, requires --script-console switch) +op 0x2000171: user4 (implicit reference, console only, requires --script-console switch) +opcodes 0x2000172-0x3ffffff unused diff --git a/apps/openmw/mwscript/extensions.cpp b/apps/openmw/mwscript/extensions.cpp index b7425aca0..1b1560820 100644 --- a/apps/openmw/mwscript/extensions.cpp +++ b/apps/openmw/mwscript/extensions.cpp @@ -16,10 +16,12 @@ #include "dialogueextensions.hpp" #include "animationextensions.hpp" #include "transformationextensions.hpp" +#include "consoleextensions.hpp" +#include "userextensions.hpp" namespace MWScript { - void registerExtensions (Compiler::Extensions& extensions) + void registerExtensions (Compiler::Extensions& extensions, bool consoleOnly) { Cell::registerExtensions (extensions); Misc::registerExtensions (extensions); @@ -33,9 +35,15 @@ namespace MWScript Dialogue::registerExtensions (extensions); Animation::registerExtensions (extensions); Transformation::registerExtensions (extensions); + + if (consoleOnly) + { + Console::registerExtensions (extensions); + User::registerExtensions (extensions); + } } - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes (Interpreter::Interpreter& interpreter, bool consoleOnly) { Interpreter::installOpcodes (interpreter); Cell::installOpcodes (interpreter); @@ -50,5 +58,11 @@ namespace MWScript Dialogue::installOpcodes (interpreter); Animation::installOpcodes (interpreter); Transformation::installOpcodes (interpreter); + + if (consoleOnly) + { + Console::installOpcodes (interpreter); + User::installOpcodes (interpreter); + } } } diff --git a/apps/openmw/mwscript/extensions.hpp b/apps/openmw/mwscript/extensions.hpp index 9738367a0..cb1aaf9db 100644 --- a/apps/openmw/mwscript/extensions.hpp +++ b/apps/openmw/mwscript/extensions.hpp @@ -13,9 +13,11 @@ namespace Interpreter namespace MWScript { - void registerExtensions (Compiler::Extensions& extensions); - - void installOpcodes (Interpreter::Interpreter& interpreter); + void registerExtensions (Compiler::Extensions& extensions, bool consoleOnly = false); + ///< \param consoleOnly include console only extensions + + void installOpcodes (Interpreter::Interpreter& interpreter, bool consoleOnly = false); + ///< \param consoleOnly include console only opcodes } #endif diff --git a/apps/openmw/mwscript/userextensions.cpp b/apps/openmw/mwscript/userextensions.cpp new file mode 100644 index 000000000..9c9738815 --- /dev/null +++ b/apps/openmw/mwscript/userextensions.cpp @@ -0,0 +1,91 @@ + +#include "userextensions.hpp" + +#include + +#include +#include +#include +#include + +#include "ref.hpp" + +namespace MWScript +{ + /// Temporary script extensions. + /// + /// \attention Do not commit changes to this file to a git repository! + namespace User + { + class OpUser1 : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + runtime.getContext().report ("user1: not in use"); + } + }; + + class OpUser2 : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + runtime.getContext().report ("user2: not in use"); + } + }; + + template + class OpUser3 : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { +// MWWorld::Ptr ptr = R()(runtime); + + runtime.getContext().report ("user3: not in use"); + } + }; + + template + class OpUser4 : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { +// MWWorld::Ptr ptr = R()(runtime); + + runtime.getContext().report ("user4: not in use"); + } + }; + + const int opcodeUser1 = 0x200016c; + const int opcodeUser2 = 0x200016d; + const int opcodeUser3 = 0x200016e; + const int opcodeUser3Explicit = 0x200016f; + const int opcodeUser4 = 0x2000170; + const int opcodeUser4Explicit = 0x2000171; + + void registerExtensions (Compiler::Extensions& extensions) + { + extensions.registerInstruction ("user1", "", opcodeUser1); + extensions.registerInstruction ("user2", "", opcodeUser2); + extensions.registerInstruction ("user3", "", opcodeUser3, opcodeUser3); + extensions.registerInstruction ("user4", "", opcodeUser4, opcodeUser4); + } + + void installOpcodes (Interpreter::Interpreter& interpreter) + { + interpreter.installSegment5 (opcodeUser1, new OpUser1); + interpreter.installSegment5 (opcodeUser2, new OpUser2); + interpreter.installSegment5 (opcodeUser3, new OpUser3); + interpreter.installSegment5 (opcodeUser3Explicit, new OpUser3); + interpreter.installSegment5 (opcodeUser4, new OpUser4); + interpreter.installSegment5 (opcodeUser4Explicit, new OpUser4); + } + } +} diff --git a/apps/openmw/mwscript/userextensions.hpp b/apps/openmw/mwscript/userextensions.hpp new file mode 100644 index 000000000..3642eb5f4 --- /dev/null +++ b/apps/openmw/mwscript/userextensions.hpp @@ -0,0 +1,25 @@ +#ifndef GAME_SCRIPT_USEREXTENSIONS_H +#define GAME_SCRIPT_USEREXTENSIONS_H + +namespace Compiler +{ + class Extensions; +} + +namespace Interpreter +{ + class Interpreter; +} + +namespace MWScript +{ + /// \brief Temporaty script functionality limited to the console + namespace User + { + void registerExtensions (Compiler::Extensions& extensions); + + void installOpcodes (Interpreter::Interpreter& interpreter); + } +} + +#endif From 23f8595b8767f97492e9aeb67d82a8a8cbd81543 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 30 Jul 2012 11:57:19 +0200 Subject: [PATCH 270/298] Issue #352: refactored script execution in console --- apps/openmw/mwgui/console.cpp | 49 +++++++++++++++++++---------------- apps/openmw/mwgui/console.hpp | 2 ++ 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 39f36d28a..fdcc67604 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -175,6 +175,32 @@ namespace MWGui print("#FF2222" + msg + "\n"); } + void Console::execute (const std::string& command) + { + // Log the command + print("#FFFFFF> " + command + "\n"); + + Compiler::Locals locals; + Compiler::Output output (locals); + + if (compile (command + "\n", output)) + { + try + { + ConsoleInterpreterContext interpreterContext (*this, mPtr); + Interpreter::Interpreter interpreter; + MWScript::installOpcodes (interpreter, mConsoleOnlyScripts); + std::vector code; + output.getCode (code); + interpreter.run (&code[0], code.size(), interpreterContext); + } + catch (const std::exception& error) + { + printError (std::string ("An exception has been thrown: ") + error.what()); + } + } + } + void Console::keyPress(MyGUI::WidgetPtr _sender, MyGUI::KeyCode key, MyGUI::Char _char) @@ -236,28 +262,7 @@ namespace MWGui current = command_history.end(); editString.clear(); - // Log the command - print("#FFFFFF> " + cm + "\n"); - - Compiler::Locals locals; - Compiler::Output output (locals); - - if (compile (cm + "\n", output)) - { - try - { - ConsoleInterpreterContext interpreterContext (*this, mPtr); - Interpreter::Interpreter interpreter; - MWScript::installOpcodes (interpreter, mConsoleOnlyScripts); - std::vector code; - output.getCode (code); - interpreter.run (&code[0], code.size(), interpreterContext); - } - catch (const std::exception& error) - { - printError (std::string ("An exception has been thrown: ") + error.what()); - } - } + execute (cm); command->setCaption(""); } diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index b01ebf7fa..f587fefed 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -89,6 +89,8 @@ namespace MWGui /// Error message void printError(const std::string &msg); + void execute (const std::string& command); + private: void keyPress(MyGUI::WidgetPtr _sender, From fd6c155118fbe469fb78fc584eb3fbf98df7a80f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 30 Jul 2012 12:37:46 +0200 Subject: [PATCH 271/298] Issue #352: added --script-run switch --- apps/openmw/engine.cpp | 8 ++++++++ apps/openmw/engine.hpp | 4 ++++ apps/openmw/main.cpp | 6 ++++++ apps/openmw/mwgui/console.cpp | 16 ++++++++++++++++ apps/openmw/mwgui/console.hpp | 2 ++ apps/openmw/mwgui/window_manager.cpp | 5 +++++ apps/openmw/mwgui/window_manager.hpp | 2 ++ 7 files changed, 43 insertions(+) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d224ab71b..5e9aedf66 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -390,6 +390,9 @@ void OMW::Engine::go() << std::endl; } + if (!mStartupScript.empty()) + MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); + // Start the main rendering loop mOgre->start(); @@ -497,3 +500,8 @@ void OMW::Engine::setScriptConsoleMode (bool enabled) { mScriptConsoleMode = enabled; } + +void OMW::Engine::setStartupScript (const std::string& path) +{ + mStartupScript = path; +} diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 01b2dd8d6..57402c91e 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -74,6 +74,7 @@ namespace OMW std::string mFocusName; std::map mFallbackMap; bool mScriptConsoleMode; + std::string mStartupScript; Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; @@ -162,6 +163,9 @@ namespace OMW /// Enable console-only script functionality void setScriptConsoleMode (bool enabled); + /// Set path for a script that is run on startup in the console. + void setStartupScript (const std::string& path); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index c54c5969b..fb1657ad4 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -130,6 +130,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("script-console", bpo::value()->implicit_value(true) ->default_value(false), "enable console-only script functionality") + ("script-run", bpo::value()->default_value(""), + "set a file that is execute in the console on startup\n\n" + "Note: The file contains a list of script lines, but not a complete scripts. " + "That means no begin/end and no variable declarations.") + ("new-game", bpo::value()->implicit_value(true) ->default_value(false), "activate char gen/new game mechanics") @@ -253,6 +258,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setAnimationVerbose(variables["anim-verbose"].as()); engine.setFallbackValues(variables["fallback"].as().mMap); engine.setScriptConsoleMode (variables["script-console"].as()); + engine.setStartupScript (variables["script-run"].as()); return true; } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index fdcc67604..86c8940a1 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -2,6 +2,7 @@ #include "console.hpp" #include +#include #include #include @@ -201,6 +202,21 @@ namespace MWGui } } + void Console::executeFile (const std::string& path) + { + std::ifstream stream (path.c_str()); + + if (!stream.is_open()) + printError ("failed to open file: " + path); + else + { + std::string line; + + while (std::getline (stream, line)) + execute (line); + } + } + void Console::keyPress(MyGUI::WidgetPtr _sender, MyGUI::KeyCode key, MyGUI::Char _char) diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index f587fefed..1893b0148 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -91,6 +91,8 @@ namespace MWGui void execute (const std::string& command); + void executeFile (const std::string& command); + private: void keyPress(MyGUI::WidgetPtr _sender, diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 25f0fc097..659af0447 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -740,3 +740,8 @@ bool WindowManager::getWorldMouseOver() { return mHud->getWorldMouseOver(); } + +void WindowManager::executeInConsole (const std::string& path) +{ + mConsole->executeFile (path); +} diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index 51fde071d..3653615a6 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -237,6 +237,8 @@ namespace MWGui void processChangedSettings(const Settings::CategorySettingVector& changed); + void executeInConsole (const std::string& path); + private: OEngine::GUI::MyGUIManager *mGuiManager; HUD *mHud; From 2ccecd839bdac85672cd3aa861cfa95f25c27f46 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 30 Jul 2012 12:43:23 +0200 Subject: [PATCH 272/298] improved the help text for --script-run; updated readme.txt --- apps/openmw/main.cpp | 2 +- readme.txt | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index fb1657ad4..0ca9dd6d9 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -131,7 +131,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ->default_value(false), "enable console-only script functionality") ("script-run", bpo::value()->default_value(""), - "set a file that is execute in the console on startup\n\n" + "select a file that is executed in the console on startup\n\n" "Note: The file contains a list of script lines, but not a complete scripts. " "That means no begin/end and no variable declarations.") diff --git a/readme.txt b/readme.txt index ded9bcd7b..ce3d115e3 100644 --- a/readme.txt +++ b/readme.txt @@ -66,9 +66,16 @@ Allowed options: --debug [=arg(=1)] (=0) debug mode --nosound [=arg(=1)] (=0) disable all sounds --script-verbose [=arg(=1)] (=0) verbose script output - --new-game [=arg(=1)] (=0) activate char gen/new game mechanics --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue scri pts) at startup + --script-console [=arg(=1)] (=0) enable console-only script functionality + --script-run arg select a file that is executed in the consol + e on startup + + Note: The file contains a list of script + lines, but not a complete scripts. That mean + s no begin/end and no variable declarations. + --new-game [=arg(=1)] (=0) activate char gen/new game mechanics --fs-strict [=arg(=1)] (=0) strict file system handling (no case folding ) --encoding arg (=win1252) Character encoding used in OpenMW game messa From d37d0b19474a31535ddd5bd3f1debf4ba5d58600 Mon Sep 17 00:00:00 2001 From: gugus Date: Mon, 30 Jul 2012 19:45:40 +0200 Subject: [PATCH 273/298] build fix for windows --- apps/openmw/mwrender/terrainmaterial.cpp | 12 ++++++------ libs/openengine/ogre/renderer.cpp | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index 1a2d96a14..9ef4652f6 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -86,15 +86,15 @@ namespace MWRender normalMap->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getTerrainNormalMap ()->getName()))); normalMap->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); - uint maxLayers = getMaxLayers(terrain); - uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); - uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + Ogre::uint maxLayers = getMaxLayers(terrain); + Ogre::uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); + Ogre::uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); p->mShaderProperties.setProperty ("num_layers", sh::makeProperty(new sh::StringValue(Ogre::StringConverter::toString(numLayers)))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty(new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); // blend maps - for (uint i = 0; i < numBlendTextures; ++i) + for (Ogre::uint i = 0; i < numBlendTextures; ++i) { sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i)); blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getBlendTextureName(i)))); @@ -102,7 +102,7 @@ namespace MWRender } // layer maps - for (uint i = 0; i < numLayers; ++i) + for (Ogre::uint i = 0; i < numLayers; ++i) { sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getLayerTextureName(i, 0)))); @@ -111,7 +111,7 @@ namespace MWRender } // shadow - for (uint i = 0; i < 3; ++i) + for (Ogre::uint i = 0; i < 3; ++i) { sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index e40bdf708..b1893568e 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -110,7 +110,9 @@ void OgreRenderer::configure(const std::string &logPath, pluginDir = OGRE_PLUGIN_DIR_REL; #endif } - +#ifndef OGRE_PLUGIN_DEBUG_SUFFIX +#define OGRE_PLUGIN_DEBUG_SUFFIX "" +#endif std::string glPlugin = std::string(pluginDir) + "/RenderSystem_GL" + OGRE_PLUGIN_DEBUG_SUFFIX; if (boost::filesystem::exists(glPlugin + ".so") || boost::filesystem::exists(glPlugin + ".dll")) mRoot->loadPlugin (glPlugin); From e7a1ab9fa627e5509d0611d86b07e0ed9b65d5ce Mon Sep 17 00:00:00 2001 From: Michael Mc Donnell Date: Mon, 30 Jul 2012 15:04:14 -0400 Subject: [PATCH 274/298] Define OGRE_PLUGIN_DEBUG_SUFFIX in all cases. --- CMakeLists.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea0c41fe2..9ac6419ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -235,14 +235,13 @@ endif (APPLE) # Set up Ogre plugin folder & debug suffix set(DEBUG_SUFFIX "") -if (DEFINED CMAKE_BUILD_TYPE) - if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(DEBUG_SUFFIX "_d") - add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") - else() - add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="") - endif() +if (DEFINED CMAKE_BUILD_TYPE AND CMAKE_BUILD_TYPE STREQUAL "Debug") + set(DEBUG_SUFFIX "_d") + add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") +else() + add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="") endif() + add_definitions(-DOGRE_PLUGIN_DIR_REL="${OGRE_PLUGIN_DIR_REL}") add_definitions(-DOGRE_PLUGIN_DIR_DBG="${OGRE_PLUGIN_DIR_DBG}") add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}") From b05dfeae70302bd4b44d30a16c8beecd81aebc26 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Tue, 31 Jul 2012 01:00:53 +0400 Subject: [PATCH 275/298] bug #348: cleanup moved files to appropriate component, moved function to namespace, added docs --- apps/launcher/graphicspage.cpp | 6 +-- components/CMakeLists.txt | 6 +-- .../{ogreplugin => files}/ogreplugin.cpp | 6 ++- components/files/ogreplugin.hpp | 50 +++++++++++++++++++ components/ogreplugin/ogreplugin.h | 12 ----- libs/openengine/ogre/renderer.cpp | 8 +-- 6 files changed, 63 insertions(+), 25 deletions(-) rename components/{ogreplugin => files}/ogreplugin.cpp (94%) create mode 100644 components/files/ogreplugin.hpp delete mode 100644 components/ogreplugin/ogreplugin.h diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 7d25f2df1..c3c39cffc 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -6,8 +6,8 @@ #include #include +#include #include -#include #include "graphicspage.hpp" #include "naturalsort.hpp" @@ -120,8 +120,8 @@ bool GraphicsPage::setupOgre() pluginDir = absPluginPath.string(); - loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre); - loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mOgre); + Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre); + Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mOgre); #ifdef ENABLE_PLUGIN_GL mGLPlugin = new Ogre::GLPlugin(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e16a860c4..c0585d5ee 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -52,7 +52,7 @@ add_component_dir (misc add_component_dir (files linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager - filelibrary + filelibrary ogreplugin ) add_component_dir (compiler @@ -66,10 +66,6 @@ add_component_dir (interpreter miscopcodes opcodes runtime scriptopcodes spatialopcodes types ) -add_component_dir (ogreplugin - ogreplugin -) - include_directories(${BULLET_INCLUDE_DIRS}) add_library(components STATIC ${COMPONENT_FILES}) diff --git a/components/ogreplugin/ogreplugin.cpp b/components/files/ogreplugin.cpp similarity index 94% rename from components/ogreplugin/ogreplugin.cpp rename to components/files/ogreplugin.cpp index 329cd1ea9..d0e80f68d 100644 --- a/components/ogreplugin/ogreplugin.cpp +++ b/components/files/ogreplugin.cpp @@ -1,10 +1,12 @@ -#include "ogreplugin.h" +#include "ogreplugin.hpp" #include #include #include +namespace Files { + bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot) { pluginName = pluginName + OGRE_PLUGIN_DEBUG_SUFFIX; #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE @@ -32,4 +34,6 @@ bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre:: else { return false; } +} + } \ No newline at end of file diff --git a/components/files/ogreplugin.hpp b/components/files/ogreplugin.hpp new file mode 100644 index 000000000..a6d2a35c9 --- /dev/null +++ b/components/files/ogreplugin.hpp @@ -0,0 +1,50 @@ +/** + * Open Morrowind - an opensource Elder Scrolls III: Morrowind + * engine implementation. + * + * Copyright (C) 2011 Open Morrowind Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** \file components/files/ogreplugin.hpp */ + +#ifndef COMPONENTS_FILES_OGREPLUGIN_H +#define COMPONENTS_FILES_OGREPLUGIN_H + +#include + +namespace Ogre { + class Root; +} + +/** + * \namespace Files + */ +namespace Files { + +/** + * \brief Loads Ogre plugin with given name. + * + * \param pluginDir absolute path to plugins + * \param pluginName plugin name, for example "RenderSystem_GL" + * \param ogreRoot Ogre::Root instance + * + * \return whether plugin was located or not + */ +bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot); + +} + +#endif /* COMPONENTS_FILES_OGREPLUGIN_H */ \ No newline at end of file diff --git a/components/ogreplugin/ogreplugin.h b/components/ogreplugin/ogreplugin.h deleted file mode 100644 index d9b00e576..000000000 --- a/components/ogreplugin/ogreplugin.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef OGREPLUGIN_H -#define OGREPLUGIN_H - -#include - -namespace Ogre { - class Root; -} - -extern bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot); - -#endif \ No newline at end of file diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index f8fed3899..7fff2a8b4 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -11,7 +11,7 @@ #include -#include +#include #include #include @@ -117,9 +117,9 @@ void OgreRenderer::configure(const std::string &logPath, pluginDir = absPluginPath.string(); - loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); - loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); - loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); + Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); if (rs == 0) From 03cccee0e4eb7f57a039559308cfe8f25ce5b0f1 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Tue, 31 Jul 2012 10:51:34 +0400 Subject: [PATCH 276/298] bug #348: workaround for boost older than 1.44 --- components/files/ogreplugin.cpp | 2 -- components/files/ogreplugin.hpp | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/components/files/ogreplugin.cpp b/components/files/ogreplugin.cpp index d0e80f68d..c434114b3 100644 --- a/components/files/ogreplugin.cpp +++ b/components/files/ogreplugin.cpp @@ -3,8 +3,6 @@ #include #include -#include - namespace Files { bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot) { diff --git a/components/files/ogreplugin.hpp b/components/files/ogreplugin.hpp index a6d2a35c9..d8469aa4b 100644 --- a/components/files/ogreplugin.hpp +++ b/components/files/ogreplugin.hpp @@ -25,10 +25,25 @@ #include +#include +#include + namespace Ogre { class Root; } +#if (BOOST_VERSION <= 104300) +#error BOOST_VERSION +namespace boost { +namespace filesystem { +path absolute(const path& p, const path& base=current_path()) { + // call obsolete version of this function on older boost + return complete(p, base); +} +} +} +#endif /* (BOOST_VERSION <= 104300) */ + /** * \namespace Files */ From 32d2326b4dddc0420cc5df23a89de17cf76677fa Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Tue, 31 Jul 2012 18:15:09 +0400 Subject: [PATCH 277/298] Update components/files/ogreplugin.hpp removed #error directive --- components/files/ogreplugin.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/files/ogreplugin.hpp b/components/files/ogreplugin.hpp index d8469aa4b..7a0b5841e 100644 --- a/components/files/ogreplugin.hpp +++ b/components/files/ogreplugin.hpp @@ -33,7 +33,6 @@ namespace Ogre { } #if (BOOST_VERSION <= 104300) -#error BOOST_VERSION namespace boost { namespace filesystem { path absolute(const path& p, const path& base=current_path()) { From f2a2e5f57d566a7a6c97f91bc63968dae2a67dcb Mon Sep 17 00:00:00 2001 From: greye Date: Tue, 31 Jul 2012 19:30:24 +0400 Subject: [PATCH 278/298] remove MWWorld::Player::setPos() --- apps/openmw/mwworld/player.cpp | 6 ------ apps/openmw/mwworld/player.hpp | 3 --- apps/openmw/mwworld/scene.cpp | 26 +++++++++++++++++--------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 4d508c3e9..54e5a625f 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -38,12 +38,6 @@ namespace MWWorld delete mClass; } - void Player::setPos(float x, float y, float z) - { - /// \todo This fcuntion should be removed during the mwrender-refactoring. - MWBase::Environment::get().getWorld()->moveObject (getPlayer(), x, y, z); - } - void Player::setRot(float x, float y, float z) { mRenderer->setRot(x, y, z); diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index ee7c030a5..d2058dea6 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -42,9 +42,6 @@ namespace MWWorld ~Player(); - /// Set the player position. Uses Morrowind coordinates. - void setPos(float x, float y, float z); - /// Set where the player is looking at. Uses Morrowind (euler) angles void setRot(float x, float y, float z); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 13e5ecb85..c768fce26 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -135,23 +135,31 @@ namespace MWWorld } - void Scene::playerCellChange (Ptr::CellStore *cell, const ESM::Position& position, + void + Scene::playerCellChange( + MWWorld::CellStore *cell, + const ESM::Position& pos, bool adjustPlayerPos) { bool hasWater = cell->cell->data.flags & cell->cell->HasWater; mPhysics->setCurrentWater(hasWater, cell->cell->water); - if (adjustPlayerPos) - { - MWBase::Environment::get().getWorld()->getPlayer().setPos (position.pos[0], position.pos[1], position.pos[2]); - MWBase::Environment::get().getWorld()->getPlayer().setRot (position.rot[0], position.rot[1], position.rot[2]); + + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + + if (adjustPlayerPos) { + world->moveObject(player, pos.pos[0], pos.pos[1], pos.pos[2]); + MWBase::Environment::get().getWorld()->getPlayer().setRot (pos.rot[0], pos.rot[1], pos.rot[2]); } + world->getPlayer().setCell(cell); - MWBase::Environment::get().getWorld()->getPlayer().setCell (cell); + MWMechanics::MechanicsManager *mechMgr = + MWBase::Environment::get().getMechanicsManager(); - MWBase::Environment::get().getMechanicsManager()->addActor (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - MWBase::Environment::get().getMechanicsManager()->watchActor (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + mechMgr->addActor(player); + mechMgr->watchActor(player); - MWBase::Environment::get().getWindowManager()->changeCell( mCurrentCell ); + MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); } void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) From 3b776cb3ca5ef6417cb965daf60a95be96c09555 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Wed, 1 Aug 2012 00:01:32 +0400 Subject: [PATCH 279/298] fixed submodule version --- extern/shiny | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/shiny b/extern/shiny index 5546a5bd8..164bc8d3b 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 5546a5bd8474ef328dfedae2df42126cdaf9515f +Subproject commit 164bc8d3bfe860bd16ad89c0bd1b59f465c9bb24 From 15a16aeba1c2b7fd0ba3e47b14fd673a17295524 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 1 Aug 2012 04:11:16 +0200 Subject: [PATCH 280/298] Fixed DirectX HLSL shaders and re-enabled them in the gui --- apps/openmw/mwgui/settingswindow.cpp | 12 ++++------ apps/openmw/mwrender/renderingmanager.cpp | 16 ++++++------- files/materials/core.h | 29 +++++++++++++---------- files/materials/objects.shaderset | 4 ++-- files/materials/terrain.shaderset | 4 ++-- files/materials/underwater.h | 10 ++++---- files/materials/water.shader | 11 +++++---- files/materials/water.shaderset | 4 ++-- 8 files changed, 46 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index d8b0c96d7..4488096fe 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -75,9 +75,9 @@ namespace return boost::lexical_cast(xaspect) + " : " + boost::lexical_cast(yaspect); } - bool hasGLSL () + std::string hlslGlsl () { - return (Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") != std::string::npos); + return (Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") != std::string::npos) ? "glsl" : "hlsl"; } } @@ -393,12 +393,9 @@ namespace MWGui std::string val = static_cast(_sender)->getCaption(); if (val == "off") { - if (hasGLSL ()) - val = "glsl"; - else - val = "cg"; + val = hlslGlsl(); } - else if (val == "glsl") + else if (val == hlslGlsl()) val = "cg"; else val = "off"; @@ -411,6 +408,7 @@ namespace MWGui // water shader not supported with object shaders off mWaterShaderButton->setCaptionWithReplacing("#{sOff}"); + mUnderwaterButton->setCaptionWithReplacing("#{sOff}"); mWaterShaderButton->setEnabled(false); mReflectObjectsButton->setEnabled(false); mReflectActorsButton->setEnabled(false); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ae0e57219..a4b2e8489 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -43,12 +43,14 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const :mRendering(_rend), mObjects(mRendering), mActors(mRendering), mAmbientMode(0), mSunEnabled(0) { // select best shader mode - if (Settings::Manager::getString("shader mode", "General") == "") + bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos); + + // glsl is only supported in opengl mode and hlsl only in direct3d mode. + if (Settings::Manager::getString("shader mode", "General") == "" + || (openGL && Settings::Manager::getString("shader mode", "General") == "hlsl") + || (!openGL && Settings::Manager::getString("shader mode", "General") == "glsl")) { - if (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") == std::string::npos) - Settings::Manager::setString("shader mode", "General", "cg"); - else - Settings::Manager::setString("shader mode", "General", "glsl"); + Settings::Manager::setString("shader mode", "General", openGL ? "glsl" : "hlsl"); } mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); @@ -74,10 +76,6 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mFactory->setCurrentLanguage (lang); mFactory->loadAllFiles(); - //The fog type must be set before any terrain objects are created as if the - //fog type is set to FOG_NONE then the initially created terrain won't have any fog - configureFog(1, ColourValue(1,1,1)); - // Set default mipmap level (NB some APIs ignore this) TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); diff --git a/files/materials/core.h b/files/materials/core.h index 6e27d349c..cba716777 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -1,20 +1,20 @@ -#if SH_HLSL == 1 - #error "HLSL is unsupported" -#endif - -#if SH_CG == 1 +#if SH_HLSL == 1 || SH_CG == 1 #define shTexture2D sampler2D #define shSample(tex, coord) tex2D(tex, coord) + #define shCubicSample(tex, coord) texCUBE(tex, coord) #define shLerp(a, b, t) lerp(a, b, t) #define shSaturate(a) saturate(a) #define shSampler2D(name) , uniform sampler2D name : register(s@shCounter(0)) @shUseSampler(name) + + #define shSamplerCube(name) , uniform samplerCUBE name : register(s@shCounter(0)) @shUseSampler(name) #define shMatrixMult(m, v) mul(m, v) #define shUniform(type, name) , uniform type name + #define shTangentInput(type) , in type tangent : TANGENT #define shVertexInput(type, name) , in type name : TEXCOORD@shCounter(1) #define shInput(type, name) , in type name : TEXCOORD@shCounter(1) #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2) @@ -65,9 +65,10 @@ #define float4 vec4 #define int2 ivec2 #define int3 ivec3 - #define int4 ivec4/ + #define int4 ivec4 #define shTexture2D sampler2D #define shSample(tex, coord) texture2D(tex, coord) + #define shCubicSample(tex, coord) textureCube(tex, coord) #define shLerp(a, b, t) mix(a, b, t) #define shSaturate(a) clamp(a, 0.0, 1.0) @@ -75,11 +76,14 @@ #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name) + #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name) + #define shMatrixMult(m, v) (m * v) #define shOutputPosition gl_Position #define float4x4 mat4 + #define float3x3 mat3 // GLSL 1.3 #if 0 @@ -89,8 +93,8 @@ #define shOutputColour(num) oColor##num - - + #define shTangentInput(type) in type tangent; + #define shVertexInput(type, name) in type name; #define shInput(type, name) in type name; #define shOutput(type, name) out type name; @@ -101,7 +105,7 @@ #ifdef SH_VERTEX_SHADER #define SH_BEGIN_PROGRAM \ - in float4 shInputPosition; + in float4 vertex; #define SH_START_PROGRAM \ void main(void) @@ -126,10 +130,11 @@ #if 1 // automatically recognized by ogre when the input name equals this - #define shInputPosition gl_Vertex + #define shInputPosition vertex #define shOutputColour(num) gl_FragData[num] + #define shTangentInput(type) attribute type tangent; #define shVertexInput(type, name) attribute type name; #define shInput(type, name) varying type name; #define shOutput(type, name) varying type name; @@ -140,8 +145,8 @@ #ifdef SH_VERTEX_SHADER - #define SH_BEGIN_PROGRAM - + #define SH_BEGIN_PROGRAM \ + attribute vec4 vertex; #define SH_START_PROGRAM \ void main(void) diff --git a/files/materials/objects.shaderset b/files/materials/objects.shaderset index ccb975fe9..028c15ce8 100644 --- a/files/materials/objects.shaderset +++ b/files/materials/objects.shaderset @@ -3,7 +3,7 @@ shader_set openmw_objects_vertex source objects.shader type vertex profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_2_0 + profiles_hlsl vs_3_0 vs_2_0 } shader_set openmw_objects_fragment @@ -11,5 +11,5 @@ shader_set openmw_objects_fragment source objects.shader type fragment profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 - profiles_hlsl ps_2_0 + profiles_hlsl ps_3_0 ps_2_0 } diff --git a/files/materials/terrain.shaderset b/files/materials/terrain.shaderset index be8ecd7d8..a72f2358f 100644 --- a/files/materials/terrain.shaderset +++ b/files/materials/terrain.shaderset @@ -3,7 +3,7 @@ shader_set terrain_vertex source terrain.shader type vertex profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_2_0 + profiles_hlsl vs_3_0 vs_2_0 } shader_set terrain_fragment @@ -11,5 +11,5 @@ shader_set terrain_fragment source terrain.shader type fragment profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 - profiles_hlsl ps_2_0 + profiles_hlsl ps_3_0 ps_2_0 } diff --git a/files/materials/underwater.h b/files/materials/underwater.h index fe19ff93b..18052a98d 100644 --- a/files/materials/underwater.h +++ b/files/materials/underwater.h @@ -28,7 +28,7 @@ float3 intercept(float3 lineP, float3 perturb1(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) { - float2 nCoord = float2(0.0); + float2 nCoord = float2(0,0); bend *= WAVE_CHOPPYNESS; nCoord = coords * (WAVE_SCALE * 0.05) + windDir * timer * (windSpeed*0.04); float3 normal0 = 2.0 * shSample(tex, nCoord + float2(-timer*0.015,-timer*0.05)).rgb - 1.0; @@ -55,8 +55,8 @@ float3 perturb1(shTexture2D tex, float2 coords, float bend, float2 windDir, floa float3 perturb(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) { bend *= WAVE_CHOPPYNESS; - float3 col = float3(0.0); - float2 nCoord = float2(0.0); //normal coords + float3 col = float3(0,0,0); + float2 nCoord = float2(0,0); //normal coords nCoord = coords * (WAVE_SCALE * 0.025) + windDir * timer * (windSpeed*0.03); col += shSample(tex,nCoord + float2(-timer*0.005,-timer*0.01)).rgb*0.20; @@ -102,11 +102,11 @@ float3 getCaustics (shTexture2D causticMap, float3 worldPos, float3 waterEyePos, /// \todo sunFade // float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*sunFade*causticdepth; - float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*causticdepth; + float3 caustics = clamp(pow(float3(causticR,causticR,causticR)*5.5,float3(5.5*causticdepth,5.5*causticdepth,5.5*causticdepth)),0.0,1.0)*NdotL*causticdepth; float causticG = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; float causticB = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION*2.0, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; //caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*sunFade*causticdepth; - caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*causticdepth; + caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth,5.5*causticdepth,5.5*causticdepth)))*NdotL*causticdepth; caustics *= 3; diff --git a/files/materials/water.shader b/files/materials/water.shader index 2145919b0..08a19ace9 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -198,7 +198,7 @@ float depth = shSample(depthMap, screenCoords).x * far - depthPassthrough; float shoreFade = shSaturate(depth / 50.0); - float2 nCoord = float2(0.0); + float2 nCoord = float2(0,0); nCoord = UV * (WAVE_SCALE * 0.05) + WIND_DIR * waterTimer * (WIND_SPEED*0.04); float3 normal0 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.015,-waterTimer*0.005)).rgb - 1.0; @@ -238,12 +238,12 @@ // sunlight scattering float3 pNormal = float3(0,1,0); - vec3 lR = reflect(lVec, lNormal); - vec3 llR = reflect(lVec, pNormal); + float3 lR = reflect(lVec, lNormal); + float3 llR = reflect(lVec, pNormal); float s = shSaturate(dot(lR, vVec)*2.0-1.2); float lightScatter = shSaturate(dot(-lVec,lNormal)*0.7+0.3) * s * SCATTER_AMOUNT * waterSunFade_sunHeight.x * shSaturate(1.0-exp(-waterSunFade_sunHeight.y)); - float3 scatterColour = shLerp(vec3(SCATTER_COLOUR)*vec3(1.0,0.4,0.0), SCATTER_COLOUR, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + float3 scatterColour = shLerp(float3(SCATTER_COLOUR)*float3(1.0,0.4,0.0), SCATTER_COLOUR, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); // fresnel float ior = (cameraPos.y>0)?(1.333/1.0):(1.0/1.333); //air to water; water to air @@ -284,7 +284,7 @@ waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); float3 watercolour = (float3(0.0078, 0.5176, 0.700)+waterSunColour)*waterGradient*2.0; float3 waterext = float3(0.6, 0.9, 1.0);//water extinction - watercolour = mix(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + watercolour = shLerp(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); float darkness = VISIBILITY*2.0; darkness = clamp((cameraPos.y+darkness)/darkness,0.2,1.0); @@ -299,6 +299,7 @@ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); } + shOutputColour(0).w = 1; } #endif diff --git a/files/materials/water.shaderset b/files/materials/water.shaderset index 754ac8e49..5e070a45a 100644 --- a/files/materials/water.shaderset +++ b/files/materials/water.shaderset @@ -3,7 +3,7 @@ shader_set water_vertex source water.shader type vertex profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_2_0 + profiles_hlsl vs_3_0 vs_2_0 } shader_set water_fragment @@ -11,5 +11,5 @@ shader_set water_fragment source water.shader type fragment profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 - profiles_hlsl ps_2_0 + profiles_hlsl ps_3_0 ps_2_0 } From f8e54b401bc0b65a8bda04f0e332775c1f9a81f9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 1 Aug 2012 09:09:00 +0200 Subject: [PATCH 281/298] fixed linkage problem --- components/files/ogreplugin.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/files/ogreplugin.hpp b/components/files/ogreplugin.hpp index 7a0b5841e..c5292b3a2 100644 --- a/components/files/ogreplugin.hpp +++ b/components/files/ogreplugin.hpp @@ -35,7 +35,7 @@ namespace Ogre { #if (BOOST_VERSION <= 104300) namespace boost { namespace filesystem { -path absolute(const path& p, const path& base=current_path()) { +inline path absolute(const path& p, const path& base=current_path()) { // call obsolete version of this function on older boost return complete(p, base); } @@ -50,7 +50,7 @@ namespace Files { /** * \brief Loads Ogre plugin with given name. - * + * * \param pluginDir absolute path to plugins * \param pluginName plugin name, for example "RenderSystem_GL" * \param ogreRoot Ogre::Root instance @@ -61,4 +61,4 @@ bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre:: } -#endif /* COMPONENTS_FILES_OGREPLUGIN_H */ \ No newline at end of file +#endif /* COMPONENTS_FILES_OGREPLUGIN_H */ From ebd8b064f64fa7d1f5c0e346135c0c0d5c06173a Mon Sep 17 00:00:00 2001 From: gugus Date: Wed, 1 Aug 2012 12:21:42 +0200 Subject: [PATCH 282/298] getStartingAngle script instruction --- apps/openmw/mwscript/docs/vmformat.txt | 8 ++++- .../mwscript/transformationextensions.cpp | 34 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index b979420dd..2f08353db 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -169,5 +169,11 @@ op 0x2000164: SetScale op 0x2000165: SetScale, explicit reference op 0x2000166: SetAngle op 0x2000167: SetAngle, explicit reference -opcodes 0x2000168-0x3ffffff unused +op 0x2000168: GetScale +op 0x2000169: GetScale, explicit reference +op 0x200016a: GetAngle +op 0x200016b: GetAngle, explicit reference +op 0x200016c: GetStartingAngle +op 0x200016d: GetStartingAngle, explicit reference +opcodes 0x200016e-0x3ffffff unused diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 2ea80c0d8..1c6940710 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -82,7 +82,7 @@ namespace MWScript }; template - class OpGetAngle : public Interpreter::Opcode0 + class OpGetStartingAngle : public Interpreter::Opcode0 { public: @@ -108,6 +108,33 @@ namespace MWScript } }; + template + class OpGetAngle : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + if(axis == "X") + { + runtime.push(Ogre::Radian(ptr.getCellRef().pos.rot[0]).valueDegrees()); + } + if(axis == "Y") + { + runtime.push(Ogre::Radian(ptr.getCellRef().pos.rot[1]).valueDegrees()); + } + if(axis == "Z") + { + runtime.push(Ogre::Radian(ptr.getCellRef().pos.rot[2]).valueDegrees()); + } + } + }; + const int opcodeSetScale = 0x2000164; const int opcodeSetScaleExplicit = 0x2000165; const int opcodeSetAngle = 0x2000166; @@ -116,6 +143,8 @@ namespace MWScript const int opcodeGetScaleExplicit = 0x2000169; const int opcodeGetAngle = 0x200016a; const int opcodeGetAngleExplicit = 0x200016b; + const int opcodeGetStartingAngle = 0x200016c; + const int opcodeGetStartingAngleExplicit = 0x200016d; void registerExtensions (Compiler::Extensions& extensions) { @@ -123,6 +152,7 @@ namespace MWScript extensions.registerFunction("getscale",'f',"",opcodeGetScale,opcodeGetScaleExplicit); extensions.registerInstruction("setangle","Sf",opcodeSetAngle,opcodeSetAngleExplicit); extensions.registerFunction("getangle",'f',"S",opcodeGetAngle,opcodeGetAngleExplicit); + extensions.registerFunction("getstartingangle",'f',"S",opcodeGetStartingAngle,opcodeGetStartingAngleExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -135,6 +165,8 @@ namespace MWScript interpreter.installSegment5(opcodeGetScaleExplicit,new OpGetScale); interpreter.installSegment5(opcodeGetAngle,new OpGetAngle); interpreter.installSegment5(opcodeGetAngleExplicit,new OpGetAngle); + interpreter.installSegment5(opcodeGetStartingAngle,new OpGetStartingAngle); + interpreter.installSegment5(opcodeGetStartingAngleExplicit,new OpGetStartingAngle); } } } From 18fd14ff75441a8f2464dfedf1676bb14dd69efb Mon Sep 17 00:00:00 2001 From: Michael Mc Donnell Date: Mon, 30 Jul 2012 14:18:02 -0400 Subject: [PATCH 283/298] Workaround for FindMyGUI.cmake path space bug on Windows. See bug 353 http://bugs.openmw.org/issues/353 --- cmake/FindMyGUI.cmake | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 7278fe200..e2fefac7d 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -128,9 +128,11 @@ ELSE (WIN32) #Unix ENDIF (WIN32) #Do some preparation -SEPARATE_ARGUMENTS(MYGUI_INCLUDE_DIRS) -SEPARATE_ARGUMENTS(MYGUI_LIBRARIES) -SEPARATE_ARGUMENTS(MYGUI_PLATFORM_LIBRARIES) +IF (NOT WIN32) # This does not work on Windows for paths with spaces in them + SEPARATE_ARGUMENTS(MYGUI_INCLUDE_DIRS) + SEPARATE_ARGUMENTS(MYGUI_LIBRARIES) + SEPARATE_ARGUMENTS(MYGUI_PLATFORM_LIBRARIES) +ENDIF (NOT WIN32) SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} ${FREETYPE_LIBRARIES}) From e9aac4512b8563d5b5e65e9c05ecfd2502a8207f Mon Sep 17 00:00:00 2001 From: gugus Date: Mon, 30 Jul 2012 19:45:40 +0200 Subject: [PATCH 284/298] Build fix for Windows Fixes a number of places where uint was used and not defined. This commit was originally authored by gus. It rebased by Michael Mc Donnell to take recent commit fixes into account. --- apps/openmw/mwrender/terrainmaterial.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index 1a2d96a14..9ef4652f6 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -86,15 +86,15 @@ namespace MWRender normalMap->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getTerrainNormalMap ()->getName()))); normalMap->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); - uint maxLayers = getMaxLayers(terrain); - uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); - uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + Ogre::uint maxLayers = getMaxLayers(terrain); + Ogre::uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); + Ogre::uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); p->mShaderProperties.setProperty ("num_layers", sh::makeProperty(new sh::StringValue(Ogre::StringConverter::toString(numLayers)))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty(new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); // blend maps - for (uint i = 0; i < numBlendTextures; ++i) + for (Ogre::uint i = 0; i < numBlendTextures; ++i) { sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i)); blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getBlendTextureName(i)))); @@ -102,7 +102,7 @@ namespace MWRender } // layer maps - for (uint i = 0; i < numLayers; ++i) + for (Ogre::uint i = 0; i < numLayers; ++i) { sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getLayerTextureName(i, 0)))); @@ -111,7 +111,7 @@ namespace MWRender } // shadow - for (uint i = 0; i < 3; ++i) + for (Ogre::uint i = 0; i < 3; ++i) { sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); From 7f802a22b5a68437709b473310c81d6055ff9784 Mon Sep 17 00:00:00 2001 From: Michael Mc Donnell Date: Wed, 1 Aug 2012 17:50:42 -0400 Subject: [PATCH 285/298] Compare with stream position instead of int. Fixes compilation of Debug build on Windows. --- components/bsa/bsa_file.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 053191c9b..1700e1aab 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -200,7 +200,7 @@ void BSAFile::readHeader() input.read(&stringBuf[0], stringBuf.size()); // Check our position - assert(input.tellg() == static_cast (12+dirsize)); + assert(input.tellg() == std::streampos(12+dirsize)); // Calculate the offset of the data buffer. All file offsets are // relative to this. 12 header bytes + directory + hash table From ff627706573cc0c624cadd79c692316de80e5686 Mon Sep 17 00:00:00 2001 From: greye Date: Fri, 3 Aug 2012 14:42:09 +0400 Subject: [PATCH 286/298] World::isUnderwater(), World::isSwimming() --- apps/openmw/mwbase/world.hpp | 3 +++ apps/openmw/mwrender/renderingmanager.cpp | 20 +++++++++-------- apps/openmw/mwrender/renderingmanager.hpp | 1 - apps/openmw/mwrender/water.cpp | 20 ++++++----------- apps/openmw/mwrender/water.hpp | 3 ++- apps/openmw/mwworld/worldimp.cpp | 26 +++++++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 3 +++ 7 files changed, 52 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f257b723e..d9e19bead 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -249,6 +249,9 @@ namespace MWBase ///< @return true if it is possible to place on object at specified cursor location virtual void processChangedSettings (const Settings::CategorySettingVector& settings) = 0; + + virtual bool isSwimming(const MWWorld::Ptr &object) = 0; + virtual bool isUnderwater(const ESM::Cell &cell, const Ogre::Vector3 &pos) = 0; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ae0e57219..cbf799d7a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -275,11 +275,20 @@ void RenderingManager::update (float duration){ mLocalMap->updatePlayer( mRendering.getCamera()->getRealPosition(), mRendering.getCamera()->getRealOrientation() ); - checkUnderwater(); + if (mWater) { + Ogre::Vector3 cam = mRendering.getCamera()->getRealPosition(); - if (mWater) + MWBase::World *world = MWBase::Environment::get().getWorld(); + + mWater->updateUnderwater( + world->isUnderwater( + *world->getPlayer().getPlayer().getCell()->cell, + Ogre::Vector3(cam.x, -cam.z, cam.y)) + ); mWater->update(duration); + } } + void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store){ if(store->cell->data.flags & store->cell->HasWater || ((!(store->cell->data.flags & ESM::Cell::Interior)) @@ -459,13 +468,6 @@ void RenderingManager::toggleLight() setAmbientMode(); } -void RenderingManager::checkUnderwater() -{ - if(mWater) - { - mWater->checkUnderwater( mRendering.getCamera()->getRealPosition().y ); - } -} void RenderingManager::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index d6a372d10..12cbee9b8 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -91,7 +91,6 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList void scaleObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& scale); void rotateObject (const MWWorld::Ptr& ptr, const::Ogre::Quaternion& orientation); - void checkUnderwater(); void setWaterHeight(const float height); void toggleWater(); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 92fc97b3b..d5b93b7cb 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -184,22 +184,16 @@ void Water::toggle() updateVisible(); } -void Water::checkUnderwater(float y) +void +Water::updateUnderwater(bool underwater) { - if (!mActive) - { + if (!mActive) { return; } - - if ((mIsUnderwater && y > mTop) || !mWater->isVisible() || mCamera->getPolygonMode() != Ogre::PM_SOLID) - { - mIsUnderwater = false; - } - - if (!mIsUnderwater && y < mTop && mWater->isVisible() && mCamera->getPolygonMode() == Ogre::PM_SOLID) - { - mIsUnderwater = true; - } + mIsUnderwater = + underwater && + mWater->isVisible() && + mCamera->getPolygonMode() == Ogre::PM_SOLID; updateVisible(); } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index d0a5a4352..f56ba7410 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -101,7 +101,8 @@ namespace MWRender { void processChangedSettings(const Settings::CategorySettingVector& settings); - void checkUnderwater(float y); + /// Updates underwater state accordingly + void updateUnderwater(bool underwater); void changeCell(const ESM::Cell* cell); void setHeight(const float height); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 67d8a7cec..4bdf3f948 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -18,6 +18,8 @@ #include "manualref.hpp" #include "cellfunctors.hpp" +#include + using namespace Ogre; namespace @@ -1095,4 +1097,28 @@ namespace MWWorld { mRendering->getTriangleBatchCount(triangles, batches); } + + bool + World::isSwimming(const MWWorld::Ptr &object) + { + /// \todo add check ifActor() - only actors can swim + float *fpos = object.getRefData().getPosition().pos; + Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]); + + /// \fixme should rely on object height + pos.z += 30; + + return isUnderwater(*object.getCell()->cell, pos); + } + + bool + World::isUnderwater(const ESM::Cell &cell, const Ogre::Vector3 &pos) + { + if (cell.data.flags & ESM::Cell::HasWater == 0) { + return false; + } + bool res = pos.z < cell.water; + std::cout << "World::isUnderwater(" << pos.z << "):" << res << std::endl; + return res; + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index d39871c21..f80b88be2 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -270,6 +270,9 @@ namespace MWWorld ///< @return true if it is possible to place on object at specified cursor location virtual void processChangedSettings(const Settings::CategorySettingVector& settings); + + virtual bool isSwimming(const MWWorld::Ptr &object); + virtual bool isUnderwater(const ESM::Cell &cell, const Ogre::Vector3 &pos); }; } From 0f3cb5667fa85c5e36a3dc783e16e8a5e4cb32e6 Mon Sep 17 00:00:00 2001 From: Edmondo Tommasina Date: Fri, 3 Aug 2012 22:04:02 +0200 Subject: [PATCH 287/298] esm_reader.hpp: fix std::runtime_error compile error --- components/esm/esm_reader.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index 13f1f4a01..c369f01f5 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include From 11d0761528d8f5ee3cbea99e6206600147b92e28 Mon Sep 17 00:00:00 2001 From: Edmondo Tommasina Date: Fri, 3 Aug 2012 22:10:51 +0200 Subject: [PATCH 288/298] terrainmaterial.cpp: fix std::runtime_error compile error --- apps/openmw/mwrender/terrainmaterial.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index 9ef4652f6..5ef9fe58f 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -2,6 +2,8 @@ #include +#include + #include namespace From b0b3ebe123bb710838408e5ec09d2336805fedd3 Mon Sep 17 00:00:00 2001 From: Edmondo Tommasina Date: Fri, 3 Aug 2012 21:53:38 +0200 Subject: [PATCH 289/298] nif_file.hpp: include type definitions to fix build --- components/nif/nif_file.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 624066317..143c06993 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -39,6 +39,7 @@ #include "record.hpp" #include "nif_types.hpp" +#include "components/esm/loadland.hpp" namespace Nif { From e4dc01832bd8fc5cd0ddbd262d3c12cf8fb3d9d0 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 4 Aug 2012 09:18:20 +0200 Subject: [PATCH 290/298] fixing object rotation script instructions (wrong argument type and missing error handling) --- .../mwscript/transformationextensions.cpp | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 532bc8cee..6f906343c 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -66,18 +66,20 @@ namespace MWScript float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); float az = Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees(); - if(axis == "X") + if (axis == "x") { MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az); } - if(axis == "Y") + else if (axis == "y") { MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az); } - if(axis == "Z") + else if (axis == "z") { MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); } + else + throw std::runtime_error ("invalid ration axis: " + axis); } }; @@ -93,18 +95,20 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - if(axis == "X") + if (axis == "x") { runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees()); } - if(axis == "Y") + else if (axis == "y") { runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees()); } - if(axis == "Z") + else if (axis == "z") { runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees()); } + else + throw std::runtime_error ("invalid ration axis: " + axis); } }; @@ -120,18 +124,20 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - if(axis == "X") + if (axis=="x") { runtime.push(Ogre::Radian(ptr.getCellRef().pos.rot[0]).valueDegrees()); } - if(axis == "Y") + else if (axis=="y") { runtime.push(Ogre::Radian(ptr.getCellRef().pos.rot[1]).valueDegrees()); } - if(axis == "Z") + else if (axis=="z") { runtime.push(Ogre::Radian(ptr.getCellRef().pos.rot[2]).valueDegrees()); } + else + throw std::runtime_error ("invalid ration axis: " + axis); } }; @@ -150,9 +156,9 @@ namespace MWScript { extensions.registerInstruction("setscale","f",opcodeSetScale,opcodeSetScaleExplicit); extensions.registerFunction("getscale",'f',"",opcodeGetScale,opcodeGetScaleExplicit); - extensions.registerInstruction("setangle","Sf",opcodeSetAngle,opcodeSetAngleExplicit); - extensions.registerFunction("getangle",'f',"S",opcodeGetAngle,opcodeGetAngleExplicit); - extensions.registerFunction("getstartingangle",'f',"S",opcodeGetStartingAngle,opcodeGetStartingAngleExplicit); + extensions.registerInstruction("setangle","cf",opcodeSetAngle,opcodeSetAngleExplicit); + extensions.registerFunction("getangle",'f',"c",opcodeGetAngle,opcodeGetAngleExplicit); + extensions.registerFunction("getstartingangle",'f',"c",opcodeGetStartingAngle,opcodeGetStartingAngleExplicit); } void installOpcodes (Interpreter::Interpreter& interpreter) From 994e33e3d27e04047f97ae3504f6838cd662abef Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 4 Aug 2012 09:34:49 +0200 Subject: [PATCH 291/298] fixed object rotation code --- apps/openmw/mwworld/worldimp.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 67d8a7cec..c1d7763ce 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -606,10 +606,10 @@ namespace MWWorld ptr.getRefData().getPosition().rot[1] = Ogre::Degree(y).valueRadians(); ptr.getRefData().getPosition().rot[2] = Ogre::Degree(z).valueRadians(); - Ogre::Quaternion rotx(Ogre::Degree(x),Ogre::Vector3::UNIT_X); - Ogre::Quaternion roty(Ogre::Degree(y),Ogre::Vector3::UNIT_Y); - Ogre::Quaternion rotz(Ogre::Degree(z),Ogre::Vector3::UNIT_Z); - ptr.getRefData().getBaseNode()->setOrientation(rotz*roty*rotx); + Ogre::Quaternion rotx(Ogre::Degree(-x),Ogre::Vector3::UNIT_X); + Ogre::Quaternion roty(Ogre::Degree(-y),Ogre::Vector3::UNIT_Y); + Ogre::Quaternion rotz(Ogre::Degree(-z),Ogre::Vector3::UNIT_Z); + ptr.getRefData().getBaseNode()->setOrientation(rotx*roty*rotz); mPhysics->rotateObject(ptr.getRefData().getHandle(),ptr.getRefData().getBaseNode()->getOrientation()); } @@ -1074,7 +1074,7 @@ namespace MWWorld Ogre::Vector3 orig = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1); - + float len = (pos.pos[2] >= 0) ? pos.pos[2] : -pos.pos[2]; len += 100.0; From 16ad97610d2e21ce2608913a928027e47b9befa1 Mon Sep 17 00:00:00 2001 From: greye Date: Sat, 4 Aug 2012 11:54:42 +0400 Subject: [PATCH 292/298] add support for some scripting player control switches --- apps/openmw/mwinput/inputmanager.cpp | 84 +++++++++++++++------- apps/openmw/mwinput/inputmanager.hpp | 2 + apps/openmw/mwscript/controlextensions.cpp | 6 ++ apps/openmw/mwworld/worldimp.cpp | 6 +- 4 files changed, 68 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwinput/inputmanager.cpp b/apps/openmw/mwinput/inputmanager.cpp index 3a8315b71..6bda77ea8 100644 --- a/apps/openmw/mwinput/inputmanager.cpp +++ b/apps/openmw/mwinput/inputmanager.cpp @@ -90,6 +90,7 @@ namespace MWInput bool mDragDrop; + std::map mControlSwitch; /* InputImpl Methods */ public: @@ -339,6 +340,14 @@ private: poller.bind(A_Jump, KC_E); poller.bind(A_Crouch, KC_LCONTROL); + + mControlSwitch["playercontrols"] = true; + mControlSwitch["playerfighting"] = true; + mControlSwitch["playerjumping"] = true; + mControlSwitch["playerlooking"] = true; + mControlSwitch["playermagic"] = true; + mControlSwitch["playerviewswitch"] = true; + mControlSwitch["vanitymode"] = true; } void setDragDrop(bool dragDrop) @@ -366,33 +375,35 @@ private: // Configure player movement according to keyboard input. Actual movement will // be done in the physics system. - if (poller.isDown(A_MoveLeft)) - { - player.setAutoMove (false); - player.setLeftRight (1); - } - else if (poller.isDown(A_MoveRight)) - { - player.setAutoMove (false); - player.setLeftRight (-1); + if (mControlSwitch["playercontrols"]) { + if (poller.isDown(A_MoveLeft)) + { + player.setAutoMove (false); + player.setLeftRight (1); + } + else if (poller.isDown(A_MoveRight)) + { + player.setAutoMove (false); + player.setLeftRight (-1); + } + else + player.setLeftRight (0); + + if (poller.isDown(A_MoveForward)) + { + player.setAutoMove (false); + player.setForwardBackward (1); + } + else if (poller.isDown(A_MoveBackward)) + { + player.setAutoMove (false); + player.setForwardBackward (-1); + } + else + player.setForwardBackward (0); } - else - player.setLeftRight (0); - - if (poller.isDown(A_MoveForward)) - { - player.setAutoMove (false); - player.setForwardBackward (1); - } - else if (poller.isDown(A_MoveBackward)) - { - player.setAutoMove (false); - player.setForwardBackward (-1); - } - else - player.setForwardBackward (0); - if (poller.isDown(A_Jump)) + if (poller.isDown(A_Jump) && mControlSwitch["playerjumping"]) player.setUpDown (1); else if (poller.isDown(A_Crouch)) player.setUpDown (-1); @@ -423,6 +434,24 @@ private: guiEvents->enabled = false; } } + + void toggleControlSwitch(std::string sw, bool value) + { + if (mControlSwitch[sw] == value) { + return; + } + /// \note 7 switches at all, if-else is relevant + if (sw == "playercontrols") { + player.setLeftRight(0); + player.setForwardBackward(0); + player.setAutoMove(false); + } else if (sw == "playerjumping") { + /// \fixme maybe crouching at this time + player.setUpDown(0); + } + mControlSwitch[sw] = value; + } + }; /***CONSTRUCTOR***/ @@ -471,4 +500,9 @@ private: if (changeRes) impl->adjustMouseRegion(Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video")); } + + void MWInputManager::toggleControlSwitch(std::string sw, bool value) + { + impl->toggleControlSwitch(sw, value); + } } diff --git a/apps/openmw/mwinput/inputmanager.hpp b/apps/openmw/mwinput/inputmanager.hpp index 4ef3df137..2486f82d6 100644 --- a/apps/openmw/mwinput/inputmanager.hpp +++ b/apps/openmw/mwinput/inputmanager.hpp @@ -57,6 +57,8 @@ namespace MWInput void processChangedSettings(const Settings::CategorySettingVector& changed); void setDragDrop(bool dragDrop); + + void toggleControlSwitch(std::string sw, bool value); }; } #endif diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 24d8bcf1e..084698c5b 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -14,6 +14,8 @@ #include "../mwmechanics/npcstats.hpp" +#include "../mwinput/inputmanager.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -36,6 +38,10 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { + MWBase::Environment::get() + .getInputManager() + ->toggleControlSwitch(mControl, mEnable); + if (mEnable) std::cout << "enable: " << mControl << std::endl; else diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4bdf3f948..8eb9a1c84 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -18,8 +18,6 @@ #include "manualref.hpp" #include "cellfunctors.hpp" -#include - using namespace Ogre; namespace @@ -1117,8 +1115,6 @@ namespace MWWorld if (cell.data.flags & ESM::Cell::HasWater == 0) { return false; } - bool res = pos.z < cell.water; - std::cout << "World::isUnderwater(" << pos.z << "):" << res << std::endl; - return res; + return pos.z < cell.water; } } From 1db797836192232dc04cccc46129e9b61aebb1e6 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 4 Aug 2012 22:03:14 +0200 Subject: [PATCH 293/298] silenced a warning --- apps/openmw/mwworld/worldimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 203f5f915..8fb7b1ba7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1112,7 +1112,7 @@ namespace MWWorld bool World::isUnderwater(const ESM::Cell &cell, const Ogre::Vector3 &pos) { - if (cell.data.flags & ESM::Cell::HasWater == 0) { + if (!(cell.data.flags & ESM::Cell::HasWater)) { return false; } return pos.z < cell.water; From a63fd77ccca398c76b6cfd69580ad01277c01e3e Mon Sep 17 00:00:00 2001 From: Edmondo Tommasina Date: Sat, 4 Aug 2012 22:58:26 +0200 Subject: [PATCH 294/298] Revert "esm_reader.hpp: fix std::runtime_error compile error" This reverts commit 0f3cb5667fa85c5e36a3dc783e16e8a5e4cb32e6. --- components/esm/esm_reader.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index c369f01f5..13f1f4a01 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include From 32b167ce2bd11068cffe119e6c44b6c8ae9de33e Mon Sep 17 00:00:00 2001 From: Edmondo Tommasina Date: Sat, 4 Aug 2012 23:02:27 +0200 Subject: [PATCH 295/298] Revert "nif_file.hpp: include type definitions to fix build" This reverts commit b0b3ebe123bb710838408e5ec09d2336805fedd3. --- components/nif/nif_file.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 143c06993..624066317 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -39,7 +39,6 @@ #include "record.hpp" #include "nif_types.hpp" -#include "components/esm/loadland.hpp" namespace Nif { From c08e098d7f9218089e38610f1fc5b9faaee7c1c0 Mon Sep 17 00:00:00 2001 From: Edmondo Tommasina Date: Sat, 4 Aug 2012 22:51:43 +0200 Subject: [PATCH 296/298] esm_reader.cpp: fix std::runtime_error compile error --- components/esm/esm_reader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esm/esm_reader.cpp b/components/esm/esm_reader.cpp index a2cf69ddc..95ef46e81 100644 --- a/components/esm/esm_reader.cpp +++ b/components/esm/esm_reader.cpp @@ -1,4 +1,5 @@ #include "esm_reader.hpp" +#include namespace ESM { From 20deb97a09bf85b1456421609cc85848a04ee594 Mon Sep 17 00:00:00 2001 From: Edmondo Tommasina Date: Sat, 4 Aug 2012 23:14:53 +0200 Subject: [PATCH 297/298] nif_file.hpp: add stdint.h include to fix build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It fixes such compile errors as: * error: ‘uint8_t’ does not name a type * error: ‘uint16_t’ does not name a type * error: ‘uint32_t’ does not name a type --- components/nif/nif_file.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index 624066317..d236bc460 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -36,6 +36,7 @@ #include #include #include +#include #include "record.hpp" #include "nif_types.hpp" From dbcd4a8b5bef088bc10bd6e4c92b72848b788007 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 5 Aug 2012 00:06:19 +0200 Subject: [PATCH 298/298] compatibility fix --- components/nif/nif_file.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/nif/nif_file.hpp b/components/nif/nif_file.hpp index d236bc460..a4eaa8990 100644 --- a/components/nif/nif_file.hpp +++ b/components/nif/nif_file.hpp @@ -36,7 +36,8 @@ #include #include #include -#include + +#include #include "record.hpp" #include "nif_types.hpp"