/* Copyright (C) 2007, 2010 - Bit-Blot This file is part of Aquaria. Aquaria 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "SkeletalSprite.h" #include "Core.h" #include "Particles.h" #include "MathFunctions.h" #include "SimpleIStringStream.h" #include "ReadXML.h" #include "RenderBase.h" #include using namespace tinyxml2; std::string SkeletalSprite::animationPath = "data/animations/"; std::string SkeletalSprite::skinPath = "skins/"; std::string SkeletalSprite::secondaryAnimationPath = ""; static std::map skelCache; static XMLDocument *_retrieveSkeletalXML(const std::string& name, bool keepEmpty) { std::map::iterator it = skelCache.find(name); if(it != skelCache.end()) return it->second; XMLDocument *doc = readXML(name, NULL, keepEmpty); if(doc) skelCache[name] = doc; return doc; } void SkeletalSprite::clearCache() { for(std::map::iterator it = skelCache.begin(); it != skelCache.end(); ++it) delete it->second; skelCache.clear(); } void SkeletalKeyframe::copyAllButTime(SkeletalKeyframe *copy) { if (!copy) return; float t = this->t; (*this) = (*copy); this->t = t; } Bone::Bone() : CollideQuad() { addType(SCO_BONE); fileRenderQuad = true; skeleton = 0; generateCollisionMask = true; enableCollision = true; animated = ANIM_ALL; originalScale = Vector(1,1); boneIdx = pidx = -1; rbp = false; segmentChain = 0; collisionMaskRadius = 0; minDist = maxDist = 128; reverse = false; selectable = true; originalRenderPass = 0; stripVert = false; } Bone::~Bone() { } ParticleEffect *Bone::getEmitter(unsigned slot) const { return slot < emitters.size() ? emitters[slot] : NULL; } void Bone::destroy() { Quad::destroy(); for (size_t i = 0; i < segments.size(); i++) { segments[i]->setLife(1.0); segments[i]->setDecayRate(10); segments[i]->alpha = 0; } segments.clear(); } bool Bone::canCollide() const { return this->enableCollision && this->alpha.x == 1 && this->renderQuad && (!this->collisionMask.empty() || this->collideRadius); } void Bone::addSegment(Bone *b) { segments.push_back(b); b->segmentChain = 2; skeleton->removeChild(b); core->getTopStateData()->addRenderObject(b, skeleton->getTopLayer()); b->position = this->getWorldPosition(); } void Bone::createStrip(bool vert, int num) { if (!vert) { createGrid(num, 2); } else { createGrid(2, num); } stripVert = vert; gridType = GRID_SET; changeStrip.resize(num); } Quad* Bone::addFrame(const std::string &gfx) { renderQuad = false; Quad *q = new Quad(); q->setTexture(gfx); q->renderBeforeParent = 1; addChild(q, PM_POINTER); return q; } void Bone::showFrame(int idx) { int c = 0; for (Children::iterator i = children.begin(); i != children.end(); i++) { RenderObject *r = (*i); if (idx == c) { if (r->alpha == 0) { r->alpha = 1; // add option to turn on alpha fading //r->alpha.interpolateTo(1, t); } else { r->alpha = 1; } } else { if (r->alpha == 1) { r->alpha = 0; //r->alpha.interpolateTo(0, t*2); } else { r->alpha = 0; } } c++; } } void Bone::setAnimated(int b) { animated = b; } void Bone::setSegmentProps(int minDist, int maxDist, bool reverse) { this->minDist = minDist; this->maxDist = maxDist; this->reverse = reverse; } void Bone::updateSegment(Bone *b, const Vector &diff) { float angle = -1; if (diff.getSquaredLength2D() > sqr(maxDist)) { Vector useDiff = diff; useDiff.setLength2D(maxDist); Vector reallyUseDiff = diff - useDiff; b->position += reallyUseDiff; MathFunctions::calculateAngleBetweenVectorsInDegrees(Vector(0,0,0), diff, angle); } else if (diff.getSquaredLength2D() > sqr(minDist)) { b->position += diff*0.05f; MathFunctions::calculateAngleBetweenVectorsInDegrees(Vector(0,0,0), diff, angle); } if (angle != -1) { if (b->rotation.z >= 270 && angle < 90) { b->rotation.stop(); b->rotation.z -= 360; } if (b->rotation.z <= 90 && angle > 270) { b->rotation.stop(); b->rotation.z += 360; } b->rotation.interpolateTo(Vector(0,0,angle),0.2f); } } void Bone::updateSegments() { if (segmentChain>0 && !segments.empty()) { if (!reverse) { for (size_t i = 0; i < segments.size(); i++) { Vector diff; if (i == 0) { Vector world = getWorldCollidePosition(segmentOffset); diff = world - segments[i]->getWorldPosition(); } else diff = segments[i-1]->getWorldPosition() - segments[i]->getWorldPosition(); updateSegment(segments[i], diff); } } else { int top = segments.size()-1; for (int i = top; i >= 0; i--) { Vector diff; if (i == top) { Vector world = getWorldCollidePosition(segmentOffset); diff = world - segments[i]->getWorldPosition(); } else diff = segments[i+1]->getWorldPosition() - segments[i]->getWorldPosition(); updateSegment(segments[i], diff); } } } } void Bone::spawnParticlesFromCollisionMask(const char *p, unsigned intv, int layer, float rotz) { for (size_t j = 0; j < this->collisionMask.size(); j+=intv) { Vector pos = this->getWorldCollidePosition(this->collisionMask[j]); core->createParticleEffect(p, pos, layer, rotz); } } void Bone::renderCollision(const RenderState& rs) const { if (!collisionMask.empty()) { glPushAttrib(GL_ALL_ATTRIB_BITS); glPushMatrix(); glBindTexture(GL_TEXTURE_2D, 0); glLoadIdentity(); core->setupRenderPositionAndScale(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(1,1,0,0.5); for (size_t i = 0; i < transformedCollisionMask.size(); i++) { Vector collide = this->transformedCollisionMask[i]; glTranslatef(collide.x, collide.y, 0); RenderObject *parent = this->getTopParent(); if (parent) drawCircle(collideRadius*parent->scale.x, 45); glTranslatef(-collide.x, -collide.y, 0); } glDisable(GL_BLEND); glPopMatrix(); glPopAttrib(); } else CollideQuad::renderCollision(rs); } Vector Bone::getCollisionMaskNormal(size_t index) { Vector sum; size_t num=0; for (size_t i = 0; i < this->transformedCollisionMask.size(); i++) { if (i != index) { Vector diff = transformedCollisionMask[index] - transformedCollisionMask[i]; if (diff.isLength2DIn(128)) { sum += diff; num++; } } } if (!sum.isZero()) { sum /= num; sum.normalize2D(); } return sum; } bool BoneCommand::parse(Bone *b, SimpleIStringStream &is) { std::string type; is >> type; this->b = b; if (type=="AC_PRT_LOAD") { command = AC_PRT_LOAD; is >> slot >> file; } else if (type=="AC_SND_PLAY") { command = AC_SND_PLAY; is >> file; } else if (type=="AC_FRM_SHOW") { command = AC_FRM_SHOW; is >> slot; } else if (type=="AC_PRT_START") { command = AC_PRT_START; is >> slot; } else if (type=="AC_PRT_STOP") { command = AC_PRT_STOP; is >> slot; } else if (type=="AC_SEGS_START") command = AC_SEGS_START; else if (type=="AC_SEGS_STOP") command = AC_SEGS_STOP; else if (type == "AC_SET_PASS") { command = AC_SET_PASS; is >> slot; } else if(type == "AC_RESET_PASS") command = AC_RESET_PASS; else // fail { std::ostringstream os; os << "Failed to parse bone command string: invalid command: " << type; errorLog(os.str()); } return true; } void BoneCommand::run() { switch(command) { case AC_SND_PLAY: { core->sound->playSfx(file); } break; case AC_FRM_SHOW: { b->showFrame(slot); } break; case AC_PRT_LOAD: { ParticleEffect *e = b->getEmitter(slot); if (e) { e->load(file); } } break; case AC_PRT_START: { ParticleEffect *e = b->getEmitter(slot); if (e) e->start(); } break; case AC_PRT_STOP: { ParticleEffect *e = b->getEmitter(slot); if (e) e->stop(); } break; case AC_SET_PASS: b->setRenderPass(slot); break; case AC_RESET_PASS: b->setRenderPass(b->originalRenderPass); break; case AC_SEGS_START: case AC_SEGS_STOP: break; } } AnimationLayer::AnimationLayer() { lastNewKey = 0; fallThru= 0; timer = 0; loop = 0; enqueuedAnimationLoop = 0; timeMultiplier = 1; animationLength = 0; currentAnimation = 0; animating = false; fallThruSpeed = 0; s = 0; } void AnimationLayer::setTimeMultiplier(float t) { timeMultiplier = t; } void AnimationLayer::playCurrentAnimation(int loop) { playAnimation(currentAnimation, loop); } void AnimationLayer::animate(const std::string &a, int loop) { std::string animation = a; stringToLower(animation); bool played = false; for (size_t i = 0; i < s->animations.size(); i++) { if (s->animations[i].name == animation) { playAnimation(i, loop); played = true; break; } } if (!played) { std::ostringstream os; os << "Could not find animation: " << animation; debugLog(os.str()); } } void AnimationLayer::playAnimation(int idx, int loop) { if (!(&s->animLayers[0] == this)) { fallThru = 1; fallThruSpeed = 10; } timeMultiplier = 1; currentAnimation = idx; timer = 0; animating = true; this->loop = loop; animationLength = getCurrentAnimation()->getAnimationLength(); } void AnimationLayer::enqueueAnimation(const std::string& anim, int loop) { enqueuedAnimation = anim; enqueuedAnimationLoop = loop; stringToLower(enqueuedAnimation); } float AnimationLayer::transitionAnimate(std::string anim, float time, int loop) { stringToLower(anim); float totalTime =0; if (createTransitionAnimation(anim, time)) { timeMultiplier = 1; currentAnimation = -1; this->loop = 0; timer = 0; animating = 1; animationLength = getCurrentAnimation()->getAnimationLength(); enqueueAnimation(anim, loop); Animation *a = this->s->getAnimation(anim); if (a) { if (loop > -1) totalTime = a->getAnimationLength()*(loop+1) + time; else totalTime = a->getAnimationLength() + time; } } return totalTime; } void AnimationLayer::setSkeletalSprite(SkeletalSprite *s) { this->s = s; } Animation* AnimationLayer::getCurrentAnimation() { if (currentAnimation == -1) return &blendAnimation; if (currentAnimation >= s->animations.size()) { std::ostringstream os; os << "skel: " << s->filenameLoaded << " currentAnimation: " << currentAnimation << " is out of range\n error in anim file?"; exit_error(os.str()); return 0; } return &s->animations[currentAnimation]; } bool AnimationLayer::createTransitionAnimation(const std::string& anim, float time) { Animation *to = s->getAnimation(anim); if (!to) return false; blendAnimation.keyframes.clear(); SkeletalKeyframe k; k.t = 0; for (size_t i = 0; i < s->bones.size(); i++) { BoneKeyframe b; b.idx = s->bones[i]->boneIdx; b.x = s->bones[i]->position.x; b.y = s->bones[i]->position.y; b.rot = s->bones[i]->rotation.z; b.sx = s->bones[i]->scale.x; b.sy = s->bones[i]->scale.y; k.keyframes.push_back(b); } blendAnimation.keyframes.push_back(k); SkeletalKeyframe k2; SkeletalKeyframe *rk = to->getKeyframe(0); if (!rk) return false; k2 = *rk; k2.t = time; blendAnimation.keyframes.push_back(k2); blendAnimation.name = anim; return true; } void AnimationLayer::stopAnimation() { if(s->loaded && getCurrentAnimation()->resetPassOnEnd) resetPass(); animating = false; if (!enqueuedAnimation.empty()) { animate(enqueuedAnimation, enqueuedAnimationLoop); enqueuedAnimation = ""; enqueuedAnimationLoop = 0; } } bool AnimationLayer::isAnimating() { return animating; } float AnimationLayer::getAnimationLength() { return animationLength; } Animation::Animation() : resetPassOnEnd(false) { } size_t Animation::getNumKeyframes() { return keyframes.size(); } SkeletalKeyframe *Animation::getKeyframe(size_t key) { if (key >= keyframes.size()) return 0; return &keyframes[key]; } void Animation::reverse() { Keyframes copy = keyframes; Keyframes copy2 = keyframes; keyframes.clear(); int sz = copy.size()-1; for (int i = sz; i >= 0; i--) { keyframes.push_back(copy[i]); keyframes[keyframes.size()-1].t = copy2[sz-i].t; } reorderKeyframes(); } float Animation::getAnimationLength() { return getLastKeyframe()->t; } SkeletalKeyframe *Animation::getLastKeyframe() { if (!keyframes.empty()) return &keyframes[keyframes.size()-1]; return 0; } SkeletalKeyframe *Animation::getFirstKeyframe() { if (!keyframes.empty()) return &keyframes[0]; return 0; } void Animation::reorderKeyframes() { for (size_t i = 0; i < keyframes.size(); i++) { for (size_t j = 0; j < keyframes.size()-1; j++) { if (keyframes[j].t > keyframes[j+1].t) { SkeletalKeyframe temp = keyframes[j+1]; keyframes[j+1] = keyframes[j]; keyframes[j] = temp; } } } } void Animation::cloneKey(size_t key, float toffset) { std::vector copy = this->keyframes; keyframes.clear(); size_t i = 0; for (i = 0; i <= key; i++) keyframes.push_back(copy[i]); for (i = key; i < copy.size(); i++) keyframes.push_back(copy[i]); keyframes[key+1].t += toffset; } void Animation::deleteKey(size_t key) { std::vector copy = this->keyframes; keyframes.clear(); size_t i = 0; for (i = 0; i < key; i++) keyframes.push_back(copy[i]); for (i = key+1; i < copy.size(); i++) keyframes.push_back(copy[i]); } size_t Animation::getSkeletalKeyframeIndex(SkeletalKeyframe *skey) { for (size_t i = 0; i < keyframes.size(); i++) { if (&keyframes[i] == skey) return i; } return -1; } BoneKeyframe *SkeletalKeyframe::getBoneKeyframe(size_t idx) { for (size_t i = 0; i < keyframes.size(); i++) { if (keyframes[i].idx == idx) { return &keyframes[i]; } } return 0; } SkeletalKeyframe *Animation::getPrevKeyframe(float t) { size_t kf = -1; for (size_t i = keyframes.size(); i-- > 0; ) { if (t >= keyframes[i].t) { kf = i; break; } } if (kf == -1) return 0; if (kf >= keyframes.size()) kf = keyframes.size()-1; return &keyframes[kf]; } SkeletalKeyframe *Animation::getNextKeyframe(float t) { size_t kf = -1; for (size_t i = 0; i < keyframes.size(); i++) { if (t <= keyframes[i].t) { kf = i; break; } } if (kf == -1) return 0; if (kf >= keyframes.size()) kf = keyframes.size()-1; return &keyframes[kf]; } SkeletalSprite::SkeletalSprite() : RenderObject() { frozen = false; animKeyNotify = 0; loaded = false; animLayers.resize(10); for (size_t i = 0; i < animLayers.size(); i++) animLayers[i].setSkeletalSprite(this); selectedBone = -1; } SkeletalSprite::~SkeletalSprite() { } void SkeletalSprite::destroy() { bones.clear(); // they are added as children too, so the next call will do the actual deletion RenderObject::destroy(); } void SkeletalSprite::setAnimationKeyNotify(RenderObject *r) { animKeyNotify = r; } void SkeletalSprite::animate(const std::string &animation, int loop, int layer) { animLayers[layer].animate(animation, loop); } float SkeletalSprite::transitionAnimate(const std::string& anim, float time, int loop, int layer) { AnimationLayer *animLayer = getAnimationLayer(layer); if (animLayer) return animLayer->transitionAnimate(anim, time, loop); std::ostringstream os; os << "playing animation on invalid layer: " << layer; errorLog(os.str()); return 0; } AnimationLayer* SkeletalSprite::getAnimationLayer(size_t l) { if (l < animLayers.size()) { return &animLayers[l]; } std::ostringstream os; os << "couldn't get animLayer: " << l; debugLog(os.str()); return 0; } bool SkeletalSprite::isLoaded() { return loaded; } void SkeletalSprite::onUpdate(float dt) { if (frozen) return; RenderObject::onUpdate(dt); size_t i = 0; for (i = 0; i < bones.size(); i++) { Bone *b = bones[i]; if (b && !b->collisionMask.empty()) { if (b->collisionMask.size() != b->transformedCollisionMask.size()) { b->transformedCollisionMask.resize(b->collisionMask.size()); } for (size_t i = 0; i < b->collisionMask.size(); i++) { b->transformedCollisionMask[i] = b->getWorldCollidePosition(b->collisionMask[i]); } } } for (i = 0; i < animLayers.size(); i++) { animLayers[i].update(dt); } } void AnimationLayer::update(float dt) { timeMultiplier.update(dt); if (animating) { timer += dt*timeMultiplier.x; if (timer > animationLength) { float leftover; if (animationLength > 0) leftover = fmodf(timer, animationLength); else leftover = 0; timer = animationLength; if (loop==-1 || loop > 0) { playAnimation(this->currentAnimation, loop); if (loop > 0) loop --; timer = leftover; } else { stopAnimation(); } } updateBones(); } else if (!animating) { if (fallThru > 0) { fallThru -= dt * fallThruSpeed; if (fallThru < 0) fallThru = 0; updateBones(); } } } bool SkeletalSprite::saveSkeletal(const std::string &fn) { std::string file, filename=fn; stringToLower(filename); if (!secondaryAnimationPath.empty()) { createDir(secondaryAnimationPath); file = secondaryAnimationPath + filename + ".xml"; } else { file = animationPath + filename + ".xml"; } size_t i = 0; XMLDocument *xml = _retrieveSkeletalXML(file, true); xml->Clear(); XMLElement *animationLayers = xml->NewElement("AnimationLayers"); for (i = 0; i < animLayers.size(); i++) { XMLElement *animationLayer = xml->NewElement("AnimationLayer"); if (animLayers[i].ignoreBones.size() > 0) { std::ostringstream os; for (size_t j = 0; j < animLayers[i].ignoreBones.size(); j++) { os << animLayers[i].ignoreBones[j] << " "; } animationLayer->SetAttribute("ignore", os.str().c_str()); } if (animLayers[i].includeBones.size() > 0) { std::ostringstream os; for (size_t j = 0; j < animLayers[i].includeBones.size(); j++) { os << animLayers[i].includeBones[j] << " "; } animationLayer->SetAttribute("include", os.str().c_str()); } if (!animLayers[i].name.empty()) { animationLayer->SetAttribute("name", animLayers[i].name.c_str()); } animationLayers->InsertEndChild(animationLayer); } xml->InsertEndChild(animationLayers); XMLElement *bones = xml->NewElement("Bones"); for (i = 0; i < this->bones.size(); i++) { XMLElement *bone = xml->NewElement("Bone"); bone->SetAttribute("idx", (unsigned int) this->bones[i]->boneIdx); bone->SetAttribute("gfx", this->bones[i]->gfx.c_str()); bone->SetAttribute("pidx", this->bones[i]->pidx); bone->SetAttribute("name", this->bones[i]->name.c_str()); bone->SetAttribute("fh", this->bones[i]->isfh()); bone->SetAttribute("fv", this->bones[i]->isfv()); bone->SetAttribute("gc", this->bones[i]->generateCollisionMask); bone->SetAttribute("cr", this->bones[i]->collideRadius); if(!this->bones[i]->enableCollision) bone->SetAttribute("c", this->bones[i]->enableCollision); if (!this->bones[i]->fileRenderQuad) { bone->SetAttribute("rq", this->bones[i]->fileRenderQuad); } if (!this->bones[i]->selectable) { bone->SetAttribute("sel", this->bones[i]->selectable); } if (this->bones[i]->rbp) bone->SetAttribute("rbp", (int)this->bones[i]->rbp); if (this->bones[i]->originalRenderPass) bone->SetAttribute("pass", this->bones[i]->originalRenderPass); if (this->bones[i]->offset.x) bone->SetAttribute("offx", this->bones[i]->offset.x); if (this->bones[i]->offset.y) bone->SetAttribute("offy", this->bones[i]->offset.y); if (!this->bones[i]->prt.empty()) bone->SetAttribute("prt", this->bones[i]->prt.c_str()); if (!this->bones[i]->changeStrip.empty()) { std::ostringstream os; os << this->bones[i]->stripVert << " " << this->bones[i]->changeStrip.size(); bone->SetAttribute("strip", os.str().c_str()); } if (!this->bones[i]->internalOffset.isZero()) { std::ostringstream os; os << this->bones[i]->internalOffset.x << " " << this->bones[i]->internalOffset.y; bone->SetAttribute("io", os.str().c_str()); } if (this->bones[i]->isRepeatingTextureToFill()) { bone->SetAttribute("rt", 1); } if (this->bones[i]->originalScale.x != 1 || this->bones[i]->originalScale.y != 1) { std::ostringstream os; os << this->bones[i]->originalScale.x << " " << this->bones[i]->originalScale.y; bone->SetAttribute("sz", os.str().c_str()); } for (Children::iterator j = this->bones[i]->children.begin(); j != this->bones[i]->children.end(); j++) { Bone *b = dynamic_cast(*j); Quad *q = dynamic_cast(*j); Particle *p = dynamic_cast(*j); if (q && !b && !p) { XMLElement *frame = xml->NewElement("Frame"); frame->SetAttribute("gfx", q->texture->name.c_str()); if (q->getRenderPass() != 0) { frame->SetAttribute("pass", q->getRenderPass()); } bone->InsertEndChild(frame); } } bones->InsertEndChild(bone); } xml->InsertEndChild(bones); XMLElement *animations = xml->NewElement("Animations"); for (i = 0; i < this->animations.size(); i++) { Animation *a = &this->animations[i]; XMLElement *animation = xml->NewElement("Animation"); animation->SetAttribute("name", a->name.c_str()); if(a->resetPassOnEnd) animation->SetAttribute("resetPassOnEnd", a->resetPassOnEnd); for (size_t j = 0; j < a->keyframes.size(); j++) { XMLElement *key = xml->NewElement("Key"); if (!a->keyframes[j].sound.empty()) key->SetAttribute("sound", a->keyframes[j].sound.c_str()); if (!a->keyframes[j].cmd.empty()) { key->SetAttribute("cmd", a->keyframes[j].cmd.c_str()); } if (a->keyframes[j].lerpType != 0) { key->SetAttribute("lerp", a->keyframes[j].lerpType); } std::ostringstream os; os << a->keyframes[j].t << " "; std::ostringstream szos; for (size_t k = 0; k < a->keyframes[j].keyframes.size(); k++) { BoneKeyframe *b = &a->keyframes[j].keyframes[k]; os << b->idx << " " << b->x << " " << b->y << " " << b->rot << " "; os << b->strip.size() << " "; for (size_t i = 0; i < b->strip.size(); i++) { os << b->strip[i].x << " " << b->strip[i].y << " "; } if (b->doScale) { szos << b->idx << " " << b->sx << " " << b->sy << " "; } } std::string szoss = szos.str(); if (!szoss.empty()) key->SetAttribute("sz", szoss.c_str()); key->SetAttribute("e", os.str().c_str()); animation->InsertEndChild(key); } animations->InsertEndChild(animation); } xml->InsertEndChild(animations); return xml->SaveFile(file.c_str()) == XML_SUCCESS; } size_t SkeletalSprite::getBoneIdx(Bone *b) { for (size_t i = 0; i < bones.size(); i++) { if (bones[i] == b) return i; } return -1; } void SkeletalSprite::toggleBone(size_t idx, int v) { if (idx < bones.size()) { bones[idx]->alpha.x = v; } } Bone *SkeletalSprite::getBoneByName(const std::string &name) { for (size_t i = 0; i < bones.size(); i++) { if (bones[i]->name == name) return bones[i]; } std::ostringstream os; os << "Could not find bone with name[" << name << "]"; debugLog(os.str()); return 0; } Bone *SkeletalSprite::getBoneByIdx(size_t idx) { for (size_t i = 0; i < bones.size(); i++) { if (bones[i]->boneIdx == idx) return bones[i]; } std::ostringstream os; os << "Could not find bone with idx[" << idx << "]"; debugLog(os.str()); return 0; } Bone *SkeletalSprite::initBone(int idx, std::string gfx, int pidx, bool rbp, std::string name, float cr, bool fh, bool fv) { Bone *b = new Bone; b->boneIdx = idx; b->setTexture(gfx); b->skeleton = this; b->gfx = gfx; b->rbp = rbp; b->renderBeforeParent = rbp; b->pidx = pidx; b->collideRadius = cr; b->name = name; if (fh) b->flipHorizontal(); if (fv) b->flipVertical(); bones.push_back(b); return b; } void SkeletalSprite::firstAnimation() { stopAnimation(); animLayers[0].currentAnimation = 0; } void SkeletalSprite::lastAnimation() { stopAnimation(); animLayers[0].currentAnimation = animations.size()-1; } void SkeletalSprite::nextAnimation() { stopAnimation(); animLayers[0].currentAnimation++; if (animLayers[0].currentAnimation >= animations.size()) animLayers[0].currentAnimation = 0; } void SkeletalSprite::prevAnimation() { stopAnimation(); animLayers[0].currentAnimation--; if (animLayers[0].currentAnimation >= animations.size()) animLayers[0].currentAnimation = animations.size()-1; } void SkeletalSprite::deleteBones() { bones.clear(); for(Children::iterator it = children.begin(); it != children.end(); ++it) { (*it)->safeKill(); } } Animation *SkeletalSprite::getAnimation(const std::string& anim) { for (size_t i = 0; i < animations.size(); i++) { if (animations[i].name == anim) return &animations[i]; } return 0; } void SkeletalSprite::loadSkin(const std::string &fn) { std::string file; if (!secondaryAnimationPath.empty()) { file = secondaryAnimationPath + skinPath + fn + ".xml"; } if (file.empty() || !exists(file, false)) { file = animationPath + skinPath + fn + ".xml"; } file = adjustFilenameCase(file); if (!exists(file)) { errorLog("Could not load skin[" + file + "] - File not found."); return; } XMLDocument *d = _retrieveSkeletalXML(file, false); if(!d) { errorLog("Could not load skin[" + file + "] - Malformed XML."); return; } XMLElement *bonesXml = d->FirstChildElement("Bones"); if (bonesXml) { XMLElement *boneXml = bonesXml->FirstChildElement("Bone"); while (boneXml) { int idx = atoi(boneXml->Attribute("idx")); Bone *b = getBoneByIdx(idx); if (b) { if (boneXml->Attribute("rq")) { int rq = atoi(boneXml->Attribute("rq")); b->renderQuad = !!rq; } std::string gfx; if (boneXml->Attribute("gfx")) { gfx = boneXml->Attribute("gfx"); if (!gfx.empty()) { b->gfx = gfx; b->setTexture(b->gfx); b->renderQuad = true; } } if (gfx.empty()) { b->renderQuad = false; } if (boneXml->Attribute("fh")) { int fh = atoi(boneXml->Attribute("fh")); if (fh) b->flipHorizontal(); } if (boneXml->Attribute("fv")) { int fv = atoi(boneXml->Attribute("fv")); if (fv) b->flipVertical(); } } else { std::ostringstream os; os << "SkinLoad: Could not find idx[" << idx << "]"; debugLog(os.str()); } boneXml = boneXml->NextSiblingElement("Bone"); } } } void SkeletalSprite::stopAnimation(int layer) { if(size_t(layer) < animLayers.size()) animLayers[layer].stopAnimation(); } void SkeletalSprite::stopAllAnimations() { for (size_t i = 0; i < animLayers.size(); i++) { animLayers[i].stopAnimation(); } } void SkeletalSprite::playCurrentAnimation(int loop, int layer) { if(size_t(layer) < animLayers.size()) animLayers[layer].playCurrentAnimation(loop); } void SkeletalSprite::loadSkeletal(const std::string &fn) { filenameLoaded = ""; loaded = false; stopAnimation(); animLayers.clear(); deleteBones(); filenameLoaded = fn; stringToLower(filenameLoaded); std::string file; if (!secondaryAnimationPath.empty()) { file = secondaryAnimationPath + filenameLoaded + ".xml"; } if (file.empty() || !exists(file, false)) { file = animationPath + filenameLoaded + ".xml"; } if (!exists(file)) { filenameLoaded = ""; errorLog("Could not load skeletal[" + file + "] - File not found."); return; } file = adjustFilenameCase(file); XMLDocument *xml = _retrieveSkeletalXML(file, false); if(!xml) { filenameLoaded = ""; errorLog("Could not load skeletal[" + file + "] - Malformed XML."); return; } loaded = true; XMLElement *bones = xml->FirstChildElement("Bones"); if (bones) { if (bones->Attribute("scale")) { SimpleIStringStream is(bones->Attribute("scale")); is >> scale.x >> scale.y; } XMLElement *bone = bones->FirstChildElement("Bone"); while(bone) { int idx = atoi(bone->Attribute("idx")); int pidx = -1, rbp=0, cr=0, fh=0, fv=0; std::string name; if (bone->Attribute("pidx")) pidx = atoi(bone->Attribute("pidx")); if (bone->Attribute("rbp")) rbp = atoi(bone->Attribute("rbp")); if (bone->Attribute("name")) name = bone->Attribute("name"); if (bone->Attribute("cr")) cr = atoi(bone->Attribute("cr")); if (bone->Attribute("fh")) fh = atoi(bone->Attribute("fh")); if (bone->Attribute("fv")) fv = atoi(bone->Attribute("fv")); std::string gfx = bone->Attribute("gfx"); Bone *newb = initBone(idx, gfx, pidx, rbp, name, cr, fh, fv); if (bone->Attribute("offx")) newb->offset.x = atoi(bone->Attribute("offx")); if (bone->Attribute("offy")) newb->offset.y = atoi(bone->Attribute("offy")); if (bone->Attribute("prt")) { newb->prt = bone->Attribute("prt"); SimpleIStringStream is(newb->prt); int slot; std::string pfile; while (is >> slot) { if(slot < 0) { errorLog("particle slot < 0"); break; } is >> pfile; // add particle system + load ParticleEffect *e = new ParticleEffect; if(newb->emitters.size() <= (size_t)slot) newb->emitters.resize(slot+4, NULL); newb->emitters[slot] = e; newb->addChild(e, PM_POINTER); e->load(pfile); } } XMLElement *fr=0; fr = bone->FirstChildElement("Frame"); int frc=0; while(fr) { Quad *q=0; std::string gfx; if (fr->Attribute("gfx")) { gfx = fr->Attribute("gfx"); q = newb->addFrame(gfx); } if (fr->Attribute("pass")) { if (q) { q->setRenderPass(atoi(fr->Attribute("pass"))); } } fr = fr->NextSiblingElement("Frame"); frc++; } if (frc) { newb->showFrame(0); } if (bone->Attribute("pass")) { int pass = atoi(bone->Attribute("pass")); newb->originalRenderPass = pass; newb->setRenderPass(pass); } if (bone->Attribute("gc")) { newb->generateCollisionMask = atoi(bone->Attribute("gc")); } if (bone->Attribute("c")) { newb->enableCollision = atoi(bone->Attribute("c")); } if (bone->Attribute("rq")) { newb->renderQuad = newb->fileRenderQuad = atoi(bone->Attribute("rq")); } if (bone->Attribute("io")) { SimpleIStringStream is(bone->Attribute("io")); is >> newb->internalOffset.x >> newb->internalOffset.y; } if (bone->Attribute("strip")) { SimpleIStringStream is(bone->Attribute("strip")); bool vert; int num; is >> vert >> num; newb->createStrip(vert, num); } if (bone->Attribute("sz")) { float sx, sy; SimpleIStringStream is(bone->Attribute("sz")); is >> sx >> sy; newb->scale = newb->originalScale = Vector(sx,sy); } if (bone->Attribute("rt")) { newb->repeatTextureToFill(true); } if (bone->Attribute("blend")) { //if (bone->Attribute("blend")=="add") newb->setBlendType(BLEND_ADD); //this->setBlendType(BLEND_ADD); // FIXME: seems wrong to do this here -- fg } if (bone->Attribute("alpha")) { float a=1.0; SimpleIStringStream is(bone->Attribute("alpha")); is >> a; newb->alpha = a; } if (bone->Attribute("alphaMod")) { float a=1.0; SimpleIStringStream is(bone->Attribute("alphaMod")); is >> a; newb->alphaMod = a; } if (bone->Attribute("segs")) { int x, y; float dgox, dgoy, dgmx, dgmy, dgtm; bool dgo; SimpleIStringStream is(bone->Attribute("segs")); is >> x >> y >> dgox >> dgoy >> dgmx >> dgmy >> dgtm >> dgo; newb->setSegs(x, y, dgox, dgoy, dgmx, dgmy, dgtm, dgo); } if (bone->Attribute("color")) { SimpleIStringStream in(bone->Attribute("color")); in >> newb->color.x >> newb->color.y >> newb->color.z; } if (bone->Attribute("sel")) { newb->selectable = bone->BoolAttribute("sel"); } bone = bone->NextSiblingElement("Bone"); } // attach bones for (size_t i = 0; i < this->bones.size(); i++) { Bone *b = this->bones[i]; if (b->pidx != -1) { Bone *pb = getBoneByIdx(b->pidx); if (!pb) { std::ostringstream os; os << "Parent bone not found, index: " << b->pidx << " from bone idx: " << b->getIdx(); debugLog(os.str()); } else { pb->addChild(b, PM_POINTER); } } else addChild(b, PM_POINTER); } } animLayers.clear(); XMLElement *animationLayers = xml->FirstChildElement("AnimationLayers"); if (animationLayers) { XMLElement *animationLayer = animationLayers->FirstChildElement("AnimationLayer"); while (animationLayer) { AnimationLayer newAnimationLayer; if (animationLayer->Attribute("ignore")) { SimpleIStringStream is(animationLayer->Attribute("ignore")); int t; while (is >> t) { newAnimationLayer.ignoreBones.push_back(t); } } if (animationLayer->Attribute("include")) { SimpleIStringStream is(animationLayer->Attribute("include")); int t; while (is >> t) { newAnimationLayer.includeBones.push_back(t); } } if (animationLayer->Attribute("name")) { newAnimationLayer.name = animationLayer->Attribute("name"); } newAnimationLayer.setSkeletalSprite(this); animLayers.push_back(newAnimationLayer); animationLayer = animationLayer->NextSiblingElement("AnimationLayer"); } } animations.clear(); XMLElement *animations = xml->FirstChildElement("Animations"); if (animations) { XMLElement *animation = animations->FirstChildElement("Animation"); while(animation) { Animation newAnimation; newAnimation.name = animation->Attribute("name"); newAnimation.resetPassOnEnd = animation->BoolAttribute("resetPassOnEnd"); stringToLower(newAnimation.name); XMLElement *key = animation->FirstChildElement("Key"); while (key) { SkeletalKeyframe newSkeletalKeyframe; if (key->Attribute("e")) { float time; SimpleIStringStream is(key->Attribute("e")); is >> time; int idx, x, y, rot, strip; newSkeletalKeyframe.t = time; if (key->Attribute("sound")) { newSkeletalKeyframe.sound = key->Attribute("sound"); } if (key->Attribute("lerp")) { newSkeletalKeyframe.lerpType = atoi(key->Attribute("lerp")); } while (is >> idx) { BoneKeyframe b; is >> x >> y >> rot >> strip; b.idx = idx; b.x = x; b.y = y; b.rot = rot; if (strip>0) { b.strip.resize(strip); for (size_t i = 0; i < b.strip.size(); i++) { is >> b.strip[i].x >> b.strip[i].y; } } if (key->Attribute("sz")) { SimpleIStringStream is2(key->Attribute("sz")); int midx; float bsx, bsy; while (is2 >> midx) { is2 >> bsx >> bsy; if (midx == idx) { b.doScale = true; b.sx = bsx; b.sy = bsy; break; } } } newSkeletalKeyframe.keyframes.push_back(b); } } if (key->Attribute("d")) { float time; SimpleIStringStream is(key->Attribute("d")); is >> time; int idx, x, y, rot; newSkeletalKeyframe.t = time; if (key->Attribute("sound")) { newSkeletalKeyframe.sound = key->Attribute("sound"); } while (is >> idx) { is >> x >> y >> rot; BoneKeyframe b; b.idx = idx; b.x = x; b.y = y; b.rot = rot; newSkeletalKeyframe.keyframes.push_back(b); } } if (key->Attribute("cmd")) { newSkeletalKeyframe.cmd = key->Attribute("cmd"); SimpleIStringStream is(newSkeletalKeyframe.cmd); int bidx; while (is >> bidx) { Bone *b = this->getBoneByIdx(bidx); if (b) { BoneCommand bcmd; if(!bcmd.parse(b, is)) break; newSkeletalKeyframe.commands.push_back(bcmd); } else { std::ostringstream os; os << "SkeletalSprite::loadSkeletal: File " << fn << " anim " << newAnimation.name << " specifies non-existing bone idx " << bidx; errorLog(os.str()); } } } // generate empty bone keys for (size_t i = 0; i < this->bones.size(); i++) { if (newSkeletalKeyframe.getBoneKeyframe(this->bones[i]->boneIdx)) { } else { BoneKeyframe b; b.idx = this->bones[i]->boneIdx; newSkeletalKeyframe.keyframes.push_back(b); } } newAnimation.keyframes.push_back(newSkeletalKeyframe); key = key->NextSiblingElement("Key"); } animation = animation->NextSiblingElement("Animation"); this->animations.push_back(newAnimation); } } } Animation *SkeletalSprite::getCurrentAnimation(size_t layer) { return layer < animLayers.size() ? animLayers[layer].getCurrentAnimation() : NULL; } void SkeletalSprite::setTime(float time, size_t layer) { if(layer < animLayers.size()) animLayers[layer].timer = time; } void AnimationLayer::resetPass() { for (size_t i = 0; i < s->bones.size(); i++) { Bone *b = s->bones[i]; if (contains(b)) b->setRenderPass(b->originalRenderPass); } } bool AnimationLayer::contains(const Bone *b) const { const int idx = b->boneIdx; if (!ignoreBones.empty()) { for (size_t j = 0; j < ignoreBones.size(); j++) if (idx == ignoreBones[j]) return false; } else if (!includeBones.empty()) { for (size_t j = 0; j < includeBones.size(); j++) if (idx == includeBones[j]) return true; return false; } return true; } void AnimationLayer::updateBones() { if (!animating && !(&s->animLayers[0] == this) && fallThru == 0) return; SkeletalKeyframe *key1 = getCurrentAnimation()->getPrevKeyframe(timer); SkeletalKeyframe *key2 = getCurrentAnimation()->getNextKeyframe(timer); if (!key1 || !key2) return; float t1 = key1->t; float t2 = key2->t; float diff = t2-t1; float dt; if (diff != 0) dt = (timer - t1)/(t2-t1); else dt = 0; if (lastNewKey != key2) { if (!key2->sound.empty()) { core->sound->playSfx(key2->sound); } if (!key2->commands.empty()) { for (size_t i = 0; i < key2->commands.size(); i++) { key2->commands[i].run(); } } if (s->animKeyNotify) { s->animKeyNotify->onAnimationKeyPassed(getCurrentAnimation()->getSkeletalKeyframeIndex(lastNewKey)); } } lastNewKey = key2; for (size_t i = 0; i < s->bones.size(); i++) { Bone *b = s->bones[i]; if (b->segmentChain == 1) { b->updateSegments(); } if (b->segmentChain < 2) { if (b->animated != Bone::ANIM_NONE && contains(b)) { int idx = b->boneIdx; BoneKeyframe *bkey1 = key1->getBoneKeyframe(idx); BoneKeyframe *bkey2 = key2->getBoneKeyframe(idx); if (bkey1 && bkey2) { if (!animating && fallThru > 0) { //HACK: TODO: fix this up nice like below Vector p = Vector((bkey2->x-bkey1->x)*dt+bkey1->x, (bkey2->y-bkey1->y)*dt+bkey1->y); float rot = (bkey2->rot - bkey1->rot)*dt + bkey1->rot; p = Vector((p.x-b->position.x)*fallThru+b->position.x, (p.y-b->position.y)*fallThru+b->position.y); rot = (rot-b->rotation.z)*fallThru + b->rotation.z; if (b->animated==Bone::ANIM_ALL || b->animated==Bone::ANIM_POS) b->position = p; if (b->animated==Bone::ANIM_ALL || b->animated==Bone::ANIM_ROT) b->rotation.z = rot; } else { int lerpType = key2->lerpType; //k(0)×(2u3-3u2+1) + k(1)×(3u2-2u3) if (b->animated==Bone::ANIM_ALL || b->animated==Bone::ANIM_POS) { b->position = Vector(lerp(bkey1->x, bkey2->x, dt, lerpType), lerp(bkey1->y, bkey2->y, dt, lerpType)); } if (b->animated==Bone::ANIM_ALL || b->animated==Bone::ANIM_ROT) { b->rotation.z = lerp(bkey1->rot, bkey2->rot, dt, lerpType); } if (b->animated==Bone::ANIM_ALL && (bkey1->doScale || bkey2->doScale)) { b->scale.x = lerp(bkey1->sx, bkey2->sx, dt, lerpType); b->scale.y = lerp(bkey1->sy, bkey2->sy, dt, lerpType); } if (b->animated==Bone::ANIM_ALL && !b->changeStrip.empty()) { if (bkey2->strip.size() < b->changeStrip.size()) bkey2->strip.resize(b->changeStrip.size()); if (bkey1->strip.size() < b->changeStrip.size()) bkey1->strip.resize(b->changeStrip.size()); for (size_t i = 0; i < b->changeStrip.size(); i++) { b->changeStrip[i] = Vector(lerp(bkey1->strip[i].x, bkey2->strip[i].x, dt, lerpType), lerp(bkey1->strip[i].y, bkey2->strip[i].y, dt, lerpType)); } b->setGridPoints(b->stripVert, b->changeStrip); } } } } } } } void SkeletalSprite::setFreeze(bool f) { frozen = f; } void SkeletalSprite::updateBones() { if (!frozen) { for (size_t i = 0; i < animLayers.size(); i++) { animLayers[i].updateBones(); } } } bool SkeletalSprite::isAnimating(int layer) { return animLayers[layer].animating; } void SkeletalSprite::setTimeMultiplier(float t, int layer) { animLayers[layer].timeMultiplier = t; } Bone* SkeletalSprite::getSelectedBone(bool mouseBased) { if (!loaded) return 0; if (mouseBased) { float closestDist = HUGE_VALF; Bone *b = 0; Vector p = core->mouse.position; for (size_t i = 0; i < bones.size(); i++) { if (bones[i]->renderQuad || core->getShiftState()) { bones[i]->color = Vector(1,1,1); if (bones[i]->selectable && bones[i]->renderQuad && bones[i]->isCoordinateInsideWorld(p)) { float dist = (bones[i]->getWorldPosition() - p).getSquaredLength2D(); if (dist <= closestDist) { closestDist = dist; b = bones[i]; selectedBone = i; } } } } if (b) { b->color = Vector(1,0,0); } return b; } // else if (!bones.empty() && selectedBone < bones.size()) return bones[selectedBone]; return 0; } void SkeletalSprite::updateSelectedBoneColor() { for (size_t i = 0; i < bones.size(); i++) { bones[i]->color = Vector(1,1,1); } Bone *b = bones[selectedBone]; if (b) b->color = Vector(0.5,0.5,1); } void SkeletalSprite::setSelectedBone(int b) { selectedBone = b; updateSelectedBoneColor(); } void SkeletalSprite::selectPrevBone() { const size_t oldsel = selectedBone; do { selectedBone++; if(selectedBone == oldsel) break; if (selectedBone >= bones.size()) selectedBone = 0; } while (!bones[selectedBone]->selectable); updateSelectedBoneColor(); } void SkeletalSprite::selectNextBone() { const size_t oldsel = selectedBone; do { selectedBone--; if(selectedBone == oldsel) break; if (selectedBone >= bones.size()) selectedBone = bones.size()-1; } while (!bones[selectedBone]->selectable); updateSelectedBoneColor(); }