("EndSecondaryCamera", cachedArg);
-}
+}
\ No newline at end of file
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 094b3bb67b..2e9c9fdecd 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -104,6 +104,9 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()");
_myAvatar->update(deltaTime);
+ render::Transaction transaction;
+ _myAvatar->updateRenderItem(transaction);
+ qApp->getMain3DScene()->enqueueTransaction(transaction);
quint64 now = usecTimestampNow();
quint64 dt = now - _lastSendAvatarDataTime;
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 7f5aa00466..6a3d0355f6 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -29,10 +29,25 @@
/**jsdoc
* The AvatarManager API has properties and methods which manage Avatars within the same domain.
+ *
+ * Note: This API is also provided to Interface and client entity scripts as the synonym,
+ * AvatarList
. For assignment client scripts, see the separate {@link AvatarList} API.
+ *
* @namespace AvatarManager
*
* @hifi-interface
* @hifi-client-entity
+ *
+ * @borrows AvatarList.getAvatarIdentifiers as getAvatarIdentifiers
+ * @borrows AvatarList.getAvatarsInRange as getAvatarsInRange
+ * @borrows AvatarList.avatarAddedEvent as avatarAddedEvent
+ * @borrows AvatarList.avatarRemovedEvent as avatarRemovedEvent
+ * @borrows AvatarList.avatarSessionChangedEvent as avatarSessionChangedEvent
+ * @borrows AvatarList.isAvatarInRange as isAvatarInRange
+ * @borrows AvatarList.sessionUUIDChanged as sessionUUIDChanged
+ * @borrows AvatarList.processAvatarDataPacket as processAvatarDataPacket
+ * @borrows AvatarList.processAvatarIdentityPacket as processAvatarIdentityPacket
+ * @borrows AvatarList.processKillAvatar as processKillAvatar
*/
class AvatarManager : public AvatarHashMap {
diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp
index 900c1c0a11..6fc1bd8196 100644
--- a/interface/src/avatar/AvatarMotionState.cpp
+++ b/interface/src/avatar/AvatarMotionState.cpp
@@ -21,6 +21,17 @@ AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisi
_type = MOTIONSTATE_TYPE_AVATAR;
}
+void AvatarMotionState::handleEasyChanges(uint32_t& flags) {
+ ObjectMotionState::handleEasyChanges(flags);
+ if (flags & Simulation::DIRTY_PHYSICS_ACTIVATION && !_body->isActive()) {
+ _body->activate();
+ }
+}
+
+bool AvatarMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
+ return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
+}
+
AvatarMotionState::~AvatarMotionState() {
assert(_avatar);
_avatar = nullptr;
@@ -46,6 +57,9 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
const btCollisionShape* AvatarMotionState::computeNewShape() {
ShapeInfo shapeInfo;
std::static_pointer_cast(_avatar)->computeShapeInfo(shapeInfo);
+ glm::vec3 halfExtents = shapeInfo.getHalfExtents();
+ halfExtents.y = 0.0f;
+ _diameter = 2.0f * glm::length(halfExtents);
return getShapeManager()->getShape(shapeInfo);
}
@@ -60,25 +74,31 @@ void AvatarMotionState::getWorldTransform(btTransform& worldTrans) const {
worldTrans.setRotation(glmToBullet(getObjectRotation()));
if (_body) {
_body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
- _body->setAngularVelocity(glmToBullet(getObjectLinearVelocity()));
+ _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
}
}
// virtual
void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) {
- // HACK: The PhysicsEngine does not actually move OTHER avatars -- instead it slaves their local RigidBody to the transform
- // as specified by a remote simulation. However, to give the remote simulation time to respond to our own objects we tie
- // the other avatar's body to its true position with a simple spring. This is a HACK that will have to be improved later.
const float SPRING_TIMESCALE = 0.5f;
float tau = PHYSICS_ENGINE_FIXED_SUBSTEP / SPRING_TIMESCALE;
btVector3 currentPosition = worldTrans.getOrigin();
- btVector3 targetPosition = glmToBullet(getObjectPosition());
- btTransform newTransform;
- newTransform.setOrigin((1.0f - tau) * currentPosition + tau * targetPosition);
- newTransform.setRotation(glmToBullet(getObjectRotation()));
- _body->setWorldTransform(newTransform);
- _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
- _body->setAngularVelocity(glmToBullet(getObjectLinearVelocity()));
+ btVector3 offsetToTarget = glmToBullet(getObjectPosition()) - currentPosition;
+ float distance = offsetToTarget.length();
+ if ((1.0f - tau) * distance > _diameter) {
+ // the avatar body is far from its target --> slam position
+ btTransform newTransform;
+ newTransform.setOrigin(currentPosition + offsetToTarget);
+ newTransform.setRotation(glmToBullet(getObjectRotation()));
+ _body->setWorldTransform(newTransform);
+ _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
+ _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
+ } else {
+ // the avatar body is near its target --> slam velocity
+ btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget;
+ _body->setLinearVelocity(velocity);
+ _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
+ }
}
// These pure virtual methods must be implemented for each MotionState type
@@ -140,8 +160,13 @@ QUuid AvatarMotionState::getSimulatorID() const {
}
// virtual
-void AvatarMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
+void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
group = BULLET_COLLISION_GROUP_OTHER_AVATAR;
mask = Physics::getDefaultCollisionMask(group);
}
+// virtual
+float AvatarMotionState::getMass() const {
+ return std::static_pointer_cast(_avatar)->computeMass();
+}
+
diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h
index 90bd2a60ac..2738aba8ee 100644
--- a/interface/src/avatar/AvatarMotionState.h
+++ b/interface/src/avatar/AvatarMotionState.h
@@ -23,6 +23,9 @@ class AvatarMotionState : public ObjectMotionState {
public:
AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape);
+ virtual void handleEasyChanges(uint32_t& flags) override;
+ virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
+
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
virtual uint32_t getIncomingDirtyFlags() override;
@@ -62,7 +65,9 @@ public:
void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; }
- virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
+ virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
+
+ virtual float getMass() const override;
friend class AvatarManager;
friend class Avatar;
@@ -76,6 +81,7 @@ protected:
virtual const btCollisionShape* computeNewShape() override;
AvatarSharedPointer _avatar;
+ float _diameter { 0.0f };
uint32_t _dirtyFlags;
};
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index f017ba0527..1ce897fd3a 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -68,8 +68,8 @@ using namespace std;
const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f;
-const float YAW_SPEED_DEFAULT = 75.0f; // degrees/sec
-const float PITCH_SPEED_DEFAULT = 50.0f; // degrees/sec
+const float YAW_SPEED_DEFAULT = 100.0f; // degrees/sec
+const float PITCH_SPEED_DEFAULT = 75.0f; // degrees/sec
const float MAX_BOOST_SPEED = 0.5f * DEFAULT_AVATAR_MAX_WALKING_SPEED; // action motor gets additive boost below this speed
const float MIN_AVATAR_SPEED = 0.05f;
@@ -1145,7 +1145,11 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) {
}
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
- _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
+ return Avatar::setEnableMeshVisible(isEnabled);
+}
+
+bool MyAvatar::getEnableMeshVisible() const {
+ return Avatar::getEnableMeshVisible();
}
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
@@ -1499,7 +1503,10 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModelChangeCount++;
int skeletonModelChangeCount = _skeletonModelChangeCount;
Avatar::setSkeletonModelURL(skeletonModelURL);
- _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
+ _skeletonModel->setTagMask(render::hifi::TAG_NONE);
+ _skeletonModel->setGroupCulled(true);
+ _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene());
+
_headBoneSet.clear();
_cauterizationNeedsUpdate = true;
@@ -2074,14 +2081,12 @@ void MyAvatar::preDisplaySide(const RenderArgs* renderArgs) {
_attachmentData[i].jointName.compare("RightEye", Qt::CaseInsensitive) == 0 ||
_attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 ||
_attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) {
- uint8_t modelRenderTagBits = shouldDrawHead ? render::ItemKey::TAG_BITS_0 : render::ItemKey::TAG_BITS_NONE;
- modelRenderTagBits |= render::ItemKey::TAG_BITS_1;
- _attachmentModels[i]->setVisibleInScene(true, qApp->getMain3DScene(),
- modelRenderTagBits, false);
+ uint8_t modelRenderTagBits = shouldDrawHead ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_SECONDARY_VIEW;
- uint8_t castShadowRenderTagBits = render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1;
- _attachmentModels[i]->setCanCastShadow(true, qApp->getMain3DScene(),
- castShadowRenderTagBits, false);
+ _attachmentModels[i]->setTagMask(modelRenderTagBits);
+ _attachmentModels[i]->setGroupCulled(false);
+ _attachmentModels[i]->setCanCastShadow(true);
+ _attachmentModels[i]->setVisibleInScene(true, qApp->getMain3DScene());
}
}
}
@@ -2265,9 +2270,15 @@ void MyAvatar::updateActionMotor(float deltaTime) {
_actionMotorVelocity = getSensorToWorldScale() * (_walkSpeed.get() * _walkSpeedScalar) * direction;
}
+ float previousBoomLength = _boomLength;
float boomChange = getDriveKey(ZOOM);
- _boomLength += 4.0f * _boomLength * boomChange + boomChange * boomChange;
+ _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
_boomLength = glm::clamp(_boomLength, ZOOM_MIN, ZOOM_MAX);
+
+ // May need to change view if boom length has changed
+ if (previousBoomLength != _boomLength) {
+ qApp->changeViewAsNeeded(_boomLength);
+ }
}
void MyAvatar::updatePosition(float deltaTime) {
@@ -2674,7 +2685,6 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
} else {
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
}
- setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions));
setProperty("lookAtSnappingEnabled", menu->isOptionChecked(MenuOption::EnableLookAtSnapping));
}
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 0d9fd860b7..2eab636a5c 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -124,7 +124,7 @@ class MyAvatar : public Avatar {
* while flying.
* @property {number} hmdRollControlDeadZone=8 - The amount of HMD roll, in degrees, required before your avatar turns if
* hmdRollControlEnabled
is enabled.
- * @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of
+ * @property {number} hmdRollControlRate If hmdRollControlEnabled is true, this value determines the maximum turn rate of
* your avatar when rolling your HMD in degrees per second.
* @property {number} userHeight=1.75 - The height of the user in sensor space.
* @property {number} userEyeHeight=1.65 - The estimated height of the user's eyes in sensor space. Read-only.
@@ -140,9 +140,9 @@ class MyAvatar : public Avatar {
* @property {number} scale
* @property {number} density Read-only.
* @property {Vec3} handPosition
- * @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of MyAvatar. Yaw
- * is sometimes called "heading".
- * @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of MyAvatar. Pitch is
+ * @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of the avatar.
+ * Yaw is sometimes called "heading".
+ * @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of the avatar. Pitch is
* sometimes called "elevation".
* @property {number} bodyRoll - The rotation about an axis running from the chest to the back of the avatar. Roll is
* sometimes called "bank".
@@ -1196,7 +1196,7 @@ public slots:
* @function MyAvatar.getEnableMeshVisible
* @returns {boolean} true
if your avatar's mesh is visible, otherwise false
.
*/
- bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); }
+ bool getEnableMeshVisible() const override;
/**jsdoc
* Set whether or not your avatar mesh is visible.
@@ -1208,7 +1208,7 @@ public slots:
* MyAvatar.setEnableMeshVisible(true);
* }, 10000);
*/
- void setEnableMeshVisible(bool isEnabled);
+ virtual void setEnableMeshVisible(bool isEnabled) override;
/**jsdoc
* @function MyAvatar.setEnableInverseKinematics
@@ -1439,7 +1439,7 @@ private:
SharedSoundPointer _collisionSound;
MyCharacterController _characterController;
- int16_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
+ int32_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
AvatarWeakPointer _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
index 4a7c203f11..87a22396cd 100644
--- a/interface/src/avatar/MySkeletonModel.cpp
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -70,7 +70,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
// rotate the hips back to match the flying animation.
const float TILT_ANGLE = 0.523f;
- const glm::quat tiltRot = glm::angleAxis(TILT_ANGLE, transformVectorFast(avatarToSensorMat, -Vectors::UNIT_X));
+ const glm::quat tiltRot = glm::angleAxis(TILT_ANGLE, glm::normalize(transformVectorFast(avatarToSensorMat, -Vectors::UNIT_X)));
glm::vec3 headPos;
int headIndex = myAvatar->getJointIndex("Head");
diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h
index c2e6c8f113..5aaacd7960 100644
--- a/interface/src/raypick/LaserPointerScriptingInterface.h
+++ b/interface/src/raypick/LaserPointerScriptingInterface.h
@@ -20,24 +20,122 @@ class LaserPointerScriptingInterface : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
+/**jsdoc
+ * Synonym for {@link Pointers} as used for laser pointers.
+ *
+ * @namespace LaserPointers
+ *
+ * @hifi-interface
+ * @hifi-client-entity
+ */
public:
+
+ /**jsdoc
+ * @function LaserPointers.createLaserPointer
+ * @param {Pointers.LaserPointerProperties} properties
+ * @returns {number}
+ */
Q_INVOKABLE unsigned int createLaserPointer(const QVariant& properties) const;
+
+ /**jsdoc
+ * @function LaserPointers.enableLaserPointer
+ * @param {number} id
+ */
Q_INVOKABLE void enableLaserPointer(unsigned int uid) const { DependencyManager::get()->enablePointer(uid); }
+
+ /**jsdoc
+ * @function LaserPointers.disableLaserPointer
+ * @param {number} id
+ */
Q_INVOKABLE void disableLaserPointer(unsigned int uid) const { DependencyManager::get()->disablePointer(uid); }
+
+ /**jsdoc
+ * @function LaserPointers.removeLaserPointer
+ * @param {number} id
+ */
Q_INVOKABLE void removeLaserPointer(unsigned int uid) const { DependencyManager::get()->removePointer(uid); }
+
+ /**jsdoc
+ * @function LaserPointers.editRenderState
+ * @param {number} id
+ * @param {string} renderState
+ * @param {Pointers.RayPointerRenderState} properties
+ */
Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const;
+
+ /**jsdoc
+ * @function LaserPointers.setRenderState
+ * @param {string} renderState
+ * @param {number} id
+ */
Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get()->setRenderState(uid, renderState.toStdString()); }
+
+ /**jsdoc
+ * @function LaserPointers.getPrevRayPickResult
+ * @param {number} id
+ * @returns {RayPickResult}
+ */
Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid) const;
+
+ /**jsdoc
+ * @function LaserPointers.setPrecisionPicking
+ * @param {number} id
+ * @param {boolean} precisionPicking
+ */
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); }
+
+ /**jsdoc
+ * @function LaserPointers.setLaserLength
+ * @param {number} id
+ * @param {number} laserLength
+ */
Q_INVOKABLE void setLaserLength(unsigned int uid, float laserLength) const { DependencyManager::get()->setLength(uid, laserLength); }
+
+ /**jsdoc
+ * @function LaserPointers.setIgnoreItems
+ * @param {number} id
+ * @param {Uuid[]} ignoreItems
+ */
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const;
+
+ /**jsdoc
+ * @function LaserPointers.setIncludeItems
+ * @param {number} id
+ * @param {Uuid[]} includeItems
+ */
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const;
+
+ /**jsdoc
+ * @function LaserPointers.setLockEndUUID
+ * @param {number} id
+ * @param {Uuid} itemID
+ * @param {boolean} isOverlay
+ * @param {Mat4} [offsetMat]
+ */
Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
+
+ /**jsdoc
+ * @function LaserPointers.isLeftHand
+ * @param {number} id
+ * @returns {boolean}
+ */
Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get()->isLeftHand(uid); }
+
+ /**jsdoc
+ * @function LaserPointers.isRightHand
+ * @param {number} id
+ * @returns {boolean}
+ */
Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get()->isRightHand(uid); }
+
+ /**jsdoc
+ * @function LaserPointers.isMouse
+ * @param {number} id
+ * @returns {boolean}
+ */
Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get()->isMouse(uid); }
};
diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp
index 1bf6dd2f8e..8da6e7c615 100644
--- a/interface/src/raypick/PickScriptingInterface.cpp
+++ b/interface/src/raypick/PickScriptingInterface.cpp
@@ -31,6 +31,20 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
}
}
+/**jsdoc
+ * A set of properties that can be passed to {@link Picks.createPick} to create a new Ray Pick.
+ * @typedef {object} Picks.RayPickProperties
+ * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
+ * @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
+ * @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
+ * @property {string} [joint] Only for Joint or Mouse Ray Picks. If "Mouse", it will create a Ray Pick that follows the system mouse, in desktop or HMD.
+ * If "Avatar", it will create a Joint Ray Pick that follows your avatar's head. Otherwise, it will create a Joint Ray Pick that follows the given joint, if it
+ * exists on your current avatar.
+ * @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Ray Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
+ * @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Ray Picks. A local joint direction offset. x = upward, y = forward, z = lateral
+ * @property {Vec3} [position] Only for Static Ray Picks. The world-space origin of the ray.
+ * @property {Vec3} [direction=-Vec3.UP] Only for Static Ray Picks. The world-space direction of the ray.
+ */
unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
@@ -83,6 +97,14 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
return PickManager::INVALID_PICK_ID;
}
+/**jsdoc
+ * A set of properties that can be passed to {@link Picks.createPick} to create a new Stylus Pick.
+ * @typedef {object} Picks.StylusPickProperties
+ * @property {number} [hand=-1] An integer. 0 == left, 1 == right. Invalid otherwise.
+ * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
+ * @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
+ * @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
+ */
unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h
index 2568dd8457..5ef5d27d74 100644
--- a/interface/src/raypick/PickScriptingInterface.h
+++ b/interface/src/raypick/PickScriptingInterface.h
@@ -22,19 +22,23 @@
* @hifi-interface
* @hifi-client-entity
*
- * @property PICK_NOTHING {number} A filter flag. Don't intersect with anything.
- * @property PICK_ENTITIES {number} A filter flag. Include entities when intersecting.
- * @property PICK_OVERLAYS {number} A filter flag. Include overlays when intersecting.
- * @property PICK_AVATARS {number} A filter flag. Include avatars when intersecting.
- * @property PICK_HUD {number} A filter flag. Include the HUD sphere when intersecting in HMD mode.
- * @property PICK_COARSE {number} A filter flag. Pick against coarse meshes, instead of exact meshes.
- * @property PICK_INCLUDE_INVISIBLE {number} A filter flag. Include invisible objects when intersecting.
- * @property PICK_INCLUDE_NONCOLLIDABLE {number} A filter flag. Include non-collidable objects when intersecting.
- * @property INTERSECTED_NONE {number} An intersection type. Intersected nothing with the given filter flags.
- * @property INTERSECTED_ENTITY {number} An intersection type. Intersected an entity.
- * @property INTERSECTED_OVERLAY {number} An intersection type. Intersected an overlay.
- * @property INTERSECTED_AVATAR {number} An intersection type. Intersected an avatar.
- * @property INTERSECTED_HUD {number} An intersection type. Intersected the HUD sphere.
+ * @property {number} PICK_NOTHING A filter flag. Don't intersect with anything. Read-only.
+ * @property {number} PICK_ENTITIES A filter flag. Include entities when intersecting. Read-only.
+ * @property {number} PICK_OVERLAYS A filter flag. Include overlays when intersecting. Read-only.
+ * @property {number} PICK_AVATARS A filter flag. Include avatars when intersecting. Read-only.
+ * @property {number} PICK_HUD A filter flag. Include the HUD sphere when intersecting in HMD mode. Read-only.
+ * @property {number} PICK_COARSE A filter flag. Pick against coarse meshes, instead of exact meshes. Read-only.
+ * @property {number} PICK_INCLUDE_INVISIBLE A filter flag. Include invisible objects when intersecting. Read-only.
+ * @property {number} PICK_INCLUDE_NONCOLLIDABLE A filter flag. Include non-collidable objects when intersecting.
+ * Read-only.
+ * @property {number} PICK_ALL_INTERSECTIONS Read-only.
+ * @property {number} INTERSECTED_NONE An intersection type. Intersected nothing with the given filter flags.
+ * Read-only.
+ * @property {number} INTERSECTED_ENTITY An intersection type. Intersected an entity. Read-only.
+ * @property {number} INTERSECTED_OVERLAY An intersection type. Intersected an overlay. Read-only.
+ * @property {number} INTERSECTED_AVATAR An intersection type. Intersected an avatar. Read-only.
+ * @property {number} INTERSECTED_HUD An intersection type. Intersected the HUD sphere. Read-only.
+ * @property {number} perFrameTimeBudget - The max number of usec to spend per frame updating Pick results. Read-only.
*/
class PickScriptingInterface : public QObject, public Dependency {
@@ -61,46 +65,31 @@ public:
void registerMetaTypes(QScriptEngine* engine);
- /**jsdoc
- * A set of properties that can be passed to {@link Picks.createPick} to create a new Pick.
- *
- * Different {@link Picks.PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
- * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
- *
- * @typedef {Object} Picks.PickProperties
- * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
- * @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
- * @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
- * @property {string} [joint] Only for Joint or Mouse Ray Picks. If "Mouse", it will create a Ray Pick that follows the system mouse, in desktop or HMD.
- * If "Avatar", it will create a Joint Ray Pick that follows your avatar's head. Otherwise, it will create a Joint Ray Pick that follows the given joint, if it
- * exists on your current avatar.
- * @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Ray Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
- * @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Ray Picks. A local joint direction offset. x = upward, y = forward, z = lateral
- * @property {Vec3} [position] Only for Static Ray Picks. The world-space origin of the ray.
- * @property {Vec3} [direction=-Vec3.UP] Only for Static Ray Picks. The world-space direction of the ray.
- * @property {number} [hand=-1] Only for Stylus Picks. An integer. 0 == left, 1 == right. Invalid otherwise.
- */
-
/**jsdoc
* Adds a new Pick.
+ * Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
+ * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
* @function Picks.createPick
- * @param {Picks.PickType} type A PickType that specifies the method of picking to use
- * @param {Picks.PickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
+ * @param {PickType} type A PickType that specifies the method of picking to use
+ * @param {Picks.RayPickProperties|Picks.StylusPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
*/
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
+
/**jsdoc
* Enables a Pick.
* @function Picks.enablePick
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
*/
Q_INVOKABLE void enablePick(unsigned int uid);
+
/**jsdoc
* Disables a Pick.
* @function Picks.disablePick
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
*/
Q_INVOKABLE void disablePick(unsigned int uid);
+
/**jsdoc
* Removes a Pick.
* @function Picks.removePick
@@ -111,7 +100,7 @@ public:
/**jsdoc
* An intersection result for a Ray Pick.
*
- * @typedef {Object} Picks.RayPickResult
+ * @typedef {object} RayPickResult
* @property {number} type The intersection type.
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
@@ -125,7 +114,7 @@ public:
/**jsdoc
* An intersection result for a Stylus Pick.
*
- * @typedef {Object} Picks.StylusPickResult
+ * @typedef {object} StylusPickResult
* @property {number} type The intersection type.
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
@@ -140,7 +129,7 @@ public:
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
* @function Picks.getPrevPickResult
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
- * @returns {PickResult} The most recent intersection result. This will be slightly different for different PickTypes. See {@link Picks.RayPickResult} and {@link Picks.StylusPickResult}.
+ * @returns {RayPickResult|StylusPickResult} The most recent intersection result. This will be different for different PickTypes.
*/
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid);
@@ -151,6 +140,7 @@ public:
* @param {boolean} precisionPicking Whether or not to use precision picking
*/
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
+
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to ignore during intersection. Not used by Stylus Picks.
* @function Picks.setIgnoreItems
@@ -158,6 +148,7 @@ public:
* @param {Uuid[]} ignoreItems A list of IDs to ignore.
*/
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems);
+
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus
* Picks only intersect with objects in their include list.
@@ -174,6 +165,7 @@ public:
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid);
+
/**jsdoc
* Check if a Pick is associated with the right hand.
* @function Picks.isRightHand
@@ -181,6 +173,7 @@ public:
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
*/
Q_INVOKABLE bool isRightHand(unsigned int uid);
+
/**jsdoc
* Check if a Pick is associated with the system mouse.
* @function Picks.isMouse
@@ -189,28 +182,96 @@ public:
*/
Q_INVOKABLE bool isMouse(unsigned int uid);
+ // FIXME: Move to other property definitions.
Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget)
- /**jsdoc
- * The max number of usec to spend per frame updating Pick results.
- * @typedef {number} Picks.perFrameTimeBudget
- */
+
unsigned int getPerFrameTimeBudget() const;
void setPerFrameTimeBudget(unsigned int numUsecs);
public slots:
+
+ /**jsdoc
+ * @function Picks.PICK_NOTHING
+ * @returns {number}
+ */
static constexpr unsigned int PICK_NOTHING() { return 0; }
+
+ /**jsdoc
+ * @function Picks.PICK_ENTITIES
+ * @returns {number}
+ */
static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ENTITIES); }
+
+ /**jsdoc
+ * @function Picks.PICK_OVERLAYS
+ * @returns {number}
+ */
static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_OVERLAYS); }
+
+ /**jsdoc
+ * @function Picks.PICK_AVATARS
+ * @returns {number}
+ */
static constexpr unsigned int PICK_AVATARS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_AVATARS); }
+
+ /**jsdoc
+ * @function Picks.PICK_HUD
+ * @returns {number}
+ */
static constexpr unsigned int PICK_HUD() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_HUD); }
+
+ /**jsdoc
+ * @function Picks.PICK_COARSE
+ * @returns {number}
+ */
static constexpr unsigned int PICK_COARSE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_COARSE); }
+
+ /**jsdoc
+ * @function Picks.PICK_INCLUDE_INVISIBLE
+ * @returns {number}
+ */
static constexpr unsigned int PICK_INCLUDE_INVISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_INCLUDE_INVISIBLE); }
+
+ /**jsdoc
+ * @function Picks.PICK_INCLUDE_NONCOLLIDABLE
+ * @returns {number}
+ */
static constexpr unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_INCLUDE_NONCOLLIDABLE); }
+
+ /**jsdoc
+ * @function Picks.PICK_ALL_INTERSECTIONS
+ * @returns {number}
+ */
static constexpr unsigned int PICK_ALL_INTERSECTIONS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ALL_INTERSECTIONS); }
+
+ /**jsdoc
+ * @function Picks.INTERSECTED_NONE
+ * @returns {number}
+ */
static constexpr unsigned int INTERSECTED_NONE() { return IntersectionType::NONE; }
+
+ /**jsdoc
+ * @function Picks.INTERSECTED_ENTITY
+ * @returns {number}
+ */
static constexpr unsigned int INTERSECTED_ENTITY() { return IntersectionType::ENTITY; }
+
+ /**jsdoc
+ * @function Picks.INTERSECTED_OVERLAY
+ * @returns {number}
+ */
static constexpr unsigned int INTERSECTED_OVERLAY() { return IntersectionType::OVERLAY; }
+
+ /**jsdoc
+ * @function Picks.INTERSECTED_AVATAR
+ * @returns {number}
+ */
static constexpr unsigned int INTERSECTED_AVATAR() { return IntersectionType::AVATAR; }
+
+ /**jsdoc
+ * @function Picks.INTERSECTED_HUD
+ * @returns {number}
+ */
static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; }
};
diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp
index ac5a467e76..4e953a5cb8 100644
--- a/interface/src/raypick/PointerScriptingInterface.cpp
+++ b/interface/src/raypick/PointerScriptingInterface.cpp
@@ -42,6 +42,12 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType&
}
}
+/**jsdoc
+ * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
+ * @typedef {object} Pointers.StylusPointerProperties
+ * @property {boolean} [hover=false] If this pointer should generate hover events.
+ * @property {boolean} [enabled=false]
+ */
unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
@@ -58,6 +64,48 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
return DependencyManager::get()->addPointer(std::make_shared(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled));
}
+/**jsdoc
+ * A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.RayPointerRenderState},
+ * but with an additional distance field.
+ *
+ * @typedef {object} Pointers.DefaultRayPointerRenderState
+ * @augments Pointers.RayPointerRenderState
+ * @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined.
+ */
+/**jsdoc
+ * A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something.
+ *
+ * @typedef {object} Pointers.RayPointerRenderState
+ * @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
+ * @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type
field).
+ * An overlay to represent the beginning of the Ray Pointer, if desired.
+ * @property {Overlays.OverlayProperties} [path] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type
field), which must be "line3d"
.
+ * An overlay to represent the path of the Ray Pointer, if desired.
+ * @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type
field).
+ * An overlay to represent the end of the Ray Pointer, if desired.
+ */
+/**jsdoc
+ * A trigger mechanism for Ray Pointers.
+ *
+ * @typedef {object} Pointers.Trigger
+ * @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button
.
+ * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
+ * it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
+ */
+/**jsdoc
+ * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
+ * @typedef {object} Pointers.LaserPointerProperties
+ * @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar.
+ * @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height.
+ * @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
+ * @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance.
+ * @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale.
+ * @property {boolean} [enabled=false]
+ * @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between.
+ * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection.
+ * @property {boolean} [hover=false] If this Pointer should generate hover events.
+ * @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation.
+ */
unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h
index e7acfd4037..49eb40504d 100644
--- a/interface/src/raypick/PointerScriptingInterface.h
+++ b/interface/src/raypick/PointerScriptingInterface.h
@@ -16,7 +16,7 @@
/**jsdoc
* The Pointers API lets you create and manage objects for repeatedly calculating intersections in different ways, as well as the visual representation of those objects.
- * Pointers can also be configured to automatically generate PointerEvents.
+ * Pointers can also be configured to automatically generate {@link PointerEvent}s on {@link Entities} and {@link Overlays}.
*
* @namespace Pointers
*
@@ -33,59 +33,12 @@ public:
unsigned int createStylus(const QVariant& properties) const;
/**jsdoc
- * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Also contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
- *
+ * Adds a new Pointer
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer.
- *
- * @typedef {Object} Pointers.PointerProperties
- * @property {boolean} [hover=false] If this Pointer should generate hover events.
- * @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar.
- * @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height.
- * @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
- * @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance.
- * @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale.
- * @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between.
- * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection.
- * @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation.
- */
-
- /**jsdoc
- * A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something.
- *
- * @typedef {Object} Pointers.RayPointerRenderState
- * @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
- * @property {OverlayProperties} [start] All of the properties you would normally pass to {@Overlays.addOverlay}, plus the type (as a type
field).
- * An overlay to represent the beginning of the Ray Pointer, if desired.
- * @property {OverlayProperties} [path] All of the properties you would normally pass to {@Overlays.addOverlay}, plus the type (as a type
field), which must be "line3d".
- * An overlay to represent the path of the Ray Pointer, if desired.
- * @property {OverlayProperties} [end] All of the properties you would normally pass to {@Overlays.addOverlay}, plus the type (as a type
field).
- * An overlay to represent the end of the Ray Pointer, if desired.
- */
-
- /**jsdoc
- * A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.RayPointerRenderState},
- * but with an additional distance field.
- *
- * @typedef {Object} Pointers.DefaultRayPointerRenderState
- * @augments Pointers.RayPointerRenderState
- * @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined.
- */
-
- /**jsdoc
- * A trigger mechanism for Ray Pointers.
- *
- * @typedef {Object} Pointers.Trigger
- * @property {Controller.Action} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button
.
- * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
- * it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
- */
-
- /**jsdoc
- * Adds a new Pointer
* @function Pointers.createPointer
- * @param {Picks.PickType} type A PickType that specifies the method of picking to use
- * @param {Pointers.PointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer and the {@link Picks.PickProperties} for the Pick that
+ * @param {PickType} type A PickType that specifies the method of picking to use
+ * @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer and the {@link Picks.PickProperties} for the Pick that
* this Pointer will use to do its picking.
* @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid.
*
@@ -121,32 +74,37 @@ public:
* Pointers.setRenderState(pointer, "test");
*/
Q_INVOKABLE unsigned int createPointer(const PickQuery::PickType& type, const QVariant& properties);
+
/**jsdoc
* Enables a Pointer.
* @function Pointers.enablePointer
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
*/
Q_INVOKABLE void enablePointer(unsigned int uid) const { DependencyManager::get()->enablePointer(uid); }
+
/**jsdoc
* Disables a Pointer.
* @function Pointers.disablePointer
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
*/
Q_INVOKABLE void disablePointer(unsigned int uid) const { DependencyManager::get()->disablePointer(uid); }
+
/**jsdoc
* Removes a Pointer.
* @function Pointers.removePointer
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
*/
Q_INVOKABLE void removePointer(unsigned int uid) const { DependencyManager::get()->removePointer(uid); }
+
/**jsdoc
* Edit some visual aspect of a Pointer. Currently only supported for Ray Pointers.
* @function Pointers.editRenderState
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {string} renderState The name of the render state you want to edit.
- * @param {RenderState} properties The new properties for renderState
. For Ray Pointers, a {@link Pointers.RayPointerRenderState}.
+ * @param {Pointers.RayPointerRenderState} properties The new properties for renderStates
item.
*/
Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const;
+
/**jsdoc
* Set the render state of a Pointer. For Ray Pointers, this means switching between their {@link Pointers.RayPointerRenderState}s, or "" to turn off rendering and hover/trigger events.
* For Stylus Pointers, there are three built-in options: "events on" (render and send events, the default), "events off" (render but don't send events), and "disabled" (don't render, don't send events).
@@ -156,14 +114,16 @@ public:
*/
Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get()->setRenderState(uid, renderState.toStdString()); }
+
/**jsdoc
* Get the most recent pick result from this Pointer. This will be updated as long as the Pointer is enabled, regardless of the render state.
* @function Pointers.getPrevPickResult
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
- * @returns {PickResult} The most recent intersection result. This will be slightly different for different PickTypes. See {@link Picks.RayPickResult} and {@link Picks.StylusPickResult}.
+ * @returns {RayPickResult|StylusPickResult} The most recent intersection result. This will be slightly different for different PickTypes.
*/
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid) const;
+
/**jsdoc
* Sets whether or not to use precision picking.
* @function Pointers.setPrecisionPicking
@@ -171,6 +131,7 @@ public:
* @param {boolean} precisionPicking Whether or not to use precision picking
*/
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); }
+
/**jsdoc
* Sets the length of this Pointer. No effect on Stylus Pointers.
* @function Pointers.setLength
@@ -178,6 +139,7 @@ public:
* @param {float} length The desired length of the Pointer.
*/
Q_INVOKABLE void setLength(unsigned int uid, float length) const { DependencyManager::get()->setLength(uid, length); }
+
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to ignore during intersection. Not used by Stylus Pointers.
* @function Pointers.setIgnoreItems
@@ -185,6 +147,7 @@ public:
* @param {Uuid[]} ignoreItems A list of IDs to ignore.
*/
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const;
+
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus
* Pointers only intersect with objects in their include list.
@@ -194,17 +157,19 @@ public:
*/
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const;
+
/**jsdoc
* Lock a Pointer onto a specific object (overlay, entity, or avatar). Optionally, provide an offset in object-space, otherwise the Pointer will lock on to the center of the object.
* Not used by Stylus Pointers.
* @function Pointers.setLockEndUUID
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
- * @param {QUuid} objectID The ID of the object to which to lock on.
+ * @param {Uuid} objectID The ID of the object to which to lock on.
* @param {boolean} isOverlay False for entities or avatars, true for overlays
* @param {Mat4} [offsetMat] The offset matrix to use if you do not want to lock on to the center of the object.
*/
Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
+
/**jsdoc
* Check if a Pointer is associated with the left hand.
* @function Pointers.isLeftHand
@@ -212,6 +177,7 @@ public:
* @returns {boolean} True if the Pointer is a Joint Ray Pointer with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pointer with hand == 0
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get()->isLeftHand(uid); }
+
/**jsdoc
* Check if a Pointer is associated with the right hand.
* @function Pointers.isRightHand
@@ -219,6 +185,7 @@ public:
* @returns {boolean} True if the Pointer is a Joint Ray Pointer with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pointer with hand == 1
*/
Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get()->isRightHand(uid); }
+
/**jsdoc
* Check if a Pointer is associated with the system mouse.
* @function Pointers.isMouse
diff --git a/interface/src/raypick/RayPickScriptingInterface.h b/interface/src/raypick/RayPickScriptingInterface.h
index 3795f191b3..d5e224018e 100644
--- a/interface/src/raypick/RayPickScriptingInterface.h
+++ b/interface/src/raypick/RayPickScriptingInterface.h
@@ -18,6 +18,30 @@
#include "PickScriptingInterface.h"
+/**jsdoc
+ * Synonym for {@link Picks} as used for ray picks.
+ *
+ * @namespace RayPick
+ *
+ * @hifi-interface
+ * @hifi-client-entity
+ *
+ * @property {number} PICK_NOTHING Read-only.
+ * @property {number} PICK_ENTITIES Read-only.
+ * @property {number} PICK_OVERLAYS Read-only.
+ * @property {number} PICK_AVATARS Read-only.
+ * @property {number} PICK_HUD Read-only.
+ * @property {number} PICK_COARSE Read-only.
+ * @property {number} PICK_INCLUDE_INVISIBLE Read-only.
+ * @property {number} PICK_INCLUDE_NONCOLLIDABLE Read-only.
+ * @property {number} PICK_ALL_INTERSECTIONS Read-only.
+ * @property {number} INTERSECTED_NONE Read-only.
+ * @property {number} INTERSECTED_ENTITY Read-only.
+ * @property {number} INTERSECTED_OVERLAY Read-only.
+ * @property {number} INTERSECTED_AVATAR Read-only.
+ * @property {number} INTERSECTED_HUD Read-only.
+ */
+
class RayPickScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(unsigned int PICK_NOTHING READ PICK_NOTHING CONSTANT)
@@ -37,34 +61,167 @@ class RayPickScriptingInterface : public QObject, public Dependency {
SINGLETON_DEPENDENCY
public:
+
+ /**jsdoc
+ * @function RayPick.createRayPick
+ * @param {Picks.RayPickProperties}
+ * @returns {number}
+ */
Q_INVOKABLE unsigned int createRayPick(const QVariant& properties);
+
+ /**jsdoc
+ * @function RayPick.enableRayPick
+ * @param {number} id
+ */
Q_INVOKABLE void enableRayPick(unsigned int uid);
+
+ /**jsdoc
+ * @function RayPick.disableRayPick
+ * @param {number} id
+ */
Q_INVOKABLE void disableRayPick(unsigned int uid);
+
+ /**jsdoc
+ * @function RayPick.removeRayPick
+ * @param {number} id
+ */
Q_INVOKABLE void removeRayPick(unsigned int uid);
+
+ /**jsdoc
+ * @function RayPick.getPrevRayPickResult
+ * @param {number} id
+ * @returns {RayPickResult}
+ */
Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid);
+
+ /**jsdoc
+ * @function RayPick.setPrecisionPicking
+ * @param {number} id
+ * @param {boolean} precisionPicking
+ */
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
+
+ /**jsdoc
+ * @function RayPick.setIgnoreItems
+ * @param {number} id
+ * @param {Uuid[]) ignoreEntities
+ */
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities);
+
+ /**jsdoc
+ * @function RayPick.setIncludeItems
+ * @param {number} id
+ * @param {Uuid[]) includeEntities
+ */
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities);
+
+ /**jsdoc
+ * @function RayPick.isLeftHand
+ * @param {number} id
+ * @returns {boolean}
+ */
Q_INVOKABLE bool isLeftHand(unsigned int uid);
+
+ /**jsdoc
+ * @function RayPick.isRightHand
+ * @param {number} id
+ * @returns {boolean}
+ */
Q_INVOKABLE bool isRightHand(unsigned int uid);
+
+ /**jsdoc
+ * @function RayPick.isMouse
+ * @param {number} id
+ * @returns {boolean}
+ */
Q_INVOKABLE bool isMouse(unsigned int uid);
public slots:
+
+ /**jsdoc
+ * @function RayPick.PICK_NOTHING
+ * @returns {number}
+ */
static unsigned int PICK_NOTHING() { return PickScriptingInterface::PICK_NOTHING(); }
+
+ /**jsdoc
+ * @function RayPick.PICK_ENTITIES
+ * @returns {number}
+ */
static unsigned int PICK_ENTITIES() { return PickScriptingInterface::PICK_ENTITIES(); }
+
+ /**jsdoc
+ * @function RayPick.PICK_OVERLAYS
+ * @returns {number}
+ */
static unsigned int PICK_OVERLAYS() { return PickScriptingInterface::PICK_OVERLAYS(); }
+
+ /**jsdoc
+ * @function RayPick.PICK_AVATARS
+ * @returns {number}
+ */
static unsigned int PICK_AVATARS() { return PickScriptingInterface::PICK_AVATARS(); }
+
+ /**jsdoc
+ * @function RayPick.PICK_HUD
+ * @returns {number}
+ */
static unsigned int PICK_HUD() { return PickScriptingInterface::PICK_HUD(); }
+
+ /**jsdoc
+ * @function RayPick.PICK_COARSE
+ * @returns {number}
+ */
static unsigned int PICK_COARSE() { return PickScriptingInterface::PICK_COARSE(); }
+
+ /**jsdoc
+ * @function RayPick.PICK_INCLUDE_INVISIBLE
+ * @returns {number}
+ */
static unsigned int PICK_INCLUDE_INVISIBLE() { return PickScriptingInterface::PICK_INCLUDE_INVISIBLE(); }
+
+ /**jsdoc
+ * @function RayPick.PICK_INCLUDE_NONCOLLIDABLE
+ * @returns {number}
+ */
static unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE(); }
+
+ /**jsdoc
+ * @function RayPick.PICK_ALL_INTERSECTIONS
+ * @returns {number}
+ */
static unsigned int PICK_ALL_INTERSECTIONS() { return PickScriptingInterface::PICK_ALL_INTERSECTIONS(); }
+
+ /**jsdoc
+ * @function RayPick.INTERSECTED_NONE
+ * @returns {number}
+ */
static unsigned int INTERSECTED_NONE() { return PickScriptingInterface::INTERSECTED_NONE(); }
+
+ /**jsdoc
+ * @function RayPick.INTERSECTED_ENTITY
+ * @returns {number}
+ */
static unsigned int INTERSECTED_ENTITY() { return PickScriptingInterface::INTERSECTED_ENTITY(); }
+
+ /**jsdoc
+ * @function RayPick.INTERSECTED_OVERLAY
+ * @returns {number}
+ */
static unsigned int INTERSECTED_OVERLAY() { return PickScriptingInterface::INTERSECTED_OVERLAY(); }
+
+ /**jsdoc
+ * @function RayPick.INTERSECTED_AVATAR
+ * @returns {number}
+ */
static unsigned int INTERSECTED_AVATAR() { return PickScriptingInterface::INTERSECTED_AVATAR(); }
+
+ /**jsdoc
+ * @function RayPick.INTERSECTED_HUD
+ * @returns {number}
+ */
static unsigned int INTERSECTED_HUD() { return PickScriptingInterface::INTERSECTED_HUD(); }
};
diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
index f0a4328c2f..8d16b06995 100644
--- a/interface/src/scripting/Audio.h
+++ b/interface/src/scripting/Audio.h
@@ -89,7 +89,7 @@ public:
/**jsdoc
* @function Audio.setReverbOptions
- * @param {} options
+ * @param {AudioEffectOptions} options
*/
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp
index 9e7c0e142e..430441226f 100644
--- a/interface/src/scripting/TestScriptingInterface.cpp
+++ b/interface/src/scripting/TestScriptingInterface.cpp
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -160,3 +161,33 @@ void TestScriptingInterface::clearCaches() {
qApp->reloadResourceCaches();
}
+// Writes a JSON object from javascript to a file
+void TestScriptingInterface::saveObject(QVariant variant, const QString& filename) {
+ if (_testResultsLocation.isNull()) {
+ return;
+ }
+
+ QJsonDocument jsonDocument;
+ jsonDocument = QJsonDocument::fromVariant(variant);
+ if (jsonDocument.isNull()) {
+ return;
+ }
+
+ QByteArray jsonData = jsonDocument.toJson();
+
+ // Append trailing slash if needed
+ if (_testResultsLocation.right(1) != "/") {
+ _testResultsLocation += "/";
+ }
+
+ QString filepath = QDir::cleanPath(_testResultsLocation + filename);
+ QFile file(filepath);
+
+ file.open(QFile::WriteOnly);
+ file.write(jsonData);
+ file.close();
+}
+
+void TestScriptingInterface::showMaximized() {
+ qApp->getWindow()->showMaximized();
+}
\ No newline at end of file
diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h
index 687cb41689..c47e39d1f3 100644
--- a/interface/src/scripting/TestScriptingInterface.h
+++ b/interface/src/scripting/TestScriptingInterface.h
@@ -18,73 +18,140 @@ class QScriptValue;
class TestScriptingInterface : public QObject {
Q_OBJECT
+public:
+ void setTestResultsLocation(const QString path) { _testResultsLocation = path; }
+ const QString& getTestResultsLocation() { return _testResultsLocation; };
+
public slots:
static TestScriptingInterface* getInstance();
/**jsdoc
* Exits the application
+ * @function Test.quit
*/
void quit();
/**jsdoc
* Waits for all texture transfers to be complete
+ * @function Test.waitForTextureIdle
*/
void waitForTextureIdle();
/**jsdoc
* Waits for all pending downloads to be complete
+ * @function Test.waitForDownloadIdle
*/
void waitForDownloadIdle();
/**jsdoc
* Waits for all file parsing operations to be complete
+ * @function Test.waitForProcessingIdle
*/
void waitForProcessingIdle();
/**jsdoc
* Waits for all pending downloads, parsing and texture transfers to be complete
+ * @function Test.waitIdle
*/
void waitIdle();
-
+ /**jsdoc
+ * Waits for establishment of connection to server
+ * @function Test.waitForConnection
+ * @param {int} maxWaitMs [default=10000] - Number of milliseconds to wait
+ */
bool waitForConnection(qint64 maxWaitMs = 10000);
+ /**jsdoc
+ * Waits a specific number of milliseconds
+ * @function Test.wait
+ * @param {int} milliseconds - Number of milliseconds to wait
+ */
void wait(int milliseconds);
+ /**jsdoc
+ * Waits for all pending downloads, parsing and texture transfers to be complete
+ * @function Test.loadTestScene
+ * @param {string} sceneFile - URL of scene to load
+ */
bool loadTestScene(QString sceneFile);
+ /**jsdoc
+ * Clears all caches
+ * @function Test.clear
+ */
void clear();
/**jsdoc
* Start recording Chrome compatible tracing events
* logRules can be used to specify a set of logging category rules to limit what gets captured
+ * @function Test.startTracing
+ * @param {string} logrules [defaultValue=""] - See implementation for explanation
*/
bool startTracing(QString logrules = "");
/**jsdoc
* Stop recording Chrome compatible tracing events and serialize recorded events to a file
* Using a filename with a .gz extension will automatically compress the output file
+ * @function Test.stopTracing
+ * @param {string} filename - Name of file to save to
+ * @returns {bool} True if successful.
*/
bool stopTracing(QString filename);
+ /**jsdoc
+ * Starts a specific trace event
+ * @function Test.startTraceEvent
+ * @param {string} name - Name of event
+ */
void startTraceEvent(QString name);
+ /**jsdoc
+ * Stop a specific name event
+ * Using a filename with a .gz extension will automatically compress the output file
+ * @function Test.endTraceEvent
+ * @param {string} filename - Name of event
+ */
void endTraceEvent(QString name);
/**jsdoc
* Write detailed timing stats of next physics stepSimulation() to filename
+ * @function Test.savePhysicsSimulationStats
+ * @param {string} filename - Name of file to save to
*/
void savePhysicsSimulationStats(QString filename);
+ /**jsdoc
+ * Profiles a specific function
+ * @function Test.savePhysicsSimulationStats
+ * @param {string} name - Name used to reference the function
+ * @param {function} function - Function to profile
+ */
Q_INVOKABLE void profileRange(const QString& name, QScriptValue function);
/**jsdoc
* Clear all caches (menu command Reload Content)
+ * @function Test.clearCaches
*/
void clearCaches();
+ /**jsdoc
+ * Save a JSON object to a file in the test results location
+ * @function Test.saveObject
+ * @param {string} name - Name of the object
+ * @param {string} filename - Name of file to save to
+ */
+ void saveObject(QVariant v, const QString& filename);
+
+ /**jsdoc
+ * Maximizes the window
+ * @function Test.showMaximized
+ */
+ void showMaximized();
+
private:
bool waitForCondition(qint64 maxWaitMs, std::function condition);
+ QString _testResultsLocation;
};
-#endif // hifi_TestScriptingInterface_h
+#endif // hifi_TestScriptingInterface_h
diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h
index 9e40aad087..25955ca7a3 100644
--- a/interface/src/scripting/WalletScriptingInterface.h
+++ b/interface/src/scripting/WalletScriptingInterface.h
@@ -30,6 +30,14 @@ public:
};
+/**jsdoc
+ * @namespace Wallet
+ *
+ * @hifi-interface
+ * @hifi-client-entity
+ *
+ * @property {number} walletStatus
+ */
class WalletScriptingInterface : public QObject, public Dependency {
Q_OBJECT
@@ -38,17 +46,53 @@ class WalletScriptingInterface : public QObject, public Dependency {
public:
WalletScriptingInterface();
+ /**jsdoc
+ * @function Wallet.refreshWalletStatus
+ */
Q_INVOKABLE void refreshWalletStatus();
+
+ /**jsdoc
+ * @function Wallet.getWalletStatus
+ * @returns {number}
+ */
Q_INVOKABLE uint getWalletStatus() { return _walletStatus; }
+
+ /**jsdoc
+ * @function Wallet.proveAvatarEntityOwnershipVerification
+ * @param {Uuid} entityID
+ */
Q_INVOKABLE void proveAvatarEntityOwnershipVerification(const QUuid& entityID);
+
// setWalletStatus() should never be made Q_INVOKABLE. If it were,
// scripts could cause the Wallet to incorrectly report its status.
void setWalletStatus(const uint& status);
signals:
+
+ /**jsdoc
+ * @function Wallet.walletStatusChanged
+ * @returns {Signal}
+ */
void walletStatusChanged();
+
+ /**jsdoc
+ * @function Wallet.walletNotSetup
+ * @returns {Signal}
+ */
void walletNotSetup();
+
+ /**jsdoc
+ * @function Wallet.ownershipVerificationSuccess
+ * @param {Uuid} entityID
+ * @returns {Signal}
+ */
void ownershipVerificationSuccess(const QUuid& entityID);
+
+ /**jsdoc
+ * @function Wallet.ownershipVerificationFailed
+ * @param {Uuid} entityID
+ * @returns {Signal}
+ */
void ownershipVerificationFailed(const QUuid& entityID);
private:
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index 9c46f9e98a..0aea7a02c5 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -15,12 +15,13 @@
#include
#include
#include
-
+#include
#include
#include
#include
-
+#include
+#include "AndroidHelper.h"
#include "Application.h"
#include "DomainHandler.h"
#include "MainWindow.h"
@@ -131,6 +132,24 @@ void WindowScriptingInterface::disconnectedFromDomain() {
emit domainChanged(QUrl());
}
+void WindowScriptingInterface::openUrl(const QUrl& url) {
+ if (!url.isEmpty()) {
+ if (url.scheme() == URL_SCHEME_HIFI) {
+ DependencyManager::get()->handleLookupString(url.toString());
+ } else {
+ // address manager did not handle - ask QDesktopServices to handle
+ QDesktopServices::openUrl(url);
+ }
+ }
+}
+
+void WindowScriptingInterface::openAndroidActivity(const QString& activityName, const bool backToScene) {
+#if defined(Q_OS_ANDROID)
+ AndroidHelper::instance().requestActivity(activityName, backToScene);
+#endif
+}
+
+
QString fixupPathForMac(const QString& directory) {
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
// filename if the directory is valid.
@@ -427,8 +446,12 @@ void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, f
qApp->takeSnapshot(notify, includeAnimated, aspectRatio, filename);
}
-void WindowScriptingInterface::takeSecondaryCameraSnapshot(const QString& filename) {
- qApp->takeSecondaryCameraSnapshot(filename);
+void WindowScriptingInterface::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) {
+ qApp->takeSecondaryCameraSnapshot(notify, filename);
+}
+
+void WindowScriptingInterface::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) {
+ qApp->takeSecondaryCamera360Snapshot(cameraPosition, cubemapOutputFormat, notify, filename);
}
void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) {
@@ -499,7 +522,7 @@ int WindowScriptingInterface::openMessageBox(QString title, QString text, int bu
* RestoreDefaults | 0x8000000 | "Restore Defaults" |
*
*
- * @typedef Window.MessageBoxButton
+ * @typedef {number} Window.MessageBoxButton
*/
int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) {
auto messageBox = DependencyManager::get()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text,
diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h
index 348882e0f8..452d9681ca 100644
--- a/interface/src/scripting/WindowScriptingInterface.h
+++ b/interface/src/scripting/WindowScriptingInterface.h
@@ -362,13 +362,31 @@ public slots:
* Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API.
* NOTE: to provide a non-default value - all previous parameters must be provided.
* @function Window.takeSecondaryCameraSnapshot
+ * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
+ * signal.
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'.
* If this parameter is ""
then the image will be saved as ".jpg".
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
*
* var filename = QString();
*/
- void takeSecondaryCameraSnapshot(const QString& filename = QString());
+ void takeSecondaryCameraSnapshot(const bool& notify = true, const QString& filename = QString());
+
+ /**jsdoc
+ * Takes a 360 snapshot given a position of the secondary camera (which does not need to have been previously set up).
+ * @function Window.takeSecondaryCamera360Snapshot
+ * @param {vec3} [cameraPosition] - The (x, y, z) position of the camera for the 360 snapshot
+ * @param {boolean} [cubemapOutputFormat=false] - If true
then the snapshot is saved as a cube map image,
+ * otherwise is saved as an equirectangular image.
+ * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
+ * signal.
+ * @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'.
+ * If this parameter is ""
then the image will be saved as ".jpg".
+ * Otherwise, the image will be saved to this filename, with an appended ".jpg".
+ *
+ * var filename = QString();
+ */
+ void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat = false, const bool& notify = true, const QString& filename = QString());
/**jsdoc
* Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that
@@ -456,7 +474,7 @@ public slots:
*
*
*
- * @typedef Window.DisplayTexture
+ * @typedef {string} Window.DisplayTexture
*/
bool setDisplayTexture(const QString& name);
@@ -508,6 +526,25 @@ public slots:
*/
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
+ /**jsdoc
+ * Open a URL in the Interface window or other application, depending on the URL's scheme. If the URL starts with
+ * hifi://
then that URL is navigated to in Interface, otherwise the URL is opened in the application the OS
+ * associates with the URL's scheme (e.g., a Web browser for http://
).
+ * @function Window.openUrl
+ * @param {string} url - The URL to open.
+ */
+ void openUrl(const QUrl& url);
+
+ /**jsdoc
+ * Open an Android activity and optionally return back to the scene when the activity is completed. Android only.
+ * @function Window.openAndroidActivity
+ * @param {string} activityName - The name of the activity to open: one of "Home"
, "Login"
, or
+ * "Privacy Policy"
.
+ * @param {boolean} backToScene - If true
, the user is automatically returned back to the scene when the
+ * activity is completed.
+ */
+ void openAndroidActivity(const QString& activityName, const bool backToScene);
+
/**jsdoc
* Update the content of a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
* @function Window.updateMessageBox
@@ -578,6 +615,16 @@ signals:
*/
void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify);
+ /**jsdoc
+ * Triggered when a still equirectangular snapshot has been taken by calling {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}
+ * @function Window.snapshot360Taken
+ * @param {string} pathStillSnapshot - The path and name of the snapshot image file.
+ * @param {boolean} notify - The value of the notify
parameter that {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}
+ * was called with.
+ * @returns {Signal}
+ */
+ void snapshot360Taken(const QString& path360Snapshot, bool notify);
+
/**jsdoc
* Triggered when a snapshot submitted via {@link Window.shareSnapshot|shareSnapshot} is ready for sharing. The snapshot
* may then be shared via the {@link Account.metaverseServerURL} Web API.
diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp
index 9a7ebdf784..ea660fb0e2 100644
--- a/interface/src/ui/ApplicationOverlay.cpp
+++ b/interface/src/ui/ApplicationOverlay.cpp
@@ -179,7 +179,7 @@ static const auto DEPTH_FORMAT = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPT
void ApplicationOverlay::buildFramebufferObject() {
PROFILE_RANGE(app, __FUNCTION__);
- auto uiSize = qApp->getUiSize();
+ auto uiSize = glm::uvec2(glm::vec2(qApp->getUiSize()) * qApp->getRenderResolutionScale());
if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) {
_overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("ApplicationOverlay"));
}
diff --git a/interface/src/ui/HMDToolsDialog.cpp b/interface/src/ui/HMDToolsDialog.cpp
index 63794da60f..fb49c4f4c4 100644
--- a/interface/src/ui/HMDToolsDialog.cpp
+++ b/interface/src/ui/HMDToolsDialog.cpp
@@ -86,7 +86,7 @@ HMDToolsDialog::HMDToolsDialog(QWidget* parent) :
if (dialogsManager->getLodToolsDialog()) {
watchWindow(dialogsManager->getLodToolsDialog()->windowHandle());
}
-
+
connect(_switchModeButton, &QPushButton::clicked, [this]{
toggleHMDMode();
});
diff --git a/interface/src/ui/LodToolsDialog.cpp b/interface/src/ui/LodToolsDialog.cpp
index 71e5293f30..e2f2d9e011 100644
--- a/interface/src/ui/LodToolsDialog.cpp
+++ b/interface/src/ui/LodToolsDialog.cpp
@@ -130,4 +130,3 @@ void LodToolsDialog::closeEvent(QCloseEvent* event) {
#endif
}
-
diff --git a/interface/src/ui/LodToolsDialog.h b/interface/src/ui/LodToolsDialog.h
index b2390a1cd7..3ae59661dc 100644
--- a/interface/src/ui/LodToolsDialog.h
+++ b/interface/src/ui/LodToolsDialog.h
@@ -52,4 +52,4 @@ private:
QLabel* _feedback;
};
-#endif // hifi_LodToolsDialog_h
+#endif // hifi_LodToolsDialog_h
\ No newline at end of file
diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp
index 2e40d3c087..39a5849d85 100644
--- a/interface/src/ui/LoginDialog.cpp
+++ b/interface/src/ui/LoginDialog.cpp
@@ -31,10 +31,13 @@ HIFI_QML_DEF(LoginDialog)
LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) {
auto accountManager = DependencyManager::get();
+#if !defined(Q_OS_ANDROID)
connect(accountManager.data(), &AccountManager::loginComplete,
this, &LoginDialog::handleLoginCompleted);
connect(accountManager.data(), &AccountManager::loginFailed,
this, &LoginDialog::handleLoginFailed);
+#endif
+
}
void LoginDialog::showWithSelection()
diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
index 4c233b986c..3d3c432e92 100644
--- a/interface/src/ui/PreferencesDialog.cpp
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
#include
#include "Application.h"
@@ -52,33 +53,100 @@ void setupPreferences() {
preferences->addPreference(preference);
}
+ // Graphics quality
+ static const QString GRAPHICS_QUALITY { "Graphics Quality" };
{
- auto getter = [=]()->bool { return myAvatar->getSnapTurn(); };
- auto setter = [=](bool value) { myAvatar->setSnapTurn(value); };
- preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter));
- }
- {
- auto getter = [=]()->bool { return myAvatar->getClearOverlayWhenMoving(); };
- auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
- preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter));
+ static const float MAX_DESKTOP_FPS = 60;
+ static const float MAX_HMD_FPS = 90;
+ static const float MIN_FPS = 10;
+ static const float LOW = 0.25f;
+ static const float MEDIUM = 0.5f;
+ static const float HIGH = 0.75f;
+ auto getter = []()->float {
+ auto lodManager = DependencyManager::get();
+ bool inHMD = qApp->isHMDMode();
+
+ float increaseFPS = 0;
+ if (inHMD) {
+ increaseFPS = lodManager->getHMDLODDecreaseFPS();
+ } else {
+ increaseFPS = lodManager->getDesktopLODDecreaseFPS();
+ }
+ float maxFPS = inHMD ? MAX_HMD_FPS : MAX_DESKTOP_FPS;
+ float percentage = increaseFPS / maxFPS;
+
+ if (percentage >= HIGH) {
+ return LOW;
+ } else if (percentage >= LOW) {
+ return MEDIUM;
+ }
+ return HIGH;
+ };
+
+ auto setter = [](float value) {
+ static const float THRASHING_DIFFERENCE = 10;
+ auto lodManager = DependencyManager::get();
+
+ bool isLowestValue = value == LOW;
+ bool isHMDMode = qApp->isHMDMode();
+
+ float maxFPS = isHMDMode ? MAX_HMD_FPS : MAX_DESKTOP_FPS;
+ float desiredFPS = maxFPS - THRASHING_DIFFERENCE;
+
+ if (!isLowestValue) {
+ float calculatedFPS = (maxFPS - (maxFPS * value)) - THRASHING_DIFFERENCE;
+ desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS;
+ }
+
+ if (isHMDMode) {
+ lodManager->setHMDLODDecreaseFPS(desiredFPS);
+ } else {
+ lodManager->setDesktopLODDecreaseFPS(desiredFPS);
+ }
+ };
+
+ auto wodSlider = new SliderPreference(GRAPHICS_QUALITY, "World Detail", getter, setter);
+ wodSlider->setMin(0.25f);
+ wodSlider->setMax(0.75f);
+ wodSlider->setStep(0.25f);
+ preferences->addPreference(wodSlider);
+
+ auto getterShadow = []()->bool {
+ auto menu = Menu::getInstance();
+ return menu->isOptionChecked(MenuOption::Shadows);
+ };
+ auto setterShadow = [](bool value) {
+ auto menu = Menu::getInstance();
+ menu->setIsOptionChecked(MenuOption::Shadows, value);
+ };
+ preferences->addPreference(new CheckPreference(GRAPHICS_QUALITY, "Show Shadows", getterShadow, setterShadow));
}
// UI
- static const QString UI_CATEGORY { "UI" };
+ static const QString UI_CATEGORY { "User Interface" };
{
auto getter = []()->bool { return qApp->getSettingConstrainToolbarPosition(); };
auto setter = [](bool value) { qApp->setSettingConstrainToolbarPosition(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Constrain Toolbar Position to Horizontal Center", getter, setter));
}
+
{
- auto getter = []()->float { return qApp->getHMDTabletScale(); };
- auto setter = [](float value) { qApp->setHMDTabletScale(value); };
- auto preference = new SpinnerPreference(UI_CATEGORY, "HMD Tablet Scale %", getter, setter);
+ auto getter = []()->float { return qApp->getDesktopTabletScale(); };
+ auto setter = [](float value) { qApp->setDesktopTabletScale(value); };
+ auto preference = new SpinnerPreference(UI_CATEGORY, "Desktop Tablet Scale %", getter, setter);
preference->setMin(20);
preference->setMax(500);
preferences->addPreference(preference);
}
+ {
+ auto getter = []()->float { return qApp->getHMDTabletScale(); };
+ auto setter = [](float value) { qApp->setHMDTabletScale(value); };
+ auto preference = new SpinnerPreference(UI_CATEGORY, "VR Tablet Scale %", getter, setter);
+ preference->setMin(20);
+ preference->setMax(500);
+ preferences->addPreference(preference);
+ }
{
auto getter = []()->bool { return qApp->getPreferStylusOverLaser(); };
@@ -86,19 +154,24 @@ void setupPreferences() {
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Stylus Over Laser", getter, setter));
}
- static const QString ADVANCED_UI_CATEGORY { "Advanced UI" };
{
- auto getter = []()->float { return qApp->getDesktopTabletScale(); };
- auto setter = [](float value) { qApp->setDesktopTabletScale(value); };
- auto preference = new SpinnerPreference(ADVANCED_UI_CATEGORY, "Desktop Tablet Scale %", getter, setter);
- preference->setMin(20);
- preference->setMax(500);
- preferences->addPreference(preference);
+ static const QString RETICLE_ICON_NAME = { Cursor::Manager::getIconName(Cursor::Icon::RETICLE) };
+ auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; };
+ auto setter = [](bool value) { qApp->setPreferredCursor(value ? RETICLE_ICON_NAME : QString()); };
+ preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use reticle cursor instead of arrow", getter, setter));
}
+
+ {
+ auto getter = [=]()->bool { return myAvatar->getClearOverlayWhenMoving(); };
+ auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
+ preferences->addPreference(new CheckPreference(UI_CATEGORY, "Clear overlays when moving", getter, setter));
+ }
+
+ static const QString VIEW_CATEGORY{ "View" };
{
auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); };
auto setter = [=](float value) { myAvatar->setRealWorldFieldOfView(value); };
- auto preference = new SpinnerPreference(ADVANCED_UI_CATEGORY, "Real world vertical field of view (angular size of monitor)", getter, setter);
+ auto preference = new SpinnerPreference(VIEW_CATEGORY, "Real world vertical field of view (angular size of monitor)", getter, setter);
preference->setMin(1);
preference->setMax(180);
preferences->addPreference(preference);
@@ -106,7 +179,7 @@ void setupPreferences() {
{
auto getter = []()->float { return qApp->getFieldOfView(); };
auto setter = [](float value) { qApp->setFieldOfView(value); };
- auto preference = new SpinnerPreference(ADVANCED_UI_CATEGORY, "Vertical field of view", getter, setter);
+ auto preference = new SpinnerPreference(VIEW_CATEGORY, "Vertical field of view", getter, setter);
preference->setMin(1);
preference->setMax(180);
preference->setStep(1);
@@ -122,18 +195,12 @@ void setupPreferences() {
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter));
}
*/
- {
- static const QString RETICLE_ICON_NAME = { Cursor::Manager::getIconName(Cursor::Icon::RETICLE) };
- auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; };
- auto setter = [](bool value) { qApp->setPreferredCursor(value ? RETICLE_ICON_NAME : QString()); };
- preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use reticle cursor instead of arrow", getter, setter));
- }
// Snapshots
static const QString SNAPSHOTS { "Snapshots" };
{
- auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };
- auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); emit DependencyManager::get()->snapshotLocationSet(value); };
+ auto getter = []()->QString { return DependencyManager::get()->_snapshotsLocation.get(); };
+ auto setter = [](const QString& value) { DependencyManager::get()->_snapshotsLocation.set(value); emit DependencyManager::get()->snapshotLocationSet(value); };
auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter);
preferences->addPreference(preference);
}
@@ -156,27 +223,6 @@ void setupPreferences() {
"this information you are helping to improve the product. ", getter, setter));
}
- static const QString LOD_TUNING("Level of Detail Tuning");
- {
- auto getter = []()->float { return DependencyManager::get()->getDesktopLODDecreaseFPS(); };
- auto setter = [](float value) { DependencyManager::get()->setDesktopLODDecreaseFPS(value); };
- auto preference = new SpinnerPreference(LOD_TUNING, "Minimum desktop FPS", getter, setter);
- preference->setMin(0);
- preference->setMax(120);
- preference->setStep(1);
- preferences->addPreference(preference);
- }
-
- {
- auto getter = []()->float { return DependencyManager::get()->getHMDLODDecreaseFPS(); };
- auto setter = [](float value) { DependencyManager::get()->setHMDLODDecreaseFPS(value); };
- auto preference = new SpinnerPreference(LOD_TUNING, "Minimum HMD FPS", getter, setter);
- preference->setMin(0);
- preference->setMax(120);
- preference->setStep(1);
- preferences->addPreference(preference);
- }
-
static const QString AVATAR_TUNING { "Avatar Tuning" };
{
auto getter = [=]()->QString { return myAvatar->getDominantHand(); };
@@ -196,26 +242,7 @@ void setupPreferences() {
// which can't be changed across domain switches. Having these values loaded up when you load the Dialog each time
// is a way around this, therefore they're not specified here but in the QML.
}
- {
- auto getter = [=]()->float { return myAvatar->getUserHeight(); };
- auto setter = [=](float value) { myAvatar->setUserHeight(value); };
- auto preference = new SpinnerPreference(AVATAR_TUNING, "User height (meters)", getter, setter);
- preference->setMin(1.0f);
- preference->setMax(2.2f);
- preference->setDecimals(3);
- preference->setStep(0.001f);
- preferences->addPreference(preference);
- }
- {
- auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); };
- auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); };
- preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Camera binary eyelid threshold", getter, setter));
- }
- {
- auto getter = []()->float { return FaceTracker::getEyeDeflection(); };
- auto setter = [](float value) { FaceTracker::setEyeDeflection(value); };
- preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Face tracker eye deflection", getter, setter));
- }
+
{
auto getter = [=]()->QString { return myAvatar->getAnimGraphOverrideUrl().toString(); };
auto setter = [=](const QString& value) { myAvatar->setAnimGraphOverrideUrl(QUrl(value)); };
@@ -224,11 +251,65 @@ void setupPreferences() {
preferences->addPreference(preference);
}
- static const QString AVATAR_CAMERA { "Avatar Camera" };
+ {
+ auto getter = [=]()->bool { return myAvatar->getCollisionsEnabled(); };
+ auto setter = [=](bool value) { myAvatar->setCollisionsEnabled(value); };
+ auto preference = new CheckPreference(AVATAR_TUNING, "Enable Avatar collisions", getter, setter);
+ preferences->addPreference(preference);
+ }
+
+ static const QString FACE_TRACKING{ "Face Tracking" };
+ {
+ auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); };
+ auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); };
+ preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Closing Threshold", getter, setter));
+ }
+ {
+ auto getter = []()->float { return FaceTracker::getEyeDeflection(); };
+ auto setter = [](float value) { FaceTracker::setEyeDeflection(value); };
+ preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter));
+ }
+
+ static const QString MOVEMENT{ "VR Movement" };
+ {
+
+ static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler");
+ auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); };
+ auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); };
+ preferences->addPreference(new CheckPreference(MOVEMENT,
+ QStringLiteral("Advanced movement for hand controllers"),
+ getter, setter));
+ }
+ {
+ auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); };
+ auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); };
+ preferences->addPreference(new CheckPreference(MOVEMENT, "Flying & jumping", getter, setter));
+ }
+ {
+ auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; };
+ auto setter = [=](int value) { myAvatar->setSnapTurn(value == 0); };
+ auto preference = new RadioButtonsPreference(MOVEMENT, "Snap turn / Smooth turn", getter, setter);
+ QStringList items;
+ items << "Snap turn" << "Smooth turn";
+ preference->setItems(items);
+ preferences->addPreference(preference);
+ }
+ {
+ auto getter = [=]()->float { return myAvatar->getUserHeight(); };
+ auto setter = [=](float value) { myAvatar->setUserHeight(value); };
+ auto preference = new SpinnerPreference(MOVEMENT, "User real-world height (meters)", getter, setter);
+ preference->setMin(1.0f);
+ preference->setMax(2.2f);
+ preference->setDecimals(3);
+ preference->setStep(0.001f);
+ preferences->addPreference(preference);
+ }
+
+ static const QString AVATAR_CAMERA{ "Mouse Sensitivity" };
{
auto getter = [=]()->float { return myAvatar->getPitchSpeed(); };
auto setter = [=](float value) { myAvatar->setPitchSpeed(value); };
- auto preference = new SpinnerPreference(AVATAR_CAMERA, "Camera pitch speed (degrees/second)", getter, setter);
+ auto preference = new SpinnerPreference(AVATAR_CAMERA, "Pitch speed (degrees/second)", getter, setter);
preference->setMin(1.0f);
preference->setMax(360.0f);
preferences->addPreference(preference);
@@ -236,7 +317,7 @@ void setupPreferences() {
{
auto getter = [=]()->float { return myAvatar->getYawSpeed(); };
auto setter = [=](float value) { myAvatar->setYawSpeed(value); };
- auto preference = new SpinnerPreference(AVATAR_CAMERA, "Camera yaw speed (degrees/second)", getter, setter);
+ auto preference = new SpinnerPreference(AVATAR_CAMERA, "Yaw speed (degrees/second)", getter, setter);
preference->setMin(1.0f);
preference->setMax(360.0f);
preferences->addPreference(preference);
diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index 39fef1d742..2b306ace91 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -21,7 +21,8 @@
#include
#include
#include
-#include
+#include
+#include
#include
#include
@@ -31,20 +32,39 @@
#include
#include
#include
+#include
+#include
#include "Application.h"
+#include "display-plugins/CompositorHelper.h"
+#include "scripting/WindowScriptingInterface.h"
+#include "MainWindow.h"
+#include "Snapshot.h"
#include "SnapshotUploader.h"
+#include "ToneMappingEffect.h"
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
// %1 <= username, %2 <= date and time, %3 <= current location
const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2.jpg";
-
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss";
const QString SNAPSHOTS_DIRECTORY = "Snapshots";
-
const QString URL = "highfidelity_url";
+static const int SNAPSHOT_360_TIMER_INTERVAL = 350;
-Setting::Handle Snapshot::snapshotsLocation("snapshotsLocation");
+Snapshot::Snapshot() {
+ _snapshotTimer.setSingleShot(false);
+ _snapshotTimer.setTimerType(Qt::PreciseTimer);
+ _snapshotTimer.setInterval(SNAPSHOT_360_TIMER_INTERVAL);
+ connect(&_snapshotTimer, &QTimer::timeout, this, &Snapshot::takeNextSnapshot);
+
+ _snapshotIndex = 0;
+ _oldEnabled = false;
+ _oldAttachedEntityId = 0;
+ _oldOrientation = 0;
+ _oldvFoV = 0;
+ _oldNearClipPlaneDistance = 0;
+ _oldFarClipPlaneDistance = 0;
+}
SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
@@ -78,14 +98,239 @@ QString Snapshot::saveSnapshot(QImage image, const QString& filename, const QStr
QFile* snapshotFile = savedFileForSnapshot(image, false, filename, pathname);
- // we don't need the snapshot file, so close it, grab its filename and delete it
- snapshotFile->close();
+ if (snapshotFile) {
+ // we don't need the snapshot file, so close it, grab its filename and delete it
+ snapshotFile->close();
- QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath();
+ QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath();
- delete snapshotFile;
+ delete snapshotFile;
- return snapshotPath;
+ return snapshotPath;
+ }
+
+ return "";
+}
+
+static const float CUBEMAP_SIDE_PIXEL_DIMENSION = 2048.0f;
+static const float SNAPSHOT_360_FOV = 90.0f;
+static const float SNAPSHOT_360_NEARCLIP = 0.3f;
+static const float SNAPSHOT_360_FARCLIP = 16384.0f;
+static const glm::quat CAMERA_ORIENTATION_DOWN(glm::quat(glm::radians(glm::vec3(-90.0f, 0.0f, 0.0f))));
+static const glm::quat CAMERA_ORIENTATION_FRONT(glm::quat(glm::radians(glm::vec3(0.0f, 0.0f, 0.0f))));
+static const glm::quat CAMERA_ORIENTATION_LEFT(glm::quat(glm::radians(glm::vec3(0.0f, 90.0f, 0.0f))));
+static const glm::quat CAMERA_ORIENTATION_BACK(glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f))));
+static const glm::quat CAMERA_ORIENTATION_RIGHT(glm::quat(glm::radians(glm::vec3(0.0f, 270.0f, 0.0f))));
+static const glm::quat CAMERA_ORIENTATION_UP(glm::quat(glm::radians(glm::vec3(90.0f, 0.0f, 0.0f))));
+void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) {
+ _snapshotFilename = filename;
+ _notify360 = notify;
+ _cubemapOutputFormat = cubemapOutputFormat;
+ SecondaryCameraJobConfig* secondaryCameraRenderConfig = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
+
+ // Save initial values of secondary camera render config
+ _oldEnabled = secondaryCameraRenderConfig->isEnabled();
+ _oldAttachedEntityId = secondaryCameraRenderConfig->property("attachedEntityId");
+ _oldOrientation = secondaryCameraRenderConfig->property("orientation");
+ _oldvFoV = secondaryCameraRenderConfig->property("vFoV");
+ _oldNearClipPlaneDistance = secondaryCameraRenderConfig->property("nearClipPlaneDistance");
+ _oldFarClipPlaneDistance = secondaryCameraRenderConfig->property("farClipPlaneDistance");
+
+ if (!_oldEnabled) {
+ secondaryCameraRenderConfig->enableSecondaryCameraRenderConfigs(true);
+ }
+
+ // Initialize some secondary camera render config options for 360 snapshot capture
+ static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(0);
+
+ secondaryCameraRenderConfig->resetSizeSpectatorCamera(static_cast(CUBEMAP_SIDE_PIXEL_DIMENSION), static_cast(CUBEMAP_SIDE_PIXEL_DIMENSION));
+ secondaryCameraRenderConfig->setProperty("attachedEntityId", "");
+ secondaryCameraRenderConfig->setPosition(cameraPosition);
+ secondaryCameraRenderConfig->setProperty("vFoV", SNAPSHOT_360_FOV);
+ secondaryCameraRenderConfig->setProperty("nearClipPlaneDistance", SNAPSHOT_360_NEARCLIP);
+ secondaryCameraRenderConfig->setProperty("farClipPlaneDistance", SNAPSHOT_360_FARCLIP);
+
+ // Setup for Down Image capture
+ secondaryCameraRenderConfig->setOrientation(CAMERA_ORIENTATION_DOWN);
+
+ _snapshotIndex = 0;
+
+ _snapshotTimer.start(SNAPSHOT_360_TIMER_INTERVAL);
+}
+
+void Snapshot::takeNextSnapshot() {
+ SecondaryCameraJobConfig* config = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
+
+ // Order is:
+ // 0. Down
+ // 1. Front
+ // 2. Left
+ // 3. Back
+ // 4. Right
+ // 5. Up
+ if (_snapshotIndex < 6) {
+ _imageArray[_snapshotIndex] = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
+ }
+
+ if (_snapshotIndex == 0) {
+ // Setup for Front Image capture
+ config->setOrientation(CAMERA_ORIENTATION_FRONT);
+ } else if (_snapshotIndex == 1) {
+ // Setup for Left Image capture
+ config->setOrientation(CAMERA_ORIENTATION_LEFT);
+ } else if (_snapshotIndex == 2) {
+ // Setup for Back Image capture
+ config->setOrientation(CAMERA_ORIENTATION_BACK);
+ } else if (_snapshotIndex == 3) {
+ // Setup for Right Image capture
+ config->setOrientation(CAMERA_ORIENTATION_RIGHT);
+ } else if (_snapshotIndex == 4) {
+ // Setup for Up Image capture
+ config->setOrientation(CAMERA_ORIENTATION_UP);
+ } else if (_snapshotIndex > 5) {
+ _snapshotTimer.stop();
+
+ // Reset secondary camera render config
+ static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(1);
+ config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height());
+ config->setProperty("attachedEntityId", _oldAttachedEntityId);
+ config->setProperty("vFoV", _oldvFoV);
+ config->setProperty("nearClipPlaneDistance", _oldNearClipPlaneDistance);
+ config->setProperty("farClipPlaneDistance", _oldFarClipPlaneDistance);
+
+ if (!_oldEnabled) {
+ config->enableSecondaryCameraRenderConfigs(false);
+ }
+
+ // Process six QImages
+ if (_cubemapOutputFormat) {
+ QtConcurrent::run([this]() { convertToCubemap(); });
+ } else {
+ QtConcurrent::run([this]() { convertToEquirectangular(); });
+ }
+ }
+
+ _snapshotIndex++;
+}
+
+void Snapshot::convertToCubemap() {
+ float outputImageHeight = CUBEMAP_SIDE_PIXEL_DIMENSION * 3.0f;
+ float outputImageWidth = CUBEMAP_SIDE_PIXEL_DIMENSION * 4.0f;
+
+ QImage outputImage(outputImageWidth, outputImageHeight, QImage::Format_RGB32);
+
+ QPainter painter(&outputImage);
+ QPoint destPos;
+
+ // Paint DownImage
+ destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION, CUBEMAP_SIDE_PIXEL_DIMENSION * 2.0f);
+ painter.drawImage(destPos, _imageArray[0]);
+
+ // Paint FrontImage
+ destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION, CUBEMAP_SIDE_PIXEL_DIMENSION);
+ painter.drawImage(destPos, _imageArray[1]);
+
+ // Paint LeftImage
+ destPos = QPoint(0, CUBEMAP_SIDE_PIXEL_DIMENSION);
+ painter.drawImage(destPos, _imageArray[2]);
+
+ // Paint BackImage
+ destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION * 3.0f, CUBEMAP_SIDE_PIXEL_DIMENSION);
+ painter.drawImage(destPos, _imageArray[3]);
+
+ // Paint RightImage
+ destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION * 2.0f, CUBEMAP_SIDE_PIXEL_DIMENSION);
+ painter.drawImage(destPos, _imageArray[4]);
+
+ // Paint UpImage
+ destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION, 0);
+ painter.drawImage(destPos, _imageArray[5]);
+
+ painter.end();
+
+ emit DependencyManager::get()->snapshot360Taken(saveSnapshot(outputImage, _snapshotFilename),
+ _notify360);
+}
+
+void Snapshot::convertToEquirectangular() {
+ // I got help from StackOverflow while writing this code:
+ // https://stackoverflow.com/questions/34250742/converting-a-cubemap-into-equirectangular-panorama
+
+ int cubeFaceWidth = static_cast(CUBEMAP_SIDE_PIXEL_DIMENSION);
+ int cubeFaceHeight = static_cast(CUBEMAP_SIDE_PIXEL_DIMENSION);
+ float outputImageHeight = CUBEMAP_SIDE_PIXEL_DIMENSION * 2.0f;
+ float outputImageWidth = outputImageHeight * 2.0f;
+ QImage outputImage(outputImageWidth, outputImageHeight, QImage::Format_RGB32);
+ outputImage.fill(0);
+ QRgb sourceColorValue;
+ float phi, theta;
+
+ for (int j = 0; j < outputImageHeight; j++) {
+ theta = (1.0f - ((float)j / outputImageHeight)) * PI;
+
+ for (int i = 0; i < outputImageWidth; i++) {
+ phi = ((float)i / outputImageWidth) * 2.0f * PI;
+
+ float x = glm::sin(phi) * glm::sin(theta) * -1.0f;
+ float y = glm::cos(theta);
+ float z = glm::cos(phi) * glm::sin(theta) * -1.0f;
+
+ float a = std::max(std::max(std::abs(x), std::abs(y)), std::abs(z));
+
+ float xa = x / a;
+ float ya = y / a;
+ float za = z / a;
+
+ // Pixel in the source images
+ int xPixel, yPixel;
+ QImage sourceImage;
+
+ if (xa == 1) {
+ // Right image
+ xPixel = (int)((((za + 1.0f) / 2.0f) - 1.0f) * cubeFaceWidth);
+ yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = _imageArray[4];
+ } else if (xa == -1) {
+ // Left image
+ xPixel = (int)((((za + 1.0f) / 2.0f)) * cubeFaceWidth);
+ yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = _imageArray[2];
+ } else if (ya == 1) {
+ // Down image
+ xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
+ yPixel = (int)((((za + 1.0f) / 2.0f) - 1.0f) * cubeFaceHeight);
+ sourceImage = _imageArray[0];
+ } else if (ya == -1) {
+ // Up image
+ xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
+ yPixel = (int)((((za + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = _imageArray[5];
+ } else if (za == 1) {
+ // Front image
+ xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
+ yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = _imageArray[1];
+ } else if (za == -1) {
+ // Back image
+ xPixel = (int)((((xa + 1.0f) / 2.0f) - 1.0f) * cubeFaceWidth);
+ yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = _imageArray[3];
+ } else {
+ qDebug() << "Unknown face encountered when processing 360 Snapshot";
+ xPixel = 0;
+ yPixel = 0;
+ }
+
+ xPixel = std::min(std::abs(xPixel), 2047);
+ yPixel = std::min(std::abs(yPixel), 2047);
+
+ sourceColorValue = sourceImage.pixel(xPixel, yPixel);
+ outputImage.setPixel(i, j, sourceColorValue);
+ }
+ }
+
+ emit DependencyManager::get