Merge branch 'master' into vr-edit-a

This commit is contained in:
David Rowe 2017-09-30 09:30:38 +13:00
commit 75365847a1
14 changed files with 314 additions and 31 deletions

View file

@ -14,7 +14,6 @@
var isWindowFocused = true;
var isKeyboardRaised = false;
var isNumericKeyboard = false;
var KEYBOARD_HEIGHT = 200;
function shouldRaiseKeyboard() {
var nodeName = document.activeElement.nodeName;
@ -38,6 +37,19 @@
return document.activeElement.type === "number";
};
function scheduleBringToView(timeout) {
var timer = setTimeout(function () {
clearTimeout(timer);
var elementRect = document.activeElement.getBoundingClientRect();
var absoluteElementTop = elementRect.top + window.scrollY;
var middle = absoluteElementTop - (window.innerHeight / 2);
window.scrollTo(0, middle);
}, timeout);
}
setInterval(function () {
var keyboardRaised = shouldRaiseKeyboard();
var numericKeyboard = shouldSetNumeric();
@ -56,13 +68,8 @@
}
if (!isKeyboardRaised) {
var delta = document.activeElement.getBoundingClientRect().bottom + 10
- (document.body.clientHeight - KEYBOARD_HEIGHT);
if (delta > 0) {
setTimeout(function () {
document.body.scrollTop += delta;
}, 500); // Allow time for keyboard to be raised in QML.
}
scheduleBringToView(250); // Allow time for keyboard to be raised in QML.
// 2DO: should it be rather done from 'client area height changed' event?
}
isKeyboardRaised = keyboardRaised;
@ -70,6 +77,13 @@
}
}, POLL_FREQUENCY);
window.addEventListener("click", function () {
var keyboardRaised = shouldRaiseKeyboard();
if(keyboardRaised && isKeyboardRaised) {
scheduleBringToView(150);
}
});
window.addEventListener("focus", function () {
isWindowFocused = true;
});

View file

@ -278,9 +278,10 @@ Transform Line3DOverlay::evalRenderTransform() {
auto endPos = getEnd();
auto vec = endPos - transform.getTranslation();
auto scale = glm::length(vec);
const float MIN_LINE_LENGTH = 0.0001f;
auto scale = glm::max(glm::length(vec), MIN_LINE_LENGTH);
auto dir = vec / scale;
auto orientation = glm::rotation(glm::vec3(0,0,-1), dir);
auto orientation = glm::rotation(glm::vec3(0.0f, 0.0f, -1.0f), dir);
transform.setRotation(orientation);
transform.setScale(scale);

View file

@ -9,6 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/transform.hpp>
#include "ModelOverlay.h"
#include <Rig.h>
@ -60,6 +63,15 @@ void ModelOverlay::update(float deltatime) {
_model->simulate(deltatime);
}
_isLoaded = _model->isActive();
if (isAnimatingSomething()) {
if (!jointsMapped()) {
mapAnimationJoints(_model->getJointNames());
}
animate();
}
}
bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) {
@ -172,6 +184,51 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
}
_updateModel = true;
}
auto animationSettings = properties["animationSettings"];
if (animationSettings.canConvert(QVariant::Map)) {
QVariantMap animationSettingsMap = animationSettings.toMap();
auto animationURL = animationSettingsMap["url"];
auto animationFPS = animationSettingsMap["fps"];
auto animationCurrentFrame = animationSettingsMap["currentFrame"];
auto animationFirstFrame = animationSettingsMap["firstFrame"];
auto animationLastFrame = animationSettingsMap["lastFrame"];
auto animationRunning = animationSettingsMap["running"];
auto animationLoop = animationSettingsMap["loop"];
auto animationHold = animationSettingsMap["hold"];
auto animationAllowTranslation = animationSettingsMap["allowTranslation"];
if (animationURL.canConvert(QVariant::Url)) {
_animationURL = animationURL.toUrl();
}
if (animationFPS.isValid()) {
_animationFPS = animationFPS.toFloat();
}
if (animationCurrentFrame.isValid()) {
_animationCurrentFrame = animationCurrentFrame.toFloat();
}
if (animationFirstFrame.isValid()) {
_animationFirstFrame = animationFirstFrame.toFloat();
}
if (animationLastFrame.isValid()) {
_animationLastFrame = animationLastFrame.toFloat();
}
if (animationRunning.canConvert(QVariant::Bool)) {
_animationRunning = animationRunning.toBool();
}
if (animationLoop.canConvert(QVariant::Bool)) {
_animationLoop = animationLoop.toBool();
}
if (animationHold.canConvert(QVariant::Bool)) {
_animationHold = animationHold.toBool();
}
if (animationAllowTranslation.canConvert(QVariant::Bool)) {
_animationAllowTranslation = animationAllowTranslation.toBool();
}
}
}
template <typename vectorType, typename itemType>
@ -259,6 +316,24 @@ QVariant ModelOverlay::getProperty(const QString& property) {
});
}
// animation properties
if (property == "animationSettings") {
QVariantMap animationSettingsMap;
animationSettingsMap["url"] = _animationURL;
animationSettingsMap["fps"] = _animationFPS;
animationSettingsMap["currentFrame"] = _animationCurrentFrame;
animationSettingsMap["firstFrame"] = _animationFirstFrame;
animationSettingsMap["lastFrame"] = _animationLastFrame;
animationSettingsMap["running"] = _animationRunning;
animationSettingsMap["loop"] = _animationLoop;
animationSettingsMap["hold"]= _animationHold;
animationSettingsMap["allowTranslation"] = _animationAllowTranslation;
return animationSettingsMap;
}
return Volume3DOverlay::getProperty(property);
}
@ -301,3 +376,134 @@ QString ModelOverlay::getName() const {
}
return QString("Overlay:") + getType() + ":" + _url.toString();
}
void ModelOverlay::animate() {
if (!_animation || !_animation->isLoaded() || !_model || !_model->isLoaded()) {
return;
}
QVector<JointData> jointsData;
const QVector<FBXAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy
int frameCount = frames.size();
if (frameCount <= 0) {
return;
}
if (!_lastAnimated) {
_lastAnimated = usecTimestampNow();
return;
}
auto now = usecTimestampNow();
auto interval = now - _lastAnimated;
_lastAnimated = now;
float deltaTime = (float)interval / (float)USECS_PER_SECOND;
_animationCurrentFrame += (deltaTime * _animationFPS);
int animationCurrentFrame = (int)(glm::floor(_animationCurrentFrame)) % frameCount;
if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) {
animationCurrentFrame = 0;
}
if (animationCurrentFrame == _lastKnownCurrentFrame) {
return;
}
_lastKnownCurrentFrame = animationCurrentFrame;
if (_jointMapping.size() != _model->getJointStateCount()) {
return;
}
QStringList animationJointNames = _animation->getGeometry().getJointNames();
auto& fbxJoints = _animation->getGeometry().joints;
auto& originalFbxJoints = _model->getFBXGeometry().joints;
auto& originalFbxIndices = _model->getFBXGeometry().jointIndices;
const QVector<glm::quat>& rotations = frames[_lastKnownCurrentFrame].rotations;
const QVector<glm::vec3>& translations = frames[_lastKnownCurrentFrame].translations;
jointsData.resize(_jointMapping.size());
for (int j = 0; j < _jointMapping.size(); j++) {
int index = _jointMapping[j];
if (index >= 0) {
glm::mat4 translationMat;
if (_animationAllowTranslation) {
if (index < translations.size()) {
translationMat = glm::translate(translations[index]);
}
} else if (index < animationJointNames.size()) {
QString jointName = fbxJoints[index].name;
if (originalFbxIndices.contains(jointName)) {
// Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get its translation.
int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual.
translationMat = glm::translate(originalFbxJoints[remappedIndex].translation);
}
}
glm::mat4 rotationMat;
if (index < rotations.size()) {
rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation);
} else {
rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation);
}
glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform *
rotationMat * fbxJoints[index].postTransform);
auto& jointData = jointsData[j];
jointData.translation = extractTranslation(finalMat);
jointData.translationSet = true;
jointData.rotation = glmExtractRotation(finalMat);
jointData.rotationSet = true;
}
}
// Set the data in the model
copyAnimationJointDataToModel(jointsData);
}
void ModelOverlay::mapAnimationJoints(const QStringList& modelJointNames) {
// if we don't have animation, or we're already joint mapped then bail early
if (!hasAnimation() || jointsMapped()) {
return;
}
if (!_animation || _animation->getURL() != _animationURL) {
_animation = DependencyManager::get<AnimationCache>()->getAnimation(_animationURL);
}
if (_animation && _animation->isLoaded()) {
QStringList animationJointNames = _animation->getJointNames();
if (modelJointNames.size() > 0 && animationJointNames.size() > 0) {
_jointMapping.resize(modelJointNames.size());
for (int i = 0; i < modelJointNames.size(); i++) {
_jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]);
}
_jointMappingCompleted = true;
_jointMappingURL = _animationURL;
}
}
}
void ModelOverlay::copyAnimationJointDataToModel(QVector<JointData> jointsData) {
if (!_model || !_model->isLoaded()) {
return;
}
// relay any inbound joint changes from scripts/animation/network to the model/rig
for (int index = 0; index < jointsData.size(); ++index) {
auto& jointData = jointsData[index];
_model->setJointRotation(index, true, jointData.rotation, 1.0f);
_model->setJointTranslation(index, true, jointData.translation, 1.0f);
}
_updateModel = true;
}

View file

@ -13,6 +13,7 @@
#define hifi_ModelOverlay_h
#include <Model.h>
#include <AnimationCache.h>
#include "Volume3DOverlay.h"
@ -45,6 +46,9 @@ public:
float getLoadPriority() const { return _loadPriority; }
bool hasAnimation() const { return !_animationURL.isEmpty(); }
bool jointsMapped() const { return _jointMappingURL == _animationURL && _jointMappingCompleted; }
protected:
Transform evalRenderTransform() override;
@ -53,6 +57,14 @@ protected:
template <typename vectorType, typename itemType>
vectorType mapJoints(mapFunction<itemType> function) const;
void animate();
void mapAnimationJoints(const QStringList& modelJointNames);
bool isAnimatingSomething() const {
return !_animationURL.isEmpty() && _animationRunning && _animationFPS != 0.0f;
}
void copyAnimationJointDataToModel(QVector<JointData> jointsData);
private:
ModelPointer _model;
@ -62,6 +74,25 @@ private:
bool _updateModel = { false };
bool _scaleToFit = { false };
float _loadPriority { 0.0f };
AnimationPointer _animation;
QUrl _animationURL;
float _animationFPS { 0.0f };
float _animationCurrentFrame { 0.0f };
bool _animationRunning { false };
bool _animationLoop { false };
float _animationFirstFrame { 0.0f };
float _animationLastFrame = { 0.0f };
bool _animationHold { false };
bool _animationAllowTranslation { false };
uint64_t _lastAnimated { 0 };
int _lastKnownCurrentFrame { -1 };
QUrl _jointMappingURL;
bool _jointMappingCompleted { false };
QVector<int> _jointMapping; // domain is index into model-joints, range is index into animation-joints
};
#endif // hifi_ModelOverlay_h

View file

@ -45,7 +45,19 @@ void Volume3DOverlay::setProperties(const QVariantMap& properties) {
}
if (dimensions.isValid()) {
setDimensions(vec3FromVariant(dimensions));
glm::vec3 scale = vec3FromVariant(dimensions);
// don't allow a zero or negative dimension component to reach the renderTransform
const float MIN_DIMENSION = 0.0001f;
if (scale.x < MIN_DIMENSION) {
scale.x = MIN_DIMENSION;
}
if (scale.y < MIN_DIMENSION) {
scale.y = MIN_DIMENSION;
}
if (scale.z < MIN_DIMENSION) {
scale.z = MIN_DIMENSION;
}
setDimensions(scale);
}
}

View file

@ -1570,13 +1570,17 @@ void EntityItem::updatePosition(const glm::vec3& value) {
}
void EntityItem::updateParentID(const QUuid& value) {
if (getParentID() != value) {
QUuid oldParentID = getParentID();
if (oldParentID != value) {
EntityTreePointer tree = getTree();
if (!oldParentID.isNull()) {
tree->removeFromChildrenOfAvatars(getThisPointer());
}
setParentID(value);
// children are forced to be kinematic
// may need to not collide with own avatar
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP);
EntityTreePointer tree = getTree();
if (tree) {
tree->addToNeedsParentFixupList(getThisPointer());
}

View file

@ -1148,11 +1148,8 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
} else if (!senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
failedAdd = true;
qCDebug(entities) << "User without 'rez rights' [" << senderNode->getUUID()
<< "] attempted to add an entity ID:" << entityItemID;
// FIXME after Cert ID property integrated
} else if (/*!properties.getCertificateID().isNull() && */!senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID()
<< "] attempted to add a certified entity with ID:" << entityItemID;
<< "] attempted to add an entity ID:" << entityItemID;
} else {
// this is a new entity... assign a new entityID
properties.setCreated(properties.getLastEdited());
@ -1329,6 +1326,13 @@ void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) {
}
}
void EntityTree::removeFromChildrenOfAvatars(EntityItemPointer entity) {
QUuid avatarID = entity->getParentID();
if (_childrenOfAvatars.contains(avatarID)) {
_childrenOfAvatars[avatarID].remove(entity->getID());
}
}
void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) {
QWriteLocker locker(&_needsParentFixupLock);
_needsParentFixup.append(entity);

View file

@ -254,6 +254,7 @@ public:
void knowAvatarID(QUuid avatarID) { _avatarIDs += avatarID; }
void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; }
void deleteDescendantsOfAvatar(QUuid avatarID);
void removeFromChildrenOfAvatars(EntityItemPointer entity);
void addToNeedsParentFixupList(EntityItemPointer entity);

View file

@ -334,10 +334,8 @@ void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime,
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) *
inputCalibrationData.defaultHeadMat;
controller::Pose hmdHeadPose = pose.transform(sensorToAvatar);
pose.valid = true;
_poseStateMap[controller::HEAD] = hmdHeadPose.postTransform(defaultHeadOffset);
_poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar);
}
void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,

View file

@ -255,6 +255,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
this.messageGrabEntity = false;
this.grabEntityProps = null;
this.shouldSendStart = false;
this.equipedWithSecondary = false;
this.parameters = makeDispatcherModuleParameters(
300,
@ -370,6 +371,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
return this.rawSecondaryValue < BUMPER_ON_VALUE;
};
this.secondarySmoothedSqueezed = function() {
return this.rawSecondaryValue > BUMPER_ON_VALUE;
};
this.chooseNearEquipHotspots = function(candidateEntityProps, controllerData) {
var _this = this;
var collectedHotspots = flatten(candidateEntityProps.map(function(props) {
@ -592,11 +597,13 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
// if the potentialHotspot os not cloneable and locked return null
if (potentialEquipHotspot &&
((this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) || this.messageGrabEntity)) {
(((this.triggerSmoothedSqueezed() || this.secondarySmoothedSqueezed()) && !this.waitForTriggerRelease) ||
this.messageGrabEntity)) {
this.grabbedHotspot = potentialEquipHotspot;
this.targetEntityID = this.grabbedHotspot.entityID;
this.startEquipEntity(controllerData);
this.messageGrabEnity = false;
this.equipedWithSecondary = this.secondarySmoothedSqueezed();
return makeRunningValues(true, [potentialEquipHotspot.entityID], []);
} else {
return makeRunningValues(false, [], []);
@ -627,7 +634,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
return this.checkNearbyHotspots(controllerData, deltaTime, timestamp);
}
if (controllerData.secondaryValues[this.hand]) {
if (controllerData.secondaryValues[this.hand] && !this.equipedWithSecondary) {
// this.secondaryReleased() will always be true when not depressed
// so we cannot simply rely on that for release - ensure that the
// trigger was first "prepared" by being pushed in before the release
@ -644,7 +651,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
var dropDetected = this.dropGestureProcess(deltaTime);
if (this.triggerSmoothedReleased()) {
if (this.triggerSmoothedReleased() || this.secondaryReleased()) {
if (this.shouldSendStart) {
// we don't want to send startEquip message until the trigger is released. otherwise,
// guns etc will fire right as they are equipped.
@ -653,6 +660,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
this.shouldSendStart = false;
}
this.waitForTriggerRelease = false;
if (this.secondaryReleased() && this.equipedWithSecondary) {
this.equipedWithSecondary = false;
}
}
if (dropDetected && this.prevDropDetected !== dropDetected) {

View file

@ -132,7 +132,7 @@ Script.include("/~/system/libraries/controllers.js");
this.updateLaserPointer = function(controllerData) {
var SEARCH_SPHERE_SIZE = 0.011;
var MIN_SPHERE_SIZE = 0.0005;
var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE);
var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE) * MyAvatar.sensorToWorldScale;
var dim = {x: radius, y: radius, z: radius};
var mode = "hold";
if (!this.distanceHolding && !this.distanceRotating) {
@ -424,7 +424,7 @@ Script.include("/~/system/libraries/controllers.js");
this.laserPointerOff();
return makeRunningValues(false, [], []);
}
this.intersectionDistance = controllerData.rayPicks[this.hand].distance;
this.updateLaserPointer(controllerData);
var otherModuleName =this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity";

View file

@ -335,7 +335,7 @@ Script.include("/~/system/libraries/controllers.js");
var SCALED_TABLET_MAX_HOVER_DISTANCE = TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor;
if (nearestStylusTarget && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE &&
nearestStylusTarget.distance < SCALED_TABLET_MAX_HOVER_DISTANCE) {
nearestStylusTarget.distance < SCALED_TABLET_MAX_HOVER_DISTANCE && !this.getOtherHandController().stylusTouchingTarget) {
this.requestTouchFocus(nearestStylusTarget);

View file

@ -306,7 +306,7 @@ Grabber.prototype.computeNewGrabPlane = function() {
};
Grabber.prototype.pressEvent = function(event) {
if (isInEditMode()) {
if (isInEditMode() || HMD.active) {
return;
}
@ -401,7 +401,7 @@ Grabber.prototype.pressEvent = function(event) {
};
Grabber.prototype.releaseEvent = function(event) {
if (event.isLeftButton!==true || event.isRightButton===true || event.isMiddleButton===true) {
if ((event.isLeftButton!==true || event.isRightButton===true || event.isMiddleButton===true) && !HMD.active) {
return;
}
@ -447,7 +447,7 @@ Grabber.prototype.moveEvent = function(event) {
// during the handling of the event, do as little as possible. We save the updated mouse position,
// and start a timer to react to the change. If more changes arrive before the timer fires, only
// the last update will be considered. This is done to avoid backing-up Qt's event queue.
if (!this.isGrabbing) {
if (!this.isGrabbing || HMD.active) {
return;
}
mouse.updateDrag(event);
@ -458,7 +458,7 @@ Grabber.prototype.moveEventProcess = function() {
this.moveEventTimer = null;
// see if something added/restored gravity
var entityProperties = Entities.getEntityProperties(this.entityID);
if (!entityProperties || !entityProperties.gravity) {
if (!entityProperties || !entityProperties.gravity || HMD.active) {
return;
}

View file

@ -1474,6 +1474,8 @@ function onFileSaveChanged(filename) {
}
function onFileOpenChanged(filename) {
// disconnect the event, otherwise the requests will stack up
Window.openFileChanged.disconnect(onFileOpenChanged);
var importURL = null;
if (filename !== "") {
importURL = "file:///" + filename;