Added shape-related lookup functions
This commit is contained in:
parent
db368b4311
commit
6d67f77f62
|
@ -1,17 +1,59 @@
|
||||||
#include "animationRules.h"
|
#include "animationRules.h"
|
||||||
#include <boost/algorithm/clamp.hpp>
|
#include <boost/algorithm/clamp.hpp>
|
||||||
#include "shapeShorthands.h"
|
#include "shapeShorthands.h"
|
||||||
|
#include "array.h"
|
||||||
|
|
||||||
using std::chrono::duration_cast;
|
using std::chrono::duration_cast;
|
||||||
using boost::algorithm::clamp;
|
using boost::algorithm::clamp;
|
||||||
using boost::optional;
|
using boost::optional;
|
||||||
|
using std::array;
|
||||||
|
|
||||||
|
constexpr size_t shapeValueCount = static_cast<size_t>(Shape::EndSentinel);
|
||||||
|
|
||||||
|
Shape getBasicShape(Shape shape) {
|
||||||
|
static constexpr array<Shape, shapeValueCount> basicShapes = make_array(A, B, C, D, E, F, B, C, A);
|
||||||
|
return basicShapes[static_cast<size_t>(shape)];
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape relax(Shape shape) {
|
||||||
|
static constexpr array<Shape, shapeValueCount> relaxedShapes = make_array(A, B, C, C, C, B, B, C, X);
|
||||||
|
return relaxedShapes[static_cast<size_t>(shape)];
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape getClosestShape(Shape reference, ShapeSet shapes) {
|
||||||
|
if (shapes.empty()) {
|
||||||
|
throw std::invalid_argument("Cannot select from empty set of shapes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// A matrix that for each shape contains all shapes in ascending order of effort required to move to them
|
||||||
|
constexpr static array<array<Shape, shapeValueCount>, shapeValueCount> effortMatrix = make_array(
|
||||||
|
/* A */ make_array(A, X, G, B, C, H, E, D, F),
|
||||||
|
/* B */ make_array(B, G, A, X, C, H, E, D, F),
|
||||||
|
/* C */ make_array(C, H, B, G, D, A, X, E, F),
|
||||||
|
/* D */ make_array(D, C, H, B, G, A, X, E, F),
|
||||||
|
/* E */ make_array(E, C, H, B, G, A, X, D, F),
|
||||||
|
/* F */ make_array(F, B, G, A, X, C, H, E, D),
|
||||||
|
/* G */ make_array(G, B, C, H, A, X, E, D, F),
|
||||||
|
/* H */ make_array(H, C, B, G, D, A, X, E, F), // Like C
|
||||||
|
/* X */ make_array(X, A, G, B, C, H, E, D, F) // Like A
|
||||||
|
);
|
||||||
|
|
||||||
|
auto& closestShapes = effortMatrix.at(static_cast<size_t>(reference));
|
||||||
|
for (Shape closestShape : closestShapes) {
|
||||||
|
if (shapes.find(closestShape) != shapes.end()) {
|
||||||
|
return closestShape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::invalid_argument("Unable to find closest shape.");
|
||||||
|
}
|
||||||
|
|
||||||
ShapeRule::ShapeRule(const ShapeSet& regularShapes, const ShapeSet& alternativeShapes) :
|
ShapeRule::ShapeRule(const ShapeSet& regularShapes, const ShapeSet& alternativeShapes) :
|
||||||
regularShapes(regularShapes),
|
regularShapes(regularShapes),
|
||||||
alternativeShapes(alternativeShapes)
|
alternativeShapes(alternativeShapes)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Timeline<ShapeRule> animatePhone(optional<Phone> phone, centiseconds duration, centiseconds previousDuration) {
|
Timeline<ShapeRule> getShapeRule(Phone phone, centiseconds duration, centiseconds previousDuration) {
|
||||||
// Returns a timeline with a single shape set
|
// Returns a timeline with a single shape set
|
||||||
auto single = [duration](ShapeRule value) {
|
auto single = [duration](ShapeRule value) {
|
||||||
return Timeline<ShapeRule> {{0_cs, duration, value}};
|
return Timeline<ShapeRule> {{0_cs, duration, value}};
|
||||||
|
@ -37,19 +79,15 @@ Timeline<ShapeRule> animatePhone(optional<Phone> phone, centiseconds duration, c
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the result of `animatePhone` when called with identical arguments
|
// Returns the result of `getShapeRule` when called with identical arguments
|
||||||
// except for a different phone.
|
// except for a different phone.
|
||||||
auto like = [duration, previousDuration](optional<Phone> referencePhone) {
|
auto like = [duration, previousDuration](Phone referencePhone) {
|
||||||
return animatePhone(referencePhone, duration, previousDuration);
|
return getShapeRule(referencePhone, duration, previousDuration);
|
||||||
};
|
};
|
||||||
|
|
||||||
static const ShapeRule any{{A, B, C, D, E, F, G, H, X}};
|
static const ShapeRule any{{A, B, C, D, E, F, G, H, X}};
|
||||||
|
|
||||||
if (!phone) {
|
switch (phone) {
|
||||||
return single({{X}});
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (*phone) {
|
|
||||||
case Phone::AO: return single({{E}});
|
case Phone::AO: return single({{E}});
|
||||||
case Phone::AA: return single({{D}});
|
case Phone::AA: return single({{D}});
|
||||||
case Phone::IY: return single({{B}, {C}});
|
case Phone::IY: return single({{B}, {C}});
|
||||||
|
|
|
@ -1,19 +1,37 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <boost/optional.hpp>
|
|
||||||
#include "Shape.h"
|
#include "Shape.h"
|
||||||
#include "Timeline.h"
|
#include "Timeline.h"
|
||||||
#include "Phone.h"
|
#include "Phone.h"
|
||||||
|
|
||||||
|
// Returns the basic shape (A-F) that most closely resembles the specified shape.
|
||||||
|
Shape getBasicShape(Shape shape);
|
||||||
|
|
||||||
|
// Returns the mouth shape that results from relaxing the specified shape.
|
||||||
|
Shape relax(Shape shape);
|
||||||
|
|
||||||
// A set of mouth shapes that can be used to represent a certain sound
|
// A set of mouth shapes that can be used to represent a certain sound
|
||||||
using ShapeSet = std::set<Shape>;
|
using ShapeSet = std::set<Shape>;
|
||||||
|
|
||||||
|
// Gets the shape from a non-empty set of shapes that most closely resembles a reference shape.
|
||||||
|
Shape getClosestShape(Shape reference, ShapeSet shapes);
|
||||||
|
|
||||||
|
// A struct describing the possible shapes to use during a given time range.
|
||||||
struct ShapeRule {
|
struct ShapeRule {
|
||||||
ShapeRule(const ShapeSet& regularShapes, const ShapeSet& alternativeShapes = {});
|
ShapeRule(const ShapeSet& regularShapes, const ShapeSet& alternativeShapes = {});
|
||||||
|
|
||||||
|
// A set of one or more shapes that may be used to animate a given time range.
|
||||||
|
// The actual selection will be performed based on similarity with the previous or next shape.
|
||||||
ShapeSet regularShapes;
|
ShapeSet regularShapes;
|
||||||
|
|
||||||
|
// The regular animation algorithm tries to minimize mouth shape changes. As a result, the mouth may sometimes remain static for too long.
|
||||||
|
// This is a set of zero or more shapes that may be used in these cases.
|
||||||
|
// In contrast to the regular shapes, this set should only contain shapes that can be used regardless of the surrounding shapes.
|
||||||
ShapeSet alternativeShapes;
|
ShapeSet alternativeShapes;
|
||||||
};
|
};
|
||||||
|
|
||||||
Timeline<ShapeRule> animatePhone(boost::optional<Phone> phone, centiseconds duration, centiseconds previousDuration);
|
// Returns the shape rule(s) to use for a given phone.
|
||||||
|
// The resulting timeline will always cover the entire duration of the phone (starting at 0 cs).
|
||||||
|
// It may extend into the negative time range if animation is required prior to the sound being heard.
|
||||||
|
Timeline<ShapeRule> getShapeRule(Phone phone, centiseconds duration, centiseconds previousDuration);
|
||||||
|
|
Loading…
Reference in New Issue