System pozwalający na wykorzystanie ragdolls (wsparcie przez bibliotekę PhysX) w połączeniu z modelami posiadającymi animacje szkieletowa. Umożliwia on zarówno kontrolę ciał fizycznych przez szkielet modelu, jak i odwrotnie, dzięki czemu możliwa jest zarówno symulacja bezwładności ciała jak i precyzyjny raycasting na animowanym modelu. Przedstawione fragmenty pochodzą z projektu The Human Project (FPS, 2008).
Przykład wykorzystania (fragmeny logiki sprawdzajacej trafienie pocisku):
NxOgre::RayCaster* rayCast= new NxOgre::RayCaster(ray.getOrigin(), ray.getDirection().normalisedCopy(), 50, NxOgre::RayCaster::RCT_CLOSEST,mPlayer->getGameWorld()->getPhysXScene()); if (rayCast->castShape(NxOgre::RayCaster::AF_NONE)) { NxOgre::RayCastHit rayHit=rayCast->getClosestRaycastHit(); if (rayHit.mActor) { printf("hit: %s\n",rayHit.mActor->getName().c_str()); if (rayHit.mActor->getGroup()) if (rayHit.mActor->getGroup()->getName()=="Monster") { Ogre::String name=rayHit.mActor->getName(); Ogre::String monsterId=name.substr(0,4); Ogre::String bodyPart=name.substr(6); mPlayer->getGameWorld()->getMonsterByName(monsterId)->injectHit(bodyPart, getMoreInfo()->damage, mPlayer->mName, rayHit); } } }
cRagDoll.h
#pragma once #include "NxOgre.h" struct sBoneBoneActorBind { Ogre::Bone* bone; NxOgre::Actor* BoneActor; Ogre::Vector3 BAoffset; Ogre::String name; Ogre::Quaternion BoneGlobalBindOrientation; Ogre::Quaternion BoneActorGlobalBindOrientationInverse; }; class cRagDoll { public: //creates ragdoll/collision shapes for character based on definition from file cRagDoll(const Ogre::String& fileName, Ogre::SceneNode* characterSN, NxOgre::Character* nxCharacter, Ogre::String& id, NxOgre::Scene* PHXScene, bool justRagdoll=false, Ogre::Real bodyDensity=985); /* this does NOT create a ragdoll! Use it for diagnostics only, it saves to a file of a given name all the bones in the skeleton (their names) and their lenght's. */ cRagDoll(const Ogre::String& outFileName, Ogre::SceneNode* characterSN); ~cRagDoll(); //Bones from skeleton/animation have control over Bone Actors (collision bodies) void setControlToBones(); //Bone Actors have controll over animation via skeleton bones (real ragdoll) void setControlToBoneActors(); //Bone Actors need to be updated every rendering loop? Use with setControlToBones(). void setConstantlyUpdateBoneActors(bool c) { mConstantlyUpdateBoneActors=c;}; bool getConstantlyUpdateBoneActors() { return mConstantlyUpdateBoneActors;}; //call this to update simulation void update(); /* By default, the position of character node is controlled by the skeleton root bone, if its controlled by an Bone Actor. If not, the bone that is controlled by "Pelvis" Bone Actor is choosed. Here you can override this and set the bone you want to use for controlling character by specifing its name. */ void setCharacterPositionControllingBone(const Ogre::String& n); //attaches sceneNode to ragdoll's head void attachSceneNodeToHead(Ogre::SceneNode* n); //dettaches sceneNode to ragdoll's head void dettachSceneNodeFromHead(); protected: void updateBoneActors(); void updateBones(); sBoneBoneActorBind* getBoneBoneActorBindByName(Ogre::String n); //Helper's void _setControlToBones(); void _setControlToBoneActors(); void _parseSectionData(Ogre::String& secName, std::map<Ogre::String, Ogre::String> section); void setRagdollBindPose(); //performs bone->setManuallyControlled(manual) on every bone in skeleton void setAllBonesToManualControll(bool manual); //performs bone->reset() on every bone in skeleton void resetAllBones(); void addJoints(); sBoneBoneActorBind* mPositionControllingBone; bool mControlToBones; bool mControlToBoneActors; bool mConstantlyUpdateBoneActors; //Bone Actors and skeleton bones /* assuming we need only 15 Bone Actors to controll the body (Head, Torso, Pelvis, LeftUpArm, LeftLoArm, LeftHand, RightUpArm, RightoArm, RightHand, LeftUpLeg, LeftLoLeg, LeftFoot, RightUpLeg, RightLoLeg, RightFoot) they must be named exactly like this. */ sBoneBoneActorBind mBoneBoneActorBind[15]; std::map<Ogre::String, NxOgre::Joint*> mJoints; Ogre::SceneNode *mCharacterSN; NxOgre::Character* mNxCharacter; NxOgre::Scene *mPHXScene; NxOgre::NxString mCharacterID; //Wikipedia claims that normal human body has a density of 985, so its a default valueOgre::Real mBodyDensity; int mBonesCounter; bool mHasNodeToHeadAttached; };
cRagDoll.cpp
#include "cRagDoll.h" cRagDoll::cRagDoll(const Ogre::String& fileName, Ogre::SceneNode *characterSN, NxOgre::Character* nxCharacter, Ogre::String id, NxOgre::Scene *PHXScene, bool justRagdoll, Ogre::Real bodyDensity) { mCharacterSN=characterSN; mNxCharacter=nxCharacter; mPHXScene=PHXScene; mCharacterID=id; mPositionControllingBone=NULL; mBodyDensity=bodyDensity; mBonesCounter=0; Ogre::ConfigFile mRagDollCfgFile; mRagDollCfgFile.loadFromResourceSystem(fileName,"General"); Ogre::ConfigFile::SectionIterator seci = mRagDollCfgFile.getSectionIterator(); Ogre::String secName, paramName, valueName; while (seci.hasMoreElements()) { secName = seci.peekNextKey(); Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); Ogre::ConfigFile::SettingsMultiMap::iterator i; std::map<Ogre::String, Ogre::String> sectionData; for (i = settings->begin(); i != settings->end(); ++i) { paramName = i->first; valueName = i->second; sectionData[paramName]=valueName; } if (secName!="") _parseSectionData(secName, sectionData); } if (mBonesCounter==15) { setControlToBones(); setAllBonesToManualControll(true); setRagdollBindPose(); setAllBonesToManualControll(false); addJoints(); //...as we dont have root bone attached to Bone Actor, we use Pelvis BA instead if (!mPositionControllingBone) mPositionControllingBone=getBoneBoneActorBindByName("Pelvis"); mHasNodeToHeadAttached=false; } else Ogre::LogManager::getSingletonPtr()->getDefaultLog()->logMessage(Ogre::String("Not enough bones declared in file: ")+fileName+Ogre::String(". Ragdoll was not created!")); } void cRagDoll::_parseSectionData(Ogre::String& secName, std::map<Ogre::String, Ogre::String> section) { Ogre::Vector3 dimensions=Ogre::StringConverter::parseVector3(section["dimensions"]); Ogre::Vector3 offset=Ogre::StringConverter::parseVector3(section["offset"]); NxOgre::NxString density=Ogre::StringConverter::toString(mBodyDensity); Ogre::Entity* ent=(Ogre::Entity*)mCharacterSN->getAttachedObject(0); mBoneBoneActorBind[mBonesCounter].name=secName; mBoneBoneActorBind[mBonesCounter].bone=ent->getSkeleton()->getBone(section["boneName"]); mBoneBoneActorBind[mBonesCounter].BAoffset=offset; if (section["actorShape"]==Ogre::String("sphere")) { mBoneBoneActorBind[mBonesCounter].BoneActor=mPHXScene->createActor(mCharacterID+Ogre::String("BA")+secName,new NxOgre::SphereShape(dimensions.x),NxOgre::Pose(0,0,0),NxOgre::ActorParams("Density: "+density+", Group: Monster")); mBoneBoneActorBind[mBonesCounter].BoneActor->raiseBodyFlag(NX_BF_VISUALIZATION); } if (section["actorShape"]==Ogre::String("cube")) { mBoneBoneActorBind[mBonesCounter].BoneActor=mPHXScene->createActor(mCharacterID+Ogre::String("BA")+secName,new NxOgre::CubeShape(dimensions.x,dimensions.y,dimensions.z),NxOgre::Pose(0,0,0),NxOgre::ActorParams("Density: "+density+", Group: Monster")); mBoneBoneActorBind[mBonesCounter].BoneActor->raiseBodyFlag(NX_BF_VISUALIZATION); } if (section["actorShape"]==Ogre::String("capsule")) { if (secName=="Head") { mBoneBoneActorBind[mBonesCounter].BoneActor=mPHXScene->createBody("",new NxOgre::CapsuleShape(dimensions.x,dimensions.y),NxOgre::Pose(0,0,0),NxOgre::ActorParams("Density: "+density+", Group: Monster")); mBoneBoneActorBind[mBonesCounter].BoneActor->setName(mCharacterID+Ogre::String("BA")+secName); } else mBoneBoneActorBind[mBonesCounter].BoneActor=mPHXScene->createActor(mCharacterID+Ogre::String("BA")+secName,new NxOgre::CapsuleShape(dimensions.x,dimensions.y),NxOgre::Pose(0,0,0),NxOgre::ActorParams("Density: "+density+", Group: Monster")); mBoneBoneActorBind[mBonesCounter].BoneActor->raiseBodyFlag(NX_BF_VISUALIZATION); } //he have found a root bone - use it for character positioning if (!mBoneBoneActorBind[mBonesCounter].bone->getParent()) mPositionControllingBone=&mBoneBoneActorBind[mBonesCounter]; NxShape* const * shapeArray = mBoneBoneActorBind[mBonesCounter].BoneActor->getNxActor()->getShapes(); shapeArray[0]->setFlag(NX_SF_VISUALIZATION,true); //bind to group mBoneBoneActorBind[mBonesCounter].BoneActor->getNxActor()->getShapes()[0]-> setGroup(mPHXScene->getShapeGroup("ragdolls")->getGroupID()); mBonesCounter++; } void cRagDoll::setControlToBones() { mControlToBones=true; mControlToBoneActors=false; setAllBonesToManualControll(false); _setControlToBones(); } void cRagDoll::setControlToBoneActors() { mControlToBoneActors=true; mControlToBones=false; _setControlToBoneActors(); } void cRagDoll::_setControlToBones() { for (int i=0; i<15; i++) { mBoneBoneActorBind[i].BoneActor->raiseActorFlag(NX_AF_DISABLE_COLLISION); mBoneBoneActorBind[i].BoneActor->raiseBodyFlag(NX_BF_KINEMATIC); mBoneBoneActorBind[i].BoneActor->putToSleep(); } } void cRagDoll::_setControlToBoneActors() { //updateBoneActors(); setAllBonesToManualControll(true); resetAllBones(); //disabling all animations Ogre::Entity* e=(Ogre::Entity*)mCharacterSN->getAttachedObject(0); Ogre::AnimationStateSet* set=e->getAllAnimationStates(); Ogre::AnimationStateIterator it = set->getAnimationStateIterator(); Ogre::AnimationState *anim; while(it.hasMoreElements()) { anim = it.getNext(); anim->setWeight(0); } for (int i=0; i<15; i++) { mBoneBoneActorBind[i].BoneActor->clearActorFlag(NX_AF_DISABLE_COLLISION); mBoneBoneActorBind[i].BoneActor->clearBodyFlag(NX_BF_KINEMATIC); mBoneBoneActorBind[i].BoneActor->wakeUp(); } } void cRagDoll::updateBoneActors() { for(int i=0; i<15; i++) { //Get parent and child Positions Ogre::Vector3 bonePos = mBoneBoneActorBind[i].bone->getWorldPosition(); Ogre::Vector3 nextBonePos = mBoneBoneActorBind[i].bone->getChild(0)->getWorldPosition(); //get vector difference between parent and child Ogre::Vector3 difference = nextBonePos-bonePos; Ogre::Vector3 forward = difference.normalisedCopy(); //Get bone Orientation and re-align Ogre::Quaternion new_orient = Ogre::Vector3::UNIT_Y.getRotationTo(forward); //mid point of bone Ogre::Vector3 pos = bonePos + (forward * (difference.length() * 0.5)); //adjust Bone Actor placement pos.x+=mBoneBoneActorBind[i].BAoffset.x; pos.y+=mBoneBoneActorBind[i].BAoffset.y; pos.z+=mBoneBoneActorBind[i].BAoffset.z; //update Bone Actor mBoneBoneActorBind[i].BoneActor->setGlobalPosition(mCharacterSN->getWorldOrientation()*pos+mCharacterSN->getWorldPosition()); mBoneBoneActorBind[i].BoneActor->setGlobalOrientation(mCharacterSN->getWorldOrientation()*new_orient); //temporarly - Ageia bug mBoneBoneActorBind[i].BoneActor->render(0); } } void cRagDoll::updateBones() { Ogre::Quaternion PhysxRotation, OgreGlobalQuat, NodeRotationInverse = mCharacterSN->getOrientation().Inverse(); for(int i=0; i<15; i++) { PhysxRotation = mBoneBoneActorBind[i].BoneActor->getGlobalOrientation()* mBoneBoneActorBind[i].BoneActorGlobalBindOrientationInverse; Ogre::Quaternion ParentInverse = NodeRotationInverse; if ( mBoneBoneActorBind[i].bone->getParent()) ParentInverse = mBoneBoneActorBind[i].bone->getParent()->_getDerivedOrientation().Inverse() * NodeRotationInverse; else mCharacterSN->setPosition(mBoneBoneActorBind[i].BoneActor->getGlobalPosition() - mCharacterSN->getOrientation()*mBoneBoneActorBind[i].bone->getPosition()); OgreGlobalQuat = PhysxRotation * mBoneBoneActorBind[i].BoneGlobalBindOrientation; mBoneBoneActorBind[i].bone->setOrientation(ParentInverse * OgreGlobalQuat); } Ogre::Vector3 newPos=mPositionControllingBone->BoneActor->getGlobalPosition() - mCharacterSN->getOrientation()*mPositionControllingBone->bone->getPosition() - mPositionControllingBone->BAoffset; mCharacterSN->setPosition(newPos); } void cRagDoll::update() { if (mControlToBones) updateBoneActors(); if (mControlToBoneActors) updateBones(); } void cRagDoll::addJoints() { //TODO - split this ugly loong code NxOgre::JointParams jp; jp.mHasLimits=true; jp.mLowerLimit=-0.75*NxPi; jp.mUpperLimit=0; NxOgre::JointParams sphJointParam; sphJointParam.mHasLimits=true; sphJointParam.mHasTwistLimit=true; sphJointParam.mTwistLimit_Low_Value=-(NxReal)0.25*NxPi; sphJointParam.mTwistLimit_Low_Restitution=0.5; sphJointParam.mTwistLimit_High_Value=(NxReal)0.5*NxPi; sphJointParam.mTwistLimit_High_Restitution=1; sphJointParam.mHasSwingLimit=true; sphJointParam.mSwingLimit_Value=(NxReal)0.15*NxPi; sphJointParam.mSwingLimit_Restitution=0.5; sphJointParam.mHasTwistSpring=true; sphJointParam.mTwistSpring_Damper=1; sphJointParam.mTwistSpring_Spring=0.5; sphJointParam.mHasSwingSpring=true; sphJointParam.mSwingSpring_Spring=0.5; sphJointParam.mSwingSpring_Damper=1; sphJointParam.mJointProjectionDistance=(NxReal)0.15; sphJointParam.mJointProjectionMode=NX_JPM_POINT_MINDIST; /// //shoulder NxOgre::JointParams sphJointParam2=sphJointParam; sphJointParam2.mSwingLimit_Value=(NxReal)1*NxPi; sphJointParam2.mTwistLimit_Low_Value=-(NxReal)0.25*NxPi; sphJointParam2.mTwistLimit_High_Value=(NxReal)0.25*NxPi; //Neck Ogre::Vector3 pos=getBoneBoneActorBindByName("Head")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); NxOgre::Joint* joint=mPHXScene->createSphericalJoint(getBoneBoneActorBindByName("Head")->BoneActor, getBoneBoneActorBindByName("Torso")->BoneActor,pos,sphJointParam); //Chest pos=getBoneBoneActorBindByName("Pelvis")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createFixedJoint(getBoneBoneActorBindByName("Torso")->BoneActor, getBoneBoneActorBindByName("Pelvis")->BoneActor); //Left Leg pos=getBoneBoneActorBindByName("LeftUpLeg")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createSphericalJoint(getBoneBoneActorBindByName("Pelvis")->BoneActor, getBoneBoneActorBindByName("LeftUpLeg")->BoneActor,pos,sphJointParam); joint->setGlobalAxis(Ogre::Vector3::NEGATIVE_UNIT_Y); //Left knee pos=getBoneBoneActorBindByName("LeftLoLeg")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createRevoluteJoint(getBoneBoneActorBindByName("LeftUpLeg")->BoneActor, getBoneBoneActorBindByName("LeftLoLeg")->BoneActor,-Ogre::Vector3::UNIT_X,pos,jp); //Left ankle pos=getBoneBoneActorBindByName("LeftFoot")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createRevoluteJoint(getBoneBoneActorBindByName("LeftLoLeg")->BoneActor, getBoneBoneActorBindByName("LeftFoot")->BoneActor,Ogre::Vector3::UNIT_X,pos,jp); //Right Leg pos=getBoneBoneActorBindByName("RightUpLeg")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createSphericalJoint(getBoneBoneActorBindByName("Pelvis")->BoneActor, getBoneBoneActorBindByName("RightUpLeg")->BoneActor,pos,sphJointParam); joint->setGlobalAxis(Ogre::Vector3::NEGATIVE_UNIT_Y); //Right knee pos=getBoneBoneActorBindByName("RightLoLeg")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createRevoluteJoint(getBoneBoneActorBindByName("RightUpLeg")->BoneActor, getBoneBoneActorBindByName("RightLoLeg")->BoneActor,-Ogre::Vector3::UNIT_X,pos,jp); //Right ankle pos=getBoneBoneActorBindByName("RightFoot")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createRevoluteJoint(getBoneBoneActorBindByName("RightLoLeg")->BoneActor, getBoneBoneActorBindByName("RightFoot")->BoneActor,Ogre::Vector3::UNIT_X,pos,jp); //Left shoulder pos=getBoneBoneActorBindByName("LeftUpArm")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createSphericalJoint(getBoneBoneActorBindByName("Torso")->BoneActor, getBoneBoneActorBindByName("LeftUpArm")->BoneActor, pos, sphJointParam2); joint->setGlobalAxis(Ogre::Vector3::UNIT_X); //Left elbow pos=getBoneBoneActorBindByName("LeftLoArm")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createRevoluteJoint(getBoneBoneActorBindByName("LeftUpArm")->BoneActor, getBoneBoneActorBindByName("LeftLoArm")->BoneActor,Ogre::Vector3::UNIT_Y,pos,jp); //Left hand pos=getBoneBoneActorBindByName("LeftHand")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createSphericalJoint(getBoneBoneActorBindByName("LeftLoArm")->BoneActor, getBoneBoneActorBindByName("LeftHand")->BoneActor, pos, sphJointParam); //Right shoulder pos=getBoneBoneActorBindByName("RightUpArm")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createSphericalJoint(getBoneBoneActorBindByName("Torso")->BoneActor, getBoneBoneActorBindByName("RightUpArm")->BoneActor, pos, sphJointParam2); joint->setGlobalAxis(Ogre::Vector3::NEGATIVE_UNIT_X); //Right elbow pos=getBoneBoneActorBindByName("RightLoArm")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createRevoluteJoint(getBoneBoneActorBindByName("RightUpArm")->BoneActor, getBoneBoneActorBindByName("RightLoArm")->BoneActor,Ogre::Vector3::UNIT_Y,pos,jp); //Right hand pos=getBoneBoneActorBindByName("RightHand")->bone->_getDerivedPosition()+mCharacterSN->getWorldPosition(); joint=mPHXScene->createSphericalJoint(getBoneBoneActorBindByName("RightLoArm")->BoneActor, getBoneBoneActorBindByName("RightHand")->BoneActor, pos,sphJointParam); } void cRagDoll::setRagdollBindPose() { updateBoneActors(); for (int i=0; i<15; i++) { mBoneBoneActorBind[i].BoneGlobalBindOrientation = mBoneBoneActorBind[i].bone->_getDerivedOrientation(); mBoneBoneActorBind[i].BoneActorGlobalBindOrientationInverse = mBoneBoneActorBind[i].BoneActor->getGlobalOrientation().Inverse(); } } void cRagDoll::setAllBonesToManualControll(bool manual) { Ogre::Entity* e=(Ogre::Entity*)mCharacterSN->getAttachedObject(0); Ogre::SkeletonInstance* skeletonInst = e->getSkeleton(); Ogre::Skeleton::BoneIterator boneI=skeletonInst->getBoneIterator(); while(boneI.hasMoreElements()) boneI.getNext()->setManuallyControlled(manual); } void cRagDoll::resetAllBones() { Ogre::Entity* e=(Ogre::Entity*)mCharacterSN->getAttachedObject(0); Ogre::SkeletonInstance* skeletonInst = e->getSkeleton(); Ogre::Skeleton::BoneIterator boneI=skeletonInst->getBoneIterator(); while(boneI.hasMoreElements()) boneI.getNext()->reset(); } sBoneBoneActorBind* cRagDoll::getBoneBoneActorBindByName(const Ogre::String& n) { for (int i=0; i<15; i++) if (mBoneBoneActorBind[i].name==n) return &mBoneBoneActorBind[i]; return NULL; } void cRagDoll::setCharacterPositionControllingBone(Ogre::String n) { mPositionControllingBone=getBoneBoneActorBindByName(n); } cRagDoll::cRagDoll(const Ogre::String& outFileName, Ogre::SceneNode* characterSN) { mHasNodeToHeadAttached=false; std::ofstream file(outFileName.c_str()); if (file) { Ogre::Entity* e=(Ogre::Entity*)characterSN->getAttachedObject(0); Ogre::SkeletonInstance* skeletonInst = e->getSkeleton(); Ogre::Skeleton::BoneIterator boneI=skeletonInst->getBoneIterator(); file<<"Creating bone lenght information from:\n"; file<<"Mesh name: "<<e->getMesh()->getName()<<"\n"; file<<"Skeleton name: "<<skeletonInst->getName()<<"\n\n"; while(boneI.hasMoreElements()) { Ogre::Bone* bone=boneI.getNext(); Ogre::String bName=bone->getName(); if (bone->getChild(0)) { Ogre::Vector3 curr = bone->_getDerivedPosition(); Ogre::Vector3 next = bone->getChild(0)->_getDerivedPosition(); Ogre::Vector3 difference = next-curr; //length of bone Ogre::Real lenght = difference.length(); file<<bName<<"\t\t\t=\t"<<Ogre::StringConverter::toString(lenght,3)<<"\n"; if (!bone->getParent()) file<<bName<<" is a Root Bone!\n"; } } } } void cRagDoll::attachSceneNodeToHead(Ogre::SceneNode* n) { NxOgre::Body* head=dynamic_cast<NxOgre::Body*>(getBoneBoneActorBindByName("Head")->BoneActor); head->setNode(n); mHasNodeToHeadAttached=true; } void cRagDoll::dettachSceneNodeFromHead() { NxOgre::Body* head=dynamic_cast<NxOgre::Body*>(getBoneBoneActorBindByName("Head")->BoneActor); head->getNode()->detachAllObjects(); mHasNodeToHeadAttached=false; } cRagDoll::~cRagDoll() { if (mHasNodeToHeadAttached) dettachSceneNodeFromHead(); }