khanat-opennel-code/code/nel/include/nel/3d/skeleton_model.h
2010-06-12 12:17:29 +02:00

655 lines
23 KiB
C++

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef NL_SKELETON_MODEL_H
#define NL_SKELETON_MODEL_H
#include "nel/misc/types_nl.h"
#include "nel/misc/object_vector.h"
#include "nel/3d/transform_shape.h"
#include "nel/3d/bone.h"
#include "nel/3d/mrm_level_detail.h"
#include "nel/3d/lod_character_instance.h"
#include "nel/3d/skeleton_spawn_script.h"
namespace NLMISC
{
class CAABBox;
}
namespace NL3D
{
class CSkeletonShape;
class CLodCharacterManager;
// ***************************************************************************
// ClassIds.
const NLMISC::CClassId SkeletonModelId=NLMISC::CClassId(0x7d4703b4, 0x43ad6ab1);
// ***************************************************************************
/** Struct used when SkinGroup is used.
* Used to sort specular render pass of all Skins (sorted by specular map).
*/
class CSkinSpecularRdrPass
{
public:
// The skin index in the grouped rendering.
uint16 SkinIndex;
// The index of the skin rdrPass
uint16 RdrPassIndex;
// The texture id of the specular texture. This is the sort Key.
uint32 SpecId;
bool operator<(const CSkinSpecularRdrPass &o) const
{
return SpecId<o.SpecId;
}
};
// ***************************************************************************
/**
* A Skeleton model, instance of CSkeletonShape.
*
* Skeletons sons are added with bindSkin(), stickObject(). They are removed auto at dtor.
*
* \author Lionel Berenguier
* \author Nevrax France
* \date 2001
*/
class CSkeletonModel : public CTransformShape
{
public:
/// max number of bones supported in skeleton
enum { MaxNumBones = 256 };
/// Call at the begining of the program, to register the model
static void registerBasic();
public:
/** The list of CBone, created from the CSkeletonShape.
* They are odered in depth-first order.
*/
std::vector<CBone> Bones;
public:
/// \name IAnimatable Interface (registering only IAnimatable sons (bones)).
// @{
enum TAnimValues
{
OwnerBit= CTransformShape::AnimValueLast,
SpawnScriptValue,
AnimValueLast,
};
/// From IAnimatable
virtual IAnimatedValue* getValue (uint valueId);
/// From IAnimatable
virtual const char *getValueName (uint valueId) const;
/// Default Track Values for SpawnScriptValue is empty string
virtual ITrack* getDefaultTrack (uint valueId);
/// Register bones into chanMixer.
virtual void registerToChannelMixer(CChannelMixer *chanMixer, const std::string &prefix);
/// Return the name of the spwan script track
static const char *getSpawnScriptValueName() {return "spawn_script";}
// @}
/// \name Skin operation.
// @{
/** bind a skin to the skeleton. NB: ~CTransform() calls detachSkeletonSon().
* NB: nlassert() if there is too many skins/sticked objects on this skeleton (more than 255).
* NB: an object can't be skinned and sticked at same time :)
* NB: replaced if already here.
* NB: when a skin is binded, the command hide(), show(), ... have no effect on it, until it is detachSkeletonSon()-ed
* NB: For Skins, all Hrc/Clip/UpdateList link is done here
* \return false if mi is not skinnable, true otherwise
*/
bool bindSkin(CTransform *mi);
/** parent a CTransform to a bone of the skeleton. NB: ~CTransform() calls detachSkeletonSon().
* NB: nlassert() if there is too many skins/sticked objects on this skeleton (more than 255).
* NB: an object can't be skinned and sticked at same time :)
* NB: replaced if already here.
* NB: mi is made son of skeleton model in Traversals Hrc, and change are made at render() for ClipTrav.
*/
void stickObject(CTransform *mi, uint boneId);
/** same method as stickObject(), but if you set forceCLod as true, then this object will be visible
* even if the skeleton father is in CLod state (ie displayed with a CLodCharacterShape)
* NB: if "mi" is a skeleton model, forceCLod is considerer true, whatever the value passed in.
*/
void stickObjectEx(CTransform *mi, uint boneId, bool forceCLod);
/** unparent a CTransform from a bone of the skeleton, or unbind a skin. No-op if not a son of this skeleton
* NB: mi is made son of Root in Traversals Hrc, and change are made at render() for ClipTrav.
* NB: For Skins, all Hrc/Clip/UpdateList link is done here
*/
void detachSkeletonSon(CTransform *mi);
/** Force the skeletonModel to recompute at next render which skins to render, at wich pass.
* If you call setOpacity()/setTransparency() on one of the skins binded to the skeleton, you should call this
* method, else strange result may occurs.
* NB: this is automatically called by bindSkin()/detachSkeletonSon()
*/
void dirtSkinRenderLists() {_SkinToRenderDirty= true;}
/** return the set of skins added with bindSkin()
*/
const std::set<CTransform*> &getSkins() const {return _Skins;};
/** return the set of stickedObjects added with stickObject()
*/
const std::set<CTransform*> &getStickedObjects() const {return _StickedObjects;};
// @}
/// \name Skin/BoneUsage Accessor. Called by CMeshInstance only.
// @{
typedef enum {UsageNormal, UsageForced, UsageCLodForced} TBoneUsageType;
/** increment the refCount of the ith bone.
* set boneUsageType to UsageNormal if enable Skeleton Bone degradation (skins)
* Forced usage are for Sticked objects
*/
void incBoneUsage(uint i, TBoneUsageType boneUsageType);
/// decrement the refCount of the ith bone. set forced to the same param passed when incBoneUsage()
void decBoneUsage(uint i, TBoneUsageType boneUsageType);
/** This method update boneUsage (must be of size of Bones).
* It's flag boneUsage[boneId] to true, and all parents of boneId.
*/
void flagBoneAndParents(uint32 boneId, std::vector<bool> &boneUsage) const;
// @}
/// \name Misc.
// @{
/// return, from skeleton shape, the BoneIdByName. -1 if not here.
sint32 getBoneIdByName(const std::string &name) const;
/// Tell if a bone has been computed in the last frame or not. false if boneId is invalid
bool isBoneComputed(uint boneId) const;
/** Force to compute a bone right now (useful if the skeleton is not visible and the bone position is needed)
* no-op is bone not present or already computed
* \return true if good indes & bone recomputed
*/
bool forceComputeBone(uint boneId);
/// return the number of bones currently animated/computed (because of bindSkin()/stickObject() / Lod system).
uint getNumBoneComputed() const {return (uint)_BoneToCompute.size();}
/** change the Lod Bone interpolation distance (in meters). If 0, interpolation is disabled.
* The smaller this value is, the more Lod skeleton system will "pop". Default is 0.5 meters.
*/
void setInterpolationDistance(float dist);
/// see setInterpolationDistance()
float getInterpolationDistance() const;
/** if Bones[boneId] is "Computed" (usage/lod), return Bones[boneId].getBoneSkinMatrix()
* else return parent ones (recurs, but precomputed)
*/
const NLMISC::CMatrix &getActiveBoneSkinMatrix(uint boneId) const;
/** Tool function, especially for animation bake. It updates all bones (independent of bone usage,
* and lod interpolation), and take a user skeleton worldMatrix as input.
* NB: no detail animation is performed here, just the compute of bone hierarchy.
* NB: also, no special AnimCtrl (IK etc....) is performed here
*/
void computeAllBones(const CMatrix &modelWorldMatrix);
/** Retrieve the current approx BBox around the skeleton, computed in the last CSene::render().
* for all computed bones, extend the bbox with their pos
* \param bbox return the bbox of the skinned skeleton, local to the skeleton. If the skeleton was clipped, the bbox
* is not modified.
* \param computeInWorld true if want to get the bbox in world.
* \return true if the bbox is computed, false otherwise.
*/
bool computeRenderedBBox(NLMISC::CAABBox &bbox, bool computeInWorld= false);
/** same as computeRenderedBBox(), always in world, but use the bone max sphere to enlarge the bbox
* NB: sticked objects don't influence the result
*/
bool computeRenderedBBoxWithBoneSphere(NLMISC::CAABBox &bbox, bool computeInWorld= true);
/** same as computeRenderedBBox() but force animation and compute of all bones => don't need render(), but slower.
* for all used bones, extend the bbox with their pos
* NB: AnimCtrl are not evaluated by this method (since computed with 0 pos).
* \param bbox return the bbox of the skinned skeleton, local to the skeleton. If the skeleton is not skinned/sticked
* at all, bbox is not modified.
* \param forceCompute force evalution even if not skinned
* \param computeInWorld true if want to get the bbox in world.
* \return true if the bbox is computed, false otherwise.
*/
bool computeCurrentBBox(NLMISC::CAABBox &bbox, bool forceCompute = false, bool computeInWorld= false);
/// return the current Script of Spwan.
const std::string &getSpawnScript() const {return _SpawnScript.Value;}
/// set the current Spawn Script (for debug, should only be used by animation)
void setSpawnScript(const std::string &s) {_SpawnScript.Value= s;}
/// SkeletonSpawnScript special: World Spawned objects are still relative to this position (default: Null)
void setSSSWOPos(const CVector &pos) {_SSSWOPos= pos;}
const CVector &getSSSWOPos() const {return _SSSWOPos;}
/// SkeletonSpawnScript special: World Spawned objects are still relative to this direction (default: J)
void setSSSWODir(const CVector &dir) {_SSSWODir= dir;}
const CVector &getSSSWODir() const {return _SSSWODir;}
/// don't use
CSkeletonSpawnScript &getSSSScript() {return _SpawnScriptEvaluator;}
/// see CTransform::fastIntersect()
virtual bool fastIntersect(const NLMISC::CVector &p0, const NLMISC::CVector &dir, float &dist2D, float &distZ, bool computeDist2D);
/** Internal. Tool method used by Skins, to remap their bones id to skeleton ones
* \param bonesName input list of bones name
* \param boneId output list of bone remapped (size==bonesName.size()). -1 if bone not found
* \param remap == boneId, but with 0 where boneId[i]==-1
*/
void remapSkinBones(const std::vector<std::string> &bonesName, std::vector<sint32> &bonesId, std::vector<uint> &remap);
// @}
/// \name CLod / Character Lod
// @{
/// enable/disable LOD
void enableLOD(bool isEnable) { _IsEnableLOD=isEnable; }
/// Change the Character Lod shape Id. set -1 if want to disable the feature (default)
void setLodCharacterShape(sint shapeId);
/// see setLodCharacterShape
sint getLodCharacterShape() const {return _CLodInstance.ShapeId;}
/// Change/get the Character Lod anim setup.
void setLodCharacterAnimId(uint animId);
uint getLodCharacterAnimId() const {return _CLodInstance.AnimId;}
void setLodCharacterAnimTime(TGlobalAnimationTime time);
TGlobalAnimationTime getLodCharacterAnimTime() const {return _CLodInstance.AnimTime;}
/// tells if the animation must loop or clamp.
void setLodCharacterWrapMode(bool wrapMode);
bool getLodCharacterWrapMode() const {return _CLodInstance.WrapMode;}
/** True if the skeleton model and his skins are to be displayed with a CLodCharacterShape, instead of the std way
* This state is modified early during the HRC Traversal. Because Clip traversal need this result.
*/
bool isDisplayedAsLodCharacter() const {return _DisplayedAsLodCharacter;}
/** This is the distance at which the skeleton use a CLodCharacterShape to display himself
* if 0, never display the skeleton as a CLodCharacterShape
*/
void setLodCharacterDistance(float dist);
/// see setLodCharacterDistance. 0 if disabled
float getLodCharacterDistance() const {return _LodCharacterDistance;}
/** Called in ClipTrav pass. Used to update the flag _DisplayedAsLodCharacter.
* return 0 if CLod not enabled. Else return a priority>0 computed from curDistance/LodCharacterDistance
* return 0 too if the skeleton is not visible or if his ancestorSkeletonModel is not visible.
* If priority is >1 then the skeleton must be displayed in CLod form
*/
float computeDisplayLodCharacterPriority() const;
/** Called in ClipTrav pass. setup the flag.
*/
void setDisplayLodCharacterFlag(bool displayCLod);
/** Call it when you want the system to recompute the Lod texture
* NB: Lod texturing is possible only in conjunction with AsyncTextureManager. Hence, instances skinned
* to the skeleton should be in AsyncTextureMode.
* For best result, you should wait that each of these instances are isAsyncTextureReady() (texture loaded)
*/
void computeLodTexture();
/// Set the emissive of the skeleton model, when it is rendered in CLod form. Default to Black
void setLodEmit(NLMISC::CRGBA emit) {_LodEmit= emit;}
NLMISC::CRGBA getLodEmit() const {return _LodEmit;}
// @}
/// \name Load balancing methods
// @{
/// Special version for skins
virtual float getNumTriangles (float distance);
/** Special version for skins. NB: skins never follow their original MRM distance setup, but follow
* this skeleton MRM setup. Default is to follow the MAX of all skins binded (ie the finer).
* NB: Unlike CMeshBaseInstance::changeMRMDistanceSetup(), this setup applies to the SkeletonModel, not the shape.
* NB: no-op if distanceFinest<0, distanceMiddle<=distanceFinest or if distanceCoarsest<=distanceMiddle.
* \param distanceFinest The MRM has its max faces when dist<=distanceFinest.
* \param distanceMiddle The MRM has 50% of its faces at dist==distanceMiddle.
* \param distanceCoarsest The MRM has faces/Divisor (ie near 0) when dist>=distanceCoarsest.
*/
void changeMRMDistanceSetup(float distanceFinest, float distanceMiddle, float distanceCoarsest);
/// Reset the Default MRM setup: follow MAX skin setup (ie the finer)
void resetDefaultMRMDistanceSetup();
// @}
/// \name CTransform traverse specialisation
// @{
/** this do :
* - call CTransformShape::traverseAnimDetail()
* - update animated bones.
*/
virtual void traverseAnimDetail();
/** If displayed as a CLod, render it, else render the skins binded to this skeleton
*/
virtual void traverseRender();
// @}
// Lighting: get the Root world position!
virtual void getLightHotSpotInWorld(CVector &modelPos, float &modelRadius) const;
/// \name AnimCtrl (IK...)
// @{
/** Set a special ctrl on a bone. see IAnimCtrl. NB: once an animCtrl is set to a bone in a skeleton,
* his bones are always computed each frame.
* set to NULL if you want to reset this bone AnimCtrl.
*/
void setBoneAnimCtrl(uint boneId, IAnimCtrl *ctrl);
IAnimCtrl *getBoneAnimCtrl(uint boneId) const;
// @}
/// \name ShadowMap CTransform Implementation
// @{
virtual void generateShadowMap(const CVector &lightDir);
virtual CShadowMap *getShadowMap();
virtual bool computeWorldBBoxForShadow(NLMISC::CAABBox &worldBB);
virtual void renderIntoSkeletonShadowMap(CSkeletonModel *rootSkeleton, CMaterial &castMat);
// @}
// TMP nico debug function : retrieve all max bone spheres in world coordinates
void getWorldMaxBoneSpheres(std::vector<NLMISC::CBSphere> &dest) const;
// ***********************
protected:
/// Constructor
CSkeletonModel();
/// Destructor
virtual ~CSkeletonModel();
/// Build link to traversals.
virtual void initModel();
private:
static CTransform *creator() {return new CSkeletonModel;}
friend class CSkeletonShape;
public:
// update if needed the renderList
void updateSkinRenderLists();
private:
// The iterator of the skeleton inserted in the scene
std::list<CSkeletonModel*>::iterator _ItSkeletonInScene;
/// skins/sticked objects
typedef std::set<CTransform*> TTransformSet;
typedef TTransformSet::iterator ItTransformSet;
/// The skins.
TTransformSet _Skins;
/// The StickedObjects.
TTransformSet _StickedObjects;
// see dirtSkinRenderLists
bool _SkinToRenderDirty;
// see _LevelDetail
bool _DefaultMRMSetup;
// Raw lists of Skins. Both for transparent and opaque pass
typedef NLMISC::CObjectVector<CTransform*, false> TTransformArray;
TTransformArray _OpaqueSkins;
TTransformArray _TransparentSkins;
// Skins which need to be animated (very rare)
TTransformArray _AnimDetailSkins;
// The level detail used to drive MRM skins
CMRMLevelDetail _LevelDetail;
// build a bug-free level detail
void buildDefaultLevelDetail();
/// \name Bone Usage.
// @{
struct CBoneUsage
{
/// The bone Usage (refCount).
uint16 Usage;
/// Same as Usage, but must be animated/computed, even if Skeleton Lods say not (stickedObjects).
uint16 ForcedUsage;
/** Same as ForcedUsage, but must be animated/computed, even if the skeleton is in CLod state
* ie displayed with a CLodCharacterShape. This is important for skeletons which have skeletons
* sons sticked on them
*/
uint16 CLodForcedUsage;
/** The current state: which bones need to be computed. ie:
* (CLodForcedUsage) | ( ((Usage & currentLodUsage) | ForcedUsage) & skeleton not in CLod state )
*/
uint16 MustCompute;
/// Myself if MustCompute==true, or the first parent with MustCompute==true.
uint ValidBoneSkinMatrix;
};
// A bone to compute information
struct CBoneCompute
{
// The bone
CBone *Bone;
// Father of the bone. May be NULL
CBone *Father;
// true if must interpolate this bone with next lod
bool MustInterpolate;
};
/// The list of BoneUsage. Updated by Meshes, stickObject(), and lod changes.
std::vector<CBoneUsage> _BoneUsage;
/// List of bones to compute.
std::vector<CBoneCompute> _BoneToCompute;
/// Flag set if the MRSkeleton lod change, or if a new bone Usage change (only if was 0 or become 0).
bool _BoneToComputeDirty;
/// The current lod activated for this skeleton
uint _CurLod;
/// The current lod Interpolation Value for this skeleton
float _CurLodInterp;
/// For lod interpolation. Inverse of distance. If 0, disable interpolation.
float _LodInterpMultiplier;
/// called by CSkeletonShape::createInstance(). init the vector.
void initBoneUsages();
/// increment the refCount of the ith bone and its parents. for stickObjects.
void incForcedBoneUsageAndParents(uint i, bool forceCLod);
/// increment the refCount of the ith bone and its parents. for stickObjects.
void decForcedBoneUsageAndParents(uint i, bool forceCLod);
/// According to Usage, ForedUsage and current skeleton Lod, update MustCompute and ValidBoneSkinMatrix
void updateBoneToCompute();
// @}
/// \name CLod / Character Lod
// @{
// return the contribution of lights (for Character Lod render).
const CLightContribution &getSkeletonLightContribution() {return _LightContribution;}
/** True if the skeleton model and his skins have to be displayed with a CLodCharacterShape, instead of the std way
* This state is modified early during the HRC Traversal. Because Clip traversal need this result.
*/
bool _DisplayedAsLodCharacter;
/// is enable LOD
bool _IsEnableLOD;
/// see setLodCharacterDistance
float _LodCharacterDistance;
float _OOLodCharacterDistance;
/// The Lod instance. -1 by default
CLodCharacterInstance _CLodInstance;
/** dirt when a bindSkin/stickObject/detachSkeletonSon is called
*/
bool _CLodVertexAlphaDirty;
/// see setLodEmit()
NLMISC::CRGBA _LodEmit;
void dirtLodVertexAlpha() {_CLodVertexAlphaDirty= true;}
/// recompute _CLodVertexAlpha, ignoring _CLodVertexAlphaDirty
void computeCLodVertexAlpha(CLodCharacterManager *mngr);
// @}
/// \name Rendering
// @{
/** render the skeleton as a CLod.
* - update instance Lighting
* - render the lod.
* - no op if passTransparent
*/
void renderCLod();
/** render the skins of the skeleton
* - update instance Lighting, and setup Driver lighting
* - activate skeleton Matrix
* - render all the skins (according if passOpaque or not)
*/
void renderSkins();
/** render a list of skin, no lighting setup etc..., but use where possible a CVertexStreamManager
*/
void renderSkinList(NLMISC::CObjectVector<CTransform*, false> &skinList, float alphaMRM);
// @}
/// \name AnimCtrl (IK...)
// @{
// If >0, then user may change any if this bone each frame...
sint _AnimCtrlUsage;
// @}
// SkeletonModel can generate Shadow Map
CShadowMap *_ShadowMap;
void updateShadowMap(IDriver *driver);
void renderShadowSkins(CMaterial &castMat);
/// \name Spawn Script Animation
// @{
// The animated Spawn script
CAnimatedValueString _SpawnScript;
// The manager which evaluates the script and appropriate process
CSkeletonSpawnScript _SpawnScriptEvaluator;
// Special for World Objects
CVector _SSSWOPos;
CVector _SSSWODir;
sint _SpawnScriptChannelId;
// Default track
static CTrackDefaultString _DefaultSpawnScript; // ""
// @}
protected:
virtual void createShadowMap();
virtual void deleteShadowMap();
};
// ***************************************************************************
/*
* Function used by CMeshMRM and CMeshMRMSkinned, to compute CMatrix3x4 of used skeleton bones
*/
template <class TMatrixArray>
inline void computeBoneMatrixes3x4(TMatrixArray &boneMat3x4, const std::vector<uint32> &matInfs, const CSkeletonModel *skeleton)
{
// For all matrix this lod use.
for(uint i= 0; i<matInfs.size(); i++)
{
// Get Matrix info.
uint matId= matInfs[i];
const CMatrix &boneMat= skeleton->getActiveBoneSkinMatrix(matId);
// compute "fast" matrix 3x4.
// resize Matrix3x4.
if(matId>=boneMat3x4.size())
{
boneMat3x4.resize(matId+1);
}
boneMat3x4[matId].set(boneMat);
}
}
/// Same as computeBoneMatrixes3x4, but premul by a matrix.
template <class TMatrixArray>
inline void computeBoneMatrixes3x4PreMul(TMatrixArray &boneMat3x4, const CMatrix &preMulMat, const std::vector<uint32> &matInfs, const CSkeletonModel *skeleton)
{
// For all matrix this lod use.
for(uint i= 0; i<matInfs.size(); i++)
{
// Get Matrix info.
uint matId= matInfs[i];
const CMatrix &boneMat= skeleton->getActiveBoneSkinMatrix(matId);
CMatrix boneMatMul;
boneMatMul.setMulMatrixNoProj(preMulMat, boneMat);
// compute "fast" matrix 3x4.
// resize Matrix3x4.
if(matId>=boneMat3x4.size())
{
boneMat3x4.resize(matId+1);
}
boneMat3x4[matId].set(boneMatMul);
}
}
} // NL3D
#endif // NL_SKELETON_MODEL_H
/* End of skeleton_model.h */