diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp index fe056be81d..8807262028 100644 --- a/libraries/animation/src/AnimExpression.cpp +++ b/libraries/animation/src/AnimExpression.cpp @@ -16,58 +16,56 @@ AnimExpression::AnimExpression(const QString& str) : _expression(str) { - parseExpression(_expression); + auto iter = str.begin(); + parseExpression(_expression, iter); } -bool AnimExpression::parseExpression(const QString& str) { - Token token(Token::End); - auto iter = str.begin(); - do { - token = consumeToken(str, iter); - switch(token.type) { - case Token::Error: - case Token::End: - return false; - } - } while(iter != str.end()); +void AnimExpression::unconsumeToken(const Token& token) { + _tokenStack.push(token); } AnimExpression::Token AnimExpression::consumeToken(const QString& str, QString::const_iterator& iter) const { - while (iter != str.end()) { - if (iter->isSpace()) { - ++iter; - } else if (iter->isLetter()) { - return consumeIdentifier(str, iter); - } else if (iter->isDigit()) { - return consumeNumber(str, iter); - } else { - switch (iter->unicode()) { - case '&': return consumeAnd(str, iter); - case '|': return consumeOr(str, iter); - case '>': return consumeGreaterThan(str, iter); - case '<': return consumeLessThan(str, iter); - case '(': ++iter; return Token(Token::LeftParen); - case ')': ++iter; return Token(Token::RightParen); - case '!': return consumeNot(str, iter); - case '-': ++iter; return Token(Token::Minus); - case '+': ++iter; return Token(Token::Plus); - case '*': ++iter; return Token(Token::Multiply); - case '%': ++iter; return Token(Token::Modulus); - case ',': ++iter; return Token(Token::Comma); - default: - qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin()); - return Token(Token::Error); + if (!_tokenStack.empty()) { + Token top = _tokenStack.top(); + _tokenStack.pop(); + return top; + } else { + while (iter != str.end()) { + if (iter->isSpace()) { + ++iter; + } else if (iter->isLetter()) { + return consumeIdentifier(str, iter); + } else if (iter->isDigit()) { + return consumeNumber(str, iter); + } else { + switch (iter->unicode()) { + case '&': return consumeAnd(str, iter); + case '|': return consumeOr(str, iter); + case '>': return consumeGreaterThan(str, iter); + case '<': return consumeLessThan(str, iter); + case '(': ++iter; return Token(Token::LeftParen); + case ')': ++iter; return Token(Token::RightParen); + case '!': return consumeNot(str, iter); + case '-': ++iter; return Token(Token::Minus); + case '+': ++iter; return Token(Token::Plus); + case '*': ++iter; return Token(Token::Multiply); + case '%': ++iter; return Token(Token::Modulus); + case ',': ++iter; return Token(Token::Comma); + default: + qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin()); + return Token(Token::Error); + } } } + return Token(Token::End); } - return Token(Token::End); } AnimExpression::Token AnimExpression::consumeIdentifier(const QString& str, QString::const_iterator& iter) const { assert(iter != str.end()); assert(iter->isLetter()); auto begin = iter; - while (iter->isLetter() && iter != str.end()) { + while ((iter->isLetter() || iter->isDigit()) && iter != str.end()) { ++iter; } int pos = (int)(begin - str.begin()); @@ -185,3 +183,93 @@ AnimExpression::Token AnimExpression::consumeNot(const QString& str, QString::co return Token(Token::Not); } } + +bool AnimExpression::parseExpression(const QString& str, QString::const_iterator& iter) { + auto token = consumeToken(str, iter); + if (token.type == Token::Identifier) { + if (token.strVal == "true") { + _opCodes.push_back(OpCode {true}); + } else if (token.strVal == "false") { + _opCodes.push_back(OpCode {false}); + } else { + _opCodes.push_back(OpCode {token.strVal}); + } + return true; + } else if (token.type == Token::Int) { + _opCodes.push_back(OpCode {token.intVal}); + return true; + } else if (token.type == Token::Float) { + _opCodes.push_back(OpCode {token.floatVal}); + return true; + } else if (token.type == Token::LeftParen) { + if (parseUnaryExpression(str, iter)) { + token = consumeToken(str, iter); + if (token.type != Token::RightParen) { + qCCritical(animation) << "Error parsing expression, expected ')'"; + return false; + } else { + return true; + } + } else { + return false; + } + } else { + qCCritical(animation) << "Error parsing expression, unexpected symbol"; + return false; + } +} + +bool AnimExpression::parseUnaryExpression(const QString& str, QString::const_iterator& iter) { + auto token = consumeToken(str, iter); + if (token.type == Token::Plus) { + if (parseExpression(str, iter)) { + _opCodes.push_back(OpCode {OpCode::UnaryPlus}); + return true; + } else { + return false; + } + } else if (token.type == Token::Minus) { + if (parseExpression(str, iter)) { + _opCodes.push_back(OpCode {OpCode::UnaryMinus}); + return true; + } else { + return false; + } + } else if (token.type == Token::Not) { + if (parseExpression(str, iter)) { + _opCodes.push_back(OpCode {OpCode::Not}); + return true; + } else { + return false; + } + } else { + unconsumeToken(token); + return parseExpression(str, iter); + } +} + +AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const { + std::stack stack; + for (auto& opCode : _opCodes) { + switch (opCode.type) { + case OpCode::Identifier: + case OpCode::Int: + case OpCode::Float: + stack.push(opCode); + break; + default: + switch (opCode.type) { + case OpCode::Not: + evalNot(map, stack); + break; + } + } + } + return stack.top(); +} + +void AnimExpression::evalNot(const AnimVariantMap& map, std::stack& stack) const { + bool lhs = stack.top().coerceBool(map); + stack.pop(); + stack.push(OpCode {!lhs}); +} diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index 145350547b..8d216ca412 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include "AnimVariant.h" class AnimExpression { public: @@ -44,15 +47,64 @@ protected: Comma, Error }; - Token(Type type) : type(type) {} - Token(const QStringRef& strRef) : type(Type::Identifier), strVal(strRef.toString()) {} - Token(int val) : type(Type::Int), intVal(val) {} - Token(float val) : type(Type::Float), floatVal(val) {} - Type type = End; + Token(Type type) : type {type} {} + Token(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} + Token(int val) : type {Type::Int}, intVal {val} {} + Token(float val) : type {Type::Float}, floatVal {val} {} + Type type {End}; QString strVal; - int intVal; - float floatVal; + int intVal {0}; + float floatVal {0.0f}; }; + + struct OpCode { + enum Type { + Identifier, + Bool, + Int, + Float, + And, + Or, + GreaterThan, + GreaterThanEqual, + LessThan, + LessThanEqual, + Equal, + NotEqual, + LeftParen, + RightParen, + Not, + Minus, + Plus, + Multiply, + Modulus, + UnaryPlus, + UnaryMinus + }; + OpCode(Type type) : type {type} {} + OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} + OpCode(const QString& str) : type {Type::Identifier}, strVal {str} {} + OpCode(int val) : type {Type::Int}, intVal {val} {} + OpCode(bool val) : type {Type::Bool}, intVal {(int)val} {} + OpCode(float val) : type {Type::Float}, floatVal {val} {} + + bool coerceBool(const AnimVariantMap& map) const { + if (type == Int || type == Bool) { + return intVal != 0; + } else if (type == Identifier) { + return map.lookup(strVal, false); + } else { + return true; + } + } + + Type type {Int}; + QString strVal; + int intVal {0}; + float floatVal {0.0f}; + }; + + void unconsumeToken(const Token& token); Token consumeToken(const QString& str, QString::const_iterator& iter) const; Token consumeIdentifier(const QString& str, QString::const_iterator& iter) const; Token consumeNumber(const QString& str, QString::const_iterator& iter) const; @@ -62,9 +114,16 @@ protected: Token consumeLessThan(const QString& str, QString::const_iterator& iter) const; Token consumeNot(const QString& str, QString::const_iterator& iter) const; - bool parseExpression(const QString& str); + bool parseExpression(const QString& str, QString::const_iterator& iter); + bool parseUnaryExpression(const QString& str, QString::const_iterator& iter); + + OpCode evaluate(const AnimVariantMap& map) const; + void evalNot(const AnimVariantMap& map, std::stack& stack) const; QString _expression; + mutable std::stack _tokenStack; + std::vector _opCodes; + }; #endif diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 0d7c657058..ff7794a16a 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -18,8 +18,9 @@ #include #include #include +#include +#include #include "AnimationLogging.h" -#include "StreamUtils.h" class AnimVariant { public: @@ -29,7 +30,6 @@ public: Float, Vec3, Quat, - Mat4, String, NumTypes }; @@ -40,7 +40,6 @@ public: AnimVariant(float value) : _type(Type::Float) { _val.floats[0] = value; } AnimVariant(const glm::vec3& value) : _type(Type::Vec3) { *reinterpret_cast(&_val) = value; } AnimVariant(const glm::quat& value) : _type(Type::Quat) { *reinterpret_cast(&_val) = value; } - AnimVariant(const glm::mat4& value) : _type(Type::Mat4) { *reinterpret_cast(&_val) = value; } AnimVariant(const QString& value) : _type(Type::String) { _stringVal = value; } bool isBool() const { return _type == Type::Bool; } @@ -48,7 +47,6 @@ public: bool isFloat() const { return _type == Type::Float; } bool isVec3() const { return _type == Type::Vec3; } bool isQuat() const { return _type == Type::Quat; } - bool isMat4() const { return _type == Type::Mat4; } bool isString() const { return _type == Type::String; } Type getType() const { return _type; } @@ -57,17 +55,52 @@ public: void setFloat(float value) { assert(_type == Type::Float); _val.floats[0] = value; } void setVec3(const glm::vec3& value) { assert(_type == Type::Vec3); *reinterpret_cast(&_val) = value; } void setQuat(const glm::quat& value) { assert(_type == Type::Quat); *reinterpret_cast(&_val) = value; } - void setMat4(const glm::mat4& value) { assert(_type == Type::Mat4); *reinterpret_cast(&_val) = value; } void setString(const QString& value) { assert(_type == Type::String); _stringVal = value; } - bool getBool() const { assert(_type == Type::Bool); return _val.boolVal; } - int getInt() const { assert(_type == Type::Int || _type == Type::Float); return _type == Type::Float ? (int)_val.floats[0] : _val.intVal; } - float getFloat() const { assert(_type == Type::Float || _type == Type::Int); return _type == Type::Int ? (float)_val.intVal : _val.floats[0]; } - - const glm::vec3& getVec3() const { assert(_type == Type::Vec3); return *reinterpret_cast(&_val); } - const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast(&_val); } - const glm::mat4& getMat4() const { assert(_type == Type::Mat4); return *reinterpret_cast(&_val); } - const QString& getString() const { assert(_type == Type::String); return _stringVal; } + bool getBool() const { + if (_type == Type::Bool) { + return _val.boolVal; + } else if (_type == Type::Int) { + return _val.intVal != 0; + } else { + return false; + } + } + int getInt() const { + if (_type == Type::Int) { + return _val.intVal; + } else if (_type == Type::Float) { + return (int)_val.floats[0]; + } else { + return 0; + } + } + float getFloat() const { + if (_type == Type::Float) { + return _val.floats[0]; + } else if (_type == Type::Int) { + return (float)_val.intVal; + } else { + return 0.0f; + } + } + const glm::vec3& getVec3() const { + if (_type == Type::Vec3) { + return *reinterpret_cast(&_val); + } else { + return Vectors::ZERO; + } + } + const glm::quat& getQuat() const { + if (_type == Type::Quat) { + return *reinterpret_cast(&_val); + } else { + return Quaternions::IDENTITY; + } + } + const QString& getString() const { + return _stringVal; + } protected: Type _type; @@ -75,7 +108,7 @@ protected: union { bool boolVal; int intVal; - float floats[16]; + float floats[4]; } _val; }; @@ -130,15 +163,6 @@ public: } } - const glm::mat4& lookup(const QString& key, const glm::mat4& defaultValue) const { - if (key.isEmpty()) { - return defaultValue; - } else { - auto iter = _map.find(key); - return iter != _map.end() ? iter->second.getMat4() : defaultValue; - } - } - const QString& lookup(const QString& key, const QString& defaultValue) const { if (key.isEmpty()) { return defaultValue; @@ -153,7 +177,6 @@ public: void set(const QString& key, float value) { _map[key] = AnimVariant(value); } void set(const QString& key, const glm::vec3& value) { _map[key] = AnimVariant(value); } void set(const QString& key, const glm::quat& value) { _map[key] = AnimVariant(value); } - void set(const QString& key, const glm::mat4& value) { _map[key] = AnimVariant(value); } void set(const QString& key, const QString& value) { _map[key] = AnimVariant(value); } void unset(const QString& key) { _map.erase(key); } @@ -189,9 +212,6 @@ public: case AnimVariant::Type::Quat: qCDebug(animation) << " " << pair.first << "=" << pair.second.getQuat(); break; - case AnimVariant::Type::Mat4: - qCDebug(animation) << " " << pair.first << "=" << pair.second.getMat4(); - break; case AnimVariant::Type::String: qCDebug(animation) << " " << pair.first << "=" << pair.second.getString(); break; diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 9c1bbe23a4..e69d11629e 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -80,6 +80,7 @@ public: static const vec3& RIGHT; static const vec3& UP; static const vec3& FRONT; + static const vec3 ZERO4; }; // These pack/unpack functions are designed to start specific known types in as efficient a manner diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 44f9c05c22..94caed66f5 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -195,10 +195,7 @@ void AnimTests::testVariant() { auto floatVarNegative = AnimVariant(-1.0f); auto vec3Var = AnimVariant(glm::vec3(1.0f, -2.0f, 3.0f)); auto quatVar = AnimVariant(glm::quat(1.0f, 2.0f, -3.0f, 4.0f)); - auto mat4Var = AnimVariant(glm::mat4(glm::vec4(1.0f, 2.0f, 3.0f, 4.0f), - glm::vec4(5.0f, 6.0f, -7.0f, 8.0f), - glm::vec4(9.0f, 10.0f, 11.0f, 12.0f), - glm::vec4(13.0f, 14.0f, 15.0f, 16.0f))); + QVERIFY(defaultVar.isBool()); QVERIFY(defaultVar.getBool() == false); @@ -233,12 +230,6 @@ void AnimTests::testVariant() { QVERIFY(q.x == 2.0f); QVERIFY(q.y == -3.0f); QVERIFY(q.z == 4.0f); - - QVERIFY(mat4Var.isMat4()); - auto m = mat4Var.getMat4(); - QVERIFY(m[0].x == 1.0f); - QVERIFY(m[1].z == -7.0f); - QVERIFY(m[3].w == 16.0f); } void AnimTests::testAccumulateTime() { @@ -323,10 +314,11 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram QVERIFY(resultFrame == startFrame + 0.5f); QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); triggers.clear(); +} -void AnimTests::testTokenizer() { +void AnimTests::testExpressionTokenizer() { QString str = "(10 + x) >= 20.1 && (y != !z)"; - AnimExpression e(""); + AnimExpression e("x"); auto iter = str.cbegin(); AnimExpression::Token token = e.consumeToken(str, iter); QVERIFY(token.type == AnimExpression::Token::LeftParen); @@ -364,3 +356,21 @@ void AnimTests::testTokenizer() { token = e.consumeToken(str, iter); } +void AnimTests::testExpressionParser() { + QString str = "(!x)"; + AnimExpression e(str); + QVERIFY(e._opCodes.size() == 2); + if (e._opCodes.size() == 2) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier); + QVERIFY(e._opCodes[0].strVal == "x"); + QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Not); + } + + auto vars = AnimVariantMap(); + vars.set("x", false); + + auto opCode = e.evaluate(vars); + QVERIFY(opCode.type == AnimExpression::OpCode::Bool); + QVERIFY(opCode.coerceBool(vars) == true); +} + diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index bfea4eb086..94b3eddd25 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -26,7 +26,8 @@ private slots: void testLoader(); void testVariant(); void testAccumulateTime(); - void testTokenizer(); + void testExpressionTokenizer(); + void testExpressionParser(); }; #endif // hifi_AnimTests_h