3
0
Fork 0
mirror of https://github.com/JulianGro/overte.git synced 2025-04-30 09:43:08 +02:00

Merge remote-tracking branch 'origin/master' into character_entity_fixes

updating branch to master
This commit is contained in:
amantley 2017-12-13 16:47:15 -08:00
commit 7c29a1b09a
116 changed files with 5040 additions and 1581 deletions
assignment-client/src/avatars
interface
libraries

View file

@ -33,11 +33,6 @@
#include "AvatarMixerClientData.h"
#include "AvatarMixerSlave.h"
namespace PrioritySortUtil {
// we declare this callback here but override it later
std::function<uint64_t(const AvatarSharedPointer&)> getAvatarAgeCallback = [] (const AvatarSharedPointer& avatar) { return 0; };
}
void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
_begin = begin;
_end = end;
@ -191,6 +186,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes
std::vector<AvatarSharedPointer> avatarsToSort;
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
std::unordered_map<QUuid, uint64_t> avatarEncodeTimes;
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
// make sure this is an agent that we have avatar data for before considering it for inclusion
if (otherNode->getType() == NodeType::Agent
@ -200,34 +196,29 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
avatarsToSort.push_back(otherAvatar);
avatarDataToNodes[otherAvatar] = otherNode;
QUuid id = otherAvatar->getSessionUUID();
avatarEncodeTimes[id] = nodeData->getLastOtherAvatarEncodeTime(id);
}
});
// now that we've assembled the avatarDataToNodes map we can replace PrioritySortUtil::getAvatarAgeCallback
// with the true implementation
PrioritySortUtil::getAvatarAgeCallback = [&] (const AvatarSharedPointer& avatar) {
auto avatarNode = avatarDataToNodes[avatar];
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
return nodeData->getLastOtherAvatarEncodeTime(avatarNode->getUUID());
};
class SortableAvatar: public PrioritySortUtil::Sortable {
public:
SortableAvatar() = delete;
SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {}
SortableAvatar(const AvatarSharedPointer& avatar, uint64_t lastEncodeTime)
: _avatar(avatar), _lastEncodeTime(lastEncodeTime) {}
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
float getRadius() const override {
glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale());
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
}
uint64_t getTimestamp() const override {
// use the callback implemented above
return PrioritySortUtil::getAvatarAgeCallback(_avatar);
return _lastEncodeTime;
}
const AvatarSharedPointer& getAvatar() const { return _avatar; }
private:
AvatarSharedPointer _avatar;
uint64_t _lastEncodeTime;
};
// prepare to sort
@ -322,7 +313,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
if (!shouldIgnore) {
// sort this one for later
sortedAvatars.push(SortableAvatar(avatar));
uint64_t lastEncodeTime = 0;
std::unordered_map<QUuid, uint64_t>::const_iterator itr = avatarEncodeTimes.find(avatar->getSessionUUID());
if (itr != avatarEncodeTimes.end()) {
lastEncodeTime = itr->second;
}
sortedAvatars.push(SortableAvatar(avatar, lastEncodeTime));
}
}

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

@ -905,7 +905,7 @@ Rectangle {
}
buyTextContainer.color = "#FFC3CD";
buyTextContainer.border.color = "#F3808F";
buyGlyph.text = hifi.glyphs.error;
buyGlyph.text = hifi.glyphs.alert;
buyGlyph.size = 54;
} else {
if (root.alreadyOwned) {

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

@ -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

@ -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

@ -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

@ -18,7 +18,9 @@ GameplayObjects::GameplayObjects() {
bool GameplayObjects::addToGameplayObjects(const QUuid& avatarID) {
containsData = true;
_avatarIDs.push_back(avatarID);
if (std::find(_avatarIDs.begin(), _avatarIDs.end(), avatarID) == _avatarIDs.end()) {
_avatarIDs.push_back(avatarID);
}
return true;
}
bool GameplayObjects::removeFromGameplayObjects(const QUuid& avatarID) {
@ -28,7 +30,9 @@ bool GameplayObjects::removeFromGameplayObjects(const QUuid& avatarID) {
bool GameplayObjects::addToGameplayObjects(const EntityItemID& entityID) {
containsData = true;
_entityIDs.push_back(entityID);
if (std::find(_entityIDs.begin(), _entityIDs.end(), entityID) == _entityIDs.end()) {
_entityIDs.push_back(entityID);
}
return true;
}
bool GameplayObjects::removeFromGameplayObjects(const EntityItemID& entityID) {
@ -38,7 +42,9 @@ bool GameplayObjects::removeFromGameplayObjects(const EntityItemID& entityID) {
bool GameplayObjects::addToGameplayObjects(const OverlayID& overlayID) {
containsData = true;
_overlayIDs.push_back(overlayID);
if (std::find(_overlayIDs.begin(), _overlayIDs.end(), overlayID) == _overlayIDs.end()) {
_overlayIDs.push_back(overlayID);
}
return true;
}
bool GameplayObjects::removeFromGameplayObjects(const OverlayID& overlayID) {
@ -72,28 +78,125 @@ bool SelectionScriptingInterface::removeFromSelectedItemsList(const QString& lis
}
bool SelectionScriptingInterface::clearSelectedItemsList(const QString& listName) {
_selectedItemsListMap.insert(listName, GameplayObjects());
emit selectedItemsListChanged(listName);
{
QWriteLocker lock(&_selectionListsLock);
_selectedItemsListMap.insert(listName, GameplayObjects());
}
onSelectedItemsListChanged(listName);
return true;
}
QStringList SelectionScriptingInterface::getListNames() const {
QStringList list;
QReadLocker lock(&_selectionListsLock);
list = _selectedItemsListMap.keys();
return list;
}
QStringList SelectionScriptingInterface::getHighlightedListNames() const {
QStringList list;
QReadLocker lock(&_highlightStylesLock);
list = _highlightStyleMap.keys();
return list;
}
bool SelectionScriptingInterface::enableListHighlight(const QString& listName, const QVariantMap& highlightStyleValues) {
QWriteLocker lock(&_highlightStylesLock);
auto highlightStyle = _highlightStyleMap.find(listName);
if (highlightStyle == _highlightStyleMap.end()) {
highlightStyle = _highlightStyleMap.insert(listName, SelectionHighlightStyle());
}
if (!(*highlightStyle).isBoundToList()) {
setupHandler(listName);
(*highlightStyle).setBoundToList(true);
}
(*highlightStyle).fromVariantMap(highlightStyleValues);
auto mainScene = qApp->getMain3DScene();
if (mainScene) {
render::Transaction transaction;
transaction.resetSelectionHighlight(listName.toStdString(), (*highlightStyle).getStyle());
mainScene->enqueueTransaction(transaction);
}
else {
qWarning() << "SelectionToSceneHandler::highlightStyleChanged(), Unexpected null scene, possibly during application shutdown";
}
return true;
}
bool SelectionScriptingInterface::disableListHighlight(const QString& listName) {
QWriteLocker lock(&_highlightStylesLock);
auto highlightStyle = _highlightStyleMap.find(listName);
if (highlightStyle != _highlightStyleMap.end()) {
if ((*highlightStyle).isBoundToList()) {
}
_highlightStyleMap.erase(highlightStyle);
auto mainScene = qApp->getMain3DScene();
if (mainScene) {
render::Transaction transaction;
transaction.removeHighlightFromSelection(listName.toStdString());
mainScene->enqueueTransaction(transaction);
}
else {
qWarning() << "SelectionToSceneHandler::highlightStyleChanged(), Unexpected null scene, possibly during application shutdown";
}
}
return true;
}
QVariantMap SelectionScriptingInterface::getListHighlightStyle(const QString& listName) const {
QReadLocker lock(&_highlightStylesLock);
auto highlightStyle = _highlightStyleMap.find(listName);
if (highlightStyle == _highlightStyleMap.end()) {
return QVariantMap();
} else {
return (*highlightStyle).toVariantMap();
}
}
render::HighlightStyle SelectionScriptingInterface::getHighlightStyle(const QString& listName) const {
QReadLocker lock(&_highlightStylesLock);
auto highlightStyle = _highlightStyleMap.find(listName);
if (highlightStyle == _highlightStyleMap.end()) {
return render::HighlightStyle();
} else {
return (*highlightStyle).getStyle();
}
}
template <class T> bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) {
GameplayObjects currentList = _selectedItemsListMap.value(listName);
currentList.addToGameplayObjects(idToAdd);
_selectedItemsListMap.insert(listName, currentList);
emit selectedItemsListChanged(listName);
{
QWriteLocker lock(&_selectionListsLock);
GameplayObjects currentList = _selectedItemsListMap.value(listName);
currentList.addToGameplayObjects(idToAdd);
_selectedItemsListMap.insert(listName, currentList);
}
onSelectedItemsListChanged(listName);
return true;
}
template <class T> bool SelectionScriptingInterface::removeFromGameplayObjects(const QString& listName, T idToRemove) {
GameplayObjects currentList = _selectedItemsListMap.value(listName);
if (currentList.getContainsData()) {
currentList.removeFromGameplayObjects(idToRemove);
_selectedItemsListMap.insert(listName, currentList);
emit selectedItemsListChanged(listName);
bool listExist = false;
{
QWriteLocker lock(&_selectionListsLock);
auto currentList = _selectedItemsListMap.find(listName);
if (currentList != _selectedItemsListMap.end()) {
listExist = true;
(*currentList).removeFromGameplayObjects(idToRemove);
}
}
if (listExist) {
onSelectedItemsListChanged(listName);
return true;
} else {
}
else {
return false;
}
}
@ -102,50 +205,123 @@ template <class T> bool SelectionScriptingInterface::removeFromGameplayObjects(c
//
GameplayObjects SelectionScriptingInterface::getList(const QString& listName) {
QReadLocker lock(&_selectionListsLock);
return _selectedItemsListMap.value(listName);
}
void SelectionScriptingInterface::printList(const QString& listName) {
GameplayObjects currentList = _selectedItemsListMap.value(listName);
if (currentList.getContainsData()) {
QReadLocker lock(&_selectionListsLock);
auto currentList = _selectedItemsListMap.find(listName);
if (currentList != _selectedItemsListMap.end()) {
if ((*currentList).getContainsData()) {
qDebug() << "Avatar IDs:";
for (auto i : currentList.getAvatarIDs()) {
qDebug() << i << ';';
}
qDebug() << "";
qDebug() << "List named " << listName << ":";
qDebug() << "Avatar IDs:";
for (auto i : (*currentList).getAvatarIDs()) {
qDebug() << i << ';';
}
qDebug() << "";
qDebug() << "Entity IDs:";
for (auto j : currentList.getEntityIDs()) {
qDebug() << j << ';';
}
qDebug() << "";
qDebug() << "Entity IDs:";
for (auto j : (*currentList).getEntityIDs()) {
qDebug() << j << ';';
}
qDebug() << "";
qDebug() << "Overlay IDs:";
for (auto k : currentList.getOverlayIDs()) {
qDebug() << k << ';';
qDebug() << "Overlay IDs:";
for (auto k : (*currentList).getOverlayIDs()) {
qDebug() << k << ';';
}
qDebug() << "";
}
else {
qDebug() << "List named " << listName << " empty";
}
qDebug() << "";
} else {
qDebug() << "List named" << listName << "doesn't exist.";
qDebug() << "List named " << listName << " doesn't exist.";
}
}
QVariantMap SelectionScriptingInterface::getSelectedItemsList(const QString& listName) const {
QReadLocker lock(&_selectionListsLock);
QVariantMap list;
auto currentList = _selectedItemsListMap.find(listName);
if (currentList != _selectedItemsListMap.end()) {
QList<QVariant> avatarIDs;
QList<QVariant> entityIDs;
QList<QVariant> overlayIDs;
if ((*currentList).getContainsData()) {
if (!(*currentList).getAvatarIDs().empty()) {
for (auto j : (*currentList).getAvatarIDs()) {
avatarIDs.push_back((QUuid)j);
}
}
if (!(*currentList).getEntityIDs().empty()) {
for (auto j : (*currentList).getEntityIDs()) {
entityIDs.push_back((QUuid)j );
}
}
if (!(*currentList).getOverlayIDs().empty()) {
for (auto j : (*currentList).getOverlayIDs()) {
overlayIDs.push_back((QUuid)j);
}
}
}
list["avatars"] = (avatarIDs);
list["entities"] = (entityIDs);
list["overlays"] = (overlayIDs);
return list;
}
else {
return list;
}
}
bool SelectionScriptingInterface::removeListFromMap(const QString& listName) {
if (_selectedItemsListMap.remove(listName)) {
emit selectedItemsListChanged(listName);
bool removed = false;
{
QWriteLocker lock(&_selectionListsLock);
removed = _selectedItemsListMap.remove(listName);
}
if (removed) {
onSelectedItemsListChanged(listName);
return true;
} else {
return false;
}
}
void SelectionScriptingInterface::setupHandler(const QString& selectionName) {
QWriteLocker lock(&_selectionHandlersLock);
auto handler = _handlerMap.find(selectionName);
if (handler == _handlerMap.end()) {
handler = _handlerMap.insert(selectionName, new SelectionToSceneHandler());
}
(*handler)->initialize(selectionName);
}
void SelectionScriptingInterface::onSelectedItemsListChanged(const QString& listName) {
{
QWriteLocker lock(&_selectionHandlersLock);
auto handler = _handlerMap.find(listName);
if (handler != _handlerMap.end()) {
(*handler)->updateSceneFromSelectedList();
}
}
emit selectedItemsListChanged(listName);
}
SelectionToSceneHandler::SelectionToSceneHandler() {
}
void SelectionToSceneHandler::initialize(const QString& listName) {
_listName = listName;
updateSceneFromSelectedList();
}
void SelectionToSceneHandler::selectedItemsListChanged(const QString& listName) {
@ -199,3 +375,85 @@ void SelectionToSceneHandler::updateSceneFromSelectedList() {
qWarning() << "SelectionToSceneHandler::updateRendererSelectedList(), Unexpected null scene, possibly during application shutdown";
}
}
bool SelectionHighlightStyle::fromVariantMap(const QVariantMap& properties) {
auto colorVariant = properties["outlineUnoccludedColor"];
if (colorVariant.isValid()) {
bool isValid;
auto color = xColorFromVariant(colorVariant, isValid);
if (isValid) {
_style._outlineUnoccluded.color = toGlm(color);
}
}
colorVariant = properties["outlineOccludedColor"];
if (colorVariant.isValid()) {
bool isValid;
auto color = xColorFromVariant(colorVariant, isValid);
if (isValid) {
_style._outlineOccluded.color = toGlm(color);
}
}
colorVariant = properties["fillUnoccludedColor"];
if (colorVariant.isValid()) {
bool isValid;
auto color = xColorFromVariant(colorVariant, isValid);
if (isValid) {
_style._fillUnoccluded.color = toGlm(color);
}
}
colorVariant = properties["fillOccludedColor"];
if (colorVariant.isValid()) {
bool isValid;
auto color = xColorFromVariant(colorVariant, isValid);
if (isValid) {
_style._fillOccluded.color = toGlm(color);
}
}
auto intensityVariant = properties["outlineUnoccludedAlpha"];
if (intensityVariant.isValid()) {
_style._outlineUnoccluded.alpha = intensityVariant.toFloat();
}
intensityVariant = properties["outlineOccludedAlpha"];
if (intensityVariant.isValid()) {
_style._outlineOccluded.alpha = intensityVariant.toFloat();
}
intensityVariant = properties["fillUnoccludedAlpha"];
if (intensityVariant.isValid()) {
_style._fillUnoccluded.alpha = intensityVariant.toFloat();
}
intensityVariant = properties["fillOccludedAlpha"];
if (intensityVariant.isValid()) {
_style._fillOccluded.alpha = intensityVariant.toFloat();
}
auto outlineWidth = properties["outlineWidth"];
if (outlineWidth.isValid()) {
_style._outlineWidth = outlineWidth.toFloat();
}
auto isOutlineSmooth = properties["isOutlineSmooth"];
if (isOutlineSmooth.isValid()) {
_style._isOutlineSmooth = isOutlineSmooth.toBool();
}
return true;
}
QVariantMap SelectionHighlightStyle::toVariantMap() const {
QVariantMap properties;
properties["outlineUnoccludedColor"] = xColorToVariant(xColorFromGlm(_style._outlineUnoccluded.color));
properties["outlineOccludedColor"] = xColorToVariant(xColorFromGlm(_style._outlineOccluded.color));
properties["fillUnoccludedColor"] = xColorToVariant(xColorFromGlm(_style._fillUnoccluded.color));
properties["fillOccludedColor"] = xColorToVariant(xColorFromGlm(_style._fillOccluded.color));
properties["outlineUnoccludedAlpha"] = _style._outlineUnoccluded.alpha;
properties["outlineOccludedAlpha"] = _style._outlineOccluded.alpha;
properties["fillUnoccludedAlpha"] = _style._fillUnoccluded.alpha;
properties["fillOccludedAlpha"] = _style._fillOccluded.alpha;
properties["outlineWidth"] = _style._outlineWidth;
properties["isOutlineSmooth"] = _style._isOutlineSmooth;
return properties;
}

View file

@ -21,22 +21,23 @@
#include "RenderableEntityItem.h"
#include "ui/overlays/Overlay.h"
#include <avatar/AvatarManager.h>
#include <render/HighlightStyle.h>
class GameplayObjects {
public:
GameplayObjects();
bool getContainsData() { return containsData; }
bool getContainsData() const { return containsData; }
std::vector<QUuid> getAvatarIDs() { return _avatarIDs; }
std::vector<QUuid> getAvatarIDs() const { return _avatarIDs; }
bool addToGameplayObjects(const QUuid& avatarID);
bool removeFromGameplayObjects(const QUuid& avatarID);
std::vector<EntityItemID> getEntityIDs() { return _entityIDs; }
std::vector<EntityItemID> getEntityIDs() const { return _entityIDs; }
bool addToGameplayObjects(const EntityItemID& entityID);
bool removeFromGameplayObjects(const EntityItemID& entityID);
std::vector<OverlayID> getOverlayIDs() { return _overlayIDs; }
std::vector<OverlayID> getOverlayIDs() const { return _overlayIDs; }
bool addToGameplayObjects(const OverlayID& overlayID);
bool removeFromGameplayObjects(const OverlayID& overlayID);
@ -48,20 +49,52 @@ private:
};
class SelectionToSceneHandler : public QObject {
Q_OBJECT
public:
SelectionToSceneHandler();
void initialize(const QString& listName);
void updateSceneFromSelectedList();
public slots:
void selectedItemsListChanged(const QString& listName);
private:
QString _listName{ "" };
};
using SelectionToSceneHandlerPointer = QSharedPointer<SelectionToSceneHandler>;
class SelectionHighlightStyle {
public:
SelectionHighlightStyle() {}
void setBoundToList(bool bound) { _isBoundToList = bound; }
bool isBoundToList() const { return _isBoundToList; }
bool fromVariantMap(const QVariantMap& properties);
QVariantMap toVariantMap() const;
render::HighlightStyle getStyle() const { return _style; }
protected:
bool _isBoundToList{ false };
render::HighlightStyle _style;
};
class SelectionScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
SelectionScriptingInterface();
GameplayObjects getList(const QString& listName);
/**jsdoc
* Prints out the list of avatars, entities and overlays stored in a particular selection.
* @function Selection.printList
* @param listName {string} name of the selection
* Query the names of all the selection lists
* @function Selection.getListNames
* @return An array of names of all the selection lists
*/
Q_INVOKABLE void printList(const QString& listName);
Q_INVOKABLE QStringList getListNames() const;
/**jsdoc
* Removes a named selection from the list of selections.
* @function Selection.removeListFromMap
@ -96,30 +129,103 @@ public:
*/
Q_INVOKABLE bool clearSelectedItemsList(const QString& listName);
/**jsdoc
* Prints out the list of avatars, entities and overlays stored in a particular selection.
* @function Selection.printList
* @param listName {string} name of the selection
*/
Q_INVOKABLE void printList(const QString& listName);
/**jsdoc
* Query the list of avatars, entities and overlays stored in a particular selection.
* @function Selection.getList
* @param listName {string} name of the selection
* @return a js object describing the content of a selection list with the following properties:
* - "entities": [ and array of the entityID of the entities in the selection]
* - "avatars": [ and array of the avatarID of the avatars in the selection]
* - "overlays": [ and array of the overlayID of the overlays in the selection]
* If the list name doesn't exist, the function returns an empty js object with no properties.
*/
Q_INVOKABLE QVariantMap getSelectedItemsList(const QString& listName) const;
/**jsdoc
* Query the names of the highlighted selection lists
* @function Selection.getHighlightedListNames
* @return An array of names of the selection list currently highlight enabled
*/
Q_INVOKABLE QStringList getHighlightedListNames() const;
/**jsdoc
* Enable highlighting for the named selection.
* If the Selection doesn't exist, it will be created.
* All objects in the list will be displayed with the highlight effect as specified from the highlightStyle.
* The function can be called several times with different values in the style to modify it.
*
* @function Selection.enableListHighlight
* @param listName {string} name of the selection
* @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle).
* @returns {bool} true if the selection was successfully enabled for highlight.
*/
Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle);
/**jsdoc
* Disable highlighting for the named selection.
* If the Selection doesn't exist or wasn't enabled for highliting then nothing happens simply returning false.
*
* @function Selection.disableListHighlight
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully disabled for highlight, false otherwise.
*/
Q_INVOKABLE bool disableListHighlight(const QString& listName);
/**jsdoc
* Query the highlight style values for the named selection.
* If the Selection doesn't exist or hasn't been highlight enabled yet, it will return an empty object.
* Otherwise, the jsObject describes the highlight style properties:
* - outlineUnoccludedColor: {xColor} Color of the specified highlight region
* - outlineOccludedColor: {xColor} "
* - fillUnoccludedColor: {xColor} "
* - fillOccludedColor: {xColor} "
*
* - outlineUnoccludedAlpha: {float} Alpha value ranging from 0.0 (not visible) to 1.0 (fully opaque) for the specified highlight region
* - outlineOccludedAlpha: {float} "
* - fillUnoccludedAlpha: {float} "
* - fillOccludedAlpha: {float} "
*
* - outlineWidth: {float} width of the outline expressed in pixels
* - isOutlineSmooth: {bool} true to enable oultine smooth falloff
*
* @function Selection.getListHighlightStyle
* @param listName {string} name of the selection
* @returns {jsObject} highlight style as described above
*/
Q_INVOKABLE QVariantMap getListHighlightStyle(const QString& listName) const;
GameplayObjects getList(const QString& listName);
render::HighlightStyle getHighlightStyle(const QString& listName) const;
void onSelectedItemsListChanged(const QString& listName);
signals:
void selectedItemsListChanged(const QString& listName);
private:
mutable QReadWriteLock _selectionListsLock;
QMap<QString, GameplayObjects> _selectedItemsListMap;
mutable QReadWriteLock _selectionHandlersLock;
QMap<QString, SelectionToSceneHandler*> _handlerMap;
mutable QReadWriteLock _highlightStylesLock;
QMap<QString, SelectionHighlightStyle> _highlightStyleMap;
template <class T> bool addToGameplayObjects(const QString& listName, T idToAdd);
template <class T> bool removeFromGameplayObjects(const QString& listName, T idToRemove);
};
void setupHandler(const QString& selectionName);
class SelectionToSceneHandler : public QObject {
Q_OBJECT
public:
SelectionToSceneHandler();
void initialize(const QString& listName);
void updateSceneFromSelectedList();
public slots:
void selectedItemsListChanged(const QString& listName);
private:
QString _listName { "" };
};
#endif // hifi_SelectionScriptingInterface_h

View file

@ -23,30 +23,7 @@ void WalletScriptingInterface::refreshWalletStatus() {
wallet->getWalletStatus();
}
static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml";
void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "buy", Q_ARG(const QString&, name), Q_ARG(const QString&, id), Q_ARG(const int&, price), Q_ARG(const QString&, href));
return;
}
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
tablet->loadQMLSource(CHECKOUT_QML_PATH);
DependencyManager::get<HMDScriptingInterface>()->openTablet();
QQuickItem* root = nullptr;
if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !qApp->isHMDMode())) {
root = DependencyManager::get<OffscreenUi>()->getRootItem();
} else {
root = tablet->getTabletRoot();
}
CheckoutProxy* checkout = new CheckoutProxy(root->findChild<QObject*>("checkout"));
// Example: Wallet.buy("Test Flaregun", "0d90d21c-ce7a-4990-ad18-e9d2cf991027", 17, "http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json");
checkout->writeProperty("itemName", name);
checkout->writeProperty("itemId", id);
checkout->writeProperty("itemPrice", price);
checkout->writeProperty("itemHref", href);
void WalletScriptingInterface::setWalletStatus(const uint& status) {
_walletStatus = status;
emit DependencyManager::get<Wallet>()->walletStatusResult(status);
}

View file

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

@ -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

@ -35,6 +35,8 @@ public:
// getters
virtual bool is3D() const override { return true; }
virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); }
// TODO: consider implementing registration points in this class
glm::vec3 getCenter() const { return getWorldPosition(); }
@ -59,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

@ -72,14 +72,7 @@ ContextOverlayInterface::ContextOverlayInterface() {
connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay);
{
render::Transaction transaction;
initializeSelectionToSceneHandler(_selectionToSceneHandlers[0], "contextOverlayHighlightList", transaction);
for (auto i = 1; i < MAX_SELECTION_COUNT; i++) {
auto selectionName = QString("highlightList") + QString::number(i);
initializeSelectionToSceneHandler(_selectionToSceneHandlers[i], selectionName, transaction);
}
const render::ScenePointer& scene = qApp->getMain3DScene();
scene->enqueueTransaction(transaction);
_selectionScriptingInterface->enableListHighlight("contextOverlayHighlightList", QVariantMap());
}
auto nodeList = DependencyManager::get<NodeList>();
@ -88,12 +81,6 @@ ContextOverlayInterface::ContextOverlayInterface() {
_challengeOwnershipTimeoutTimer.setSingleShot(true);
}
void ContextOverlayInterface::initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction) {
handler.initialize(selectionName);
connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &handler, &SelectionToSceneHandler::selectedItemsListChanged);
transaction.resetSelectionHighlight(selectionName.toStdString());
}
static const xColor CONTEXT_OVERLAY_COLOR = { 255, 255, 255 };
static const float CONTEXT_OVERLAY_INSIDE_DISTANCE = 1.0f; // in meters
static const float CONTEXT_OVERLAY_SIZE = 0.09f; // in meters, same x and y dims

View file

@ -96,9 +96,6 @@ private:
void disableEntityHighlight(const EntityItemID& entityItemID);
void deletingEntity(const EntityItemID& entityItemID);
void initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction);
SelectionToSceneHandler _selectionToSceneHandlers[MAX_SELECTION_COUNT];
Q_INVOKABLE void startChallengeOwnershipTimer();
QTimer _challengeOwnershipTimeoutTimer;

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

@ -79,6 +79,12 @@ void ModelOverlay::update(float deltatime) {
if (_model->needsFixupInScene()) {
_model->removeFromScene(scene, transaction);
_model->addToScene(scene, transaction);
auto newRenderItemIDs{ _model->fetchRenderItemIDs() };
transaction.updateItem<Overlay>(getRenderItemID(), [newRenderItemIDs](Overlay& data) {
auto modelOverlay = static_cast<ModelOverlay*>(&data);
modelOverlay->setSubRenderItemIDs(newRenderItemIDs);
});
}
if (_visibleDirty) {
_visibleDirty = false;
@ -104,6 +110,10 @@ bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePoint
void ModelOverlay::removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) {
Volume3DOverlay::removeFromScene(overlay, scene, transaction);
_model->removeFromScene(scene, transaction);
transaction.updateItem<Overlay>(getRenderItemID(), [](Overlay& data) {
auto modelOverlay = static_cast<ModelOverlay*>(&data);
modelOverlay->clearSubRenderItemIDs();
});
}
void ModelOverlay::setVisible(bool visible) {
@ -142,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;
}
@ -171,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)) {
@ -266,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();
@ -529,3 +612,19 @@ void ModelOverlay::copyAnimationJointDataToModel(QVector<JointData> jointsData)
_updateModel = true;
}
void ModelOverlay::clearSubRenderItemIDs() {
_subRenderItemIDs.clear();
}
void ModelOverlay::setSubRenderItemIDs(const render::ItemIDs& ids) {
_subRenderItemIDs = ids;
}
uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const {
if (_model) {
auto metaSubItems = _subRenderItemIDs;
subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end());
return (uint32_t)metaSubItems.size();
}
return 0;
}

View file

@ -30,6 +30,12 @@ public:
virtual void update(float deltatime) override;
virtual void render(RenderArgs* args) override {};
virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override;
void clearSubRenderItemIDs();
void setSubRenderItemIDs(const render::ItemIDs& ids);
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
@ -74,6 +80,8 @@ private:
ModelPointer _model;
QVariantMap _modelTextures;
render::ItemIDs _subRenderItemIDs;
QUrl _url;
bool _updateModel { false };
bool _scaleToFit { false };

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

@ -53,6 +53,8 @@ public:
virtual const render::ShapeKey getShapeKey() { return render::ShapeKey::Builder::ownPipeline(); }
virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const { return 0; }
// getters
virtual QString getType() const = 0;
virtual bool is3D() const = 0;
@ -120,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
};
@ -130,6 +135,7 @@ namespace render {
template <> int payloadGetLayer(const Overlay::Pointer& overlay);
template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args);
template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay);
template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems);
}
Q_DECLARE_METATYPE(OverlayID);

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

@ -26,6 +26,8 @@ public:
virtual bool is3D() const override { return false; }
virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return 1; }
// getters
int getX() const { return _bounds.x(); }
int getY() const { return _bounds.y(); }

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

@ -87,4 +87,10 @@ namespace render {
template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay) {
return overlay->getShapeKey();
}
template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems) {
return overlay->fetchMetaSubItems(subItems);
}
}

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

@ -113,8 +113,15 @@ void TextureBaker::handleTextureNetworkReply() {
}
void TextureBaker::processTexture() {
auto processedTexture = image::processImage(_originalTexture, _textureURL.toString().toStdString(),
// the baked textures need to have the source hash added for cache checks in Interface
// so we add that to the processed texture before handling it off to be serialized
auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5);
std::string hash = hashData.toHex().toStdString();
// IMPORTANT: _originalTexture is empty past this point
auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(),
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing);
processedTexture->setSourceHash(hash);
if (shouldStop()) {
return;
@ -125,11 +132,6 @@ void TextureBaker::processTexture() {
return;
}
// the baked textures need to have the source hash added for cache checks in Interface
// so we add that to the processed texture before handling it off to be serialized
auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5);
std::string hash = hashData.toHex().toStdString();
processedTexture->setSourceHash(hash);
auto memKTX = gpu::Texture::serialize(*processedTexture);

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

@ -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

@ -118,64 +118,64 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con
}
}
gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, true, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing);
}
gpu::TexturePointer TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureNormalMapFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureNormalMapFromImage(srcImage, srcImageName, true, abortProcessing);
return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, true, abortProcessing);
}
gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureGrayscaleFromImage(srcImage, srcImageName, true, abortProcessing);
return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, true, abortProcessing);
}
gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createCubeTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return processCubeTextureColorFromImage(srcImage, srcImageName, true, abortProcessing);
return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing);
}
gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return processCubeTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
@ -246,33 +246,43 @@ uint32 packR11G11B10F(const glm::vec3& color) {
return glm::packF2x11_1x10(ucolor);
}
gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename,
int maxNumPixels, TextureUsage::Type textureType,
const std::atomic<bool>& abortProcessing) {
QImage processRawImageData(QByteArray&& content, const std::string& filename) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QByteArray localCopy = std::move(content);
// Help the QImage loader by extracting the image file format from the url filename ext.
// Some tga are not created properly without it.
auto filenameExtension = filename.substr(filename.find_last_of('.') + 1);
QBuffer buffer;
buffer.setData(content);
buffer.setData(localCopy);
QImageReader imageReader(&buffer, filenameExtension.c_str());
QImage image;
if (imageReader.canRead()) {
image = imageReader.read();
return imageReader.read();
} else {
// Extension could be incorrect, try to detect the format from the content
QImageReader newImageReader;
newImageReader.setDecideFormatFromContent(true);
buffer.setData(content);
buffer.setData(localCopy);
newImageReader.setDevice(&buffer);
if (newImageReader.canRead()) {
qCWarning(imagelogging) << "Image file" << filename.c_str() << "has extension" << filenameExtension.c_str()
<< "but is actually a" << qPrintable(newImageReader.format()) << "(recovering)";
image = newImageReader.read();
return newImageReader.read();
}
}
return QImage();
}
gpu::TexturePointer processImage(QByteArray&& content, const std::string& filename,
int maxNumPixels, TextureUsage::Type textureType,
const std::atomic<bool>& abortProcessing) {
QImage image = processRawImageData(std::move(content), filename);
int imageWidth = image.width();
int imageHeight = image.height();
@ -290,22 +300,26 @@ gpu::TexturePointer processImage(const QByteArray& content, const std::string& f
int originalHeight = imageHeight;
imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f);
imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f);
QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
image.swap(newImage);
image = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
qCDebug(imagelogging).nospace() << "Downscaled " << filename.c_str() << " (" <<
QSize(originalWidth, originalHeight) << " to " <<
QSize(imageWidth, imageHeight) << ")";
}
auto loader = TextureUsage::getTextureLoaderForType(textureType);
auto texture = loader(image, filename, abortProcessing);
auto texture = loader(std::move(image), filename, abortProcessing);
return texture;
}
QImage processSourceImage(const QImage& srcImage, bool cubemap) {
QImage processSourceImage(QImage&& srcImage, bool cubemap) {
PROFILE_RANGE(resource_parse, "processSourceImage");
const glm::uvec2 srcImageSize = toGlm(srcImage.size());
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(srcImage);
const glm::uvec2 srcImageSize = toGlm(localCopy.size());
glm::uvec2 targetSize = srcImageSize;
while (glm::any(glm::greaterThan(targetSize, MAX_TEXTURE_SIZE))) {
@ -327,10 +341,10 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) {
if (targetSize != srcImageSize) {
PROFILE_RANGE(resource_parse, "processSourceImage Rectify");
qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y;
return srcImage.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
return localCopy.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
return srcImage;
return localCopy;
}
#if defined(NVTT_API)
@ -437,10 +451,14 @@ public:
}
};
void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atomic<bool>& abortProcessing, int face) {
assert(image.format() == QIMAGE_HDR_FORMAT);
void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& abortProcessing, int face) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(image);
const int width = image.width(), height = image.height();
assert(localCopy.format() == QIMAGE_HDR_FORMAT);
const int width = localCopy.width(), height = localCopy.height();
std::vector<glm::vec4> data;
std::vector<glm::vec4>::iterator dataIt;
auto mipFormat = texture->getStoredMipFormat();
@ -479,10 +497,10 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom
return;
}
data.resize(width*height);
data.resize(width * height);
dataIt = data.begin();
for (auto lineNb = 0; lineNb < height; lineNb++) {
const uint32* srcPixelIt = reinterpret_cast<const uint32*>( image.constScanLine(lineNb) );
const uint32* srcPixelIt = reinterpret_cast<const uint32*>(localCopy.constScanLine(lineNb));
const uint32* srcPixelEnd = srcPixelIt + width;
while (srcPixelIt < srcPixelEnd) {
@ -493,6 +511,9 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom
}
assert(dataIt == data.end());
// We're done with the localCopy, free up the memory to avoid bloating the heap
localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
nvtt::OutputOptions outputOptions;
outputOptions.setOutputHeader(false);
std::unique_ptr<nvtt::OutputHandler> outputHandler;
@ -505,7 +526,7 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom
// Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats
outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat));
} else {
outputHandler.reset( new OutputHandler(texture, face) );
outputHandler.reset(new OutputHandler(texture, face));
}
outputOptions.setOutputHandler(outputHandler.get());
@ -526,13 +547,17 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom
}
}
void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<bool>& abortProcessing, int face) {
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& abortProcessing, int face) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(image);
if (localCopy.format() != QImage::Format_ARGB32) {
localCopy = localCopy.convertToFormat(QImage::Format_ARGB32);
}
const int width = image.width(), height = image.height();
const void* data = static_cast<const void*>(image.constBits());
const int width = localCopy.width(), height = localCopy.height();
const void* data = static_cast<const void*>(localCopy.constBits());
nvtt::TextureType textureType = nvtt::TextureType_2D;
nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB;
@ -545,7 +570,11 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<boo
nvtt::InputOptions inputOptions;
inputOptions.setTextureLayout(textureType, width, height);
inputOptions.setMipmapData(data, width, height);
// setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap
data = nullptr;
localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
inputOptions.setFormat(inputFormat);
inputOptions.setGamma(inputGamma, outputGamma);
@ -649,14 +678,14 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<boo
void generateMips(gpu::Texture* texture, QImage& image, const std::atomic<bool>& abortProcessing = false, int face = -1) {
void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& abortProcessing = false, int face = -1) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateMips");
if (image.format() == QIMAGE_HDR_FORMAT) {
generateHDRMips(texture, image, abortProcessing, face);
generateHDRMips(texture, std::move(image), abortProcessing, face);
} else {
generateLDRMips(texture, image, abortProcessing, face);
generateLDRMips(texture, std::move(image), abortProcessing, face);
}
#else
texture->setAutoGenerateMips(true);
@ -690,10 +719,11 @@ void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAs
validAlpha = (numOpaques != NUM_PIXELS);
}
gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName,
bool isStrict, const std::atomic<bool>& abortProcessing) {
PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage");
QImage image = processSourceImage(srcImage, false);
QImage image = processSourceImage(std::move(srcImage), false);
bool validAlpha = image.hasAlphaChannel();
bool alphaAsMask = false;
@ -739,7 +769,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& s
}
theTexture->setUsage(usage.build());
theTexture->setStoredMipFormat(formatMip);
generateMips(theTexture.get(), image, abortProcessing);
generateMips(theTexture.get(), std::move(image), abortProcessing);
}
return theTexture;
@ -757,16 +787,20 @@ double mapComponent(double sobelValue) {
return (sobelValue + 1.0) * factor;
}
QImage processBumpMap(QImage& image) {
if (image.format() != QImage::Format_Grayscale8) {
image = image.convertToFormat(QImage::Format_Grayscale8);
QImage processBumpMap(QImage&& image) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(image);
if (localCopy.format() != QImage::Format_Grayscale8) {
localCopy = localCopy.convertToFormat(QImage::Format_Grayscale8);
}
// PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps
// The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image
const double pStrength = 2.0;
int width = image.width();
int height = image.height();
int width = localCopy.width();
int height = localCopy.height();
QImage result(width, height, QImage::Format_ARGB32);
@ -779,14 +813,14 @@ QImage processBumpMap(QImage& image) {
const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1);
// surrounding pixels
const QRgb topLeft = image.pixel(iPrevClamped, jPrevClamped);
const QRgb top = image.pixel(iPrevClamped, j);
const QRgb topRight = image.pixel(iPrevClamped, jNextClamped);
const QRgb right = image.pixel(i, jNextClamped);
const QRgb bottomRight = image.pixel(iNextClamped, jNextClamped);
const QRgb bottom = image.pixel(iNextClamped, j);
const QRgb bottomLeft = image.pixel(iNextClamped, jPrevClamped);
const QRgb left = image.pixel(i, jPrevClamped);
const QRgb topLeft = localCopy.pixel(iPrevClamped, jPrevClamped);
const QRgb top = localCopy.pixel(iPrevClamped, j);
const QRgb topRight = localCopy.pixel(iPrevClamped, jNextClamped);
const QRgb right = localCopy.pixel(i, jNextClamped);
const QRgb bottomRight = localCopy.pixel(iNextClamped, jNextClamped);
const QRgb bottom = localCopy.pixel(iNextClamped, j);
const QRgb bottomLeft = localCopy.pixel(iNextClamped, jPrevClamped);
const QRgb left = localCopy.pixel(i, jPrevClamped);
// take their gray intensities
// since it's a grayscale image, the value of each component RGB is the same
@ -815,13 +849,13 @@ QImage processBumpMap(QImage& image) {
return result;
}
gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName,
bool isBumpMap, const std::atomic<bool>& abortProcessing) {
PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage");
QImage image = processSourceImage(srcImage, false);
QImage image = processSourceImage(std::move(srcImage), false);
if (isBumpMap) {
image = processBumpMap(image);
image = processBumpMap(std::move(image));
}
// Make sure the normal map source image is ARGB32
@ -841,17 +875,17 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImag
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
generateMips(theTexture.get(), image, abortProcessing);
generateMips(theTexture.get(), std::move(image), abortProcessing);
}
return theTexture;
}
gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName,
bool isInvertedPixels,
const std::atomic<bool>& abortProcessing) {
PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage");
QImage image = processSourceImage(srcImage, false);
QImage image = processSourceImage(std::move(srcImage), false);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
@ -877,7 +911,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImag
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
generateMips(theTexture.get(), image, abortProcessing);
generateMips(theTexture.get(), std::move(image), abortProcessing);
}
return theTexture;
@ -947,7 +981,7 @@ public:
static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) {
QImage image(faceWidth, faceWidth, source.format());
glm::vec2 dstInvSize(1.0f / (float)image.width(), 1.0f / (float)image.height());
glm::vec2 dstInvSize(1.0f / (float)source.width(), 1.0f / (float)source.height());
struct CubeToXYZ {
gpu::Texture::CubeFace _face;
@ -1150,8 +1184,12 @@ const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS)
//#define DEBUG_COLOR_PACKING
QImage convertToHDRFormat(QImage srcImage, gpu::Element format) {
QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT);
QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(srcImage);
QImage hdrImage(localCopy.width(), localCopy.height(), (QImage::Format)QIMAGE_HDR_FORMAT);
std::function<uint32(const glm::vec3&)> packFunc;
#ifdef DEBUG_COLOR_PACKING
std::function<glm::vec3(uint32)> unpackFunc;
@ -1173,13 +1211,13 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) {
default:
qCWarning(imagelogging) << "Unsupported HDR format";
Q_UNREACHABLE();
return srcImage;
return localCopy;
}
srcImage = srcImage.convertToFormat(QImage::Format_ARGB32);
for (auto y = 0; y < srcImage.height(); y++) {
const QRgb* srcLineIt = reinterpret_cast<const QRgb*>( srcImage.constScanLine(y) );
const QRgb* srcLineEnd = srcLineIt + srcImage.width();
localCopy = localCopy.convertToFormat(QImage::Format_ARGB32);
for (auto y = 0; y < localCopy.height(); y++) {
const QRgb* srcLineIt = reinterpret_cast<const QRgb*>( localCopy.constScanLine(y) );
const QRgb* srcLineEnd = srcLineIt + localCopy.width();
uint32* hdrLineIt = reinterpret_cast<uint32*>( hdrImage.scanLine(y) );
glm::vec3 color;
@ -1204,86 +1242,99 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) {
return hdrImage;
}
gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName,
bool generateIrradiance,
const std::atomic<bool>& abortProcessing) {
PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage");
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(srcImage);
int originalWidth = localCopy.width();
int originalHeight = localCopy.height();
if ((originalWidth <= 0) && (originalHeight <= 0)) {
return nullptr;
}
gpu::TexturePointer theTexture = nullptr;
if ((srcImage.width() > 0) && (srcImage.height() > 0)) {
QImage image = processSourceImage(srcImage, true);
if (image.format() != QIMAGE_HDR_FORMAT) {
image = convertToHDRFormat(image, HDR_FORMAT);
QImage image = processSourceImage(std::move(localCopy), true);
if (image.format() != QIMAGE_HDR_FORMAT) {
image = convertToHDRFormat(std::move(image), HDR_FORMAT);
}
gpu::Element formatMip;
gpu::Element formatGPU;
if (isCubeTexturesCompressionEnabled()) {
formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
} else {
formatMip = HDR_FORMAT;
formatGPU = HDR_FORMAT;
}
// Find the layout of the cubemap in the 2D image
// Use the original image size since processSourceImage may have altered the size / aspect ratio
int foundLayout = CubeLayout::findLayout(originalWidth, originalHeight);
if (foundLayout < 0) {
qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str());
return nullptr;
}
std::vector<QImage> faces;
// If found, go extract the faces as separate images
auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout];
if (layout._type == CubeLayout::FLAT) {
int faceWidth = image.width() / layout._widthRatio;
faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror));
} else if (layout._type == CubeLayout::EQUIRECTANGULAR) {
// THe face width is estimated from the input image
const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4;
const int EQUIRECT_MAX_FACE_WIDTH = 2048;
int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH);
for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) {
QImage faceImage = CubeLayout::extractEquirectangularFace(std::move(image), (gpu::Texture::CubeFace) face, faceWidth);
faces.push_back(std::move(faceImage));
}
}
gpu::Element formatMip;
gpu::Element formatGPU;
if (isCubeTexturesCompressionEnabled()) {
formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
} else {
formatMip = HDR_FORMAT;
formatGPU = HDR_FORMAT;
}
// free up the memory afterward to avoid bloating the heap
image = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
// Find the layout of the cubemap in the 2D image
// Use the original image size since processSourceImage may have altered the size / aspect ratio
int foundLayout = CubeLayout::findLayout(srcImage.width(), srcImage.height());
std::vector<QImage> faces;
// If found, go extract the faces as separate images
if (foundLayout >= 0) {
auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout];
if (layout._type == CubeLayout::FLAT) {
int faceWidth = image.width() / layout._widthRatio;
faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror));
} else if (layout._type == CubeLayout::EQUIRECTANGULAR) {
// THe face width is estimated from the input image
const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4;
const int EQUIRECT_MAX_FACE_WIDTH = 2048;
int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH);
for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) {
QImage faceImage = CubeLayout::extractEquirectangularFace(image, (gpu::Texture::CubeFace) face, faceWidth);
faces.push_back(faceImage);
}
}
} else {
qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str());
return nullptr;
}
// If the 6 faces have been created go on and define the true Texture
if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) {
theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
// If the 6 faces have been created go on and define the true Texture
if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) {
theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(resource_parse, "generateIrradiance");
auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
irradianceTexture->setSource(srcImageName);
irradianceTexture->setStoredMipFormat(HDR_FORMAT);
for (uint8 face = 0; face < faces.size(); ++face) {
generateMips(theTexture.get(), faces[face], abortProcessing, face);
irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits());
}
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(resource_parse, "generateIrradiance");
auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
irradianceTexture->setSource(srcImageName);
irradianceTexture->setStoredMipFormat(HDR_FORMAT);
for (uint8 face = 0; face < faces.size(); ++face) {
irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits());
}
irradianceTexture->generateIrradiance();
irradianceTexture->generateIrradiance();
auto irradiance = irradianceTexture->getIrradiance();
theTexture->overrideIrradiance(irradiance);
}
auto irradiance = irradianceTexture->getIrradiance();
theTexture->overrideIrradiance(irradiance);
}
for (uint8 face = 0; face < faces.size(); ++face) {
generateMips(theTexture.get(), std::move(faces[face]), abortProcessing, face);
}
}

View file

@ -41,41 +41,41 @@ enum Type {
UNUSED_TEXTURE
};
using TextureLoader = std::function<gpu::TexturePointer(const QImage&, const std::string&, const std::atomic<bool>&)>;
using TextureLoader = std::function<gpu::TexturePointer(QImage&&, const std::string&, const std::atomic<bool>&)>;
TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap());
gpu::TexturePointer create2DTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createCubeTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isStrict,
gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, bool isBumpMap,
gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, bool isInvertedPixels,
gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance,
gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance,
const std::atomic<bool>& abortProcessing);
} // namespace TextureUsage
@ -92,7 +92,7 @@ void setNormalTexturesCompressionEnabled(bool enabled);
void setGrayscaleTexturesCompressionEnabled(bool enabled);
void setCubeTexturesCompressionEnabled(bool enabled);
gpu::TexturePointer processImage(const QByteArray& content, const std::string& url,
gpu::TexturePointer processImage(QByteArray&& content, const std::string& url,
int maxNumPixels, TextureUsage::Type textureType,
const std::atomic<bool>& abortProcessing = false);

View file

@ -264,7 +264,7 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) {
gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) {
QImage image = QImage(path);
auto loader = image::TextureUsage::getTextureLoaderForType(type, options);
return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString(), false));
return gpu::TexturePointer(loader(std::move(image), QUrl::fromLocalFile(path).fileName().toStdString(), false));
}
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
@ -954,7 +954,9 @@ void ImageReader::read() {
gpu::TexturePointer texture;
{
PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0);
texture = image::processImage(_content, _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType());
// IMPORTANT: _content is empty past this point
texture = image::processImage(std::move(_content), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType());
if (!texture) {
qCWarning(modelnetworking) << "Could not process:" << _url;

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

@ -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

@ -16,7 +16,7 @@
<@include Highlight_shared.slh@>
uniform highlightParamsBuffer {
HighlightParameters params;
HighlightParameters params;
};
uniform sampler2D sceneDepthMap;
@ -35,8 +35,7 @@ void main(void) {
// We offset by half a texel to be centered on the depth sample. If we don't do this
// the blur will have a different width between the left / right sides and top / bottom
// sides of the silhouette
float highlightedDepth = texture(highlightedDepthMap, varTexCoord0).x;
float intensity = 0.0;
float highlightedDepth = texture(highlightedDepthMap, varTexCoord0).x;
if (highlightedDepth < FAR_Z) {
// We're not on the far plane so we are on the highlighted object, thus no outline to do!
@ -47,10 +46,13 @@ void main(void) {
highlightedDepth = -evalZeyeFromZdb(highlightedDepth);
sceneDepth = -evalZeyeFromZdb(sceneDepth);
// Are we occluded?
intensity = sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS) ? params._occludedFillOpacity : params._unoccludedFillOpacity;
if (sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS)) {
outFragColor = vec4(params._fillOccludedColor, params._fillOccludedAlpha);
} else {
outFragColor = vec4(params._fillUnoccludedColor, params._fillUnoccludedAlpha);
}
<@else@>
discard;
discard;
<@endif@>
} else {
vec2 halfTexel = getInvWidthHeight() / 2;
@ -62,6 +64,10 @@ void main(void) {
int x;
int y;
float intensity = 0.0;
float outlinedDepth = 0.0;
float sumOutlineDepth = 0.0;
for (y=0 ; y<params._blurKernelSize ; y++) {
uv = lineStartUv;
lineStartUv.y += deltaUv.y;
@ -70,8 +76,10 @@ void main(void) {
for (x=0 ; x<params._blurKernelSize ; x++) {
if (uv.x>=0.0 && uv.x<=1.0)
{
highlightedDepth = texture(highlightedDepthMap, uv).x;
intensity += (highlightedDepth < FAR_Z) ? 1.0 : 0.0;
outlinedDepth = texture(highlightedDepthMap, uv).x;
float touch = (outlinedDepth < FAR_Z) ? 1.0 : 0.0;
sumOutlineDepth = max(outlinedDepth * touch, sumOutlineDepth);
intensity += touch;
weight += 1.0;
}
uv.x += deltaUv.x;
@ -79,15 +87,32 @@ void main(void) {
}
}
if (intensity > 0) {
// sumOutlineDepth /= intensity;
} else {
sumOutlineDepth = FAR_Z;
}
intensity /= weight;
if (intensity < OPACITY_EPSILON) {
discard;
}
intensity = min(1.0, intensity / params._threshold);
intensity = min(1.0, intensity / params._threshold) * params._intensity;
// But we need to check the scene depth against the depth of the outline
float sceneDepth = texture(sceneDepthMap, texCoord0).x;
// Transform to linear depth for better precision
outlinedDepth = -evalZeyeFromZdb(sumOutlineDepth);
sceneDepth = -evalZeyeFromZdb(sceneDepth);
// Are we occluded?
if (sceneDepth < (outlinedDepth/*-LINEAR_DEPTH_BIAS*/)) {
outFragColor = vec4(params._outlineOccludedColor, intensity * params._outlineOccludedAlpha);
} else {
outFragColor = vec4(params._outlineUnoccludedColor, intensity * params._outlineUnoccludedAlpha);
}
}
outFragColor = vec4(params._color.rgb, intensity);
}
<@endfunc@>

View file

@ -88,7 +88,7 @@ HighlightSharedParameters::HighlightSharedParameters() {
}
float HighlightSharedParameters::getBlurPixelWidth(const render::HighlightStyle& style, int frameBufferHeight) {
return ceilf(style.outlineWidth * frameBufferHeight / 400.0f);
return ceilf(style._outlineWidth * frameBufferHeight / 400.0f);
}
PrepareDrawHighlight::PrepareDrawHighlight() {
@ -267,14 +267,19 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const
{
auto& shaderParameters = _configuration.edit();
shaderParameters._color = highlight._style.color;
shaderParameters._intensity = highlight._style.outlineIntensity * (highlight._style.isOutlineSmooth ? 2.0f : 1.0f);
shaderParameters._unoccludedFillOpacity = highlight._style.unoccludedFillOpacity;
shaderParameters._occludedFillOpacity = highlight._style.occludedFillOpacity;
shaderParameters._threshold = highlight._style.isOutlineSmooth ? 1.0f : 1e-3f;
shaderParameters._blurKernelSize = std::min(7, std::max(2, (int)floorf(highlight._style.outlineWidth * 3 + 0.5f)));
shaderParameters._outlineUnoccludedColor = highlight._style._outlineUnoccluded.color;
shaderParameters._outlineUnoccludedAlpha = highlight._style._outlineUnoccluded.alpha * (highlight._style._isOutlineSmooth ? 2.0f : 1.0f);
shaderParameters._outlineOccludedColor = highlight._style._outlineOccluded.color;
shaderParameters._outlineOccludedAlpha = highlight._style._outlineOccluded.alpha * (highlight._style._isOutlineSmooth ? 2.0f : 1.0f);
shaderParameters._fillUnoccludedColor = highlight._style._fillUnoccluded.color;
shaderParameters._fillUnoccludedAlpha = highlight._style._fillUnoccluded.alpha;
shaderParameters._fillOccludedColor = highlight._style._fillOccluded.color;
shaderParameters._fillOccludedAlpha = highlight._style._fillOccluded.alpha;
shaderParameters._threshold = highlight._style._isOutlineSmooth ? 1.0f : 1e-3f;
shaderParameters._blurKernelSize = std::min(7, std::max(2, (int)floorf(highlight._style._outlineWidth * 3 + 0.5f)));
// Size is in normalized screen height. We decide that for highlight width = 1, this is equal to 1/400.
auto size = highlight._style.outlineWidth / 400.0f;
auto size = highlight._style._outlineWidth / 400.0f;
shaderParameters._size.x = (size * framebufferSize.y) / framebufferSize.x;
shaderParameters._size.y = size;
}
@ -432,19 +437,20 @@ void SelectionToHighlight::run(const render::RenderContextPointer& renderContext
outputs.clear();
_sharedParameters->_highlightIds.fill(render::HighlightStage::INVALID_INDEX);
for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) {
std::ostringstream stream;
if (i > 0) {
stream << "highlightList" << i;
} else {
stream << "contextOverlayHighlightList";
}
auto selectionName = stream.str();
if (!scene->isSelectionEmpty(selectionName)) {
auto highlightId = highlightStage->getHighlightIdBySelection(selectionName);
if (!render::HighlightStage::isIndexInvalid(highlightId)) {
_sharedParameters->_highlightIds[outputs.size()] = highlightId;
outputs.emplace_back(selectionName);
int numLayers = 0;
auto highlightList = highlightStage->getActiveHighlightIds();
for (auto styleId : highlightList) {
auto highlight = highlightStage->getHighlight(styleId);
if (!scene->isSelectionEmpty(highlight._selectionName)) {
auto highlightId = highlightStage->getHighlightIdBySelection(highlight._selectionName);
_sharedParameters->_highlightIds[outputs.size()] = highlightId;
outputs.emplace_back(highlight._selectionName);
numLayers++;
if (numLayers == HighlightSharedParameters::MAX_PASS_COUNT) {
break;
}
}
}

View file

@ -11,17 +11,18 @@
struct HighlightParameters
{
TVEC3 _color;
float _intensity;
TVEC3 _outlineUnoccludedColor;
float _outlineUnoccludedAlpha;
TVEC3 _outlineOccludedColor;
float _outlineOccludedAlpha;
TVEC3 _fillUnoccludedColor;
float _fillUnoccludedAlpha;
TVEC3 _fillOccludedColor;
float _fillOccludedAlpha;
TVEC2 _size;
float _unoccludedFillOpacity;
float _occludedFillOpacity;
float _threshold;
int _blurKernelSize;
float padding2;
float padding3;
float _threshold;
TVEC2 _size;
};
// <@if 1@>

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) {

View file

@ -26,8 +26,9 @@ void main(void) {
DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0);
vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0);
float shadowAttenuation = evalShadowAttenuation(worldPos);
vec4 viewPos = vec4(frag.position.xyz, 1.0);
vec4 worldPos = getViewInverse() * viewPos;
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal);
if (frag.mode == FRAG_MODE_UNLIT) {
discard;

View file

@ -26,8 +26,9 @@ void main(void) {
DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0);
vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0);
float shadowAttenuation = evalShadowAttenuation(worldPos);
vec4 viewPos = vec4(frag.position.xyz, 1.0);
vec4 worldPos = getViewInverse() * viewPos;
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal);
// Light mapped or not ?
if (frag.mode == FRAG_MODE_UNLIT) {

View file

@ -82,28 +82,30 @@ void FetchSpatialTree::configure(const Config& config) {
_lodAngle = config.lodAngle;
}
void FetchSpatialTree::run(const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
auto& scene = renderContext->_scene;
void FetchSpatialTree::run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection) {
// start fresh
outSelection.clear();
// Eventually use a frozen frustum
auto queryFrustum = args->getViewFrustum();
if (_freezeFrustum) {
if (_justFrozeFrustum) {
_justFrozeFrustum = false;
_frozenFrutstum = args->getViewFrustum();
}
queryFrustum = _frozenFrutstum;
}
if (!filter.selectsNothing()) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
auto& scene = renderContext->_scene;
// Octree selection!
float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
scene->getSpatialTree().selectCellItems(outSelection, _filter, queryFrustum, angle);
// Eventually use a frozen frustum
auto queryFrustum = args->getViewFrustum();
if (_freezeFrustum) {
if (_justFrozeFrustum) {
_justFrozeFrustum = false;
_frozenFrustum = args->getViewFrustum();
}
queryFrustum = _frozenFrustum;
}
// Octree selection!
float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
scene->getSpatialTree().selectCellItems(outSelection, filter, queryFrustum, angle);
}
}
void CullSpatialSelection::configure(const Config& config) {
@ -113,11 +115,12 @@ void CullSpatialSelection::configure(const Config& config) {
}
void CullSpatialSelection::run(const RenderContextPointer& renderContext,
const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems) {
const Inputs& inputs, ItemBounds& outItems) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
auto& scene = renderContext->_scene;
auto& inSelection = inputs.get0();
auto& details = args->_details.edit(_detailType);
details._considered += (int)inSelection.numItems();
@ -126,9 +129,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
if (_freezeFrustum) {
if (_justFrozeFrustum) {
_justFrozeFrustum = false;
_frozenFrutstum = args->getViewFrustum();
_frozenFrustum = args->getViewFrustum();
}
args->pushViewFrustum(_frozenFrutstum); // replace the true view frustum by the frozen one
args->pushViewFrustum(_frozenFrustum); // replace the true view frustum by the frozen one
}
// Culling Frustum / solidAngle test helper class
@ -181,122 +184,124 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
outItems.clear();
outItems.reserve(inSelection.numItems());
// Now get the bound, and
// filter individually against the _filter
// visibility cull if partially selected ( octree cell contianing it was partial)
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
const auto filter = inputs.get1();
if (!filter.selectsNothing()) {
// Now get the bound, and
// filter individually against the _filter
// visibility cull if partially selected ( octree cell contianing it was partial)
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
if (_skipCulling) {
// inside & fit items: filter only, culling is disabled
{
PerformanceTimer perfTimer("insideFitItems");
for (auto id : inSelection.insideItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// inside & subcell items: filter only, culling is disabled
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// partial & fit items: filter only, culling is disabled
{
PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// partial & subcell items: filter only, culling is disabled
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
} else {
// inside & fit items: easy, just filter
{
PerformanceTimer perfTimer("insideFitItems");
for (auto id : inSelection.insideItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// inside & subcell items: filter & distance cull
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.solidAngleTest(itemBound.bound)) {
if (_skipCulling) {
// inside & fit items: filter only, culling is disabled
{
PerformanceTimer perfTimer("insideFitItems");
for (auto id : inSelection.insideItems) {
auto& item = scene->getItem(id);
if (filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
}
// partial & fit items: filter & frustum cull
{
PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
// inside & subcell items: filter only, culling is disabled
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
}
// partial & subcell items:: filter & frutum cull & solidangle cull
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
// partial & fit items: filter only, culling is disabled
{
PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id);
if (filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// partial & subcell items: filter only, culling is disabled
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
} else {
// inside & fit items: easy, just filter
{
PerformanceTimer perfTimer("insideFitItems");
for (auto id : inSelection.insideItems) {
auto& item = scene->getItem(id);
if (filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// inside & subcell items: filter & distance cull
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.solidAngleTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
// partial & fit items: filter & frustum cull
{
PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id);
if (filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
// partial & subcell items:: filter & frutum cull & solidangle cull
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
if (test.solidAngleTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
}
}
}
details._rendered += (int)outItems.size();
// Restore frustum if using the frozen one:
if (_freezeFrustum) {
args->popViewFrustum();

View file

@ -51,19 +51,16 @@ namespace render {
class FetchSpatialTree {
bool _freezeFrustum{ false }; // initialized by Config
bool _justFrozeFrustum{ false };
ViewFrustum _frozenFrutstum;
ViewFrustum _frozenFrustum;
float _lodAngle;
public:
using Config = FetchSpatialTreeConfig;
using JobModel = Job::ModelO<FetchSpatialTree, ItemSpatialTree::ItemSelection, Config>;
using JobModel = Job::ModelIO<FetchSpatialTree, ItemFilter, ItemSpatialTree::ItemSelection, Config>;
FetchSpatialTree() {}
FetchSpatialTree(const ItemFilter& filter) : _filter(filter) {}
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
void configure(const Config& config);
void run(const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection);
void run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection);
};
class CullSpatialSelectionConfig : public Job::Config {
@ -88,25 +85,24 @@ namespace render {
bool _freezeFrustum{ false }; // initialized by Config
bool _justFrozeFrustum{ false };
bool _skipCulling{ false };
ViewFrustum _frozenFrutstum;
ViewFrustum _frozenFrustum;
public:
using Config = CullSpatialSelectionConfig;
using JobModel = Job::ModelIO<CullSpatialSelection, ItemSpatialTree::ItemSelection, ItemBounds, Config>;
using Inputs = render::VaryingSet2<ItemSpatialTree::ItemSelection, ItemFilter>;
using JobModel = Job::ModelIO<CullSpatialSelection, Inputs, ItemBounds, Config>;
CullSpatialSelection(CullFunctor cullFunctor, RenderDetails::Type type, const ItemFilter& filter) :
CullSpatialSelection(CullFunctor cullFunctor, RenderDetails::Type type) :
_cullFunctor{ cullFunctor },
_detailType(type),
_filter(filter) {}
_detailType(type) {}
CullSpatialSelection(CullFunctor cullFunctor) :
_cullFunctor{ cullFunctor } {}
CullFunctor _cullFunctor;
RenderDetails::Type _detailType{ RenderDetails::OTHER };
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
void configure(const Config& config);
void run(const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems);
void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems);
};
}

View file

@ -215,79 +215,158 @@ void DrawBounds::run(const RenderContextPointer& renderContext,
});
}
gpu::PipelinePointer DrawFrustum::_pipeline;
DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) :
_color{ color } {
_meshVertices = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(glm::vec3) * 8, nullptr), gpu::Element::VEC3F_XYZ);
_meshStream.addBuffer(_meshVertices._buffer, _meshVertices._offset, _meshVertices._stride);
}
void DrawQuadVolume::configure(const Config& configuration) {
_isUpdateEnabled = !configuration.isFrozen;
}
void DrawQuadVolume::run(const render::RenderContextPointer& renderContext, const glm::vec3 vertices[8],
const gpu::BufferView& indices, int indexCount) {
assert(renderContext->args);
assert(renderContext->args->_context);
if (_isUpdateEnabled) {
auto& streamVertices = _meshVertices.edit<std::array<glm::vec3, 8U> >();
std::copy(vertices, vertices + 8, streamVertices.begin());
}
RenderArgs* args = renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch;
batch.setViewportTransform(args->_viewport);
batch.setStateScissorRect(args->_viewport);
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
batch.setPipeline(getPipeline());
batch.setIndexBuffer(indices);
batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f);
batch.setInputStream(0, _meshStream);
batch.drawIndexed(gpu::LINES, indexCount, 0U);
args->_batch = nullptr;
});
}
gpu::PipelinePointer DrawQuadVolume::getPipeline() {
static gpu::PipelinePointer pipeline;
if (!pipeline) {
auto vs = gpu::StandardShaderLib::getDrawTransformVertexPositionVS();
auto ps = gpu::StandardShaderLib::getDrawColorPS();
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding("color", 0));
gpu::Shader::makeProgram(*program, slotBindings);
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setDepthTest(gpu::State::DepthTest(true, false));
pipeline = gpu::Pipeline::create(program, state);
}
return pipeline;
}
gpu::BufferView DrawAABox::_cubeMeshIndices;
DrawAABox::DrawAABox(const glm::vec3& color) :
DrawQuadVolume{ color } {
}
void DrawAABox::run(const render::RenderContextPointer& renderContext, const Inputs& box) {
if (!box.isNull()) {
static const uint8_t indexData[] = {
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
3, 7,
2, 6
};
if (!_cubeMeshIndices._buffer) {
auto indices = std::make_shared<gpu::Buffer>(sizeof(indexData), indexData);
_cubeMeshIndices = gpu::BufferView(indices, gpu::Element(gpu::SCALAR, gpu::UINT8, gpu::INDEX));
}
glm::vec3 vertices[8];
getVertices(box, vertices);
DrawQuadVolume::run(renderContext, vertices, _cubeMeshIndices, sizeof(indexData) / sizeof(indexData[0]));
}
}
void DrawAABox::getVertices(const AABox& box, glm::vec3 vertices[8]) {
vertices[0] = box.getVertex(TOP_LEFT_NEAR);
vertices[1] = box.getVertex(TOP_RIGHT_NEAR);
vertices[2] = box.getVertex(BOTTOM_RIGHT_NEAR);
vertices[3] = box.getVertex(BOTTOM_LEFT_NEAR);
vertices[4] = box.getVertex(TOP_LEFT_FAR);
vertices[5] = box.getVertex(TOP_RIGHT_FAR);
vertices[6] = box.getVertex(BOTTOM_RIGHT_FAR);
vertices[7] = box.getVertex(BOTTOM_LEFT_FAR);
}
gpu::BufferView DrawFrustum::_frustumMeshIndices;
DrawFrustum::DrawFrustum(const glm::vec3& color) :
_color{ color } {
_frustumMeshVertices = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(glm::vec3) * 8, nullptr), gpu::Element::VEC3F_XYZ);
_frustumMeshStream.addBuffer(_frustumMeshVertices._buffer, _frustumMeshVertices._offset, _frustumMeshVertices._stride);
}
void DrawFrustum::configure(const Config& configuration) {
_updateFrustum = !configuration.isFrozen;
DrawQuadVolume{ color } {
}
void DrawFrustum::run(const render::RenderContextPointer& renderContext, const Input& input) {
assert(renderContext->args);
assert(renderContext->args->_context);
RenderArgs* args = renderContext->args;
if (input) {
const auto& frustum = *input;
static uint8_t indexData[] = { 0, 1, 2, 3, 0, 4, 5, 6, 7, 4, 5, 1, 2, 6, 7, 3 };
static const uint8_t indexData[] = {
0, 1,
1, 2,
2, 3,
3, 0,
0, 2,
3, 1,
4, 5,
5, 6,
6, 7,
7, 4,
4, 6,
7, 5,
0, 4,
1, 5,
3, 7,
2, 6
};
if (!_frustumMeshIndices._buffer) {
auto indices = std::make_shared<gpu::Buffer>(sizeof(indexData), indexData);
_frustumMeshIndices = gpu::BufferView(indices, gpu::Element(gpu::SCALAR, gpu::UINT8, gpu::INDEX));
}
if (!_pipeline) {
auto vs = gpu::StandardShaderLib::getDrawTransformVertexPositionVS();
auto ps = gpu::StandardShaderLib::getDrawColorPS();
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
glm::vec3 vertices[8];
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding("color", 0));
gpu::Shader::makeProgram(*program, slotBindings);
getVertices(frustum, vertices);
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setDepthTest(gpu::State::DepthTest(true, false));
_pipeline = gpu::Pipeline::create(program, state);
}
if (_updateFrustum) {
updateFrustum(frustum);
}
// Render the frustums in wireframe
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch;
batch.setViewportTransform(args->_viewport);
batch.setStateScissorRect(args->_viewport);
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
batch.setPipeline(_pipeline);
batch.setIndexBuffer(_frustumMeshIndices);
batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f);
batch.setInputStream(0, _frustumMeshStream);
batch.drawIndexed(gpu::LINE_STRIP, sizeof(indexData) / sizeof(indexData[0]), 0U);
args->_batch = nullptr;
});
DrawQuadVolume::run(renderContext, vertices, _frustumMeshIndices, sizeof(indexData) / sizeof(indexData[0]));
}
}
void DrawFrustum::updateFrustum(const ViewFrustum& frustum) {
auto& vertices = _frustumMeshVertices.edit<std::array<glm::vec3, 8U> >();
void DrawFrustum::getVertices(const ViewFrustum& frustum, glm::vec3 vertices[8]) {
vertices[0] = frustum.getNearTopLeft();
vertices[1] = frustum.getNearTopRight();
vertices[2] = frustum.getNearBottomRight();

View file

@ -70,12 +70,12 @@ private:
int _colorLocation { -1 };
};
class DrawFrustumConfig : public render::JobConfig {
class DrawQuadVolumeConfig : public render::JobConfig {
Q_OBJECT
Q_PROPERTY(bool isFrozen MEMBER isFrozen NOTIFY dirty)
public:
DrawFrustumConfig(bool enabled = false) : JobConfig(enabled) {}
DrawQuadVolumeConfig(bool enabled = false) : JobConfig(enabled) {}
bool isFrozen{ false };
signals:
@ -83,30 +83,58 @@ signals:
};
class DrawFrustum {
class DrawQuadVolume {
public:
using Config = DrawQuadVolumeConfig;
void configure(const Config& configuration);
protected:
DrawQuadVolume(const glm::vec3& color);
void run(const render::RenderContextPointer& renderContext, const glm::vec3 vertices[8],
const gpu::BufferView& indices, int indexCount);
gpu::BufferView _meshVertices;
gpu::BufferStream _meshStream;
glm::vec3 _color;
bool _isUpdateEnabled{ true };
static gpu::PipelinePointer getPipeline();
};
class DrawAABox : public DrawQuadVolume {
public:
using Inputs = AABox;
using JobModel = render::Job::ModelI<DrawAABox, Inputs, Config>;
DrawAABox(const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f));
void run(const render::RenderContextPointer& renderContext, const Inputs& box);
protected:
static gpu::BufferView _cubeMeshIndices;
static void getVertices(const AABox& box, glm::vec3 vertices[8]);
};
class DrawFrustum : public DrawQuadVolume {
public:
using Config = DrawFrustumConfig;
using Input = ViewFrustumPointer;
using JobModel = render::Job::ModelI<DrawFrustum, Input, Config>;
DrawFrustum(const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f));
void configure(const Config& configuration);
void run(const render::RenderContextPointer& renderContext, const Input& input);
private:
static gpu::PipelinePointer _pipeline;
static gpu::BufferView _frustumMeshIndices;
bool _updateFrustum{ true };
gpu::BufferView _frustumMeshVertices;
gpu::BufferStream _frustumMeshStream;
glm::vec3 _color;
void updateFrustum(const ViewFrustum& frustum);
static void getVertices(const ViewFrustum& frustum, glm::vec3 vertices[8]);
};
}
#endif // hifi_render_DrawTask_h

View file

@ -61,42 +61,42 @@ void HighlightStageConfig::setSelectionName(const QString& name) {
}
void HighlightStageConfig::setOutlineSmooth(bool isSmooth) {
editStyle().isOutlineSmooth = isSmooth;
editStyle()._isOutlineSmooth = isSmooth;
emit dirty();
}
void HighlightStageConfig::setColorRed(float value) {
editStyle().color.r = value;
editStyle()._outlineUnoccluded.color.r = value;
emit dirty();
}
void HighlightStageConfig::setColorGreen(float value) {
editStyle().color.g = value;
editStyle()._outlineUnoccluded.color.g = value;
emit dirty();
}
void HighlightStageConfig::setColorBlue(float value) {
editStyle().color.b = value;
editStyle()._outlineUnoccluded.color.b = value;
emit dirty();
}
void HighlightStageConfig::setOutlineWidth(float value) {
editStyle().outlineWidth = value;
editStyle()._outlineWidth = value;
emit dirty();
}
void HighlightStageConfig::setOutlineIntensity(float value) {
editStyle().outlineIntensity = value;
editStyle()._outlineUnoccluded.alpha = value;
emit dirty();
}
void HighlightStageConfig::setUnoccludedFillOpacity(float value) {
editStyle().unoccludedFillOpacity = value;
editStyle()._fillUnoccluded.alpha = value;
emit dirty();
}
void HighlightStageConfig::setOccludedFillOpacity(float value) {
editStyle().occludedFillOpacity = value;
editStyle()._fillOccluded.alpha = value;
emit dirty();
}

View file

@ -51,6 +51,7 @@ namespace render {
HighlightIdList::iterator begin() { return _activeHighlightIds.begin(); }
HighlightIdList::iterator end() { return _activeHighlightIds.end(); }
const HighlightIdList& getActiveHighlightIds() const { return _activeHighlightIds; }
private:
@ -82,28 +83,28 @@ namespace render {
QString getSelectionName() const { return QString(_selectionName.c_str()); }
void setSelectionName(const QString& name);
bool isOutlineSmooth() const { return getStyle().isOutlineSmooth; }
bool isOutlineSmooth() const { return getStyle()._isOutlineSmooth; }
void setOutlineSmooth(bool isSmooth);
float getColorRed() const { return getStyle().color.r; }
float getColorRed() const { return getStyle()._outlineUnoccluded.color.r; }
void setColorRed(float value);
float getColorGreen() const { return getStyle().color.g; }
float getColorGreen() const { return getStyle()._outlineUnoccluded.color.g; }
void setColorGreen(float value);
float getColorBlue() const { return getStyle().color.b; }
float getColorBlue() const { return getStyle()._outlineUnoccluded.color.b; }
void setColorBlue(float value);
float getOutlineWidth() const { return getStyle().outlineWidth; }
float getOutlineWidth() const { return getStyle()._outlineWidth; }
void setOutlineWidth(float value);
float getOutlineIntensity() const { return getStyle().outlineIntensity; }
float getOutlineIntensity() const { return getStyle()._outlineUnoccluded.alpha; }
void setOutlineIntensity(float value);
float getUnoccludedFillOpacity() const { return getStyle().unoccludedFillOpacity; }
float getUnoccludedFillOpacity() const { return getStyle()._fillUnoccluded.alpha; }
void setUnoccludedFillOpacity(float value);
float getOccludedFillOpacity() const { return getStyle().occludedFillOpacity; }
float getOccludedFillOpacity() const { return getStyle()._fillOccluded.alpha; }
void setOccludedFillOpacity(float value);
std::string _selectionName{ "contextOverlayHighlightList" };

View file

@ -20,17 +20,24 @@ namespace render {
// This holds the configuration for a particular outline style
class HighlightStyle {
public:
struct RGBA {
glm::vec3 color{ 1.0f, 0.7f, 0.2f };
float alpha{ 0.9f };
RGBA(const glm::vec3& c, float a) : color(c), alpha(a) {}
};
RGBA _outlineUnoccluded{ { 1.0f, 0.7f, 0.2f }, 0.9f };
RGBA _outlineOccluded{ { 1.0f, 0.7f, 0.2f }, 0.9f };
RGBA _fillUnoccluded{ { 0.2f, 0.7f, 1.0f }, 0.0f };
RGBA _fillOccluded{ { 0.2f, 0.7f, 1.0f }, 0.0f };
float _outlineWidth{ 2.0f };
bool _isOutlineSmooth{ false };
bool isFilled() const {
return unoccludedFillOpacity > 5e-3f || occludedFillOpacity > 5e-3f;
return _fillUnoccluded.alpha > 5e-3f || _fillOccluded.alpha > 5e-3f;
}
glm::vec3 color{ 1.f, 0.7f, 0.2f };
float outlineWidth{ 2.0f };
float outlineIntensity{ 0.9f };
float unoccludedFillOpacity{ 0.0f };
float occludedFillOpacity{ 0.0f };
bool isOutlineSmooth{ false };
};
}

View file

@ -182,6 +182,8 @@ public:
Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
Builder& withNothing() { _value.reset(); _mask.reset(); return (*this); }
// Convenient standard keys that we will keep on using all over the place
static Builder visibleWorldItems() { return Builder().withVisible().withWorldSpace(); }
static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); }
@ -191,12 +193,14 @@ public:
static Builder background() { return Builder().withViewSpace().withLayered(); }
static Builder opaqueShapeLayered() { return Builder().withTypeShape().withOpaque().withWorldSpace().withLayered(); }
static Builder transparentShapeLayered() { return Builder().withTypeShape().withTransparent().withWorldSpace().withLayered(); }
static Builder nothing() { return Builder().withNothing(); }
};
ItemFilter(const Builder& builder) : ItemFilter(builder._value, builder._mask) {}
// Item Filter operator testing if a key pass the filter
bool test(const ItemKey& key) const { return (key._flags & _mask) == (_value & _mask); }
bool selectsNothing() const { return !_mask.any(); }
class Less {
public:

View file

@ -22,9 +22,11 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin
// CPU jobs:
// Fetch and cull the items from the scene
auto spatialFilter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
const auto spatialFilter = render::Varying(filter);
const auto spatialSelection = task.addJob<FetchSpatialTree>("FetchSceneSelection", spatialFilter);
const auto culledSpatialSelection = task.addJob<CullSpatialSelection>("CullSceneSelection", spatialSelection, cullFunctor, RenderDetails::ITEM, spatialFilter);
const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, spatialFilter).asVarying();
const auto culledSpatialSelection = task.addJob<CullSpatialSelection>("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM);
// Overlays are not culled
const auto nonspatialSelection = task.addJob<FetchNonspatialItems>("FetchOverlaySelection");

View file

@ -532,7 +532,7 @@ bool Scene::isSelectionEmpty(const Selection::Name& name) const {
std::unique_lock<std::mutex> lock(_selectionsMutex);
auto found = _selections.find(name);
if (found == _selections.end()) {
return false;
return true;
} else {
return (*found).second.isEmpty();
}

View file

@ -75,7 +75,6 @@ void render::depthSortItems(const RenderContextPointer& renderContext, bool fron
std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), backToFrontSort);
}
// Finally once sorted result to a list of itemID
// Finally once sorted result to a list of itemID and keep uniques
render::ItemID previousID = Item::INVALID_ITEM_ID;
if (!bounds) {

View file

@ -51,22 +51,24 @@ QScriptValue menuItemPropertiesToScriptValue(QScriptEngine* engine, const MenuIt
}
/**jsdoc
* `MenuItemProperties` is a list of properties that can be passed to Menu.addMenuItem
* to create a new menu item.
* A set of properties that can be passed to {@link Menu.addMenuItem} to create a new menu item.
*
* If none of position, beforeItem, afterItem, or grouping are specified, the
* menu item will be placed in the last position.
* If none of <code>position</code>, <code>beforeItem</code>, <code>afterItem</code>, or <code>grouping</code> are specified,
* the menu item will be placed at the end of the menu.
*
* @typedef {Object} Menu.MenuItemProperties
* @property {string} menuName Name of the top-level menu
* @property {string} menuItemName Name of the menu item
* @property {bool} isCheckable Whether the menu item is checkable or not
* @property {bool} isChecked Where the menu item is checked or not
* @property {string} shortcutKey An optional shortcut key to trigger the menu item.
* @property {int} position The position to place the new menu item. `0` is the first menu item.
* @property {string} beforeItem The name of the menu item to place this menu item before.
* @property {string} afterItem The name of the menu item to place this menu item after.
* @property {string} grouping The name of grouping to add this menu item to.
* @typedef {object} Menu.MenuItemProperties
* @property {string} menuName Name of the menu. Nested menus can be described using the ">" symbol.
* @property {string} menuItemName Name of the menu item.
* @property {boolean} [isCheckable=false] Whether or not the menu item is checkable.
* @property {boolean} [isChecked=false] Whether or not the menu item is checked.
* @property {boolean} [isSeparator=false] Whether or not the menu item is a separator.
* @property {string} [shortcutKey] A shortcut key that triggers the menu item.
* @property {KeyEvent} [shortcutKeyEvent] A {@link KeyEvent} that specifies a key that triggers the menu item.
* @property {number} [position] The position to place the new menu item. An integer number with <code>0</code> being the first
* menu item.
* @property {string} [beforeItem] The name of the menu item to place this menu item before.
* @property {string} [afterItem] The name of the menu item to place this menu item after.
* @property {string} [grouping] The name of grouping to add this menu item to.
*/
void menuItemPropertiesFromScriptValue(const QScriptValue& object, MenuItemProperties& properties) {
properties.menuName = object.property("menuName").toVariant().toString();

View file

@ -21,13 +21,24 @@
#include <QtScript/QScriptable>
/**jsdoc
* A Quaternion
*
* @typedef Quat
* @property {float} x imaginary component i.
* @property {float} y imaginary component j.
* @property {float} z imaginary component k.
* @property {float} w real component.
* A quaternion value. See also the {@link Quat(0)|Quat} object.
* @typedef {object} Quat
* @property {number} x - Imaginary component i.
* @property {number} y - Imaginary component j.
* @property {number} z - Imaginary component k.
* @property {number} w - Real component.
*/
/**jsdoc
* The Quat API provides facilities for generating and manipulating quaternions.
* Quaternions should be used in preference to Euler angles wherever possible because quaternions don't suffer from the problem
* of gimbal lock.
* @namespace Quat
* @variation 0
* @property IDENTITY {Quat} The identity rotation, i.e., no rotation.
* @example <caption>Print the <code>IDENTITY</code> value.</caption>
* print(JSON.stringify(Quat.IDENTITY)); // { x: 0, y: 0, z: 0, w: 1 }
* print(JSON.stringify(Quat.safeEulerAngles(Quat.IDENTITY))); // { x: 0, y: 0, z: 0 }
*/
/// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API
@ -36,33 +47,408 @@ class Quat : public QObject, protected QScriptable {
Q_PROPERTY(glm::quat IDENTITY READ IDENTITY CONSTANT)
public slots:
/**jsdoc
* Multiply two quaternions.
* @function Quat(0).multiply
* @param {Quat} q1 - The first quaternion.
* @param {Quat} q2 - The second quaternion.
* @returns {Quat} <code>q1</code> multiplied with <code>q2</code>.
* @example <caption>Calculate the orientation of your avatar's right hand in world coordinates.</caption>
* var handController = Controller.Standard.RightHand;
* var handPose = Controller.getPoseValue(handController);
* if (handPose.valid) {
* var handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation);
* }
*/
glm::quat multiply(const glm::quat& q1, const glm::quat& q2);
/**jsdoc
* Normalizes a quaternion.
* @function Quat(0).normalize
* @param {Quat} q - The quaternion to normalize.
* @returns {Quat} <code>q</code> normalized to have unit length.
* @example <caption>Normalize a repeated delta rotation so that maths rounding errors don't accumulate.</caption>
* var deltaRotation = Quat.fromPitchYawRollDegrees(0, 0.1, 0);
* var currentRotation = Quat.ZERO;
* while (Quat.safeEulerAngles(currentRotation).y < 180) {
* currentRotation = Quat.multiply(deltaRotation, currentRotation);
* currentRotation = Quat.normalize(currentRotation);
* // Use currentRotatation for something.
* }
*/
glm::quat normalize(const glm::quat& q);
/**jsdoc
* Calculate the conjugate of a quaternion. For a unit quaternion, its conjugate is the same as its
* {@link Quat(0).inverse|Quat.inverse}.
* @function Quat(0).conjugate
* @param {Quat} q - The quaternion to conjugate.
* @returns {Quat} The conjugate of <code>q</code>.
* @example <caption>A unit quaternion multiplied by its conjugate is a zero rotation.</caption>
* var quaternion = Quat.fromPitchYawRollDegrees(10, 20, 30);
* Quat.print("quaternion", quaternion, true); // dvec3(10.000000, 20.000004, 30.000004)
* var conjugate = Quat.conjugate(quaternion);
* Quat.print("conjugate", conjugate, true); // dvec3(1.116056, -22.242186, -28.451778)
* var identity = Quat.multiply(conjugate, quaternion);
* Quat.print("identity", identity, true); // dvec3(0.000000, 0.000000, 0.000000)
*/
glm::quat conjugate(const glm::quat& q);
/**jsdoc
* Calculate a camera orientation given eye position, point of interest, and "up" direction. The camera's negative z-axis is
* the forward direction. The result has zero roll about its forward direction with respect to the given "up" direction.
* @function Quat(0).lookAt
* @param {Vec3} eye - The eye position.
* @param {Vec3} target - The point to look at.
* @param {Vec3} up - The "up" direction.
* @returns {Quat} A quaternion that orients the negative z-axis to point along the eye-to-target vector and the x-axis to
* be the cross product of the eye-to-target and up vectors.
* @example <caption>Rotate your view in independent mode to look at the world origin upside down.</caption>
* Camera.mode = "independent";
* Camera.orientation = Quat.lookAt(Camera.position, Vec3.ZERO, Vec3.UNIT_NEG_Y);
*/
glm::quat lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up);
/**jsdoc
* Calculate a camera orientation given eye position and point of interest. The camera's negative z-axis is the forward
* direction. The result has zero roll about its forward direction.
* @function Quat(0).lookAtSimple
* @param {Vec3} eye - The eye position.
* @param {Vec3} target - The point to look at.
* @returns {Quat} A quaternion that orients the negative z-axis to point along the eye-to-target vector and the x-axis to be
* the cross product of the eye-to-target and an "up" vector. The "up" vector is the y-axis unless the eye-to-target
* vector is nearly aligned with it (i.e., looking near vertically up or down), in which case the x-axis is used as the
* "up" vector.
* @example <caption>Rotate your view in independent mode to look at the world origin.</caption>
* Camera.mode = "independent";
* Camera.orientation = Quat.lookAtSimple(Camera.position, Vec3.ZERO);
*/
glm::quat lookAtSimple(const glm::vec3& eye, const glm::vec3& center);
/**jsdoc
* Calculate the shortest rotation from a first vector onto a second.
* @function Quat(0).rotationBetween
* @param {Vec3} v1 - The first vector.
* @param {Vec3} v2 - The second vector.
* @returns {Quat} The rotation from <code>v1</code> onto <code>v2</code>.
* @example <caption>Apply a change in velocity to an entity and rotate it to face the direction it's travelling.</caption>
* var newVelocity = Vec3.sum(entityVelocity, deltaVelocity);
* var properties = { velocity: newVelocity };
* if (Vec3.length(newVelocity) > 0.001) {
* properties.rotation = Quat.rotationBetween(entityVelocity, newVelocity);
* }
* Entities.editEntity(entityID, properties);
* entityVelocity = newVelocity;
*/
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::quat fromVec3Degrees(const glm::vec3& vec3); // degrees
glm::quat fromVec3Radians(const glm::vec3& vec3); // radians
glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll); // degrees
glm::quat fromPitchYawRollRadians(float pitch, float yaw, float roll); // radians
/**jsdoc
* Generate a quaternion from a {@link Vec3} of Euler angles in degrees.
* @function Quat(0).fromVec3Degrees
* @param {Vec3} vector - A vector of three Euler angles in degrees, the angles being the rotations about the x, y, and z
* axes.
* @returns {Quat} A quaternion created from the Euler angles in <code>vector</code>.
* @example <caption>Zero out pitch and roll from an orientation.</caption>
* var eulerAngles = Quat.safeEulerAngles(orientation);
* eulerAngles.x = 0;
* eulerAngles.z = 0;
* var newOrientation = Quat.fromVec3Degrees(eulerAngles);
*/
glm::quat fromVec3Degrees(const glm::vec3& vec3);
/**jsdoc
* Generate a quaternion from a {@link Vec3} of Euler angles in radians.
* @function Quat(0).fromVec3Radians
* @param {Vec3} vector - A vector of three Euler angles in radians, the angles being the rotations about the x, y, and z
* axes.
* @returns {Quat} A quaternion created using the Euler angles in <code>vector</code>.
* @example <caption>Create a rotation of 180 degrees about the y axis.</caption>
* var rotation = Quat.fromVec3Radians({ x: 0, y: Math.PI, z: 0 });
*/
glm::quat fromVec3Radians(const glm::vec3& vec3);
/**jsdoc
* Generate a quaternion from pitch, yaw, and roll values in degrees.
* @function Quat(0).fromPitchYawRollDegrees
* @param {number} pitch - The pitch angle in degrees.
* @param {number} yaw - The yaw angle in degrees.
* @param {number} roll - The roll angle in degrees.
* @returns {Quat} A quaternion created using the <code>pitch</code>, <code>yaw</code>, and <code>roll</code> Euler angles.
* @example <caption>Create a rotation of 180 degrees about the y axis.</caption>
* var rotation = Quat.fromPitchYawRollDgrees(0, 180, 0 );
*/
glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll);
/**jsdoc
* Generate a quaternion from pitch, yaw, and roll values in radians.
* @function Quat(0).fromPitchYawRollRadians
* @param {number} pitch - The pitch angle in radians.
* @param {number} yaw - The yaw angle in radians.
* @param {number} roll - The roll angle in radians.
* @returns {Quat} A quaternion created from the <code>pitch</code>, <code>yaw</code>, and <code>roll</code> Euler angles.
* @example <caption>Create a rotation of 180 degrees about the y axis.</caption>
* var rotation = Quat.fromPitchYawRollRadians(0, Math.PI, 0);
*/
glm::quat fromPitchYawRollRadians(float pitch, float yaw, float roll);
/**jsdoc
* Calculate the inverse of a quaternion. For a unit quaternion, its inverse is the same as its
* {@link Quat(0).conjugate|Quat.conjugate}.
* @function Quat(0).inverse
* @param {Quat} q - The quaternion.
* @returns {Quat} The inverse of <code>q</code>.
* @example <caption>A quaternion multiplied by its inverse is a zero rotation.</caption>
* var quaternion = Quat.fromPitchYawRollDegrees(10, 20, 30);
* Quat.print("quaternion", quaternion, true); // dvec3(10.000000, 20.000004, 30.000004)
* var inverse = Quat.invserse(quaternion);
* Quat.print("inverse", inverse, true); // dvec3(1.116056, -22.242186, -28.451778)
* var identity = Quat.multiply(inverse, quaternion);
* Quat.print("identity", identity, true); // dvec3(0.000000, 0.000000, 0.000000)
*/
glm::quat inverse(const glm::quat& q);
// redundant, calls getForward which better describes the returned vector as a direction
/**jsdoc
* Get the "front" direction that the camera would face if its orientation was set to the quaternion value.
* This is a synonym for {@link Quat(0).getForward|Quat.getForward}.
* The High Fidelity camera has axes x = right, y = up, -z = forward.
* @function Quat(0).getFront
* @param {Quat} orientation - A quaternion representing an orientation.
* @returns {Vec3} The negative z-axis rotated by <code>orientation</code>.
*/
glm::vec3 getFront(const glm::quat& orientation) { return getForward(orientation); }
/**jsdoc
* Get the "forward" direction that the camera would face if its orientation was set to the quaternion value.
* This is a synonym for {@link Quat(0).getFront|Quat.getFront}.
* The High Fidelity camera has axes x = right, y = up, -z = forward.
* @function Quat(0).getForward
* @param {Quat} orientation - A quaternion representing an orientation.
* @returns {Vec3} The negative z-axis rotated by <code>orientation</code>.
* @example <caption>Demonstrate that the "forward" vector is for the negative z-axis.</caption>
* var forward = Quat.getForward(Quat.IDENTITY);
* print(JSON.stringify(forward)); // {"x":0,"y":0,"z":-1}
*/
glm::vec3 getForward(const glm::quat& orientation);
/**jsdoc
* Get the "right" direction that the camera would have if its orientation was set to the quaternion value.
* The High Fidelity camera has axes x = right, y = up, -z = forward.
* @function Quat(0).getRight
* @param {Quat} orientation - A quaternion representing an orientation.
* @returns {Vec3} The x-axis rotated by <code>orientation</code>.
*/
glm::vec3 getRight(const glm::quat& orientation);
/**jsdoc
* Get the "up" direction that the camera would have if its orientation was set to the quaternion value.
* The High Fidelity camera has axes x = right, y = up, -z = forward.
* @function Quat(0).getUp
* @param {Quat} orientation - A quaternion representing an orientation.
* @returns {Vec3} The y-axis rotated by <code>orientation</code>.
*/
glm::vec3 getUp(const glm::quat& orientation);
glm::vec3 safeEulerAngles(const glm::quat& orientation); // degrees
glm::quat angleAxis(float angle, const glm::vec3& v); // degrees
/**jsdoc
* Calculate the Euler angles for the quaternion, in degrees. (The "safe" in the name signifies that the angle results will
* not be garbage even when the rotation is particularly difficult to decompose with pitches around +/-90 degrees.)
* @function Quat(0).safeEulerAngles
* @param {Quat} orientation - A quaternion representing an orientation.
* @returns {Vec3} A {@link Vec3} of Euler angles for the <code>orientation</code>, in degrees, the angles being the
* rotations about the x, y, and z axes.
* @example <caption>Report the camera yaw.</caption>
* var eulerAngles = Quat.safeEulerAngles(Camera.orientation);
* print("Camera yaw: " + eulerAngles.y);
*/
glm::vec3 safeEulerAngles(const glm::quat& orientation);
/**jsdoc
* Generate a quaternion given an angle to rotate through and an axis to rotate about.
* @function Quat(0).angleAxis
* @param {number} angle - The angle to rotate through, in degrees.
* @param {Vec3} axis - The unit axis to rotate about.
* @returns {Quat} A quaternion that is a rotation through <code>angle</code> degrees about the <code>axis</code>.
* <strong>WARNING:</strong> This value is in degrees whereas the value returned by {@link Quat(0).angle|Quat.angle} is
* in radians.
* @example <caption>Calculate a rotation of 90 degrees about the direction your camera is looking.</caption>
* var rotation = Quat.angleAxis(90, Quat.getForward(Camera.orientation));
*/
glm::quat angleAxis(float angle, const glm::vec3& v);
/**jsdoc
* Get the rotation axis for a quaternion.
* @function Quat(0).axis
* @param {Quat} q - The quaternion.
* @returns {Vec3} The normalized rotation axis for <code>q</code>.
* @example <caption>Get the rotation axis of a quaternion.</caption>
* var forward = Quat.getForward(Camera.orientation);
* var rotation = Quat.angleAxis(90, forward);
* var axis = Quat.axis(rotation);
* print("Forward: " + JSON.stringify(forward));
* print("Axis: " + JSON.stringify(axis)); // Same value as forward.
*/
glm::vec3 axis(const glm::quat& orientation);
/**jsdoc
* Get the rotation angle for a quaternion.
* @function Quat(0).angle
* @param {Quat} q - The quaternion.
* @returns {number} The rotation angle for <code>q</code>, in radians. <strong>WARNING:</strong> This value is in radians
* whereas the value used by {@link Quat(0).angleAxis|Quat.angleAxis} is in degrees.
* @example <caption>Get the rotation angle of a quaternion.</caption>
* var forward = Quat.getForward(Camera.orientation);
* var rotation = Quat.angleAxis(90, forward);
* var angle = Quat.angle(rotation);
* print("Angle: " + angle * 180 / Math.PI); // 90 degrees.
*/
float angle(const glm::quat& orientation);
// spherical linear interpolation
// alpha: 0.0 to 1.0?
/**jsdoc
* Compute a spherical linear interpolation between two rotations, safely handling two rotations that are very similar.
* See also, {@link Quat(0).slerp|Quat.slerp}.
* @function Quat(0).mix
* @param {Quat} q1 - The beginning rotation.
* @param {Quat} q2 - The ending rotation.
* @param {number} alpha - The mixture coefficient between <code>0.0</code> and <code>1.0</code>. Specifies the proportion
* of <code>q2</code>'s value to return in favor of <code>q1</code>'s value. A value of <code>0.0</code> returns
* <code>q1</code>'s value; <code>1.0</code> returns <code>q2s</code>'s value.
* @returns {Quat} A spherical linear interpolation between rotations <code>q1</code> and <code>q2</code>.
* @example <caption>Animate between one rotation and another.</caption>
* var dt = amountOfTimeThatHasPassed;
* var mixFactor = amountOfTimeThatHasPassed / TIME_TO_COMPLETE;
* if (mixFactor > 1) {
* mixFactor = 1;
* }
* var newRotation = Quat.mix(startRotation, endRotation, mixFactor);
*/
glm::quat mix(const glm::quat& q1, const glm::quat& q2, float alpha);
/**jsdoc
* Compute a spherical linear interpolation between two rotations, for rotations that are not very similar.
* See also, {@link Quat(0).mix|Quat.mix}.
* @function Quat(0).slerp
* @param {Quat} q1 - The beginning rotation.
* @param {Quat} q2 - The ending rotation.
* @param {number} alpha - The mixture coefficient between <code>0.0</code> and <code>1.0</code>. Specifies the proportion
* of <code>q2</code>'s value to return in favor of <code>q1</code>'s value. A value of <code>0.0</code> returns
* <code>q1</code>'s value; <code>1.0</code> returns <code>q2s</code>'s value.
* @returns {Quat} A spherical linear interpolation between rotations <code>q1</code> and <code>q2</code>.
*/
glm::quat slerp(const glm::quat& q1, const glm::quat& q2, float alpha);
/**jsdoc
* Compute a spherical quadrangle interpolation between two rotations along a path oriented toward two other rotations.
* Equivalent to: <code>Quat.slerp(Quat.slerp(q1, q2, alpha), Quat.slerp(s1, s2, alpha), 2 * alpha * (1.0 - alpha))</code>.
* @function Quat(0).squad
* @param {Quat} q1 - Initial rotation.
* @param {Quat} q2 - Final rotation.
* @param {Quat} s1 - First control point.
* @param {Quat} s2 - Second control point.
* @param {number} alpha - The mixture coefficient between <code>0.0</code> and <code>1.0</code>. A value of
* <code>0.0</code> returns <code>q1</code>'s value; <code>1.0</code> returns <code>q2s</code>'s value.
* @returns {Quat} A spherical quadrangle interpolation between rotations <code>q1</code> and <code>q2</code> using control
* points <code>s1</code> and <code>s2</code>.
*/
glm::quat squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h);
/**jsdoc
* Calculate the dot product of two quaternions. The closer the quaternions are to each other the more non-zero the value is
* (either positive or negative). Identical unit rotations have a dot product of +/- 1.
* @function Quat(0).dot
* @param {Quat} q1 - The first quaternion.
* @param {Quat} q2 - The second quaternion.
* @returns {number} The dot product of <code>q1</code> and <code>q2</code>.
* @example <caption>Testing unit quaternions for equality.</caption>
* var q1 = Quat.fromPitchYawRollDegrees(0, 0, 0);
* var q2 = Quat.fromPitchYawRollDegrees(0, 0, 0);
* print(Quat.equal(q1, q2)); // true
* var q3 = Quat.fromPitchYawRollDegrees(0, 0, 359.95);
* print(Quat.equal(q1, q3)); // false
*
* var dot = Quat.dot(q1, q3);
* print(dot); // -0.9999999403953552
* var equal = Math.abs(1 - Math.abs(dot)) < 0.000001;
* print(equal); // true
*/
float dot(const glm::quat& q1, const glm::quat& q2);
/**jsdoc
* Print to the program log a text label followed by a quaternion's pitch, yaw, and roll Euler angles.
* @function Quat(0).print
* @param {string} label - The label to print.
* @param {Quat} q - The quaternion to print.
* @param {boolean} [asDegrees=false] - If <code>true</code> the angle values are printed in degrees, otherwise they are
* printed in radians.
* @example <caption>Two ways of printing a label plus a quaternion's Euler angles.</caption>
* var quaternion = Quat.fromPitchYawRollDegrees(0, 45, 0);
*
* // Quaternion: dvec3(0.000000, 45.000004, 0.000000)
* Quat.print("Quaternion:", quaternion, true);
*
* // Quaternion: {"x":0,"y":45.000003814697266,"z":0}
* print("Quaternion: " + JSON.stringify(Quat.safeEulerAngles(quaternion)));
*/
void print(const QString& label, const glm::quat& q, bool asDegrees = false);
/**jsdoc
* Test whether two quaternions are equal. <strong>Note:</strong> The quaternions must be exactly equal in order for
* <code>true</code> to be returned; it is often better to use {@link Quat(0).dot|Quat.dot} and test for closeness to +/-1.
* @function Quat(0).equal
* @param {Quat} q1 - The first quaternion.
* @param {Quat} q2 - The second quaternion.
* @returns {boolean} <code>true</code> if the quaternions are equal, otherwise <code>false</code>.
* @example <caption>Testing unit quaternions for equality.</caption>
* var q1 = Quat.fromPitchYawRollDegrees(0, 0, 0);
* var q2 = Quat.fromPitchYawRollDegrees(0, 0, 0);
* print(Quat.equal(q1, q2)); // true
* var q3 = Quat.fromPitchYawRollDegrees(0, 0, 359.95);
* print(Quat.equal(q1, q3)); // false
*
* var dot = Quat.dot(q1, q3);
* print(dot); // -0.9999999403953552
* var equal = Math.abs(1 - Math.abs(dot)) < 0.000001;
* print(equal); // true
*/
bool equal(const glm::quat& q1, const glm::quat& q2);
/**jsdoc
* Cancels out the roll and pitch component of a quaternion so that its completely horizontal with a yaw pointing in the
* given quaternion's direction.
* @function Quat(0).cancelOutRollAndPitch
* @param {Quat} orientation - A quaternion representing an orientation.
* @returns {Quat} <code>orientation</code> with its roll and pitch canceled out.
* @example <caption>Two ways of calculating a camera orientation in the x-z plane with a yaw pointing in the direction of
* a given quaternion.</caption>
* var quaternion = Quat.fromPitchYawRollDegrees(10, 20, 30);
*
* var noRollOrPitch = Quat.cancelOutRollAndPitch(quaternion);
* Quat.print("", noRollOrPitch, true); // dvec3(0.000000, 22.245995, 0.000000)
*
* var front = Quat.getFront(quaternion);
* var lookAt = Quat.lookAtSimple(Vec3.ZERO, { x: front.x, y: 0, z: front.z });
* Quat.print("", lookAt, true); // dvec3(0.000000, 22.245996, 0.000000)
*
*/
glm::quat cancelOutRollAndPitch(const glm::quat& q);
/**jsdoc
* Cancels out the roll component of a quaternion so that its horizontal axis is level.
* @function Quat(0).cancelOutRoll
* @param {Quat} orientation - A quaternion representing an orientation.
* @returns {Quat} <code>orientation</code> with its roll canceled out.
* @example <caption>Two ways of calculating a camera orientation that points in the direction of a given quaternion but
* keeps the camera's horizontal axis level.</caption>
* var quaternion = Quat.fromPitchYawRollDegrees(10, 20, 30);
*
* var noRoll = Quat.cancelOutRoll(quaternion);
* Quat.print("", noRoll, true); // dvec3(-1.033004, 22.245996, -0.000000)
*
* var front = Quat.getFront(quaternion);
* var lookAt = Quat.lookAtSimple(Vec3.ZERO, front);
* Quat.print("", lookAt, true); // dvec3(-1.033004, 22.245996, -0.000000)
*/
glm::quat cancelOutRoll(const glm::quat& q);
private:

View file

@ -17,17 +17,100 @@
#include <QUuid>
#include <QtScript/QScriptable>
/**jsdoc
* A UUID (Universally Unique IDentifier) is used to uniquely identify entities, overlays, avatars, and the like. It is
* represented in JavaScript as a string in the format, <code>{nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}</code>, where the "n"s are
* hexadecimal digits.
*
* @namespace Uuid
* @property NULL {Uuid} The null UUID, <code>{00000000-0000-0000-0000-000000000000}</code>.
*/
/// Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API
class ScriptUUID : public QObject, protected QScriptable {
Q_OBJECT
Q_PROPERTY(QString NULL READ NULL_UUID CONSTANT) // String for use in scripts.
public slots:
/**jsdoc
* Generates a UUID from a string representation of the UUID.
* @function Uuid.fromString
* @param {string} string - A string representation of a UUID. The curly braces are optional.
* @returns {Uuid} A UUID if the given <code>string</code> is valid, <code>null</code> otherwise.
* @example <caption>Valid and invalid parameters.</caption>
* var uuid = Uuid.fromString("{527c27ea-6d7b-4b47-9ae2-b3051d50d2cd}");
* print(uuid); // {527c27ea-6d7b-4b47-9ae2-b3051d50d2cd}
*
* uuid = Uuid.fromString("527c27ea-6d7b-4b47-9ae2-b3051d50d2cd");
* print(uuid); // {527c27ea-6d7b-4b47-9ae2-b3051d50d2cd}
*
* uuid = Uuid.fromString("527c27ea");
* print(uuid); // null
*/
QUuid fromString(const QString& string);
/**jsdoc
* Generates a string representation of a UUID. However, because UUIDs are represented in JavaScript as strings, this is in
* effect a no-op.
* @function Uuid.toString
* @param {Uuid} id - The UUID to generate a string from.
* @returns {string} - A string representation of the UUID.
*/
QString toString(const QUuid& id);
/**jsdoc
* Generate a new UUID.
* @function Uuid.generate
* @returns {Uuid} A new UUID.
* @example <caption>Generate a new UUID and reports its JavaScript type.</caption>
* var uuid = Uuid.generate();
* print(uuid); // {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}
* print(typeof uuid); // string
*/
QUuid generate();
/**jsdoc
* Test whether two given UUIDs are equal.
* @function Uuid.isEqual
* @param {Uuid} idA - The first UUID to compare.
* @param {Uuid} idB - The second UUID to compare.
* @returns {boolean} <code>true</code> if the two UUIDs are equal, otherwise <code>false</code>.
* @example <caption>Demonstrate <code>true</code> and <code>false</code> cases.</caption>
* var uuidA = Uuid.generate();
* var uuidB = Uuid.generate();
* print(Uuid.isEqual(uuidA, uuidB)); // false
* uuidB = uuidA;
* print(Uuid.isEqual(uuidA, uuidB)); // true
*/
bool isEqual(const QUuid& idA, const QUuid& idB);
/**jsdoc
* Test whether a given UUID is null.
* @function Uuid.isNull
* @param {Uuid} id - The UUID to test.
* @returns {boolean} <code>true</code> if the UUID equals Uuid.NULL or is <code>null</code>, otherwise <code>false</code>.
* @example <caption>Demonstrate <code>true</code> and <code>false</code> cases.</caption>
* var uuid; // undefined
* print(Uuid.isNull(uuid)); // false
* uuid = Uuid.generate();
* print(Uuid.isNull(uuid)); // false
* uuid = Uuid.NULL;
* print(Uuid.isNull(uuid)); // true
* uuid = null;
* print(Uuid.isNull(uuid)); // true
*/
bool isNull(const QUuid& id);
/**jsdoc
* Print to the program log a text label followed by the UUID value.
* @function Uuid.print
* @param {string} label - The label to print.
* @param {Uuid} id - The UUID to print.
* @example <caption>Two ways of printing a label plus UUID.</caption>
* var uuid = Uuid.generate();
* Uuid.print("Generated UUID:", uuid); // Generated UUID: {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}
* print("Generated UUID: " + uuid); // Generated UUID: {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}
*/
void print(const QString& label, const QUuid& id);
private:

View file

@ -24,7 +24,7 @@
/**jsdoc
* A 2-dimensional vector.
*
* @typedef Vec2
* @typedef {object} Vec2
* @property {float} x X-coordinate of the vector.
* @property {float} y Y-coordinate of the vector.
*/
@ -32,7 +32,7 @@
/**jsdoc
* A 3-dimensional vector.
*
* @typedef Vec3
* @typedef {object} Vec3
* @property {float} x X-coordinate of the vector.
* @property {float} y Y-coordinate of the vector.
* @property {float} z Z-coordinate of the vector.
@ -41,7 +41,7 @@
/**jsdoc
* A 4-dimensional vector.
*
* @typedef Vec4
* @typedef {object} Vec4
* @property {float} x X-coordinate of the vector.
* @property {float} y Y-coordinate of the vector.
* @property {float} z Z-coordinate of the vector.

View file

@ -432,6 +432,12 @@ glm::vec3 toGlm(const xColor& color) {
return glm::vec3(color.red, color.green, color.blue) / MAX_COLOR;
}
xColor xColorFromGlm(const glm::vec3 & color) {
static const float MAX_COLOR = 255.0f;
return { (uint8_t)(color.x * MAX_COLOR), (uint8_t)(color.y * MAX_COLOR), (uint8_t)(color.z * MAX_COLOR) };
}
glm::vec4 toGlm(const QColor& color) {
return glm::vec4(color.redF(), color.greenF(), color.blueF(), color.alphaF());
}

View file

@ -177,6 +177,8 @@ vec4 toGlm(const QColor& color);
ivec4 toGlm(const QRect& rect);
vec4 toGlm(const xColor& color, float alpha);
xColor xColorFromGlm(const glm::vec3 & c);
QSize fromGlm(const glm::ivec2 & v);
QMatrix4x4 fromGlm(const glm::mat4 & m);

View file

@ -338,8 +338,6 @@ int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle
int clippedTriangleCount = 0;
int i;
assert(clippedTriangleCount > 0);
for (i = 0; i < 3; i++) {
pointDistanceToPlane[i] = plane.distance(triangleVertices[i]);
arePointsClipped.set(i, pointDistanceToPlane[i] < 0.0f);
@ -424,7 +422,7 @@ int clipTriangleWithPlanes(const Triangle& triangle, const Plane* planes, int pl
*clippedTriangles = triangle;
while (planes < planesEnd) {
while (planes < planesEnd && triangleCount) {
int clippedSubTriangleCount;
trianglesToTest.clear();

View file

@ -19,9 +19,13 @@
#include "DependencyManager.h"
/**jsdoc
* The Paths API provides absolute paths to the scripts and resources directories.
*
* @namespace Paths
* @deprecated The Paths API is deprecated. Use {@link Script.resolvePath} and {@link Script.resourcesPath} instead.
* @readonly
* @property {string} resources The path to the resources directory.
* @property {string} defaultScripts - The path to the scripts directory. <em>Read-only.</em>
* @property {string} resources - The path to the resources directory. <em>Read-only.</em>
*/
class PathUtils : public QObject, public Dependency {
Q_OBJECT

View file

@ -65,6 +65,64 @@ void PointerEvent::setButton(Button button) {
_buttons |= button;
}
/**jsdoc
* A PointerEvent defines a 2D or 3D mouse or similar pointer event.
* @typedef {object} PointerEvent
* @property {string} type - The type of event: <code>"Press"</code>, <code>"DoublePress"</code>, <code>"Release"</code>, or
* <code>"Move"</code>.
* @property {number} id - Integer number used to identify the pointer: <code>0</code> = hardware mouse, <code>1</code> = left
* controller, <code>2</code> = right controller.
* @property {Vec2} pos2D - The 2D position of the event on the intersected overlay or entity XY plane, where applicable.
* @property {Vec3} pos3D - The 3D position of the event on the intersected overlay or entity, where applicable.
* @property {Vec3} normal - The surface normal at the intersection point.
* @property {Vec3} direction - The direction of the intersection ray.
* @property {string} button - The name of the button pressed: <code>None</code>, <code>Primary</code>, <code>Secondary</code>,
* or <code>Tertiary</code>.
* @property {boolean} isPrimaryButton - <code>true</code> if the button pressed was the primary button, otherwise
* <code>undefined</code>;
* @property {boolean} isLeftButton - <code>true</code> if the button pressed was the primary button, otherwise
* <code>undefined</code>;
* @property {boolean} isSecondaryButton - <code>true</code> if the button pressed was the secondary button, otherwise
* <code>undefined</code>;
* @property {boolean} isRightButton - <code>true</code> if the button pressed was the secondary button, otherwise
* <code>undefined</code>;
* @property {boolean} isTertiaryButton - <code>true</code> if the button pressed was the tertiary button, otherwise
* <code>undefined</code>;
* @property {boolean} isMiddleButton - <code>true</code> if the button pressed was the tertiary button, otherwise
* <code>undefined</code>;
* @property {boolean} isPrimaryHeld - <code>true</code> if the primary button is currently being pressed, otherwise
* <code>false</code>
* @property {boolean} isSecondaryHeld - <code>true</code> if the secondary button is currently being pressed, otherwise
* <code>false</code>
* @property {boolean} isTertiaryHeld - <code>true</code> if the tertiary button is currently being pressed, otherwise
* <code>false</code>
* @property {KeyboardModifiers} keyboardModifiers - Integer value with bits set according to which keyboard modifier keys were
* pressed when the event was generated.
*/
/**jsdoc
* <p>A KeyboardModifiers value is used to specify which modifier keys on the keyboard are pressed. The value is the sum
* (bitwise OR) of the relevant combination of values from the following table:</p>
* <table>
* <thead>
* <tr><th>Key</th><th>Hexadecimal value</th><th>Decimal value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td>Shift</td><td><code>0x02000000</code></td><td><code>33554432</code></td>
* <td>A Shift key on the keyboard is pressed.</td></tr>
* <tr><td>Control</td><td><code>0x04000000</code></td><td><code>67108864</code></td>
* <td>A Control key on the keyboard is pressed.</td></tr>
* <tr><td>Alt</td><td><code>0x08000000</code></td><td><code>134217728</code></td>
* <td>An Alt key on the keyboard is pressed.</td></tr>
* <tr><td>Meta</td><td><code>0x10000000</code></td><td><code>268435456</code></td>
* <td>A Meta or Windows key on the keyboard is pressed.</td></tr>
* <tr><td>Keypad</td><td><code>0x20000000</code></td><td><code>536870912</code></td>
* <td>A keypad button is pressed.</td></tr>
* <tr><td>Group</td><td><code>0x40000000</code></td><td><code>1073741824</code></td>
* <td>X11 operating system only: An AltGr / Mode_switch key on the keyboard is pressed.</td></tr>
* </tbody>
* </table>
* @typedef {number} KeyboardModifiers
*/
QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEvent& event) {
QScriptValue obj = engine->newObject();

View file

@ -561,6 +561,14 @@ QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) {
return obj;
}
/**jsdoc
* Defines a rectangular portion of an image or screen.
* @typedef {object} Rect
* @property {number} x - Integer left, x-coordinate value.
* @property {number} y - Integer top, y-coordinate value.
* @property {number} width - Integer width of the rectangle.
* @property {number} height - Integer height of the rectangle.
*/
QVariant qRectToVariant(const QRect& rect) {
QVariantMap obj;
obj["x"] = rect.x();
@ -613,7 +621,13 @@ void xColorFromScriptValue(const QScriptValue &object, xColor& color) {
}
}
/**jsdoc
* An RGB color value.
* @typedef {object} Color
* @property {number} red - Red component value. Integer in the range <code>0</code> - <code>255</code>.
* @property {number} green - Green component value. Integer in the range <code>0</code> - <code>255</code>.
* @property {number} blue - Blue component value. Integer in the range <code>0</code> - <code>255</code>.
*/
QVariant xColorToVariant(const xColor& color) {
QVariantMap obj;
obj["red"] = color.red;
@ -790,6 +804,12 @@ void quuidFromScriptValue(const QScriptValue& object, QUuid& uuid) {
uuid = fromString;
}
/**jsdoc
* A 2D size value.
* @typedef {object} Size
* @property {number} height - The height value.
* @property {number} width - The width value.
*/
QScriptValue qSizeFToScriptValue(QScriptEngine* engine, const QSizeF& qSizeF) {
QScriptValue obj = engine->newObject();
obj.setProperty("width", qSizeF.width());

View file

@ -134,15 +134,16 @@ public:
virtual QVariantMap toVariantMap() const = 0;
};
/**jsdoc
* A PickRay defines a vector with a starting point. It is used, for example, when finding entities or overlays that lie under a
* mouse click or intersect a laser beam.
*
* @typedef {object} PickRay
* @property {Vec3} origin - The starting position of the PickRay.
* @property {Quat} direction - The direction that the PickRay travels.
*/
class PickRay : public MathPick {
public:
/**jsdoc
* The mathematical definition of a ray.
*
* @typedef {Object} PickRay
* @property {Vec3} origin The origin of the ray.
* @property {Vec3} direction The direction of the ray.
*/
PickRay() : origin(NAN), direction(NAN) { }
PickRay(const QVariantMap& pickVariant) : origin(vec3FromVariant(pickVariant["origin"])), direction(vec3FromVariant(pickVariant["direction"])) {}
PickRay(const glm::vec3& origin, const glm::vec3 direction) : origin(origin), direction(direction) {}
@ -166,17 +167,17 @@ Q_DECLARE_METATYPE(PickRay)
QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay);
void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay);
/**jsdoc
* A StylusTip defines the tip of a stylus.
*
* @typedef {object} StylusTip
* @property {number} side - The hand the tip is attached to: <code>0</code> for left, <code>1</code> for right.
* @property {Vec3} position - The position of the stylus tip.
* @property {Quat} orientation - The orientation of the stylus tip.
* @property {Vec3} velocity - The velocity of the stylus tip.
*/
class StylusTip : public MathPick {
public:
/**jsdoc
* The mathematical definition of a stylus tip.
*
* @typedef {Object} StylusTip
* @property {number} side The hand the tip is attached to. 0 == left, 1 == right.
* @property {Vec3} position The position of the tip.
* @property {Quat} orientation The orientation of the tip.
* @property {Vec3} velocity The velocity of the tip.
*/
StylusTip() : position(NAN), velocity(NAN) {}
StylusTip(const QVariantMap& pickVariant) : side(bilateral::Side(pickVariant["side"].toInt())), position(vec3FromVariant(pickVariant["position"])),
orientation(quatFromVariant(pickVariant["orientation"])), velocity(vec3FromVariant(pickVariant["velocity"])) {}
@ -208,9 +209,9 @@ namespace std {
template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
hash_combine(seed, rest...);
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
hash_combine(seed, rest...);
}
template <>

View file

@ -71,6 +71,8 @@ void ViewFrustum::setProjection(const glm::mat4& projection) {
glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f);
top /= top.w;
_fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0.0f, 0.0f, -1.0f), glm::normalize(vec3(top))))));
_height = _corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_RIGHT_NEAR].y;
_width = _corners[TOP_RIGHT_NEAR].x - _corners[TOP_LEFT_NEAR].x;
}
// ViewFrustum::calculate()
@ -691,7 +693,7 @@ void ViewFrustum::getFurthestPointFromCamera(const AACube& box, glm::vec3& furth
}
}
const ViewFrustum::Corners ViewFrustum::getCorners(const float& depth) const {
const ViewFrustum::Corners ViewFrustum::getCorners(const float depth) const {
glm::vec3 normal = glm::normalize(_direction);
auto getCorner = [&](enum::BoxVertex nearCorner, enum::BoxVertex farCorner) {
@ -750,3 +752,98 @@ void ViewFrustum::invalidate() {
}
_centerSphereRadius = -1.0e6f; // -10^6 should be negative enough
}
void ViewFrustum::getSidePlanes(::Plane planes[4]) const {
planes[0] = _planes[TOP_PLANE];
planes[1] = _planes[BOTTOM_PLANE];
planes[2] = _planes[LEFT_PLANE];
planes[3] = _planes[RIGHT_PLANE];
}
void ViewFrustum::getTransformedSidePlanes(const Transform& transform, ::Plane planes[4]) const {
glm::mat4 normalTransform;
transform.getInverseTransposeMatrix(normalTransform);
getSidePlanes(planes);
for (auto i = 0; i < 4; 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.
auto transformedNormal = normalTransform * Transform::Vec4(planes[i].getNormal(), 0.0f);
auto planePoint = transform.transform(planes[i].getPoint());
glm::vec3 planeNormal(transformedNormal.x, transformedNormal.y, transformedNormal.z);
planes[i].setNormalAndPoint(planeNormal, planePoint);
}
}
void ViewFrustum::getUniformlyTransformedSidePlanes(const Transform& transform, ::Plane planes[4]) const {
getSidePlanes(planes);
for (auto i = 0; i < 4; 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.
auto planeNormal = transform.transformDirection(planes[i].getNormal());
auto planePoint = transform.transform(planes[i].getPoint());
planes[i].setNormalAndPoint(planeNormal, planePoint);
}
}
void ViewFrustum::tesselateSides(Triangle triangles[8]) const {
tesselateSides(_cornersWorld, triangles);
}
void ViewFrustum::tesselateSides(const Transform& transform, Triangle triangles[8]) const {
glm::vec3 points[8];
for (auto i = 0; i < 8; i++) {
points[i] = transform.transform(_cornersWorld[i]);
}
tesselateSides(points, triangles);
}
void ViewFrustum::tesselateSidesAndFar(const Transform& transform, Triangle triangles[10], float farDistance) const {
glm::vec3 points[8];
// First 4 points are at near
for (auto i = 0; i < 4; i++) {
points[i] = transform.transform(_cornersWorld[i]);
}
auto farCorners = getCorners(farDistance);
points[BOTTOM_LEFT_FAR] = transform.transform(farCorners.bottomLeft);
points[BOTTOM_RIGHT_FAR] = transform.transform(farCorners.bottomRight);
points[TOP_LEFT_FAR] = transform.transform(farCorners.topLeft);
points[TOP_RIGHT_FAR] = transform.transform(farCorners.topRight);
tesselateSides(points, triangles);
// Add far side
triangles[8].v0 = points[BOTTOM_LEFT_FAR];
triangles[8].v1 = points[BOTTOM_RIGHT_FAR];
triangles[8].v2 = points[TOP_RIGHT_FAR];
triangles[9].v0 = points[BOTTOM_LEFT_FAR];
triangles[9].v1 = points[TOP_LEFT_FAR];
triangles[9].v2 = points[TOP_RIGHT_FAR];
}
void ViewFrustum::tesselateSides(const glm::vec3 points[8], Triangle triangles[8]) {
static_assert(BOTTOM_RIGHT_NEAR == (BOTTOM_LEFT_NEAR + 1), "Assuming a certain sequence in corners");
static_assert(TOP_RIGHT_NEAR == (BOTTOM_RIGHT_NEAR + 1), "Assuming a certain sequence in corners");
static_assert(TOP_LEFT_NEAR == (TOP_RIGHT_NEAR + 1), "Assuming a certain sequence in corners");
static_assert(BOTTOM_RIGHT_FAR == (BOTTOM_LEFT_FAR + 1), "Assuming a certain sequence in corners");
static_assert(TOP_RIGHT_FAR == (BOTTOM_RIGHT_FAR + 1), "Assuming a certain sequence in corners");
static_assert(TOP_LEFT_FAR == (TOP_RIGHT_FAR + 1), "Assuming a certain sequence in corners");
static const int triangleVertexIndices[8][3] = {
{ BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR },{ BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR },
{ BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR },{ BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR },
{ TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_RIGHT_FAR },{ TOP_LEFT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR },
{ BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR },{ BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR }
};
for (auto i = 0; i < 8; i++) {
auto& triangle = triangles[i];
auto vertexIndices = triangleVertexIndices[i];
triangle.v0 = points[vertexIndices[0]];
triangle.v1 = points[vertexIndices[1]];
triangle.v2 = points[vertexIndices[2]];
}
}

View file

@ -33,9 +33,6 @@ const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f;
const float DEFAULT_NEAR_CLIP = 0.08f;
const float DEFAULT_FAR_CLIP = 16384.0f;
// the "ViewFrustum" has a "keyhole" shape: a regular frustum for stuff that is "visible" with
// a central sphere for stuff that is nearby (for physics simulation).
class ViewFrustum {
public:
// setters for camera attributes
@ -74,7 +71,7 @@ public:
glm::vec3 bottomRight;
// Get the corners depth units from frustum position, along frustum orientation
};
const Corners getCorners(const float& depth) const;
const Corners getCorners(const float depth) const;
// getters for corners
const glm::vec3& getFarTopLeft() const { return _cornersWorld[TOP_LEFT_FAR]; }
@ -90,6 +87,10 @@ public:
void setCenterRadius(float radius) { _centerSphereRadius = radius; }
float getCenterRadius() const { return _centerSphereRadius; }
void tesselateSides(Triangle triangles[8]) const;
void tesselateSides(const Transform& transform, Triangle triangles[8]) const;
void tesselateSidesAndFar(const Transform& transform, Triangle triangles[10], float farDistance) const;
void calculate();
typedef enum { OUTSIDE = 0, INTERSECT, INSIDE } intersection;
@ -134,6 +135,12 @@ public:
enum PlaneIndex { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE, NUM_PLANES };
const ::Plane* getPlanes() const { return _planes; }
void getSidePlanes(::Plane planes[4]) const;
// Transform can have a different scale value in X,Y,Z components
void getTransformedSidePlanes(const Transform& transform, ::Plane planes[4]) const;
// Transform is assumed to have the same scale value in all three X,Y,Z components, which
// allows for a faster computation.
void getUniformlyTransformedSidePlanes(const Transform& transform, ::Plane planes[4]) const;
void invalidate(); // causes all reasonable intersection tests to fail
@ -175,6 +182,8 @@ private:
template <typename TBOX>
CubeProjectedPolygon computeProjectedPolygon(const TBOX& box) const;
static void tesselateSides(const glm::vec3 points[8], Triangle triangles[8]);
};
using ViewFrustumPointer = std::shared_ptr<ViewFrustum>;

View file

@ -10,6 +10,52 @@
#include "Camera.h"
/**jsdoc
* <p>Camera modes affect the position of the camera and the controls for camera movement. The camera can be in one of the
* following modes:</p>
* <table>
* <thead>
* <tr>
* <th>Mode</th>
* <th>String</th>
* <th>Description</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><strong>First&nbsp;Person</strong></td>
* <td><code>"first&nbsp;person"</code></td>
* <td>The camera is positioned such that you have the same view as your avatar. The camera moves and rotates with your
* avatar.</td>
* </tr>
* <tr>
* <td><strong>Third&nbsp;Person</strong></td>
* <td><code>"third&nbsp;person"</code></td>
* <td>The camera is positioned such that you have a view from just behind your avatar. The camera moves and rotates with
* your avatar.</td>
* </tr>
* <tr>
* <td><strong>Mirror</strong></td>
* <td><code>"mirror"</code></td>
* <td>The camera is positioned such that you are looking directly at your avatar. The camera moves and rotates with your
* avatar.</td>
* </tr>
* <tr>
* <td><strong>Independent</strong></td>
* <td><code>"independent"</code></td>
* <td>The camera's position and orientation don't change with your avatar movement. Instead, they can be set via
* scripting.</td>
* </tr>
* <tr>
* <td><strong>Entity</strong></td>
* <td><code>"entity"</code></td>
* <td>The camera's position and orientation are set to be the same as a specified entity's, and move with the entity as
* it moves.
* </tr>
* </tbody>
* </table>
* @typedef {string} Camera.Mode
*/
CameraMode stringToMode(const QString& mode) {
if (mode == "third person") {
return CAMERA_MODE_THIRD_PERSON;
@ -131,6 +177,18 @@ void Camera::loadViewFrustum(ViewFrustum& frustum) const {
frustum.calculate();
}
/**jsdoc
* A ViewFrustum has a "keyhole" shape: a regular frustum for stuff that is visible plus a central sphere for stuff that is
* nearby (for physics simulation).
*
* @typedef {object} ViewFrustum
* @property {Vec3} position - The location of the frustum's apex.
* @property {Quat} orientation - The direction that the frustum is looking at.
* @property {number} centerRadius - Center radius of the keyhole in meters.
* @property {number} fieldOfView - Horizontal field of view in degrees.
* @property {number} aspectRatio - Aspect ratio of the frustum.
* @property {Mat4} projection - The projection matrix for the view defined by the frustum.
*/
QVariantMap Camera::getViewFrustum() {
ViewFrustum frustum;
loadViewFrustum(frustum);

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