added MyAvatar.setMotorVelocity() & friends for JS

This commit is contained in:
Andrew Meadows 2014-09-16 17:09:32 -07:00
parent 612dc9d226
commit 489871d0d6
6 changed files with 187 additions and 104 deletions

View file

@ -3890,7 +3890,7 @@ void Application::stopAllScripts(bool restart) {
// HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities
// whenever a script stops in case it happened to have been setting joint rotations.
// TODO: expose animation priorities and provide a layered animation control system.
_myAvatar->clearJointAnimationPriorities();
_myAvatar->clearScriptableSettings();
}
void Application::stopScript(const QString &scriptName) {
@ -3903,6 +3903,9 @@ void Application::stopScript(const QString &scriptName) {
// TODO: expose animation priorities and provide a layered animation control system.
_myAvatar->clearJointAnimationPriorities();
}
if (_scriptEnginesHash.empty()) {
_myAvatar->clearScriptableSettings();
}
}
void Application::reloadAllScripts() {

View file

@ -268,13 +268,17 @@ Menu::Menu() :
SLOT(resetSize()));
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::KeyboardMotorControl,
Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehavior()));
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ScriptedMotorControl, 0, true,
avatar, SLOT(updateMotionBehavior()));
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ChatCircling, 0, false);
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::GlowWhenSpeaking, 0, true);
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true);
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false,
avatar, SLOT(updateMotionBehaviorsFromMenu()));
avatar, SLOT(updateMotionBehavior()));
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true,
avatar, SLOT(updateMotionBehaviorsFromMenu()));
avatar, SLOT(updateMotionBehavior()));
QMenu* collisionsMenu = avatarMenu->addMenu("Collide With...");
addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll, 0, false,
@ -744,9 +748,11 @@ void Menu::loadSettings(QSettings* settings) {
// MyAvatar caches some menu options, so we have to update them whenever we load settings.
// TODO: cache more settings in MyAvatar that are checked with very high frequency.
setIsOptionChecked(MenuOption::KeyboardMotorControl , true);
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
myAvatar->updateCollisionGroups();
myAvatar->onToggleRagdoll();
myAvatar->updateMotionBehavior();
if (lockedSettings) {
Application::getInstance()->unlockSettings();

View file

@ -399,6 +399,7 @@ namespace MenuOption {
const QString HeadMouse = "Head Mouse";
const QString IncreaseAvatarSize = "Increase Avatar Size";
const QString IncreaseVoxelSize = "Increase Voxel Size";
const QString KeyboardMotorControl = "Enable Keyboard Motor Control";
const QString LoadScript = "Open and Run Script File...";
const QString LoadScriptURL = "Open and Run Script from URL...";
const QString LodTools = "LOD Tools";
@ -437,6 +438,7 @@ namespace MenuOption {
const QString RunningScripts = "Running Scripts";
const QString RunTimingTests = "Run Timing Tests";
const QString ScriptEditor = "Script Editor...";
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
const QString SettingsExport = "Export Settings";
const QString SettingsImport = "Import Settings";
const QString ShowBordersModelNodes = "Show Model Nodes";

View file

@ -54,9 +54,11 @@ const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED;
// TODO: normalize avatar speed for standard avatar size, then scale all motion logic
// to properly follow avatar size.
float DEFAULT_MOTOR_TIMESCALE = 0.25f;
float MAX_AVATAR_SPEED = 300.0f;
float MAX_MOTOR_SPEED = MAX_AVATAR_SPEED;
float MAX_KEYBOARD_MOTOR_SPEED = MAX_AVATAR_SPEED;
float DEFAULT_KEYBOARD_MOTOR_TIMESCALE = 0.25f;
float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f;
float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f;
MyAvatar::MyAvatar() :
Avatar(),
@ -71,9 +73,10 @@ MyAvatar::MyAvatar() :
_isBraking(false),
_trapDuration(0.0f),
_thrust(0.0f),
_motorVelocity(0.0f),
_motorTimescale(DEFAULT_MOTOR_TIMESCALE),
_maxMotorSpeed(MAX_MOTOR_SPEED),
_keyboardMotorVelocity(0.0f),
_keyboardMotorTimescale(DEFAULT_KEYBOARD_MOTOR_TIMESCALE),
_scriptedMotorVelocity(0.0f),
_scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE),
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
_lookAtTargetAvatar(),
_shouldRender(true),
@ -1027,6 +1030,28 @@ void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData)
_billboardValid = false;
}
void MyAvatar::setMotorVelocity(const glm::vec3& velocity) {
float MAX_SCRIPTED_MOTOR_SPEED = 500.0f;
_scriptedMotorVelocity = velocity;
float speed = glm::length(_scriptedMotorVelocity);
if (speed > MAX_SCRIPTED_MOTOR_SPEED) {
_scriptedMotorVelocity *= MAX_SCRIPTED_MOTOR_SPEED / speed;
}
}
void MyAvatar::setMotorTimescale(float timescale) {
// we clamp the timescale on the large side (instead of just the low side) to prevent
// obnoxiously large values from introducing NaN into avatar's velocity
_scriptedMotorTimescale = glm::clamp(timescale, MIN_SCRIPTED_MOTOR_TIMESCALE,
DEFAULT_SCRIPTED_MOTOR_TIMESCALE);
}
void MyAvatar::clearScriptableSettings() {
clearJointAnimationPriorities();
_scriptedMotorVelocity = glm::vec3(0.0f);
_scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE;
}
void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation,
const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) {
if (QThread::currentThread() != thread()) {
@ -1138,13 +1163,102 @@ void MyAvatar::updateOrientation(float deltaTime) {
setOrientation(orientation);
}
glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool walkingOnFloor) {
if (! (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED)) {
return localVelocity;
}
// compute motor efficiency
// The timescale of the motor is the approximate time it takes for the motor to
// accomplish its intended localVelocity. A short timescale makes the motor strong,
// and a long timescale makes it weak. The value of timescale to use depends
// on what the motor is doing:
//
// (1) braking --> short timescale (aggressive motor assertion)
// (2) pushing --> medium timescale (mild motor assertion)
// (3) inactive --> long timescale (gentle friction for low speeds)
float MIN_KEYBOARD_MOTOR_TIMESCALE = 0.125f;
float MAX_KEYBOARD_MOTOR_TIMESCALE = 0.4f;
float MIN_KEYBOARD_BRAKE_SPEED = 0.3f;
float timescale = MAX_KEYBOARD_MOTOR_TIMESCALE;
bool isThrust = (glm::length2(_thrust) > EPSILON);
if (_isPushing || isThrust ||
(_scriptedMotorTimescale < MAX_KEYBOARD_MOTOR_TIMESCALE &&
_motionBehaviors | AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)) {
// we don't want to break if anything is pushing the avatar around
timescale = _keyboardMotorTimescale;
_isBraking = false;
} else {
float speed = glm::length(localVelocity);
_isBraking = _wasPushing || (_isBraking && speed > MIN_KEYBOARD_BRAKE_SPEED);
if (_isBraking) {
timescale = MIN_KEYBOARD_MOTOR_TIMESCALE;
}
}
_wasPushing = _isPushing || isThrust;
_isPushing = false;
float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
(fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) +
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
if (keyboardInput) {
// Compute keyboard input
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
glm::vec3 direction = front + right + up;
float directionLength = glm::length(direction);
// Compute motor magnitude
if (directionLength > EPSILON) {
direction /= directionLength;
// Compute the target keyboard velocity (which ramps up slowly, and damps very quickly)
// the max magnitude of which depends on what we're doing:
float finalMaxMotorSpeed = walkingOnFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED;
float motorLength = glm::length(_keyboardMotorVelocity);
if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) {
// an active keyboard motor should never be slower than this
_keyboardMotorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction;
motorEfficiency = 1.0f;
} else {
float KEYBOARD_MOTOR_LENGTH_TIMESCALE = 2.0f;
float INCREASE_FACTOR = 1.8f;
motorLength *= 1.0f + glm::clamp(deltaTime / KEYBOARD_MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f) * INCREASE_FACTOR;
if (motorLength > finalMaxMotorSpeed) {
motorLength = finalMaxMotorSpeed;
}
_keyboardMotorVelocity = motorLength * direction;
}
_isPushing = true;
}
} else {
_keyboardMotorVelocity = glm::vec3(0.0f);
}
// apply keyboard motor
return localVelocity + motorEfficiency * (_keyboardMotorVelocity - localVelocity);
}
glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVelocity) {
if (! (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)) {
return localVelocity;
}
float motorEfficiency = glm::clamp(deltaTime / _scriptedMotorTimescale, 0.0f, 1.0f);
return localVelocity + motorEfficiency * (_scriptedMotorVelocity - localVelocity);
}
const float NEARBY_FLOOR_THRESHOLD = 5.0f;
void MyAvatar::updatePosition(float deltaTime) {
// check for floor by casting a ray straight down from avatar's position
float heightAboveFloor = FLT_MAX;
bool walkingOnFloor = false;
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD;
RayIntersectionInfo intersection;
// NOTE: avatar is center of PhysicsSimulation, so rayStart is the origin for the purposes of the raycast
intersection._rayStart = glm::vec3(0.0f);
@ -1153,26 +1267,28 @@ void MyAvatar::updatePosition(float deltaTime) {
if (_physicsSimulation.findFloorRayIntersection(intersection)) {
// NOTE: heightAboveFloor is the distance between the bottom of the avatar and the floor
heightAboveFloor = intersection._hitDistance - boundingShape.getBoundingRadius();
if (heightAboveFloor < maxFloorDistance) {
walkingOnFloor = true;
}
}
// velocity is initialized to the measured _velocity but will be modified by friction, external thrust, etc
glm::vec3 velocity = _velocity;
bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f);
bool walkingOnFloor = false;
bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f) || _scriptedMotorVelocity.y > 0.0f;
if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) {
const float MAX_SPEED_UNDER_GRAVITY = 2.0f * _scale * MAX_WALKING_SPEED;
if (pushingUp || glm::length2(velocity) > MAX_SPEED_UNDER_GRAVITY * MAX_SPEED_UNDER_GRAVITY) {
// we're pushing up or moving quickly, so disable gravity
setLocalGravity(glm::vec3(0.0f));
walkingOnFloor = false;
} else {
const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD;
if (heightAboveFloor > maxFloorDistance) {
// disable local gravity when floor is too far away
setLocalGravity(glm::vec3(0.0f));
walkingOnFloor = false;
} else {
// enable gravity
walkingOnFloor = true;
setLocalGravity(-_worldUpDirection);
}
}
@ -1195,59 +1311,23 @@ void MyAvatar::updatePosition(float deltaTime) {
velocity += (deltaTime * GRAVITY_EARTH) * _gravity;
}
float motorEfficiency = glm::clamp(deltaTime / computeMotorTimescale(velocity), 0.0f, 1.0f);
// rotate velocity into camera frame
glm::quat rotation = getHead()->getCameraOrientation();
glm::vec3 localVelocity = glm::inverse(rotation) * velocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED) {
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
(fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) +
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
if (keyboardInput) {
// Compute keyboard input
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
glm::vec3 direction = front + right + up;
float directionLength = glm::length(direction);
// Compute motor magnitude
if (directionLength > EPSILON) {
direction /= directionLength;
// Compute the target keyboard velocity (which ramps up slowly, and damps very quickly)
// the max magnitude of which depends on what we're doing:
float finalMaxMotorSpeed = walkingOnFloor ? _scale * MAX_WALKING_SPEED : _scale * _maxMotorSpeed;
float motorLength = glm::length(_motorVelocity);
if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) {
// an active keyboard motor should never be slower than this
_motorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction;
motorEfficiency = 1.0f;
} else {
float MOTOR_LENGTH_TIMESCALE = 2.0f;
float INCREASE_FACTOR = 1.8f;
motorLength *= 1.0f + glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f) * INCREASE_FACTOR;
if (motorLength > finalMaxMotorSpeed) {
motorLength = finalMaxMotorSpeed;
}
_motorVelocity = motorLength * direction;
}
_isPushing = true;
}
} else {
_motorVelocity = glm::vec3(0.0f);
}
}
glm::vec3 targetVelocity = getHead()->getCameraOrientation() * _motorVelocity;
glm::vec3 deltaVelocity = targetVelocity - velocity;
// apply motors in camera frame
glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, walkingOnFloor);
newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity);
if (walkingOnFloor && !pushingUp) {
// remove vertical component of deltaVelocity
// remove any delta component that points into floor
glm::vec3 deltaVelocity = newLocalVelocity - localVelocity;
deltaVelocity -= glm::dot(deltaVelocity, _worldUpDirection) * _worldUpDirection;
newLocalVelocity = localVelocity + deltaVelocity;
}
// apply motor
velocity += motorEfficiency * deltaVelocity;
// rotate back into world-frame
velocity = rotation * newLocalVelocity;
// apply thrust
velocity += _thrust * deltaTime;
@ -1282,37 +1362,6 @@ void MyAvatar::updatePosition(float deltaTime) {
measureMotionDerivatives(deltaTime);
}
float MyAvatar::computeMotorTimescale(const glm::vec3& velocity) {
// The timescale of the motor is the approximate time it takes for the motor to
// accomplish its intended velocity. A short timescale makes the motor strong,
// and a long timescale makes it weak. The value of timescale to use depends
// on what the motor is doing:
//
// (1) braking --> short timescale (aggressive motor assertion)
// (2) pushing --> medium timescale (mild motor assertion)
// (3) inactive --> long timescale (gentle friction for low speeds)
float MIN_MOTOR_TIMESCALE = 0.125f;
float MAX_MOTOR_TIMESCALE = 0.4f;
float MIN_BRAKE_SPEED = 0.3f;
float timescale = MAX_MOTOR_TIMESCALE;
bool isThrust = (glm::length2(_thrust) > EPSILON);
if (_isPushing || isThrust) {
timescale = _motorTimescale;
_isBraking = false;
} else {
float speed = glm::length(velocity);
_isBraking = _wasPushing || (_isBraking && speed > MIN_BRAKE_SPEED);
if (_isBraking) {
timescale = MIN_MOTOR_TIMESCALE;
}
}
_wasPushing = _isPushing || isThrust;
_isPushing = false;
return timescale;
}
void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
glm::vec3 up = getBodyUpDirection();
const float ENVIRONMENT_SURFACE_ELASTICITY = 0.0f;
@ -1432,10 +1481,10 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) {
// we're colliding against an edge
// rotate _motorVelocity into world frame
glm::vec3 targetVelocity = _motorVelocity;
// rotate _keyboardMotorVelocity into world frame
glm::vec3 targetVelocity = _keyboardMotorVelocity;
glm::quat rotation = getHead()->getCameraOrientation();
targetVelocity = rotation * _motorVelocity;
targetVelocity = rotation * _keyboardMotorVelocity;
if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) {
// we're puhing into the edge, so we want to lift
@ -1786,7 +1835,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, c
emit transformChanged();
}
void MyAvatar::updateMotionBehaviorsFromMenu() {
void MyAvatar::updateMotionBehavior() {
Menu* menu = Menu::getInstance();
if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
_motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
@ -1809,6 +1858,16 @@ void MyAvatar::updateMotionBehaviorsFromMenu() {
if (!(_collisionGroups | COLLISION_GROUP_VOXELS)) {
_voxelShapeManager.clearShapes();
}
if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) {
_motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED;
} else {
_motionBehaviors &= ~AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED;
}
if (menu->isOptionChecked(MenuOption::ScriptedMotorControl)) {
_motionBehaviors |= AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
} else {
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
}
}
void MyAvatar::onToggleRagdoll() {

View file

@ -131,6 +131,14 @@ public:
void clearJointAnimationPriorities();
Q_INVOKABLE glm::vec3 getMotorVelocity() const { return _scriptedMotorVelocity; }
Q_INVOKABLE float getTimescale() const { return _scriptedMotorTimescale; }
Q_INVOKABLE void setMotorVelocity(const glm::vec3& velocity);
Q_INVOKABLE void setMotorTimescale(float timescale);
void clearScriptableSettings();
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
bool allowDuplicates = false, bool useSaved = true);
@ -163,7 +171,7 @@ public slots:
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
void updateMotionBehaviorsFromMenu();
void updateMotionBehavior();
void onToggleRagdoll();
glm::vec3 getLeftPalmPosition();
@ -202,9 +210,10 @@ private:
float _trapDuration; // seconds that avatar has been trapped by collisions
glm::vec3 _thrust; // impulse accumulator for outside sources
glm::vec3 _motorVelocity; // intended velocity of avatar motion (relative to what it's standing on)
float _motorTimescale; // timescale for avatar motor to achieve its desired velocity
float _maxMotorSpeed;
glm::vec3 _keyboardMotorVelocity; // target local-frame velocity of avatar (keyboard)
float _keyboardMotorTimescale; // timescale for avatar to achieve its target velocity
glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (script)
float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity
quint32 _motionBehaviors;
QWeakPointer<AvatarData> _lookAtTargetAvatar;
@ -221,8 +230,9 @@ private:
// private methods
void updateOrientation(float deltaTime);
glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool walkingOnFloor);
glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity);
void updatePosition(float deltaTime);
float computeMotorTimescale(const glm::vec3& velocity);
void updateCollisionWithAvatars(float deltaTime);
void updateCollisionWithEnvironment(float deltaTime, float radius);
void updateCollisionWithVoxels(float deltaTime, float radius);

View file

@ -55,18 +55,21 @@ typedef unsigned long long quint64;
#include "HandData.h"
// avatar motion behaviors
const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 0;
const quint32 AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED = 1U << 0;
const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1;
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 1;
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 2;
const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 3;
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 2;
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 3;
const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 4;
const quint32 AVATAR_MOTION_DEFAULTS =
AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED |
AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED |
AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED |
AVATAR_MOTION_STAND_ON_NEARBY_FLOORS;
// these bits will be expanded as features are exposed
const quint32 AVATAR_MOTION_SCRIPTABLE_BITS =
AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED |
AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY |
AVATAR_MOTION_OBEY_LOCAL_GRAVITY |
AVATAR_MOTION_STAND_ON_NEARBY_FLOORS;