Extend Overlays API to allow for 3D UI panels.

Currently, only BillboardOverlays can be added to a panel, but more
types of overlays will be supported in the future.
This commit is contained in:
Zander Otavka 2015-07-13 16:38:16 -07:00
parent dff6b0a456
commit 173a79867c
8 changed files with 530 additions and 41 deletions

View file

@ -0,0 +1,173 @@
//
// floatingUI.js
// examples/example/ui
//
// Created by Alexander Otavka
// Copyright 2015 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
//
Script.include(["../../libraries/globals.js"]);
var BG_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/card-bg.svg";
var RED_DOT_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/red-dot.svg";
var BLUE_SQUARE_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/blue-square.svg";
var BLANK_ROTATION = { x: 0, y: 0, z: 0, w: 0 };
function isBlank(rotation) {
return rotation.x == BLANK_ROTATION.x &&
rotation.y == BLANK_ROTATION.y &&
rotation.z == BLANK_ROTATION.z &&
rotation.w == BLANK_ROTATION.w;
}
var panel = Overlays.addPanel({
offsetPosition: { x: 0, y: 0, z: 1 },
});
var panelChildren = [];
var bg = Overlays.addOverlay("billboard", {
url: BG_IMAGE_URL,
dimensions: {
x: 0.5,
y: 0.5,
},
isFacingAvatar: false,
visible: true,
alpha: 1.0,
ignoreRayIntersection: false,
attachedPanel: panel,
});
panelChildren.push(bg);
var redDot = Overlays.addOverlay("billboard", {
url: RED_DOT_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
visible: true,
alpha: 1.0,
ignoreRayIntersection: false,
attachedPanel: panel,
offsetPosition: {
x: -0.15,
y: -0.15,
z: -0.001
}
});
panelChildren.push(redDot);
var redDot2 = Overlays.addOverlay("billboard", {
url: RED_DOT_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
visible: true,
alpha: 1.0,
ignoreRayIntersection: false,
attachedPanel: panel,
offsetPosition: {
x: -0.15,
y: 0,
z: -0.001
}
});
panelChildren.push(redDot2);
var blueSquare = Overlays.addOverlay("billboard", {
url: BLUE_SQUARE_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
visible: true,
alpha: 1.0,
ignoreRayIntersection: false,
attachedPanel: panel,
offsetPosition: {
x: 0.1,
y: 0,
z: -0.001
}
});
panelChildren.push(blueSquare);
var blueSquare2 = Overlays.addOverlay("billboard", {
url: BLUE_SQUARE_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
visible: true,
alpha: 1.0,
ignoreRayIntersection: false,
attachedPanel: panel,
offsetPosition: {
x: 0.1,
y: 0.11,
z: -0.001
}
});
panelChildren.push(blueSquare2);
var blueSquare3 = Overlays.addOverlay("billboard", {
url: BLUE_SQUARE_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
visible: true,
alpha: 1.0,
ignoreRayIntersection: false,
attachedPanel: panel,
offsetPosition: {
x: -0.01,
y: 0.11,
z: -0.001
}
});
panelChildren.push(blueSquare3);
Controller.mousePressEvent.connect(function(event) {
if (event.isRightButton) {
var newOffsetRotation = BLANK_ROTATION;
if (isBlank(Overlays.getPanelProperty(panel, "offsetRotation"))) {
newOffsetRotation = Quat.multiply(MyAvatar.orientation, { x: 0, y: 1, z: 0, w: 0 });
}
Overlays.editPanel(panel, {
offsetRotation: newOffsetRotation
});
} else if (event.isLeftButton) {
var pickRay = Camera.computePickRay(event.x, event.y)
var rayPickResult = Overlays.findRayIntersection(pickRay);
print(String(rayPickResult.overlayID));
if (rayPickResult.intersects) {
for (var i in panelChildren) {
if (panelChildren[i] == rayPickResult.overlayID) {
var oldPos = Overlays.getProperty(rayPickResult.overlayID, "offsetPosition");
var newPos = {
x: Number(oldPos.x),
y: Number(oldPos.y),
z: Number(oldPos.z) + 0.1
}
Overlays.editOverlay(rayPickResult.overlayID, { offsetPosition: newPos });
}
}
}
}
});
Script.scriptEnding.connect(function() {
Overlays.deletePanel(panel);
});

View file

@ -36,6 +36,13 @@ BillboardOverlay::BillboardOverlay(const BillboardOverlay* billboardOverlay) :
{
}
void BillboardOverlay::update(float deltatime) {
glm::vec3 newPos = getTranslatedPosition(Application::getInstance()->getAvatarPosition());
if (newPos != glm::vec3()) {
setPosition(newPos);
}
}
void BillboardOverlay::render(RenderArgs* args) {
if (!_texture) {
_isLoaded = true;
@ -46,15 +53,17 @@ void BillboardOverlay::render(RenderArgs* args) {
return;
}
glm::vec3 newPos = getTranslatedPosition(Application::getInstance()->getAvatarPosition());
if (newPos != glm::vec3()) {
setPosition(newPos);
}
glm::quat rotation;
if (_isFacingAvatar) {
// LOL, quaternions are hard.
// rotate about vertical to face the camera
// glm::vec3 dPos = getPosition() - args->_viewFrustum->getPosition();
// dPos = glm::normalize(dPos);
// rotation = glm::quat(0, dPos.x, dPos.y, dPos.z);
rotation = args->_viewFrustum->getOrientation();
rotation *= glm::angleAxis(glm::pi<float>(), IDENTITY_UP);
// float horizontal = glm::sqrt(dPos.x * dPos.x + dPos.y + dPos.y);
// glm::vec3 zAxis = glm::vec3(0, 0, 1);
// rotation = rotationBetween(zAxis, dPos);
@ -70,9 +79,24 @@ void BillboardOverlay::render(RenderArgs* args) {
// rotation = yawQuat * pitchQuat;
// glm::vec3 pitch = glm::vec3(dPos.x, dPos.y, 0);
// rotation = glm::quat(glm::vec3(pitch, yaw, 0));
// rotate about vertical to be perpendicular to the camera
rotation = args->_viewFrustum->getOrientation();
rotation *= glm::angleAxis(glm::pi<float>(), IDENTITY_UP);
rotation *= getRotation();
} else {
rotation = getRotation();
if (getAttachedPanel()) {
rotation *= getAttachedPanel()->getOffsetRotation() *
getAttachedPanel()->getFacingRotation();
// if (getAttachedPanel()->getFacingRotation() != glm::quat(0, 0, 0, 0)) {
// rotation *= getAttachedPanel()->getFacingRotation();
// } else if (getAttachedPanel()->getOffsetRotation() != glm::quat(0, 0, 0, 0)) {
// rotation *= getAttachedPanel()->getOffsetRotation();
// } else {
// rotation *= Application::getInstance()->getCamera()->getOrientation() *
// glm::quat(0, 0, 1, 0);
// }
}
}
float imageWidth = _texture->getWidth();
@ -114,7 +138,7 @@ void BillboardOverlay::render(RenderArgs* args) {
Transform transform = _transform;
transform.postScale(glm::vec3(getDimensions(), 1.0f));
transform.setRotation(rotation);
batch->setModelTransform(transform);
batch->setResourceTexture(0, _texture->getGPUTexture());
@ -171,6 +195,21 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) {
if (isFacingAvatarValue.isValid()) {
_isFacingAvatar = isFacingAvatarValue.toVariant().toBool();
}
QScriptValue offsetPosition = properties.property("offsetPosition");
if (offsetPosition.isValid()) {
QScriptValue x = offsetPosition.property("x");
QScriptValue y = offsetPosition.property("y");
QScriptValue z = offsetPosition.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
setOffsetPosition(newPosition);
}
}
}
QScriptValue BillboardOverlay::getProperty(const QString& property) {
@ -183,6 +222,9 @@ QScriptValue BillboardOverlay::getProperty(const QString& property) {
if (property == "isFacingAvatar") {
return _isFacingAvatar;
}
if (property == "offsetPosition") {
return vec3toScriptValue(_scriptEngine, getOffsetPosition());
}
return Planar3DOverlay::getProperty(property);
}

View file

@ -15,8 +15,9 @@
#include <TextureCache.h>
#include "Planar3DOverlay.h"
#include "PanelAttachable.h"
class BillboardOverlay : public Planar3DOverlay {
class BillboardOverlay : public Planar3DOverlay, public PanelAttachable {
Q_OBJECT
public:
BillboardOverlay();
@ -24,6 +25,8 @@ public:
virtual void render(RenderArgs* args);
virtual void update(float deltatime);
// setters
void setURL(const QString& url);
void setIsFacingAvatar(bool isFacingAvatar) { _isFacingAvatar = isFacingAvatar; }

View file

@ -0,0 +1,89 @@
//
// FloatingUIPanel.cpp
// interface/src/ui/overlays
//
// Created by Zander Otavka on 7/2/15.
// Copyright 2014 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
//
#include "FloatingUIPanel.h"
#include <QVariant>
#include <RegisteredMetaTypes.h>
#include "Application.h"
glm::quat FloatingUIPanel::getOffsetRotation() const {
if (getActualOffsetRotation() == glm::quat(0, 0, 0, 0)) {
return Application::getInstance()->getCamera()->getOrientation() * glm::quat(0, 0, 1, 0);
}
return getActualOffsetRotation();
}
QScriptValue FloatingUIPanel::getProperty(const QString &property) {
if (property == "offsetPosition") {
return vec3toScriptValue(_scriptEngine, getOffsetPosition());
}
if (property == "offsetRotation") {
return quatToScriptValue(_scriptEngine, getActualOffsetRotation());
}
if (property == "facingRotation") {
return quatToScriptValue(_scriptEngine, getFacingRotation());
}
return QScriptValue();
}
void FloatingUIPanel::setProperties(const QScriptValue &properties) {
QScriptValue offsetPosition = properties.property("offsetPosition");
if (offsetPosition.isValid()) {
QScriptValue x = offsetPosition.property("x");
QScriptValue y = offsetPosition.property("y");
QScriptValue z = offsetPosition.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
setOffsetPosition(newPosition);
}
}
QScriptValue offsetRotation = properties.property("offsetRotation");
if (offsetRotation.isValid()) {
QScriptValue x = offsetRotation.property("x");
QScriptValue y = offsetRotation.property("y");
QScriptValue z = offsetRotation.property("z");
QScriptValue w = offsetRotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
setOffsetRotation(newRotation);
}
}
QScriptValue facingRotation = properties.property("facingRotation");
if (offsetRotation.isValid()) {
QScriptValue x = facingRotation.property("x");
QScriptValue y = facingRotation.property("y");
QScriptValue z = facingRotation.property("z");
QScriptValue w = facingRotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
setFacingRotation(newRotation);
}
}
}

View file

@ -0,0 +1,47 @@
//
// FloatingUIPanel.h
// interface/src/ui/overlays
//
// Created by Zander Otavka on 7/2/15.
// Copyright 2014 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
//
#ifndef hifi_FloatingUIPanel_h
#define hifi_FloatingUIPanel_h
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QScriptValue>
class FloatingUIPanel : public QObject {
Q_OBJECT
public:
typedef std::shared_ptr<FloatingUIPanel> Pointer;
QList<unsigned int> children;
void init(QScriptEngine* scriptEngine) { _scriptEngine = scriptEngine; }
glm::vec3 getOffsetPosition() const { return _offsetPosition; }
glm::quat getOffsetRotation() const;
glm::quat getActualOffsetRotation() const { return _offsetRotation; }
glm::quat getFacingRotation() const { return _facingRotation; }
void setOffsetPosition(glm::vec3 position) { _offsetPosition = position; };
void setOffsetRotation(glm::quat rotation) { _offsetRotation = rotation; };
void setFacingRotation(glm::quat rotation) { _facingRotation = rotation; };
QScriptValue getProperty(const QString& property);
void setProperties(const QScriptValue& properties);
private:
glm::vec3 _offsetPosition = glm::vec3(0, 0, 0);
glm::quat _offsetRotation = glm::quat(0, 0, 0, 0);
glm::quat _facingRotation = glm::quat(1, 0, 0, 0);
QScriptEngine* _scriptEngine;
};
#endif // hifi_FloatingUIPanel_h

View file

@ -48,6 +48,7 @@ Overlays::~Overlays() {
}
_overlaysHUD.clear();
_overlaysWorld.clear();
_panels.clear();
}
cleanupOverlaysToDelete();
@ -124,11 +125,36 @@ void Overlays::renderHUD(RenderArgs* renderArgs) {
}
}
Overlay::Pointer Overlays::getOverlay(unsigned int id) const {
if (_overlaysHUD.contains(id)) {
return _overlaysHUD[id];
}
if (_overlaysWorld.contains(id)) {
return _overlaysWorld[id];
}
return nullptr;
}
void Overlays::setAttachedPanel(Overlay* overlay, unsigned int overlayId, const QScriptValue& property) {
if (PanelAttachable* attachable = dynamic_cast<PanelAttachable*>(overlay)) {
if (property.isValid()) {
unsigned int attachedPanelId = property.toVariant().toUInt();
FloatingUIPanel* panel = nullptr;
if (_panels.contains(attachedPanelId)) {
panel = _panels[attachedPanelId].get();
panel->children.append(overlayId);
attachable->setAttachedPanel(panel);
} else {
attachable->getAttachedPanel()->children.removeAll(overlayId);
attachable->setAttachedPanel(nullptr);
}
}
}
}
unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) {
unsigned int thisID = 0;
Overlay* thisOverlay = NULL;
bool created = true;
if (type == "image") {
thisOverlay = new ImageOverlay();
} else if (type == "text") {
@ -153,16 +179,15 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
thisOverlay = new ModelOverlay();
} else if (type == "billboard") {
thisOverlay = new BillboardOverlay();
} else {
created = false;
}
if (created) {
if (thisOverlay) {
thisOverlay->setProperties(properties);
thisID = addOverlay(thisOverlay);
unsigned int overlayId = addOverlay(thisOverlay);
setAttachedPanel(thisOverlay, overlayId, properties.property("attachedPanel"));
return overlayId;
}
return thisID;
return 0;
}
unsigned int Overlays::addOverlay(Overlay* overlay) {
@ -189,17 +214,12 @@ unsigned int Overlays::addOverlay(Overlay* overlay) {
} else {
_overlaysHUD[thisID] = overlayPointer;
}
return thisID;
}
unsigned int Overlays::cloneOverlay(unsigned int id) {
Overlay::Pointer thisOverlay = NULL;
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay) {
return addOverlay(thisOverlay->createClone());
@ -210,14 +230,8 @@ unsigned int Overlays::cloneOverlay(unsigned int id) {
bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) {
QWriteLocker lock(&_lock);
Overlay::Pointer thisOverlay;
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay) {
if (thisOverlay->is3D()) {
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(thisOverlay);
@ -239,6 +253,8 @@ bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) {
thisOverlay->setProperties(properties);
}
setAttachedPanel(thisOverlay.get(), id, properties.property("attachedPanel"));
return true;
}
return false;
@ -302,15 +318,18 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
OverlayPropertyResult Overlays::getProperty(unsigned int id, const QString& property) {
OverlayPropertyResult result;
Overlay::Pointer thisOverlay;
Overlay::Pointer thisOverlay = getOverlay(id);
QReadLocker lock(&_lock);
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
if (thisOverlay) {
result.value = thisOverlay->getProperty(property);
if (property == "attachedPanel") {
if (FloatingUIPanel* panel = dynamic_cast<FloatingUIPanel*>(thisOverlay.get())) {
result.value = _panels.key(FloatingUIPanel::Pointer(panel));
} else {
result.value = 0;
}
} else {
result.value = thisOverlay->getProperty(property);
}
}
return result;
}
@ -456,12 +475,8 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R
bool Overlays::isLoaded(unsigned int id) {
QReadLocker lock(&_lock);
Overlay::Pointer thisOverlay = NULL;
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
} else {
Overlay::Pointer thisOverlay = getOverlay(id);
if (!thisOverlay) {
return false; // not found
}
return thisOverlay->isLoaded();
@ -483,3 +498,55 @@ QSizeF Overlays::textSize(unsigned int id, const QString& text) const {
}
return QSizeF(0.0f, 0.0f);
}
unsigned int Overlays::addPanel(FloatingUIPanel* panel) {
QWriteLocker lock(&_lock);
FloatingUIPanel::Pointer panelPointer(panel);
unsigned int thisID = _nextOverlayID;
_nextOverlayID++;
_panels[thisID] = panelPointer;
return thisID;
}
unsigned int Overlays::addPanel(const QScriptValue& properties) {
FloatingUIPanel* panel = new FloatingUIPanel();
panel->init(_scriptEngine);
panel->setProperties(properties);
return addPanel(panel);
}
void Overlays::editPanel(unsigned int panelId, const QScriptValue& properties) {
if (_panels.contains(panelId)) {
_panels[panelId]->setProperties(properties);
}
}
OverlayPropertyResult Overlays::getPanelProperty(unsigned int panelId, const QString& property) {
OverlayPropertyResult result;
if (_panels.contains(panelId)) {
FloatingUIPanel::Pointer thisPanel = _panels[panelId];
QReadLocker lock(&_lock);
result.value = thisPanel->getProperty(property);
}
return result;
}
void Overlays::deletePanel(unsigned int panelId) {
FloatingUIPanel::Pointer panelToDelete;
{
QWriteLocker lock(&_lock);
if (_panels.contains(panelId)) {
panelToDelete = _panels.take(panelId);
} else {
return;
}
}
while (!panelToDelete->children.isEmpty()) {
deleteOverlay(panelToDelete->children.takeLast());
}
}

View file

@ -16,6 +16,9 @@
#include "Overlay.h"
#include "FloatingUIPanel.h"
#include "PanelAttachable.h"
class PickRay;
class OverlayPropertyResult {
@ -90,12 +93,33 @@ public slots:
/// overlay; in meters if it is a 3D text overlay
QSizeF textSize(unsigned int id, const QString& text) const;
/// adds a panel that has already been created
unsigned int addPanel(FloatingUIPanel* panel);
/// creates and adds a panel based on a set of properties
unsigned int addPanel(const QScriptValue& properties);
/// edit the properties of a panel
void editPanel(unsigned int panelId, const QScriptValue& properties);
/// get a property of a panel
OverlayPropertyResult getPanelProperty(unsigned int panelId, const QString& property);
/// deletes a panel and all child overlays
void deletePanel(unsigned int panelId);
private:
void cleanupOverlaysToDelete();
Overlay::Pointer getOverlay(unsigned int id) const;
void setAttachedPanel(Overlay* overlay, unsigned int overlayId, const QScriptValue& property);
QMap<unsigned int, Overlay::Pointer> _overlaysHUD;
QMap<unsigned int, Overlay::Pointer> _overlaysWorld;
QMap<unsigned int, FloatingUIPanel::Pointer> _panels;
QList<Overlay::Pointer> _overlaysToDelete;
unsigned int _nextOverlayID;
QReadWriteLock _lock;
QReadWriteLock _deleteLock;
QScriptEngine* _scriptEngine;

View file

@ -0,0 +1,44 @@
//
// PanelAttachable.h
// interface/src/ui/overlays
//
// Created by Zander Otavka on 7/1/15.
// Copyright 2014 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
//
#ifndef hifi_PanelAttachable_h
#define hifi_PanelAttachable_h
#include "FloatingUIPanel.h"
#include <glm/glm.hpp>
class PanelAttachable {
public:
glm::vec3 getOffsetPosition() const { return _offsetPosition; }
void setOffsetPosition(glm::vec3 position) { _offsetPosition = position; }
FloatingUIPanel* getAttachedPanel() const { return _attachedPanel; }
void setAttachedPanel(FloatingUIPanel* panel) { _attachedPanel = panel; }
glm::vec3 getTranslatedPosition(glm::vec3 avatarPosition) {
if (getAttachedPanel()) {
glm::vec3 totalOffsetPosition =
getAttachedPanel()->getFacingRotation() * getOffsetPosition() +
getAttachedPanel()->getOffsetPosition();
return getAttachedPanel()->getOffsetRotation() * totalOffsetPosition +
avatarPosition;
}
return glm::vec3();
}
private:
FloatingUIPanel* _attachedPanel = nullptr;
glm::vec3 _offsetPosition = glm::vec3(0, 0, 0);
};
#endif // hifi_PanelAttachable_h