overte-thingvellir/libraries/entities/src/EntityDynamicInterface.cpp
2019-06-22 14:16:17 +12:00

409 lines
17 KiB
C++

//
// EntityDynamicInterface.cpp
// libraries/entities/src
//
// Created by Seth Alves on 2015-6-4
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*
+-------------------------+ +--------------------------------+
| | | |
| EntityDynamicsInterface | | EntityDynamicsFactoryInterface |
| (entities) | | (entities) |
+-------------------------+ +--------------------------------+
| | | |
+----+ +--+ | |
| | | |
+---------------------+ +----------------+ +--------------------------+ +---------------------------+
| | | | | | | |
| AssignmentDynamics | | ObjectDynamics | | InterfaceDynamicsFactory | | AssignmentDynamicsFactory |
|(assignment client) | | (physics) | | (interface) | | (assignment client) |
+---------------------+ +----------------+ +--------------------------+ +---------------------------+
| |
| |
+---------------------+ | |
| | | |
| btActionInterface | | |
| (bullet) | | |
+---------------------+ | |
| | |
+--------------+ +------------------+ +-------------------+
| | | | | |
| ObjectAction | | ObjectConstraint | | btTypedConstraint |
| (physics) | | (physics) --|--->| (bullet) |
+--------------+ +------------------+ +-------------------+
| |
| |
+--------------------+ +-----------------------+
| | | |
| ObjectActionTractor| | ObjectConstraintHinge |
| (physics) | | (physics) |
+--------------------+ +-----------------------+
A dynamic is a callback which is registered with bullet. A dynamic is called-back every physics
simulation step and can do whatever it wants with the various datastructures it has available. A
dynamic, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that
point by a spring.
In this system, a dynamic is a property of an EntityItem (rather, an EntityItem has a property which
encodes a list of dynamics). Each dynamic has a type and some arguments. Dynamics can be created by a
script or when receiving information via an EntityTree data-stream (either over the network or from an
svo file).
In the interface, if an EntityItem has dynamics, this EntityItem will have pointers to ObjectDynamic
subclass (like ObjectDynamicTractor) instantiations. Code in the entities library affects a dynamic-object
via the EntityDynamicInterface (which knows nothing about bullet). When the ObjectDynamic subclass
instance is created, it is registered as a dynamic with bullet. Bullet will call into code in this
instance with the btDynamicInterface every physics-simulation step.
Because the dynamic can exist next to the interface's EntityTree or the entity-server's EntityTree,
parallel versions of the factories and dynamics are needed.
In an entity-server, any type of dynamic is instantiated as an AssignmentDynamic. This dynamic isn't called
by bullet (which isn't part of an assignment-client). It does nothing but remember its type and its
arguments. This may change as we try to make the entity-server's simple physics simulation better, but
right now the AssignmentDynamic class is a place-holder.
The dynamic-objects are instantiated by singleton (dependecy) subclasses of EntityDynamicFactoryInterface.
In the interface, the subclass is an InterfaceDynamicFactory and it will produce things like
ObjectDynamicTractor. In an entity-server the subclass is an AssignmentDynamicFactory and it always
produces AssignmentDynamics.
Depending on the dynamic's type, it will have various arguments. When a script changes an argument of an
dynamic, the argument-holding member-variables of ObjectDynamicTractor (in this example) are updated and
also serialized into _dynamicData in the EntityItem. Each subclass of ObjectDynamic knows how to serialize
and deserialize its own arguments. _dynamicData is what gets sent over the wire or saved in an svo file.
When a packet-reader receives data for _dynamicData, it will save it in the EntityItem; this causes the
deserializer in the ObjectDynamic subclass to be called with the new data, thereby updating its argument
variables. These argument variables are used by the code which is run when bullet does a callback.
*/
#include "EntityDynamicInterface.h"
#include "EntityItem.h"
/**jsdoc
* <p>An entity action may be one of the following types:</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Type</th><th>Description</th><th>Arguments</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"far-grab"</code></td><td>Avatar action</td>
* <td>Moves and rotates an entity to a target position and orientation, optionally relative to another entity. Collisions
* between the entity and the user's avatar are disabled during the far-grab.</td>
* <td>{@link Entities.ActionArguments-FarGrab}</td></tr>
* <tr><td><code>"hold"</code></td><td>Avatar action</td>
* <td>Positions and rotates an entity relative to an avatar's hand. Collisions between the entity and the user's avatar
* are disabled during the hold.</td>
* <td>{@link Entities.ActionArguments-Hold}</td></tr>
* <tr><td><code>"offset"</code></td><td>Object action</td>
* <td>Moves an entity so that it is a set distance away from a target point.</td>
* <td>{@link Entities.ActionArguments-Offset}</td></tr>
* <tr><td><code>"tractor"</code></td><td>Object action</td>
* <td>Moves and rotates an entity to a target position and orientation, optionally relative to another entity.</td>
* <td>{@link Entities.ActionArguments-Tractor}</td></tr>
* <tr><td><code>"travel-oriented"</code></td><td>Object action</td>
* <td>Orients an entity to align with its direction of travel.</td>
* <td>{@link Entities.ActionArguments-TravelOriented}</td></tr>
* <tr><td><code>"hinge"</code></td><td>Object constraint</td>
* <td>Lets an entity pivot about an axis or connects two entities with a hinge joint.</td>
* <td>{@link Entities.ActionArguments-Hinge}</td></tr>
* <tr><td><code>"slider"</code></td><td>Object constraint</td>
* <td>Lets an entity slide and rotate along an axis, or connects two entities that slide and rotate along a shared
* axis.</td>
* <td>{@link Entities.ActionArguments-Slider|ActionArguments-Slider}</td></tr>
* <tr><td><code>"cone-twist"</code></td><td>Object constraint</td>
* <td>Connects two entities with a joint that can move through a cone and can twist.</td>
* <td>{@link Entities.ActionArguments-ConeTwist}</td></tr>
* <tr><td><code>"ball-socket"</code></td><td>Object constraint</td>
* <td>Connects two entities with a ball and socket joint.</td>
* <td>{@link Entities.ActionArguments-BallSocket}</td></tr>
* <tr><td><code>"spring"</code></td><td colspan="3">Synonym for <code>"tractor"</code>.
* <span class="important">Deprecated.</span></td></tr>
* </tbody>
* </table>
* @typedef {string} Entities.ActionType
*/
// Note: The "none" action type is not listed because it's an internal "uninitialized" value and not useful for scripts.
EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicTypeString) {
QString normalizedDynamicTypeString = dynamicTypeString.toLower().remove('-').remove('_');
if (normalizedDynamicTypeString == "none") {
return DYNAMIC_TYPE_NONE;
}
if (normalizedDynamicTypeString == "offset") {
return DYNAMIC_TYPE_OFFSET;
}
if (normalizedDynamicTypeString == "spring") {
return DYNAMIC_TYPE_TRACTOR;
}
if (normalizedDynamicTypeString == "tractor") {
return DYNAMIC_TYPE_TRACTOR;
}
if (normalizedDynamicTypeString == "hold") {
return DYNAMIC_TYPE_HOLD;
}
if (normalizedDynamicTypeString == "traveloriented") {
return DYNAMIC_TYPE_TRAVEL_ORIENTED;
}
if (normalizedDynamicTypeString == "hinge") {
return DYNAMIC_TYPE_HINGE;
}
if (normalizedDynamicTypeString == "fargrab") {
return DYNAMIC_TYPE_FAR_GRAB;
}
if (normalizedDynamicTypeString == "slider") {
return DYNAMIC_TYPE_SLIDER;
}
if (normalizedDynamicTypeString == "ballsocket") {
return DYNAMIC_TYPE_BALL_SOCKET;
}
if (normalizedDynamicTypeString == "conetwist") {
return DYNAMIC_TYPE_CONE_TWIST;
}
qCDebug(entities) << "Warning -- EntityDynamicInterface::dynamicTypeFromString got unknown dynamic-type name"
<< dynamicTypeString;
return DYNAMIC_TYPE_NONE;
}
QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicType) {
switch(dynamicType) {
case DYNAMIC_TYPE_NONE:
return "none";
case DYNAMIC_TYPE_OFFSET:
return "offset";
case DYNAMIC_TYPE_SPRING:
case DYNAMIC_TYPE_TRACTOR:
return "tractor";
case DYNAMIC_TYPE_HOLD:
return "hold";
case DYNAMIC_TYPE_TRAVEL_ORIENTED:
return "travel-oriented";
case DYNAMIC_TYPE_HINGE:
return "hinge";
case DYNAMIC_TYPE_FAR_GRAB:
return "far-grab";
case DYNAMIC_TYPE_SLIDER:
return "slider";
case DYNAMIC_TYPE_BALL_SOCKET:
return "ball-socket";
case DYNAMIC_TYPE_CONE_TWIST:
return "cone-twist";
}
assert(false);
return "none";
}
glm::vec3 EntityDynamicInterface::extractVec3Argument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return glm::vec3(0.0f);
}
QVariant resultV = arguments[argumentName];
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map";
ok = false;
return glm::vec3(0.0f);
}
QVariantMap resultVM = resultV.toMap();
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z";
ok = false;
return glm::vec3(0.0f);
}
QVariant xV = resultVM["x"];
QVariant yV = resultVM["y"];
QVariant zV = resultVM["z"];
bool xOk = true;
bool yOk = true;
bool zOk = true;
float x = xV.toFloat(&xOk);
float y = yV.toFloat(&yOk);
float z = zV.toFloat(&zOk);
if (!xOk || !yOk || !zOk) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, and z of type float.";
ok = false;
return glm::vec3(0.0f);
}
if (x != x || y != y || z != z) {
// at least one of the values is NaN
ok = false;
return glm::vec3(0.0f);
}
return glm::vec3(x, y, z);
}
glm::quat EntityDynamicInterface::extractQuatArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return glm::quat();
}
QVariant resultV = arguments[argumentName];
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map, not" << resultV.typeName();
ok = false;
return glm::quat();
}
QVariantMap resultVM = resultV.toMap();
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z") || !resultVM.contains("w")) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z, and w";
ok = false;
return glm::quat();
}
QVariant xV = resultVM["x"];
QVariant yV = resultVM["y"];
QVariant zV = resultVM["z"];
QVariant wV = resultVM["w"];
bool xOk = true;
bool yOk = true;
bool zOk = true;
bool wOk = true;
float x = xV.toFloat(&xOk);
float y = yV.toFloat(&yOk);
float z = zV.toFloat(&zOk);
float w = wV.toFloat(&wOk);
if (!xOk || !yOk || !zOk || !wOk) {
qCDebug(entities) << objectName << "argument" << argumentName
<< "must be a map with keys: x, y, z, and w of type float.";
ok = false;
return glm::quat();
}
if (x != x || y != y || z != z || w != w) {
// at least one of the components is NaN!
ok = false;
return glm::quat();
}
return glm::normalize(glm::quat(w, x, y, z));
}
float EntityDynamicInterface::extractFloatArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return 0.0f;
}
QVariant variant = arguments[argumentName];
bool variantOk = true;
float value = variant.toFloat(&variantOk);
if (!variantOk || std::isnan(value)) {
ok = false;
return 0.0f;
}
return value;
}
int EntityDynamicInterface::extractIntegerArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return 0.0f;
}
QVariant variant = arguments[argumentName];
bool variantOk = true;
int value = variant.toInt(&variantOk);
if (!variantOk) {
ok = false;
return 0;
}
return value;
}
QString EntityDynamicInterface::extractStringArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return "";
}
return arguments[argumentName].toString();
}
bool EntityDynamicInterface::extractBooleanArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return false;
}
return arguments[argumentName].toBool();
}
QDataStream& operator<<(QDataStream& stream, const EntityDynamicType& entityDynamicType) {
return stream << (quint16)entityDynamicType;
}
QDataStream& operator>>(QDataStream& stream, EntityDynamicType& entityDynamicType) {
quint16 dynamicTypeAsInt;
stream >> dynamicTypeAsInt;
entityDynamicType = (EntityDynamicType)dynamicTypeAsInt;
return stream;
}
QString serializedDynamicsToDebugString(QByteArray data) {
if (data.size() == 0) {
return QString();
}
QVector<QByteArray> serializedDynamics;
QDataStream serializedDynamicsStream(data);
serializedDynamicsStream >> serializedDynamics;
QString result;
foreach(QByteArray serializedDynamic, serializedDynamics) {
QDataStream serializedDynamicStream(serializedDynamic);
EntityDynamicType dynamicType;
QUuid dynamicID;
serializedDynamicStream >> dynamicType;
serializedDynamicStream >> dynamicID;
result += EntityDynamicInterface::dynamicTypeToString(dynamicType) + "-" + dynamicID.toString() + " ";
}
return result;
}