1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-12-26 14:45:48 +00:00
Aquaria/BBGE/SkeletalSprite.cpp

1944 lines
40 KiB
C++
Raw Normal View History

/*
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"
2011-11-20 14:44:17 +00:00
#include "../ExternalLibs/tinyxml.h"
#include "Core.h"
#include "Particles.h"
#include "MathFunctions.h"
#include "SimpleIStringStream.h"
std::string SkeletalSprite::animationPath = "data/animations/";
std::string SkeletalSprite::skinPath = "skins/";
std::string SkeletalSprite::secondaryAnimationPath = "";
static std::map<std::string, TiXmlDocument> skelCache;
static TiXmlDocument& _retrieveSkeletalXML(const std::string& name)
{
TiXmlDocument& doc = skelCache[name];
if (!doc.RootElement())
doc.LoadFile(name);
return doc;
}
void SkeletalSprite::clearCache()
{
skelCache.clear();
}
void SkeletalKeyframe::copyAllButTime(SkeletalKeyframe *copy)
{
if (!copy) return;
float t = this->t;
(*this) = (*copy);
this->t = t;
}
Bone::Bone() : Quad()
{
addType(SCO_BONE);
fileRenderQuad = true;
skeleton = 0;
generateCollisionMask = true;
animated = ANIM_ALL;
originalScale = Vector(1,1);
boneIdx = pidx = -1;
rbp = 0;
segmentChain = 0;
minDist = maxDist = 128;
reverse = false;
}
/*
void Bone::createStrip(bool vert, int num)
{
Quad::createStrip(vert, num);
changeStrip.resize(num);
}
*/
void Bone::destroy()
{
Quad::destroy();
for (int i = 0; i < segments.size(); i++)
{
segments[i]->setLife(1.0);
segments[i]->setDecayRate(10);
segments[i]->alpha = 0;
}
segments.clear();
}
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);
setGridPoints(vert, strip);
}
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)
{
//float t = 0.1;
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)
{
/*
std::ostringstream os;
os << "setting animated: " << b;
debugLog(os.str());
*/
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)
{
/*
int maxDist, minDist;
maxDist = minDist = 128;
*/
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)) // 6.3
{
b->position += diff*0.05f;
MathFunctions::calculateAngleBetweenVectorsInDegrees(Vector(0,0,0), diff, angle);
//b->rotation.interpolateTo(Vector(0,0,angle),0.2);
}
if (angle != -1)
{
/*
std::ostringstream os;
os << "rotz: " << b->rotation.z << " angle: " << angle;
debugLog(os.str());
*/
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.2);
}
/*
else
{
float angle;
MathFunctions::calculateAngleBetweenVectorsInDegrees(Vector(0,0,0), diff, angle);
b->rotation.interpolateTo(Vector(0,0,angle),0);
}
*/
}
void Bone::updateSegments()
{
if (segmentChain>0 && !segments.empty())
{
//bool reverse = true;
/*
std::vector<Bone*> segments;
Bone *child = (Bone*)(this->children.front());
while (child)
{
segments.push_back(child);
if (child->children.empty())
child = 0;
else
child = (Bone*)(child->children.front());
}
*/
if (!reverse)
{
for (int 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 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;
/*
Emitter *e = b->emitters[slot];
if (e)
{
e->start();
}
*/
}
else if (type=="AC_PRT_STOP")
{
command = AC_PRT_STOP;
is >> slot;
/*
Emitter *e = b->emitters[slot];
if (e)
{
e->stop();
}
*/
}
else if (type=="AC_SEGS_START")
command = AC_SEGS_START;
else if (type=="AC_SEGS_STOP")
command = AC_SEGS_STOP;
}
void BoneCommand::run()
{
//debugLog("running CMD");
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->emitters[slot];
if (e)
{
e->load(file);
}
}
break;
case AC_PRT_START:
{
ParticleEffect *e = b->emitters[slot];
if (e)
e->start();
}
break;
case AC_PRT_STOP:
{
ParticleEffect *e = b->emitters[slot];
if (e)
e->stop();
}
break;
}
}
AnimationLayer::AnimationLayer()
{
lastNewKey = 0;
fallThru= 0;
//index = -1;
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 (int 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;
//currentKeyframe = 0;
currentAnimation = idx;
timer = 0;
animating = true;
this->loop = loop;
animationLength = getCurrentAnimation()->getAnimationLength();
//doNextKeyframe();
}
void AnimationLayer::enqueueAnimation(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 < 0 || currentAnimation >= s->animations.size())
{
std::ostringstream os;
os << "skel: " << s->filenameLoaded << " currentAnimation: " << currentAnimation << " is out of range\n error in anim file?";
errorLog(os.str());
exit(-1);
return 0;
}
return &s->animations[currentAnimation];
}
bool AnimationLayer::createTransitionAnimation(std::string anim, float time)
{
//Animation *a = getCurrentAnimation();
Animation *to = s->getAnimation(anim);
if (!to) return false;
blendAnimation.keyframes.clear();
SkeletalKeyframe k;
k.t = 0;
for (int 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.strip = s->bones[i]->strip;
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()
{
animating = false;
if (!enqueuedAnimation.empty())
{
animate(enqueuedAnimation, enqueuedAnimationLoop);
enqueuedAnimation = "";
}
}
bool AnimationLayer::isAnimating()
{
return animating;
}
float AnimationLayer::getAnimationLength()
{
return animationLength;
}
int Animation::getNumKeyframes()
{
return keyframes.size();
}
SkeletalKeyframe *Animation::getKeyframe(int key)
{
if (key < 0 || 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()
{
/*
std::vector<SkeletalKeyframe> copy = this->keyframes;
keyframes.clear();
*/
for (int i = 0; i < keyframes.size(); i++)
{
for (int 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(int key, float toffset)
{
std::vector<SkeletalKeyframe> copy = this->keyframes;
keyframes.clear();
int 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(int key)
{
std::vector<SkeletalKeyframe> copy = this->keyframes;
keyframes.clear();
int 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]);
}
int Animation::getSkeletalKeyframeIndex(SkeletalKeyframe *skey)
{
for (int i = 0; i < keyframes.size(); i++)
{
if (&keyframes[i] == skey)
return i;
}
return -1;
}
BoneKeyframe *SkeletalKeyframe::getBoneKeyframe(int idx)
{
for (int i = 0; i < keyframes.size(); i++)
{
if (keyframes[i].idx == idx)
{
return &keyframes[i];
}
}
return 0;
}
SkeletalKeyframe *Animation::getPrevKeyframe(float t)
{
int kf = -1;
for (int i = keyframes.size()-1; i >= 0; i--)
{
if (t >= keyframes[i].t)
{
kf = i;
break;
}
}
if (kf == -1)
return 0;
if (kf >= keyframes.size())
kf = keyframes.size()-1;
if (kf < 0)
kf = 0;
return &keyframes[kf];
}
SkeletalKeyframe *Animation::getNextKeyframe(float t)
{
int kf = -1;
for (int i = 0; i < keyframes.size(); i++)
{
if (t <= keyframes[i].t)
{
kf = i;
break;
}
}
// kf++;
if (kf == -1)
return 0;
if (kf >= keyframes.size())
kf = keyframes.size()-1;
if (kf < 0)
kf = 0;
return &keyframes[kf];
}
SkeletalSprite::SkeletalSprite() : RenderObject()
{
frozen = false;
animKeyNotify = 0;
loaded = false;
animLayers.resize(10);
for (int i = 0; i < animLayers.size(); i++)
animLayers[i].setSkeletalSprite(this);
selectedBone = -1;
}
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(int l)
{
if (l >= 0 && 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);
int 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 (int i = 0; i < b->collisionMask.size(); i++)
{
b->transformedCollisionMask[i] = b->getWorldCollidePosition(b->collisionMask[i]);
}
}
}
/*
for (int i = 0; i < bones.size(); i++)
{
bones[i]->update(dt);
}
*/
for (i = 0; i < animLayers.size(); i++)
{
animLayers[i].update(dt);
}
//updateBones();
}
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();
}
}
}
void SkeletalSprite::saveSkeletal(const std::string &fn)
{
std::string file, filename=fn;
stringToLower(filename);
if (!secondaryAnimationPath.empty())
file = secondaryAnimationPath + filename + ".xml";
else
file = animationPath + filename + ".xml";
int i = 0;
TiXmlDocument& xml = _retrieveSkeletalXML(file);
xml.Clear();
TiXmlElement animationLayers("AnimationLayers");
for (i = 0; i < animLayers.size(); i++)
{
TiXmlElement animationLayer("AnimationLayer");
if (animLayers[i].ignoreBones.size() > 0)
{
std::ostringstream os;
for (int j = 0; j < animLayers[i].ignoreBones.size(); j++)
{
os << animLayers[i].ignoreBones[j] << " ";
}
animationLayer.SetAttribute("ignore", os.str());
}
if (animLayers[i].includeBones.size() > 0)
{
std::ostringstream os;
for (int j = 0; j < animLayers[i].includeBones.size(); j++)
{
os << animLayers[i].includeBones[j] << " ";
}
animationLayer.SetAttribute("include", os.str());
}
if (!animLayers[i].name.empty())
{
animationLayer.SetAttribute("name", animLayers[i].name);
}
animationLayers.InsertEndChild(animationLayer);
}
xml.InsertEndChild(animationLayers);
TiXmlElement bones("Bones");
for (i = 0; i < this->bones.size(); i++)
{
TiXmlElement bone("Bone");
bone.SetAttribute("idx", this->bones[i]->boneIdx);
bone.SetAttribute("gfx", this->bones[i]->gfx);
bone.SetAttribute("pidx", this->bones[i]->pidx);
bone.SetAttribute("name", this->bones[i]->name);
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]->renderQuad)
{
bone.SetAttribute("rq", this->bones[i]->fileRenderQuad);
}
if (!this->bones[i]->collisionRects.empty())
{
std::ostringstream os;
os << this->bones[i]->collisionRects.size() << " ";
for (int j = 0; j < this->bones[i]->collisionRects.size(); j++)
{
RectShape *r = &this->bones[i]->collisionRects[j];
int x, y, w, h;
r->getCWH(&x, &y, &w, &h);
os << x << " " << y << " " << w << " " << h << " ";
}
bone.SetAttribute("crects", os.str());
}
std::ostringstream os;
os << this->bones[i]->collidePosition.x << " " << this->bones[i]->collidePosition.y;
bone.SetAttribute("cp", os.str());
if (this->bones[i]->rbp)
bone.SetAttribute("rbp", this->bones[i]->rbp);
if (this->bones[i]->getRenderPass())
bone.SetAttribute("pass", this->bones[i]->getRenderPass());
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);
if (!this->bones[i]->changeStrip.empty())
{
std::ostringstream os;
os << this->bones[i]->stripVert << " " << this->bones[i]->changeStrip.size();
bone.SetAttribute("strip", os.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());
}
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());
}
/*
if (this->bones[i]->color.x != 1 || this->bones[i]->color.y != 1 || this->bones[i]->color.z != 1)
{
std::ostringstream os;
os << this->bones[i]->color.x << " " << this->bones[i]->color.y << " " << this->bones[i]->color.z;
bone.SetAttribute("color", os.str());
}
*/
for (Children::iterator j = this->bones[i]->children.begin(); j != this->bones[i]->children.end(); j++)
{
Bone *b = dynamic_cast<Bone*>(*j);
Quad *q = dynamic_cast<Quad*>(*j);
Particle *p = dynamic_cast<Particle*>(*j);
if (q && !b && !p)
{
TiXmlElement frame("Frame");
frame.SetAttribute("gfx", q->texture->name);
if (q->getRenderPass() != 0)
{
frame.SetAttribute("pass", q->getRenderPass());
}
bone.InsertEndChild(frame);
}
}
bones.InsertEndChild(bone);
}
xml.InsertEndChild(bones);
TiXmlElement animations("Animations");
for (i = 0; i < this->animations.size(); i++)
{
Animation *a = &this->animations[i];
TiXmlElement animation("Animation");
animation.SetAttribute("name", a->name);
for (int j = 0; j < a->keyframes.size(); j++)
{
TiXmlElement key("Key");
if (!a->keyframes[j].sound.empty())
key.SetAttribute("sound", a->keyframes[j].sound);
if (!a->keyframes[j].cmd.empty())
{
key.SetAttribute("cmd", a->keyframes[j].cmd);
}
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 (int 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 (int 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());
animation.InsertEndChild(key);
}
animations.InsertEndChild(animation);
}
xml.InsertEndChild(animations);
xml.SaveFile(file);
}
int SkeletalSprite::getBoneIdx(Bone *b)
{
for (int i = 0; i < bones.size(); i++)
{
if (bones[i] == b)
return i;
}
return -1;
}
void SkeletalSprite::toggleBone(int idx, int v)
{
if (idx >= 0 && idx < bones.size())
{
bones[idx]->alpha.x = v;
}
}
Bone *SkeletalSprite::getBoneByName(const std::string &name)
{
for (int 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(int idx)
{
for (int 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, int rbp, std::string name, int cr, bool fh, bool fv, const Vector &cp)
{
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->collidePosition = cp;
b->name = name;
//core->generateCollisionMask(b);
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 < 0)
animLayers[0].currentAnimation = animations.size()-1;
}
void SkeletalSprite::deleteBones()
{
bones.clear();
if (!children.empty())
{
// remove child had better be recursive
Bone *b = (Bone*)children.front();
//removeChild(b);
b->destroy();
delete b;
deleteBones();
}
children.clear();
bones.clear();
}
Animation *SkeletalSprite::getAnimation(std::string anim)
{
for (int 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 = core->adjustFilenameCase(file);
if (!exists(file,1))
{
errorLog("Could not load skin[" + file + "]");
return;
}
TiXmlDocument& d = _retrieveSkeletalXML(file);
TiXmlElement *bonesXml = d.FirstChildElement("Bones");
if (bonesXml)
{
TiXmlElement *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();
}
/*
if (boneXml->Attribute("a"))
{
float alpha = 0;
boneXml->Attribute("a", &alpha);
b->alpha = alpha;
}
*/
/*
// this is for SKINS
if (boneXml->Attribute("sz"))
{
float v1, v2;
SimpleIStringStream is(boneXml->Attribute("sz"));
is >> v1 >> v2;
b->scale = Vector(v1,v2);
b->originalScale = b->scale;
}
*/
}
else
{
std::ostringstream os;
os << "SkinLoad: Could not find idx[" << idx << "]";
debugLog(os.str());
}
boneXml = boneXml->NextSiblingElement("Bone");
}
}
}
void SkeletalSprite::stopAnimation(int layer)
{
animLayers[layer].stopAnimation();
}
void SkeletalSprite::stopAllAnimations()
{
for (int i = 0; i < animLayers.size(); i++)
{
animLayers[i].stopAnimation();
}
}
void SkeletalSprite::playCurrentAnimation(int loop, int layer)
{
animLayers[layer].playCurrentAnimation(loop);
}
void SkeletalSprite::loadSkeletal(const std::string &fn)
{
filenameLoaded = "";
loaded = false;
stopAnimation();
for (int i = 0; i < animLayers.size(); i++)
{
animLayers[i].currentAnimation = 0;
}
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 = "";
return;
}
file = core->adjustFilenameCase(file);
loaded = true;
TiXmlDocument& xml = _retrieveSkeletalXML(file);
xml.LoadFile(file.c_str());
TiXmlElement *bones = xml.FirstChildElement("Bones");
if (bones)
{
if (bones->Attribute("scale"))
{
SimpleIStringStream is(bones->Attribute("scale"));
is >> scale.x >> scale.y;
}
TiXmlElement *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;
Vector cp;
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"));
if (bone->Attribute("cp"))
{
SimpleIStringStream is(bone->Attribute("cp"));
is >> cp.x >> cp.y;
}
std::string gfx = bone->Attribute("gfx");
Bone *newb = initBone(idx, gfx, pidx, rbp, name, cr, fh, fv, cp);
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("crects"))
{
SimpleIStringStream is(bone->Attribute("crects"));
int num = 0;
is >> num;
for (int i = 0; i < num; i++)
{
RectShape r;
int x, y, w, h;
is >> x >> y >> w >> h;
r.setCWH(x, y, w, h);
newb->collisionRects.push_back(r);
}
}
if (bone->Attribute("prt"))
{
newb->prt = bone->Attribute("prt");
SimpleIStringStream is(newb->prt);
int slot;
while (is >> slot)
{
std::string pfile;
is >> pfile;
// add particle system + load
newb->emitters[slot] = new ParticleEffect;
ParticleEffect *e = newb->emitters[slot];
newb->addChild(e, PM_POINTER);
e->load(pfile);
// hack for now:
//e->start();
}
}
TiXmlElement *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"))
{
newb->setRenderPass(atoi(bone->Attribute("pass")));
}
if (bone->Attribute("gc"))
{
newb->generateCollisionMask = atoi(bone->Attribute("gc"));
}
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->blendType = blendType = BLEND_ADD;
}
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;
}
bone = bone->NextSiblingElement("Bone");
}
// attach bones
for (int 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();
TiXmlElement *animationLayers = xml.FirstChildElement("AnimationLayers");
if (animationLayers)
{
TiXmlElement *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();
TiXmlElement *animations = xml.FirstChildElement("Animations");
if (animations)
{
TiXmlElement *animation = animations->FirstChildElement("Animation");
while(animation)
{
Animation newAnimation;
newAnimation.name = animation->Attribute("name");
stringToLower(newAnimation.name);
TiXmlElement *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 (int i = 0; i < b.strip.size(); i++)
{
is >> b.strip[i].x >> b.strip[i].y;
//b.strip[i].y *= 10;
}
}
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;
bcmd.parse(b, is);
newSkeletalKeyframe.commands.push_back(bcmd);
}
}
}
// generate empty bone keys
for (int 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(int layer)
{
return animLayers[layer].getCurrentAnimation();
}
void SkeletalSprite::setTime(float time, int layer)
{
animLayers[layer].timer = time;
}
// hack:
// calculate based on frames
const int lerpAvg = 3;
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;
/*
if (key1 == key2)
stopAnimation();
*/
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 (int i = 0; i < key2->commands.size(); i++)
{
key2->commands[i].run();
}
}
if (s->animKeyNotify)
{
s->animKeyNotify->onAnimationKeyPassed(getCurrentAnimation()->getSkeletalKeyframeIndex(lastNewKey));
}
}
lastNewKey = key2;
bool c = 0;
for (int i = 0; i < s->bones.size(); i++)
{
int idx = s->bones[i]->boneIdx;
Bone *b = s->bones[i];
if (b->segmentChain == 1)
{
b->updateSegments();
}
if (b->segmentChain < 2)
{
c=0;
if (!ignoreBones.empty())
{
for (int j = 0; j < ignoreBones.size(); j++)
{
if (idx == ignoreBones[j])
{ c=1; break; }
}
}
else if (!includeBones.empty())
{
c = 1;
for (int j = 0; j < includeBones.size(); j++)
{
if (idx == includeBones[j])
{ c=0; break; }
}
}
if (b->animated==Bone::ANIM_NONE)
{
c = 1;
}
if (!c)
{
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 (int 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 (int i = 0; i < animLayers.size(); i++)
{
animLayers[i].updateBones();
}
}
/*
for (int i = animLayers.size()-1; i >= 0; i--)
{
if (animLayers[i].animating)
{
animLayers[i].updateBones();
return;
}
}
*/
}
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 (int i = 0; i < bones.size(); i++)
{
if (bones[i]->renderQuad || core->getShiftState())
{
bones[i]->color = Vector(1,1,1);
if (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 >= 0 && selectedBone < bones.size())
return bones[selectedBone];
return 0;
}
void SkeletalSprite::updateSelectedBoneColor()
{
for (int 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()
{
selectedBone++;
if (selectedBone >= bones.size())
selectedBone = 0;
updateSelectedBoneColor();
}
void SkeletalSprite::selectNextBone()
{
selectedBone--;
if (selectedBone < 0)
selectedBone = bones.size()-1;
updateSelectedBoneColor();
}