Merge pull request #2737 from AndrewMeadows/inertia

swissArmyJetpack.js toggles gravity for avatar
This commit is contained in:
Philip Rosedale 2014-04-28 17:16:22 -07:00
commit 75f75a78f3
10 changed files with 201 additions and 66 deletions

View file

@ -12,7 +12,65 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var numberOfButtons = 3;
// misc global constants
var NUMBER_OF_BUTTONS = 3;
var DOWN = { x: 0.0, y: -1.0, z: 0.0 };
var MAX_VOXEL_SCAN_DISTANCE = 20.0;
// behavior transition thresholds
var MIN_FLYING_SPEED = 1.0;
var MIN_COLLISIONLESS_SPEED = 5.0;
var MAX_WALKING_SPEED = 10.0;
var MAX_COLLIDABLE_SPEED = 25.0;
// button URL and geometry/UI tuning
var BUTTON_IMAGE_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/testing-swatches.svg";
var DISABLED_OFFSET_Y = 12;
var ENABLED_OFFSET_Y = 55 + 12;
var UI_BUFFER = 1;
var OFFSET_X = UI_BUFFER;
var OFFSET_Y = 200;
var BUTTON_WIDTH = 30;
var BUTTON_HEIGHT = 30;
var TEXT_OFFSET_X = OFFSET_X + BUTTON_WIDTH + UI_BUFFER;
var TEXT_HEIGHT = BUTTON_HEIGHT;
var TEXT_WIDTH = 210;
var MSEC_PER_SECOND = 1000;
var EXPIRY_PERIOD = 2 * MSEC_PER_SECOND;
var dater = new Date();
var collisionOnExpiry = dater.getTime() + EXPIRY_PERIOD;
var gravityOnExpiry = dater.getTime() + EXPIRY_PERIOD;
// avatar state
var velocity = { x: 0.0, y: 0.0, z: 0.0 };
var standing = false;
// speedometer globals
var speed = 0.0;
var lastPosition = MyAvatar.position;
var speedometer = Overlays.addOverlay("text", {
x: OFFSET_X,
y: OFFSET_Y - BUTTON_HEIGHT,
width: BUTTON_WIDTH + UI_BUFFER + TEXT_WIDTH,
height: TEXT_HEIGHT,
color: { red: 0, green: 0, blue: 0 },
textColor: { red: 255, green: 0, blue: 0},
topMargin: 4,
leftMargin: 4,
text: "Speed: 0.0"
});
// collision group buttons
var buttons = new Array();
var labels = new Array();
var labelContents = new Array();
labelContents[0] = "Collide with Avatars";
labelContents[1] = "Collide with Voxels";
labelContents[2] = "Collide with Particles";
var groupBits = 0;
var enabledColors = new Array();
enabledColors[0] = { red: 255, green: 0, blue: 0};
@ -24,47 +82,28 @@ disabledColors[0] = { red: 90, green: 75, blue: 75};
disabledColors[1] = { red: 75, green: 90, blue: 75};
disabledColors[2] = { red: 75, green: 90, blue: 90};
var buttons = new Array();
var labels = new Array();
var labelContents = new Array();
labelContents[0] = "Collide with Avatars";
labelContents[1] = "Collide with Voxels";
labelContents[2] = "Collide with Particles";
var groupBits = 0;
var buttonStates = new Array();
var disabledOffsetT = 0;
var enabledOffsetT = 55;
var buttonX = 50;
var buttonY = 200;
var buttonWidth = 30;
var buttonHeight = 54;
var textX = buttonX + buttonWidth + 10;
for (i = 0; i < numberOfButtons; i++) {
for (i = 0; i < NUMBER_OF_BUTTONS; i++) {
var offsetS = 12
var offsetT = disabledOffsetT;
var offsetT = DISABLED_OFFSET_Y;
buttons[i] = Overlays.addOverlay("image", {
//x: buttonX + (buttonWidth * i),
x: buttonX,
y: buttonY + (buttonHeight * i),
width: buttonWidth,
height: buttonHeight,
subImage: { x: offsetS, y: offsetT, width: buttonWidth, height: buttonHeight },
imageURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/testing-swatches.svg",
x: OFFSET_X,
y: OFFSET_Y + (BUTTON_HEIGHT * i),
width: BUTTON_WIDTH,
height: BUTTON_HEIGHT,
subImage: { x: offsetS, y: offsetT, width: BUTTON_WIDTH, height: BUTTON_HEIGHT },
imageURL: BUTTON_IMAGE_URL,
color: disabledColors[i],
alpha: 1,
});
labels[i] = Overlays.addOverlay("text", {
x: textX,
y: buttonY + (buttonHeight * i) + 12,
width: 150,
height: 50,
x: TEXT_OFFSET_X,
y: OFFSET_Y + (BUTTON_HEIGHT * i),
width: TEXT_WIDTH,
height: TEXT_HEIGHT,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 0, blue: 0},
topMargin: 4,
@ -75,12 +114,14 @@ for (i = 0; i < numberOfButtons; i++) {
buttonStates[i] = false;
}
// functions
function updateButton(i, enabled) {
var offsetY = disabledOffsetT;
var offsetY = DISABLED_OFFSET_Y;
var buttonColor = disabledColors[i];
groupBits
if (enabled) {
offsetY = enabledOffsetT;
offsetY = ENABLED_OFFSET_Y;
buttonColor = enabledColors[i];
if (i == 0) {
groupBits |= COLLISION_GROUP_AVATARS;
@ -98,24 +139,33 @@ function updateButton(i, enabled) {
groupBits &= ~COLLISION_GROUP_PARTICLES;
}
}
MyAvatar.collisionGroups = groupBits;
if (groupBits != MyAvatar.collisionGroups) {
MyAvatar.collisionGroups = groupBits;
}
Overlays.editOverlay(buttons[i], { subImage: { y: offsetY } } );
Overlays.editOverlay(buttons[i], { color: buttonColor } );
buttonStates[i] = enabled;
}
// When our script shuts down, we should clean up all of our overlays
function scriptEnding() {
for (i = 0; i < numberOfButtons; i++) {
print("adebug deleting overlay " + i);
for (i = 0; i < NUMBER_OF_BUTTONS; i++) {
Overlays.deleteOverlay(buttons[i]);
Overlays.deleteOverlay(labels[i]);
}
Overlays.deleteOverlay(speedometer);
}
Script.scriptEnding.connect(scriptEnding);
function updateSpeedometerDisplay() {
Overlays.editOverlay(speedometer, { text: "Speed: " + speed.toFixed(2) });
}
Script.setInterval(updateSpeedometerDisplay, 100);
// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays
function update(deltaTime) {
if (groupBits != MyAvatar.collisionGroups) {
@ -124,6 +174,61 @@ function update(deltaTime) {
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
updateButton(2, groupBits & COLLISION_GROUP_PARTICLES);
}
// measure speed
var distance = Vec3.distance(MyAvatar.position, lastPosition);
speed = 0.8 * speed + 0.2 * distance / deltaTime;
lastPosition = MyAvatar.position;
dater = new Date();
var now = dater.getTime();
if (speed < MIN_FLYING_SPEED) {
// scan for landing platform
ray = { origin: MyAvatar.position, direction: DOWN };
var intersection = Voxels.findRayIntersection(ray);
if (intersection.intersects) {
if (!(MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY)) {
var v = intersection.voxel;
var maxCorner = Vec3.sum({ x: v.x, y: v.y, z: v.z }, {x: v.s, y: v.s, z: v.s });
var distance = lastPosition.y - maxCorner.y;
if ((gravityOnExpiry < now) && (distance < MAX_VOXEL_SCAN_DISTANCE)) {
// NOTE: setting the gravity automatically sets the AVATAR_MOTION_OBEY_LOCAL_GRAVITY behavior bit.
MyAvatar.gravity = DOWN;
}
}
} else {
if (MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
MyAvatar.motionBehaviors = MyAvatar.motionBehaviors & ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
}
gravityOnExpiry = now + EXPIRY_PERIOD;
}
} else {
gravityOnExpiry = now + EXPIRY_PERIOD;
}
if (speed < MIN_COLLISIONLESS_SPEED) {
if (collisionOnExpiry < now && !(MyAvatar.collisionGroups & COLLISION_GROUP_VOXELS)) {
// TODO: check to make sure not already colliding
// enable collision with voxels
groupBits |= COLLISION_GROUP_VOXELS;
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
}
} else {
collisionOnExpiry = now + EXPIRY_PERIOD;
}
if (speed > MAX_WALKING_SPEED) {
if (MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
// turn off gravity
MyAvatar.motionBehaviors = MyAvatar.motionBehaviors & ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
}
}
if (speed > MAX_COLLIDABLE_SPEED) {
if (MyAvatar.collisionGroups & COLLISION_GROUP_VOXELS) {
// disable collisions with voxels
groupBits &= ~COLLISION_GROUP_VOXELS;
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
}
}
}
Script.update.connect(update);
@ -131,7 +236,7 @@ Script.update.connect(update);
// we also handle click detection in our mousePressEvent()
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
for (i = 0; i < numberOfButtons; i++) {
for (i = 0; i < NUMBER_OF_BUTTONS; i++) {
if (clickedOverlay == buttons[i]) {
var enabled = !(buttonStates[i]);
updateButton(i, enabled);

View file

@ -857,7 +857,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_G:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::ObeyGravity);
Menu::getInstance()->triggerOption(MenuOption::ObeyEnvironmentalGravity);
}
break;

View file

@ -188,8 +188,8 @@ Menu::Menu() :
addDisabledActionAndSeparator(editMenu, "Physics");
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyGravity, Qt::SHIFT | Qt::Key_G, true,
avatar, SLOT(updateMotionBehaviorFlags()));
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, true,
avatar, SLOT(updateMotionBehaviors()));
addAvatarCollisionSubMenu(editMenu);

View file

@ -315,7 +315,7 @@ namespace MenuOption {
const QString GoTo = "Go To...";
const QString GoToDomain = "Go To Domain...";
const QString GoToLocation = "Go To Location...";
const QString ObeyGravity = "Obey Gravity";
const QString ObeyEnvironmentalGravity = "Obey Environmental Gravity";
const QString HandsCollideWithSelf = "Collide With Self";
const QString HeadMouse = "Head Mouse";
const QString IncreaseAvatarSize = "Increase Avatar Size";

View file

@ -125,10 +125,8 @@ void MyAvatar::update(float deltaTime) {
head->setAudioLoudness(audio->getLastInputLoudness());
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
if (_motionBehaviors & AVATAR_MOTION_OBEY_GRAVITY) {
if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) {
setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()));
} else {
setGravity(glm::vec3(0.0f, 0.0f, 0.0f));
}
simulate(deltaTime);
@ -463,6 +461,27 @@ void MyAvatar::renderHeadMouse() const {
*/
}
void MyAvatar::setLocalGravity(glm::vec3 gravity) {
_motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
// Environmental and Local gravities are incompatible. Since Local is being set here
// the environmental setting must be removed.
_motionBehaviors &= ~AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
setGravity(gravity);
}
void MyAvatar::setGravity(const glm::vec3& gravity) {
_gravity = gravity;
getHead()->setGravity(_gravity);
// use the gravity to determine the new world up direction, if possible
float gravityLength = glm::length(gravity);
if (gravityLength > EPSILON) {
_worldUpDirection = _gravity / -gravityLength;
} else {
_worldUpDirection = DEFAULT_UP_DIRECTION;
}
}
void MyAvatar::saveData(QSettings* settings) {
settings->beginGroup("Avatar");
@ -1046,19 +1065,6 @@ void MyAvatar::maybeUpdateBillboard() {
sendBillboardPacket();
}
void MyAvatar::setGravity(glm::vec3 gravity) {
_gravity = gravity;
getHead()->setGravity(_gravity);
// use the gravity to determine the new world up direction, if possible
float gravityLength = glm::length(gravity);
if (gravityLength > EPSILON) {
_worldUpDirection = _gravity / -gravityLength;
} else {
_worldUpDirection = DEFAULT_UP_DIRECTION;
}
}
void MyAvatar::goHome() {
qDebug("Going Home!");
setPosition(START_LOCATION);
@ -1147,8 +1153,13 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) {
void MyAvatar::updateMotionBehaviors() {
_motionBehaviors = 0;
if (Menu::getInstance()->isOptionChecked(MenuOption::ObeyGravity)) {
_motionBehaviors |= AVATAR_MOTION_OBEY_GRAVITY;
if (Menu::getInstance()->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
_motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
// Environmental and Local gravities are incompatible. Environmental setting trumps local.
_motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
}
if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) {
setGravity(glm::vec3(0.0f));
}
}
@ -1164,7 +1175,14 @@ void MyAvatar::setCollisionGroups(quint32 collisionGroups) {
void MyAvatar::setMotionBehaviors(quint32 flags) {
_motionBehaviors = flags;
Menu* menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::ObeyGravity, (bool)(_motionBehaviors & AVATAR_MOTION_OBEY_GRAVITY));
menu->setIsOptionChecked(MenuOption::ObeyEnvironmentalGravity, (bool)(_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY));
// Environmental and Local gravities are incompatible. Environmental setting trumps local.
if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) {
_motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()));
} else if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) {
setGravity(glm::vec3(0.0f));
}
}
void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) {

View file

@ -25,12 +25,11 @@ enum AvatarHandState
NUM_HAND_STATES
};
const quint32 AVATAR_MOTION_OBEY_GRAVITY = 1U << 0;
class MyAvatar : public Avatar {
Q_OBJECT
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
Q_PROPERTY(quint32 motionBehaviors READ getMotionBehaviors WRITE setMotionBehaviors)
Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setLocalGravity)
public:
MyAvatar();
@ -52,8 +51,7 @@ public:
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
void setLeanScale(float scale) { _leanScale = scale; }
void setGravity(glm::vec3 gravity);
void setMoveTarget(const glm::vec3 moveTarget);
void setLocalGravity(glm::vec3 gravity);
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; }
// getters
@ -123,6 +121,7 @@ private:
bool _shouldJump;
float _driveKeys[MAX_DRIVE_KEYS];
glm::vec3 _gravity;
glm::vec3 _environmentGravity;
float _distanceToNearestAvatar; // How close is the nearest avatar?
// motion stuff
@ -151,6 +150,7 @@ private:
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void updateChatCircle(float deltaTime);
void maybeUpdateBillboard();
void setGravity(const glm::vec3& gravity);
};
#endif // hifi_MyAvatar_h

View file

@ -50,6 +50,10 @@ typedef unsigned long long quint64;
#include "HeadData.h"
#include "HandData.h"
// avatar motion behaviors
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 0;
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 1;
// First bitset
const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits
const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits

View file

@ -239,6 +239,9 @@ void ScriptEngine::init() {
globalObject.setProperty("COLLISION_GROUP_VOXELS", _engine.newVariant(QVariant(COLLISION_GROUP_VOXELS)));
globalObject.setProperty("COLLISION_GROUP_PARTICLES", _engine.newVariant(QVariant(COLLISION_GROUP_PARTICLES)));
globalObject.setProperty("AVATAR_MOTION_OBEY_LOCAL_GRAVITY", _engine.newVariant(QVariant(AVATAR_MOTION_OBEY_LOCAL_GRAVITY)));
globalObject.setProperty("AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY", _engine.newVariant(QVariant(AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY)));
// let the VoxelPacketSender know how frequently we plan to call it
_voxelsScriptingInterface.getVoxelPacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS);
_particlesScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS);

View file

@ -36,6 +36,10 @@ float Vec3::length(const glm::vec3& v) {
return glm::length(v);
}
float Vec3::distance(const glm::vec3& v1, const glm::vec3& v2) {
return glm::distance(v1, v2);
}
glm::vec3 Vec3::normalize(const glm::vec3& v) {
return glm::normalize(v);
}

View file

@ -31,6 +31,7 @@ public slots:
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2);
float length(const glm::vec3& v);
float distance(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 normalize(const glm::vec3& v);
void print(const QString& lable, const glm::vec3& v);
};