diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index c8af792d8f..c47cfdb383 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -113,6 +113,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_recentModeReadings(MODE_READINGS_RING_BUFFER_SIZE),
_bodySensorMatrix(),
_goToPending(false),
+ _goToSafe(true),
_goToPosition(),
_goToOrientation(),
_prevShouldDrawHead(true),
@@ -148,7 +149,8 @@ MyAvatar::MyAvatar(QThread* thread) :
});
connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady);
connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset);
-
+ connect(&_skeletonModel->getRig(), &Rig::onLoadComplete, this, &MyAvatar::updateCollisionCapsuleCache);
+ connect(this, &MyAvatar::sensorToWorldScaleChanged, this, &MyAvatar::updateCollisionCapsuleCache);
using namespace recording;
_skeletonModel->flagAsCauterized();
@@ -254,6 +256,7 @@ MyAvatar::MyAvatar(QThread* thread) :
});
connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete()));
+
_characterController.setDensity(_density);
}
@@ -509,7 +512,9 @@ void MyAvatar::update(float deltaTime) {
if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) {
// When needed and ready, arrange to check and fix.
_physicsSafetyPending = false;
- safeLanding(_goToPosition); // no-op if already safe
+ if (_goToSafe) {
+ safeLanding(_goToPosition); // no-op if already safe
+ }
}
Head* head = getHead();
@@ -3011,7 +3016,7 @@ void MyAvatar::goToFeetLocation(const glm::vec3& newPosition,
void MyAvatar::goToLocation(const glm::vec3& newPosition,
bool hasOrientation, const glm::quat& newOrientation,
- bool shouldFaceLocation) {
+ bool shouldFaceLocation, bool withSafeLanding) {
// Most cases of going to a place or user go through this now. Some possible improvements to think about in the future:
// - It would be nice if this used the same teleport steps and smoothing as in the teleport.js script, as long as it
@@ -3031,6 +3036,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
_goToPending = true;
_goToPosition = newPosition;
+ _goToSafe = withSafeLanding;
_goToOrientation = getWorldOrientation();
if (hasOrientation) {
qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - new orientation is "
@@ -3303,6 +3309,22 @@ bool MyAvatar::getCollisionsEnabled() {
return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS;
}
+void MyAvatar::updateCollisionCapsuleCache() {
+ glm::vec3 start, end;
+ float radius;
+ getCapsule(start, end, radius);
+ QVariantMap capsule;
+ capsule["start"] = vec3toVariant(start);
+ capsule["end"] = vec3toVariant(end);
+ capsule["radius"] = QVariant(radius);
+ _collisionCapsuleCache.set(capsule);
+}
+
+// thread safe
+QVariantMap MyAvatar::getCollisionCapsule() const {
+ return _collisionCapsuleCache.get();
+}
+
void MyAvatar::setCharacterControllerEnabled(bool enabled) {
qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead.";
setCollisionsEnabled(enabled);
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 5008190c33..139f1f6ea2 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -1017,6 +1017,12 @@ public:
*/
Q_INVOKABLE bool getCollisionsEnabled();
+ /**jsdoc
+ * @function MyAvatar.getCollisionCapsule
+ * @returns {object}
+ */
+ Q_INVOKABLE QVariantMap getCollisionCapsule() const;
+
/**jsdoc
* @function MyAvatar.setCharacterControllerEnabled
* @param {boolean} enabled
@@ -1180,11 +1186,12 @@ public slots:
* @param {boolean} [hasOrientation=false] - Set to true
to set the orientation of the avatar.
* @param {Quat} [orientation=Quat.IDENTITY] - The new orientation for the avatar.
* @param {boolean} [shouldFaceLocation=false] - Set to true
to position the avatar a short distance away from
+ * @param {boolean} [withSafeLanding=true] - Set to false
MyAvatar::safeLanding will not be called (used when teleporting).
* the new position and orientate the avatar to face the position.
*/
void goToLocation(const glm::vec3& newPosition,
bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(),
- bool shouldFaceLocation = false);
+ bool shouldFaceLocation = false, bool withSafeLanding = true);
/**jsdoc
* @function MyAvatar.goToLocation
* @param {object} properties
@@ -1498,6 +1505,7 @@ signals:
private slots:
void leaveDomain();
+ void updateCollisionCapsuleCache();
protected:
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override;
@@ -1722,6 +1730,7 @@ private:
bool _goToPending { false };
bool _physicsSafetyPending { false };
+ bool _goToSafe { true };
glm::vec3 _goToPosition;
glm::quat _goToOrientation;
diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp
index c21ee69b74..9aee76a3da 100644
--- a/interface/src/raypick/CollisionPick.cpp
+++ b/interface/src/raypick/CollisionPick.cpp
@@ -51,6 +51,7 @@ void buildObjectIntersectionsMap(IntersectionType intersectionType, const std::v
QVariantMap collisionPointPair;
collisionPointPair["pointOnPick"] = vec3toVariant(objectIntersection.testCollisionPoint);
collisionPointPair["pointOnObject"] = vec3toVariant(objectIntersection.foundCollisionPoint);
+ collisionPointPair["normalOnPick"] = vec3toVariant(objectIntersection.collisionNormal);
collisionPointPairs[objectIntersection.foundID].append(collisionPointPair);
}
@@ -397,7 +398,7 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi
}
getShapeInfoReady(pick);
- auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *_mathPick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold);
+ auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *_mathPick.shapeInfo, pick.transform, pick.collisionGroup, pick.threshold);
filterIntersections(entityIntersections);
return std::make_shared(pick, entityIntersections, std::vector());
}
@@ -413,13 +414,13 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi
}
getShapeInfoReady(pick);
- auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *_mathPick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold);
+ auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *_mathPick.shapeInfo, pick.transform, pick.collisionGroup, pick.threshold);
filterIntersections(avatarIntersections);
return std::make_shared(pick, std::vector(), avatarIntersections);
}
PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) {
- return std::make_shared(pick.toVariantMap(), std::vector(), std::vector());
+ return std::make_shared(pick, std::vector(), std::vector());
}
Transform CollisionPick::getResultTransform() const {
diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h
index fe0e5a6337..0662ab6c19 100644
--- a/interface/src/raypick/CollisionPick.h
+++ b/interface/src/raypick/CollisionPick.h
@@ -70,6 +70,9 @@ protected:
CollisionRegion _mathPick;
PhysicsEnginePointer _physicsEngine;
QSharedPointer _cachedResource;
+
+ // Options for what information to get from collision results
+ bool _includeNormals;
};
#endif // hifi_CollisionPick_h
\ No newline at end of file
diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp
index 0273b084b2..b9693f6782 100644
--- a/interface/src/raypick/PickScriptingInterface.cpp
+++ b/interface/src/raypick/PickScriptingInterface.cpp
@@ -270,6 +270,8 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti
* @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined.
* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region.
* The depth is measured in world space, but will scale with the parent if defined.
+* @property {CollisionMask} [collisionGroup=8] - The type of object this collision pick collides as. Objects whose collision masks overlap with the pick's collision group
+* will be considered colliding with the pick.
* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay.
* @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint)
* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar.
diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h
index 4d99309618..36079cec2b 100644
--- a/interface/src/raypick/PickScriptingInterface.h
+++ b/interface/src/raypick/PickScriptingInterface.h
@@ -167,6 +167,7 @@ public:
* @typedef {object} CollisionContact
* @property {Vec3} pointOnPick A point representing a penetration of the object's surface into the volume of the pick, in world space.
* @property {Vec3} pointOnObject A point representing a penetration of the pick's surface into the volume of the found object, in world space.
+ * @property {Vec3} normalOnPick The normalized vector pointing away from the pick, representing the direction of collision.
*/
/**jsdoc
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 056e745370..39f0ea34f6 100644
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -1438,6 +1438,8 @@ protected:
ThreadSafeValueCache _farGrabLeftMatrixCache { glm::mat4() };
ThreadSafeValueCache _farGrabMouseMatrixCache { glm::mat4() };
+ ThreadSafeValueCache _collisionCapsuleCache{ QVariantMap() };
+
int getFauxJointIndex(const QString& name) const;
float _audioLoudness { 0.0f };
diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp
index 871958f883..8343919ae1 100644
--- a/libraries/physics/src/PhysicsEngine.cpp
+++ b/libraries/physics/src/PhysicsEngine.cpp
@@ -936,19 +936,22 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback {
const btCollisionObject* otherBody;
btVector3 penetrationPoint;
btVector3 otherPenetrationPoint;
+ btVector3 normal;
if (colObj0->m_collisionObject == &collisionObject) {
otherBody = colObj1->m_collisionObject;
penetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform());
otherPenetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform());
+ normal = -cp.m_normalWorldOnB;
} else {
otherBody = colObj0->m_collisionObject;
penetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform());
otherPenetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform());
+ normal = cp.m_normalWorldOnB;
}
// TODO: Give MyAvatar a motion state so we don't have to do this
if ((m_collisionFilterMask & BULLET_COLLISION_GROUP_MY_AVATAR) && myAvatarCollisionObject && myAvatarCollisionObject == otherBody) {
- contacts.emplace_back(Physics::getSessionUUID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint));
+ contacts.emplace_back(Physics::getSessionUUID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint), bulletToGLM(normal));
return 0;
}
@@ -964,7 +967,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback {
}
// This is the correct object type. Add it to the list.
- contacts.emplace_back(candidate->getObjectID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint));
+ contacts.emplace_back(candidate->getObjectID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint), bulletToGLM(normal));
return 0;
}
diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h
index 09b68d9a8b..d10be018b8 100644
--- a/libraries/physics/src/PhysicsEngine.h
+++ b/libraries/physics/src/PhysicsEngine.h
@@ -49,13 +49,15 @@ struct ContactTestResult {
ContactTestResult(const ContactTestResult& contactTestResult) :
foundID(contactTestResult.foundID),
testCollisionPoint(contactTestResult.testCollisionPoint),
- foundCollisionPoint(contactTestResult.foundCollisionPoint) {
+ foundCollisionPoint(contactTestResult.foundCollisionPoint),
+ collisionNormal(contactTestResult.collisionNormal) {
}
- ContactTestResult(QUuid foundID, glm::vec3 testCollisionPoint, glm::vec3 otherCollisionPoint) :
+ ContactTestResult(const QUuid& foundID, const glm::vec3& testCollisionPoint, const glm::vec3& otherCollisionPoint, const glm::vec3& collisionNormal) :
foundID(foundID),
testCollisionPoint(testCollisionPoint),
- foundCollisionPoint(otherCollisionPoint) {
+ foundCollisionPoint(otherCollisionPoint),
+ collisionNormal(collisionNormal) {
}
QUuid foundID;
@@ -63,6 +65,8 @@ struct ContactTestResult {
glm::vec3 testCollisionPoint;
// The deepest point of an intersection within the volume of the found object, in world space.
glm::vec3 foundCollisionPoint;
+ // The normal vector of this intersection
+ glm::vec3 collisionNormal;
};
using ContactMap = std::map;
diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h
index d59c58def8..6ecf9faca7 100644
--- a/libraries/shared/src/RegisteredMetaTypes.h
+++ b/libraries/shared/src/RegisteredMetaTypes.h
@@ -24,6 +24,7 @@
#include "SharedUtil.h"
#include "shared/Bilateral.h"
#include "Transform.h"
+#include "PhysicsCollisionGroups.h"
class QColor;
class QUrl;
@@ -264,6 +265,8 @@ public:
* @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined.
* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region.
* The depth is measured in world space, but will scale with the parent if defined.
+* @property {CollisionMask} [collisionGroup=8] - The type of object this collision pick collides as. Objects whose collision masks overlap with the pick's collision group
+* will be considered colliding with the pick.
* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay.
* @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint)
* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar.
@@ -277,7 +280,8 @@ public:
modelURL(collisionRegion.modelURL),
shapeInfo(std::make_shared()),
transform(collisionRegion.transform),
- threshold(collisionRegion.threshold)
+ threshold(collisionRegion.threshold),
+ collisionGroup(collisionRegion.collisionGroup)
{
shapeInfo->setParams(collisionRegion.shapeInfo->getType(), collisionRegion.shapeInfo->getHalfExtents(), collisionRegion.modelURL.toString());
}
@@ -316,6 +320,9 @@ public:
if (pickVariant["orientation"].isValid()) {
transform.setRotation(quatFromVariant(pickVariant["orientation"]));
}
+ if (pickVariant["collisionGroup"].isValid()) {
+ collisionGroup = pickVariant["collisionGroup"].toUInt();
+ }
}
QVariantMap toVariantMap() const override {
@@ -330,6 +337,7 @@ public:
collisionRegion["loaded"] = loaded;
collisionRegion["threshold"] = threshold;
+ collisionRegion["collisionGroup"] = collisionGroup;
collisionRegion["position"] = vec3toVariant(transform.getTranslation());
collisionRegion["orientation"] = quatToVariant(transform.getRotation());
@@ -341,12 +349,14 @@ public:
return !std::isnan(threshold) &&
!(glm::any(glm::isnan(transform.getTranslation())) ||
glm::any(glm::isnan(transform.getRotation())) ||
- shapeInfo->getType() == SHAPE_TYPE_NONE);
+ shapeInfo->getType() == SHAPE_TYPE_NONE ||
+ collisionGroup == 0);
}
bool operator==(const CollisionRegion& other) const {
return loaded == other.loaded &&
threshold == other.threshold &&
+ collisionGroup == other.collisionGroup &&
glm::all(glm::equal(transform.getTranslation(), other.transform.getTranslation())) &&
glm::all(glm::equal(transform.getRotation(), other.transform.getRotation())) &&
glm::all(glm::equal(transform.getScale(), other.transform.getScale())) &&
@@ -362,6 +372,10 @@ public:
return false;
}
+ if (collisionGroup == 0) {
+ return false;
+ }
+
return !shapeInfo->getPointCollection().size();
}
@@ -372,7 +386,8 @@ public:
// We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick
std::shared_ptr shapeInfo = std::make_shared();
Transform transform;
- float threshold;
+ float threshold { 0.0f };
+ uint16_t collisionGroup { USER_COLLISION_GROUP_MY_AVATAR };
};
namespace std {
diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js
index e4dd1c43fa..deaa934f99 100644
--- a/scripts/system/controllers/controllerModules/teleport.js
+++ b/scripts/system/controllers/controllerModules/teleport.js
@@ -62,40 +62,51 @@ Script.include("/~/system/libraries/controllers.js");
alpha: 1,
width: 0.025
};
+
var teleportPath = {
color: COLORS_TELEPORT_CAN_TELEPORT,
alpha: 1,
width: 0.025
};
+
var seatPath = {
color: COLORS_TELEPORT_SEAT,
alpha: 1,
width: 0.025
};
+
var teleportEnd = {
type: "model",
url: TARGET_MODEL_URL,
dimensions: TARGET_MODEL_DIMENSIONS,
ignorePickIntersection: true
};
+
var seatEnd = {
type: "model",
url: SEAT_MODEL_URL,
dimensions: TARGET_MODEL_DIMENSIONS,
ignorePickIntersection: true
};
-
-
+
+ var collisionEnd = {
+ type: "shape",
+ shape: "box",
+ dimensions: { x: 1.0, y: 0.001, z: 1.0 },
+ alpha: 0.0,
+ ignorePickIntersection: true
+ };
+
var teleportRenderStates = [{name: "cancel", path: cancelPath},
{name: "teleport", path: teleportPath, end: teleportEnd},
- {name: "seat", path: seatPath, end: seatEnd}];
+ {name: "seat", path: seatPath, end: seatEnd},
+ {name: "collision", end: collisionEnd}];
var DEFAULT_DISTANCE = 8.0;
var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}];
var ignoredEntities = [];
-
var TELEPORTER_STATES = {
IDLE: 'idle',
TARGETTING: 'targetting',
@@ -104,8 +115,9 @@ Script.include("/~/system/libraries/controllers.js");
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.)
+ COLLIDES: 'collides', // Insufficient space to accommodate the avatar capsule
+ DISCREPANCY: 'discrepancy', // We are not 100% sure the avatar will fit so we trigger safe landing
SURFACE: 'surface', // The current target is a valid surface
SEAT: 'seat' // The current target is a seat
};
@@ -115,6 +127,7 @@ Script.include("/~/system/libraries/controllers.js");
function Teleporter(hand) {
var _this = this;
+ this.init = false;
this.hand = hand;
this.buttonValue = 0;
this.disabled = false; // used by the 'Hifi-Teleport-Disabler' message handler
@@ -122,74 +135,138 @@ Script.include("/~/system/libraries/controllers.js");
this.state = TELEPORTER_STATES.IDLE;
this.currentTarget = TARGET.INVALID;
this.currentResult = null;
+ this.capsuleThreshold = 0.05;
+ this.pickHeightOffset = 0.05;
this.getOtherModule = function() {
var otherModule = this.hand === RIGHT_HAND ? leftTeleporter : rightTeleporter;
return otherModule;
};
- this.teleportParabolaHandVisible = Pointers.createPointer(PickType.Parabola, {
- joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
- dirOffset: { x: 0, y: 1, z: 0.1 },
- posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 },
- filter: Picks.PICK_ENTITIES,
- faceAvatar: true,
- scaleWithAvatar: true,
- centerEndY: false,
- speed: speed,
- accelerationAxis: accelerationAxis,
- rotateAccelerationWithAvatar: true,
- renderStates: teleportRenderStates,
- defaultRenderStates: teleportDefaultRenderStates,
- maxDistance: 8.0
- });
- this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Parabola, {
- joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
- dirOffset: { x: 0, y: 1, z: 0.1 },
- posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 },
- filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE,
- faceAvatar: true,
- scaleWithAvatar: true,
- centerEndY: false,
- speed: speed,
- accelerationAxis: accelerationAxis,
- rotateAccelerationWithAvatar: true,
- renderStates: teleportRenderStates,
- maxDistance: 8.0
- });
- this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, {
- joint: "Avatar",
- filter: Picks.PICK_ENTITIES,
- faceAvatar: true,
- scaleWithAvatar: true,
- centerEndY: false,
- speed: speed,
- accelerationAxis: accelerationAxis,
- rotateAccelerationWithAvatar: true,
- renderStates: teleportRenderStates,
- defaultRenderStates: teleportDefaultRenderStates,
- maxDistance: 8.0
- });
- this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Parabola, {
- joint: "Avatar",
- filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE,
- faceAvatar: true,
- scaleWithAvatar: true,
- centerEndY: false,
- speed: speed,
- accelerationAxis: accelerationAxis,
- rotateAccelerationWithAvatar: true,
- renderStates: teleportRenderStates,
- maxDistance: 8.0
- });
+ this.teleportHeadCollisionPick;
+ this.teleportHandCollisionPick;
+ this.teleportParabolaHandVisuals;
+ this.teleportParabolaHandCollisions;
+ this.teleportParabolaHeadVisuals;
+ this.teleportParabolaHeadCollisions;
this.cleanup = function() {
- Pointers.removePointer(this.teleportParabolaHandVisible);
- Pointers.removePointer(this.teleportParabolaHandInvisible);
- Pointers.removePointer(this.teleportParabolaHeadVisible);
- Pointers.removePointer(this.teleportParabolaHeadInvisible);
+ Pointers.removePointer(_this.teleportParabolaHandVisuals);
+ Pointers.removePointer(_this.teleportParabolaHandCollisions);
+ Pointers.removePointer(_this.teleportParabolaHeadVisuals);
+ Pointers.removePointer(_this.teleportParabolaHeadCollisions);
+ Picks.removePick(_this.teleportHandCollisionPick);
+ Picks.removePick(_this.teleportHeadCollisionPick);
};
+ this.initPointers = function () {
+ if (_this.init) {
+ _this.cleanup();
+ }
+ _this.teleportParabolaHandVisuals = Pointers.createPointer(PickType.Parabola, {
+ joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
+ dirOffset: { x: 0, y: 1, z: 0.1 },
+ posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 },
+ filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE,
+ faceAvatar: true,
+ scaleWithAvatar: true,
+ centerEndY: false,
+ speed: speed,
+ accelerationAxis: accelerationAxis,
+ rotateAccelerationWithAvatar: true,
+ renderStates: teleportRenderStates,
+ defaultRenderStates: teleportDefaultRenderStates,
+ maxDistance: 8.0
+ });
+
+ _this.teleportParabolaHandCollisions = Pointers.createPointer(PickType.Parabola, {
+ joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
+ dirOffset: { x: 0, y: 1, z: 0.1 },
+ posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 },
+ filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE,
+ faceAvatar: true,
+ scaleWithAvatar: true,
+ centerEndY: false,
+ speed: speed,
+ accelerationAxis: accelerationAxis,
+ rotateAccelerationWithAvatar: true,
+ renderStates: teleportRenderStates,
+ maxDistance: 8.0
+ });
+
+ _this.teleportParabolaHeadVisuals = Pointers.createPointer(PickType.Parabola, {
+ joint: "Avatar",
+ filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE,
+ faceAvatar: true,
+ scaleWithAvatar: true,
+ centerEndY: false,
+ speed: speed,
+ accelerationAxis: accelerationAxis,
+ rotateAccelerationWithAvatar: true,
+ renderStates: teleportRenderStates,
+ defaultRenderStates: teleportDefaultRenderStates,
+ maxDistance: 8.0
+ });
+
+ _this.teleportParabolaHeadCollisions = Pointers.createPointer(PickType.Parabola, {
+ joint: "Avatar",
+ filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE,
+ faceAvatar: true,
+ scaleWithAvatar: true,
+ centerEndY: false,
+ speed: speed,
+ accelerationAxis: accelerationAxis,
+ rotateAccelerationWithAvatar: true,
+ renderStates: teleportRenderStates,
+ maxDistance: 8.0
+ });
+
+
+ var capsuleData = MyAvatar.getCollisionCapsule();
+
+ var sensorToWorldScale = MyAvatar.getSensorToWorldScale();
+
+ var radius = capsuleData.radius / sensorToWorldScale;
+ var height = (Vec3.distance(capsuleData.start, capsuleData.end) + (capsuleData.radius * 2.0)) / sensorToWorldScale;
+ var capsuleRatio = 10.0 * radius / height;
+ var offset = _this.pickHeightOffset * capsuleRatio;
+
+ _this.teleportHandCollisionPick = Picks.createPick(PickType.Collision, {
+ enabled: true,
+ parentID: Pointers.getPointerProperties(_this.teleportParabolaHandCollisions).renderStates["collision"].end,
+ filter: Picks.PICK_ENTITIES | Picks.PICK_AVATARS,
+ shape: {
+ shapeType: "capsule-y",
+ dimensions: {
+ x: radius * 2.0,
+ y: height - (radius * 2.0),
+ z: radius * 2.0
+ }
+ },
+ position: { x: 0, y: offset + height * 0.5, z: 0 },
+ threshold: _this.capsuleThreshold
+ });
+
+ _this.teleportHeadCollisionPick = Picks.createPick(PickType.Collision, {
+ enabled: true,
+ parentID: Pointers.getPointerProperties(_this.teleportParabolaHeadCollisions).renderStates["collision"].end,
+ filter: Picks.PICK_ENTITIES | Picks.PICK_AVATARS,
+ shape: {
+ shapeType: "capsule-y",
+ dimensions: {
+ x: radius * 2.0,
+ y: height - (radius * 2.0),
+ z: radius * 2.0
+ }
+ },
+ position: { x: 0, y: offset + height * 0.5, z: 0 },
+ threshold: _this.capsuleThreshold
+ });
+ _this.init = true;
+ }
+
+ _this.initPointers();
+
this.axisButtonStateX = 0; // Left/right axis button pressed.
this.axisButtonStateY = 0; // Up/down axis button pressed.
this.BUTTON_TRANSITION_DELAY = 100; // Allow time for transition from direction buttons to touch-pad.
@@ -254,52 +331,56 @@ Script.include("/~/system/libraries/controllers.js");
var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput);
var mode = pose.valid ? _this.hand : 'head';
if (!pose.valid) {
- Pointers.disablePointer(_this.teleportParabolaHandVisible);
- Pointers.disablePointer(_this.teleportParabolaHandInvisible);
- Pointers.enablePointer(_this.teleportParabolaHeadVisible);
- Pointers.enablePointer(_this.teleportParabolaHeadInvisible);
+ Pointers.disablePointer(_this.teleportParabolaHandVisuals);
+ Pointers.disablePointer(_this.teleportParabolaHandCollisions);
+ Picks.disablePick(_this.teleportHandCollisionPick);
+ Pointers.enablePointer(_this.teleportParabolaHeadVisuals);
+ Pointers.enablePointer(_this.teleportParabolaHeadCollisions);
+ Picks.enablePick(_this.teleportHeadCollisionPick);
} else {
- Pointers.enablePointer(_this.teleportParabolaHandVisible);
- Pointers.enablePointer(_this.teleportParabolaHandInvisible);
- Pointers.disablePointer(_this.teleportParabolaHeadVisible);
- Pointers.disablePointer(_this.teleportParabolaHeadInvisible);
+ Pointers.enablePointer(_this.teleportParabolaHandVisuals);
+ Pointers.enablePointer(_this.teleportParabolaHandCollisions);
+ Picks.enablePick(_this.teleportHandCollisionPick);
+ Pointers.disablePointer(_this.teleportParabolaHeadVisuals);
+ Pointers.disablePointer(_this.teleportParabolaHeadCollisions);
+ Picks.disablePick(_this.teleportHeadCollisionPick);
}
// We do up to 2 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"
+ //
+ // 1. A visible floor. This can be any entity surface that points within some degree of "up"
+ // and where the avatar capsule can be positioned without colliding
+ //
// 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.
+ // The Collision Pick is currently parented to the end overlay on teleportParabolaXXXXCollisions
//
- var result;
+ // TODO
+ // Parent the collision Pick directly to the teleportParabolaXXXXVisuals and get rid of teleportParabolaXXXXCollisions
+ //
+ var result, collisionResult;
if (mode === 'head') {
- result = Pointers.getPrevPickResult(_this.teleportParabolaHeadInvisible);
+ result = Pointers.getPrevPickResult(_this.teleportParabolaHeadCollisions);
+ collisionResult = Picks.getPrevPickResult(_this.teleportHeadCollisionPick);
} else {
- result = Pointers.getPrevPickResult(_this.teleportParabolaHandInvisible);
- }
-
- var teleportLocationType = getTeleportTargetType(result);
- if (teleportLocationType === TARGET.INVISIBLE) {
- if (mode === 'head') {
- result = Pointers.getPrevPickResult(_this.teleportParabolaHeadVisible);
- } else {
- result = Pointers.getPrevPickResult(_this.teleportParabolaHandVisible);
- }
- teleportLocationType = getTeleportTargetType(result);
+ result = Pointers.getPrevPickResult(_this.teleportParabolaHandCollisions);
+ collisionResult = Picks.getPrevPickResult(_this.teleportHandCollisionPick);
}
+
+ var teleportLocationType = getTeleportTargetType(result, collisionResult);
if (teleportLocationType === TARGET.NONE) {
// Use the cancel default state
this.setTeleportState(mode, "cancel", "");
- } else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) {
+ } else if (teleportLocationType === TARGET.INVALID) {
this.setTeleportState(mode, "", "cancel");
- } else if (teleportLocationType === TARGET.SURFACE) {
- this.setTeleportState(mode, "teleport", "");
+ } else if (teleportLocationType === TARGET.COLLIDES) {
+ this.setTeleportState(mode, "cancel", "collision");
+ } else if (teleportLocationType === TARGET.SURFACE || teleportLocationType === TARGET.DISCREPANCY) {
+ this.setTeleportState(mode, "teleport", "collision");
} else if (teleportLocationType === TARGET.SEAT) {
- this.setTeleportState(mode, "", "seat");
+ this.setTeleportState(mode, "collision", "seat");
}
return this.teleport(result, teleportLocationType);
};
@@ -314,10 +395,11 @@ Script.include("/~/system/libraries/controllers.js");
// Do nothing
} else if (target === TARGET.SEAT) {
Entities.callEntityMethod(result.objectID, 'sit');
- } else if (target === TARGET.SURFACE) {
+ } else if (target === TARGET.SURFACE || target === TARGET.DISCREPANCY) {
var offset = getAvatarFootOffset();
result.intersection.y += offset;
- MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false);
+ var shouldLandSafe = target === TARGET.DISCREPANCY;
+ MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false, shouldLandSafe);
HMD.centerUI();
MyAvatar.centerBody();
}
@@ -328,33 +410,38 @@ Script.include("/~/system/libraries/controllers.js");
};
this.disableLasers = function() {
- Pointers.disablePointer(_this.teleportParabolaHandVisible);
- Pointers.disablePointer(_this.teleportParabolaHandInvisible);
- Pointers.disablePointer(_this.teleportParabolaHeadVisible);
- Pointers.disablePointer(_this.teleportParabolaHeadInvisible);
+ Pointers.disablePointer(_this.teleportParabolaHandVisuals);
+ Pointers.disablePointer(_this.teleportParabolaHandCollisions);
+ Pointers.disablePointer(_this.teleportParabolaHeadVisuals);
+ Pointers.disablePointer(_this.teleportParabolaHeadCollisions);
+ Picks.disablePick(_this.teleportHeadCollisionPick);
+ Picks.disablePick(_this.teleportHandCollisionPick);
};
this.setTeleportState = function(mode, visibleState, invisibleState) {
if (mode === 'head') {
- Pointers.setRenderState(_this.teleportParabolaHeadVisible, visibleState);
- Pointers.setRenderState(_this.teleportParabolaHeadInvisible, invisibleState);
+ Pointers.setRenderState(_this.teleportParabolaHeadVisuals, visibleState);
+ Pointers.setRenderState(_this.teleportParabolaHeadCollisions, invisibleState);
} else {
- Pointers.setRenderState(_this.teleportParabolaHandVisible, visibleState);
- Pointers.setRenderState(_this.teleportParabolaHandInvisible, invisibleState);
+ Pointers.setRenderState(_this.teleportParabolaHandVisuals, visibleState);
+ Pointers.setRenderState(_this.teleportParabolaHandCollisions, invisibleState);
}
};
this.setIgnoreEntities = function(entitiesToIgnore) {
- Pointers.setIgnoreItems(this.teleportParabolaHandVisible, entitiesToIgnore);
- Pointers.setIgnoreItems(this.teleportParabolaHandInvisible, entitiesToIgnore);
- Pointers.setIgnoreItems(this.teleportParabolaHeadVisible, entitiesToIgnore);
- Pointers.setIgnoreItems(this.teleportParabolaHeadInvisible, entitiesToIgnore);
+ Pointers.setIgnoreItems(this.teleportParabolaHandVisuals, entitiesToIgnore);
+ Pointers.setIgnoreItems(this.teleportParabolaHandCollisions, entitiesToIgnore);
+ Pointers.setIgnoreItems(this.teleportParabolaHeadVisuals, entitiesToIgnore);
+ Pointers.setIgnoreItems(this.teleportParabolaHeadCollisions, entitiesToIgnore);
+ Picks.setIgnoreItems(_this.teleportHeadCollisionPick, entitiesToIgnore);
+ Picks.setIgnoreItems(_this.teleportHandCollisionPick, entitiesToIgnore);
};
}
// related to repositioning the avatar after you teleport
var FOOT_JOINT_NAMES = ["RightToe_End", "RightToeBase", "RightFoot"];
var DEFAULT_ROOT_TO_FOOT_OFFSET = 0.5;
+
function getAvatarFootOffset() {
// find a valid foot jointIndex
@@ -395,7 +482,29 @@ Script.include("/~/system/libraries/controllers.js");
// than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from your avatar's up, then
// you can't teleport there.
var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
- function getTeleportTargetType(result) {
+ var MAX_DISCREPANCY_DISTANCE = 1.0;
+ var MAX_DOT_SIGN = -0.6;
+
+ function checkForMeshDiscrepancy(result, collisionResult) {
+ var intersectingObjects = collisionResult.intersectingObjects;
+ if (intersectingObjects.length > 0 && intersectingObjects.length < 3) {
+ for (var j = 0; j < collisionResult.intersectingObjects.length; j++) {
+ var intersectingObject = collisionResult.intersectingObjects[j];
+ for (var i = 0; i < intersectingObject.collisionContacts.length; i++) {
+ var normal = intersectingObject.collisionContacts[i].normalOnPick;
+ var distanceToPick = Vec3.distance(intersectingObject.collisionContacts[i].pointOnPick, result.intersection);
+ var normalSign = Vec3.dot(normal, Quat.getUp(MyAvatar.orientation));
+ if ((distanceToPick > MAX_DISCREPANCY_DISTANCE) || (normalSign > MAX_DOT_SIGN)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ function getTeleportTargetType(result, collisionResult) {
if (result.type === Picks.INTERSECTED_NONE) {
return TARGET.NONE;
}
@@ -410,9 +519,14 @@ Script.include("/~/system/libraries/controllers.js");
return TARGET.INVALID;
}
}
-
- if (!props.visible) {
- return TARGET.INVISIBLE;
+ var isDiscrepancy = false;
+ if (collisionResult.collisionRegion != undefined) {
+ if (collisionResult.intersects) {
+ isDiscrepancy = checkForMeshDiscrepancy(result, collisionResult);
+ if (!isDiscrepancy) {
+ return TARGET.COLLIDES;
+ }
+ }
}
var surfaceNormal = result.surfaceNormal;
@@ -420,6 +534,8 @@ Script.include("/~/system/libraries/controllers.js");
if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT) {
return TARGET.INVALID;
+ } else if (isDiscrepancy) {
+ return TARGET.DISCREPANCY;
} else {
return TARGET.SURFACE;
}
@@ -513,7 +629,14 @@ Script.include("/~/system/libraries/controllers.js");
}
}
};
-
+
+ MyAvatar.onLoadComplete.connect(function () {
+ Script.setTimeout(function () {
+ leftTeleporter.initPointers();
+ rightTeleporter.initPointers();
+ }, 500);
+ });
+
Messages.subscribe('Hifi-Teleport-Disabler');
Messages.subscribe('Hifi-Teleport-Ignore-Add');
Messages.subscribe('Hifi-Teleport-Ignore-Remove');