void AssimpModelMover::boneUpdate(const string &name, ofQuaternion &q) { BoneNode *bn = getBoneNode(name); if (!bn) return; bn->setOrientation(q); }
void Skeleton::_relate() { for (BoneNodeMap::iterator it = _boneNodes.begin(); it != _boneNodes.end(); ++it) { BoneNode* node = it->second; if (node) { Bone* bone = node->getBone(); if (bone) { int parentID = bone->parent; BoneNode* parentNode = _getBoneNode(parentID); if (parentNode) { parentNode->addChild(node); } else { _roots.push_back(bone->name); } } } } if (_roots.empty()) { throw Zen::Exception("no bone root found!"); } // if (roots.size() > 1) // { // for (size_t i = 0; i != roots.size(); ++i) // { // std::string name = roots[i]; // std::transform(name.begin(), name.end(), name.begin(), std::tolower); // if (name == "root") // { // _rootName = roots[i]; // break; // } // } // if (_rootName.empty()) // { // _addRootNode(); // _relate(); // } // } // else // { // _rootName = roots[0]; // } // _matrices.resize(_boneNodes.size()); _matricesFull.resize(_boneNodes.size()); }
BoneNode* BoneNode::create() { BoneNode* ret = new (std::nothrow) BoneNode(); if (ret && ret->init()) { ret->autorelease(); return ret; } CC_SAFE_DELETE(ret); return nullptr; }
void AssimpModelMover::listBones(const struct aiScene *sc, const struct aiNode *nd) { //cout << "node: " << nd->mName.data << endl; for (unsigned n = 0; n < nd->mNumMeshes; ++n) //nd->mNumMeshes; ++n) { const struct aiMesh *mesh = sc->mMeshes[nd->mMeshes[n]]; //cout << "mesh " << n << " " << mesh->mName.data << endl; for (size_t a = 0; a < mesh->mNumBones; ++a) { const aiBone *bone = mesh->mBones[a]; map<std::string, BoneNode*>::iterator i = boneNodes.find(bone->mName.data); bool alreadyStored = (i != boneNodes.end()); // find the corresponding node aiNode *node = sc->mRootNode->FindNode(bone->mName); if (!alreadyStored) { const string name(bone->mName.data); boneNames.push_back(name); // TODO: free the boneNodes map pointers BoneNode *boneNode = new BoneNode(node); boneNodes[name] = boneNode; // build hierarchical structure of node aiNode *parentNode = node->mParent; while (parentNode) { string parentName = parentNode->mName.data; BoneNode *parentBNode = getBoneNode(parentName); if (parentBNode == NULL) { boneNames.push_back(parentName); parentBNode = new BoneNode(parentNode); boneNodes[parentName] = parentBNode; } boneNode->setParent(parentBNode); boneNode = parentBNode; parentNode = parentNode->mParent; } } } } // process all children for (unsigned n = 0; n < nd->mNumChildren; ++n) { listBones(sc, nd->mChildren[n]); } }
BoneNode* Skeleton::_getBoneNode( int id ) { for (BoneNodeMap::iterator it = _boneNodes.begin(); it != _boneNodes.end(); ++it) { BoneNode* node = it->second; Bone* bone = node->getBone(); if (id == bone->id) { return node; } } return NULL; }
void Skeleton::generateBoneTransform( Matrix4* matVec , Matrix4 const& baseTrans , float* frames , float* weights , int num ) { assert( mBaseBone->id == 0 ); matVec[ 0 ] = baseTrans; int size = mBoneVec.size(); for ( int i = 1 ; i < size ; ++i ) { BoneNode* bone = mBoneVec[i]; Matrix4& boneTrans = matVec[bone->id]; bone->calcFrameTransform( boneTrans , frames , weights , num ); TransformUntility::mul( boneTrans , boneTrans , matVec[ bone->parentId ] ); } }
BoneNode* BoneNode::create(int length) { BoneNode* ret = new (std::nothrow) BoneNode(); if (ret && ret->init()) { ret->setDebugDrawLength(length); ret->autorelease(); } else { CC_SAFE_DELETE(ret); } return ret; }
void BoneNode::update( const AnimationTime& at , Skin& sk) { if (_bone->id >= 0 && _bone->id < sk.boneKFs.size()) { sBoneKFs& b = sk.boneKFs[_bone->id]; //translation Vec3 t = b.translationKFs.getFrame(&at); //rotation Quaternion q = b.rotationKFs.getFrame(-1, &at); //scale Vec3 s = b.scaleKFs.getFrame(-1, &at); Mat4 dynamicMtx = Mat4::IDENTITY; dynamicMtx.makeTransform(t, s, q); // if(0) { Mat4 tM; tM.makeTrans(t); Mat4 tQ(q); dynamicMtx = tM * tQ; } // if (_parent) { _mtxTransform = _parent->_fullMatrix * dynamicMtx; } else { _mtxTransform = dynamicMtx; } _fullMatrix = _mtxTransform; // _skeleton->_matricesFull[_bone->id] = _mtxTransform; _mtxTransform = _fullMatrix * _bone->initialMatrix.inverse(); // _skeleton->_matrices[_bone->id] = _mtxTransform; } NameNodeMap::iterator it = _children.begin(); for ( ; it != _children.end(); ++it) { BoneNode* n = it->second; n->update(at, sk); } }
void testApp::setupBone(const string &name, float degx, float degy, float degz) { BoneNode *bn = model.getBoneNode(name); if (bn == NULL) { cerr << "WARNING: could find bind bone " << name << endl; return; } bn->setInheritOrientation(false); bn->bindPoseOrientation.makeRotate(degx, ofVec3f(1, 0, 0), degy, ofVec3f(0, 1, 0), degz, ofVec3f(0, 0, 1)); bn->resetOrientation(); }
void Skeleton::destroy() { for (BoneNodeMap::iterator it = _boneNodes.begin(); it != _boneNodes.end(); ++it) { BoneNode* r = it->second; if (r) { Bone* b = r->getBone(); delete b; r->detachObject(); delete r; } } _clear(); }
void Skeleton::generateBoneTransform( Matrix4* matVec , Matrix4 const& baseTrans , int frame , float fract ) { assert( 0 <= fract && fract < 1.0f ); //M( blend ) = M(motion) * M( base ) assert( mBaseBone->id == 0 ); matVec[ 0 ] = baseTrans; int size = mBoneVec.size(); for ( int i = 1 ; i < size ; ++i ) { BoneNode* bone = mBoneVec[i]; Matrix4& boneTrans = matVec[bone->id]; bone->calcFrameTransform( boneTrans , frame , frame + 1 , fract ); TransformUntility::mul( boneTrans , boneTrans , matVec[ bone->parentId ] ); } }
void AssimpModelMover::drawNodes() { glEnable(GL_NORMALIZE); ofPushMatrix(); ofTranslate(pos); ofRotate(180, 0, 0, 1); ofTranslate(-scene_center.x, -scene_center.y, scene_center.z); if(normalizeScale) { ofScale(normalizedScale, normalizedScale, normalizedScale); } for(int i = 0; i < (int)rotAngle.size(); i++){ ofRotate(rotAngle[i], rotAxis[i].x, rotAxis[i].y, rotAxis[i].z); } ofScale(scale.x, scale.y, scale.z); //glEnable(GL_DEPTH_TEST); //glEnable(GL_NORMALIZE); glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); //light.enable(); //cam.begin(); map<string, BoneNode *>::iterator i = boneNodes.begin(); for(; i != boneNodes.end(); ++i) { BoneNode *bn = i->second; bn->draw(); } //cam.end(); //light.disable(); //glDisable(GL_LIGHTING); ofPopMatrix(); }
void Skeleton::destroy() { //STLDeleteAssociate(_boneNodes); // BoneNodeMap::iterator it = _boneNodes.begin(); // for (; it != _boneNodes.end(); ++it) // { // BoneNode* n = it->second; // if (n) // { // delete n; // n = NULL; // } // } BoneNode* r = getRootBoneNode(); if (r) { r->detachObject(); r->release(); //r = NULL; } _boneNodes.clear(); }
void Skeleton::generateInvLoacalTransform() { assert( mUseInvLocalTrans ); std::vector< Matrix4 > globalTransVec; globalTransVec.resize( mBoneVec.size() ); globalTransVec[0] = Matrix4::Identity(); mBaseBone->invLocalTrans = Matrix4::Identity(); assert( mBaseBone->id == 0 ); int size = mBoneVec.size(); for ( int i = 1 ; i < size ; ++i ) { BoneNode* bone = mBoneVec[i]; Matrix4 const& parentTrans = globalTransVec[ bone->parentId ]; Matrix4& worldTrans = globalTransVec[ bone->id ]; bone->calcFrameTransform( worldTrans , 0 ); TransformUntility::mul( worldTrans , worldTrans , parentTrans ); float det; bool result = worldTrans.inverseAffine( bone->invLocalTrans , det ); } }
void AbstractSceneManager::addPickedObjects(std::vector<SceneObject*>& vPickedObjects) { for (size_t i=0; i<m_vPickedList.size(); i++) { m_vPickedList.at(i)->setPicked(false); } m_vPickedList.clear(); m_vPickedList.insert(m_vPickedList.begin(),vPickedObjects.begin(),vPickedObjects.end()); for (size_t i=0; i<m_vPickedList.size(); i++) { m_vPickedList.at(i)->setPicked(true); } if (m_vPickedList.empty()) { emit pickedSeveralSkeletons(); } else if (m_vPickedList.size() == 1) { //Node* pNode = m_vPickedList.at(0)->m_pNode; //if (pNode) //{ // MotionJointProp mjp; // mjp.strName = pNode->getName(); // mjp.vAbsolutePosition = pNode->getAbsolutePosition(); // mjp.vRelativePosition = pNode->getPosition(); // mjp.eRotation = pNode->getRotation(); // emit pickedOneObject(mjp); //} } else { int n = 0; Skeleton* pSkeleton = NULL; m_sPickedSkeleton.clear(); for (size_t i=1; i<m_vPickedList.size(); i++) { if (m_vPickedList.at(i)->m_pNode->isBoneNode()) { BoneNode* pBoneNode = (BoneNode*)m_vPickedList.at(i)->m_pNode; if (pSkeleton != pBoneNode->getSkeleton()) { n++; pSkeleton = pBoneNode->getSkeleton(); m_sPickedSkeleton.insert(pSkeleton); } } } if (n == 1) { if (pSkeleton) { MotionClip* pMotionClip = pSkeleton->getMotionClip(); //if (pMotionClip) //{ // MotionFileProp mfp; // mfp.strPath = QString::fromStdString(pMotionClip->getFileName()); // mfp.fFrameTime = pMotionClip->getFrameTime(); // mfp.nFrameCount = pMotionClip->getFrameCount(); // mfp.nJointCount = pMotionClip->getJointCount(); // mfp.mstate = pSkeleton->getViewState(); // mfp.bshowmesh = pSkeleton->isShowMesh(); // emit pickedOneSkeleton(mfp); //} } } else { emit pickedSeveralSkeletons(); } } }
void Skeleton::create(const std::string& fileName) { // std::ifstream f(fileName.c_str(), std::ios::binary); if (!f.good()) { return; } u32 version = 0; // [Tag Size Data] int t; int s; while(f.good()) { t = 0; s = 0; f.read((char*)&t, sizeof(int)); f.read((char*)&s, sizeof(int)); if (s <= 0) { continue; } char c[5]; c[0] = *((char*)&t + 3); c[1] = *((char*)&t + 2); c[2] = *((char*)&t + 1); c[3] = *((char*)&t + 0); c[4] = 0; switch (t) { case 'MVER': { f.read((char*)&_version, sizeof(_version)); }break; case 'MBON': // ¹Ç÷À { u32 nBones; f.read((char*)&nBones,sizeof(nBones)); for (u32 i = 0;i < nBones;i++) { // Bone* bone = new Bone; // f.read((char*)&bone->id,sizeof(bone->id)); u8 JointnameLen; f.read((char*)&JointnameLen,sizeof(JointnameLen)); char name[MODEL_NAME_NODE_SIZE+1]; f.read((char*)name,JointnameLen); name[JointnameLen] = 0; // bone->name = name; //ascii2unicode(name, bone->name); int parent; f.read((char*)&parent,sizeof(parent)); // bone->parent = parent; Mat4 initialMatrix = Mat4::IDENTITY; { f.read((char*)&initialMatrix, sizeof(initialMatrix)); } bone->initialMatrix = initialMatrix; // BoneNode* node = createBoneNode(bone->name); // node->attachObject(bone); } if (nBones != 0) { _relate(); } }break; default: { f.ignore(s); }break; } } }
void AssimpModelMover::updateSkeleton() { // update mesh position for the animation for (unsigned int i = 0; i < modelMeshes.size(); ++i) { // current mesh we are introspecting const aiMesh *mesh = modelMeshes[i].mesh; // calculate bone matrices std::vector<ofMatrix4x4> boneMatrices(mesh->mNumBones); for (size_t a = 0; a < mesh->mNumBones; ++a) { const aiBone *bone = mesh->mBones[a]; // find the corresponding node by again looking recursively through the node hierarchy for the same name map<string, BoneNode *>::iterator it = boneNodes.find(bone->mName.data); assert(it != boneNodes.end()); BoneNode *bn = it->second; // start with the mesh-to-bone matrix //boneMatrices[a] = aiMatrix4x4ToOfMatrix44(bone->mOffsetMatrix) * bn->getGlobalTransformMatrix(); boneMatrices[a] = aiMatrix4x4ToOfMatrix44(bone->mOffsetMatrix) * bn->getDerivedTransformMatrix(); modelMeshes[i].hasChanged = true; modelMeshes[i].validCache = false; } modelMeshes[i].animatedPos.assign(modelMeshes[i].animatedPos.size(),0); if(mesh->HasNormals()){ modelMeshes[i].animatedNorm.assign(modelMeshes[i].animatedNorm.size(),0); } // loop through all vertex weights of all bones for( size_t a = 0; a < mesh->mNumBones; ++a) { const aiBone* bone = mesh->mBones[a]; const aiMatrix4x4& posTrafo = ofMatrix4x4ToAiMatrix44(boneMatrices[a]); for( size_t b = 0; b < bone->mNumWeights; ++b) { const aiVertexWeight& weight = bone->mWeights[b]; size_t vertexId = weight.mVertexId; const aiVector3D& srcPos = mesh->mVertices[vertexId]; modelMeshes[i].animatedPos[vertexId] += weight.mWeight * (posTrafo * srcPos); } if(mesh->HasNormals()){ // 3x3 matrix, contains the bone matrix without the translation, only with rotation and possibly scaling aiMatrix3x3 normTrafo = aiMatrix3x3( posTrafo); for( size_t b = 0; b < bone->mNumWeights; ++b) { const aiVertexWeight& weight = bone->mWeights[b]; size_t vertexId = weight.mVertexId; const aiVector3D& srcNorm = mesh->mNormals[vertexId]; modelMeshes[i].animatedNorm[vertexId] += weight.mWeight * (normTrafo * srcNorm); } } } } updateGLResources(); }
//TestActionTimelineSkeleton void TestActionTimelineSkeleton::onEnter() { ActionTimelineBaseTest::onEnter(); _changedDisplays = _changedDisplay = false; Node* node = CSLoader::createNode("ActionTimeline/DemoPlayer_skeleton.csb"); ActionTimeline* action = CSLoader::createTimeline("ActionTimeline/DemoPlayer_skeleton.csb"); node->runAction(action); node->setScale(0.2f); node->setPosition(150, 150); action->gotoFrameAndPlay(0); addChild(node); auto skeletonNode = static_cast<SkeletonNode*>(node); const std::string weapBoneName = "Layer20"; auto weaponHandeBone = skeletonNode->getBoneNode(weapBoneName); /*********** debug draw bones *************/ auto boneDrawsBtn = cocos2d::ui::Button::create(); addChild(boneDrawsBtn); boneDrawsBtn->setPosition(Vec2(VisibleRect::right().x - 30, VisibleRect::top().y - 30)); boneDrawsBtn->setTitleText("Draw bone"); skeletonNode->setDebugDrawEnabled(true); boneDrawsBtn->addClickEventListener([skeletonNode, this](Ref* sender) { skeletonNode->setDebugDrawEnabled(!skeletonNode->isDebugDrawEnabled()); }); /***************** change bone display **************************/ // add display auto weapSkinToAdd = Sprite::create("ActionTimeline/testAnimationResource/girl_arms.png"); weapSkinToAdd->setName("Knife"); weapSkinToAdd->setPosition(Vec2(135, 23)); weapSkinToAdd->setScale(3.0f); weapSkinToAdd->setRotation(86); weaponHandeBone->addSkin(weapSkinToAdd, false); // change display auto changeBoneDispBtn = cocos2d::ui::Button::create(); addChild(changeBoneDispBtn); changeBoneDispBtn->setPosition(Vec2(VisibleRect::right().x - 60, VisibleRect::top().y - 60)); changeBoneDispBtn->setTitleText("change bone display"); changeBoneDispBtn->addClickEventListener([weapSkinToAdd, weaponHandeBone](Ref* sender) { // or use skeletonNode->display(bone name, skin name, hide) if (weapSkinToAdd->isVisible()) weaponHandeBone->displaySkin("3", true); else { weaponHandeBone->displaySkin(weapSkinToAdd, true); } }); /*************** debug draw boundingbox and transforms ***************/ auto debugDrawNode = DrawNode::create(); addChild(debugDrawNode); auto drawBoxBtn = cocos2d::ui::Button::create(); addChild(drawBoxBtn); drawBoxBtn->setPosition(Vec2(VisibleRect::right().x - 30, VisibleRect::top().y - 45)); drawBoxBtn->setTitleText("Draw Box"); drawBoxBtn->addClickEventListener([debugDrawNode](Ref* sender) { debugDrawNode->setVisible(!debugDrawNode->isVisible()); }); skeletonNode->schedule([skeletonNode, weaponHandeBone, debugDrawNode](float interval) { if (debugDrawNode->isVisible()) { debugDrawNode->clear(); // skeleton boundingbox auto rect = skeletonNode->getBoundingBox(); cocos2d::Vec2 leftbottom(rect.getMinX(), rect.getMinY()); cocos2d::Vec2 righttop(rect.getMaxX(), rect.getMaxY()); debugDrawNode->drawRect(leftbottom, righttop, cocos2d::Color4F::YELLOW); // bone boundingbox rect = weaponHandeBone->getBoundingBox(); leftbottom.x = rect.getMinX(); leftbottom.y = rect.getMinY(); righttop.x = rect.getMaxX(); righttop.y = rect.getMaxY(); cocos2d::Vec2 lefttop(rect.getMinX(), rect.getMaxY()); cocos2d::Vec2 rightbottom(rect.getMaxX(), rect.getMinY()); auto skeletonToP = skeletonNode->getNodeToParentAffineTransform(); auto bonePtoSkeletonPTrans = AffineTransformConcat( static_cast<BoneNode*>((weaponHandeBone->getParent()) )->getNodeToParentAffineTransform(skeletonNode), skeletonToP); leftbottom = PointApplyAffineTransform(leftbottom, bonePtoSkeletonPTrans); righttop = PointApplyAffineTransform(righttop, bonePtoSkeletonPTrans); lefttop = PointApplyAffineTransform(lefttop, bonePtoSkeletonPTrans); rightbottom = PointApplyAffineTransform(rightbottom, bonePtoSkeletonPTrans); debugDrawNode->drawLine(leftbottom, rightbottom, Color4F::BLUE); debugDrawNode->drawLine(rightbottom, righttop, Color4F::BLUE); debugDrawNode->drawLine(righttop, lefttop, Color4F::BLUE); debugDrawNode->drawLine(lefttop, leftbottom, Color4F::BLUE); // skin boundingbox // get displaying nodes auto currentskin = weaponHandeBone->getVisibleSkins().front(); rect = currentskin->getBoundingBox(); leftbottom.x = rect.getMinX(); leftbottom.y = rect.getMinY(); righttop.x = rect.getMaxX(); righttop.y = rect.getMaxY(); lefttop.x = rect.getMinX(); lefttop.y = rect.getMaxY(); rightbottom.x = rect.getMaxX(); rightbottom.y = rect.getMinY(); auto boneToSkeletonParentTrans = AffineTransformConcat( weaponHandeBone->getNodeToParentAffineTransform(skeletonNode), skeletonToP); leftbottom = PointApplyAffineTransform(leftbottom, boneToSkeletonParentTrans); righttop = PointApplyAffineTransform(righttop, boneToSkeletonParentTrans); lefttop = PointApplyAffineTransform(lefttop, boneToSkeletonParentTrans); rightbottom = PointApplyAffineTransform(rightbottom, boneToSkeletonParentTrans); debugDrawNode->drawLine(leftbottom, rightbottom, Color4F::GREEN); debugDrawNode->drawLine(rightbottom, righttop, Color4F::GREEN); debugDrawNode->drawLine(righttop, lefttop, Color4F::GREEN); debugDrawNode->drawLine(lefttop, leftbottom, Color4F::GREEN); } }, 0, "update debug draw"); // change displays , can be use for dress up a skeleton auto changeBoneDispsBtn = cocos2d::ui::Button::create(); addChild(changeBoneDispsBtn); changeBoneDispsBtn->setPosition(Vec2(VisibleRect::right().x - 60, VisibleRect::top().y - 75)); changeBoneDispsBtn->setTitleText("change bone displays"); std::map < std::string, std::string> boneSkinNames; boneSkinNames.insert(std::make_pair("Layer20", "fire")); boneSkinNames.insert(std::make_pair("Layer14", "fruit")); skeletonNode->addSkinGroup("fruitKnife", boneSkinNames); std::map < std::string, std::string> boneSkinNames2; boneSkinNames2.insert(std::make_pair("Layer20", "3")); boneSkinNames2.insert(std::make_pair("Layer14", "hat")); skeletonNode->addSkinGroup("cowboy", boneSkinNames2); changeBoneDispsBtn->addClickEventListener([skeletonNode, this](Ref* sender) { if (!_changedDisplays) { skeletonNode->changeSkins("fruitKnife"); _changedDisplays = true; } else { skeletonNode->changeSkins("cowboy"); _changedDisplays = false; } }); /*********** test cases for bugs **********/ // bug: #13060 https://github.com/cocos2d/cocos2d-x/issues/13060 // bug: bone draw at the other edge when move to outside right edge. BoneNode* bugtestBoneNode = BoneNode::create(500); bugtestBoneNode->setRotation(-10); bugtestBoneNode->retain(); bugtestBoneNode->setDebugDrawEnabled(true); bugtestBoneNode->setPosition(Vec2(1500, VisibleRect::top().y - 90)); auto bug13060Btn = cocos2d::ui::Button::create(); bug13060Btn->setPosition(Vec2(VisibleRect::right().x - 30, VisibleRect::top().y - 90)); bug13060Btn->setTitleText("bug #13060"); addChild(bug13060Btn); bug13060Btn->addClickEventListener([bugtestBoneNode, skeletonNode](Ref* sender) { if (bugtestBoneNode->getParent() == nullptr) skeletonNode->addChild(bugtestBoneNode); else bugtestBoneNode->removeFromParent(); // bug fixed while bugtestBoneNode not be drawn at the bottom edge }); // bug: #13005 https://github.com/cocos2d/cocos2d-x/issues/#13005 // bug: BoneNode 's debugdraw can not be controlled by ancestor's visible auto leftleg = skeletonNode->getBoneNode("Layer26"); auto bug13005Btn = cocos2d::ui::Button::create(); addChild(bug13005Btn); bug13005Btn->setPosition(Vec2(VisibleRect::right().x - 30, VisibleRect::top().y - 105)); bug13005Btn->setTitleText("bug #13005"); bug13005Btn->addClickEventListener([leftleg](Ref* sender) { leftleg->setVisible(!leftleg->isVisible()); // bug fixed while leftleg's child hide with leftleg's visible }); /************* Skeleton nest Skeleton test *************/ auto nestSkeletonBtn = cocos2d::ui::Button::create(); nestSkeletonBtn->setTitleText("Skeleton Nest"); nestSkeletonBtn->setPosition(Vec2(VisibleRect::right().x - 40, VisibleRect::top().y - 120)); addChild(nestSkeletonBtn); auto nestSkeleton = static_cast<SkeletonNode*>(CSLoader::createNode("ActionTimeline/DemoPlayer_skeleton.csb")); nestSkeleton->retain(); ActionTimeline* nestSkeletonAction = action->clone(); nestSkeletonAction->retain(); nestSkeleton->runAction(nestSkeletonAction); nestSkeleton->setScale(0.2f); nestSkeleton->setPosition(150, 300); nestSkeletonAction->gotoFrameAndPlay(0); // show debug draws, or comment this for hide bones draws for (auto& nestbonechild : nestSkeleton->getAllSubBonesMap()) { nestbonechild.second->setDebugDrawEnabled(true); } nestSkeletonBtn->addClickEventListener([leftleg, nestSkeleton, nestSkeletonAction](Ref* sender) { if (nestSkeleton->getParent() == nullptr) { leftleg->addChild(nestSkeleton); } else { nestSkeleton->removeFromParentAndCleanup(false); } }); }