mirror of
https://github.com/lubosz/overte.git
synced 2025-08-07 19:41:20 +02:00
Merge pull request #9690 from Atlante45/feat/sit
Add hips pinning / Sit script
This commit is contained in:
commit
7f9437dcc5
11 changed files with 640 additions and 490 deletions
BIN
interface/resources/avatar/animations/sitting.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_idle.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_idle.fbx
Normal file
Binary file not shown.
|
@ -260,6 +260,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
|
||||||
return AvatarData::toByteArrayStateful(dataDetail);
|
return AvatarData::toByteArrayStateful(dataDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MyAvatar::resetSensorsAndBody() {
|
||||||
|
qApp->getActiveDisplayPlugin()->resetSensors();
|
||||||
|
reset(true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
void MyAvatar::centerBody() {
|
void MyAvatar::centerBody() {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QMetaObject::invokeMethod(this, "centerBody");
|
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
|
// thread-safe
|
||||||
void MyAvatar::addHoldAction(AvatarActionHold* holdAction) {
|
void MyAvatar::addHoldAction(AvatarActionHold* holdAction) {
|
||||||
std::lock_guard<std::mutex> guard(_holdActionsMutex);
|
std::lock_guard<std::mutex> guard(_holdActionsMutex);
|
||||||
|
|
|
@ -99,6 +99,7 @@ public:
|
||||||
|
|
||||||
void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
|
void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
|
||||||
|
|
||||||
|
Q_INVOKABLE void resetSensorsAndBody();
|
||||||
Q_INVOKABLE void centerBody(); // thread-safe
|
Q_INVOKABLE void centerBody(); // thread-safe
|
||||||
Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe
|
Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe
|
||||||
|
|
||||||
|
@ -216,6 +217,11 @@ public:
|
||||||
virtual void clearJointData(int index) override;
|
virtual void clearJointData(int index) override;
|
||||||
virtual void clearJointsData() 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 void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
|
||||||
Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
|
Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
|
||||||
Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; }
|
Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; }
|
||||||
|
@ -527,6 +533,8 @@ private:
|
||||||
bool didTeleport();
|
bool didTeleport();
|
||||||
bool getIsAway() const { return _isAway; }
|
bool getIsAway() const { return _isAway; }
|
||||||
void setAway(bool value);
|
void setAway(bool value);
|
||||||
|
|
||||||
|
std::vector<int> _pinnedJoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||||
|
|
|
@ -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
|
// finally set the relative rotation of each tip to agree with absolute target rotation
|
||||||
for (auto& target: targets) {
|
for (auto& target: targets) {
|
||||||
|
@ -268,13 +269,13 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
||||||
|
|
||||||
glm::quat deltaRotation;
|
glm::quat deltaRotation;
|
||||||
if (targetType == IKTarget::Type::RotationAndPosition ||
|
if (targetType == IKTarget::Type::RotationAndPosition ||
|
||||||
targetType == IKTarget::Type::HipsRelativeRotationAndPosition) {
|
targetType == IKTarget::Type::HipsRelativeRotationAndPosition) {
|
||||||
// compute the swing that would get get tip closer
|
// compute the swing that would get get tip closer
|
||||||
glm::vec3 targetLine = target.getTranslation() - jointPosition;
|
glm::vec3 targetLine = target.getTranslation() - jointPosition;
|
||||||
|
|
||||||
const float MIN_AXIS_LENGTH = 1.0e-4f;
|
const float MIN_AXIS_LENGTH = 1.0e-4f;
|
||||||
RotationConstraint* constraint = getConstraint(pivotIndex);
|
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
|
// 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)
|
// (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();
|
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;
|
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
|
||||||
if (angle > MIN_ADJUSTMENT_ANGLE) {
|
if (angle > MIN_ADJUSTMENT_ANGLE) {
|
||||||
// reduce angle by a fraction (for stability)
|
// reduce angle by a fraction (for stability)
|
||||||
const float FRACTION = 0.5f;
|
const float STABILITY_FRACTION = 0.5f;
|
||||||
angle *= FRACTION;
|
angle *= STABILITY_FRACTION;
|
||||||
deltaRotation = glm::angleAxis(angle, axis);
|
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
|
// 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);
|
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
|
||||||
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
|
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
|
||||||
float dotSign = copysignf(1.0f, twistPart.w);
|
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
|
// measure new _hipsOffset for next frame
|
||||||
// by looking for discrepancies between where a targeted endEffector is
|
// by looking for discrepancies between where a targeted endEffector is
|
||||||
// and where it wants to be (after IK solutions are done)
|
// 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) {
|
for (auto& target: targets) {
|
||||||
int targetIndex = target.getIndex();
|
int targetIndex = target.getIndex();
|
||||||
if (targetIndex == _headIndex && _headIndex != -1) {
|
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 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans();
|
||||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
||||||
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
|
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) {
|
} 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();
|
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
||||||
_hipsOffset += target.getTranslation() - actual;
|
glm::vec3 thisOffset = target.getTranslation() - actual;
|
||||||
// and ignore all other targets
|
glm::vec3 futureHipsOffset = _hipsOffset + thisOffset;
|
||||||
newHipsOffset = _hipsOffset;
|
if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) {
|
||||||
break;
|
// 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) {
|
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
|
||||||
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans();
|
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans();
|
||||||
glm::vec3 targetPosition = target.getTranslation();
|
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
|
// 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;
|
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;
|
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() {
|
void AnimInverseKinematics::clearIKJointLimitHistory() {
|
||||||
for (auto& pair : _constraints) {
|
for (auto& pair : _constraints) {
|
||||||
pair.second->clearHistory();
|
pair.second->clearHistory();
|
||||||
|
@ -740,7 +777,7 @@ void AnimInverseKinematics::initConstraints() {
|
||||||
stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST);
|
stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST);
|
||||||
|
|
||||||
std::vector<float> minDots;
|
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));
|
minDots.push_back(cosf(MAX_SPINE_SWING));
|
||||||
stConstraint->setSwingLimits(minDots);
|
stConstraint->setSwingLimits(minDots);
|
||||||
if (0 == baseName.compare("Spine1", Qt::CaseSensitive)
|
if (0 == baseName.compare("Spine1", Qt::CaseSensitive)
|
||||||
|
@ -776,11 +813,11 @@ void AnimInverseKinematics::initConstraints() {
|
||||||
} else if (0 == baseName.compare("Head", Qt::CaseSensitive)) {
|
} else if (0 == baseName.compare("Head", Qt::CaseSensitive)) {
|
||||||
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
|
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
|
||||||
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot());
|
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);
|
stConstraint->setTwistLimits(-MAX_HEAD_TWIST, MAX_HEAD_TWIST);
|
||||||
|
|
||||||
std::vector<float> minDots;
|
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));
|
minDots.push_back(cosf(MAX_HEAD_SWING));
|
||||||
stConstraint->setSwingLimits(minDots);
|
stConstraint->setSwingLimits(minDots);
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,10 @@ public:
|
||||||
|
|
||||||
void clearIKJointLimitHistory();
|
void clearIKJointLimitHistory();
|
||||||
|
|
||||||
|
void setMaxHipsOffsetLength(float maxLength);
|
||||||
|
|
||||||
|
float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
||||||
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
|
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
|
||||||
|
@ -83,6 +87,7 @@ protected:
|
||||||
|
|
||||||
// experimental data for moving hips during IK
|
// experimental data for moving hips during IK
|
||||||
glm::vec3 _hipsOffset { Vectors::ZERO };
|
glm::vec3 _hipsOffset { Vectors::ZERO };
|
||||||
|
float _maxHipsOffsetLength{ FLT_MAX };
|
||||||
int _headIndex { -1 };
|
int _headIndex { -1 };
|
||||||
int _hipsIndex { -1 };
|
int _hipsIndex { -1 };
|
||||||
int _hipsParentIndex { -1 };
|
int _hipsParentIndex { -1 };
|
||||||
|
@ -90,6 +95,8 @@ protected:
|
||||||
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
|
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
|
||||||
// during the the cyclic coordinate descent algorithm
|
// during the the cyclic coordinate descent algorithm
|
||||||
int _maxTargetIndex { 0 };
|
int _maxTargetIndex { 0 };
|
||||||
|
|
||||||
|
float _maxErrorOnLastSolve { FLT_MAX };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AnimInverseKinematics_h
|
#endif // hifi_AnimInverseKinematics_h
|
||||||
|
|
|
@ -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 {
|
int Rig::getJointParentIndex(int childIndex) const {
|
||||||
if (_animSkeleton && isIndexValid(childIndex)) {
|
if (_animSkeleton && isIndexValid(childIndex)) {
|
||||||
return _animSkeleton->getParentIndex(childIndex);
|
return _animSkeleton->getParentIndex(childIndex);
|
||||||
|
|
|
@ -104,6 +104,10 @@ public:
|
||||||
void clearJointAnimationPriority(int index);
|
void clearJointAnimationPriority(int index);
|
||||||
|
|
||||||
void clearIKJointLimitHistory();
|
void clearIKJointLimitHistory();
|
||||||
|
void setMaxHipsOffsetLength(float maxLength);
|
||||||
|
float getMaxHipsOffsetLength() const;
|
||||||
|
|
||||||
|
float getIKErrorOnLastSolve() const;
|
||||||
|
|
||||||
int getJointParentIndex(int childIndex) const;
|
int getJointParentIndex(int childIndex) const;
|
||||||
|
|
||||||
|
@ -318,6 +322,8 @@ protected:
|
||||||
bool _enabledAnimations { true };
|
bool _enabledAnimations { true };
|
||||||
|
|
||||||
mutable uint32_t _jointNameWarningCount { 0 };
|
mutable uint32_t _jointNameWarningCount { 0 };
|
||||||
|
float _maxHipsOffsetLength { 1.0f };
|
||||||
|
float _maxErrorOnLastSolve { 0.0f };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMap<int, StateHandler> _stateHandlers;
|
QMap<int, StateHandler> _stateHandlers;
|
||||||
|
|
BIN
scripts/system/assets/models/teleport-seat.fbx
Normal file
BIN
scripts/system/assets/models/teleport-seat.fbx
Normal file
Binary file not shown.
|
@ -17,12 +17,20 @@ var NUMBER_OF_STEPS = 6;
|
||||||
|
|
||||||
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
|
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
|
||||||
var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.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 = {
|
var TARGET_MODEL_DIMENSIONS = {
|
||||||
x: 1.15,
|
x: 1.15,
|
||||||
y: 0.5,
|
y: 0.5,
|
||||||
z: 1.15
|
z: 1.15
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var COLORS_TELEPORT_SEAT = {
|
||||||
|
red: 255,
|
||||||
|
green: 0,
|
||||||
|
blue: 170
|
||||||
|
}
|
||||||
|
|
||||||
var COLORS_TELEPORT_CAN_TELEPORT = {
|
var COLORS_TELEPORT_CAN_TELEPORT = {
|
||||||
red: 97,
|
red: 97,
|
||||||
green: 247,
|
green: 247,
|
||||||
|
@ -35,29 +43,30 @@ var COLORS_TELEPORT_CANNOT_TELEPORT = {
|
||||||
blue: 141
|
blue: 141
|
||||||
};
|
};
|
||||||
|
|
||||||
var COLORS_TELEPORT_TOO_CLOSE = {
|
var COLORS_TELEPORT_CANCEL = {
|
||||||
red: 255,
|
red: 255,
|
||||||
green: 184,
|
green: 184,
|
||||||
blue: 73
|
blue: 73
|
||||||
};
|
};
|
||||||
|
|
||||||
var TELEPORT_CANCEL_RANGE = 1;
|
var TELEPORT_CANCEL_RANGE = 1;
|
||||||
var USE_COOL_IN = true;
|
|
||||||
var COOL_IN_DURATION = 500;
|
var COOL_IN_DURATION = 500;
|
||||||
|
|
||||||
|
const handInfo = {
|
||||||
|
right: {
|
||||||
|
controllerInput: Controller.Standard.RightHand
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
controllerInput: Controller.Standard.LeftHand
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function ThumbPad(hand) {
|
function ThumbPad(hand) {
|
||||||
this.hand = hand;
|
this.hand = hand;
|
||||||
var _thisPad = this;
|
var _thisPad = this;
|
||||||
|
|
||||||
this.buttonPress = function(value) {
|
this.buttonPress = function(value) {
|
||||||
_thisPad.buttonValue = 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.buttonPress = function(value) {
|
||||||
_this.buttonValue = value;
|
_this.buttonValue = value;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.down = function() {
|
this.down = function() {
|
||||||
|
@ -78,347 +86,224 @@ function Trigger(hand) {
|
||||||
|
|
||||||
var coolInTimeout = null;
|
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() {
|
function Teleporter() {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
this.intersection = null;
|
this.active = false;
|
||||||
this.rightOverlayLine = null;
|
this.state = TELEPORTER_STATES.IDLE;
|
||||||
this.leftOverlayLine = null;
|
this.currentTarget = TARGET.INVALID;
|
||||||
this.targetOverlay = null;
|
|
||||||
this.cancelOverlay = null;
|
|
||||||
this.updateConnected = null;
|
|
||||||
this.smoothArrivalInterval = null;
|
|
||||||
this.teleportHand = null;
|
|
||||||
this.tooClose = false;
|
|
||||||
this.inCoolIn = false;
|
|
||||||
|
|
||||||
this.initialize = function() {
|
this.overlayLines = {
|
||||||
this.createMappings();
|
left: null,
|
||||||
|
right: null,
|
||||||
};
|
};
|
||||||
|
this.updateConnected = null;
|
||||||
|
this.activeHand = null;
|
||||||
|
|
||||||
this.createMappings = function() {
|
this.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
|
||||||
teleporter.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
|
this.teleportMappingInternal = Controller.newMapping(this.telporterMappingInternalName);
|
||||||
teleporter.teleportMappingInternal = Controller.newMapping(teleporter.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() {
|
this.disableMappings = function() {
|
||||||
Controller.disableMapping(teleporter.telporterMappingInternalName);
|
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) {
|
if (inTeleportMode === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDisabled === 'both') {
|
if (isDisabled === 'both' || isDisabled === hand) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inTeleportMode = true;
|
inTeleportMode = true;
|
||||||
this.inCoolIn = true;
|
|
||||||
if (coolInTimeout !== null) {
|
if (coolInTimeout !== null) {
|
||||||
Script.clearTimeout(coolInTimeout);
|
Script.clearTimeout(coolInTimeout);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.state = TELEPORTER_STATES.COOL_IN;
|
||||||
coolInTimeout = Script.setTimeout(function() {
|
coolInTimeout = Script.setTimeout(function() {
|
||||||
_this.inCoolIn = false;
|
if (_this.state === TELEPORTER_STATES.COOL_IN) {
|
||||||
|
_this.state = TELEPORTER_STATES.TARGETTING;
|
||||||
|
}
|
||||||
}, COOL_IN_DURATION)
|
}, COOL_IN_DURATION)
|
||||||
|
|
||||||
if (this.smoothArrivalInterval !== null) {
|
this.activeHand = hand;
|
||||||
Script.clearInterval(this.smoothArrivalInterval);
|
this.enableMappings();
|
||||||
}
|
Script.update.connect(this, this.update);
|
||||||
if (activationTimeout !== null) {
|
|
||||||
Script.clearInterval(activationTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.teleportHand = hand;
|
|
||||||
this.initialize();
|
|
||||||
Script.update.connect(this.update);
|
|
||||||
this.updateConnected = true;
|
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) {
|
this.exitTeleportMode = function(value) {
|
||||||
if (activationTimeout !== null) {
|
|
||||||
Script.clearTimeout(activationTimeout);
|
|
||||||
activationTimeout = null;
|
|
||||||
}
|
|
||||||
if (this.updateConnected === true) {
|
if (this.updateConnected === true) {
|
||||||
Script.update.disconnect(this.update);
|
Script.update.disconnect(this, this.update);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disableMappings();
|
this.disableMappings();
|
||||||
this.turnOffOverlayBeams();
|
this.deleteOverlayBeams();
|
||||||
|
this.hideTargetOverlay();
|
||||||
|
this.hideCancelOverlay();
|
||||||
|
|
||||||
this.updateConnected = null;
|
this.updateConnected = null;
|
||||||
this.inCoolIn = false;
|
this.state = TELEPORTER_STATES.IDLE;
|
||||||
inTeleportMode = false;
|
inTeleportMode = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update = function() {
|
this.deleteOverlayBeams = function() {
|
||||||
if (isDisabled === 'both') {
|
for (key in this.overlayLines) {
|
||||||
return;
|
if (this.overlayLines[key] !== null) {
|
||||||
}
|
Overlays.deleteOverlay(this.overlayLines[key]);
|
||||||
|
this.overlayLines[key] = null;
|
||||||
if (teleporter.teleportHand === 'left') {
|
|
||||||
if (isDisabled === 'left') {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
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() {
|
// Get current hand pose information so that we can get the direction of the teleport beam
|
||||||
var pose = Controller.getPoseValue(Controller.Standard.LeftHand);
|
var pose = Controller.getPoseValue(handInfo[_this.activeHand].controllerInput);
|
||||||
var leftPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
|
var handPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
|
||||||
var leftRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
|
var handRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
|
||||||
Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {
|
Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 0,
|
y: 0,
|
||||||
z: 0
|
z: 0
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var leftPickRay = {
|
var pickRay = {
|
||||||
origin: leftPosition,
|
origin: handPosition,
|
||||||
direction: Quat.getUp(leftRotation),
|
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 teleportLocationType = getTeleportTargetType(intersection);
|
||||||
|
if (teleportLocationType === TARGET.INVISIBLE) {
|
||||||
|
intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity], true, true);
|
||||||
var leftIntersection = Entities.findRayIntersection(teleporter.leftPickRay, true, [], [this.targetEntity], true, true);
|
teleportLocationType = getTeleportTargetType(intersection);
|
||||||
|
}
|
||||||
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 {
|
|
||||||
|
|
||||||
|
if (teleportLocationType === TARGET.NONE) {
|
||||||
this.hideTargetOverlay();
|
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) {
|
this.updateLineOverlay = function(hand, closePoint, farPoint, color) {
|
||||||
if (this.rightOverlayLine === null) {
|
if (this.overlayLines[hand] === null) {
|
||||||
var lineProperties = {
|
var lineProperties = {
|
||||||
start: closePoint,
|
start: closePoint,
|
||||||
end: farPoint,
|
end: farPoint,
|
||||||
|
@ -431,10 +316,10 @@ function Teleporter() {
|
||||||
glow: 1.0
|
glow: 1.0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.rightOverlayLine = Overlays.addOverlay("line3d", lineProperties);
|
this.overlayLines[hand] = Overlays.addOverlay("line3d", lineProperties);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
var success = Overlays.editOverlay(this.rightOverlayLine, {
|
var success = Overlays.editOverlay(this.overlayLines[hand], {
|
||||||
start: closePoint,
|
start: closePoint,
|
||||||
end: farPoint,
|
end: farPoint,
|
||||||
color: color
|
color: color
|
||||||
|
@ -442,47 +327,19 @@ function Teleporter() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.leftLineOn = function(closePoint, farPoint, color) {
|
this.hideCancelOverlay = function() {
|
||||||
if (this.leftOverlayLine === null) {
|
Overlays.editOverlay(this.cancelOverlay, { visible: false });
|
||||||
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.leftOverlayOff = function() {
|
this.hideTargetOverlay = function() {
|
||||||
if (this.leftOverlayLine !== null) {
|
Overlays.editOverlay(this.targetOverlay, { visible: false });
|
||||||
Overlays.deleteOverlay(this.leftOverlayLine);
|
|
||||||
this.leftOverlayLine = null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateTargetOverlay = function(intersection) {
|
this.hideSeatOverlay = function() {
|
||||||
_this.intersection = intersection;
|
Overlays.editOverlay(this.seatOverlay, { visible: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateDestinationOverlay = function(overlayID, intersection) {
|
||||||
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
||||||
var euler = Quat.safeEulerAngles(rotation);
|
var euler = Quat.safeEulerAngles(rotation);
|
||||||
var position = {
|
var position = {
|
||||||
|
@ -491,115 +348,15 @@ function Teleporter() {
|
||||||
z: intersection.intersection.z
|
z: intersection.intersection.z
|
||||||
};
|
};
|
||||||
|
|
||||||
this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal);
|
|
||||||
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
||||||
|
|
||||||
Overlays.editOverlay(this.targetOverlay, {
|
Overlays.editOverlay(overlayID, {
|
||||||
visible: true,
|
visible: true,
|
||||||
position: position,
|
position: position,
|
||||||
rotation: towardUs
|
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
|
//related to repositioning the avatar after you teleport
|
||||||
|
@ -611,20 +368,16 @@ function getAvatarFootOffset() {
|
||||||
var jointName = d.joint;
|
var jointName = d.joint;
|
||||||
if (jointName === "RightUpLeg") {
|
if (jointName === "RightUpLeg") {
|
||||||
upperLeg = d.translation.y;
|
upperLeg = d.translation.y;
|
||||||
}
|
} else if (jointName === "RightLeg") {
|
||||||
if (jointName === "RightLeg") {
|
|
||||||
lowerLeg = d.translation.y;
|
lowerLeg = d.translation.y;
|
||||||
}
|
} else if (jointName === "RightFoot") {
|
||||||
if (jointName === "RightFoot") {
|
|
||||||
foot = d.translation.y;
|
foot = d.translation.y;
|
||||||
}
|
} else if (jointName === "RightToeBase") {
|
||||||
if (jointName === "RightToeBase") {
|
|
||||||
toe = d.translation.y;
|
toe = d.translation.y;
|
||||||
}
|
} else if (jointName === "RightToe_End") {
|
||||||
if (jointName === "RightToe_End") {
|
|
||||||
toeTop = d.translation.y;
|
toeTop = d.translation.y;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
var offset = upperLeg + lowerLeg + foot + toe + toeTop;
|
var offset = upperLeg + lowerLeg + foot + toe + toeTop;
|
||||||
offset = offset / 100;
|
offset = offset / 100;
|
||||||
|
@ -655,7 +408,6 @@ var rightTrigger = new Trigger('right');
|
||||||
|
|
||||||
var mappingName, teleportMapping;
|
var mappingName, teleportMapping;
|
||||||
|
|
||||||
var activationTimeout = null;
|
|
||||||
var TELEPORT_DELAY = 0;
|
var TELEPORT_DELAY = 0;
|
||||||
|
|
||||||
function isMoving() {
|
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
|
// 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
|
// 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
|
// than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then
|
||||||
// you can't teleport there.
|
// you can't teleport there.
|
||||||
var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
|
const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
|
||||||
function isValidTeleportLocation(position, surfaceNormal) {
|
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 adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z);
|
||||||
var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI);
|
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) ||
|
if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
|
||||||
Vec3.distance(MyAvatar.position, position) <= TELEPORT_CANCEL_RANGE;
|
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() {
|
function registerMappings() {
|
||||||
|
@ -695,20 +474,13 @@ function registerMappings() {
|
||||||
if (isDisabled === 'left' || isDisabled === 'both') {
|
if (isDisabled === 'left' || isDisabled === 'both') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (activationTimeout !== null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (leftTrigger.down()) {
|
if (leftTrigger.down()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isMoving() === true) {
|
if (isMoving() === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
activationTimeout = Script.setTimeout(function() {
|
teleporter.enterTeleportMode('left')
|
||||||
Script.clearTimeout(activationTimeout);
|
|
||||||
activationTimeout = null;
|
|
||||||
teleporter.enterTeleportMode('left')
|
|
||||||
}, TELEPORT_DELAY)
|
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
teleportMapping.from(Controller.Standard.RightPrimaryThumb)
|
teleportMapping.from(Controller.Standard.RightPrimaryThumb)
|
||||||
|
@ -716,9 +488,6 @@ function registerMappings() {
|
||||||
if (isDisabled === 'right' || isDisabled === 'both') {
|
if (isDisabled === 'right' || isDisabled === 'both') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (activationTimeout !== null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (rightTrigger.down()) {
|
if (rightTrigger.down()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -726,11 +495,7 @@ function registerMappings() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
activationTimeout = Script.setTimeout(function() {
|
teleporter.enterTeleportMode('right')
|
||||||
teleporter.enterTeleportMode('right')
|
|
||||||
Script.clearTimeout(activationTimeout);
|
|
||||||
activationTimeout = null;
|
|
||||||
}, TELEPORT_DELAY)
|
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -741,18 +506,11 @@ var teleporter = new Teleporter();
|
||||||
|
|
||||||
Controller.enableMapping(mappingName);
|
Controller.enableMapping(mappingName);
|
||||||
|
|
||||||
Script.scriptEnding.connect(cleanup);
|
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
teleportMapping.disable();
|
teleportMapping.disable();
|
||||||
teleporter.disableMappings();
|
teleporter.cleanup();
|
||||||
teleporter.deleteTargetOverlay();
|
|
||||||
teleporter.deleteCancelOverlay();
|
|
||||||
teleporter.turnOffOverlayBeams();
|
|
||||||
if (teleporter.updateConnected !== null) {
|
|
||||||
Script.update.disconnect(teleporter.update);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Script.scriptEnding.connect(cleanup);
|
||||||
|
|
||||||
var isDisabled = false;
|
var isDisabled = false;
|
||||||
var handleHandMessages = function(channel, message, sender) {
|
var handleHandMessages = function(channel, message, sender) {
|
||||||
|
|
257
scripts/tutorials/entity_scripts/sit.js
Normal file
257
scripts/tutorials/entity_scripts/sit.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in a new issue