Skip to content

Commit

Permalink
Client|Mobj|Refactor: Working on state-based animations
Browse files Browse the repository at this point in the history
ModelRenderer's animation sequences are now used by the mobj private
data to trigger animations. Animations are advanced as the mobj
thinks.

Also moved particle generator spawning into ClientMobjThinkerData.
  • Loading branch information
skyjake committed Aug 3, 2014
1 parent 94cc541 commit 7eda59d
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 25 deletions.
4 changes: 4 additions & 0 deletions doomsday/client/include/world/clientmobjthinkerdata.h
Expand Up @@ -81,6 +81,10 @@ class ClientMobjThinkerData : public MobjThinkerData
void think();
IData *duplicate() const;

void stateChanged(state_t const *previousState);

int stateIndex() const;

bool hasRemoteSync() const;

/**
Expand Down
7 changes: 7 additions & 0 deletions doomsday/client/net.dengine.client.pack/testmodel.pack/Info
Expand Up @@ -5,4 +5,11 @@ tags: dev

asset model.thing.possessed {
path = "boblampclean.md5mesh"

group animation {
sequence "#0" {
state = POSS_STND
timeline <0, 5, 10>
}
}
}
164 changes: 154 additions & 10 deletions doomsday/client/src/world/clientmobjthinkerdata.cpp
Expand Up @@ -18,14 +18,17 @@

#include "world/clientmobjthinkerdata.h"
#include "render/modelrenderer.h"
#include "def_main.h"
#include "world/generator.h"
#include "clientapp.h"
#include "render/modelrenderer.h"
//#include <de/ModelBank>
#include "dd_loop.h"
#include "def_main.h"
#include <QFlags>

using namespace de;

static String const DEF_PROBABILITY("prob");
static String const DEF_ROOT_NODE ("node");

namespace internal
{
enum Flag
Expand All @@ -40,9 +43,79 @@ using namespace ::internal;

DENG2_PIMPL(ClientMobjThinkerData)
{
/**
* Mobj-specific model animator.
*/
struct Animator : public ModelDrawable::Animator
{
ClientMobjThinkerData::Instance &d;
ModelRenderer::StateAnims const *stateAnims;

Animator(ClientMobjThinkerData::Instance *inst, ModelDrawable const &model)
: ModelDrawable::Animator(model)
, d(*inst)
, stateAnims(ClientApp::renderSystem().modelRenderer().animations(inst->modelId()))
{}

void triggerByState(String const &stateName)
{
// No animations can be triggered if none are available.
if(!stateAnims) return;

ModelRenderer::StateAnims::const_iterator found = stateAnims->constFind(stateName);
if(found == stateAnims->constEnd()) return;

foreach(ModelRenderer::AnimSequence const &seq, found.value())
{
// Test for the probability of this animation.
float chance = seq.def->getf(DEF_PROBABILITY, 1.f);
if(frand() > chance) continue;

// Start the animation on the specified node (defaults to root),
// unless it is already running.
String const node = seq.def->gets(DEF_ROOT_NODE, "");
int animId;
if(seq.name.startsWith('#'))
{
// Animation sequence specified by index.
animId = seq.name.mid(1).toInt();
}
else
{
animId = model().animationIdForName(seq.name);
}

// Do not restart running sequences.
if(isRunning(animId, node)) continue;

start(animId, node);

qDebug() << "starting" << seq.name;
}
}

/// Determines how fast each animation sequence is proceeding.
void advanceTime(TimeDelta const &elapsed)
{
ModelDrawable::Animator::advanceTime(elapsed);

for(int i = 0; i < count(); ++i)
{
Animation &anim = at(i);
ddouble factor = 1.0;
// TODO: Determine actual time factor.

// Advance the sequence.
anim.time += factor * elapsed;

qDebug() << "advancing" << anim.animId << "time" << anim.time;
}
}
};

Flags flags;
QScopedPointer<RemoteSync> sync;
QScopedPointer<ModelDrawable::Animator> animator;
QScopedPointer<Animator> animator;

Instance(Public *i) : Base(i)
{}
Expand All @@ -55,26 +128,36 @@ DENG2_PIMPL(ClientMobjThinkerData)
}
}

String thingType() const
String thingName() const
{
return Def_GetMobjName(self.mobj()->type);
}

String stateName() const
{
return Def_GetStateName(self.mobj()->state);
}

String modelId() const
{
return String("model.thing.%1").arg(thingType().toLower());
return String("model.thing.%1").arg(thingName().toLower());
}

static ModelBank &modelBank()
{
return ClientApp::renderSystem().modelRenderer().bank();
}

void deinitModel()
{
animator.reset();
}

/**
* Initializes the client-specific mobj data. This is performed once, during the
* first time the object thinks.
*/
void initIfNeeded()
void initOnce()
{
// Initialization is only done once.
if(flags & Initialized) return;
Expand All @@ -85,10 +168,53 @@ DENG2_PIMPL(ClientMobjThinkerData)
{
// Prepare the animation state of the model.
ModelDrawable const &model = modelBank().model(modelId());
animator.reset(new ModelDrawable::Animator(model));
animator.reset(new Animator(this, model));
}
}

/**
* Checks if there are any animations defined to start in the current state. All
* animation sequences associated with the state are checked. A sequence may specify
* a less than 1.0 probability for starting. The sequence may be identified either by
* name ("walk") or index (for example, "#3").
*/
void triggerStateAnimations()
{
if(animator.isNull()) return;

// Set up the initial animation.
animator->triggerByState(stateName());
}

/**
* Checks the motion of the object and triggers suitable animations (for standing,
* walking, or running). These animations are defined separately from the state
* based animations.
*/
void triggerMovementAnimations()
{
if(animator.isNull()) return;


}

void advanceAnimations(TimeDelta const &delta)
{
if(animator.isNull()) return;

animator->advanceTime(delta);
}

void triggerParticleGenerators(bool justSpawned)
{
// Check for a ptcgen trigger.
for(ded_ptcgen_t *pg = runtimeDefs.stateInfo[self.stateIndex()].ptcGens;
pg; pg = pg->stateNext)
{
if(!(pg->flags & Generator::SpawnOnly) || justSpawned)
{
// We are allowed to spawn the generator.
Mobj_SpawnParticleGen(self.mobj(), pg);
}
}
}
};
Expand All @@ -104,14 +230,21 @@ ClientMobjThinkerData::ClientMobjThinkerData(ClientMobjThinkerData const &other)

void ClientMobjThinkerData::think()
{
d->initIfNeeded();
d->initOnce();
d->triggerMovementAnimations();
d->advanceAnimations(DD_CurrentTickDuration());
}

Thinker::IData *ClientMobjThinkerData::duplicate() const
{
return new ClientMobjThinkerData(*this);
}

int ClientMobjThinkerData::stateIndex() const
{
return runtimeDefs.states.indexOf(mobj()->state);
}

bool ClientMobjThinkerData::hasRemoteSync() const
{
return !d->sync.isNull();
Expand All @@ -130,3 +263,14 @@ ModelDrawable::Animator *ClientMobjThinkerData::animator()
{
return d->animator.data();
}

void ClientMobjThinkerData::stateChanged(state_t const *previousState)
{
MobjThinkerData::stateChanged(previousState);

bool const justSpawned = !previousState;

d->initOnce();
d->triggerStateAnimations();
d->triggerParticleGenerators(justSpawned);
}
23 changes: 8 additions & 15 deletions doomsday/client/src/world/p_mobj.cpp
Expand Up @@ -50,6 +50,7 @@
#endif

#include <de/Error>
#include <doomsday/world/mobjthinkerdata.h>
#include <cmath>

using namespace de;
Expand Down Expand Up @@ -167,9 +168,7 @@ DENG_EXTERN_C void Mobj_SetState(mobj_t *mobj, int statenum)
{
if(!mobj) return;

#ifdef __CLIENT__
bool spawning = (mobj->state == 0);
#endif
state_t const *oldState = mobj->state;

DENG_ASSERT(statenum >= 0 && statenum < defs.states.size());

Expand All @@ -178,23 +177,17 @@ DENG_EXTERN_C void Mobj_SetState(mobj_t *mobj, int statenum)
mobj->sprite = mobj->state->sprite;
mobj->frame = mobj->state->frame;

#ifdef __CLIENT__
// Check for a ptcgen trigger.
for(ded_ptcgen_t *pg = runtimeDefs.stateInfo[statenum].ptcGens; pg; pg = pg->stateNext)
{
if(!(pg->flags & Generator::SpawnOnly) || spawning)
{
// We are allowed to spawn the generator.
Mobj_SpawnParticleGen(mobj, pg);
}
}
#endif

if(!(mobj->ddFlags & DDMF_REMOTE))
{
if(defs.states[statenum].execute)
Con_Execute(CMDS_SCRIPT, defs.states[statenum].execute, true, false);
}

// Notify private data about the changed state.
if(MobjThinkerData *data = THINKER_DATA_MAYBE(mobj->thinker, MobjThinkerData))
{
data->stateChanged(oldState);
}
}

Vector3d Mobj_Origin(mobj_t const &mobj)
Expand Down

0 comments on commit 7eda59d

Please sign in to comment.