Skip to content

Commit

Permalink
Merge branch 'gl2-models'
Browse files Browse the repository at this point in the history
  • Loading branch information
skyjake committed Aug 4, 2015
2 parents 563099c + 1b8591f commit d801932
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 76 deletions.
4 changes: 3 additions & 1 deletion doomsday/apps/client/include/render/mobjanimator.h
Expand Up @@ -38,8 +38,10 @@ class MobjAnimator : public de::ModelDrawable::Animator

de::ddouble currentTime(int index) const;

struct Parameters;

private:
ModelRenderer::StateAnims const *_stateAnims;
DENG2_PRIVATE(d)
};

#endif // DENG_CLIENT_RENDER_MOBJANIMATOR_H
201 changes: 174 additions & 27 deletions doomsday/apps/client/src/render/mobjanimator.cpp
Expand Up @@ -21,46 +21,153 @@
#include "clientapp.h"
#include "dd_loop.h"

#include <de/ScriptedInfo>

using namespace de;

static String const DEF_PROBABILITY("prob");
static String const DEF_ROOT_NODE ("node");
static String const DEF_LOOPING ("looping");
static String const DEF_PRIORITY ("priority");

static int const ANIM_DEFAULT_PRIORITY = 1;

DENG2_PIMPL(MobjAnimator)
{
/**
* Specialized animation sequence state for a running animation.
*/
struct Animation : public ModelDrawable::Animator::Animation
{
enum LoopMode {
NotLooping = 0,
Looping = 1
};

LoopMode looping = NotLooping;
int priority = ANIM_DEFAULT_PRIORITY;

Animation() {}

Animation(int animationId, String const &rootNode, LoopMode looping, int priority)
: looping(looping)
, priority(priority)
{
animId = animationId;
node = rootNode;
}

void apply(Animation const &other)
{
looping = other.looping;
priority = other.priority;
}

bool isRunning() const
{
if(looping == Animation::Looping)
{
// Looping animations are always running.
return true;
}
return !isAtEnd();
}

static Animation *make() { return new Animation; }
};

ModelRenderer::StateAnims const *stateAnims;
QHash<String, Animation> pendingAnimForNode;
String currentStateName;

Instance(Public *i, DotPath const &id)
: Base(i)
, stateAnims(ClientApp::renderSystem().modelRenderer().animations(id))
{}

int animationId(String const &name) const
{
return ModelRenderer::identifierFromText(name, [this] (String const &name) {
return self.model().animationIdForName(name);
});
}

Animation &start(Animation const &spec)
{
Animation &anim = self.start(spec.animId, spec.node).as<Animation>();
anim.apply(spec);
return anim;
}
};

MobjAnimator::MobjAnimator(DotPath const &id, ModelDrawable const &model)
: ModelDrawable::Animator(model)
, _stateAnims(ClientApp::renderSystem().modelRenderer().animations(id))
: ModelDrawable::Animator(model, Instance::Animation::make)
, d(new Instance(this, id))
{}

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

auto found = _stateAnims->constFind(stateName);
if(found == _stateAnims->constEnd()) return;
auto found = d->stateAnims->constFind(stateName);
if(found == d->stateAnims->constEnd()) return;

//LOG_WIP("triggerByState: ") << stateName;
d->currentStateName = stateName;

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 = ModelRenderer::identifierFromText(seq.name, [this] (String const &name) {
return model().animationIdForName(name);
});

// Do not restart running sequences.
// TODO: Only restart if the current state is not the expected one.
if(isRunning(animId, node)) continue;

start(animId, node);

//LOG_WIP(" Starting anim: " _E(b)) << seq.name;
using Animation = Instance::Animation;

try
{
// 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 = d->animationId(seq.name);

// Do not restart running sequences.
// TODO: Only restart if the current state is not the expected one.
if(isRunning(animId, node)) continue;

int const priority = seq.def->geti(DEF_PRIORITY, ANIM_DEFAULT_PRIORITY);

// Parameters for the new sequence.
Animation anim(animId, node,
ScriptedInfo::isTrue(*seq.def, DEF_LOOPING)? Animation::Looping :
Animation::NotLooping,
priority);

// Do not override higher-priority animations.
if(auto *existing = find(node)->maybeAs<Animation>())
{
if(priority < existing->priority)
{
// This will be started once the higher-priority animation
// has finished.
d->pendingAnimForNode[node] = anim;
continue;
}
}

d->pendingAnimForNode.remove(node);

// Start a new sequence.
d->start(anim);
}
catch(ModelDrawable::Animator::InvalidError const &er)
{
LOGDEV_GL_WARNING("Failed to start animation \"%s\": %s")
<< seq.name << er.asText();
continue;
}

LOG_WIP("Starting anim: " _E(b)) << seq.name;
break;
}
}
Expand All @@ -69,16 +176,58 @@ void MobjAnimator::advanceTime(TimeDelta const &elapsed)
{
ModelDrawable::Animator::advanceTime(elapsed);

bool retrigger = false;

for(int i = 0; i < count(); ++i)
{
Animation &anim = at(i);
using Animation = Instance::Animation;

auto &anim = at(i).as<Animation>();
ddouble factor = 1.0;
// TODO: Determine actual time factor.

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

//qDebug() << "advancing" << anim.animId << "time" << anim.time;
if(anim.looping == Animation::NotLooping)
{
// Clamp at the end.
anim.time = min(anim.time, anim.duration);
}

if(anim.looping == Animation::Looping)
{
// When a looping animation has completed a loop, it may still trigger
// a variant.
if(anim.isAtEnd())
{
retrigger = true;
anim.time -= anim.duration; // Trigger only once per loop.
}
}

// Stop finished animations.
if(!anim.isRunning())
{
String const node = anim.node;

stop(i--); // `anim` gets deleted

// Start a previously triggered pending animation.
auto &pending = d->pendingAnimForNode;
if(pending.contains(node))
{
LOG_WIP("Starting pending anim %i") << pending[node].animId;
d->start(pending[node]);
pending.remove(node);
}
}
}

if(retrigger && !d->currentStateName.isEmpty())
{
LOG_WIP("retrigger: " _E(C)) << d->currentStateName;
triggerByState(d->currentStateName);
}
}

Expand All @@ -87,6 +236,4 @@ ddouble MobjAnimator::currentTime(int index) const
// Mobjs think on sharp ticks only, however we need to ensure time advances on
// every frame for smooth animation.
return ModelDrawable::Animator::currentTime(index);// + frameTimePos;

/// @todo Should prevent time from passing the end of the sequence?
}
7 changes: 2 additions & 5 deletions doomsday/apps/client/src/render/modelrenderer.cpp
Expand Up @@ -203,15 +203,12 @@ DENG2_PIMPL(ModelRenderer)
{
up = Vector3f(asset.geta(DEF_UP_VECTOR));
}
bool mirror = (asset.has(DEF_MIRROR)? ScriptedInfo::isTrue(asset.get(DEF_MIRROR)) : false);
bool mirror = ScriptedInfo::isTrue(asset, DEF_MIRROR);
aux->cull = mirror? gl::Back : gl::Front;
// Assimp's coordinate system uses different handedness than Doomsday,
// so mirroring is needed.
aux->transformation = Matrix4f::unnormalizedFrame(front, up, !mirror);
if(asset.has(DEF_AUTOSCALE))
{
aux->autoscaleToThingHeight = !ScriptedInfo::isFalse(asset.get(DEF_AUTOSCALE));
}
aux->autoscaleToThingHeight = !ScriptedInfo::isFalse(asset, DEF_AUTOSCALE, false);

// Custom texture maps.
if(asset.has(DEF_MATERIAL))
Expand Down
8 changes: 7 additions & 1 deletion doomsday/sdk/libcore/include/de/scriptsys/scriptedinfo.h
Expand Up @@ -13,7 +13,7 @@
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
* http://www.gnu.org/licenses</small>
*/

#ifndef LIBDENG2_SCRIPTEDINFO_H
Expand Down Expand Up @@ -197,8 +197,14 @@ class DENG2_PUBLIC ScriptedInfo
*/
static bool isFalse(Value const &value);

static bool isFalse(RecordAccessor const &rec, String const &name,
bool defaultValue = true /* assume false if missing */);

static bool isTrue(Value const &value);

static bool isTrue(RecordAccessor const &rec, String const &name,
bool defaultValue = false /* assume false if missing */);

public:
static Paths allBlocksOfType(String const &blockType, Record const &root);

Expand Down
24 changes: 21 additions & 3 deletions doomsday/sdk/libcore/src/scriptsys/scriptedinfo.cpp
Expand Up @@ -13,7 +13,7 @@
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
* http://www.gnu.org/licenses</small>
*/

#include "de/ScriptedInfo"
Expand Down Expand Up @@ -127,7 +127,7 @@ DENG2_PIMPL(ScriptedInfo)
String varName = variableName(block);
if(!varName.isEmpty())
{
Record &ns = process.globals();
Record &ns = process.globals();
String targetName = checkNamespaceForVariable(target);
if(!ns.has(targetName))
{
Expand Down Expand Up @@ -288,7 +288,7 @@ DENG2_PIMPL(ScriptedInfo)
else
varName = b->name().concatenateMember(varName);
}
}
}
return checkNamespaceForVariable(varName);
}

Expand Down Expand Up @@ -476,6 +476,24 @@ bool ScriptedInfo::isTrue(Value const &value) // static
return value.isTrue();
}

bool ScriptedInfo::isTrue(RecordAccessor const &rec, String const &name, bool defaultValue)
{
if(rec.has(name))
{
return isTrue(rec.get(name));
}
return defaultValue;
}

bool ScriptedInfo::isFalse(RecordAccessor const &rec, String const &name, bool defaultValue)
{
if(rec.has(name))
{
return isFalse(rec.get(name));
}
return defaultValue;
}

bool ScriptedInfo::isFalse(Value const &value) // static
{
if(TextValue const *textValue = value.maybeAs<TextValue>())
Expand Down

0 comments on commit d801932

Please sign in to comment.