Merge branch 'master' into move_to_qqc2

This commit is contained in:
vladest 2017-12-18 20:57:04 +01:00
commit 29a17d366e
139 changed files with 4215 additions and 1055 deletions

View file

@ -477,7 +477,7 @@ void EntityServer::startDynamicDomainVerification() {
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
QJsonObject request;
request["certificate_id"] = i.key();

View file

@ -94,7 +94,7 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
root.insert(requestSubobjectKey, subobject);
QJsonDocument doc { root };
QUrl url { NetworkingConstants::METAVERSE_SERVER_URL.toString() + metaversePath };
QUrl url { NetworkingConstants::METAVERSE_SERVER_URL().toString() + metaversePath };
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
@ -420,7 +420,7 @@ bool DomainServer::optionallySetupOAuth() {
// if we don't have an oauth provider URL then we default to the default node auth url
if (_oauthProviderURL.isEmpty()) {
_oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL;
_oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL();
}
auto accountManager = DependencyManager::get<AccountManager>();
@ -2159,7 +2159,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QJsonDocument doc(root);
QUrl url { NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/api/v1/places/" + place_id };
QUrl url { NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/v1/places/" + place_id };
url.setQuery("access_token=" + accessTokenVariant->toString());

View file

@ -208,7 +208,7 @@ void IceServer::requestDomainPublicKey(const QUuid& domainID) {
// send a request to the metaverse API for the public key for this domain
auto& networkAccessManager = NetworkAccessManager::getInstance();
QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL };
QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL() };
QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID));
publicKeyURL.setPath(publicKeyPath);

View file

@ -34,7 +34,7 @@ var EventBridge;
var tempEventBridge = EventBridge;
EventBridge = channel.objects.eventBridge;
EventBridge.audioOutputDeviceChanged.connect(function(deviceName) {
navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(function(mediaStream) {
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function(mediaStream) {
navigator.mediaDevices.enumerateDevices().then(function(devices) {
devices.forEach(function(device) {
if (device.kind == "audiooutput") {

View file

@ -52,7 +52,11 @@ Item {
targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height
}
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth));
var newWidth = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth));
if(!isNaN(newWidth)) {
parent.width = root.width = newWidth;
}
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
+ (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y);
}

View file

@ -20,6 +20,8 @@ import "../"
import "../toolbars"
import "../../styles-uit" as HifiStyles
import "../../controls-uit" as HifiControls
import QtQuick.Controls 2.2 as QQC2
import QtQuick.Templates 2.2 as T
// references HMD, AddressManager, AddressBarDialog from root context
@ -223,7 +225,7 @@ StackView {
visible: addressLine.text.length === 0
}
TextField {
QQC2.TextField {
id: addressLine
width: addressLineContainer.width - addressLineContainer.anchors.leftMargin - addressLineContainer.anchors.rightMargin;
anchors {
@ -238,16 +240,36 @@ StackView {
addressBarDialog.keyboardEnabled = false;
toggleOrGo();
}
placeholderText: "Type domain address here"
// unfortunately TextField from Quick Controls 2 disallow customization of placeHolderText color without creation of new style
property string placeholderText2: "Type domain address here"
verticalAlignment: TextInput.AlignBottom
style: TextFieldStyle {
textColor: hifi.colors.text
placeholderTextColor: "gray"
font {
family: hifi.fonts.fontFamily
pixelSize: hifi.fonts.pixelSize * 0.75
font {
family: hifi.fonts.fontFamily
pixelSize: hifi.fonts.pixelSize * 0.75
}
color: hifi.colors.text
background: Item {}
QQC2.Label {
T.TextField {
id: control
padding: 6 // numbers taken from Qt\5.9.2\Src\qtquickcontrols2\src\imports\controls\TextField.qml
leftPadding: padding + 4
}
background: Item {}
font: parent.font
x: control.leftPadding
y: control.topPadding
text: parent.placeholderText2
verticalAlignment: "AlignVCenter"
color: 'gray'
visible: parent.text === ''
}
}

View file

@ -971,7 +971,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// set the account manager's root URL and trigger a login request if we don't have the access token
accountManager->setIsAgent(true);
accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL());
auto addressManager = DependencyManager::get<AddressManager>();
@ -3222,8 +3222,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
void Application::keyReleaseEvent(QKeyEvent* event) {
_keysPressed.remove(event->key());
@ -4848,8 +4846,7 @@ void Application::update(float deltaTime) {
if (_physicsEnabled) {
{
PROFILE_RANGE(simulation_physics, "PreStep");
PerformanceTimer perfTimer("updateStates)");
PerformanceTimer perfTimer("preStep)");
static VectorOfMotionStates motionStates;
_entitySimulation->getObjectsToRemoveFromPhysics(motionStates);
_physicsEngine->removeObjects(motionStates);
@ -4882,22 +4879,22 @@ void Application::update(float deltaTime) {
}
{
PROFILE_RANGE(simulation_physics, "Step");
PerformanceTimer perfTimer("stepSimulation");
PerformanceTimer perfTimer("step");
getEntities()->getTree()->withWriteLock([&] {
_physicsEngine->stepSimulation();
});
}
{
PROFILE_RANGE(simulation_physics, "PostStep");
PerformanceTimer perfTimer("harvestChanges");
PerformanceTimer perfTimer("postStep");
if (_physicsEngine->hasOutgoingChanges()) {
// grab the collision events BEFORE handleOutgoingChanges() because at this point
// we have a better idea of which objects we own or should own.
auto& collisionEvents = _physicsEngine->getCollisionEvents();
getEntities()->getTree()->withWriteLock([&] {
PROFILE_RANGE(simulation_physics, "Harvest");
PerformanceTimer perfTimer("handleOutgoingChanges");
PROFILE_RANGE(simulation_physics, "HandleChanges");
PerformanceTimer perfTimer("handleChanges");
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
_entitySimulation->handleChangedMotionStates(outgoingChanges);
@ -4908,17 +4905,15 @@ void Application::update(float deltaTime) {
});
if (!_aboutToQuit) {
// handleCollisionEvents() AFTER handleOutgoinChanges()
// handleCollisionEvents() AFTER handleOutgoingChanges()
{
PROFILE_RANGE(simulation_physics, "CollisionEvents");
PerformanceTimer perfTimer("entities");
avatarManager->handleCollisionEvents(collisionEvents);
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
// deadlock.)
_entitySimulation->handleCollisionEvents(collisionEvents);
}
PROFILE_RANGE(simulation_physics, "UpdateEntities");
// NOTE: the getEntities()->update() call below will wait for lock
// and will simulate entity motion (the EntityTree has been given an EntitySimulation).
getEntities()->update(true); // update the models...
@ -4929,7 +4924,8 @@ void Application::update(float deltaTime) {
myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) &&
if (PerformanceTimer::isActive() &&
Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) &&
Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming)) {
_physicsEngine->harvestPerformanceStats();
}
@ -7506,4 +7502,9 @@ void Application::setAvatarOverrideUrl(const QUrl& url, bool save) {
_avatarOverrideUrl = url;
_saveAvatarOverrideUrl = save;
}
void Application::saveNextPhysicsStats(QString filename) {
_physicsEngine->saveNextPhysicsStats(filename);
}
#include "Application.moc"

View file

@ -280,6 +280,7 @@ public:
void clearAvatarOverrideUrl() { _avatarOverrideUrl = QUrl(); _saveAvatarOverrideUrl = false; }
QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; }
bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; }
void saveNextPhysicsStats(QString filename);
signals:
void svoImportRequested(const QString& url);
@ -432,6 +433,7 @@ private slots:
void handleSandboxStatus(QNetworkReply* reply);
void switchDisplayMode();
private:
static void initDisplay();
void init();

View file

@ -19,10 +19,14 @@ class FancyCamera : public Camera {
Q_OBJECT
/**jsdoc
* @namespace Camera
* @property cameraEntity {EntityID} The position and rotation properties of
* the entity specified by this ID are then used as the camera's position and
* orientation. Only works when <code>mode</code> is "entity".
* @namespace
* @augments Camera
*/
// FIXME: JSDoc 3.5.5 doesn't augment @property definitions. The following definition is repeated in Camera.h.
/**jsdoc
* @property cameraEntity {Uuid} The ID of the entity that the camera position and orientation follow when the camera is in
* entity mode.
*/
Q_PROPERTY(QUuid cameraEntity READ getCameraEntity WRITE setCameraEntity)
@ -34,7 +38,25 @@ public:
public slots:
/**jsdoc
* Get the ID of the entity that the camera is set to use the position and orientation from when it's in entity mode. You can
* also get the entity ID using the <code>Camera.cameraEntity</code> property.
* @function Camera.getCameraEntity
* @returns {Uuid} The ID of the entity that the camera is set to follow when in entity mode; <code>null</code> if no camera
* entity has been set.
*/
QUuid getCameraEntity() const;
/**jsdoc
* Set the entity that the camera should use the position and orientation from when it's in entity mode. You can also set the
* entity using the <code>Camera.cameraEntity</code> property.
* @function Camera.setCameraEntity
* @param {Uuid} entityID The entity that the camera should follow when it's in entity mode.
* @example <caption>Move your camera to the position and orientation of the closest entity.</caption>
* Camera.setModeString("entity");
* var entity = Entities.findClosestEntity(MyAvatar.position, 100.0);
* Camera.setCameraEntity(entity);
*/
void setCameraEntity(QUuid entityID);
private:

View file

@ -645,7 +645,8 @@ Menu::Menu() {
// Developer > Timing >>>
MenuWrapper* timingMenu = developerMenu->addMenu("Timing");
MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer");
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false,
qApp, SLOT(enablePerfStats(bool)));
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarTiming, 0, false);

View file

@ -81,6 +81,7 @@ const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amaz
const float MyAvatar::ZOOM_MIN = 0.5f;
const float MyAvatar::ZOOM_MAX = 25.0f;
const float MyAvatar::ZOOM_DEFAULT = 1.5f;
const float MIN_SCALE_CHANGED_DELTA = 0.001f;
MyAvatar::MyAvatar(QThread* thread) :
Avatar(thread),
@ -670,6 +671,11 @@ void MyAvatar::updateSensorToWorldMatrix() {
glm::mat4 desiredMat = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), getWorldOrientation(), getWorldPosition());
_sensorToWorldMatrix = desiredMat * glm::inverse(_bodySensorMatrix);
bool hasSensorToWorldScaleChanged = false;
if (fabsf(getSensorToWorldScale() - sensorToWorldScale) > MIN_SCALE_CHANGED_DELTA) {
hasSensorToWorldScaleChanged = true;
}
lateUpdatePalms();
if (_enableDebugDrawSensorToWorldMatrix) {
@ -678,9 +684,13 @@ void MyAvatar::updateSensorToWorldMatrix() {
}
_sensorToWorldMatrixCache.set(_sensorToWorldMatrix);
updateJointFromController(controller::Action::LEFT_HAND, _controllerLeftHandMatrixCache);
updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache);
if (hasSensorToWorldScaleChanged) {
emit sensorToWorldScaleChanged(sensorToWorldScale);
}
}
// Update avatar head rotation with sensor data
@ -1405,6 +1415,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene());
_headBoneSet.clear();
emit skeletonChanged();
}
@ -1440,6 +1451,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
}
markIdentityDataChanged();
}
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {

View file

@ -9,9 +9,12 @@
#include "MySkeletonModel.h"
#include <avatars-renderer/Avatar.h>
#include <DebugDraw.h>
#include "Application.h"
#include "InterfaceLogging.h"
#include "AnimUtil.h"
MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) {
}
@ -30,6 +33,39 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle
};
}
static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
glm::mat4 hipsMat = myAvatar->deriveBodyFromHMDSensor();
glm::vec3 hipsPos = extractTranslation(hipsMat);
glm::quat hipsRot = glmExtractRotation(hipsMat);
glm::mat4 avatarToWorldMat = myAvatar->getTransform().getMatrix();
glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix());
glm::mat4 avatarToSensorMat = worldToSensorMat * avatarToWorldMat;
// dampen hips rotation, by mixing it with the avatar orientation in sensor space
const float MIX_RATIO = 0.5f;
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
if (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));
glm::vec3 headPos;
int headIndex = myAvatar->getJointIndex("Head");
if (headIndex != -1) {
headPos = transformPoint(avatarToSensorMat, myAvatar->getAbsoluteJointTranslationInObjectFrame(headIndex));
} else {
headPos = transformPoint(myAvatar->getSensorToWorldMatrix(), myAvatar->getHMDSensorPosition());
}
hipsRot = tiltRot * hipsRot;
hipsPos = headPos + tiltRot * (hipsPos - headPos);
}
return AnimPose(hipsRot * Quaternions::Y_180, hipsPos);
}
// Called within Model::simulate call, below.
void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
const FBXGeometry& geometry = getFBXGeometry();
@ -124,6 +160,39 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
}
}
// if hips are not under direct control, estimate the hips position.
if (avatarHeadPose.isValid() && !params.primaryControllerActiveFlags[Rig::PrimaryControllerType_Hips]) {
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS);
if (!_prevHipsValid) {
AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying);
_prevHips = hips;
}
AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying);
// smootly lerp hips, in sensorframe, with different coeff for horiz and vertical translation.
const float ROT_ALPHA = 0.9f;
const float TRANS_HORIZ_ALPHA = 0.9f;
const float TRANS_VERT_ALPHA = 0.1f;
float hipsY = hips.trans().y;
hips.trans() = lerp(hips.trans(), _prevHips.trans(), TRANS_HORIZ_ALPHA);
hips.trans().y = lerp(hipsY, _prevHips.trans().y, TRANS_VERT_ALPHA);
hips.rot() = safeLerp(hips.rot(), _prevHips.rot(), ROT_ALPHA);
_prevHips = hips;
_prevHipsValid = true;
glm::mat4 invRigMat = glm::inverse(myAvatar->getTransform().getMatrix() * Matrices::Y_180);
AnimPose sensorToRigPose(invRigMat * myAvatar->getSensorToWorldMatrix());
params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * hips;
params.primaryControllerActiveFlags[Rig::PrimaryControllerType_Hips] = true;
} else {
_prevHipsValid = false;
}
params.isTalking = head->getTimeWithoutTalking() <= 1.5f;
// pass detailed torso k-dops to rig.

View file

@ -25,6 +25,9 @@ public:
private:
void updateFingers();
AnimPose _prevHips; // sensor frame
bool _prevHipsValid { false };
};
#endif // hifi_MySkeletonModel_h

View file

@ -130,7 +130,7 @@ QString amountString(const QString& label, const QString&color, const QJsonValue
return result + QString("</font>");
}
static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace/items/";
static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/";
void Ledger::historySuccess(QNetworkReply& reply) {
// here we send a historyResult with some extra stuff in it
// Namely, the styled text we'd like to show. The issue is the

View file

@ -315,7 +315,6 @@ Wallet::Wallet() {
}
walletScriptingInterface->setWalletStatus(status);
emit walletStatusResult(status);
});
auto accountManager = DependencyManager::get<AccountManager>();
@ -491,6 +490,7 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
}
if (_publicKeys.count() > 0) {
// we _must_ be authenticated if the publicKeys are there
DependencyManager::get<WalletScriptingInterface>()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY);
return true;
}
@ -503,6 +503,7 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
// be sure to add the public key so we don't do this over and over
_publicKeys.push_back(publicKey.toBase64());
DependencyManager::get<WalletScriptingInterface>()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY);
return true;
}
}
@ -801,15 +802,12 @@ void Wallet::account() {
void Wallet::getWalletStatus() {
auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>();
uint status;
if (DependencyManager::get<AccountManager>()->isLoggedIn()) {
// This will set account info for the wallet, allowing us to decrypt and display the security image.
account();
} else {
status = (uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN;
emit walletStatusResult(status);
walletScriptingInterface->setWalletStatus(status);
walletScriptingInterface->setWalletStatus((uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN);
return;
}
}

View file

@ -28,7 +28,7 @@ QNetworkRequest createNetworkRequest() {
QNetworkRequest request;
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath(USER_ACTIVITY_URL);
request.setUrl(requestURL);

View file

@ -23,10 +23,10 @@ static const float WEB_STYLUS_LENGTH = 0.2f;
static const float TABLET_MIN_HOVER_DISTANCE = -0.1f;
static const float TABLET_MAX_HOVER_DISTANCE = 0.1f;
static const float TABLET_MIN_TOUCH_DISTANCE = -0.1f;
static const float TABLET_MAX_TOUCH_DISTANCE = 0.01f;
static const float TABLET_MAX_TOUCH_DISTANCE = 0.005f;
static const float HOVER_HYSTERESIS = 0.01f;
static const float TOUCH_HYSTERESIS = 0.02f;
static const float TOUCH_HYSTERESIS = 0.001f;
static const float STYLUS_MOVE_DELAY = 0.33f * USECS_PER_SECOND;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f;

View file

@ -34,7 +34,7 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, const
return retVal;
}
bool ClipboardScriptingInterface::exportEntities(const QString& filename, float x, float y, float z, float s) {
bool ClipboardScriptingInterface::exportEntities(const QString& filename, float x, float y, float z, float scale) {
bool retVal;
BLOCKING_INVOKE_METHOD(qApp, "exportEntities",
Q_RETURN_ARG(bool, retVal),
@ -42,7 +42,7 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, float
Q_ARG(float, x),
Q_ARG(float, y),
Q_ARG(float, z),
Q_ARG(float, s));
Q_ARG(float, scale));
return retVal;
}

View file

@ -18,6 +18,8 @@
#include <EntityItemID.h>
/**jsdoc
* The Clipboard API enables you to export and import entities to and from JSON files.
*
* @namespace Clipboard
*/
class ClipboardScriptingInterface : public QObject {
@ -25,48 +27,56 @@ class ClipboardScriptingInterface : public QObject {
public:
ClipboardScriptingInterface();
signals:
void readyToImport();
public:
/**jsdoc
* Compute the extents of the contents held in the clipboard.
* @function Clipboard.getContentsDimensions
* @return {Vec3} The extents of the contents held in the clipboard.
* @returns {Vec3} The extents of the contents held in the clipboard.
*/
Q_INVOKABLE glm::vec3 getContentsDimensions();
/**jsdoc
* Compute largest dimension of the extents of the contents held in the clipboard
* Compute the largest dimension of the extents of the contents held in the clipboard.
* @function Clipboard.getClipboardContentsLargestDimension
* @return {float} The largest dimension computed.
* @returns {number} The largest dimension computed.
*/
Q_INVOKABLE float getClipboardContentsLargestDimension();
/**jsdoc
* Import entities from a .json file containing entity data into the clipboard.
* You can generate * a .json file using {Clipboard.exportEntities}.
* Import entities from a JSON file containing entity data into the clipboard.
* You can generate a JSON file using {@link Clipboard.exportEntities}.
* @function Clipboard.importEntities
* @param {string} filename Filename of file to import.
* @return {bool} True if the import was succesful, otherwise false.
* @param {string} filename Path and name of file to import.
* @returns {boolean} <code>true</code> if the import was successful, otherwise <code>false</code>.
*/
Q_INVOKABLE bool importEntities(const QString& filename);
/**jsdoc
* Export the entities listed in `entityIDs` to the file `filename`
* Export the entities specified to a JSON file.
* @function Clipboard.exportEntities
* @param {string} filename Path to the file to export entities to.
* @param {EntityID[]} entityIDs IDs of entities to export.
* @return {bool} True if the export was succesful, otherwise false.
* @param {string} filename Path and name of the file to export the entities to. Should have the extension ".json".
* @param {Uuid[]} entityIDs Array of IDs of the entities to export.
* @returns {boolean} <code>true</code> if the export was successful, otherwise <code>false</code>.
*/
Q_INVOKABLE bool exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs);
Q_INVOKABLE bool exportEntities(const QString& filename, float x, float y, float z, float s);
/**jsdoc
* Export the entities with centers within a cube to a JSON file.
* @function Clipboard.exportEntities
* @param {string} filename Path and name of the file to export the entities to. Should have the extension ".json".
* @param {number} x X-coordinate of the cube center.
* @param {number} y Y-coordinate of the cube center.
* @param {number} z Z-coordinate of the cube center.
* @param {number} scale Half dimension of the cube.
* @returns {boolean} <code>true</code> if the export was successful, otherwise <code>false</code>.
*/
Q_INVOKABLE bool exportEntities(const QString& filename, float x, float y, float z, float scale);
/**jsdoc
* Paste the contents of the clipboard into the world.
* @function Clipboard.pasteEntities
* @param {Vec3} position Position to paste clipboard at.
* @return {EntityID[]} Array of entity IDs for the new entities that were
* created as a result of the paste operation.
* @param {Vec3} position Position to paste the clipboard contents at.
* @returns {Uuid[]} Array of entity IDs for the new entities that were created as a result of the paste operation.
*/
Q_INVOKABLE QVector<EntityItemID> pasteEntities(glm::vec3 position);
};

View file

@ -18,15 +18,18 @@
class MenuItemProperties;
/**jsdoc
* The `Menu` provides access to the menu that is shown at the top of the window
* shown on a user's desktop and the right click menu that is accessible
* in both Desktop and HMD mode.
* The Menu API provides access to the menu that is displayed at the top of the window
* on a user's desktop and in the tablet when the "MENU" button is pressed.
*
* <p />
*
* <h3>Groupings</h3>
* A `grouping` is a way to group a set of menus and/or menu items together
* so that they can all be set visible or invisible as a group. There are
* 2 available groups: "Advanced" and "Developer"
*
* A "grouping" provides a way to group a set of menus or menu items together so
* that they can all be set visible or invisible as a group.
* There are two available groups: <code>"Advanced"</code> and <code>"Developer"</code>.
* These groupings can be toggled in the "Settings" menu.
* If a menu item doesn't belong to a group it is always displayed.
*
* @namespace Menu
*/
@ -55,86 +58,113 @@ public slots:
/**jsdoc
* Add a new top-level menu.
* @function Menu.addMenu
* @param {string} menuName Name that will be shown in the menu.
* @param {string} grouping Name of the grouping to add this menu to.
* @param {string} menuName - Name that will be displayed for the menu. Nested menus can be described using the ">" symbol.
* @param {string} [grouping] - Name of the grouping, if any, to add this menu to.
*
* @example <caption>Add a menu and a nested submenu.</caption>
* Menu.addMenu("Test Menu");
* Menu.addMenu("Test Menu > Test Sub Menu");
*
* @example <caption>Add a menu to the Settings menu that is only visible if Settings > Advanced is enabled.</caption>
* Menu.addMenu("Settings > Test Grouping Menu", "Advanced");
*/
void addMenu(const QString& menuName, const QString& grouping = QString());
/**jsdoc
* Remove a top-level menu.
* @function Menu.removeMenu
* @param {string} menuName Name of the menu to remove.
* @param {string} menuName - Name of the menu to remove.
* @example <caption>Remove a menu and nested submenu.</caption>
* Menu.removeMenu("Test Menu > Test Sub Menu");
* Menu.removeMenu("Test Menu");
*/
void removeMenu(const QString& menuName);
/**jsdoc
* Check whether a top-level menu exists.
* @function Menu.menuExists
* @param {string} menuName Name of the menu to check for existence.
* @return {bool} `true` if the menu exists, otherwise `false`.
* @param {string} menuName - Name of the menu to check for existence.
* @returns {boolean} <code>true</code> if the menu exists, otherwise <code>false</code>.
* @example <caption>Check if the "Developer" menu exists.</caption>
* if (Menu.menuExists("Developer")) {
* print("Developer menu exists.");
* }
*/
bool menuExists(const QString& menuName);
/**jsdoc
* Add a separator with an unclickable label below it.
* The line will be placed at the bottom of the menu.
* Add a separator with an unclickable label below it. The separator will be placed at the bottom of the menu.
* If you want to add a separator at a specific point in the menu, use {@link Menu.addMenuItem} with
* {@link Menu.MenuItemProperties} instead.
* @function Menu.addSeparator
* @param {string} menuName Name of the menu to add a separator to.
* @param {string} separatorName Name of the separator that will be shown (but unclickable) below the separator line.
* @param {string} menuName - Name of the menu to add a separator to.
* @param {string} separatorName - Name of the separator that will be displayed as the label below the separator line.
* @example <caption>Add a separator.</caption>
* Menu.addSeparator("Developer","Test Separator");
*/
void addSeparator(const QString& menuName, const QString& separatorName);
/**jsdoc
* Remove a separator and its label from a menu.
* Remove a separator from a menu.
* @function Menu.removeSeparator
* @param {string} menuName Name of the menu to remove a separator from.
* @param {string} separatorName Name of the separator to remove.
* @param {string} menuName - Name of the menu to remove the separator from.
* @param {string} separatorName - Name of the separator to remove.
* @example <caption>Remove a separator.</caption>
* Menu.removeSeparator("Developer","Test Separator");
*/
void removeSeparator(const QString& menuName, const QString& separatorName);
/**jsdoc
* Add a new menu item to a menu.
* @function Menu.addMenuItem
* @param {Menu.MenuItemProperties} properties
* @param {Menu.MenuItemProperties} properties - Properties of the menu item to create.
* @example <caption>Add a menu item using {@link Menu.MenuItemProperties}.</caption>
* Menu.addMenuItem({
* menuName: "Developer",
* menuItemName: "Test",
* afterItem: "Log",
* shortcutKey: "Ctrl+Shift+T",
* grouping: "Advanced"
* });
*/
void addMenuItem(const MenuItemProperties& properties);
/**jsdoc
* Add a new menu item to a menu.
* Add a new menu item to a menu. The new item is added at the end of the menu.
* @function Menu.addMenuItem
* @param {string} menuName Name of the menu to add a menu item to.
* @param {string} menuItem Name of the menu item. This is what will be displayed in the menu.
* @param {string} shortcutKey A shortcut key that can be used to trigger the menu item.
* @param {string} menuName - Name of the menu to add a menu item to.
* @param {string} menuItem - Name of the menu item. This is what will be displayed in the menu.
* @param {string} [shortcutKey] A shortcut key that can be used to trigger the menu item.
* @example <caption>Add a menu item to the end of the "Developer" menu.</caption>
* Menu.addMenuItem("Developer", "Test", "Ctrl+Shift+T");
*/
void addMenuItem(const QString& menuName, const QString& menuitem, const QString& shortcutKey);
/**jsdoc
* Add a new menu item to a menu.
* @function Menu.addMenuItem
* @param {string} menuName Name of the menu to add a menu item to.
* @param {string} menuItem Name of the menu item. This is what will be displayed in the menu.
*/
void addMenuItem(const QString& menuName, const QString& menuitem);
/**jsdoc
* Remove a menu item from a menu.
* @function Menu.removeMenuItem
* @param {string} menuName Name of the menu to remove a menu item from.
* @param {string} menuItem Name of the menu item to remove.
* @param {string} menuName - Name of the menu to remove a menu item from.
* @param {string} menuItem - Name of the menu item to remove.
* Menu.removeMenuItem("Developer", "Test");
*/
void removeMenuItem(const QString& menuName, const QString& menuitem);
/**jsdoc
* Check if a menu item exists.
* @function Menu.menuItemExists
* @param {string} menuName Name of the menu that the menu item is in.
* @param {string} menuItem Name of the menu item to check for existence of.
* @return {bool} `true` if the menu item exists, otherwise `false`.
* @param {string} menuName - Name of the menu that the menu item is in.
* @param {string} menuItem - Name of the menu item to check for existence of.
* @returns {boolean} <code>true</code> if the menu item exists, otherwise <code>false</code>.
* @example <caption>Determine if the Developer &gt; Stats menu exists.</caption>
* if (Menu.menuItemExists("Developer", "Stats")) {
* print("Developer > Stats menu item exists.");
* }
*/
bool menuItemExists(const QString& menuName, const QString& menuitem);
/**
* Not working, will not document until fixed
* TODO: Not working; don't document until fixed.
*/
void addActionGroup(const QString& groupName, const QStringList& actionList,
const QString& selected = QString());
@ -143,53 +173,73 @@ public slots:
/**jsdoc
* Check whether a checkable menu item is checked.
* @function Menu.isOptionChecked
* @param {string} menuOption The name of the menu item.
* @return `true` if the option is checked, otherwise false.
* @param {string} menuOption - The name of the menu item.
* @returns {boolean} <code>true</code> if the option is checked, otherwise <code>false</code>.
* @example <caption>Report whether the Settings > Advanced menu item is turned on.</caption>
* print(Menu.isOptionChecked("Advanced Menus")); // true or false
*/
bool isOptionChecked(const QString& menuOption);
/**jsdoc
* Set a checkable menu item as checked or unchecked.
* @function Menu.setIsOptionChecked
* @param {string} menuOption The name of the menu item to modify.
* @param {bool} isChecked If `true`, the menu item will be checked, otherwise it will not be checked.
* @param {string} menuOption - The name of the menu item to modify.
* @param {boolean} isChecked - If <code>true</code>, the menu item will be checked, otherwise it will not be checked.
* @example <caption>Turn on Settings > Advanced Menus.</caption>
* Menu.setIsOptionChecked("Advanced Menus", true);
* print(Menu.isOptionChecked("Advanced Menus")); // true
*/
void setIsOptionChecked(const QString& menuOption, bool isChecked);
/**jsdoc
* Toggle the status of a checkable menu item. If it is checked, it will be unchecked.
* If it is unchecked, it will be checked.
* @function Menu.setIsOptionChecked
* @param {string} menuOption The name of the menu item to toggle.
* Trigger the menu item as if the user clicked on it.
* @function Menu.triggerOption
* @param {string} menuOption - The name of the menu item to trigger.
* @example <caption>Open the help window.</caption>
* Menu.triggerOption('Help...');
*/
void triggerOption(const QString& menuOption);
/**jsdoc
* Check whether a menu is enabled. If a menu is disabled it will be greyed out
* and unselectable.
* Check whether a menu or menu item is enabled. If disabled, the item is grayed out and unusable.
* Menus are enabled by default.
* @function Menu.isMenuEnabled
* @param {string} menuName The name of the menu to check.
* @return {bool} `true` if the menu is enabled, otherwise false.
* @param {string} menuName The name of the menu or menu item to check.
* @returns {boolean} <code>true</code> if the menu is enabled, otherwise <code>false</code>.
* @example <caption>Report with the Settings > Advanced Menus menu item is enabled.</caption>
* print(Menu.isMenuEnabled("Settings > Advanced Menus")); // true or false
*/
bool isMenuEnabled(const QString& menuName);
/**jsdoc
* Set a menu to be enabled or disabled.
* Set a menu or menu item to be enabled or disabled. If disabled, the item is grayed out and unusable.
* @function Menu.setMenuEnabled
* @param {string} menuName The name of the menu to modify.
* @param {bool} isEnabled Whether the menu will be enabled or not.
* @param {string} menuName - The name of the menu or menu item to modify.
* @param {boolean} isEnabled - If <code>true</code>, the menu will be enabled, otherwise it will be disabled.
* @example <caption>Disable the Settings > Advanced Menus menu item.</caption>
* Menu.setMenuEnabled("Settings > Advanced Menus", false);
* print(Menu.isMenuEnabled("Settings > Advanced Menus")); // false
*/
void setMenuEnabled(const QString& menuName, bool isEnabled);
/**
* TODO: Not used or useful; will not document until used.
*/
void closeInfoView(const QString& path);
bool isInfoViewVisible(const QString& path);
signals:
/**jsdoc
* This is a signal that is emitted when a menu item is clicked.
* Triggered when a menu item is clicked (or triggered by {@link Menu.triggerOption}).
* @function Menu.menuItemEvent
* @param {string} menuItem Name of the menu item that was triggered.
* @param {string} menuItem - Name of the menu item that was clicked.
* @returns {Signal}
* @example <caption>Detect menu item events.</caption>
* function onMenuItemEvent(menuItem) {
* print("You clicked on " + menuItem);
* }
*
* Menu.menuItemEvent.connect(onMenuItemEvent);
*/
void menuItemEvent(const QString& menuItem);
};

View file

@ -11,11 +11,12 @@
#include <QtCore/QLoggingCategory>
#include <QtCore/QThread>
#include <shared/FileUtils.h>
#include <shared/QtHelpers.h>
#include <DependencyManager.h>
#include <Trace.h>
#include <StatTracker.h>
#include <OffscreenUi.h>
#include <StatTracker.h>
#include <Trace.h>
#include "Application.h"
@ -141,6 +142,15 @@ void TestScriptingInterface::endTraceEvent(QString name) {
tracing::traceEvent(trace_test(), name, tracing::DurationEnd);
}
void TestScriptingInterface::savePhysicsSimulationStats(QString originalPath) {
QString path = FileUtils::replaceDateTimeTokens(originalPath);
path = FileUtils::computeDocumentPath(path);
if (!FileUtils::canCreateFile(path)) {
return;
}
qApp->saveNextPhysicsStats(path);
}
void TestScriptingInterface::profileRange(const QString& name, QScriptValue fn) {
PROFILE_RANGE(script, name);
fn.call();

View file

@ -71,6 +71,11 @@ public slots:
void endTraceEvent(QString name);
/**jsdoc
* Write detailed timing stats of next physics stepSimulation() to filename
*/
void savePhysicsSimulationStats(QString filename);
Q_INVOKABLE void profileRange(const QString& name, QScriptValue function);
private:

View file

@ -22,3 +22,8 @@ void WalletScriptingInterface::refreshWalletStatus() {
auto wallet = DependencyManager::get<Wallet>();
wallet->getWalletStatus();
}
void WalletScriptingInterface::setWalletStatus(const uint& status) {
_walletStatus = status;
emit DependencyManager::get<Wallet>()->walletStatusResult(status);
}

View file

@ -39,7 +39,9 @@ public:
Q_INVOKABLE void refreshWalletStatus();
Q_INVOKABLE uint getWalletStatus() { return _walletStatus; }
void setWalletStatus(const uint& status) { _walletStatus = status; }
// 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:
void walletStatusChanged();

View file

@ -30,7 +30,7 @@ public:
bool forwardEnabled() { return _forwardEnabled; }
bool useFeed() { return _useFeed; }
void setUseFeed(bool useFeed) { if (_useFeed != useFeed) { _useFeed = useFeed; emit useFeedChanged(); } }
QString metaverseServerUrl() { return NetworkingConstants::METAVERSE_SERVER_URL.toString(); }
QString metaverseServerUrl() { return NetworkingConstants::METAVERSE_SERVER_URL().toString(); }
signals:
void backEnabledChanged();

View file

@ -78,6 +78,8 @@ bool Stats::includeTimingRecord(const QString& name) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
} else if (name.startsWith("/paintGL/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
} else if (name.startsWith("step/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming);
}
return true;
}

View file

@ -200,6 +200,31 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
}
}
// JSDoc for copying to @typedefs of overlay types that inherit Base3DOverlay.
/**jsdoc
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*/
QVariant Base3DOverlay::getProperty(const QString& property) {
if (property == "name") {
return _name;

View file

@ -61,8 +61,8 @@ public:
void notifyRenderVariableChange() const;
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
virtual void setProperties(const QVariantMap& properties) override;
virtual QVariant getProperty(const QString& property) override;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);

View file

@ -23,6 +23,11 @@ void Billboardable::setProperties(const QVariantMap& properties) {
}
}
// JSDoc for copying to @typedefs of overlay types that inherit Billboardable.
/**jsdoc
* @property {boolean} isFacingAvatar - If <code>true</code>, the overlay is rotated to face the user's camera about an axis
* parallel to the user's avatar's "up" direction.
*/
QVariant Billboardable::getProperty(const QString &property) {
if (property == "isFacingAvatar") {
return isFacingAvatar();

View file

@ -16,10 +16,7 @@
QString const Circle3DOverlay::TYPE = "circle3d";
Circle3DOverlay::Circle3DOverlay() {
memset(&_minorTickMarksColor, 0, sizeof(_minorTickMarksColor));
memset(&_majorTickMarksColor, 0, sizeof(_majorTickMarksColor));
}
Circle3DOverlay::Circle3DOverlay() {}
Circle3DOverlay::Circle3DOverlay(const Circle3DOverlay* circle3DOverlay) :
Planar3DOverlay(circle3DOverlay),
@ -27,17 +24,21 @@ Circle3DOverlay::Circle3DOverlay(const Circle3DOverlay* circle3DOverlay) :
_endAt(circle3DOverlay->_endAt),
_outerRadius(circle3DOverlay->_outerRadius),
_innerRadius(circle3DOverlay->_innerRadius),
_innerStartColor(circle3DOverlay->_innerStartColor),
_innerEndColor(circle3DOverlay->_innerEndColor),
_outerStartColor(circle3DOverlay->_outerStartColor),
_outerEndColor(circle3DOverlay->_outerEndColor),
_innerStartAlpha(circle3DOverlay->_innerStartAlpha),
_innerEndAlpha(circle3DOverlay->_innerEndAlpha),
_outerStartAlpha(circle3DOverlay->_outerStartAlpha),
_outerEndAlpha(circle3DOverlay->_outerEndAlpha),
_hasTickMarks(circle3DOverlay->_hasTickMarks),
_majorTickMarksAngle(circle3DOverlay->_majorTickMarksAngle),
_minorTickMarksAngle(circle3DOverlay->_minorTickMarksAngle),
_majorTickMarksLength(circle3DOverlay->_majorTickMarksLength),
_minorTickMarksLength(circle3DOverlay->_minorTickMarksLength),
_majorTickMarksColor(circle3DOverlay->_majorTickMarksColor),
_minorTickMarksColor(circle3DOverlay->_minorTickMarksColor),
_quadVerticesID(GeometryCache::UNKNOWN_ID),
_lineVerticesID(GeometryCache::UNKNOWN_ID),
_majorTicksVerticesID(GeometryCache::UNKNOWN_ID),
_minorTicksVerticesID(GeometryCache::UNKNOWN_ID)
_minorTickMarksColor(circle3DOverlay->_minorTickMarksColor)
{
}
@ -80,9 +81,8 @@ void Circle3DOverlay::render(RenderArgs* args) {
Q_ASSERT(args->_batch);
auto& batch = *args->_batch;
if (args->_shapePipeline) {
batch.setPipeline(args->_shapePipeline->pipeline);
}
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, isTransparent(), false, !getIsSolid(), true);
batch.setModelTransform(getRenderTransform());
@ -185,11 +185,10 @@ void Circle3DOverlay::render(RenderArgs* args) {
// for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise
// we just draw a line...
if (getHasTickMarks()) {
if (_majorTicksVerticesID == GeometryCache::UNKNOWN_ID) {
if (!_majorTicksVerticesID) {
_majorTicksVerticesID = geometryCache->allocateID();
}
if (_minorTicksVerticesID == GeometryCache::UNKNOWN_ID) {
if (!_minorTicksVerticesID) {
_minorTicksVerticesID = geometryCache->allocateID();
}
@ -368,6 +367,98 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
}
}
// Overlay's color and alpha properties are overridden. And the dimensions property is not used.
/**jsdoc
* These are the properties of a <code>circle3d</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.Circle3DProperties
*
* @property {string} type=circle3d - Has the value <code>"circle3d"</code>. <em>Read-only.</em>
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
* <em>Not used.</em>
*
* @property {number} startAt=0 - The counter-clockwise angle from the overlay's x-axis that drawing starts at, in degrees.
* @property {number} endAt=360 - The counter-clockwise angle from the overlay's x-axis that drawing ends at, in degrees.
* @property {number} outerRadius=1 - The outer radius of the overlay, in meters. Synonym: <code>radius</code>.
* @property {number} innerRadius=0 - The inner radius of the overlay, in meters.
* @property {Color} color=255,255,255 - The color of the overlay. Setting this value also sets the values of
* <code>innerStartColor</code>, <code>innerEndColor</code>, <code>outerStartColor</code>, and <code>outerEndColor</code>.
* @property {Color} startColor - Sets the values of <code>innerStartColor</code> and <code>outerStartColor</code>.
* <em>Write-only.</em>
* @property {Color} endColor - Sets the values of <code>innerEndColor</code> and <code>outerEndColor</code>.
* <em>Write-only.</em>
* @property {Color} innerColor - Sets the values of <code>innerStartColor</code> and <code>innerEndColor</code>.
* <em>Write-only.</em>
* @property {Color} outerColor - Sets the values of <code>outerStartColor</code> and <code>outerEndColor</code>.
* <em>Write-only.</em>
* @property {Color} innerStartcolor - The color at the inner start point of the overlay. <em>Write-only.</em>
* @property {Color} innerEndColor - The color at the inner end point of the overlay. <em>Write-only.</em>
* @property {Color} outerStartColor - The color at the outer start point of the overlay. <em>Write-only.</em>
* @property {Color} outerEndColor - The color at the outer end point of the overlay. <em>Write-only.</em>
* @property {number} alpha=0.5 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>. Setting this value also sets
* the values of <code>innerStartAlpha</code>, <code>innerEndAlpha</code>, <code>outerStartAlpha</code>, and
* <code>outerEndAlpha</code>. Synonym: <code>Alpha</code>; <em>write-only</em>.
* @property {number} startAlpha - Sets the values of <code>innerStartAlpha</code> and <code>outerStartAlpha</code>.
* <em>Write-only.</em>
* @property {number} endAlpha - Sets the values of <code>innerEndAlpha</code> and <code>outerEndAlpha</code>.
* <em>Write-only.</em>
* @property {number} innerAlpha - Sets the values of <code>innerStartAlpha</code> and <code>innerEndAlpha</code>.
* <em>Write-only.</em>
* @property {number} outerAlpha - Sets the values of <code>outerStartAlpha</code> and <code>outerEndAlpha</code>.
* <em>Write-only.</em>
* @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay. <em>Write-only.</em>
* @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay. <em>Write-only.</em>
* @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay. <em>Write-only.</em>
* @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay. <em>Write-only.</em>
* @property {boolean} hasTickMarks=false - If <code>true</code>, tick marks are drawn.
* @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees.
* @property {number} minorTickMarksAngle=0 - The angle between minor tick marks, in degrees.
* @property {number} majorTickMarksLength=0 - The length of the major tick marks, in meters. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
* @property {number} minorTickMarksLength=0 - The length of the minor tick marks, in meters. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
* @property {Color} majorTickMarksColor=0,0,0 - The color of the major tick marks.
* @property {Color} minorTickMarksColor=0,0,0 - The color of the minor tick marks.
*/
QVariant Circle3DOverlay::getProperty(const QString& property) {
if (property == "startAt") {
return _startAt;
@ -384,6 +475,30 @@ QVariant Circle3DOverlay::getProperty(const QString& property) {
if (property == "innerRadius") {
return _innerRadius;
}
if (property == "innerStartColor") {
return xColorToVariant(_innerStartColor);
}
if (property == "innerEndColor") {
return xColorToVariant(_innerEndColor);
}
if (property == "outerStartColor") {
return xColorToVariant(_outerStartColor);
}
if (property == "outerEndColor") {
return xColorToVariant(_outerEndColor);
}
if (property == "innerStartAlpha") {
return _innerStartAlpha;
}
if (property == "innerEndAlpha") {
return _innerEndAlpha;
}
if (property == "outerStartAlpha") {
return _outerStartAlpha;
}
if (property == "outerEndAlpha") {
return _outerEndAlpha;
}
if (property == "hasTickMarks") {
return _hasTickMarks;
}
@ -409,7 +524,6 @@ QVariant Circle3DOverlay::getProperty(const QString& property) {
return Planar3DOverlay::getProperty(property);
}
bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) {

View file

@ -65,22 +65,22 @@ protected:
float _outerRadius { 1 };
float _innerRadius { 0 };
xColor _innerStartColor;
xColor _innerEndColor;
xColor _outerStartColor;
xColor _outerEndColor;
float _innerStartAlpha;
float _innerEndAlpha;
float _outerStartAlpha;
float _outerEndAlpha;
xColor _innerStartColor { DEFAULT_OVERLAY_COLOR };
xColor _innerEndColor { DEFAULT_OVERLAY_COLOR };
xColor _outerStartColor { DEFAULT_OVERLAY_COLOR };
xColor _outerEndColor { DEFAULT_OVERLAY_COLOR };
float _innerStartAlpha { DEFAULT_ALPHA };
float _innerEndAlpha { DEFAULT_ALPHA };
float _outerStartAlpha { DEFAULT_ALPHA };
float _outerEndAlpha { DEFAULT_ALPHA };
bool _hasTickMarks { false };
float _majorTickMarksAngle { 0 };
float _minorTickMarksAngle { 0 };
float _majorTickMarksLength { 0 };
float _minorTickMarksLength { 0 };
xColor _majorTickMarksColor;
xColor _minorTickMarksColor;
xColor _majorTickMarksColor { DEFAULT_OVERLAY_COLOR };
xColor _minorTickMarksColor { DEFAULT_OVERLAY_COLOR };
gpu::Primitive _solidPrimitive { gpu::TRIANGLE_FAN };
int _quadVerticesID { 0 };
int _lineVerticesID { 0 };

View file

@ -289,7 +289,7 @@ void ContextOverlayInterface::openInspectionCertificate() {
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer");
QJsonObject request;
request["certificate_id"] = entityProperties.getCertificateID();
@ -359,7 +359,7 @@ void ContextOverlayInterface::openInspectionCertificate() {
}
}
static const QString MARKETPLACE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace/items/";
static const QString MARKETPLACE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/";
void ContextOverlayInterface::openMarketplace() {
// lets open the tablet and go to the current item in

View file

@ -134,6 +134,56 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) {
}
}
/**jsdoc
* These are the properties of a <code>cube</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.CubeProperties
*
* @property {string} type=cube - Has the value <code>"cube"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*
* @property {number} borderSize - Not used.
*/
QVariant Cube3DOverlay::getProperty(const QString& property) {
if (property == "borderSize") {
return _borderSize;

View file

@ -112,6 +112,61 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) {
updateGrid();
}
/**jsdoc
* These are the properties of a <code>grid</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.GridProperties
*
* @property {string} type=grid - Has the value <code>"grid"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*
* @property {boolean} followCamera=true - If <code>true</code>, the grid is always visible even as the camera moves to another
* position.
* @property {number} majorGridEvery=5 - Integer number of <code>minorGridEvery</code> intervals at which to draw a thick grid
* line. Minimum value = <code>1</code>.
* @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value =
* <code>0.001</code>.
*/
QVariant Grid3DOverlay::getProperty(const QString& property) {
if (property == "followCamera") {
return _followCamera;

View file

@ -188,6 +188,62 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) {
}
}
/**jsdoc
* These are the properties of an <code>image3d</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.Image3DProperties
*
* @property {string} type=image3d - Has the value <code>"image3d"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*
* @property {boolean} isFacingAvatar - If <code>true</code>, the overlay is rotated to face the user's camera about an axis
* parallel to the user's avatar's "up" direction.
*
* @property {string} url - The URL of the PNG or JPG image to display.
* @property {Rect} subImage - The portion of the image to display. Defaults to the full image.
* @property {boolean} emissive - If <code>true</code>, the overlay is displayed at full brightness, otherwise it is rendered
* with scene lighting.
*/
QVariant Image3DOverlay::getProperty(const QString& property) {
if (property == "url") {
return _url;

View file

@ -19,6 +19,29 @@
QString const ImageOverlay::TYPE = "image";
QUrl const ImageOverlay::URL(QString("hifi/overlays/ImageOverlay.qml"));
// ImageOverlay's properties are defined in the QML file specified above.
/**jsdoc
* These are the properties of an <code>image</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.ImageProperties
*
* @property {Rect} bounds - The position and size of the image display area, in pixels. <em>Write-only.</em>
* @property {number} x - Integer left, x-coordinate value of the image display area = <code>bounds.x</code>.
* <em>Write-only.</em>
* @property {number} y - Integer top, y-coordinate value of the image display area = <code>bounds.y</code>.
* <em>Write-only.</em>
* @property {number} width - Integer width of the image display area = <code>bounds.width</code>. <em>Write-only.</em>
* @property {number} height - Integer height of the image display area = <code>bounds.height</code>. <em>Write-only.</em>
* @property {string} imageURL - The URL of the image file to display. The image is scaled to fit to the <code>bounds</code>.
* <em>Write-only.</em>
* @property {Vec2} subImage=0,0 - Integer coordinates of the top left pixel to start using image content from.
* <em>Write-only.</em>
* @property {Color} color=0,0,0 - The color to apply over the top of the image to colorize it. <em>Write-only.</em>
* @property {number} alpha=0.0 - The opacity of the color applied over the top of the image, <code>0.0</code> -
* <code>1.0</code>. <em>Write-only.</em>
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* <em>Write-only.</em>
*/
ImageOverlay::ImageOverlay()
: QmlOverlay(URL) { }

View file

@ -255,6 +255,67 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
}
}
/**jsdoc
* These are the properties of a <code>line3d</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.Line3DProperties
*
* @property {string} type=line3d - Has the value <code>"line3d"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Uuid} endParentID=null - The avatar, entity, or overlay that the end point of the line is parented to.
* @property {number} endParentJointIndex=65535 - Integer value specifying the skeleton joint that the end point of the line is
* attached to if <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
* @property {Vec3} start - The start point of the line. Synonyms: <code>startPoint</code>, <code>p1</code>, and
* <code>position</code>.
* @property {Vec3} end - The end point of the line. Synonyms: <code>endPoint</code> and <code>p2</code>.
* @property {Vec3} localStart - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>start</code>. Synonym: <code>localPosition</code>.
* @property {Vec3} localEnd - The local position of the overlay relative to its parent if the overlay has a
* <code>endParentID</code> set, otherwise the same value as <code>end</code>.
* @property {number} length - The length of the line, in meters. This can be set after creating a line with start and end
* points.
* @property {number} glow=0 - If <code>glow > 0</code>, the line is rendered with a glow.
* @property {number} lineWidth=0.02 - If <code>glow > 0</code>, this is the width of the glow, in meters.
*/
QVariant Line3DOverlay::getProperty(const QString& property) {
if (property == "start" || property == "startPoint" || property == "p1") {
return vec3toVariant(getStart());

View file

@ -152,7 +152,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
_scaleToFit = true;
setDimensions(vec3FromVariant(dimensions));
} else if (scale.isValid()) {
// if "scale" property is set but "dimentions" is not.
// if "scale" property is set but "dimensions" is not.
// do NOT scale to fit.
_scaleToFit = false;
}
@ -181,6 +181,10 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
Q_ARG(const QVariantMap&, textureMap));
}
// jointNames is read-only.
// jointPositions is read-only.
// jointOrientations is read-only.
// relative
auto jointTranslationsValue = properties["jointTranslations"];
if (jointTranslationsValue.canConvert(QVariant::List)) {
@ -276,6 +280,75 @@ vectorType ModelOverlay::mapJoints(mapFunction<itemType> function) const {
return result;
}
// Note: ModelOverlay overrides Volume3DOverlay's "dimensions" and "scale" properties.
/**jsdoc
* These are the properties of a <code>model</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.ModelProperties
*
* @property {string} type=sphere - Has the value <code>"model"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {string} url - The URL of the FBX or OBJ model used for the overlay.
* @property {Vec3} dimensions - The dimensions of the overlay. Synonym: <code>size</code>.
* @property {Vec3} scale - The scale factor applied to the model's dimensions.
* @property {object.<name, url>} textures - Maps the named textures in the model to the JPG or PNG images in the urls.
* @property {Array.<string>} jointNames - The names of the joints - if any - in the model. <em>Read-only</em>
* @property {Array.<Quat>} jointRotations - The relative rotations of the model's joints.
* @property {Array.<Vec3>} jointTranslations - The relative translations of the model's joints.
* @property {Array.<Quat>} jointOrientations - The absolute orientations of the model's joints, in world coordinates.
* <em>Read-only</em>
* @property {Array.<Vec3>} jointPositions - The absolute positions of the model's joints, in world coordinates.
* <em>Read-only</em>
* @property {string} animationSettings.url="" - The URL of an FBX file containing an animation to play.
* @property {number} animationSettings.fps=0 - The frame rate (frames/sec) to play the animation at.
* @property {number} animationSettings.firstFrame=0 - The frame to start playing at.
* @property {number} animationSettings.lastFrame=0 - The frame to finish playing at.
* @property {boolean} animationSettings.running=false - Whether or not the animation is playing.
* @property {boolean} animationSettings.loop=false - Whether or not the animation should repeat in a loop.
* @property {boolean} animationSettings.hold=false - Whether or not when the animation finishes, the rotations and
* translations of the last frame played should be maintained.
* @property {boolean} animationSettings.allowTranslation=false - Whether or not translations contained in the animation should
* be played.
*/
QVariant ModelOverlay::getProperty(const QString& property) {
if (property == "url") {
return _url.toString();

View file

@ -15,8 +15,8 @@
#include "Application.h"
static const xColor DEFAULT_OVERLAY_COLOR = { 255, 255, 255 };
static const float DEFAULT_ALPHA = 0.7f;
const xColor Overlay::DEFAULT_OVERLAY_COLOR = { 255, 255, 255 };
const float Overlay::DEFAULT_ALPHA = 0.7f;
Overlay::Overlay() :
_renderItemID(render::Item::INVALID_ITEM_ID),
@ -101,6 +101,27 @@ void Overlay::setProperties(const QVariantMap& properties) {
}
}
// JSDoc for copying to @typedefs of overlay types that inherit Overlay.
/**jsdoc
* @property {string} type=TODO - Has the value <code>"TODO"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*/
QVariant Overlay::getProperty(const QString& property) {
if (property == "type") {
return QVariant(getType());

View file

@ -122,6 +122,9 @@ protected:
unsigned int _stackOrder { 0 };
static const xColor DEFAULT_OVERLAY_COLOR;
static const float DEFAULT_ALPHA;
private:
OverlayID _overlayID; // only used for non-3d overlays
};

View file

@ -23,6 +23,15 @@ AABox Overlay2D::getBounds() const {
glm::vec3(_bounds.width(), _bounds.height(), 0.01f));
}
// JSDoc for copying to @typedefs of overlay types that inherit Overlay2D.
// QmlOverlay-derived classes don't support getProperty().
/**jsdoc
* @property {Rect} bounds - The position and size of the rectangle. <em>Write-only.</em>
* @property {number} x - Integer left, x-coordinate value = <code>bounds.x</code>. <em>Write-only.</em>
* @property {number} y - Integer top, y-coordinate value = <code>bounds.y</code>. <em>Write-only.</em>
* @property {number} width - Integer width of the rectangle = <code>bounds.width</code>. <em>Write-only.</em>
* @property {number} height - Integer height of the rectangle = <code>bounds.height</code>. <em>Write-only.</em>
*/
void Overlay2D::setProperties(const QVariantMap& properties) {
Overlay::setProperties(properties);

View file

@ -172,6 +172,63 @@ OverlayID Overlays::addOverlay(const QString& type, const QVariant& properties)
Overlay::Pointer thisOverlay = nullptr;
/**jsdoc
* <p>An overlay may be one of the following types:</p>
* <table>
* <thead>
* <tr><th>Value</th><th>2D/3D</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>circle3d</code></td><td>3D</td><td>A circle.</td></tr>
* <tr><td><code>cube</code></td><td>3D</td><td>A cube. Can also use a <code>shape</code> overlay to create a
* cube.</td></tr>
* <tr><td><code>grid</code></td><td>3D</td><td>A grid of lines in a plane.</td></tr>
* <tr><td><code>image</code></td><td>2D</td><td>An image. Synonym: <code>billboard</code>.</td></tr>
* <tr><td><code>image3d</code></td><td>3D</td><td>An image.</td></tr>
* <tr><td><code>line3d</code></td><td>3D</td><td>A line.</td></tr>
* <tr><td><code>model</code></td><td>3D</td><td>A model.</td></tr>
* <tr><td><code>rectangle</code></td><td>2D</td><td>A rectangle.</td></tr>
* <tr><td><code>rectangle3d</code></td><td>3D</td><td>A rectangle.</td></tr>
* <tr><td><code>shape</code></td><td>3D</td><td>A geometric shape, such as a cube, sphere, or cylinder.</td></tr>
* <tr><td><code>sphere</code></td><td>3D</td><td>A sphere. Can also use a <code>shape</code> overlay to create a
* sphere.</td></tr>
* <tr><td><code>text</code></td><td>2D</td><td>Text.</td></tr>
* <tr><td><code>text3d</code></td><td>3D</td><td>Text.</td></tr>
* <tr><td><code>web3d</code></td><td>3D</td><td>Web content.</td></tr>
* </tbody>
* </table>
* <p>2D overlays are rendered on the display surface in desktop mode and on the HUD surface in HMD mode. 3D overlays are
* rendered at a position and orientation in-world.<p>
* <p>Each overlay type has different {@link Overlays.OverlayProperties|OverlayProperties}.</p>
* @typedef {string} Overlays.OverlayType
*/
/**jsdoc
* <p>Different overlay types have different properties:</p>
* <table>
* <thead>
* <tr><th>{@link Overlays.OverlayType|OverlayType}</th><th>Overlay Properties</th></tr>
* </thead>
* <tbody>
* <tr><td><code>circle3d</code></td><td>{@link Overlays.Circle3DProperties|Circle3DProperties}</td></tr>
* <tr><td><code>cube</code></td><td>{@link Overlays.CubeProperties|CubeProperties}</td></tr>
* <tr><td><code>grid</code></td><td>{@link Overlays.GridProperties|GridProperties}</td></tr>
* <tr><td><code>image</code></td><td>{@link Overlays.ImageProperties|ImageProperties}</td></tr>
* <tr><td><code>image3d</code></td><td>{@link Overlays.Image3DProperties|Image3DProperties}</td></tr>
* <tr><td><code>line3d</code></td><td>{@link Overlays.Line3DProperties|Line3DProperties}</td></tr>
* <tr><td><code>model</code></td><td>{@link Overlays.ModelProperties|ModelProperties}</td></tr>
* <tr><td><code>rectangle</code></td><td>{@link Overlays.RectangleProperties|RectangleProperties}</td></tr>
* <tr><td><code>rectangle3d</code></td><td>{@link Overlays.Rectangle3DProperties|Rectangle3DProperties}</td></tr>
* <tr><td><code>shape</code></td><td>{@link Overlays.ShapeProperties|ShapeProperties}</td></tr>
* <tr><td><code>sphere</code></td><td>{@link Overlays.SphereProperties|SphereProperties}</td></tr>
* <tr><td><code>text</code></td><td>{@link Overlays.TextProperties|TextProperties}</td></tr>
* <tr><td><code>text3d</code></td><td>{@link Overlays.Text3DProperties|Text3DProperties}</td></tr>
* <tr><td><code>web3d</code></td><td>{@link Overlays.Web3DProperties|Web3DProperties}</td></tr>
* </tbody>
* </table>
* @typedef {object} Overlays.OverlayProperties
*/
if (type == ImageOverlay::TYPE) {
thisOverlay = Overlay::Pointer(new ImageOverlay(), [](Overlay* ptr) { ptr->deleteLater(); });
} else if (type == Image3DOverlay::TYPE || type == "billboard") { // "billboard" for backwards compatibility

View file

@ -45,12 +45,13 @@ void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPro
const OverlayID UNKNOWN_OVERLAY_ID = OverlayID();
/**jsdoc
* @typedef Overlays.RayToOverlayIntersectionResult
* @property {bool} intersects True if the PickRay intersected with a 3D overlay.
* @property {Overlays.OverlayID} overlayID The ID of the overlay that was intersected with.
* @property {float} distance The distance from the PickRay origin to the intersection point.
* @property {Vec3} surfaceNormal The normal of the surface that was intersected with.
* @property {Vec3} intersection The point at which the PickRay intersected with the overlay.
* @typedef {object} Overlays.RayToOverlayIntersectionResult
* @property {boolean} intersects - <code>true</code> if the {@link PickRay} intersected with a 3D overlay, otherwise
* <code>false</code>.
* @property {Uuid} overlayID - The UUID of the overlay that was intersected.
* @property {number} distance - The distance from the {@link PickRay} origin to the intersection point.
* @property {Vec3} surfaceNormal - The normal of the overlay surface at the intersection point.
* @property {Vec3} intersection - The position of the intersection point.
*/
class RayToOverlayIntersectionResult {
public:
@ -70,13 +71,11 @@ QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine,
void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value);
/**jsdoc
* @typedef {int} Overlays.OverlayID
*/
/**jsdoc
*
* Overlays namespace...
* The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to
* yourself and that aren't persisted to the domain. They are used for UI.
* @namespace Overlays
* @property {Uuid} keyboardFocusOverlay - Get or set the {@link Overlays.OverlayType|web3d} overlay that has keyboard focus.
* If no overlay is set, get returns <code>null</code>; set to <code>null</code> to clear keyboard focus.
*/
class Overlays : public QObject {
@ -111,100 +110,246 @@ public:
public slots:
/**jsdoc
* Add an overlays to the scene. The properties specified will depend
* on the type of overlay that is being created.
*
* Add an overlay to the scene.
* @function Overlays.addOverlay
* @param {string} type The type of the overlay to add.
* @param {Overlays.OverlayProperties} The properties of the overlay that you want to add.
* @return {Overlays.OverlayID} The ID of the newly created overlay.
* @param {Overlays.OverlayType} type - The type of the overlay to add.
* @param {Overlays.OverlayProperties} properties - The properties of the overlay to add.
* @returns {Uuid} The ID of the newly created overlay.
* @example <caption>Add a cube overlay in front of your avatar.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
*/
OverlayID addOverlay(const QString& type, const QVariant& properties);
/**jsdoc
* Create a clone of an existing overlay.
*
* @function Overlays.cloneOverlay
* @param {Overlays.OverlayID} overlayID The ID of the overlay to clone.
* @return {Overlays.OverlayID} The ID of the new overlay.
* @param {Uuid} overlayID - The ID of the overlay to clone.
* @returns {Uuid} The ID of the new overlay.
* @example <caption>Add an overlay in front of your avatar, clone it, and move the clone to be above the
* original.</caption>
* var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 }));
* var original = Overlays.addOverlay("cube", {
* position: position,
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
*
* var clone = Overlays.cloneOverlay(original);
* Overlays.editOverlay(clone, {
* position: Vec3.sum({ x: 0, y: 0.5, z: 0}, position)
* });
*/
OverlayID cloneOverlay(OverlayID id);
/**jsdoc
* Edit an overlay's properties.
*
* @function Overlays.editOverlay
* @param {Overlays.OverlayID} overlayID The ID of the overlay to edit.
* @return {bool} `true` if the overlay was found and edited, otherwise false.
* @param {Uuid} overlayID - The ID of the overlay to edit.
* @param {Overlays.OverlayProperties} properties - The properties changes to make.
* @returns {boolean} <code>true</code> if the overlay was found and edited, otherwise <code>false</code>.
* @example <caption>Add an overlay in front of your avatar then change its color.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
*
* var success = Overlays.editOverlay(overlay, {
* color: { red: 255, green: 0, blue: 0 }
* });
* print("Success: " + success);
*/
bool editOverlay(OverlayID id, const QVariant& properties);
/// edits an overlay updating only the included properties, will return the identified OverlayID in case of
/// successful edit, if the input id is for an unknown overlay this function will have no effect
/**jsdoc
* Edit multiple overlays' properties.
* @function Overlays.editOverlays
* @param propertiesById {object.<Uuid, Overlays.OverlayProperties>} - An object with overlay IDs as keys and
* {@link Overlays.OverlayProperties|OverlayProperties} edits to make as values.
* @returns {boolean} <code>true</code> if all overlays were found and edited, otherwise <code>false</code> (some may have
* been found and edited).
* @example <caption>Create two overlays in front of your avatar then change their colors.</caption>
* var overlayA = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.3, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* var overlayB = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0.3, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
*
* var overlayEdits = {};
* overlayEdits[overlayA] = { color: { red: 255, green: 0, blue: 0 } };
* overlayEdits[overlayB] = { color: { red: 0, green: 255, blue: 0 } };
* var success = Overlays.editOverlays(overlayEdits);
* print("Success: " + success);
*/
bool editOverlays(const QVariant& propertiesById);
/**jsdoc
* Delete an overlay.
*
* @function Overlays.deleteOverlay
* @param {Overlays.OverlayID} overlayID The ID of the overlay to delete.
* @param {Uuid} overlayID - The ID of the overlay to delete.
* @example <caption>Create an overlay in front of your avatar then delete it.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* print("Overlay: " + overlay);
* Overlays.deleteOverlay(overlay);
*/
void deleteOverlay(OverlayID id);
/**jsdoc
* Get the type of an overlay.
*
* @function Overlays.getOverlayType
* @param {Overlays.OverlayID} overlayID The ID of the overlay to get the type of.
* @return {string} The type of the overlay if found, otherwise the empty string.
* @param {Uuid} overlayID - The ID of the overlay to get the type of.
* @returns {Overlays.OverlayType} The type of the overlay if found, otherwise an empty string.
* @example <caption>Create an overlay in front of your avatar then get and report its type.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* var type = Overlays.getOverlayType(overlay);
* print("Type: " + type);
*/
QString getOverlayType(OverlayID overlayId);
/**jsdoc
* Get the overlay Script object.
*
* @function Overlays.getOverlayObject
* @param {Overlays.OverlayID} overlayID The ID of the overlay to get the script object of.
* @return {Object} The script object for the overlay if found.
*/
* Get the overlay script object.
* @function Overlays.getOverlayObject
* @param {Uuid} overlayID - The ID of the overlay to get the script object of.
* @returns {object} The script object for the overlay if found.
*/
QObject* getOverlayObject(OverlayID id);
/**jsdoc
* Get the ID of the overlay at a particular point on the HUD/screen.
*
* Get the ID of the 2D overlay at a particular point on the screen or HUD.
* @function Overlays.getOverlayAtPoint
* @param {Vec2} point The point to check for an overlay.
* @return {Overlays.OverlayID} The ID of the overlay at the point specified.
* If no overlay is found, `0` will be returned.
* @param {Vec2} point - The point to check for an overlay.
* @returns {Uuid} The ID of the 2D overlay at the specified point if found, otherwise <code>null</code>.
* @example <caption>Create a 2D overlay and add an event function that reports the overlay clicked on, if any.</caption>
* var overlay = Overlays.addOverlay("rectangle", {
* bounds: { x: 100, y: 100, width: 200, height: 100 },
* color: { red: 255, green: 255, blue: 255 }
* });
* print("Created: " + overlay);
*
* Controller.mousePressEvent.connect(function (event) {
* var overlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
* print("Clicked: " + overlay);
* });
*/
OverlayID getOverlayAtPoint(const glm::vec2& point);
/**jsdoc
* Get the value of a an overlay's property.
*
* Get the value of a 3D overlay's property.
* @function Overlays.getProperty
* @param {Overlays.OverlayID} The ID of the overlay to get the property of.
* @param {string} The name of the property to get the value of.
* @return {Object} The value of the property. If the overlay or the property could
* not be found, `undefined` will be returned.
* @param {Uuid} overlayID - The ID of the overlay. <em>Must be for a 3D {@link Overlays.OverlayType|OverlayType}.</em>
* @param {string} property - The name of the property value to get.
* @returns {object} The value of the property if the 3D overlay and property can be found, otherwise
* <code>undefined</code>.
* @example <caption>Create an overlay in front of your avatar then report its alpha property value.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* var alpha = Overlays.getProperty(overlay, "alpha");
* print("Overlay alpha: " + alpha);
*/
OverlayPropertyResult getProperty(OverlayID id, const QString& property);
/**jsdoc
* Get the values of an overlay's properties.
* @function Overlays.getProperties
* @param {Uuid} overlayID - The ID of the overlay.
* @param {Array.<string>} properties - An array of names of properties to get the values of.
* @returns {Overlays.OverlayProperties} The values of valid properties if the overlay can be found, otherwise
* <code>undefined</code>.
* @example <caption>Create an overlay in front of your avatar then report some of its properties.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* var properties = Overlays.getProperties(overlay, ["color", "alpha", "grabbable"]);
* print("Overlay properties: " + JSON.stringify(properties));
*/
OverlayPropertyResult getProperties(const OverlayID& id, const QStringList& properties);
/**jsdoc
* Get the values of multiple overlays' properties.
* @function Overlays.getOverlaysProperties
* @param propertiesById {object.<Uuid, Array.<string>>} - An object with overlay IDs as keys and arrays of the names of
* properties to get for each as values.
* @returns {object.<Uuid, Overlays.OverlayProperties>} An object with overlay IDs as keys and
* {@link Overlays.OverlayProperties|OverlayProperties} as values.
* @example <caption>Create two cube overlays in front of your avatar then get some of their properties.</caption>
* var overlayA = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.3, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* var overlayB = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0.3, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* var propertiesToGet = {};
* propertiesToGet[overlayA] = ["color", "alpha"];
* propertiesToGet[overlayB] = ["dimensions"];
* var properties = Overlays.getOverlaysProperties(propertiesToGet);
* print("Overlays properties: " + JSON.stringify(properties));
*/
OverlayPropertyResult getOverlaysProperties(const QVariant& overlaysProperties);
/*jsdoc
* Find the closest 3D overlay hit by a pick ray.
*
/**jsdoc
* Find the closest 3D overlay intersected by a {@link PickRay}.
* @function Overlays.findRayIntersection
* @param {PickRay} The PickRay to use for finding overlays.
* @param {bool} Unused; Exists to match Entity interface
* @param {List of Overlays.OverlayID} Whitelist for intersection test.
* @param {List of Overlays.OverlayID} Blacklist for intersection test.
* @param {bool} Unused; Exists to match Entity interface
* @param {bool} Unused; Exists to match Entity interface
* @return {Overlays.RayToOverlayIntersectionResult} The result of the ray cast.
* @param {PickRay} pickRay - The PickRay to use for finding overlays.
* @param {boolean} [precisionPicking=false] - <em>Unused</em>; exists to match Entity API.
* @param {Array.<Uuid>} [overlayIDsToInclude=[]] - Whitelist for intersection test. If empty then the result isn't limited
* to overlays in the list.
* @param {Array.<Uuid>} [overlayIDsToExclude=[]] - Blacklist for intersection test. If empty then the result doesn't
* exclude overlays in the list.
* @param {boolean} [visibleOnly=false] - <em>Unused</em>; exists to match Entity API.
* @param {boolean} [collidableOnly=false] - <em>Unused</em>; exists to match Entity API.
* @returns {Overlays.RayToOverlayIntersectionResult} The closest 3D overlay intersected by <code>pickRay</code>, taking
* into account <code>overlayIDsToInclude</code> and <code>overlayIDsToExclude</code> if they're not empty.
* @example <caption>Create a cube overlay in front of your avatar. Report 3D overlay intersection details for mouse
* clicks.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
*
* Controller.mousePressEvent.connect(function (event) {
* var pickRay = Camera.computePickRay(event.x, event.y);
* var intersection = Overlays.findRayIntersection(pickRay);
* print("Intersection: " + JSON.stringify(intersection));
* });
*/
RayToOverlayIntersectionResult findRayIntersection(const PickRay& ray,
bool precisionPicking = false,
@ -213,61 +358,113 @@ public slots:
bool visibleOnly = false,
bool collidableOnly = false);
// Same as above but with QVectors
// TODO: Apart from the name, this function signature on JavaScript is identical to that of findRayIntersection() and should
// probably be removed from the JavaScript API so as not to cause confusion.
/**jsdoc
* Find the closest 3D overlay intersected by a {@link PickRay}.
* @function Overlays.findRayIntersectionVector
* @deprecated Use {@link Overlays.findRayIntersection} instead; it has identical parameters and results.
* @param {PickRay} pickRay - The PickRay to use for finding overlays.
* @param {boolean} [precisionPicking=false] - <em>Unused</em>; exists to match Entity API.
* @param {Array.<Uuid>} [overlayIDsToInclude=[]] - Whitelist for intersection test. If empty then the result isn't limited
* to overlays in the list.
* @param {Array.<Uuid>} [overlayIDsToExclude=[]] - Blacklist for intersection test. If empty then the result doesn't
* exclude overlays in the list.
* @param {boolean} [visibleOnly=false] - <em>Unused</em>; exists to match Entity API.
* @param {boolean} [collidableOnly=false] - <em>Unused</em>; exists to match Entity API.
* @returns {Overlays.RayToOverlayIntersectionResult} The closest 3D overlay intersected by <code>pickRay</code>, taking
* into account <code>overlayIDsToInclude</code> and <code>overlayIDsToExclude</code> if they're not empty.
*/
RayToOverlayIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking,
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
/**jsdoc
* Return a list of 3d overlays with bounding boxes that touch the given sphere
*
* Return a list of 3D overlays with bounding boxes that touch a search sphere.
* @function Overlays.findOverlays
* @param {Vec3} center the point to search from.
* @param {float} radius search radius
* @return {Overlays.OverlayID[]} list of overlays withing the radius
* @param {Vec3} center - The center of the search sphere.
* @param {number} radius - The radius of the search sphere.
* @returns {Uuid[]} An array of overlay IDs with bounding boxes that touch a search sphere.
* @example <caption>Create two cube overlays in front of your avatar then search for overlays near your avatar.</caption>
* var overlayA = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.3, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* print("Overlay A: " + overlayA);
* var overlayB = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0.3, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* print("Overlay B: " + overlayB);
*
* var overlaysFound = Overlays.findOverlays(MyAvatar.position, 5.0);
* print("Overlays found: " + JSON.stringify(overlaysFound));
*/
QVector<QUuid> findOverlays(const glm::vec3& center, float radius);
/**jsdoc
* Check whether an overlay's assets have been loaded. For example, if the
* overlay is an "image" overlay, this will indicate whether the its image
* has loaded.
* Check whether an overlay's assets have been loaded. For example, for an <code>image</code> overlay the result indicates
* whether its image has been loaded.
* @function Overlays.isLoaded
* @param {Overlays.OverlayID} The ID of the overlay to check.
* @return {bool} `true` if the overlay's assets have been loaded, otherwise `false`.
* @param {Uuid} overlayID - The ID of the overlay to check.
* @returns {boolean} <code>true</code> if the overlay's assets have been loaded, otherwise <code>false</code>.
* @example <caption>Create an image overlay and report whether its image is loaded after 1s.</caption>
* var overlay = Overlays.addOverlay("image", {
* bounds: { x: 100, y: 100, width: 200, height: 200 },
* imageURL: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png"
* });
* Script.setTimeout(function () {
* var isLoaded = Overlays.isLoaded(overlay);
* print("Image loaded: " + isLoaded);
* }, 1000);
*/
bool isLoaded(OverlayID id);
/**jsdoc
* Calculates the size of the given text in the specified overlay if it is a text overlay.
* If it is a 2D text overlay, the size will be in pixels.
* If it is a 3D text overlay, the size will be in meters.
*
* @function Overlays.textSize
* @param {Overlays.OverlayID} The ID of the overlay to measure.
* @param {string} The string to measure.
* @return {Vec2} The size of the text.
* @param {Uuid} overlayID - The ID of the overlay to use for calculation.
* @param {string} text - The string to calculate the size of.
* @returns {Size} The size of the <code>text</code> if the overlay is a text overlay, otherwise
* <code>{ height: 0, width : 0 }</code>. If the overlay is a 2D overlay, the size is in pixels; if the overlay is a 3D
* overlay, the size is in meters.
* @example <caption>Calculate the size of "hello" in a 3D text overlay.</caption>
* var overlay = Overlays.addOverlay("text3d", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -2 })),
* rotation: MyAvatar.orientation,
* text: "hello",
* lineHeight: 0.2
* });
* var textSize = Overlays.textSize(overlay, "hello");
* print("Size of \"hello\": " + JSON.stringify(textSize));
*/
QSizeF textSize(OverlayID id, const QString& text);
/**jsdoc
* Get the width of the virtual 2D HUD.
*
* Get the width of the window or HUD.
* @function Overlays.width
* @return {float} The width of the 2D HUD.
* @returns {number} The width, in pixels, of the Interface window if in desktop mode or the HUD if in HMD mode.
*/
float width();
/**jsdoc
* Get the height of the virtual 2D HUD.
*
* Get the height of the window or HUD.
* @function Overlays.height
* @return {float} The height of the 2D HUD.
* @returns {number} The height, in pixels, of the Interface window if in desktop mode or the HUD if in HMD mode.
*/
float height();
/// return true if there is an overlay with that id else false
/**jsdoc
* Check if there is an overlay of a given ID.
* @function Overlays.isAddedOverly
* @param {Uuid} overlayID - The ID to check.
* @returns {boolean} <code>true</code> if an overlay with the given ID exists, <code>false</code> otherwise.
*/
bool isAddedOverlay(OverlayID id);
#if OVERLAY_PANELS
@ -294,36 +491,237 @@ public slots:
#endif
/**jsdoc
* Generate a mouse press event on an overlay.
* @function Overlays.sendMousePressOnOverlay
* @param {Uuid} overlayID - The ID of the overlay to generate a mouse press event on.
* @param {PointerEvent} event - The mouse press event details.
* @example <caption>Create a 2D rectangle overlay plus a 3D cube overlay and generate mousePressOnOverlay events for the 2D
* overlay.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* print("3D overlay: " + overlay);
*
* var overlay = Overlays.addOverlay("rectangle", {
* bounds: { x: 100, y: 100, width: 200, height: 100 },
* color: { red: 255, green: 255, blue: 255 }
* });
* print("2D overlay: " + overlay);
*
* // Overlays.mousePressOnOverlay by default applies only to 3D overlays.
* Overlays.mousePressOnOverlay.connect(function(overlayID, event) {
* print("Clicked: " + overlayID);
* });
*
* Controller.mousePressEvent.connect(function (event) {
* // Overlays.getOverlayAtPoint applies only to 2D overlays.
* var overlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
* if (overlay) {
* Overlays.sendMousePressOnOverlay(overlay, {
* type: "press",
* id: 0,
* pos2D: event
* });
* }
* });
*/
void sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event);
/**jsdoc
* Generate a mouse release event on an overlay.
* @function Overlays.sendMouseReleaseOnOverlay
* @param {Uuid} overlayID - The ID of the overlay to generate a mouse release event on.
* @param {PointerEvent} event - The mouse release event details.
*/
void sendMouseReleaseOnOverlay(const OverlayID& overlayID, const PointerEvent& event);
/**jsdoc
* Generate a mouse move event on an overlay.
* @function Overlays.sendMouseMoveOnOverlay
* @param {Uuid} overlayID - The ID of the overlay to generate a mouse move event on.
* @param {PointerEvent} event - The mouse move event details.
*/
void sendMouseMoveOnOverlay(const OverlayID& overlayID, const PointerEvent& event);
/**jsdoc
* Generate a hover enter event on an overlay.
* @function Overlays.sendHoverEnterOverlay
* @param {Uuid} id - The ID of the overlay to generate a hover enter event on.
* @param {PointerEvent} event - The hover enter event details.
*/
void sendHoverEnterOverlay(const OverlayID& overlayID, const PointerEvent& event);
/**jsdoc
* Generate a hover over event on an overlay.
* @function Overlays.sendHoverOverOverlay
* @param {Uuid} overlayID - The ID of the overlay to generate a hover over event on.
* @param {PointerEvent} event - The hover over event details.
*/
void sendHoverOverOverlay(const OverlayID& overlayID, const PointerEvent& event);
/**jsdoc
* Generate a hover leave event on an overlay.
* @function Overlays.sendHoverLeaveOverlay
* @param {Uuid} overlayID - The ID of the overlay to generate a hover leave event on.
* @param {PointerEvent} event - The hover leave event details.
*/
void sendHoverLeaveOverlay(const OverlayID& overlayID, const PointerEvent& event);
/**jsdoc
* Get the ID of the Web3D overlay that has keyboard focus.
* @function Overlays.getKeyboardFocusOverlay
* @returns {Uuid} The ID of the {@link Overlays.OverlayType|web3d} overlay that has focus, if any, otherwise
* <code>null</code>.
*/
OverlayID getKeyboardFocusOverlay();
/**jsdoc
* Set the Web3D overlay that has keyboard focus.
* @function Overlays.setKeyboardFocusOverlay
* @param {Uuid} overlayID - The ID of the {@link Overlays.OverlayType|web3d} overlay to set keyboard focus to. Use
* {@link Uuid|Uuid.NULL} or <code>null</code> to unset keyboard focus from an overlay.
*/
void setKeyboardFocusOverlay(const OverlayID& id);
signals:
/**jsdoc
* Emitted when an overlay is deleted
*
* Triggered when an overlay is deleted.
* @function Overlays.overlayDeleted
* @param {OverlayID} The ID of the overlay that was deleted.
* @param {Uuid} overlayID - The ID of the overlay that was deleted.
* @returns {Signal}
* @example <caption>Create an overlay then delete it after 1s.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* print("Overlay: " + overlay);
*
* Overlays.overlayDeleted.connect(function(overlayID) {
* print("Deleted: " + overlayID);
* });
* Script.setTimeout(function () {
* Overlays.deleteOverlay(overlay);
* }, 1000);
*/
void overlayDeleted(OverlayID id);
void panelDeleted(OverlayID id);
#if OVERLAY_PANELS
void panelDeleted(OverlayID id);
#endif
/**jsdoc
* Triggered when a mouse press event occurs on an overlay. Only occurs for 3D overlays (unless you use
* {@link Overlays.sendMousePressOnOverlay|sendMousePressOnOverlay} for a 2D overlay).
* @function Overlays.mousePressOnOverlay
* @param {Uuid} overlayID - The ID of the overlay the mouse press event occurred on.
* @param {PointerEvent} event - The mouse press event details.
* @returns {Signal}
* @example <caption>Create a cube overlay in front of your avatar and report mouse clicks on it.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* print("My overlay: " + overlay);
*
* Overlays.mousePressOnOverlay.connect(function(overlayID, event) {
* if (overlayID === overlay) {
* print("Clicked on my overlay");
* }
* });
*/
void mousePressOnOverlay(OverlayID overlayID, const PointerEvent& event);
/**jsdoc
* Triggered when a mouse double press event occurs on an overlay. Only occurs for 3D overlays.
* @function Overlays.mouseDoublePressOnOverlay
* @param {Uuid} overlayID - The ID of the overlay the mouse double press event occurred on.
* @param {PointerEvent} event - The mouse double press event details.
* @returns {Signal}
*/
void mouseDoublePressOnOverlay(OverlayID overlayID, const PointerEvent& event);
/**jsdoc
* Triggered when a mouse release event occurs on an overlay. Only occurs for 3D overlays (unless you use
* {@link Overlays.sendMouseReleaseOnOverlay|sendMouseReleaseOnOverlay} for a 2D overlay).
* @function Overlays.mouseReleaseOnOverlay
* @param {Uuid} overlayID - The ID of the overlay the mouse release event occurred on.
* @param {PointerEvent} event - The mouse release event details.
* @returns {Signal}
*/
void mouseReleaseOnOverlay(OverlayID overlayID, const PointerEvent& event);
/**jsdoc
* Triggered when a mouse move event occurs on an overlay. Only occurs for 3D overlays (unless you use
* {@link Overlays.sendMouseMoveOnOverlay|sendMouseMoveOnOverlay} for a 2D overlay).
* @function Overlays.mouseMoveOnOverlay
* @param {Uuid} overlayID - The ID of the overlay the mouse moved event occurred on.
* @param {PointerEvent} event - The mouse move event details.
* @returns {Signal}
*/
void mouseMoveOnOverlay(OverlayID overlayID, const PointerEvent& event);
/**jsdoc
* Triggered when a mouse press event occurs on something other than a 3D overlay.
* @function Overlays.mousePressOffOverlay
* @returns {Signal}
*/
void mousePressOffOverlay();
/**jsdoc
* Triggered when a mouse double press event occurs on something other than a 3D overlay.
* @function Overlays.mouseDoublePressOffOverlay
* @returns {Signal}
*/
void mouseDoublePressOffOverlay();
/**jsdoc
* Triggered when a mouse cursor starts hovering over an overlay. Only occurs for 3D overlays (unless you use
* {@link Overlays.sendHoverEnterOverlay|sendHoverEnterOverlay} for a 2D overlay).
* @function Overlays.hoverEnterOverlay
* @param {Uuid} overlayID - The ID of the overlay the mouse moved event occurred on.
* @param {PointerEvent} event - The mouse move event details.
* @returns {Signal}
* @example <caption>Create a cube overlay in front of your avatar and report when you start hovering your mouse over
* it.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
* solid: true
* });
* print("Overlay: " + overlay);
* Overlays.hoverEnterOverlay.connect(function(overlayID, event) {
* print("Hover enter: " + overlayID);
* });
*/
void hoverEnterOverlay(OverlayID overlayID, const PointerEvent& event);
/**jsdoc
* Triggered when a mouse cursor continues hovering over an overlay. Only occurs for 3D overlays (unless you use
* {@link Overlays.sendHoverOverOverlay|sendHoverOverOverlay} for a 2D overlay).
* @function Overlays.hoverOverOverlay
* @param {Uuid} overlayID - The ID of the overlay the hover over event occurred on.
* @param {PointerEvent} event - The hover over event details.
* @returns {Signal}
*/
void hoverOverOverlay(OverlayID overlayID, const PointerEvent& event);
/**jsdoc
* Triggered when a mouse cursor finishes hovering over an overlay. Only occurs for 3D overlays (unless you use
* {@link Overlays.sendHoverLeaveOverlay|sendHoverLeaveOverlay} for a 2D overlay).
* @function Overlays.hoverLeaveOverlay
* @param {Uuid} overlayID - The ID of the overlay the hover leave event occurred on.
* @param {PointerEvent} event - The hover leave event details.
* @returns {Signal}
*/
void hoverLeaveOverlay(OverlayID overlayID, const PointerEvent& event);
private:

View file

@ -27,6 +27,8 @@ bool PanelAttachable::getParentVisible() const {
#endif
}
// JSDoc for copying to @typedefs of overlay types that inherit PanelAttachable.
// No JSDoc because these properties are not actually used.
QVariant PanelAttachable::getProperty(const QString& property) {
if (property == "offsetPosition") {
return vec3toVariant(getOffsetPosition());

View file

@ -58,6 +58,10 @@ void Planar3DOverlay::setProperties(const QVariantMap& properties) {
}
}
// JSDoc for copying to @typedefs of overlay types that inherit Planar3DOverlay.
/**jsdoc
* @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*/
QVariant Planar3DOverlay::getProperty(const QString& property) {
if (property == "dimensions" || property == "scale" || property == "size") {
return vec2toVariant(getDimensions());

View file

@ -27,8 +27,8 @@ public:
void setDimensions(float value) { setDimensions(glm::vec2(value)); }
void setDimensions(const glm::vec2& value);
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
virtual void setProperties(const QVariantMap& properties) override;
virtual QVariant getProperty(const QString& property) override;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) override;

View file

@ -53,6 +53,7 @@ QmlOverlay::~QmlOverlay() {
_qmlElement.reset();
}
// QmlOverlay replaces Overlay's properties with those defined in the QML file used but keeps Overlay2D's properties.
void QmlOverlay::setProperties(const QVariantMap& properties) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setProperties", Q_ARG(QVariantMap, properties));

View file

@ -107,6 +107,54 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() {
return builder.build();
}
/**jsdoc
* These are the properties of a <code>rectangle3d</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.Rectangle3DProperties
*
* @property {string} type=rectangle3d - Has the value <code>"rectangle3d"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*/
void Rectangle3DOverlay::setProperties(const QVariantMap& properties) {
Planar3DOverlay::setProperties(properties);
}

View file

@ -11,6 +11,29 @@
QString const RectangleOverlay::TYPE = "rectangle";
QUrl const RectangleOverlay::URL(QString("hifi/overlays/RectangleOverlay.qml"));
// RectangleOverlay's properties are defined in the QML file specified above.
/**jsdoc
* These are the properties of a <code>rectangle</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.RectangleProperties
*
* @property {Rect} bounds - The position and size of the rectangle, in pixels. <em>Write-only.</em>
* @property {number} x - Integer left, x-coordinate value = <code>bounds.x</code>. <em>Write-only.</em>
* @property {number} y - Integer top, y-coordinate value = <code>bounds.y</code>. <em>Write-only.</em>
* @property {number} width - Integer width of the rectangle = <code>bounds.width</code>. <em>Write-only.</em>
* @property {number} height - Integer height of the rectangle = <code>bounds.height</code>. <em>Write-only.</em>
*
* @property {Color} color=0,0,0 - The color of the overlay. <em>Write-only.</em>
* @property {number} alpha=1.0 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>. <em>Write-only.</em>
* @property {number} borderWidth=1 - Integer width of the border, in pixels. The border is drawn within the rectangle's bounds.
* It is not drawn unless either <code>borderColor</code> or <code>borderAlpha</code> are specified. <em>Write-only.</em>
* @property {number} radius=0 - Integer corner radius, in pixels. <em>Write-only.</em>
* @property {Color} borderColor=0,0,0 - The color of the border. <em>Write-only.</em>
* @property {number} borderAlpha=1.0 - The opacity of the border, <code>0.0</code> - <code>1.0</code>.
* <em>Write-only.</em>
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* <em>Write-only.</em>
*/
RectangleOverlay::RectangleOverlay() : QmlOverlay(URL) {}
RectangleOverlay::RectangleOverlay(const RectangleOverlay* rectangleOverlay)

View file

@ -108,6 +108,57 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) {
}
}
/**jsdoc
* These are the properties of a <code>shape</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.ShapeProperties
*
* @property {string} type=shape - Has the value <code>"shape"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*
* @property {Shape} shape=Hexagon - The geometrical shape of the overlay.
* @property {number} borderSize - Not used.
*/
QVariant Shape3DOverlay::getProperty(const QString& property) {
if (property == "borderSize") {
return _borderSize;

View file

@ -26,6 +26,56 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) :
{
}
// If Sphere3DOverlay had a getProperty() method then it would go here; do JSDoc here.
/**jsdoc
* These are the properties of a <code>sphere</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.SphereProperties
*
* @property {string} type=sphere - Has the value <code>"sphere"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*/
void Sphere3DOverlay::render(RenderArgs* args) {
if (!_renderVisible) {
return; // do nothing if we're not visible

View file

@ -204,6 +204,68 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) {
}
}
/**jsdoc
* These are the properties of a <code>text3d</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.Text3DProperties
*
* @property {string} type=text3d - Has the value <code>"text3d"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*
* @property {boolean} isFacingAvatar - If <code>true</code>, the overlay is rotated to face the user's camera about an axis
* parallel to the user's avatar's "up" direction.
*
* @property {string} text="" - The text to display. Text does not automatically wrap; use <code>\n</code> for a line break.
* @property {number} textAlpha=1 - The text alpha value.
* @property {Color} backgroundColor=0,0,0 - The background color.
* @property {number} backgroundAlpha=0.7 - The background alpha value.
* @property {number} lineHeight=1 - The height of a line of text in meters.
* @property {number} leftMargin=0.1 - The left margin, in meters.
* @property {number} topMargin=0.1 - The top margin, in meters.
* @property {number} rightMargin=0.1 - The right margin, in meters.
* @property {number} bottomMargin=0.1 - The bottom margin, in meters.
*/
QVariant Text3DOverlay::getProperty(const QString& property) {
if (property == "text") {
return getText();

View file

@ -27,6 +27,33 @@
QString const TextOverlay::TYPE = "text";
QUrl const TextOverlay::URL(QString("hifi/overlays/TextOverlay.qml"));
// TextOverlay's properties are defined in the QML file specified above.
/**jsdoc
* These are the properties of a <code>text</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.TextProperties
*
* @property {Rect} bounds - The position and size of the rectangle, in pixels. <em>Write-only.</em>
* @property {number} x - Integer left, x-coordinate value = <code>bounds.x</code>. <em>Write-only.</em>
* @property {number} y - Integer top, y-coordinate value = <code>bounds.y</code>. <em>Write-only.</em>
* @property {number} width - Integer width of the rectangle = <code>bounds.width</code>. <em>Write-only.</em>
* @property {number} height - Integer height of the rectangle = <code>bounds.height</code>. <em>Write-only.</em>
*
* @property {number} margin=0 - Sets the <code>leftMargin</code> and <code>topMargin</code> values, in pixels.
* <em>Write-only.</em>
* @property {number} leftMargin=0 - The left margin's size, in pixels. <em>Write-only.</em>
* @property {number} topMargin=0 - The top margin's size, in pixels. <em>Write-only.</em>
* @property {string} text="" - The text to display. Text does not automatically wrap; use <code>\n</code> for a line break. Text
* is clipped to the <code>bounds</code>. <em>Write-only.</em>
* @property {number} font.size=18 - The size of the text, in pixels. <em>Write-only.</em>
* @property {number} lineHeight=18 - The height of a line of text, in pixels. <em>Write-only.</em>
* @property {Color} color=255,255,255 - The color of the text. Synonym: <code>textColor</code>. <em>Write-only.</em>
* @property {number} alpha=1.0 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>. <em>Write-only.</em>
* @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. <em>Write-only.</em>
* @property {number} backgroundAlpha=0.7 - The opacity of the background rectangle. <em>Write-only.</em>
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* <em>Write-only.</em>
*/
TextOverlay::TextOverlay() : QmlOverlay(URL) { }
TextOverlay::TextOverlay(const TextOverlay* textOverlay)

View file

@ -62,6 +62,11 @@ void Volume3DOverlay::setProperties(const QVariantMap& properties) {
}
}
// JSDoc for copying to @typedefs of overlay types that inherit Volume3DOverlay.
/**jsdoc
* @typedef
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*/
QVariant Volume3DOverlay::getProperty(const QString& property) {
if (property == "dimensions" || property == "scale" || property == "size") {
return vec3toVariant(getDimensions());

View file

@ -466,6 +466,67 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
}
}
// Web3DOverlay overrides the meaning of Planar3DOverlay's dimensions property.
/**jsdoc
* These are the properties of a <code>web3d</code> {@link Overlays.OverlayType|OverlayType}.
* @typedef {object} Overlays.Web3DProperties
*
* @property {string} type=web3d - Has the value <code>"web3d"</code>. <em>Read-only.</em>
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
* <code>pulseMin</code> to <code>pulseMax</code>, then <code>pulseMax</code> to <code>pulseMin</code> in one period.
* @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the
* current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0
* the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise
* used.)
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {string} anchor="" - If set to <code>"MyAvatar"</code> then the overlay is attached to your avatar, moving and
* rotating as you move your avatar.
*
* @property {string} name="" - A friendly name for the overlay.
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>,
* <code>filled</code>, and <code>filed</code>. Antonyms: <code>isWire</code> and <code>wire</code>.
* <strong>Deprecated:</strong> The erroneous property spelling "<code>filed</code>" is deprecated and support for it will
* be removed.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {boolean} isFacingAvatar - If <code>true</code>, the overlay is rotated to face the user's camera about an axis
* parallel to the user's avatar's "up" direction.
*
* @property {string} url - The URL of the Web page to display.
* @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page.
* @property {Vec2} resolution - <strong>Deprecated:</strong> This property has been removed.
* @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay.
* @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms:
* <code>scale</code>, <code>size</code>.
* @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second.
* @property {boolean} showKeyboardFocusHighlight=true - If <code>true</code>, the Web overlay is highlighted when it has
* keyboard focus.
* @property {string} inputMode=Touch - The user input mode to use - either <code>"Touch"</code> or <code>"Mouse"</code>.
*/
QVariant Web3DOverlay::getProperty(const QString& property) {
if (property == "url") {
return _url;

View file

@ -1641,9 +1641,17 @@ void Rig::initAnimGraph(const QUrl& url) {
// load the anim graph
_animLoader.reset(new AnimNodeLoader(url));
_animLoading = true;
connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) {
std::weak_ptr<AnimSkeleton> weakSkeletonPtr = _animSkeleton;
connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) {
_animNode = nodeIn;
_animNode->setSkeleton(_animSkeleton);
// abort load if the previous skeleton was deleted.
auto sharedSkeletonPtr = weakSkeletonPtr.lock();
if (!sharedSkeletonPtr) {
return;
}
_animNode->setSkeleton(sharedSkeletonPtr);
if (_userAnimState.clipNodeEnum != UserAnimState::None) {
// restore the user animation we had before reset.
@ -1651,6 +1659,7 @@ void Rig::initAnimGraph(const QUrl& url) {
_userAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f };
overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame);
}
// restore the role animations we had before reset.
for (auto& roleAnimState : _roleAnimStates) {
auto roleState = roleAnimState.second;

View file

@ -31,7 +31,7 @@ class AnimInverseKinematics;
// Rig instances are reentrant.
// However only specific methods thread-safe. Noted below.
class Rig : public QObject, public std::enable_shared_from_this<Rig> {
class Rig : public QObject {
Q_OBJECT
public:
struct StateHandler {

View file

@ -96,9 +96,13 @@ void SoundProcessor::run() {
QByteArray outputAudioByteArray;
int sampleRate = interpretAsWav(rawAudioByteArray, outputAudioByteArray);
if (sampleRate != 0) {
downSample(outputAudioByteArray, sampleRate);
if (sampleRate == 0) {
qCDebug(audio) << "Unsupported WAV file type";
emit onError(300, "Failed to load sound file, reason: unsupported WAV file type");
return;
}
downSample(outputAudioByteArray, sampleRate);
} else if (fileName.endsWith(RAW_EXTENSION)) {
// check if this was a stereo raw file
// since it's raw the only way for us to know that is if the file was called .stereo.raw

View file

@ -251,10 +251,10 @@ void EntityTreeRenderer::shutdown() {
}
void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction) {
PROFILE_RANGE_EX(simulation_physics, "Add", 0xffff00ff, (uint64_t)_entitiesToAdd.size());
PROFILE_RANGE_EX(simulation_physics, "AddToScene", 0xffff00ff, (uint64_t)_entitiesToAdd.size());
PerformanceTimer pt("add");
// Clear any expired entities
// FIXME should be able to use std::remove_if, but it fails due to some
// Clear any expired entities
// FIXME should be able to use std::remove_if, but it fails due to some
// weird compilation error related to EntityItemID assignment operators
for (auto itr = _entitiesToAdd.begin(); _entitiesToAdd.end() != itr; ) {
if (itr->second.expired()) {
@ -272,7 +272,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r
continue;
}
// Path to the parent transforms is not valid,
// Path to the parent transforms is not valid,
// don't add to the scene graph yet
if (!entity->isParentPathComplete()) {
continue;
@ -296,7 +296,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r
}
void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) {
PROFILE_RANGE_EX(simulation_physics, "Change", 0xffff00ff, (uint64_t)_changedEntities.size());
PROFILE_RANGE_EX(simulation_physics, "ChangeInScene", 0xffff00ff, (uint64_t)_changedEntities.size());
PerformanceTimer pt("change");
std::unordered_set<EntityItemID> changedEntities;
_changedEntitiesGuard.withWriteLock([&] {
@ -402,6 +402,8 @@ void EntityTreeRenderer::update(bool simulate) {
PerformanceTimer perfTimer("ETRupdate");
if (_tree && !_shuttingDown) {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
// here we update _currentFrame and _lastAnimated and sync with the server properties.
tree->update(simulate);
// Update the rendereable entities as needed
@ -736,7 +738,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
PickRay ray = _viewState->computePickRay(event->x(), event->y());
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
if (rayPickResult.intersects && rayPickResult.entity) {
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
// qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID,

View file

@ -63,13 +63,17 @@ bool ModelEntityWrapper::isModelLoaded() const {
EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity(new RenderableModelEntityItem(entityID, properties.getDimensionsInitialized()),
[](EntityItem* ptr) { ptr->deleteLater(); });
entity->setProperties(properties);
return entity;
}
RenderableModelEntityItem::RenderableModelEntityItem(const EntityItemID& entityItemID, bool dimensionsInitialized) :
ModelEntityWrapper(entityItemID),
_dimensionsInitialized(dimensionsInitialized) {
}
RenderableModelEntityItem::~RenderableModelEntityItem() { }
@ -464,7 +468,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
shapeInfo.setParams(type, dimensions, getCompoundShapeURL());
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
// TODO: assert we never fall in here when model not fully loaded
//assert(_model && _model->isLoaded());
// assert(_model && _model->isLoaded());
updateModelBounds();
model->updateGeometry();
@ -974,9 +978,6 @@ void ModelEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entit
entity->setModel({});
}
bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b) {
return !(a == b);
}
void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
if (!_animation || !_animation->isLoaded()) {
@ -991,19 +992,12 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
return;
}
if (!_lastAnimated) {
_lastAnimated = usecTimestampNow();
return;
}
auto now = usecTimestampNow();
auto interval = now - _lastAnimated;
_lastAnimated = now;
float deltaTime = (float)interval / (float)USECS_PER_SECOND;
_currentFrame += (deltaTime * _renderAnimationProperties.getFPS());
{
int animationCurrentFrame = (int)(glm::floor(_currentFrame)) % frameCount;
// the current frame is set on the server in update() in ModelEntityItem.cpp
int animationCurrentFrame = (int)(glm::floor(entity->getAnimationCurrentFrame()));
// in the case where the last frame is greater than the framecount then clamp
// it to the end of the animation until it loops around.
if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) {
animationCurrentFrame = 0;
}
@ -1039,10 +1033,10 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
glm::mat4 translationMat;
if (allowTranslation) {
if(index < translations.size()){
if (index < translations.size()) {
translationMat = glm::translate(translations[index]);
}
} else if (index < animationJointNames.size()){
} else if (index < animationJointNames.size()) {
QString jointName = fbxJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation
if (originalFbxIndices.contains(jointName)) {
@ -1317,25 +1311,17 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
if (model->getRenderItemsNeedUpdate()) {
model->updateRenderItems();
}
{
DETAILED_PROFILE_RANGE(simulation_physics, "CheckAnimation");
// make a copy of the animation properites
auto newAnimationProperties = entity->getAnimationProperties();
if (newAnimationProperties != _renderAnimationProperties) {
withWriteLock([&] {
_renderAnimationProperties = newAnimationProperties;
_currentFrame = _renderAnimationProperties.getCurrentFrame();
});
}
}
// The code to deal with the change of properties is now in ModelEntityItem.cpp
// That is where _currentFrame and _lastAnimated were updated.
if (_animating) {
DETAILED_PROFILE_RANGE(simulation_physics, "Animate");
if (!jointsMapped()) {
mapJoints(entity, model->getJointNames());
}
animate(entity);
if (!(entity->getAnimationFirstFrame() < 0) && !(entity->getAnimationFirstFrame() > entity->getAnimationLastFrame())) {
animate(entity);
}
emit requestRenderUpdate();
}
}

View file

@ -33,7 +33,7 @@ namespace render { namespace entities {
class ModelEntityRenderer;
} }
//#define MODEL_ENTITY_USE_FADE_EFFECT
// #define MODEL_ENTITY_USE_FADE_EFFECT
class ModelEntityWrapper : public ModelEntityItem {
using Parent = ModelEntityItem;
friend class render::entities::ModelEntityRenderer;
@ -133,7 +133,7 @@ class ModelEntityRenderer : public TypedEntityRenderer<RenderableModelEntityItem
friend class EntityRenderer;
public:
ModelEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { }
ModelEntityRenderer(const EntityItemPointer& entity) : Parent(entity) {}
protected:
virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override;
@ -184,8 +184,6 @@ private:
bool _shouldHighlight { false };
bool _animating { false };
uint64_t _lastAnimated { 0 };
float _currentFrame { 0 };
};
} } // namespace

View file

@ -27,6 +27,8 @@
// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1
// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down.
static const float SPHERE_ENTITY_SCALE = 0.5f;
static const unsigned int SUN_SHADOW_CASCADE_COUNT{ 4 };
static const float SUN_SHADOW_MAX_DISTANCE{ 40.0f };
using namespace render;
using namespace render::entities;
@ -116,7 +118,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
// Do we need to allocate the light in the stage ?
if (LightStage::isIndexInvalid(_sunIndex)) {
_sunIndex = _stage->addLight(_sunLight);
_shadowIndex = _stage->addShadow(_sunIndex);
_shadowIndex = _stage->addShadow(_sunIndex, SUN_SHADOW_MAX_DISTANCE, SUN_SHADOW_CASCADE_COUNT);
} else {
_stage->updateLightArrayBuffer(_sunIndex);
}

View file

@ -22,15 +22,28 @@ const float AnimationPropertyGroup::MAXIMUM_POSSIBLE_FRAME = 100000.0f;
bool operator==(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b) {
return
(a._url == b._url) &&
(a._currentFrame == b._currentFrame) &&
(a._running == b._running) &&
(a._loop == b._loop) &&
(a._hold == b._hold) &&
(a._firstFrame == b._firstFrame) &&
(a._lastFrame == b._lastFrame) &&
(a._hold == b._hold);
(a._url == b._url);
}
bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b) {
return
(a._currentFrame != b._currentFrame) ||
(a._running != b._running) ||
(a._loop != b._loop) ||
(a._hold != b._hold) ||
(a._firstFrame != b._firstFrame) ||
(a._lastFrame != b._lastFrame) ||
(a._url != b._url);
}
void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const {
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_URL, Animation, animation, URL, url);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation);
@ -130,6 +143,7 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) {
allowTranslation = settingsMap["allowTranslation"].toBool();
}
setAllowTranslation(allowTranslation);
setFPS(fps);
setCurrentFrame(currentFrame);

View file

@ -89,6 +89,7 @@ public:
protected:
friend bool operator==(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b);
friend bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b);
void setFromOldAnimationSettings(const QString& value);
};

View file

@ -26,6 +26,7 @@
#include <GLMHelpers.h>
#include <Octree.h>
#include <PhysicsHelpers.h>
#include <Profile.h>
#include <RegisteredMetaTypes.h>
#include <SharedUtil.h> // usecTimestampNow()
#include <LogHandler.h>
@ -984,6 +985,7 @@ void EntityItem::setCollisionSoundURL(const QString& value) {
}
void EntityItem::simulate(const quint64& now) {
DETAILED_PROFILE_RANGE(simulation_physics, "Simulate");
if (getLastSimulated() == 0) {
setLastSimulated(now);
}
@ -1039,6 +1041,7 @@ void EntityItem::simulate(const quint64& now) {
}
bool EntityItem::stepKinematicMotion(float timeElapsed) {
DETAILED_PROFILE_RANGE(simulation_physics, "StepKinematicMotion");
// get all the data
Transform transform;
glm::vec3 linearVelocity;
@ -2840,7 +2843,7 @@ void EntityItem::retrieveMarketplacePublicKey() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath("/api/v1/commerce/marketplace_key");
QJsonObject request;
networkRequest.setUrl(requestURL);

View file

@ -17,7 +17,6 @@
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/ecdsa.h>
#include <NetworkingConstants.h>
#include <NetworkAccessManager.h>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>

View file

@ -29,7 +29,6 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) {
}
void EntitySimulation::updateEntities() {
PROFILE_RANGE(simulation_physics, "ES::updateEntities");
QMutexLocker lock(&_mutex);
quint64 now = usecTimestampNow();
@ -38,12 +37,7 @@ void EntitySimulation::updateEntities() {
callUpdateOnEntitiesThatNeedIt(now);
moveSimpleKinematics(now);
updateEntitiesInternal(now);
{
PROFILE_RANGE(simulation_physics, "Sort");
PerformanceTimer perfTimer("sortingEntities");
sortEntitiesThatMoved();
}
sortEntitiesThatMoved();
}
void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
@ -101,6 +95,7 @@ void EntitySimulation::changeEntityInternal(EntityItemPointer entity) {
// protected
void EntitySimulation::expireMortalEntities(const quint64& now) {
if (now > _nextExpiry) {
PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size());
// only search for expired entities if we expect to find one
_nextExpiry = quint64(-1);
QMutexLocker lock(&_mutex);
@ -146,6 +141,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
// protected
void EntitySimulation::sortEntitiesThatMoved() {
PROFILE_RANGE_EX(simulation_physics, "SortTree", 0xffff00ff, (uint64_t)_entitiesToSort.size());
// NOTE: this is only for entities that have been moved by THIS EntitySimulation.
// External changes to entity position/shape are expected to be sorted outside of the EntitySimulation.
MovingEntitiesOperator moveOperator;
@ -265,7 +261,7 @@ void EntitySimulation::clearEntities() {
}
void EntitySimulation::moveSimpleKinematics(const quint64& now) {
PROFILE_RANGE_EX(simulation_physics, "Kinematics", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size());
PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size());
SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin();
while (itemItr != _simpleKinematicEntities.end()) {
EntityItemPointer entity = *itemItr;

View file

@ -1308,7 +1308,7 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer");
QJsonObject request;
request["certificate_id"] = certID;
@ -1770,24 +1770,26 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) {
}
void EntityTree::update(bool simulate) {
PROFILE_RANGE(simulation_physics, "ET::update");
PROFILE_RANGE(simulation_physics, "UpdateTree");
fixupNeedsParentFixups();
if (simulate && _simulation) {
withWriteLock([&] {
_simulation->updateEntities();
VectorOfEntities pendingDeletes;
_simulation->takeEntitiesToDelete(pendingDeletes);
{
PROFILE_RANGE(simulation_physics, "Deletes");
VectorOfEntities pendingDeletes;
_simulation->takeEntitiesToDelete(pendingDeletes);
if (pendingDeletes.size() > 0) {
// translate into list of ID's
QSet<EntityItemID> idsToDelete;
if (pendingDeletes.size() > 0) {
// translate into list of ID's
QSet<EntityItemID> idsToDelete;
for (auto entity : pendingDeletes) {
idsToDelete.insert(entity->getEntityItemID());
}
for (auto entity : pendingDeletes) {
idsToDelete.insert(entity->getEntityItemID());
// delete these things the roundabout way
deleteEntities(idsToDelete, true);
}
// delete these things the roundabout way
deleteEntities(idsToDelete, true);
}
});
}

View file

@ -33,6 +33,8 @@ EntityItemPointer ModelEntityItem::factory(const EntityItemID& entityID, const E
ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID)
{
_lastAnimated = usecTimestampNow();
// set the last animated when interface (re)starts
_type = EntityTypes::Model;
_lastKnownCurrentFrame = -1;
_color[0] = _color[1] = _color[2] = 0;
@ -186,6 +188,105 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
}
// added update function back for property fix
void ModelEntityItem::update(const quint64& now) {
{
auto currentAnimationProperties = this->getAnimationProperties();
if (_previousAnimationProperties != currentAnimationProperties) {
withWriteLock([&] {
// if we hit start animation or change the first or last frame then restart the animation
if ((currentAnimationProperties.getFirstFrame() != _previousAnimationProperties.getFirstFrame()) ||
(currentAnimationProperties.getLastFrame() != _previousAnimationProperties.getLastFrame()) ||
(currentAnimationProperties.getRunning() && !_previousAnimationProperties.getRunning())) {
// when we start interface and the property is are set then the current frame is initialized to -1
if (_currentFrame < 0) {
// don't reset _lastAnimated here because we need the timestamp from the ModelEntityItem constructor for when the properties were set
_currentFrame = currentAnimationProperties.getCurrentFrame();
setAnimationCurrentFrame(_currentFrame);
} else {
_lastAnimated = usecTimestampNow();
_currentFrame = currentAnimationProperties.getFirstFrame();
setAnimationCurrentFrame(currentAnimationProperties.getFirstFrame());
}
} else if (!currentAnimationProperties.getRunning() && _previousAnimationProperties.getRunning()) {
_currentFrame = currentAnimationProperties.getFirstFrame();
setAnimationCurrentFrame(_currentFrame);
} else if (currentAnimationProperties.getCurrentFrame() != _previousAnimationProperties.getCurrentFrame()) {
// don't reset _lastAnimated here because the currentFrame was set with the previous setting of _lastAnimated
_currentFrame = currentAnimationProperties.getCurrentFrame();
}
});
_previousAnimationProperties = this->getAnimationProperties();
}
if (isAnimatingSomething()) {
if (!(getAnimationFirstFrame() < 0) && !(getAnimationFirstFrame() > getAnimationLastFrame())) {
updateFrameCount();
}
}
}
EntityItem::update(now);
}
bool ModelEntityItem::needsToCallUpdate() const {
return true;
}
void ModelEntityItem::updateFrameCount() {
if (_currentFrame < 0.0f) {
return;
}
if (!_lastAnimated) {
_lastAnimated = usecTimestampNow();
return;
}
auto now = usecTimestampNow();
// update the interval since the last animation.
auto interval = now - _lastAnimated;
_lastAnimated = now;
// if fps is negative then increment timestamp and return.
if (getAnimationFPS() < 0.0f) {
return;
}
int updatedFrameCount = getAnimationLastFrame() - getAnimationFirstFrame() + 1;
if (!getAnimationHold() && getAnimationIsPlaying()) {
float deltaTime = (float)interval / (float)USECS_PER_SECOND;
_currentFrame += (deltaTime * getAnimationFPS());
if (_currentFrame > getAnimationLastFrame()) {
if (getAnimationLoop()) {
_currentFrame = getAnimationFirstFrame() + (int)(glm::floor(_currentFrame - getAnimationFirstFrame())) % (updatedFrameCount - 1);
} else {
_currentFrame = getAnimationLastFrame();
}
} else if (_currentFrame < getAnimationFirstFrame()) {
if (getAnimationFirstFrame() < 0) {
_currentFrame = 0;
} else {
_currentFrame = getAnimationFirstFrame();
}
}
// qCDebug(entities) << "in update frame " << _currentFrame;
setAnimationCurrentFrame(_currentFrame);
}
}
void ModelEntityItem::debugDump() const {
qCDebug(entities) << "ModelEntityItem id:" << getEntityItemID();
qCDebug(entities) << " edited ago:" << getEditedAgo();
@ -538,6 +639,13 @@ void ModelEntityItem::setAnimationLoop(bool loop) {
});
}
bool ModelEntityItem::getAnimationLoop() const {
return resultWithReadLock<bool>([&] {
return _animationProperties.getLoop();
});
}
void ModelEntityItem::setAnimationHold(bool hold) {
withWriteLock([&] {
_animationProperties.setHold(hold);
@ -573,8 +681,9 @@ float ModelEntityItem::getAnimationLastFrame() const {
return _animationProperties.getLastFrame();
});
}
bool ModelEntityItem::getAnimationIsPlaying() const {
return resultWithReadLock<float>([&] {
return resultWithReadLock<bool>([&] {
return _animationProperties.getRunning();
});
}
@ -585,8 +694,15 @@ float ModelEntityItem::getAnimationCurrentFrame() const {
});
}
bool ModelEntityItem::isAnimatingSomething() const {
float ModelEntityItem::getAnimationFPS() const {
return resultWithReadLock<float>([&] {
return _animationProperties.getFPS();
});
}
bool ModelEntityItem::isAnimatingSomething() const {
return resultWithReadLock<bool>([&] {
return !_animationProperties.getURL().isEmpty() &&
_animationProperties.getRunning() &&
(_animationProperties.getFPS() != 0.0f);

View file

@ -17,6 +17,7 @@
#include <ThreadSafeValueCache.h>
#include "AnimationPropertyGroup.h"
class ModelEntityItem : public EntityItem {
public:
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
@ -46,8 +47,11 @@ public:
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override;
//virtual void update(const quint64& now) override;
//virtual bool needsToCallUpdate() const override;
// update() and needstocallupdate() added back for the entity property fix
virtual void update(const quint64& now) override;
virtual bool needsToCallUpdate() const override;
void updateFrameCount();
virtual void debugDump() const override;
void setShapeType(ShapeType type) override;
@ -90,6 +94,7 @@ public:
bool getAnimationAllowTranslation() const { return _animationProperties.getAllowTranslation(); };
void setAnimationLoop(bool loop);
bool getAnimationLoop() const;
void setAnimationHold(bool hold);
bool getAnimationHold() const;
@ -102,6 +107,7 @@ public:
bool getAnimationIsPlaying() const;
float getAnimationCurrentFrame() const;
float getAnimationFPS() const;
bool isAnimatingSomething() const;
static const QString DEFAULT_TEXTURES;
@ -147,7 +153,7 @@ protected:
};
QVector<ModelJointData> _localJointData;
int _lastKnownCurrentFrame;
int _lastKnownCurrentFrame{-1};
rgbColor _color;
QString _modelURL;
@ -160,6 +166,11 @@ protected:
QString _textures;
ShapeType _shapeType = SHAPE_TYPE_NONE;
private:
uint64_t _lastAnimated{ 0 };
AnimationPropertyGroup _previousAnimationProperties;
float _currentFrame{ -1.0f };
};
#endif // hifi_ModelEntityItem_h

View file

@ -56,7 +56,7 @@ float OBJTokenizer::getFloat() {
return std::stof((nextToken() != OBJTokenizer::DATUM_TOKEN) ? nullptr : getDatum().data());
}
int OBJTokenizer::nextToken() {
int OBJTokenizer::nextToken(bool allowSpaceChar /*= false*/) {
if (_pushedBackToken != NO_PUSHBACKED_TOKEN) {
int token = _pushedBackToken;
_pushedBackToken = NO_PUSHBACKED_TOKEN;
@ -93,7 +93,7 @@ int OBJTokenizer::nextToken() {
_datum = "";
_datum.append(ch);
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace() || ch == '\"') {
if ((QChar(ch).isSpace() || ch == '\"') && (!allowSpaceChar || ch != ' ')) {
ungetChar(ch); // read until we encounter a special character, then replace it
break;
}
@ -399,7 +399,7 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi
currentMaterialName = QString("part-") + QString::number(_partCounter++);
}
} else if (token == "mtllib" && !_url.isEmpty()) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
if (tokenizer.nextToken(true) != OBJTokenizer::DATUM_TOKEN) {
break;
}
QByteArray libraryName = tokenizer.getDatum();

View file

@ -11,7 +11,7 @@ public:
DATUM_TOKEN = 0x100,
COMMENT_TOKEN = 0x101
};
int nextToken();
int nextToken(bool allowSpaceChar = false);
const QByteArray& getDatum() const { return _datum; }
bool isNextTokenFloat();
const QByteArray getLineAsDatum(); // some "filenames" have spaces in them

View file

@ -40,10 +40,6 @@ static bool disableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(
static GLBackend* INSTANCE{ nullptr };
BackendPointer GLBackend::createBackend() {
// The ATI memory info extension only exposes 'free memory' so we want to force it to
// cache the value as early as possible
getDedicatedMemory();
// FIXME provide a mechanism to override the backend for testing
// Where the gpuContext is initialized and where the TRUE Backend is created and assigned
auto version = QOpenGLContextWrapper::currentContextVersion();

View file

@ -318,7 +318,10 @@ int GLBackend::makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slot
if (requestedBinding != slotBindings.end()) {
if (binding != (*requestedBinding)._location) {
binding = (*requestedBinding)._location;
glProgramUniform1i(glprogram, location, binding);
for (auto i = 0; i < size; i++) {
// If we are working with an array of textures, reserve for each elemet
glProgramUniform1i(glprogram, location+i, binding+i);
}
}
}

View file

@ -88,53 +88,6 @@ gpu::Size getFreeDedicatedMemory() {
return result;
}
gpu::Size getDedicatedMemory() {
static Size dedicatedMemory { 0 };
static std::once_flag once;
std::call_once(once, [&] {
#ifdef Q_OS_WIN
if (!dedicatedMemory && wglGetGPUIDsAMD && wglGetGPUInfoAMD) {
UINT maxCount = wglGetGPUIDsAMD(0, 0);
std::vector<UINT> ids;
ids.resize(maxCount);
wglGetGPUIDsAMD(maxCount, &ids[0]);
GLuint memTotal;
wglGetGPUInfoAMD(ids[0], WGL_GPU_RAM_AMD, GL_UNSIGNED_INT, sizeof(GLuint), &memTotal);
dedicatedMemory = MB_TO_BYTES(memTotal);
}
#endif
if (!dedicatedMemory) {
GLint atiGpuMemory[4];
// not really total memory, but close enough if called early enough in the application lifecycle
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, atiGpuMemory);
if (GL_NO_ERROR == glGetError()) {
dedicatedMemory = KB_TO_BYTES(atiGpuMemory[0]);
}
}
if (!dedicatedMemory) {
GLint nvGpuMemory { 0 };
glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nvGpuMemory);
if (GL_NO_ERROR == glGetError()) {
dedicatedMemory = KB_TO_BYTES(nvGpuMemory);
}
}
if (!dedicatedMemory) {
auto gpuIdent = GPUIdent::getInstance();
if (gpuIdent && gpuIdent->isValid()) {
dedicatedMemory = MB_TO_BYTES(gpuIdent->getMemory());
}
}
});
return dedicatedMemory;
}
ComparisonFunction comparisonFuncFromGL(GLenum func) {
if (func == GL_NEVER) {
return NEVER;

View file

@ -28,7 +28,6 @@ void serverWait();
// Create a fence and synchronously wait on the fence
void clientWait();
gpu::Size getDedicatedMemory();
gpu::Size getFreeDedicatedMemory();
ComparisonFunction comparisonFuncFromGL(GLenum func);
State::StencilOp stencilOpFromGL(GLenum stencilOp);

View file

@ -97,7 +97,7 @@ public:
void setTemporaryDomain(const QUuid& domainID, const QString& key);
const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); }
QUrl getMetaverseServerURL() { return NetworkingConstants::METAVERSE_SERVER_URL; }
QUrl getMetaverseServerURL() { return NetworkingConstants::METAVERSE_SERVER_URL(); }
public slots:
void requestAccessToken(const QString& login, const QString& password);

View file

@ -12,15 +12,31 @@
#ifndef hifi_NetworkingConstants_h
#define hifi_NetworkingConstants_h
#include <QtCore/QProcessEnvironment>
#include <QtCore/QUrl>
namespace NetworkingConstants {
// If you want to use STAGING instead of STABLE,
// don't forget to ALSO change the Domain Server Metaverse Server URL inside of:
// links from the Domain Server web interface (like the connect account token generation)
// will still point at stable unless you ALSO change the Domain Server Metaverse Server URL inside of:
// <hifi repo>\domain-server\resources\web\js\shared.js
// You can avoid changing that and still effectively use a connected domain on staging
// if you manually generate a personal access token for the domains scope
// at https://staging.highfidelity.com/user/tokens/new?for_domain_server=true
const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com");
const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com");
const QUrl METAVERSE_SERVER_URL = METAVERSE_SERVER_URL_STABLE;
// You can change the return of this function if you want to use a custom metaverse URL at compile time
// or you can pass a custom URL via the env variable
static const QUrl METAVERSE_SERVER_URL() {
static const QString HIFI_METAVERSE_URL_ENV = "HIFI_METAVERSE_URL";
static const QUrl serverURL = QProcessEnvironment::systemEnvironment().contains(HIFI_METAVERSE_URL_ENV)
? QUrl(QProcessEnvironment::systemEnvironment().value(HIFI_METAVERSE_URL_ENV))
: METAVERSE_SERVER_URL_STABLE;
return serverURL;
};
}
#endif // hifi_NetworkingConstants_h

View file

@ -35,7 +35,7 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->hasValidAccessToken()
&& req.url().host() == NetworkingConstants::METAVERSE_SERVER_URL.host()) {
&& req.url().host() == NetworkingConstants::METAVERSE_SERVER_URL().host()) {
QNetworkRequest authenticatedRequest(req);
authenticatedRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);

View file

@ -14,8 +14,9 @@
#include <EntityItem.h>
#include <EntityItemProperties.h>
#include <EntityEditPacketSender.h>
#include <PhysicsCollisionGroups.h>
#include <LogHandler.h>
#include <PhysicsCollisionGroups.h>
#include <Profile.h>
#include "BulletUtil.h"
#include "EntityMotionState.h"
@ -325,6 +326,7 @@ bool EntityMotionState::isCandidateForOwnership() const {
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");
// NOTE: we only get here if we think we own the simulation
assert(_body);
@ -476,6 +478,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
}
bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend");
// NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called
// after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL.
assert(_entity);
@ -516,6 +519,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
}
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Send");
assert(_entity);
assert(entityTreeIsLocked());
@ -731,6 +735,7 @@ void EntityMotionState::resetMeasuredBodyAcceleration() {
}
void EntityMotionState::measureBodyAcceleration() {
DETAILED_PROFILE_RANGE(simulation_physics, "MeasureAccel");
// try to manually measure the true acceleration of the object
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
uint32_t numSubsteps = thisStep - _lastMeasureStep;

View file

@ -10,12 +10,14 @@
//
#include "PhysicalEntitySimulation.h"
#include <Profile.h>
#include "PhysicsHelpers.h"
#include "PhysicsLogging.h"
#include "ShapeManager.h"
#include "PhysicalEntitySimulation.h"
PhysicalEntitySimulation::PhysicalEntitySimulation() {
}
@ -274,20 +276,24 @@ void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotio
}
void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionStates& motionStates) {
PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size());
QMutexLocker lock(&_mutex);
// walk the motionStates looking for those that correspond to entities
for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr);
assert(state);
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
EntityItemPointer entity = entityState->getEntity();
assert(entity.get());
if (entityState->isCandidateForOwnership()) {
_outgoingChanges.insert(entityState);
{
PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size());
for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr);
assert(state);
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
EntityItemPointer entity = entityState->getEntity();
assert(entity.get());
if (entityState->isCandidateForOwnership()) {
_outgoingChanges.insert(entityState);
}
_entitiesToSort.insert(entity);
}
_entitiesToSort.insert(entity);
}
}
@ -302,6 +308,7 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta
}
// look for entities to prune or update
PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size());
QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin();
while (stateItr != _outgoingChanges.end()) {
EntityMotionState* state = *stateItr;

View file

@ -11,7 +11,12 @@
#include <PhysicsCollisionGroups.h>
#include <functional>
#include <QFile>
#include <PerfStat.h>
#include <Profile.h>
#include "CharacterController.h"
#include "ObjectMotionState.h"
@ -290,6 +295,7 @@ void PhysicsEngine::stepSimulation() {
float timeStep = btMin(dt, MAX_TIMESTEP);
if (_myAvatarController) {
DETAILED_PROFILE_RANGE(simulation_physics, "avatarController");
BT_PROFILE("avatarController");
// TODO: move this stuff outside and in front of stepSimulation, because
// the updateShapeIfNecessary() call needs info from MyAvatar and should
@ -328,45 +334,107 @@ void PhysicsEngine::stepSimulation() {
}
}
class CProfileOperator {
public:
CProfileOperator() {}
void recurse(CProfileIterator* itr, QString context) {
// The context string will get too long if we accumulate it properly
//QString newContext = context + QString("/") + itr->Get_Current_Parent_Name();
// so we use this four-character indentation
QString newContext = context + QString(".../");
process(itr, newContext);
// count the children
int32_t numChildren = 0;
itr->First();
while (!itr->Is_Done()) {
itr->Next();
++numChildren;
}
// recurse the children
if (numChildren > 0) {
// recurse the children
for (int32_t i = 0; i < numChildren; ++i) {
itr->Enter_Child(i);
recurse(itr, newContext);
}
}
// retreat back to parent
itr->Enter_Parent();
}
virtual void process(CProfileIterator*, QString context) = 0;
};
class StatsHarvester : public CProfileOperator {
public:
StatsHarvester() {}
void process(CProfileIterator* itr, QString context) override {
QString name = context + itr->Get_Current_Parent_Name();
uint64_t time = (uint64_t)((btScalar)MSECS_PER_SECOND * itr->Get_Current_Parent_Total_Time());
PerformanceTimer::addTimerRecord(name, time);
};
};
class StatsWriter : public CProfileOperator {
public:
StatsWriter(QString filename) : _file(filename) {
_file.open(QFile::WriteOnly);
if (_file.error() != QFileDevice::NoError) {
qCDebug(physics) << "unable to open file " << _file.fileName() << " to save stepSimulation() stats";
}
}
~StatsWriter() {
_file.close();
}
void process(CProfileIterator* itr, QString context) override {
QString name = context + itr->Get_Current_Parent_Name();
float time = (btScalar)MSECS_PER_SECOND * itr->Get_Current_Parent_Total_Time();
if (_file.error() == QFileDevice::NoError) {
QTextStream s(&_file);
s << name << " = " << time << "\n";
}
}
protected:
QFile _file;
};
void PhysicsEngine::harvestPerformanceStats() {
// unfortunately the full context names get too long for our stats presentation format
//QString contextName = PerformanceTimer::getContextName(); // TODO: how to show full context name?
QString contextName("...");
CProfileIterator* profileIterator = CProfileManager::Get_Iterator();
if (profileIterator) {
CProfileIterator* itr = CProfileManager::Get_Iterator();
if (itr) {
// hunt for stepSimulation context
profileIterator->First();
for (int32_t childIndex = 0; !profileIterator->Is_Done(); ++childIndex) {
if (QString(profileIterator->Get_Current_Name()) == "stepSimulation") {
profileIterator->Enter_Child(childIndex);
recursivelyHarvestPerformanceStats(profileIterator, contextName);
itr->First();
for (int32_t childIndex = 0; !itr->Is_Done(); ++childIndex) {
if (QString(itr->Get_Current_Name()) == "stepSimulation") {
itr->Enter_Child(childIndex);
StatsHarvester harvester;
harvester.recurse(itr, "step/");
break;
}
profileIterator->Next();
itr->Next();
}
}
}
void PhysicsEngine::recursivelyHarvestPerformanceStats(CProfileIterator* profileIterator, QString contextName) {
QString parentContextName = contextName + QString("/") + QString(profileIterator->Get_Current_Parent_Name());
// get the stats for the children
int32_t numChildren = 0;
profileIterator->First();
while (!profileIterator->Is_Done()) {
QString childContextName = parentContextName + QString("/") + QString(profileIterator->Get_Current_Name());
uint64_t time = (uint64_t)((btScalar)MSECS_PER_SECOND * profileIterator->Get_Current_Total_Time());
PerformanceTimer::addTimerRecord(childContextName, time);
profileIterator->Next();
++numChildren;
void PhysicsEngine::printPerformanceStatsToFile(const QString& filename) {
CProfileIterator* itr = CProfileManager::Get_Iterator();
if (itr) {
// hunt for stepSimulation context
itr->First();
for (int32_t childIndex = 0; !itr->Is_Done(); ++childIndex) {
if (QString(itr->Get_Current_Name()) == "stepSimulation") {
itr->Enter_Child(childIndex);
StatsWriter writer(filename);
writer.recurse(itr, "");
break;
}
itr->Next();
}
}
// recurse the children
for (int32_t i = 0; i < numChildren; ++i) {
profileIterator->Enter_Child(i);
recursivelyHarvestPerformanceStats(profileIterator, contextName);
}
// retreat back to parent
profileIterator->Enter_Parent();
}
void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) {
@ -399,6 +467,7 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const
}
void PhysicsEngine::updateContactMap() {
DETAILED_PROFILE_RANGE(simulation_physics, "updateContactMap");
BT_PROFILE("updateContactMap");
++_numContactFrames;
@ -515,10 +584,21 @@ const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() {
void PhysicsEngine::dumpStatsIfNecessary() {
if (_dumpNextStats) {
_dumpNextStats = false;
CProfileManager::Increment_Frame_Counter();
if (_saveNextStats) {
_saveNextStats = false;
printPerformanceStatsToFile(_statsFilename);
}
CProfileManager::dumpAll();
}
}
void PhysicsEngine::saveNextPhysicsStats(QString filename) {
_saveNextStats = true;
_dumpNextStats = true;
_statsFilename = filename;
}
// Bullet collision flags are as follows:
// CF_STATIC_OBJECT= 1,
// CF_KINEMATIC_OBJECT= 2,

View file

@ -62,6 +62,7 @@ public:
void stepSimulation();
void harvestPerformanceStats();
void printPerformanceStatsToFile(const QString& filename);
void updateContactMap();
bool hasOutgoingChanges() const { return _hasOutgoingChanges; }
@ -76,6 +77,9 @@ public:
/// \brief prints timings for last frame if stats have been requested.
void dumpStatsIfNecessary();
/// \brief saves timings for last frame in filename
void saveNextPhysicsStats(QString filename);
/// \param offset position of simulation origin in domain-frame
void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; }
@ -94,7 +98,6 @@ public:
private:
QList<EntityDynamicPointer> removeDynamicsForBody(btRigidBody* body);
void addObjectToDynamicsWorld(ObjectMotionState* motionState);
void recursivelyHarvestPerformanceStats(CProfileIterator* profileIterator, QString contextName);
/// \brief bump any objects that touch this one, then remove contact info
void bumpAndPruneContacts(ObjectMotionState* motionState);
@ -116,6 +119,7 @@ private:
QHash<QUuid, EntityDynamicPointer> _objectDynamics;
QHash<btRigidBody*, QSet<QUuid>> _objectDynamicsByBody;
std::set<btRigidBody*> _activeStaticBodies;
QString _statsFilename;
glm::vec3 _originOffset;
@ -124,8 +128,9 @@ private:
uint32_t _numContactFrames = 0;
uint32_t _numSubsteps;
bool _dumpNextStats = false;
bool _hasOutgoingChanges = false;
bool _dumpNextStats { false };
bool _saveNextStats { false };
bool _hasOutgoingChanges { false };
};

View file

@ -18,6 +18,7 @@
#include <LinearMath/btQuickprof.h>
#include "ThreadSafeDynamicsWorld.h"
#include "Profile.h"
ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld(
btDispatcher* dispatcher,
@ -29,6 +30,7 @@ ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld(
int ThreadSafeDynamicsWorld::stepSimulationWithSubstepCallback(btScalar timeStep, int maxSubSteps,
btScalar fixedTimeStep, SubStepCallback onSubStep) {
DETAILED_PROFILE_RANGE(simulation_physics, "stepWithCB");
BT_PROFILE("stepSimulationWithSubstepCallback");
int subSteps = 0;
if (maxSubSteps) {
@ -68,11 +70,13 @@ int ThreadSafeDynamicsWorld::stepSimulationWithSubstepCallback(btScalar timeStep
saveKinematicState(fixedTimeStep*clampedSimulationSteps);
{
DETAILED_PROFILE_RANGE(simulation_physics, "applyGravity");
BT_PROFILE("applyGravity");
applyGravity();
}
for (int i=0;i<clampedSimulationSteps;i++) {
DETAILED_PROFILE_RANGE(simulation_physics, "substep");
internalSingleStepSimulation(fixedTimeStep);
onSubStep();
}
@ -118,7 +122,8 @@ void ThreadSafeDynamicsWorld::synchronizeMotionState(btRigidBody* body) {
}
void ThreadSafeDynamicsWorld::synchronizeMotionStates() {
BT_PROFILE("synchronizeMotionStates");
PROFILE_RANGE(simulation_physics, "SyncMotionStates");
BT_PROFILE("syncMotionStates");
_changedMotionStates.clear();
// NOTE: m_synchronizeAllMotionStates is 'false' by default for optimization.
@ -161,6 +166,7 @@ void ThreadSafeDynamicsWorld::saveKinematicState(btScalar timeStep) {
///would like to iterate over m_nonStaticRigidBodies, but unfortunately old API allows
///to switch status _after_ adding kinematic objects to the world
///fix it for Bullet 3.x release
DETAILED_PROFILE_RANGE(simulation_physics, "saveKinematicState");
BT_PROFILE("saveKinematicState");
for (int i=0;i<m_collisionObjects.size();i++)
{

View file

@ -39,7 +39,7 @@ void DebugDeferredBufferConfig::setMode(int newMode) {
emit dirty();
}
enum Slot {
enum TextureSlot {
Albedo = 0,
Normal,
Specular,
@ -56,7 +56,11 @@ enum Slot {
AmbientOcclusionBlurred
};
enum ParamSlot {
CameraCorrection = 0,
DeferredFrameTransform,
ShadowTransform
};
static const std::string DEFAULT_ALBEDO_SHADER {
"vec4 getFragmentColor() {"
@ -127,12 +131,14 @@ static const std::string DEFAULT_DEPTH_SHADER {
" return vec4(vec3(texture(depthMap, uv).x), 1.0);"
" }"
};
static const std::string DEFAULT_LIGHTING_SHADER {
"vec4 getFragmentColor() {"
" return vec4(pow(texture(lightingMap, uv).xyz, vec3(1.0 / 2.2)), 1.0);"
" }"
};
static const std::string DEFAULT_SHADOW_SHADER {
static const std::string DEFAULT_SHADOW_SHADER{
"uniform sampler2DShadow shadowMap;"
"vec4 getFragmentColor() {"
" for (int i = 255; i >= 0; --i) {"
@ -145,10 +151,31 @@ static const std::string DEFAULT_SHADOW_SHADER {
" }"
};
static const std::string DEFAULT_SHADOW_CASCADE_SHADER{
"vec3 cascadeColors[4] = vec3[4]( vec3(0,1,0), vec3(0,0,1), vec3(1,0,0), vec3(1) );"
"vec4 getFragmentColor() {"
" DeferredFrameTransform deferredTransform = getDeferredFrameTransform();"
" DeferredFragment frag = unpackDeferredFragment(deferredTransform, uv);"
" vec4 viewPosition = vec4(frag.position.xyz, 1.0);"
" float viewDepth = -viewPosition.z;"
" vec4 worldPosition = getViewInverse() * viewPosition;"
" vec4 cascadeShadowCoords[2];"
" ivec2 cascadeIndices;"
" float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);"
" vec3 firstCascadeColor = cascadeColors[cascadeIndices.x];"
" vec3 secondCascadeColor = cascadeColors[cascadeIndices.x];"
" if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) {"
" secondCascadeColor = cascadeColors[cascadeIndices.y];"
" }"
" vec3 color = mix(firstCascadeColor, secondCascadeColor, cascadeMix);"
" return vec4(mix(vec3(0.0), color, evalShadowFalloff(viewDepth)), 1.0);"
"}"
};
static const std::string DEFAULT_LINEAR_DEPTH_SHADER {
"vec4 getFragmentColor() {"
" return vec4(vec3(1.0 - texture(linearDepthMap, uv).x * 0.01), 1.0);"
" }"
"}"
};
static const std::string DEFAULT_HALF_LINEAR_DEPTH_SHADER{
@ -285,8 +312,13 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, std::string cust
return DEFAULT_SCATTERING_SHADER;
case LightingMode:
return DEFAULT_LIGHTING_SHADER;
case ShadowMode:
case ShadowCascade0Mode:
case ShadowCascade1Mode:
case ShadowCascade2Mode:
case ShadowCascade3Mode:
return DEFAULT_SHADOW_SHADER;
case ShadowCascadeIndicesMode:
return DEFAULT_SHADOW_CASCADE_SHADER;
case LinearDepthMode:
return DEFAULT_LINEAR_DEPTH_SHADER;
case HalfLinearDepthMode:
@ -353,6 +385,10 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str
const auto program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding("cameraCorrectionBuffer", CameraCorrection));
slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", DeferredFrameTransform));
slotBindings.insert(gpu::Shader::Binding("shadowTransformBuffer", ShadowTransform));
slotBindings.insert(gpu::Shader::Binding("albedoMap", Albedo));
slotBindings.insert(gpu::Shader::Binding("normalMap", Normal));
slotBindings.insert(gpu::Shader::Binding("specularMap", Specular));
@ -404,6 +440,7 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
auto& linearDepthTarget = inputs.get1();
auto& surfaceGeometryFramebuffer = inputs.get2();
auto& ambientOcclusionFramebuffer = inputs.get3();
auto& frameTransform = inputs.get4();
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.enableStereo(false);
@ -422,8 +459,8 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
// TODO REMOVE: Temporary until UI
auto first = _customPipelines.begin()->first;
batch.setPipeline(getPipeline(_mode, first));
auto pipeline = getPipeline(_mode, first);
batch.setPipeline(pipeline);
if (deferredFramebuffer) {
batch.setResourceTexture(Albedo, deferredFramebuffer->getDeferredColorTexture());
@ -439,7 +476,10 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow();
const auto& globalShadow = lightAndShadow.second;
if (globalShadow) {
batch.setResourceTexture(Shadow, globalShadow->map);
const auto cascadeIndex = glm::clamp(_mode - Mode::ShadowCascade0Mode, 0, (int)globalShadow->getCascadeCount() - 1);
batch.setResourceTexture(Shadow, globalShadow->getCascade(cascadeIndex).map);
batch.setUniformBuffer(ShadowTransform, globalShadow->getBuffer());
batch.setUniformBuffer(DeferredFrameTransform, frameTransform->getFrameTransformBuffer());
}
if (linearDepthTarget) {
@ -460,7 +500,6 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
const glm::vec2 topRight(_size.z, _size.w);
geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryId);
batch.setResourceTexture(Albedo, nullptr);
batch.setResourceTexture(Normal, nullptr);
batch.setResourceTexture(Specular, nullptr);

View file

@ -15,6 +15,7 @@
#include <QFileInfo>
#include <render/DrawTask.h>
#include "DeferredFrameTransform.h"
#include "DeferredFramebuffer.h"
#include "SurfaceGeometryPass.h"
#include "AmbientOcclusionEffect.h"
@ -37,7 +38,7 @@ signals:
class DebugDeferredBuffer {
public:
using Inputs = render::VaryingSet4<DeferredFramebufferPointer, LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer>;
using Inputs = render::VaryingSet5<DeferredFramebufferPointer, LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer, DeferredFrameTransformPointer>;
using Config = DebugDeferredBufferConfig;
using JobModel = render::Job::ModelI<DebugDeferredBuffer, Inputs, Config>;
@ -64,7 +65,11 @@ protected:
LightmapMode,
ScatteringMode,
LightingMode,
ShadowMode,
ShadowCascade0Mode,
ShadowCascade1Mode,
ShadowCascade2Mode,
ShadowCascade3Mode,
ShadowCascadeIndicesMode,
LinearDepthMode,
HalfLinearDepthMode,
HalfNormalMode,

View file

@ -58,7 +58,7 @@ enum DeferredShader_MapSlot {
DEFERRED_BUFFER_DEPTH_UNIT = 3,
DEFERRED_BUFFER_OBSCURANCE_UNIT = 4,
SHADOW_MAP_UNIT = 5,
SKYBOX_MAP_UNIT = 6,
SKYBOX_MAP_UNIT = SHADOW_MAP_UNIT + SHADOW_CASCADE_MAX_COUNT,
DEFERRED_BUFFER_LINEAR_DEPTH_UNIT,
DEFERRED_BUFFER_CURVATURE_UNIT,
DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT,
@ -156,7 +156,7 @@ static gpu::ShaderPointer makeLightProgram(const char* vertSource, const char* f
slotBindings.insert(gpu::Shader::Binding(std::string("specularMap"), DEFERRED_BUFFER_EMISSIVE_UNIT));
slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), DEFERRED_BUFFER_DEPTH_UNIT));
slotBindings.insert(gpu::Shader::Binding(std::string("obscuranceMap"), DEFERRED_BUFFER_OBSCURANCE_UNIT));
slotBindings.insert(gpu::Shader::Binding(std::string("shadowMap"), SHADOW_MAP_UNIT));
slotBindings.insert(gpu::Shader::Binding(std::string("shadowMaps"), SHADOW_MAP_UNIT));
slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), SKYBOX_MAP_UNIT));
slotBindings.insert(gpu::Shader::Binding(std::string("linearZeyeMap"), DEFERRED_BUFFER_LINEAR_DEPTH_UNIT));
@ -501,9 +501,11 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext,
auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow();
const auto& globalShadow = lightAndShadow.second;
// Bind the shadow buffer
// Bind the shadow buffers
if (globalShadow) {
batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow->map);
for (unsigned int i = 0; i < globalShadow->getCascadeCount(); i++) {
batch.setResourceTexture(SHADOW_MAP_UNIT+i, globalShadow->getCascade(i).map);
}
}
auto& program = deferredLightingEffect->_directionalSkyboxLight;
@ -567,8 +569,9 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext,
deferredLightingEffect->unsetKeyLightBatch(batch, locations->lightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT);
batch.setResourceTexture(SHADOW_MAP_UNIT, nullptr);
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
batch.setResourceTexture(SHADOW_MAP_UNIT+i, nullptr);
}
}
}

View file

@ -169,7 +169,12 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu
auto hazeStage = args->_scene->getStage<HazeStage>();
if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) {
model::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front());
batch.setUniformBuffer(HazeEffect_ParamsSlot, hazePointer->getHazeParametersBuffer());
if (hazePointer) {
batch.setUniformBuffer(HazeEffect_ParamsSlot, hazePointer->getHazeParametersBuffer());
} else {
// Something is wrong, so just quit Haze
return;
}
}
batch.setUniformBuffer(HazeEffect_TransformBufferSlot, transformBuffer->getFrameTransformBuffer());
@ -178,7 +183,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu
if (lightStage) {
model::LightPointer keyLight;
keyLight = lightStage->getCurrentKeyLight();
if (keyLight != nullptr) {
if (keyLight) {
batch.setUniformBuffer(HazeEffect_LightingMapSlot, keyLight->getLightSchemaBuffer());
}
}

View file

@ -74,6 +74,32 @@ static std::array<GeometryCache::Shape, (GeometryCache::NUM_SHAPES - 1)> MAPPING
GeometryCache::Cylinder,
} };
/**jsdoc
* <p>{@link Entities} and {@link Overlays} may have the following geometrical shapes:</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>Line</code></td><td>A 1D line oriented in 3 dimensions.</td></tr>
* <tr><td><code>Triangle</code></td><td>A triangular prism.</td></tr>
* <tr><td><code>Quad</code></td><td>A 2D square oriented in 3 dimensions.</tr>
* <tr><td><code>Hexagon</code></td><td>A hexagonal prism.</td></tr>
* <tr><td><code>Octagon</code></td><td>An octagonal prism.</td></tr>
* <tr><td><code>Circle</code></td><td>A 2D circle oriented in 3 dimensions.</td></td></tr>
* <tr><td><code>Cube</code></td><td>A cube.</td></tr>
* <tr><td><code>Sphere</code></td><td>A sphere.</td></tr>
* <tr><td><code>Tetrahedron</code></td><td>A tetrahedron.</td></tr>
* <tr><td><code>Octahedron</code></td><td>An octahedron.</td></tr>
* <tr><td><code>Dodecahedron</code></td><td>A dodecahedron.</td></tr>
* <tr><td><code>Icosahedron</code></td><td>An icosahedron.</td></tr>
* <tr><td><code>Torus</code></td><td>A torus. <em>Not implemented.</em></td></tr>
* <tr><td><code>Cone</code></td><td>A cone.</td></tr>
* <tr><td><code>Cylinder</code></td><td>A cylinder.</td></tr>
* </tbody>
* </table>
* @typedef {string} Shape
*/
static const std::array<const char * const, GeometryCache::NUM_SHAPES> GEOCACHE_SHAPE_STRINGS{ {
"Line",
"Triangle",

View file

@ -13,65 +13,202 @@
#include "LightStage.h"
#include <cmath>
std::string LightStage::_stageName { "LIGHT_STAGE"};
const glm::mat4 LightStage::Shadow::_biasMatrix{
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0 };
const int LightStage::Shadow::MAP_SIZE = 1024;
static const auto MAX_BIAS = 0.006f;
const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX };
LightStage::LightStage() {
}
LightStage::Shadow::Schema::Schema() :
bias{ 0.005f },
scale{ 1.0f / MAP_SIZE } {
LightStage::Shadow::Schema::Schema() {
ShadowTransform defaultTransform;
defaultTransform.bias = MAX_BIAS;
std::fill(cascades, cascades + SHADOW_CASCADE_MAX_COUNT, defaultTransform);
invMapSize = 1.0f / MAP_SIZE;
cascadeCount = 1;
invCascadeBlendWidth = 1.0f / 0.2f;
invFalloffDistance = 1.0f / 2.0f;
maxDistance = 20.0f;
}
gpu::FramebufferPointer LightStage::Shadow::framebuffer;
gpu::TexturePointer LightStage::Shadow::map;
LightStage::Shadow::Cascade::Cascade() :
_frustum{ std::make_shared<ViewFrustum>() },
_minDistance{ 0.0f },
_maxDistance{ 20.0f } {
framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::createShadowmap(MAP_SIZE));
map = framebuffer->getDepthStencilBuffer();
}
LightStage::Shadow::Shadow(model::LightPointer light) : _light{ light}, _frustum{ std::make_shared<ViewFrustum>() } {
Schema schema;
_schemaBuffer = std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema);
const glm::mat4& LightStage::Shadow::Cascade::getView() const {
return _frustum->getView();
}
if (!framebuffer) {
framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::createShadowmap(MAP_SIZE));
map = framebuffer->getDepthStencilBuffer();
const glm::mat4& LightStage::Shadow::Cascade::getProjection() const {
return _frustum->getProjection();
}
float LightStage::Shadow::Cascade::computeFarDistance(const ViewFrustum& viewFrustum, const Transform& shadowViewInverse,
float left, float right, float bottom, float top, float viewMaxShadowDistance) const {
// Far distance should be extended to the intersection of the infinitely extruded shadow frustum
// with the view frustum side planes. To do so, we generate 10 triangles in shadow space which are the result of
// tesselating the side and far faces of the view frustum and clip them with the 4 side planes of the
// shadow frustum. The resulting clipped triangle vertices with the farthest Z gives the desired
// shadow frustum far distance.
std::array<Triangle, 10> viewFrustumTriangles;
Plane shadowClipPlanes[4] = {
Plane(glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, top, 0.0f)),
Plane(glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, bottom, 0.0f)),
Plane(glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(left, 0.0f, 0.0f)),
Plane(glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(right, 0.0f, 0.0f))
};
viewFrustum.tesselateSidesAndFar(shadowViewInverse, viewFrustumTriangles.data(), viewMaxShadowDistance);
static const int MAX_TRIANGLE_COUNT = 16;
auto far = 0.0f;
for (auto& triangle : viewFrustumTriangles) {
Triangle clippedTriangles[MAX_TRIANGLE_COUNT];
auto clippedTriangleCount = clipTriangleWithPlanes(triangle, shadowClipPlanes, 4, clippedTriangles, MAX_TRIANGLE_COUNT);
for (auto i = 0; i < clippedTriangleCount; i++) {
const auto& clippedTriangle = clippedTriangles[i];
far = glm::max(far, -clippedTriangle.v0.z);
far = glm::max(far, -clippedTriangle.v1.z);
far = glm::max(far, -clippedTriangle.v2.z);
}
}
return far;
}
void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,
float viewMinShadowDistance, float viewMaxShadowDistance,
float nearDepth, float farDepth) {
assert(viewMinShadowDistance < viewMaxShadowDistance);
assert(nearDepth < farDepth);
LightStage::Shadow::Shadow(model::LightPointer light, float maxDistance, unsigned int cascadeCount) :
_light{ light } {
cascadeCount = std::min(cascadeCount, (unsigned int)SHADOW_CASCADE_MAX_COUNT);
Schema schema;
schema.cascadeCount = cascadeCount;
_schemaBuffer = std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema);
_cascades.resize(cascadeCount);
setMaxDistance(maxDistance);
}
void LightStage::Shadow::setMaxDistance(float value) {
// This overlaping factor isn't really used directly for blending of shadow cascades. It
// just there to be sure the cascades do overlap. The blending width used is relative
// to the UV space and is set in the Schema with invCascadeBlendWidth.
static const auto OVERLAP_FACTOR = 1.0f / 5.0f;
_maxDistance = std::max(0.0f, value);
if (_cascades.size() == 1) {
_cascades.front().setMinDistance(0.0f);
_cascades.front().setMaxDistance(_maxDistance);
} else {
// Distribute the cascades along that distance
// TODO : these parameters should be exposed to the user as part of the light entity parameters, no?
static const auto LOW_MAX_DISTANCE = 2.0f;
static const auto MAX_RESOLUTION_LOSS = 0.6f; // Between 0 and 1, 0 giving tighter distributions
// The max cascade distance is computed by multiplying the previous cascade's max distance by a certain
// factor. There is a "user" factor that is computed from a desired max resolution loss in the shadow
// and an optimal one based on the global min and max shadow distance, all cascades considered. The final
// distance is a gradual blend between the two
const auto userDistanceScale = 1.0f / (1.0f - MAX_RESOLUTION_LOSS);
const auto optimalDistanceScale = powf(_maxDistance / LOW_MAX_DISTANCE, 1.0f / (_cascades.size() - 1));
float maxCascadeUserDistance = LOW_MAX_DISTANCE;
float maxCascadeOptimalDistance = LOW_MAX_DISTANCE;
float minCascadeDistance = 0.0f;
for (size_t cascadeIndex = 0; cascadeIndex < _cascades.size(); ++cascadeIndex) {
float blendFactor = cascadeIndex / float(_cascades.size() - 1);
float maxCascadeDistance;
if (cascadeIndex == size_t(_cascades.size() - 1)) {
maxCascadeDistance = _maxDistance;
} else {
maxCascadeDistance = maxCascadeUserDistance + (maxCascadeOptimalDistance - maxCascadeUserDistance)*blendFactor*blendFactor;
}
float shadowOverlapDistance = maxCascadeDistance * OVERLAP_FACTOR;
_cascades[cascadeIndex].setMinDistance(minCascadeDistance);
_cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance);
// Compute distances for next cascade
minCascadeDistance = maxCascadeDistance;
maxCascadeUserDistance = maxCascadeUserDistance * userDistanceScale;
maxCascadeOptimalDistance = maxCascadeOptimalDistance * optimalDistanceScale;
maxCascadeUserDistance = std::min(maxCascadeUserDistance, _maxDistance);
}
}
// Update the buffer
const auto& lastCascade = _cascades.back();
auto& schema = _schemaBuffer.edit<Schema>();
schema.maxDistance = _maxDistance;
schema.invFalloffDistance = 1.0f / (OVERLAP_FACTOR*lastCascade.getMaxDistance());
}
void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,
float nearDepth, float farDepth) {
assert(nearDepth < farDepth);
// Orient the keylight frustum
const auto& direction = glm::normalize(_light->getDirection());
auto lightDirection = glm::normalize(_light->getDirection());
glm::quat orientation;
if (direction == IDENTITY_UP) {
if (lightDirection == IDENTITY_UP) {
orientation = glm::quat(glm::mat3(-IDENTITY_RIGHT, IDENTITY_FORWARD, -IDENTITY_UP));
} else if (direction == -IDENTITY_UP) {
} else if (lightDirection == -IDENTITY_UP) {
orientation = glm::quat(glm::mat3(IDENTITY_RIGHT, IDENTITY_FORWARD, IDENTITY_UP));
} else {
auto side = glm::normalize(glm::cross(direction, IDENTITY_UP));
auto up = glm::normalize(glm::cross(side, direction));
orientation = glm::quat_cast(glm::mat3(side, up, -direction));
auto side = glm::normalize(glm::cross(lightDirection, IDENTITY_UP));
auto up = glm::normalize(glm::cross(side, lightDirection));
orientation = glm::quat_cast(glm::mat3(side, up, -lightDirection));
}
_frustum->setOrientation(orientation);
// Position the keylight frustum
_frustum->setPosition(viewFrustum.getPosition() - (nearDepth + farDepth)*direction);
auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection;
for (auto& cascade : _cascades) {
cascade._frustum->setOrientation(orientation);
cascade._frustum->setPosition(position);
}
// Update the buffer
auto& schema = _schemaBuffer.edit<Schema>();
schema.lightDirInViewSpace = glm::inverse(viewFrustum.getView()) * glm::vec4(lightDirection, 0.f);
}
const Transform view{ _frustum->getView()};
const Transform viewInverse{ view.getInverseMatrix() };
void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum,
float nearDepth, float farDepth) {
assert(nearDepth < farDepth);
assert(cascadeIndex < _cascades.size());
auto nearCorners = viewFrustum.getCorners(viewMinShadowDistance);
auto farCorners = viewFrustum.getCorners(viewMaxShadowDistance);
auto& cascade = _cascades[cascadeIndex];
const auto viewMinCascadeShadowDistance = std::max(viewFrustum.getNearClip(), cascade.getMinDistance());
const auto viewMaxCascadeShadowDistance = std::min(viewFrustum.getFarClip(), cascade.getMaxDistance());
const auto viewMaxShadowDistance = _cascades.back().getMaxDistance();
vec3 min{ viewInverse.transform(nearCorners.bottomLeft) };
const Transform shadowView{ cascade._frustum->getView()};
const Transform shadowViewInverse{ shadowView.getInverseMatrix() };
auto nearCorners = viewFrustum.getCorners(viewMinCascadeShadowDistance);
auto farCorners = viewFrustum.getCorners(viewMaxCascadeShadowDistance);
vec3 min{ shadowViewInverse.transform(nearCorners.bottomLeft) };
vec3 max{ min };
// Expand keylight frustum to fit view frustum
auto fitFrustum = [&min, &max, &viewInverse](const vec3& viewCorner) {
const auto corner = viewInverse.transform(viewCorner);
auto fitFrustum = [&min, &max, &shadowViewInverse](const vec3& viewCorner) {
const auto corner = shadowViewInverse.transform(viewCorner);
min.x = glm::min(min.x, corner.x);
min.y = glm::min(min.y, corner.y);
@ -89,36 +226,35 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,
fitFrustum(farCorners.topLeft);
fitFrustum(farCorners.topRight);
// Re-adjust near shadow distance
auto near = glm::max(max.z, -nearDepth);
auto far = -min.z;
// Re-adjust near and far shadow distance
auto near = glm::min(-max.z, nearDepth);
auto far = cascade.computeFarDistance(viewFrustum, shadowViewInverse, min.x, max.x, min.y, max.y, viewMaxShadowDistance);
glm::mat4 ortho = glm::ortho<float>(min.x, max.x, min.y, max.y, near, far);
_frustum->setProjection(ortho);
cascade._frustum->setProjection(ortho);
// Calculate the frustum's internal state
_frustum->calculate();
cascade._frustum->calculate();
// Update the buffer
_schemaBuffer.edit<Schema>().projection = ortho;
_schemaBuffer.edit<Schema>().viewInverse = viewInverse.getMatrix();
auto& schema = _schemaBuffer.edit<Schema>();
schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix();
// Adapt shadow bias to shadow resolution with a totally empirical formula
const auto maxShadowFrustumDim = std::max(fabsf(min.x - max.x), fabsf(min.y - max.y));
const auto REFERENCE_TEXEL_DENSITY = 7.5f;
const auto cascadeTexelDensity = MAP_SIZE / maxShadowFrustumDim;
schema.cascades[cascadeIndex].bias = MAX_BIAS * std::min(1.0f, REFERENCE_TEXEL_DENSITY / cascadeTexelDensity);
}
void LightStage::Shadow::setFrustum(const ViewFrustum& shadowFrustum) {
void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) {
assert(cascadeIndex < _cascades.size());
const Transform view{ shadowFrustum.getView() };
const Transform viewInverse{ view.getInverseMatrix() };
auto& cascade = _cascades[cascadeIndex];
*_frustum = shadowFrustum;
*cascade._frustum = shadowFrustum;
// Update the buffer
_schemaBuffer.edit<Schema>().projection = shadowFrustum.getProjection();
_schemaBuffer.edit<Schema>().viewInverse = viewInverse.getMatrix();
}
const glm::mat4& LightStage::Shadow::getView() const {
return _frustum->getView();
}
const glm::mat4& LightStage::Shadow::getProjection() const {
return _frustum->getProjection();
_schemaBuffer.edit<Schema>().cascades[cascadeIndex].reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix();
}
LightStage::Index LightStage::findLight(const LightPointer& light) const {
@ -142,7 +278,7 @@ LightStage::Index LightStage::addLight(const LightPointer& light) {
_descs.emplace_back(Desc());
} else {
assert(_descs[lightId].shadowId == INVALID_INDEX);
_descs.emplace(_descs.begin() + lightId, Desc());
_descs[lightId] = Desc();
}
// INsert the light and its index in the reverese map
@ -156,12 +292,12 @@ LightStage::Index LightStage::addLight(const LightPointer& light) {
}
}
LightStage::Index LightStage::addShadow(Index lightIndex) {
LightStage::Index LightStage::addShadow(Index lightIndex, float maxDistance, unsigned int cascadeCount) {
auto light = getLight(lightIndex);
Index shadowId = INVALID_INDEX;
if (light) {
assert(_descs[lightIndex].shadowId == INVALID_INDEX);
shadowId = _shadows.newElement(std::make_shared<Shadow>(light));
shadowId = _shadows.newElement(std::make_shared<Shadow>(light, maxDistance, cascadeCount));
_descs[lightIndex].shadowId = shadowId;
}
return shadowId;

View file

@ -44,45 +44,74 @@ public:
class Shadow {
public:
using UniformBufferView = gpu::BufferView;
static const int MAP_SIZE = 1024;
static const int MAP_SIZE;
Shadow(model::LightPointer light);
class Cascade {
friend Shadow;
public:
void setKeylightFrustum(const ViewFrustum& viewFrustum, float viewMinShadowDistance, float viewMaxShadowDistance, float nearDepth = 1.0f, float farDepth = 1000.0f);
Cascade();
void setFrustum(const ViewFrustum& shadowFrustum);
const std::shared_ptr<ViewFrustum> getFrustum() const { return _frustum; }
gpu::FramebufferPointer framebuffer;
gpu::TexturePointer map;
const glm::mat4& getView() const;
const glm::mat4& getProjection() const;
const std::shared_ptr<ViewFrustum>& getFrustum() const { return _frustum; }
const glm::mat4& getView() const;
const glm::mat4& getProjection() const;
void setMinDistance(float value) { _minDistance = value; }
void setMaxDistance(float value) { _maxDistance = value; }
float getMinDistance() const { return _minDistance; }
float getMaxDistance() const { return _maxDistance; }
private:
std::shared_ptr<ViewFrustum> _frustum;
float _minDistance;
float _maxDistance;
float computeFarDistance(const ViewFrustum& viewFrustum, const Transform& shadowViewInverse,
float left, float right, float bottom, float top, float viewMaxShadowDistance) const;
};
Shadow(model::LightPointer light, float maxDistance, unsigned int cascadeCount = 1);
void setKeylightFrustum(const ViewFrustum& viewFrustum,
float nearDepth = 1.0f, float farDepth = 1000.0f);
void setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum,
float nearDepth = 1.0f, float farDepth = 1000.0f);
void setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum);
const UniformBufferView& getBuffer() const { return _schemaBuffer; }
// Shadow maps are shared among all lights for the moment as only one key light
// is used.
static gpu::FramebufferPointer framebuffer;
static gpu::TexturePointer map;
unsigned int getCascadeCount() const { return (unsigned int)_cascades.size(); }
const Cascade& getCascade(unsigned int index) const { return _cascades[index]; }
float getMaxDistance() const { return _maxDistance; }
void setMaxDistance(float value);
const model::LightPointer& getLight() const { return _light; }
protected:
model::LightPointer _light;
std::shared_ptr<ViewFrustum> _frustum;
#include "Shadows_shared.slh"
class Schema {
using Cascades = std::vector<Cascade>;
static const glm::mat4 _biasMatrix;
model::LightPointer _light;
float _maxDistance;
Cascades _cascades;
class Schema : public ShadowParameters {
public:
Schema();
glm::mat4 projection;
glm::mat4 viewInverse;
glm::float32 bias;
glm::float32 scale;
};
UniformBufferView _schemaBuffer = nullptr;
};
using ShadowPointer = std::shared_ptr<Shadow>;
@ -91,7 +120,7 @@ public:
Index findLight(const LightPointer& light) const;
Index addLight(const LightPointer& light);
Index addShadow(Index lightIndex);
Index addShadow(Index lightIndex, float maxDistance = 20.0f, unsigned int cascadeCount = 1U);
LightPointer removeLight(Index index);

View file

@ -205,34 +205,23 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
task.addJob<DrawBounds>("DrawZones", zones);
const auto frustums = task.addJob<ExtractFrustums>("ExtractFrustums");
const auto viewFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::VIEW_FRUSTUM);
const auto shadowFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::SHADOW_FRUSTUM);
task.addJob<DrawFrustum>("DrawViewFrustum", viewFrustum, glm::vec3(1.0f, 1.0f, 0.0f));
task.addJob<DrawFrustum>("DrawShadowFrustum", shadowFrustum, glm::vec3(0.0f, 0.0f, 1.0f));
for (auto i = 0; i < ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT; i++) {
const auto shadowFrustum = frustums.getN<ExtractFrustums::Output>(ExtractFrustums::SHADOW_CASCADE0_FRUSTUM+i);
float tint = 1.0f - i / float(ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT - 1);
char jobName[64];
sprintf(jobName, "DrawShadowFrustum%d", i);
task.addJob<DrawFrustum>(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f));
}
// Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true
task.addJob<DrawBounds>("DrawSelectionBounds", selectedItems);
}
// Layered Overlays
const auto filteredOverlaysOpaque = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT);
const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT);
const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0);
const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0);
const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying();
const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying();
task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true);
task.addJob<DrawOverlay3D>("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false);
{ // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer
task.addJob<DrawBounds>("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque);
task.addJob<DrawBounds>("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent);
}
// Debugging stages
{
// Debugging Deferred buffer job
const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer));
const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, deferredFrameTransform));
task.addJob<DebugDeferredBuffer>("DebugDeferredBuffer", debugFramebuffers);
const auto debugSubsurfaceScatteringInputs = DebugSubsurfaceScattering::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel,
@ -259,6 +248,22 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
task.addJob<DebugZoneLighting>("DrawZoneStack", deferredFrameTransform);
}
// Layered Overlays
const auto filteredOverlaysOpaque = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT);
const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT);
const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0);
const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0);
const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying();
const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying();
task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true);
task.addJob<DrawOverlay3D>("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false);
{ // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer
task.addJob<DrawBounds>("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque);
task.addJob<DrawBounds>("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent);
}
// AA job to be revisited
task.addJob<Antialiasing>("Antialiasing", primaryFramebuffer);
@ -555,17 +560,20 @@ void ExtractFrustums::run(const render::RenderContextPointer& renderContext, Out
}
// Return shadow frustum
auto& shadowFrustum = output[SHADOW_FRUSTUM].edit<ViewFrustumPointer>();
auto lightStage = args->_scene->getStage<LightStage>(LightStage::getName());
if (lightStage) {
auto globalShadow = lightStage->getCurrentKeyShadow();
for (auto i = 0; i < SHADOW_CASCADE_FRUSTUM_COUNT; i++) {
auto& shadowFrustum = output[SHADOW_CASCADE0_FRUSTUM+i].edit<ViewFrustumPointer>();
if (lightStage) {
auto globalShadow = lightStage->getCurrentKeyShadow();
if (globalShadow) {
shadowFrustum = globalShadow->getFrustum();
if (globalShadow && i<(int)globalShadow->getCascadeCount()) {
auto& cascade = globalShadow->getCascade(i);
shadowFrustum = cascade.getFrustum();
} else {
shadowFrustum.reset();
}
} else {
shadowFrustum.reset();
}
} else {
shadowFrustum.reset();
}
}

View file

@ -174,8 +174,14 @@ class ExtractFrustums {
public:
enum Frustum {
VIEW_FRUSTUM,
SHADOW_FRUSTUM,
SHADOW_CASCADE0_FRUSTUM = 0,
SHADOW_CASCADE1_FRUSTUM,
SHADOW_CASCADE2_FRUSTUM,
SHADOW_CASCADE3_FRUSTUM,
SHADOW_CASCADE_FRUSTUM_COUNT,
VIEW_FRUSTUM = SHADOW_CASCADE_FRUSTUM_COUNT,
FRUSTUM_COUNT
};

View file

@ -22,6 +22,8 @@
#include "DeferredLightingEffect.h"
#include "FramebufferCache.h"
#include "RenderUtilsLogging.h"
// These values are used for culling the objects rendered in the shadow map
// but are readjusted afterwards
#define SHADOW_FRUSTUM_NEAR 1.0f
@ -89,31 +91,13 @@ static void adjustNearFar(const AABox& inShapeBounds, ViewFrustum& shadowFrustum
for (i = 0; i < 8; i++) {
sceneBoundVertices[i] = shadowViewInverse.transform(inShapeBounds.getVertex(static_cast<BoxVertex>(i)));
}
// This indirection array is just a protection in case the ViewFrustum::PlaneIndex enum
// changes order especially as we don't need to test the NEAR and FAR planes.
static const ViewFrustum::PlaneIndex planeIndices[4] = {
ViewFrustum::TOP_PLANE,
ViewFrustum::BOTTOM_PLANE,
ViewFrustum::LEFT_PLANE,
ViewFrustum::RIGHT_PLANE
};
// Same goes for the shadow frustum planes.
for (i = 0; i < 4; i++) {
const auto& worldPlane = shadowFrustum.getPlanes()[planeIndices[i]];
// We assume the transform doesn't have a non uniform scale component to apply the
// transform to the normal without using the correct transpose of inverse, which should be the
// case for a view matrix.
auto planeNormal = shadowViewInverse.transformDirection(worldPlane.getNormal());
auto planePoint = shadowViewInverse.transform(worldPlane.getPoint());
shadowClipPlanes[i].setNormalAndPoint(planeNormal, planePoint);
}
shadowFrustum.getUniformlyTransformedSidePlanes(shadowViewInverse, shadowClipPlanes);
float near = std::numeric_limits<float>::max();
float far = 0.0f;
computeNearFar(sceneBoundVertices, shadowClipPlanes, near, far);
// Limit the far range to the one used originally. There's no point in rendering objects
// that are not in the view frustum.
// Limit the far range to the one used originally.
far = glm::min(far, shadowFrustum.getFarClip());
const auto depthEpsilon = 0.1f;
@ -137,9 +121,12 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
assert(lightStage);
auto shadow = lightStage->getCurrentKeyShadow();
if (!shadow) return;
if (!shadow || _cascadeIndex >= shadow->getCascadeCount()) {
return;
}
const auto& fbo = shadow->framebuffer;
auto& cascade = shadow->getCascade(_cascadeIndex);
auto& fbo = cascade.framebuffer;
RenderArgs* args = renderContext->args;
ShapeKey::Builder defaultKeyBuilder;
@ -149,7 +136,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
// the minimal Z range.
adjustNearFar(inShapeBounds, adjustedShadowFrustum);
// Reapply the frustum as it has been adjusted
shadow->setFrustum(adjustedShadowFrustum);
shadow->setCascadeFrustum(_cascadeIndex, adjustedShadowFrustum);
args->popViewFrustum();
args->pushViewFrustum(adjustedShadowFrustum);
@ -178,6 +165,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned());
std::vector<ShapeKey> skinnedShapeKeys{};
std::vector<ShapeKey> ownPipelineShapeKeys{};
// Iterate through all inShapes and render the unskinned
args->_shapePipeline = shadowPipeline;
@ -185,8 +173,10 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
for (auto items : inShapes) {
if (items.first.isSkinned()) {
skinnedShapeKeys.push_back(items.first);
} else {
} else if (!items.first.hasOwnPipeline()) {
renderItems(renderContext, items.second);
} else {
ownPipelineShapeKeys.push_back(items.first);
}
}
@ -197,7 +187,15 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
renderItems(renderContext, inShapes.at(key));
}
// Finally render the items with their own pipeline last to prevent them from breaking the
// render state. This is probably a temporary code as there is probably something better
// to do in the render call of objects that have their own pipeline.
args->_shapePipeline = nullptr;
for (const auto& key : ownPipelineShapeKeys) {
args->_itemShapeKey = key._flags.to_ulong();
renderItems(renderContext, inShapes.at(key));
}
args->_batch = nullptr;
});
}
@ -215,22 +213,26 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
initZPassPipelines(*shapePlumber, state);
}
const auto cachedMode = task.addJob<RenderShadowSetup>("ShadowSetup");
task.addJob<RenderShadowSetup>("ShadowSetup");
// CPU jobs:
// Fetch and cull the items from the scene
auto shadowFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered();
const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowSelection", shadowFilter);
const auto culledShadowSelection = task.addJob<CullSpatialSelection>("CullShadowSelection", shadowSelection, cullFunctor, RenderDetails::SHADOW, shadowFilter);
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
const auto setupOutput = task.addJob<RenderShadowCascadeSetup>("ShadowCascadeSetup", i);
const auto shadowFilter = setupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
// Sort
const auto sortedPipelines = task.addJob<PipelineSortShapes>("PipelineSortShadowSort", culledShadowSelection);
const auto sortedShapesAndBounds = task.addJob<DepthSortShapesAndComputeBounds>("DepthSortShadowMap", sortedPipelines, true);
// CPU jobs:
// Fetch and cull the items from the scene
const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowSelection", shadowFilter);
const auto cullInputs = CullSpatialSelection::Inputs(shadowSelection, shadowFilter).asVarying();
const auto culledShadowSelection = task.addJob<CullSpatialSelection>("CullShadowSelection", cullInputs, cullFunctor, RenderDetails::SHADOW);
// GPU jobs: Render to shadow map
task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapesAndBounds, shapePlumber);
// Sort
const auto sortedPipelines = task.addJob<PipelineSortShapes>("PipelineSortShadowSort", culledShadowSelection);
const auto sortedShapesAndBounds = task.addJob<DepthSortShapesAndComputeBounds>("DepthSortShadowMap", sortedPipelines, true);
task.addJob<RenderShadowTeardown>("ShadowTeardown", cachedMode);
// GPU jobs: Render to shadow map
task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapesAndBounds, shapePlumber, i);
task.addJob<RenderShadowCascadeTeardown>("ShadowCascadeTeardown", setupOutput);
}
}
void RenderShadowTask::configure(const Config& configuration) {
@ -239,31 +241,57 @@ void RenderShadowTask::configure(const Config& configuration) {
// Task::configure(configuration);
}
void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Output& output) {
void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) {
auto lightStage = renderContext->_scene->getStage<LightStage>();
assert(lightStage);
// Cache old render args
RenderArgs* args = renderContext->args;
const auto globalShadow = lightStage->getCurrentKeyShadow();
if (globalShadow) {
// Cache old render args
RenderArgs* args = renderContext->args;
output = args->_renderMode;
auto nearClip = args->getViewFrustum().getNearClip();
float nearDepth = -args->_boomOffset.z;
const float SHADOW_MAX_DISTANCE = 20.0f;
globalShadow->setKeylightFrustum(args->getViewFrustum(), nearDepth, nearClip + SHADOW_MAX_DISTANCE, SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
// Set the keylight render args
args->pushViewFrustum(*(globalShadow->getFrustum()));
args->_renderMode = RenderArgs::SHADOW_RENDER_MODE;
globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
}
}
void RenderShadowTeardown::run(const render::RenderContextPointer& renderContext, const Input& input) {
void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) {
auto lightStage = renderContext->_scene->getStage<LightStage>();
assert(lightStage);
// Cache old render args
RenderArgs* args = renderContext->args;
output.edit0() = args->_renderMode;
output.edit2() = args->_sizeScale;
const auto globalShadow = lightStage->getCurrentKeyShadow();
if (globalShadow && _cascadeIndex<globalShadow->getCascadeCount()) {
output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered();
globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
// Set the keylight render args
args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum()));
args->_renderMode = RenderArgs::SHADOW_RENDER_MODE;
if (lightStage->getCurrentKeyLight()->getType() == model::Light::SUN) {
const float shadowSizeScale = 1e16f;
// Set the size scale to a ridiculously high value to prevent small object culling which assumes
// the view frustum is a perspective projection. But this isn't the case for the sun which
// is an orthographic projection.
args->_sizeScale = shadowSizeScale;
}
} else {
output.edit1() = ItemFilter::Builder::nothing();
}
}
void RenderShadowCascadeTeardown::run(const render::RenderContextPointer& renderContext, const Input& input) {
RenderArgs* args = renderContext->args;
if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE && !input.get1().selectsNothing()) {
args->popViewFrustum();
}
assert(args->hasViewFrustum());
// Reset the render args
args->popViewFrustum();
args->_renderMode = input;
args->_renderMode = input.get0();
args->_sizeScale = input.get2();
};

View file

@ -24,11 +24,12 @@ public:
using Inputs = render::VaryingSet2<render::ShapeBounds, AABox>;
using JobModel = render::Job::ModelI<RenderShadowMap, Inputs>;
RenderShadowMap(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {}
RenderShadowMap(render::ShapePlumberPointer shapePlumber, unsigned int cascadeIndex) : _shapePlumber{ shapePlumber }, _cascadeIndex{ cascadeIndex } {}
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
protected:
render::ShapePlumberPointer _shapePlumber;
unsigned int _cascadeIndex;
};
class RenderShadowTaskConfig : public render::Task::Config::Persistent {
@ -54,15 +55,30 @@ public:
class RenderShadowSetup {
public:
using Output = RenderArgs::RenderMode;
using JobModel = render::Job::ModelO<RenderShadowSetup, Output>;
void run(const render::RenderContextPointer& renderContext, Output& output);
using JobModel = render::Job::Model<RenderShadowSetup>;
RenderShadowSetup() {}
void run(const render::RenderContextPointer& renderContext);
};
class RenderShadowTeardown {
class RenderShadowCascadeSetup {
public:
using Input = RenderArgs::RenderMode;
using JobModel = render::Job::ModelI<RenderShadowTeardown, Input>;
using Outputs = render::VaryingSet3<RenderArgs::RenderMode, render::ItemFilter, float>;
using JobModel = render::Job::ModelO<RenderShadowCascadeSetup, Outputs>;
RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {}
void run(const render::RenderContextPointer& renderContext, Outputs& output);
private:
unsigned int _cascadeIndex;
};
class RenderShadowCascadeTeardown {
public:
using Input = RenderShadowCascadeSetup::Outputs;
using JobModel = render::Job::ModelI<RenderShadowCascadeTeardown, Input>;
void run(const render::RenderContextPointer& renderContext, const Input& input);
};

View file

@ -14,15 +14,21 @@
#include "RenderDeferredTask.h"
#include "RenderForwardTask.h"
void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred) {
// auto items = input.get<Input>();
// Shadows use an orthographic projection because they are linked to sunlights
// but the cullFunctor passed is probably tailored for perspective projection and culls too much.
// TODO : create a special cull functor for this.
task.addJob<RenderShadowTask>("RenderShadowTask", nullptr);
task.addJob<RenderShadowTask>("RenderShadowTask", [](const RenderArgs* args, const AABox& bounds) {
// Cull only objects that are too small relatively to shadow frustum
auto& frustum = args->getViewFrustum();
auto frustumSize = std::max(frustum.getHeight(), frustum.getWidth());
const auto boundsRadius = bounds.getDimensions().length();
const auto relativeBoundRadius = boundsRadius / frustumSize;
const auto threshold = 1e-3f;
return relativeBoundRadius > threshold;
return true;
});
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
assert(items.canCast<RenderFetchCullSortTask::Output>());

View file

@ -11,53 +11,17 @@
<@if not SHADOW_SLH@>
<@def SHADOW_SLH@>
<@include ShadowCore.slh@>
#define SHADOW_NOISE_ENABLED 0
#define SHADOW_SCREEN_SPACE_DITHER 1
// the shadow texture
uniform sampler2DShadow shadowMap;
struct ShadowTransform {
mat4 projection;
mat4 viewInverse;
float bias;
float scale;
};
uniform shadowTransformBuffer {
ShadowTransform _shadowTransform;
};
mat4 getShadowViewInverse() {
return _shadowTransform.viewInverse;
}
mat4 getShadowProjection() {
return _shadowTransform.projection;
}
float getShadowScale() {
return _shadowTransform.scale;
}
float getShadowBias() {
return _shadowTransform.bias;
}
// Compute the texture coordinates from world coordinates
vec4 evalShadowTexcoord(vec4 position) {
mat4 biasMatrix = mat4(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0);
float bias = -getShadowBias();
vec4 shadowCoord = biasMatrix * getShadowProjection() * getShadowViewInverse() * position;
return vec4(shadowCoord.xy, shadowCoord.z + bias, 1.0);
}
uniform sampler2DShadow shadowMaps[SHADOW_CASCADE_MAX_COUNT];
// Sample the shadowMap with PCF (built-in)
float fetchShadow(vec3 shadowTexcoord) {
return texture(shadowMap, shadowTexcoord);
float fetchShadow(int cascadeIndex, vec3 shadowTexcoord) {
return texture(shadowMaps[cascadeIndex], shadowTexcoord);
}
vec2 PCFkernel[4] = vec2[4](
@ -67,38 +31,83 @@ vec2 PCFkernel[4] = vec2[4](
vec2(0.5, -1.5)
);
float evalShadowAttenuationPCF(vec4 position, vec4 shadowTexcoord) {
// PCF is buggy so disable it for the time being
#if 0
float pcfRadius = 3.0;
float evalShadowNoise(vec4 seed) {
float dot_product = dot(seed, vec4(12.9898,78.233,45.164,94.673));
return fract(sin(dot_product) * 43758.5453);
}
struct ShadowSampleOffsets {
vec3 points[4];
};
ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) {
float shadowScale = getShadowScale();
ShadowSampleOffsets offsets;
#if SHADOW_SCREEN_SPACE_DITHER
// Pattern dithering in screen space
ivec2 coords = ivec2(gl_FragCoord.xy);
#else
// Pattern dithering in world space (mm resolution)
ivec2 coords = ivec2(position.x, position.y+position.z);
#endif
#if SHADOW_NOISE_ENABLED
// Add some noise to break dithering
int index = int(4.0*evalShadowNoise(gl_FragCoord.xyyx))%4;
coords.x += index & 1;
coords.y += (index & 2) >> 1;
#endif
// Offset for efficient PCF, see http://http.developer.nvidia.com/GPUGems/gpugems_ch11.html
vec2 offset = pcfRadius * step(fract(position.xy), vec2(0.5, 0.5));
ivec2 offset = coords & ivec2(1,1);
offset.y = (offset.x+offset.y) & 1;
float shadowAttenuation = (0.25 * (
fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[0], 0.0)) +
fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[1], 0.0)) +
fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[2], 0.0)) +
fetchShadow(shadowTexcoord.xyz + shadowScale * vec3(offset + PCFkernel[3], 0.0))
));
#else
float shadowAttenuation = fetchShadow(shadowTexcoord.xyz);
#endif
offsets.points[0] = shadowScale * vec3(offset + PCFkernel[0], 0.0);
offsets.points[1] = shadowScale * vec3(offset + PCFkernel[1], 0.0);
offsets.points[2] = shadowScale * vec3(offset + PCFkernel[2], 0.0);
offsets.points[3] = shadowScale * vec3(offset + PCFkernel[3], 0.0);
return offsets;
}
float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) {
shadowTexcoord.z -= bias;
float shadowAttenuation = 0.25 * (
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[0]) +
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[1]) +
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[2]) +
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[3])
);
return shadowAttenuation;
}
float evalShadowAttenuation(vec4 position) {
vec4 shadowTexcoord = evalShadowTexcoord(position);
if (shadowTexcoord.x < 0.0 || shadowTexcoord.x > 1.0 ||
shadowTexcoord.y < 0.0 || shadowTexcoord.y > 1.0 ||
shadowTexcoord.z < 0.0 || shadowTexcoord.z > 1.0) {
float evalShadowCascadeAttenuation(int cascadeIndex, vec3 viewNormal, ShadowSampleOffsets offsets, vec4 shadowTexcoord) {
if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) {
// If a point is not in the map, do not attenuate
return 1.0;
}
// Multiply bias if we are at a grazing angle with light
float tangentFactor = abs(dot(getShadowDirInViewSpace(), viewNormal));
float bias = getShadowBias(cascadeIndex) * (5.0-4.0*tangentFactor);
return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias);
}
return evalShadowAttenuationPCF(position, shadowTexcoord);
float evalShadowAttenuation(vec4 worldPosition, float viewDepth, vec3 viewNormal) {
ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition);
vec4 cascadeShadowCoords[2];
ivec2 cascadeIndices;
float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);
vec2 cascadeAttenuations = vec2(1.0, 1.0);
cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, viewNormal, offsets, cascadeShadowCoords[0]);
if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) {
cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, viewNormal, offsets, cascadeShadowCoords[1]);
}
float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix);
// Falloff to max distance
return mix(1.0, attenuation, evalShadowFalloff(viewDepth));
}
<@endif@>

View file

@ -0,0 +1,96 @@
<!
// ShadowCore.slh
// libraries/render-utils/src
//
// Created by Olivier Prat on 11/13/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
!>
<@if not SHADOW_CORE_SLH@>
<@def SHADOW_CORE_SLH@>
<@include Shadows_shared.slh@>
layout(std140) uniform shadowTransformBuffer {
ShadowParameters shadow;
};
int getShadowCascadeCount() {
return shadow.cascadeCount;
}
float getShadowCascadeInvBlendWidth() {
return shadow.invCascadeBlendWidth;
}
float evalShadowFalloff(float depth) {
return clamp((shadow.maxDistance-depth) * shadow.invFalloffDistance, 0.0, 1.0);
}
mat4 getShadowReprojection(int cascadeIndex) {
return shadow.cascades[cascadeIndex].reprojection;
}
float getShadowScale() {
return shadow.invMapSize;
}
float getShadowBias(int cascadeIndex) {
return shadow.cascades[cascadeIndex].bias;
}
vec3 getShadowDirInViewSpace() {
return shadow.lightDirInViewSpace;
}
// Compute the texture coordinates from world coordinates
vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) {
vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position;
return vec4(shadowCoord.xyz, 1.0);
}
bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) {
bvec2 greaterThanZero = greaterThanEqual(cascadeTexCoords.xy, vec2(0));
bvec2 lessThanOne = lessThanEqual(cascadeTexCoords.xy, vec2(1));
return all(greaterThanZero) && all(lessThanOne);
}
int getFirstShadowCascadeOnPixel(int startCascadeIndex, vec4 worldPosition, out vec4 cascadeShadowCoords) {
int cascadeIndex;
startCascadeIndex = min(startCascadeIndex, getShadowCascadeCount()-1);
for (cascadeIndex=startCascadeIndex ; cascadeIndex<getShadowCascadeCount() ; cascadeIndex++) {
cascadeShadowCoords = evalShadowTexcoord(cascadeIndex, worldPosition);
if (isShadowCascadeProjectedOnPixel(cascadeShadowCoords)) {
return cascadeIndex;
}
}
return cascadeIndex;
}
float evalShadowCascadeWeight(vec4 cascadeTexCoords) {
// Inspired by DirectX CascadeShadowMaps11 example
vec2 distanceToOne = vec2(1.0) - cascadeTexCoords.xy;
float blend1 = min( cascadeTexCoords.x, cascadeTexCoords.y );
float blend2 = min( distanceToOne.x, distanceToOne.y );
float blend = min( blend1, blend2 );
return clamp(blend * getShadowCascadeInvBlendWidth(), 0.0, 1.0);
}
float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out vec4 cascadeShadowCoords[2], out ivec2 cascadeIndices) {
cascadeIndices.x = getFirstShadowCascadeOnPixel(0, worldPosition, cascadeShadowCoords[0]);
cascadeIndices.y = cascadeIndices.x+1;
if (cascadeIndices.x < (getShadowCascadeCount()-1)) {
cascadeIndices.y = getFirstShadowCascadeOnPixel(cascadeIndices.y, worldPosition, cascadeShadowCoords[1]);
float firstCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[0]);
float secondCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[1]);
// Returns the mix amount between first and second cascade.
return ((1.0-firstCascadeWeight) * secondCascadeWeight) / (firstCascadeWeight + secondCascadeWeight);
} else {
return 0.0;
}
}
<@endif@>

View file

@ -0,0 +1,34 @@
// glsl / C++ compatible source as interface for Shadows
#ifdef __cplusplus
# define MAT4 glm::mat4
# define VEC3 glm::vec3
#else
# define MAT4 mat4
# define VEC3 vec3
#endif
#define SHADOW_CASCADE_MAX_COUNT 4
struct ShadowTransform {
MAT4 reprojection;
float bias;
float _padding1;
float _padding2;
float _padding3;
};
struct ShadowParameters {
ShadowTransform cascades[SHADOW_CASCADE_MAX_COUNT];
VEC3 lightDirInViewSpace;
int cascadeCount;
float invMapSize;
float invCascadeBlendWidth;
float maxDistance;
float invFalloffDistance;
};
// <@if 1@>
// Trigger Scribe include
// <@endif@> <!def that !>
//

View file

@ -16,7 +16,6 @@
<@include gpu/Color.slh@>
<$declareColorWheel()$>
uniform sampler2D linearDepthMap;
uniform sampler2D halfLinearDepthMap;
uniform sampler2D halfNormalMap;
@ -24,6 +23,8 @@ uniform sampler2D occlusionMap;
uniform sampler2D occlusionBlurredMap;
uniform sampler2D scatteringMap;
<@include ShadowCore.slh@>
<$declareDeferredCurvature()$>
float curvatureAO(float k) {

Some files were not shown because too many files have changed in this diff Show more