Merge branch 'tablet-ui-edit-js' of github.com:sethalves/hifi into menuFix

This commit is contained in:
Dante Ruiz 2017-03-01 16:04:12 +00:00
commit e2479a78bd
22 changed files with 788 additions and 623 deletions

View file

@ -168,7 +168,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
QList<AvatarSharedPointer> avatarList;
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
int listItem = 0;
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
@ -176,7 +175,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
// but not have yet sent data that's linked to the node. Check for that case and don't
// consider those nodes.
if (otherNodeData) {
listItem++;
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
avatarList << otherAvatar;
avatarDataToNodes[otherAvatar] = otherNode;
@ -185,8 +183,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer();
ViewFrustum cameraView = nodeData->getViewFrustom();
std::priority_queue<AvatarPriority> sortedAvatars = AvatarData::sortAvatars(
avatarList, cameraView,
std::priority_queue<AvatarPriority> sortedAvatars;
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
[&](AvatarSharedPointer avatar)->uint64_t{
auto avatarNode = avatarDataToNodes[avatar];

Binary file not shown.

Binary file not shown.

View file

@ -14,7 +14,6 @@ import QtQuick.Controls.Styles 1.4
import "../styles-uit"
import "../controls-uit" as HifiControls
import "." as VrControls
FocusScope {
id: root
@ -119,14 +118,14 @@ FocusScope {
}
function showList() {
var r = 20//desktop.mapFromItem(root, 0, 0, root.width, root.height);
var y = 200;
var bottom = 0 + scrollView.height;
var r = mapFromItem(root, 0, 0, root.width, root.height);
var y = r.y + r.height;
var bottom = y + scrollView.height;
if (bottom > 720) {
y -= bottom - 720 + 8;
y -= bottom - tabletRoot.height + 8;
}
scrollView.x = 0;
scrollView.y = 0;
scrollView.x = r.x;
scrollView.y = y;
popup.visible = true;
popup.forceActiveFocus();
listView.currentIndex = root.currentIndex;
@ -141,6 +140,7 @@ FocusScope {
FocusScope {
id: popup
z: 12
parent: parent
anchors.fill: parent
visible: false

View file

@ -11,12 +11,15 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import "../../styles-uit"
import "../../controls-uit"
Rectangle {
id: newModelDialog
// width: parent.width
// height: parent.height
HifiConstants { id: hifi }
color: hifi.colors.baseGray;
property var eventBridge;
signal sendToScript(var message);
@ -32,6 +35,7 @@ Rectangle {
Text {
id: text1
text: qsTr("Model URL")
color: "#ffffff"
font.pixelSize: 12
}
@ -64,6 +68,7 @@ Rectangle {
CheckBox {
id: dynamic
text: qsTr("Dynamic")
}
Row {
@ -82,6 +87,7 @@ Rectangle {
Text {
id: text2
width: 160
color: "#ffffff"
text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic")
wrapMode: Text.WordWrap
font.pixelSize: 12
@ -97,30 +103,34 @@ Rectangle {
Text {
id: text3
text: qsTr("Automatic Collisions")
color: "#ffffff"
font.pixelSize: 12
}
ComboBox {
TabletComboBox {
id: collisionType
width: 200
z: 100
transformOrigin: Item.Center
model: ListModel {
id: collisionDropdown
ListElement { text: "No Collision" }
ListElement { text: "Basic - Whole model" }
ListElement { text: "Good - Sub-meshes" }
ListElement { text: "Exact - All polygons" }
}
model: ["No Collision",
"Basic - Whole model",
"Good - Sub-meshes",
"Exact - All polygons"]
}
Row {
id: row3
width: 200
height: 400
spacing: 5
anchors {
rightMargin: 15
}
Button {
id: button1
text: qsTr("Add")
z: -1
onClicked: {
newModelDialog.sendToScript({
method: "newModelDialogAdd",
@ -135,6 +145,7 @@ Rectangle {
Button {
id: button2
z: -1
text: qsTr("Cancel")
onClicked: {
newModelDialog.sendToScript({method: "newModelDialogCancel"})

View file

@ -351,7 +351,6 @@ void Avatar::simulate(float deltaTime, bool inView) {
_jointDataSimulationRate.increment();
_skeletonModel->simulate(deltaTime, true);
_skeletonModelSimulationRate.increment();
locationChanged(); // joints changed, so if there are any children, update them.
_hasNewJointData = false;
@ -367,8 +366,8 @@ void Avatar::simulate(float deltaTime, bool inView) {
} else {
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
_skeletonModel->simulate(deltaTime, false);
_skeletonModelSimulationRate.increment();
}
_skeletonModelSimulationRate.increment();
}
// update animation for display name fade in/out

View file

@ -157,15 +157,14 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
lock.unlock();
PerformanceTimer perfTimer("otherAvatars");
uint64_t startTime = usecTimestampNow();
auto avatarMap = getHashCopy();
QList<AvatarSharedPointer> avatarList = avatarMap.values();
ViewFrustum cameraView;
qApp->copyDisplayViewFrustum(cameraView);
std::priority_queue<AvatarPriority> sortedAvatars = AvatarData::sortAvatars(
avatarList, cameraView,
std::priority_queue<AvatarPriority> sortedAvatars;
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
[](AvatarSharedPointer avatar)->uint64_t{
return std::static_pointer_cast<Avatar>(avatar)->getLastRenderUpdateTime();
@ -194,10 +193,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
});
render::PendingChanges pendingChanges;
const uint64_t RENDER_UPDATE_BUDGET = 1500; // usec
const uint64_t MAX_UPDATE_BUDGET = 2000; // usec
uint64_t renderExpiry = startTime + RENDER_UPDATE_BUDGET;
uint64_t maxExpiry = startTime + MAX_UPDATE_BUDGET;
uint64_t startTime = usecTimestampNow();
const uint64_t UPDATE_BUDGET = 2000; // usec
uint64_t updateExpiry = startTime + UPDATE_BUDGET;
int numAvatarsUpdated = 0;
int numAVatarsNotUpdated = 0;
@ -223,7 +221,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY;
uint64_t now = usecTimestampNow();
if (now < renderExpiry) {
if (now < updateExpiry) {
// we're within budget
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
if (inView && avatar->hasNewJointData()) {
@ -232,21 +230,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
avatar->simulate(deltaTime, inView);
avatar->updateRenderItem(pendingChanges);
avatar->setLastRenderUpdateTime(startTime);
} else if (now < maxExpiry) {
// we've spent most of our time budget, but we still simulate() the avatar as it if were out of view
// --> some avatars may freeze until their priority trickles up
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
if (inView && avatar->hasNewJointData()) {
numAVatarsNotUpdated++;
}
avatar->simulate(deltaTime, false);
} else {
// we've spent ALL of our time budget --> bail on the rest of the avatar updates
// we've spent our full time budget --> bail on the rest of the avatar updates
// --> more avatars may freeze until their priority trickles up
// --> some scale or fade animations may glitch
// --> some avatar velocity measurements may be a little off
// HACK: no time simulate, but we will take the time to count how many were tragically missed
// no time simulate, but we take the time to count how many were tragically missed
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
if (!inView) {
break;

View file

@ -95,12 +95,6 @@ void CauterizedModel::createCollisionRenderItemSet() {
Model::createCollisionRenderItemSet();
}
// Called within Model::simulate call, below.
void CauterizedModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
Model::updateRig(deltaTime, parentTransform);
_needsUpdateClusterMatrices = true;
}
void CauterizedModel::updateClusterMatrices() {
PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices");

View file

@ -37,7 +37,6 @@ public:
void createVisibleRenderItemSet() override;
void createCollisionRenderItemSet() override;
virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override;
virtual void updateClusterMatrices() override;
void updateRenderItems() override;

View file

@ -260,6 +260,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
return AvatarData::toByteArrayStateful(dataDetail);
}
void MyAvatar::resetSensorsAndBody() {
qApp->getActiveDisplayPlugin()->resetSensors();
reset(true, false, true);
}
void MyAvatar::centerBody() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "centerBody");
@ -2483,6 +2488,45 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
}
}
bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& orientation) {
auto hipsIndex = getJointIndex("Hips");
if (index != hipsIndex) {
qWarning() << "Pinning is only supported for the hips joint at the moment.";
return false;
}
setPosition(position);
setOrientation(orientation);
_rig->setMaxHipsOffsetLength(0.05f);
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
if (it == _pinnedJoints.end()) {
_pinnedJoints.push_back(index);
}
return true;
}
bool MyAvatar::clearPinOnJoint(int index) {
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
if (it != _pinnedJoints.end()) {
_pinnedJoints.erase(it);
auto hipsIndex = getJointIndex("Hips");
if (index == hipsIndex) {
_rig->setMaxHipsOffsetLength(FLT_MAX);
}
return true;
}
return false;
}
float MyAvatar::getIKErrorOnLastSolve() const {
return _rig->getIKErrorOnLastSolve();
}
// thread-safe
void MyAvatar::addHoldAction(AvatarActionHold* holdAction) {
std::lock_guard<std::mutex> guard(_holdActionsMutex);

View file

@ -99,6 +99,7 @@ public:
void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
Q_INVOKABLE void resetSensorsAndBody();
Q_INVOKABLE void centerBody(); // thread-safe
Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe
@ -216,6 +217,11 @@ public:
virtual void clearJointData(int index) override;
virtual void clearJointsData() override;
Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation);
Q_INVOKABLE bool clearPinOnJoint(int index);
Q_INVOKABLE float getIKErrorOnLastSolve() const;
Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; }
@ -527,6 +533,8 @@ private:
bool didTeleport();
bool getIsAway() const { return _isAway; }
void setAway(bool value);
std::vector<int> _pinnedJoints;
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -166,7 +166,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
// evaluate AnimGraph animation and update jointStates.
CauterizedModel::updateRig(deltaTime, parentTransform);
Model::updateRig(deltaTime, parentTransform);
Rig::EyeParameters eyeParams;
eyeParams.worldHeadOrientation = headParams.worldHeadOrientation;
@ -179,7 +179,9 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_rig->updateFromEyeParameters(eyeParams);
} else {
CauterizedModel::updateRig(deltaTime, parentTransform);
// no need to call Model::updateRig() because otherAvatars get their joint state
// copied directly from AvtarData::_jointData (there are no Rig animations to blend)
_needsUpdateClusterMatrices = true;
// This is a little more work than we really want.
//

View file

@ -189,6 +189,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
}
}
}
_maxErrorOnLastSolve = maxError;
// finally set the relative rotation of each tip to agree with absolute target rotation
for (auto& target: targets) {
@ -268,13 +269,13 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
glm::quat deltaRotation;
if (targetType == IKTarget::Type::RotationAndPosition ||
targetType == IKTarget::Type::HipsRelativeRotationAndPosition) {
targetType == IKTarget::Type::HipsRelativeRotationAndPosition) {
// compute the swing that would get get tip closer
glm::vec3 targetLine = target.getTranslation() - jointPosition;
const float MIN_AXIS_LENGTH = 1.0e-4f;
RotationConstraint* constraint = getConstraint(pivotIndex);
if (constraint && constraint->isLowerSpine()) {
if (constraint && constraint->isLowerSpine() && tipIndex != _headIndex) {
// for these types of targets we only allow twist at the lower-spine
// (this prevents the hand targets from bending the spine too much and thereby driving the hips too far)
glm::vec3 twistAxis = absolutePoses[pivotIndex].trans() - absolutePoses[pivotsParentIndex].trans();
@ -300,8 +301,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
if (angle > MIN_ADJUSTMENT_ANGLE) {
// reduce angle by a fraction (for stability)
const float FRACTION = 0.5f;
angle *= FRACTION;
const float STABILITY_FRACTION = 0.5f;
angle *= STABILITY_FRACTION;
deltaRotation = glm::angleAxis(angle, axis);
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
@ -323,7 +324,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
float dotSign = copysignf(1.0f, twistPart.w);
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, FRACTION)) * deltaRotation;
const float LIMIT_LEAK_FRACTION = 0.1f;
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, LIMIT_LEAK_FRACTION)) * deltaRotation;
}
}
}
@ -486,7 +488,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
// measure new _hipsOffset for next frame
// by looking for discrepancies between where a targeted endEffector is
// and where it wants to be (after IK solutions are done)
glm::vec3 newHipsOffset = Vectors::ZERO;
// use weighted average between HMD and other targets
float HMD_WEIGHT = 10.0f;
float OTHER_WEIGHT = 1.0f;
float totalWeight = 0.0f;
glm::vec3 additionalHipsOffset = Vectors::ZERO;
for (auto& target: targets) {
int targetIndex = target.getIndex();
if (targetIndex == _headIndex && _headIndex != -1) {
@ -497,32 +505,61 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans();
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under);
additionalHipsOffset += (OTHER_WEIGHT * HEAD_OFFSET_SLAVE_FACTOR) * (under- actual);
totalWeight += OTHER_WEIGHT;
} else if (target.getType() == IKTarget::Type::HmdHead) {
// we want to shift the hips to bring the head to its designated position
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
_hipsOffset += target.getTranslation() - actual;
// and ignore all other targets
newHipsOffset = _hipsOffset;
break;
glm::vec3 thisOffset = target.getTranslation() - actual;
glm::vec3 futureHipsOffset = _hipsOffset + thisOffset;
if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) {
// it is imperative to shift the hips and bring the head to its designated position
// so we slam newHipsOffset here and ignore all other targets
additionalHipsOffset = futureHipsOffset - _hipsOffset;
totalWeight = 0.0f;
break;
} else {
additionalHipsOffset += HMD_WEIGHT * (target.getTranslation() - actual);
totalWeight += HMD_WEIGHT;
}
}
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans();
glm::vec3 targetPosition = target.getTranslation();
newHipsOffset += targetPosition - actualPosition;
additionalHipsOffset += OTHER_WEIGHT * (targetPosition - actualPosition);
totalWeight += OTHER_WEIGHT;
}
}
if (totalWeight > 1.0f) {
additionalHipsOffset /= totalWeight;
}
// Add downward pressure on the hips
additionalHipsOffset *= 0.95f;
additionalHipsOffset -= 1.0f;
// smooth transitions by relaxing _hipsOffset toward the new value
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f;
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f;
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
_hipsOffset += (newHipsOffset - _hipsOffset) * tau;
_hipsOffset += additionalHipsOffset * tau;
// clamp the hips offset
float hipsOffsetLength = glm::length(_hipsOffset);
if (hipsOffsetLength > _maxHipsOffsetLength) {
_hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength;
}
}
}
}
return _relativePoses;
}
void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) {
// manually adjust scale here
const float METERS_TO_CENTIMETERS = 100.0f;
_maxHipsOffsetLength = METERS_TO_CENTIMETERS * maxLength;
}
void AnimInverseKinematics::clearIKJointLimitHistory() {
for (auto& pair : _constraints) {
pair.second->clearHistory();
@ -740,7 +777,7 @@ void AnimInverseKinematics::initConstraints() {
stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST);
std::vector<float> minDots;
const float MAX_SPINE_SWING = PI / 14.0f;
const float MAX_SPINE_SWING = PI / 10.0f;
minDots.push_back(cosf(MAX_SPINE_SWING));
stConstraint->setSwingLimits(minDots);
if (0 == baseName.compare("Spine1", Qt::CaseSensitive)
@ -776,11 +813,11 @@ void AnimInverseKinematics::initConstraints() {
} else if (0 == baseName.compare("Head", Qt::CaseSensitive)) {
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot());
const float MAX_HEAD_TWIST = PI / 9.0f;
const float MAX_HEAD_TWIST = PI / 6.0f;
stConstraint->setTwistLimits(-MAX_HEAD_TWIST, MAX_HEAD_TWIST);
std::vector<float> minDots;
const float MAX_HEAD_SWING = PI / 10.0f;
const float MAX_HEAD_SWING = PI / 6.0f;
minDots.push_back(cosf(MAX_HEAD_SWING));
stConstraint->setSwingLimits(minDots);

View file

@ -39,6 +39,10 @@ public:
void clearIKJointLimitHistory();
void setMaxHipsOffsetLength(float maxLength);
float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; }
protected:
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
@ -83,6 +87,7 @@ protected:
// experimental data for moving hips during IK
glm::vec3 _hipsOffset { Vectors::ZERO };
float _maxHipsOffsetLength{ FLT_MAX };
int _headIndex { -1 };
int _hipsIndex { -1 };
int _hipsParentIndex { -1 };
@ -90,6 +95,8 @@ protected:
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
// during the the cyclic coordinate descent algorithm
int _maxTargetIndex { 0 };
float _maxErrorOnLastSolve { FLT_MAX };
};
#endif // hifi_AnimInverseKinematics_h

View file

@ -319,6 +319,39 @@ void Rig::clearIKJointLimitHistory() {
}
}
void Rig::setMaxHipsOffsetLength(float maxLength) {
_maxHipsOffsetLength = maxLength;
if (_animNode) {
_animNode->traverse([&](AnimNode::Pointer node) {
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
if (ikNode) {
ikNode->setMaxHipsOffsetLength(_maxHipsOffsetLength);
}
return true;
});
}
}
float Rig::getMaxHipsOffsetLength() const {
return _maxHipsOffsetLength;
}
float Rig::getIKErrorOnLastSolve() const {
float result = 0.0f;
if (_animNode) {
_animNode->traverse([&](AnimNode::Pointer node) {
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
if (ikNode) {
result = ikNode->getMaxErrorOnLastSolve();
}
return true;
});
}
return result;
}
int Rig::getJointParentIndex(int childIndex) const {
if (_animSkeleton && isIndexValid(childIndex)) {
return _animSkeleton->getParentIndex(childIndex);
@ -1274,39 +1307,50 @@ void Rig::copyJointsIntoJointData(QVector<JointData>& jointDataVec) const {
void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) {
PerformanceTimer perfTimer("copyJoints");
PROFILE_RANGE(simulation_animation_detail, "copyJoints");
if (_animSkeleton && jointDataVec.size() == (int)_internalPoseSet._relativePoses.size()) {
// make a vector of rotations in absolute-geometry-frame
const AnimPoseVec& absoluteDefaultPoses = _animSkeleton->getAbsoluteDefaultPoses();
std::vector<glm::quat> rotations;
rotations.reserve(absoluteDefaultPoses.size());
const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform));
for (int i = 0; i < jointDataVec.size(); i++) {
const JointData& data = jointDataVec.at(i);
if (data.rotationSet) {
// JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame
rotations.push_back(rigToGeometryRot * data.rotation);
} else {
rotations.push_back(absoluteDefaultPoses[i].rot());
}
}
if (!_animSkeleton) {
return;
}
if (jointDataVec.size() != (int)_internalPoseSet._relativePoses.size()) {
// animations haven't fully loaded yet.
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
}
// convert rotations from absolute to parent relative.
_animSkeleton->convertAbsoluteRotationsToRelative(rotations);
// store new relative poses
const AnimPoseVec& relativeDefaultPoses = _animSkeleton->getRelativeDefaultPoses();
for (int i = 0; i < jointDataVec.size(); i++) {
const JointData& data = jointDataVec.at(i);
_internalPoseSet._relativePoses[i].scale() = Vectors::ONE;
_internalPoseSet._relativePoses[i].rot() = rotations[i];
if (data.translationSet) {
// JointData translations are in scaled relative-frame so we scale back to regular relative-frame
_internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation;
} else {
_internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans();
}
// make a vector of rotations in absolute-geometry-frame
const AnimPoseVec& absoluteDefaultPoses = _animSkeleton->getAbsoluteDefaultPoses();
std::vector<glm::quat> rotations;
rotations.reserve(absoluteDefaultPoses.size());
const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform));
for (int i = 0; i < jointDataVec.size(); i++) {
const JointData& data = jointDataVec.at(i);
if (data.rotationSet) {
// JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame
rotations.push_back(rigToGeometryRot * data.rotation);
} else {
rotations.push_back(absoluteDefaultPoses[i].rot());
}
}
// convert rotations from absolute to parent relative.
_animSkeleton->convertAbsoluteRotationsToRelative(rotations);
// store new relative poses
const AnimPoseVec& relativeDefaultPoses = _animSkeleton->getRelativeDefaultPoses();
for (int i = 0; i < jointDataVec.size(); i++) {
const JointData& data = jointDataVec.at(i);
_internalPoseSet._relativePoses[i].scale() = Vectors::ONE;
_internalPoseSet._relativePoses[i].rot() = rotations[i];
if (data.translationSet) {
// JointData translations are in scaled relative-frame so we scale back to regular relative-frame
_internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation;
} else {
_internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans();
}
}
// build absolute poses and copy to externalPoseSet
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
QWriteLocker writeLock(&_externalPoseSetLock);
_externalPoseSet = _internalPoseSet;
}
void Rig::computeAvatarBoundingCapsule(

View file

@ -104,6 +104,10 @@ public:
void clearJointAnimationPriority(int index);
void clearIKJointLimitHistory();
void setMaxHipsOffsetLength(float maxLength);
float getMaxHipsOffsetLength() const;
float getIKErrorOnLastSolve() const;
int getJointParentIndex(int childIndex) const;
@ -318,6 +322,8 @@ protected:
bool _enabledAnimations { true };
mutable uint32_t _jointNameWarningCount { 0 };
float _maxHipsOffsetLength { 1.0f };
float _maxErrorOnLastSolve { 0.0f };
private:
QMap<int, StateHandler> _stateHandlers;

View file

@ -2324,61 +2324,57 @@ float AvatarData::_avatarSortCoefficientSize { 0.5f };
float AvatarData::_avatarSortCoefficientCenter { 0.25 };
float AvatarData::_avatarSortCoefficientAge { 1.0f };
std::priority_queue<AvatarPriority> AvatarData::sortAvatars(
QList<AvatarSharedPointer> avatarList,
const ViewFrustum& cameraView,
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
std::function<float(AvatarSharedPointer)> getBoundingRadius,
std::function<bool(AvatarSharedPointer)> shouldIgnore) {
void AvatarData::sortAvatars(
QList<AvatarSharedPointer> avatarList,
const ViewFrustum& cameraView,
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
std::function<float(AvatarSharedPointer)> getBoundingRadius,
std::function<bool(AvatarSharedPointer)> shouldIgnore) {
uint64_t startTime = usecTimestampNow();
PROFILE_RANGE(simulation, "sort");
uint64_t now = usecTimestampNow();
glm::vec3 frustumCenter = cameraView.getPosition();
const glm::vec3& forward = cameraView.getDirection();
for (int32_t i = 0; i < avatarList.size(); ++i) {
const auto& avatar = avatarList.at(i);
std::priority_queue<AvatarPriority> sortedAvatars;
{
PROFILE_RANGE(simulation, "sort");
for (int32_t i = 0; i < avatarList.size(); ++i) {
const auto& avatar = avatarList.at(i);
if (shouldIgnore(avatar)) {
continue;
}
// priority = weighted linear combination of:
// (a) apparentSize
// (b) proximity to center of view
// (c) time since last update
glm::vec3 avatarPosition = avatar->getPosition();
glm::vec3 offset = avatarPosition - frustumCenter;
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
// FIXME - AvatarData has something equivolent to this
float radius = getBoundingRadius(avatar);
const glm::vec3& forward = cameraView.getDirection();
float apparentSize = 2.0f * radius / distance;
float cosineAngle = glm::length(glm::dot(offset, forward) * forward) / distance;
float age = (float)(startTime - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND);
// NOTE: we are adding values of different units to get a single measure of "priority".
// Thus we multiply each component by a conversion "weight" that scales its units relative to the others.
// These weights are pure magic tuning and should be hard coded in the relation below,
// but are currently exposed for anyone who would like to explore fine tuning:
float priority = _avatarSortCoefficientSize * apparentSize
+ _avatarSortCoefficientCenter * cosineAngle
+ _avatarSortCoefficientAge * age;
// decrement priority of avatars outside keyhole
if (distance > cameraView.getCenterRadius()) {
if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) {
priority += OUT_OF_VIEW_PENALTY;
}
}
sortedAvatars.push(AvatarPriority(avatar, priority));
if (shouldIgnore(avatar)) {
continue;
}
// priority = weighted linear combination of:
// (a) apparentSize
// (b) proximity to center of view
// (c) time since last update
glm::vec3 avatarPosition = avatar->getPosition();
glm::vec3 offset = avatarPosition - frustumCenter;
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
// FIXME - AvatarData has something equivolent to this
float radius = getBoundingRadius(avatar);
float apparentSize = 2.0f * radius / distance;
float cosineAngle = glm::dot(offset, forward) / distance;
float age = (float)(now - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND);
// NOTE: we are adding values of different units to get a single measure of "priority".
// Thus we multiply each component by a conversion "weight" that scales its units relative to the others.
// These weights are pure magic tuning and should be hard coded in the relation below,
// but are currently exposed for anyone who would like to explore fine tuning:
float priority = _avatarSortCoefficientSize * apparentSize
+ _avatarSortCoefficientCenter * cosineAngle
+ _avatarSortCoefficientAge * age;
// decrement priority of avatars outside keyhole
if (distance > cameraView.getCenterRadius()) {
if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) {
priority += OUT_OF_VIEW_PENALTY;
}
}
sortedAvatarsOut.push(AvatarPriority(avatar, priority));
}
return sortedAvatars;
}
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {

View file

@ -597,9 +597,10 @@ public:
static const float OUT_OF_VIEW_PENALTY;
static std::priority_queue<AvatarPriority> sortAvatars(
static void sortAvatars(
QList<AvatarSharedPointer> avatarList,
const ViewFrustum& cameraView,
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
std::function<float(AvatarSharedPointer)> getBoundingRadius,
std::function<bool(AvatarSharedPointer)> shouldIgnore);

View file

@ -356,8 +356,15 @@ void TabletProxy::pushOntoStack(const QVariant& path) {
} else {
qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot doesn't have child stack";
}
} else if (_desktopWindow) {
auto stack = _desktopWindow->asQuickItem()->findChild<QQuickItem*>("stack");
if (stack) {
QMetaObject::invokeMethod(stack, "pushSource", Q_ARG(const QVariant&, path));
} else {
qCDebug(scriptengine) << "tablet cannot push QML because _desktopWindow doesn't have child stack";
}
} else {
qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot is null";
qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot or _desktopWindow is null";
}
}
@ -367,10 +374,17 @@ void TabletProxy::popFromStack() {
if (stack) {
QMetaObject::invokeMethod(stack, "popSource");
} else {
qCDebug(scriptengine) << "tablet cannot pop QML because _qmlTabletRoot doesn't have child stack";
qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot doesn't have child stack";
}
} else if (_desktopWindow) {
auto stack = _desktopWindow->asQuickItem()->findChild<QQuickItem*>("stack");
if (stack) {
QMetaObject::invokeMethod(stack, "popSource");
} else {
qCDebug(scriptengine) << "tablet cannot pop QML because _desktopWindow doesn't have child stack";
}
} else {
qCDebug(scriptengine) << "tablet cannot pop QML because _qmlTabletRoot is null";
qCDebug(scriptengine) << "tablet cannot pop QML because _qmlTabletRoot or _desktopWindow is null";
}
}

Binary file not shown.

View file

@ -17,12 +17,20 @@ var NUMBER_OF_STEPS = 6;
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx");
var SEAT_MODEL_URL = Script.resolvePath("../assets/models/teleport-seat.fbx");
var TARGET_MODEL_DIMENSIONS = {
x: 1.15,
y: 0.5,
z: 1.15
};
var COLORS_TELEPORT_SEAT = {
red: 255,
green: 0,
blue: 170
}
var COLORS_TELEPORT_CAN_TELEPORT = {
red: 97,
green: 247,
@ -35,29 +43,30 @@ var COLORS_TELEPORT_CANNOT_TELEPORT = {
blue: 141
};
var COLORS_TELEPORT_TOO_CLOSE = {
var COLORS_TELEPORT_CANCEL = {
red: 255,
green: 184,
blue: 73
};
var TELEPORT_CANCEL_RANGE = 1;
var USE_COOL_IN = true;
var COOL_IN_DURATION = 500;
const handInfo = {
right: {
controllerInput: Controller.Standard.RightHand
},
left: {
controllerInput: Controller.Standard.LeftHand
}
};
function ThumbPad(hand) {
this.hand = hand;
var _thisPad = this;
this.buttonPress = function(value) {
_thisPad.buttonValue = value;
if (value === 0) {
if (activationTimeout !== null) {
Script.clearTimeout(activationTimeout);
activationTimeout = null;
}
}
};
}
@ -67,7 +76,6 @@ function Trigger(hand) {
this.buttonPress = function(value) {
_this.buttonValue = value;
};
this.down = function() {
@ -78,347 +86,224 @@ function Trigger(hand) {
var coolInTimeout = null;
var TELEPORTER_STATES = {
IDLE: 'idle',
COOL_IN: 'cool_in',
TARGETTING_INVALID: 'targetting_invalid',
}
var TARGET = {
NONE: 'none', // Not currently targetting anything
INVISIBLE: 'invisible', // The current target is an invvsible surface
INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.)
SURFACE: 'surface', // The current target is a valid surface
SEAT: 'seat', // The current target is a seat
}
function Teleporter() {
var _this = this;
this.intersection = null;
this.rightOverlayLine = null;
this.leftOverlayLine = null;
this.targetOverlay = null;
this.cancelOverlay = null;
this.updateConnected = null;
this.smoothArrivalInterval = null;
this.teleportHand = null;
this.tooClose = false;
this.inCoolIn = false;
this.active = false;
this.state = TELEPORTER_STATES.IDLE;
this.currentTarget = TARGET.INVALID;
this.initialize = function() {
this.createMappings();
this.overlayLines = {
left: null,
right: null,
};
this.updateConnected = null;
this.activeHand = null;
this.createMappings = function() {
teleporter.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
teleporter.teleportMappingInternal = Controller.newMapping(teleporter.telporterMappingInternalName);
this.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
this.teleportMappingInternal = Controller.newMapping(this.telporterMappingInternalName);
Controller.enableMapping(teleporter.telporterMappingInternalName);
// Setup overlays
this.cancelOverlay = Overlays.addOverlay("model", {
url: TOO_CLOSE_MODEL_URL,
dimensions: TARGET_MODEL_DIMENSIONS,
visible: false
});
this.targetOverlay = Overlays.addOverlay("model", {
url: TARGET_MODEL_URL,
dimensions: TARGET_MODEL_DIMENSIONS,
visible: false
});
this.seatOverlay = Overlays.addOverlay("model", {
url: SEAT_MODEL_URL,
dimensions: TARGET_MODEL_DIMENSIONS,
visible: false
});
this.enableMappings = function() {
Controller.enableMapping(this.telporterMappingInternalName);
};
this.disableMappings = function() {
Controller.disableMapping(teleporter.telporterMappingInternalName);
};
this.enterTeleportMode = function(hand) {
this.cleanup = function() {
this.disableMappings();
Overlays.deleteOverlay(this.targetOverlay);
this.targetOverlay = null;
Overlays.deleteOverlay(this.cancelOverlay);
this.cancelOverlay = null;
Overlays.deleteOverlay(this.seatOverlay);
this.seatOverlay = null;
this.deleteOverlayBeams();
if (this.updateConnected === true) {
Script.update.disconnect(this, this.update);
}
};
this.enterTeleportMode = function(hand) {
if (inTeleportMode === true) {
return;
}
if (isDisabled === 'both') {
if (isDisabled === 'both' || isDisabled === hand) {
return;
}
inTeleportMode = true;
this.inCoolIn = true;
if (coolInTimeout !== null) {
Script.clearTimeout(coolInTimeout);
}
this.state = TELEPORTER_STATES.COOL_IN;
coolInTimeout = Script.setTimeout(function() {
_this.inCoolIn = false;
if (_this.state === TELEPORTER_STATES.COOL_IN) {
_this.state = TELEPORTER_STATES.TARGETTING;
}
}, COOL_IN_DURATION)
if (this.smoothArrivalInterval !== null) {
Script.clearInterval(this.smoothArrivalInterval);
}
if (activationTimeout !== null) {
Script.clearInterval(activationTimeout);
}
this.teleportHand = hand;
this.initialize();
Script.update.connect(this.update);
this.activeHand = hand;
this.enableMappings();
Script.update.connect(this, this.update);
this.updateConnected = true;
};
this.createTargetOverlay = function(visible) {
if (visible == undefined) {
visible = true;
}
if (_this.targetOverlay !== null) {
return;
}
var targetOverlayProps = {
url: TARGET_MODEL_URL,
dimensions: TARGET_MODEL_DIMENSIONS,
visible: visible
};
_this.targetOverlay = Overlays.addOverlay("model", targetOverlayProps);
};
this.createCancelOverlay = function(visible) {
if (visible == undefined) {
visible = true;
}
if (_this.cancelOverlay !== null) {
return;
}
var cancelOverlayProps = {
url: TOO_CLOSE_MODEL_URL,
dimensions: TARGET_MODEL_DIMENSIONS,
visible: visible
};
_this.cancelOverlay = Overlays.addOverlay("model", cancelOverlayProps);
};
this.deleteCancelOverlay = function() {
if (this.cancelOverlay === null) {
return;
}
Overlays.deleteOverlay(this.cancelOverlay);
this.cancelOverlay = null;
}
this.hideCancelOverlay = function() {
if (this.cancelOverlay === null) {
return;
}
this.intersection = null;
Overlays.editOverlay(this.cancelOverlay, { visible: false });
}
this.showCancelOverlay = function() {
if (this.cancelOverlay === null) {
return this.createCancelOverlay();
}
Overlays.editOverlay(this.cancelOverlay, { visible: true });
}
this.deleteTargetOverlay = function() {
if (this.targetOverlay === null) {
return;
}
Overlays.deleteOverlay(this.targetOverlay);
this.intersection = null;
this.targetOverlay = null;
}
this.hideTargetOverlay = function() {
if (this.targetOverlay === null) {
return;
}
this.intersection = null;
Overlays.editOverlay(this.targetOverlay, { visible: false });
}
this.showTargetOverlay = function() {
if (this.targetOverlay === null) {
return this.createTargetOverlay();
}
Overlays.editOverlay(this.targetOverlay, { visible: true });
}
this.turnOffOverlayBeams = function() {
this.rightOverlayOff();
this.leftOverlayOff();
}
this.exitTeleportMode = function(value) {
if (activationTimeout !== null) {
Script.clearTimeout(activationTimeout);
activationTimeout = null;
}
if (this.updateConnected === true) {
Script.update.disconnect(this.update);
Script.update.disconnect(this, this.update);
}
this.disableMappings();
this.turnOffOverlayBeams();
this.deleteOverlayBeams();
this.hideTargetOverlay();
this.hideCancelOverlay();
this.updateConnected = null;
this.inCoolIn = false;
this.state = TELEPORTER_STATES.IDLE;
inTeleportMode = false;
};
this.update = function() {
if (isDisabled === 'both') {
return;
}
if (teleporter.teleportHand === 'left') {
if (isDisabled === 'left') {
return;
this.deleteOverlayBeams = function() {
for (key in this.overlayLines) {
if (this.overlayLines[key] !== null) {
Overlays.deleteOverlay(this.overlayLines[key]);
this.overlayLines[key] = null;
}
teleporter.leftRay();
if ((leftPad.buttonValue === 0) && inTeleportMode === true) {
if (_this.inCoolIn === true) {
_this.exitTeleportMode();
_this.hideTargetOverlay();
_this.hideCancelOverlay();
} else {
_this.teleport();
}
return;
}
} else {
if (isDisabled === 'right') {
return;
}
teleporter.rightRay();
if ((rightPad.buttonValue === 0) && inTeleportMode === true) {
if (_this.inCoolIn === true) {
_this.exitTeleportMode();
_this.hideTargetOverlay();
_this.hideCancelOverlay();
} else {
_this.teleport();
}
return;
}
}
};
this.rightRay = function() {
var pose = Controller.getPoseValue(Controller.Standard.RightHand);
var rightPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
var rightRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {
x: 1,
y: 0,
z: 0
}));
var rightPickRay = {
origin: rightPosition,
direction: Quat.getUp(rightRotation),
};
this.rightPickRay = rightPickRay;
var location = Vec3.sum(rightPickRay.origin, Vec3.multiply(rightPickRay.direction, 50));
var rightIntersection = Entities.findRayIntersection(teleporter.rightPickRay, true, [], [this.targetEntity], true, true);
if (rightIntersection.intersects) {
if (this.tooClose === true) {
this.hideTargetOverlay();
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
if (this.cancelOverlay !== null) {
this.updateCancelOverlay(rightIntersection);
} else {
this.createCancelOverlay();
}
} else {
if (this.inCoolIn === true) {
this.hideTargetOverlay();
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
if (this.cancelOverlay !== null) {
this.updateCancelOverlay(rightIntersection);
} else {
this.createCancelOverlay();
}
} else {
this.hideCancelOverlay();
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
if (this.targetOverlay !== null) {
this.updateTargetOverlay(rightIntersection);
} else {
this.createTargetOverlay();
}
}
}
} else {
this.hideTargetOverlay();
this.rightLineOn(rightPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT);
}
}
this.update = function() {
if (_this.state === TELEPORTER_STATES.IDLE) {
return;
}
this.leftRay = function() {
var pose = Controller.getPoseValue(Controller.Standard.LeftHand);
var leftPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
var leftRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
// Get current hand pose information so that we can get the direction of the teleport beam
var pose = Controller.getPoseValue(handInfo[_this.activeHand].controllerInput);
var handPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
var handRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {
x: 1,
y: 0,
z: 0
}));
var leftPickRay = {
origin: leftPosition,
direction: Quat.getUp(leftRotation),
var pickRay = {
origin: handPosition,
direction: Quat.getUp(handRotation),
};
this.leftPickRay = leftPickRay;
// We do up to 2 ray picks to find a teleport location.
// There are 2 types of teleport locations we are interested in:
// 1. A visible floor. This can be any entity surface that points within some degree of "up"
// 2. A seat. The seat can be visible or invisible.
//
// * In the first pass we pick against visible and invisible entities so that we can find invisible seats.
// We might hit an invisible entity that is not a seat, so we need to do a second pass.
// * In the second pass we pick against visible entities only.
//
var intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity], false, true);
var location = Vec3.sum(MyAvatar.position, Vec3.multiply(leftPickRay.direction, 50));
var leftIntersection = Entities.findRayIntersection(teleporter.leftPickRay, true, [], [this.targetEntity], true, true);
if (leftIntersection.intersects) {
if (this.tooClose === true) {
this.hideTargetOverlay();
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
if (this.cancelOverlay !== null) {
this.updateCancelOverlay(leftIntersection);
} else {
this.createCancelOverlay();
}
} else {
if (this.inCoolIn === true) {
this.hideTargetOverlay();
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
if (this.cancelOverlay !== null) {
this.updateCancelOverlay(leftIntersection);
} else {
this.createCancelOverlay();
}
} else {
this.hideCancelOverlay();
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
if (this.targetOverlay !== null) {
this.updateTargetOverlay(leftIntersection);
} else {
this.createTargetOverlay();
}
}
}
} else {
var teleportLocationType = getTeleportTargetType(intersection);
if (teleportLocationType === TARGET.INVISIBLE) {
intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity], true, true);
teleportLocationType = getTeleportTargetType(intersection);
}
if (teleportLocationType === TARGET.NONE) {
this.hideTargetOverlay();
this.leftLineOn(leftPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT);
this.hideCancelOverlay();
this.hideSeatOverlay();
var farPosition = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, 50));
this.updateLineOverlay(_this.activeHand, pickRay.origin, farPosition, COLORS_TELEPORT_CANNOT_TELEPORT);
} else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) {
this.hideTargetOverlay();
this.hideSeatOverlay();
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL);
this.updateDestinationOverlay(this.cancelOverlay, intersection);
} else if (teleportLocationType === TARGET.SURFACE) {
if (this.state === TELEPORTER_STATES.COOL_IN) {
this.hideTargetOverlay();
this.hideSeatOverlay();
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL);
this.updateDestinationOverlay(this.cancelOverlay, intersection);
} else {
this.hideCancelOverlay();
this.hideSeatOverlay();
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
this.updateDestinationOverlay(this.targetOverlay, intersection);
}
} else if (teleportLocationType === TARGET.SEAT) {
this.hideCancelOverlay();
this.hideTargetOverlay();
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_SEAT);
this.updateDestinationOverlay(this.seatOverlay, intersection);
}
if (((_this.activeHand == 'left' ? leftPad : rightPad).buttonValue === 0) && inTeleportMode === true) {
this.exitTeleportMode();
this.hideCancelOverlay();
this.hideTargetOverlay();
this.hideSeatOverlay();
if (teleportLocationType === TARGET.NONE || teleportLocationType === TARGET.INVALID || this.state === TELEPORTER_STATES.COOL_IN) {
// Do nothing
} else if (teleportLocationType === TARGET.SEAT) {
Entities.callEntityMethod(intersection.entityID, 'sit');
} else if (teleportLocationType === TARGET.SURFACE) {
var offset = getAvatarFootOffset();
intersection.intersection.y += offset;
MyAvatar.position = intersection.intersection;
HMD.centerUI();
}
}
};
this.rightLineOn = function(closePoint, farPoint, color) {
if (this.rightOverlayLine === null) {
this.updateLineOverlay = function(hand, closePoint, farPoint, color) {
if (this.overlayLines[hand] === null) {
var lineProperties = {
start: closePoint,
end: farPoint,
@ -431,10 +316,10 @@ function Teleporter() {
glow: 1.0
};
this.rightOverlayLine = Overlays.addOverlay("line3d", lineProperties);
this.overlayLines[hand] = Overlays.addOverlay("line3d", lineProperties);
} else {
var success = Overlays.editOverlay(this.rightOverlayLine, {
var success = Overlays.editOverlay(this.overlayLines[hand], {
start: closePoint,
end: farPoint,
color: color
@ -442,47 +327,19 @@ function Teleporter() {
}
};
this.leftLineOn = function(closePoint, farPoint, color) {
if (this.leftOverlayLine === null) {
var lineProperties = {
ignoreRayIntersection: true,
start: closePoint,
end: farPoint,
color: color,
visible: true,
alpha: 1,
solid: true,
glow: 1.0,
drawInFront: true
};
this.leftOverlayLine = Overlays.addOverlay("line3d", lineProperties);
} else {
var success = Overlays.editOverlay(this.leftOverlayLine, {
start: closePoint,
end: farPoint,
color: color
});
}
};
this.rightOverlayOff = function() {
if (this.rightOverlayLine !== null) {
Overlays.deleteOverlay(this.rightOverlayLine);
this.rightOverlayLine = null;
}
this.hideCancelOverlay = function() {
Overlays.editOverlay(this.cancelOverlay, { visible: false });
};
this.leftOverlayOff = function() {
if (this.leftOverlayLine !== null) {
Overlays.deleteOverlay(this.leftOverlayLine);
this.leftOverlayLine = null;
}
this.hideTargetOverlay = function() {
Overlays.editOverlay(this.targetOverlay, { visible: false });
};
this.updateTargetOverlay = function(intersection) {
_this.intersection = intersection;
this.hideSeatOverlay = function() {
Overlays.editOverlay(this.seatOverlay, { visible: false });
};
this.updateDestinationOverlay = function(overlayID, intersection) {
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
var euler = Quat.safeEulerAngles(rotation);
var position = {
@ -491,115 +348,15 @@ function Teleporter() {
z: intersection.intersection.z
};
this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal);
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
Overlays.editOverlay(this.targetOverlay, {
Overlays.editOverlay(overlayID, {
visible: true,
position: position,
rotation: towardUs
});
};
this.updateCancelOverlay = function(intersection) {
_this.intersection = intersection;
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
var euler = Quat.safeEulerAngles(rotation);
var position = {
x: intersection.intersection.x,
y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2,
z: intersection.intersection.z
};
this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal);
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
Overlays.editOverlay(this.cancelOverlay, {
visible: true,
position: position,
rotation: towardUs
});
};
this.triggerHaptics = function() {
var hand = this.teleportHand === 'left' ? 0 : 1;
var haptic = Controller.triggerShortHapticPulse(0.2, hand);
};
this.teleport = function(value) {
if (value === undefined) {
this.exitTeleportMode();
}
if (this.intersection !== null) {
if (this.tooClose === true) {
this.exitTeleportMode();
this.hideCancelOverlay();
return;
}
var offset = getAvatarFootOffset();
this.intersection.intersection.y += offset;
this.exitTeleportMode();
// Disable smooth arrival, possibly temporarily
//this.smoothArrival();
MyAvatar.position = _this.intersection.intersection;
_this.hideTargetOverlay();
_this.hideCancelOverlay();
HMD.centerUI();
}
};
this.findMidpoint = function(start, end) {
var xy = Vec3.sum(start, end);
var midpoint = Vec3.multiply(0.5, xy);
return midpoint
};
this.getArrivalPoints = function(startPoint, endPoint) {
var arrivalPoints = [];
var i;
var lastPoint;
for (i = 0; i < NUMBER_OF_STEPS; i++) {
if (i === 0) {
lastPoint = startPoint;
}
var newPoint = _this.findMidpoint(lastPoint, endPoint);
lastPoint = newPoint;
arrivalPoints.push(newPoint);
}
arrivalPoints.push(endPoint);
return arrivalPoints;
};
this.smoothArrival = function() {
_this.arrivalPoints = _this.getArrivalPoints(MyAvatar.position, _this.intersection.intersection);
_this.smoothArrivalInterval = Script.setInterval(function() {
if (_this.arrivalPoints.length === 0) {
Script.clearInterval(_this.smoothArrivalInterval);
HMD.centerUI();
return;
}
var landingPoint = _this.arrivalPoints.shift();
MyAvatar.position = landingPoint;
if (_this.arrivalPoints.length === 1 || _this.arrivalPoints.length === 0) {
_this.hideTargetOverlay();
_this.hideCancelOverlay();
}
}, SMOOTH_ARRIVAL_SPACING);
}
this.createTargetOverlay(false);
this.createCancelOverlay(false);
}
//related to repositioning the avatar after you teleport
@ -611,20 +368,16 @@ function getAvatarFootOffset() {
var jointName = d.joint;
if (jointName === "RightUpLeg") {
upperLeg = d.translation.y;
}
if (jointName === "RightLeg") {
} else if (jointName === "RightLeg") {
lowerLeg = d.translation.y;
}
if (jointName === "RightFoot") {
} else if (jointName === "RightFoot") {
foot = d.translation.y;
}
if (jointName === "RightToeBase") {
} else if (jointName === "RightToeBase") {
toe = d.translation.y;
}
if (jointName === "RightToe_End") {
} else if (jointName === "RightToe_End") {
toeTop = d.translation.y;
}
})
});
var offset = upperLeg + lowerLeg + foot + toe + toeTop;
offset = offset / 100;
@ -655,7 +408,6 @@ var rightTrigger = new Trigger('right');
var mappingName, teleportMapping;
var activationTimeout = null;
var TELEPORT_DELAY = 0;
function isMoving() {
@ -668,17 +420,44 @@ function isMoving() {
}
};
function parseJSON(json) {
try {
return JSON.parse(json);
} catch (e) {
return undefined;
}
}
// When determininig whether you can teleport to a location, the normal of the
// point that is being intersected with is looked at. If this normal is more
// than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then
// you can't teleport there.
var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
function isValidTeleportLocation(position, surfaceNormal) {
const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
function getTeleportTargetType(intersection) {
if (!intersection.intersects) {
return TARGET.NONE;
}
var props = Entities.getEntityProperties(intersection.entityID, ['userData', 'visible']);
var data = parseJSON(props.userData);
if (data !== undefined && data.seat !== undefined) {
return TARGET.SEAT;
}
if (!props.visible) {
return TARGET.INVISIBLE;
}
var surfaceNormal = intersection.surfaceNormal;
var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z);
var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI);
return angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
Vec3.distance(MyAvatar.position, position) <= TELEPORT_CANCEL_RANGE;
if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
Vec3.distance(MyAvatar.position, intersection.intersection) <= TELEPORT_CANCEL_RANGE) {
return TARGET.INVALID;
} else {
return TARGET.SURFACE;
}
};
function registerMappings() {
@ -695,20 +474,13 @@ function registerMappings() {
if (isDisabled === 'left' || isDisabled === 'both') {
return;
}
if (activationTimeout !== null) {
return
}
if (leftTrigger.down()) {
return;
}
if (isMoving() === true) {
return;
}
activationTimeout = Script.setTimeout(function() {
Script.clearTimeout(activationTimeout);
activationTimeout = null;
teleporter.enterTeleportMode('left')
}, TELEPORT_DELAY)
teleporter.enterTeleportMode('left')
return;
});
teleportMapping.from(Controller.Standard.RightPrimaryThumb)
@ -716,9 +488,6 @@ function registerMappings() {
if (isDisabled === 'right' || isDisabled === 'both') {
return;
}
if (activationTimeout !== null) {
return
}
if (rightTrigger.down()) {
return;
}
@ -726,11 +495,7 @@ function registerMappings() {
return;
}
activationTimeout = Script.setTimeout(function() {
teleporter.enterTeleportMode('right')
Script.clearTimeout(activationTimeout);
activationTimeout = null;
}, TELEPORT_DELAY)
teleporter.enterTeleportMode('right')
return;
});
};
@ -741,18 +506,11 @@ var teleporter = new Teleporter();
Controller.enableMapping(mappingName);
Script.scriptEnding.connect(cleanup);
function cleanup() {
teleportMapping.disable();
teleporter.disableMappings();
teleporter.deleteTargetOverlay();
teleporter.deleteCancelOverlay();
teleporter.turnOffOverlayBeams();
if (teleporter.updateConnected !== null) {
Script.update.disconnect(teleporter.update);
}
teleporter.cleanup();
}
Script.scriptEnding.connect(cleanup);
var isDisabled = false;
var handleHandMessages = function(channel, message, sender) {

View file

@ -0,0 +1,257 @@
(function() {
Script.include("/~/system/libraries/utils.js");
var SETTING_KEY = "com.highfidelity.avatar.isSitting";
var ROLE = "fly";
var ANIMATION_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/animations/sitting_idle.fbx";
var ANIMATION_FPS = 30;
var ANIMATION_FIRST_FRAME = 1;
var ANIMATION_LAST_FRAME = 10;
var RELEASE_KEYS = ['w', 'a', 's', 'd', 'UP', 'LEFT', 'DOWN', 'RIGHT'];
var RELEASE_TIME = 500; // ms
var RELEASE_DISTANCE = 0.2; // meters
var MAX_IK_ERROR = 20;
var DESKTOP_UI_CHECK_INTERVAL = 250;
var DESKTOP_MAX_DISTANCE = 5;
var SIT_DELAY = 25
this.entityID = null;
this.timers = {};
this.animStateHandlerID = null;
this.preload = function(entityID) {
this.entityID = entityID;
}
this.unload = function() {
if (MyAvatar.sessionUUID === this.getSeatUser()) {
this.sitUp(this.entityID);
}
if (this.interval) {
Script.clearInterval(this.interval);
this.interval = null;
}
this.cleanupOverlay();
}
this.setSeatUser = function(user) {
var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData;
userData = JSON.parse(userData);
if (user) {
userData.seat.user = user;
} else {
delete userData.seat.user;
}
Entities.editEntity(this.entityID, {
userData: JSON.stringify(userData)
});
}
this.getSeatUser = function() {
var properties = Entities.getEntityProperties(this.entityID, ["userData", "position"]);
var userData = JSON.parse(properties.userData);
if (userData.seat.user && userData.seat.user !== MyAvatar.sessionUUID) {
var avatar = AvatarList.getAvatar(userData.seat.user);
if (avatar && Vec3.distance(avatar.position, properties.position) > RELEASE_DISTANCE) {
return null;
}
}
return userData.seat.user;
}
this.checkSeatForAvatar = function() {
var seatUser = this.getSeatUser();
var avatarIdentifiers = AvatarList.getAvatarIdentifiers();
for (var i in avatarIdentifiers) {
var avatar = AvatarList.getAvatar(avatarIdentifiers[i]);
if (avatar && avatar.sessionUUID === seatUser) {
return true;
}
}
return false;
}
this.sitDown = function() {
if (this.checkSeatForAvatar()) {
print("Someone is already sitting in that chair.");
return;
}
this.setSeatUser(MyAvatar.sessionUUID);
var previousValue = Settings.getValue(SETTING_KEY);
Settings.setValue(SETTING_KEY, this.entityID);
if (previousValue === "") {
MyAvatar.characterControllerEnabled = false;
MyAvatar.hmdLeanRecenterEnabled = false;
MyAvatar.overrideRoleAnimation(ROLE, ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME);
MyAvatar.resetSensorsAndBody();
}
var that = this;
Script.setTimeout(function() {
var properties = Entities.getEntityProperties(that.entityID, ["position", "rotation"]);
var index = MyAvatar.getJointIndex("Hips");
MyAvatar.pinJoint(index, properties.position, properties.rotation);
that.animStateHandlerID = MyAvatar.addAnimationStateHandler(function(properties) {
return { headType: 0 };
}, ["headType"]);
Script.update.connect(that, that.update);
Controller.keyPressEvent.connect(that, that.keyPressed);
Controller.keyReleaseEvent.connect(that, that.keyReleased);
for (var i in RELEASE_KEYS) {
Controller.captureKeyEvents({ text: RELEASE_KEYS[i] });
}
}, SIT_DELAY);
}
this.sitUp = function() {
this.setSeatUser(null);
if (Settings.getValue(SETTING_KEY) === this.entityID) {
MyAvatar.restoreRoleAnimation(ROLE);
MyAvatar.characterControllerEnabled = true;
MyAvatar.hmdLeanRecenterEnabled = true;
var index = MyAvatar.getJointIndex("Hips");
MyAvatar.clearPinOnJoint(index);
MyAvatar.resetSensorsAndBody();
Script.setTimeout(function() {
MyAvatar.bodyPitch = 0.0;
MyAvatar.bodyRoll = 0.0;
}, SIT_DELAY);
Settings.setValue(SETTING_KEY, "");
}
MyAvatar.removeAnimationStateHandler(this.animStateHandlerID);
Script.update.disconnect(this, this.update);
Controller.keyPressEvent.disconnect(this, this.keyPressed);
Controller.keyReleaseEvent.disconnect(this, this.keyReleased);
for (var i in RELEASE_KEYS) {
Controller.releaseKeyEvents({ text: RELEASE_KEYS[i] });
}
}
this.sit = function () {
this.sitDown();
}
this.createOverlay = function() {
var text = "Click to sit";
var textMargin = 0.05;
var lineHeight = 0.15;
this.overlay = Overlays.addOverlay("text3d", {
position: { x: 0.0, y: 0.0, z: 0.0},
dimensions: { x: 0.1, y: 0.1 },
backgroundColor: { red: 0, green: 0, blue: 0 },
color: { red: 255, green: 255, blue: 255 },
topMargin: textMargin,
leftMargin: textMargin,
bottomMargin: textMargin,
rightMargin: textMargin,
text: text,
lineHeight: lineHeight,
alpha: 0.9,
backgroundAlpha: 0.9,
ignoreRayIntersection: true,
visible: true,
isFacingAvatar: true
});
var textSize = Overlays.textSize(this.overlay, text);
var overlayDimensions = {
x: textSize.width + 2 * textMargin,
y: textSize.height + 2 * textMargin
}
var properties = Entities.getEntityProperties(this.entityID, ["position", "registrationPoint", "dimensions"]);
var yOffset = (1.0 - properties.registrationPoint.y) * properties.dimensions.y + (overlayDimensions.y / 2.0);
var overlayPosition = Vec3.sum(properties.position, { x: 0, y: yOffset, z: 0 });
Overlays.editOverlay(this.overlay, {
position: overlayPosition,
dimensions: overlayDimensions
});
}
this.cleanupOverlay = function() {
if (this.overlay !== null) {
Overlays.deleteOverlay(this.overlay);
this.overlay = null;
}
}
this.update = function(dt) {
if (MyAvatar.sessionUUID === this.getSeatUser()) {
var properties = Entities.getEntityProperties(this.entityID, ["position"]);
var avatarDistance = Vec3.distance(MyAvatar.position, properties.position);
var ikError = MyAvatar.getIKErrorOnLastSolve();
if (avatarDistance > RELEASE_DISTANCE || ikError > MAX_IK_ERROR) {
print("IK error: " + ikError + ", distance from chair: " + avatarDistance);
this.sitUp(this.entityID);
}
}
}
this.keyPressed = function(event) {
if (isInEditMode()) {
return;
}
if (RELEASE_KEYS.indexOf(event.text) !== -1) {
var that = this;
this.timers[event.text] = Script.setTimeout(function() {
that.sitUp();
}, RELEASE_TIME);
}
}
this.keyReleased = function(event) {
if (RELEASE_KEYS.indexOf(event.text) !== -1) {
if (this.timers[event.text]) {
Script.clearTimeout(this.timers[event.text]);
delete this.timers[event.text];
}
}
}
this.canSitDesktop = function() {
var properties = Entities.getEntityProperties(this.entityID, ["position"]);
var distanceFromSeat = Vec3.distance(MyAvatar.position, properties.position);
return distanceFromSeat < DESKTOP_MAX_DISTANCE && !this.checkSeatForAvatar();
}
this.hoverEnterEntity = function(event) {
if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) {
return;
}
var that = this;
this.interval = Script.setInterval(function() {
if (that.overlay === null) {
if (that.canSitDesktop()) {
that.createOverlay();
}
} else if (!that.canSitDesktop()) {
that.cleanupOverlay();
}
}, DESKTOP_UI_CHECK_INTERVAL);
}
this.hoverLeaveEntity = function(event) {
if (this.interval) {
Script.clearInterval(this.interval);
this.interval = null;
}
this.cleanupOverlay();
}
this.clickDownOnEntity = function () {
if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) {
return;
}
if (this.canSitDesktop()) {
this.sitDown();
}
}
});