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:
commit
7c29a1b09a
116 changed files with 5040 additions and 1581 deletions
assignment-client/src/avatars
interface
resources
src
FancyCamera.h
commerce
scripting
ClipboardScriptingInterface.cppClipboardScriptingInterface.hMenuScriptingInterface.hSelectionScriptingInterface.cppSelectionScriptingInterface.hWalletScriptingInterface.cppWalletScriptingInterface.h
ui/overlays
Base3DOverlay.cppBase3DOverlay.hBillboardable.cppCircle3DOverlay.cppCircle3DOverlay.hContextOverlayInterface.cppContextOverlayInterface.hCube3DOverlay.cppGrid3DOverlay.cppImage3DOverlay.cppImageOverlay.cppLine3DOverlay.cppModelOverlay.cppModelOverlay.hOverlay.cppOverlay.hOverlay2D.cppOverlay2D.hOverlays.cppOverlays.hOverlaysPayload.cppPanelAttachable.cppPlanar3DOverlay.cppPlanar3DOverlay.hQmlOverlay.cppRectangle3DOverlay.cppRectangleOverlay.cppShape3DOverlay.cppSphere3DOverlay.cppText3DOverlay.cppTextOverlay.cppVolume3DOverlay.cppWeb3DOverlay.cpp
libraries
baking/src
entities-renderer/src
gpu-gl/src/gpu/gl
image/src/image
model-networking/src/model-networking
render-utils/src
DebugDeferredBuffer.cppDebugDeferredBuffer.hDeferredLightingEffect.cppGeometryCache.cppHighlight.slhHighlightEffect.cppHighlight_shared.slhLightStage.cppLightStage.hRenderDeferredTask.cppRenderDeferredTask.hRenderShadowTask.cppRenderShadowTask.hRenderViewTask.cppShadow.slhShadowCore.slhShadows_shared.slhdebug_deferred_buffer.slfdirectional_ambient_light_shadow.slfdirectional_skybox_light_shadow.slf
render/src/render
CullTask.cppCullTask.hDrawTask.cppDrawTask.hHighlightStage.cppHighlightStage.hHighlightStyle.hItem.hRenderFetchCullSortTask.cppScene.cppSortTask.cpp
script-engine/src
shared/src
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 === ''
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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 > 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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) { }
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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@>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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@>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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>());
|
||||
|
|
|
@ -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@>
|
||||
|
|
96
libraries/render-utils/src/ShadowCore.slh
Normal file
96
libraries/render-utils/src/ShadowCore.slh
Normal 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@>
|
34
libraries/render-utils/src/Shadows_shared.slh
Normal file
34
libraries/render-utils/src/Shadows_shared.slh
Normal 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 !>
|
||||
//
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 <>
|
||||
|
|
|
@ -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]];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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 Person</strong></td>
|
||||
* <td><code>"first 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 Person</strong></td>
|
||||
* <td><code>"third 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
Loading…
Reference in a new issue