mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-04 04:24:47 +02:00
commit
3b7acae8a2
22 changed files with 1027 additions and 462 deletions
|
@ -137,6 +137,7 @@ var toolBar = (function () {
|
||||||
newSphereButton,
|
newSphereButton,
|
||||||
newLightButton,
|
newLightButton,
|
||||||
newTextButton,
|
newTextButton,
|
||||||
|
newWebButton,
|
||||||
newZoneButton,
|
newZoneButton,
|
||||||
browseMarketplaceButton;
|
browseMarketplaceButton;
|
||||||
|
|
||||||
|
@ -204,6 +205,16 @@ var toolBar = (function () {
|
||||||
alpha: 0.9,
|
alpha: 0.9,
|
||||||
visible: false
|
visible: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
newWebButton = toolBar.addTool({
|
||||||
|
imageURL: "https://s3.amazonaws.com/Oculus/earth17.svg",
|
||||||
|
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||||
|
width: toolWidth,
|
||||||
|
height: toolHeight,
|
||||||
|
alpha: 0.9,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
|
||||||
newZoneButton = toolBar.addTool({
|
newZoneButton = toolBar.addTool({
|
||||||
imageURL: toolIconUrl + "zonecube_text.svg",
|
imageURL: toolIconUrl + "zonecube_text.svg",
|
||||||
subImage: { x: 0, y: 128, width: 128, height: 128 },
|
subImage: { x: 0, y: 128, width: 128, height: 128 },
|
||||||
|
@ -253,6 +264,7 @@ var toolBar = (function () {
|
||||||
toolBar.showTool(newSphereButton, doShow);
|
toolBar.showTool(newSphereButton, doShow);
|
||||||
toolBar.showTool(newLightButton, doShow);
|
toolBar.showTool(newLightButton, doShow);
|
||||||
toolBar.showTool(newTextButton, doShow);
|
toolBar.showTool(newTextButton, doShow);
|
||||||
|
toolBar.showTool(newWebButton, doShow);
|
||||||
toolBar.showTool(newZoneButton, doShow);
|
toolBar.showTool(newZoneButton, doShow);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -425,6 +437,22 @@ var toolBar = (function () {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newWebButton === toolBar.clicked(clickedOverlay)) {
|
||||||
|
var position = getPositionToCreateEntity();
|
||||||
|
|
||||||
|
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||||
|
placingEntityID = Entities.addEntity({
|
||||||
|
type: "Web",
|
||||||
|
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||||
|
dimensions: { x: 1.6, y: 0.9, z: 0.01 },
|
||||||
|
sourceUrl: "https://highfidelity.com/",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
print("Can't create Web Entity: would be out of bounds.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (newZoneButton === toolBar.clicked(clickedOverlay)) {
|
if (newZoneButton === toolBar.clicked(clickedOverlay)) {
|
||||||
var position = getPositionToCreateEntity();
|
var position = getPositionToCreateEntity();
|
||||||
|
|
||||||
|
|
|
@ -279,6 +279,10 @@
|
||||||
var elModelTextures = document.getElementById("property-model-textures");
|
var elModelTextures = document.getElementById("property-model-textures");
|
||||||
var elModelOriginalTextures = document.getElementById("property-model-original-textures");
|
var elModelOriginalTextures = document.getElementById("property-model-original-textures");
|
||||||
|
|
||||||
|
var elWebSections = document.querySelectorAll(".web-section");
|
||||||
|
allSections.push(elModelSections);
|
||||||
|
var elWebSourceURL = document.getElementById("property-web-source-url");
|
||||||
|
|
||||||
var elTextSections = document.querySelectorAll(".text-section");
|
var elTextSections = document.querySelectorAll(".text-section");
|
||||||
allSections.push(elTextSections);
|
allSections.push(elTextSections);
|
||||||
var elTextText = document.getElementById("property-text-text");
|
var elTextText = document.getElementById("property-text-text");
|
||||||
|
@ -468,6 +472,12 @@
|
||||||
elModelAnimationSettings.value = properties.animationSettings;
|
elModelAnimationSettings.value = properties.animationSettings;
|
||||||
elModelTextures.value = properties.textures;
|
elModelTextures.value = properties.textures;
|
||||||
elModelOriginalTextures.value = properties.originalTextures;
|
elModelOriginalTextures.value = properties.originalTextures;
|
||||||
|
} else if (properties.type == "Web") {
|
||||||
|
for (var i = 0; i < elTextSections.length; i++) {
|
||||||
|
elWebSections[i].style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
elWebSourceURL.value = properties.sourceUrl;
|
||||||
} else if (properties.type == "Text") {
|
} else if (properties.type == "Text") {
|
||||||
for (var i = 0; i < elTextSections.length; i++) {
|
for (var i = 0; i < elTextSections.length; i++) {
|
||||||
elTextSections[i].style.display = 'block';
|
elTextSections[i].style.display = 'block';
|
||||||
|
@ -654,6 +664,8 @@
|
||||||
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent'));
|
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent'));
|
||||||
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff'));
|
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff'));
|
||||||
|
|
||||||
|
elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl'));
|
||||||
|
|
||||||
elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL'));
|
elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL'));
|
||||||
elShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType'));
|
elShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType'));
|
||||||
elCompoundShapeURL.addEventListener('change', createEmitTextPropertyUpdateFunction('compoundShapeURL'));
|
elCompoundShapeURL.addEventListener('change', createEmitTextPropertyUpdateFunction('compoundShapeURL'));
|
||||||
|
@ -1019,7 +1031,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="web-section property">
|
||||||
|
<div class="label">Source URL</div>
|
||||||
|
<div class="value">
|
||||||
|
<input type="text" id="property-web-source-url" class="url"></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="model-section property">
|
<div class="model-section property">
|
||||||
<div class="label">Model URL</div>
|
<div class="label">Model URL</div>
|
||||||
|
|
10
interface/resources/qml/WebEntity.qml
Normal file
10
interface/resources/qml/WebEntity.qml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick.Controls 1.2
|
||||||
|
import QtWebKit 3.0
|
||||||
|
|
||||||
|
WebView {
|
||||||
|
id: root
|
||||||
|
objectName: "webview"
|
||||||
|
anchors.fill: parent
|
||||||
|
url: "about:blank"
|
||||||
|
}
|
|
@ -341,6 +341,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||||
_domainConnectionRefusals(QList<QString>()),
|
_domainConnectionRefusals(QList<QString>()),
|
||||||
_maxOctreePPS(maxOctreePacketsPerSecond.get())
|
_maxOctreePPS(maxOctreePacketsPerSecond.get())
|
||||||
{
|
{
|
||||||
|
setInstance(this);
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
installNativeEventFilter(&MyNativeEventFilter::getInstance());
|
installNativeEventFilter(&MyNativeEventFilter::getInstance());
|
||||||
#endif
|
#endif
|
||||||
|
@ -4692,3 +4693,8 @@ void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) {
|
||||||
int Application::getMaxOctreePacketsPerSecond() {
|
int Application::getMaxOctreePacketsPerSecond() {
|
||||||
return _maxOctreePPS;
|
return _maxOctreePPS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qreal Application::getDevicePixelRatio() {
|
||||||
|
return _window ? _window->windowHandle()->devicePixelRatio() : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -300,7 +300,7 @@ public:
|
||||||
virtual const glm::vec3& getAvatarPosition() const { return _myAvatar->getPosition(); }
|
virtual const glm::vec3& getAvatarPosition() const { return _myAvatar->getPosition(); }
|
||||||
virtual void overrideEnvironmentData(const EnvironmentData& newData) { _environment.override(newData); }
|
virtual void overrideEnvironmentData(const EnvironmentData& newData) { _environment.override(newData); }
|
||||||
virtual void endOverrideEnvironmentData() { _environment.endOverride(); }
|
virtual void endOverrideEnvironmentData() { _environment.endOverride(); }
|
||||||
|
virtual qreal getDevicePixelRatio();
|
||||||
|
|
||||||
NodeBounds& getNodeBoundsDisplay() { return _nodeBoundsDisplay; }
|
NodeBounds& getNodeBoundsDisplay() { return _nodeBoundsDisplay; }
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,11 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid
|
||||||
}
|
}
|
||||||
|
|
||||||
_webView->setPage(new DataWebPage());
|
_webView->setPage(new DataWebPage());
|
||||||
_webView->setUrl(url);
|
if (!url.startsWith("http") && !url.startsWith("file://")) {
|
||||||
|
_webView->setUrl(QUrl::fromLocalFile(url));
|
||||||
|
} else {
|
||||||
|
_webView->setUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater);
|
connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater);
|
||||||
connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared,
|
connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared,
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "RenderableParticleEffectEntityItem.h"
|
#include "RenderableParticleEffectEntityItem.h"
|
||||||
#include "RenderableSphereEntityItem.h"
|
#include "RenderableSphereEntityItem.h"
|
||||||
#include "RenderableTextEntityItem.h"
|
#include "RenderableTextEntityItem.h"
|
||||||
|
#include "RenderableWebEntityItem.h"
|
||||||
#include "RenderableZoneEntityItem.h"
|
#include "RenderableZoneEntityItem.h"
|
||||||
#include "RenderableLineEntityItem.h"
|
#include "RenderableLineEntityItem.h"
|
||||||
#include "EntitiesRendererLogging.h"
|
#include "EntitiesRendererLogging.h"
|
||||||
|
@ -58,6 +59,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
||||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
|
||||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory)
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory)
|
||||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory)
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory)
|
||||||
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, RenderableWebEntityItem::factory)
|
||||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory)
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory)
|
||||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Zone, RenderableZoneEntityItem::factory)
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(Zone, RenderableZoneEntityItem::factory)
|
||||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Line, RenderableLineEntityItem::factory)
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(Line, RenderableLineEntityItem::factory)
|
||||||
|
|
147
libraries/entities-renderer/src/RenderableWebEntityItem.cpp
Normal file
147
libraries/entities-renderer/src/RenderableWebEntityItem.cpp
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/05/12
|
||||||
|
// Copyright 2013 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 "RenderableWebEntityItem.h"
|
||||||
|
|
||||||
|
#include <glm/gtx/quaternion.hpp>
|
||||||
|
|
||||||
|
#include <gpu/GPUConfig.h>
|
||||||
|
|
||||||
|
#include <DeferredLightingEffect.h>
|
||||||
|
#include <GeometryCache.h>
|
||||||
|
#include <PerfStat.h>
|
||||||
|
#include <TextRenderer.h>
|
||||||
|
#include <OffscreenQmlSurface.h>
|
||||||
|
#include <AbstractViewStateInterface.h>
|
||||||
|
#include <GLMHelpers.h>
|
||||||
|
#include <PathUtils.h>
|
||||||
|
#include <TextureCache.h>
|
||||||
|
#include <gpu/GLBackend.h>
|
||||||
|
|
||||||
|
const int FIXED_FONT_POINT_SIZE = 40;
|
||||||
|
const float DPI = 30.47;
|
||||||
|
const float METERS_TO_INCHES = 39.3701;
|
||||||
|
|
||||||
|
EntityItem* RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||||
|
return new RenderableWebEntityItem(entityID, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
|
||||||
|
WebEntityItem(entityItemID, properties) {
|
||||||
|
qDebug() << "Created web entity " << getID();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderableWebEntityItem::~RenderableWebEntityItem() {
|
||||||
|
if (_webSurface) {
|
||||||
|
_webSurface->pause();
|
||||||
|
_webSurface->disconnect(_connection);
|
||||||
|
// After the disconnect, ensure that we have the latest texture by acquiring the
|
||||||
|
// lock used when updating the _texture value
|
||||||
|
_textureLock.lock();
|
||||||
|
_textureLock.unlock();
|
||||||
|
// The lifetime of the QML surface MUST be managed by the main thread
|
||||||
|
// Additionally, we MUST use local variables copied by value, rather than
|
||||||
|
// member variables, since they would implicitly refer to a this that
|
||||||
|
// is no longer valid
|
||||||
|
auto webSurface = _webSurface;
|
||||||
|
auto texture = _texture;
|
||||||
|
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface, texture] {
|
||||||
|
if (texture) {
|
||||||
|
webSurface->releaseTexture(texture);
|
||||||
|
}
|
||||||
|
webSurface->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
qDebug() << "Destroyed web entity " << getID();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderableWebEntityItem::render(RenderArgs* args) {
|
||||||
|
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||||
|
QSurface * currentSurface = currentContext->surface();
|
||||||
|
if (!_webSurface) {
|
||||||
|
_webSurface = new OffscreenQmlSurface();
|
||||||
|
_webSurface->create(currentContext);
|
||||||
|
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
||||||
|
_webSurface->load("WebEntity.qml");
|
||||||
|
_webSurface->resume();
|
||||||
|
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||||
|
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||||
|
_webSurface->lockTexture(textureId);
|
||||||
|
assert(!glGetError());
|
||||||
|
// TODO change to atomic<GLuint>?
|
||||||
|
withLock(_textureLock, [&] {
|
||||||
|
std::swap(_texture, textureId);
|
||||||
|
});
|
||||||
|
if (textureId) {
|
||||||
|
_webSurface->releaseTexture(textureId);
|
||||||
|
}
|
||||||
|
if (_texture) {
|
||||||
|
_webSurface->makeCurrent();
|
||||||
|
glBindTexture(GL_TEXTURE_2D, _texture);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
_webSurface->doneCurrent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 dims = glm::vec2(_dimensions);
|
||||||
|
dims *= METERS_TO_INCHES * DPI;
|
||||||
|
// The offscreen surface is idempotent for resizes (bails early
|
||||||
|
// if it's a no-op), so it's safe to just call resize every frame
|
||||||
|
// without worrying about excessive overhead.
|
||||||
|
_webSurface->resize(QSize(dims.x, dims.y));
|
||||||
|
currentContext->makeCurrent(currentSurface);
|
||||||
|
|
||||||
|
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
|
||||||
|
assert(getType() == EntityTypes::Web);
|
||||||
|
glm::vec3 position = getPosition();
|
||||||
|
glm::vec3 dimensions = getDimensions();
|
||||||
|
glm::vec3 halfDimensions = dimensions / 2.0f;
|
||||||
|
glm::quat rotation = getRotation();
|
||||||
|
|
||||||
|
//qCDebug(entitytree) << "RenderableWebEntityItem::render() id:" << getEntityItemID() << "text:" << getText();
|
||||||
|
|
||||||
|
glPushMatrix();
|
||||||
|
{
|
||||||
|
glTranslatef(position.x, position.y, position.z);
|
||||||
|
glm::vec3 axis = glm::axis(rotation);
|
||||||
|
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||||
|
|
||||||
|
float alpha = 1.0f;
|
||||||
|
static const glm::vec2 texMin(0);
|
||||||
|
static const glm::vec2 texMax(1);
|
||||||
|
glm::vec2 topLeft(-halfDimensions.x, -halfDimensions.y);
|
||||||
|
glm::vec2 bottomRight(halfDimensions.x, halfDimensions.y);
|
||||||
|
if (_texture) {
|
||||||
|
glEnable(GL_TEXTURE_2D);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, _texture);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
}
|
||||||
|
DependencyManager::get<GeometryCache>()->renderQuad(
|
||||||
|
topLeft, bottomRight, texMin, texMax, glm::vec4(1));
|
||||||
|
if (_texture) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
glEnable(GL_TEXTURE_2D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glPopMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderableWebEntityItem::setSourceUrl(const QString& value) {
|
||||||
|
if (_sourceUrl != value) {
|
||||||
|
_sourceUrl = value;
|
||||||
|
if (_webSurface) {
|
||||||
|
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
|
||||||
|
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
libraries/entities-renderer/src/RenderableWebEntityItem.h
Normal file
36
libraries/entities-renderer/src/RenderableWebEntityItem.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/05/12
|
||||||
|
// Copyright 2013 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_RenderableWebEntityItem_h
|
||||||
|
#define hifi_RenderableWebEntityItem_h
|
||||||
|
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
|
#include <WebEntityItem.h>
|
||||||
|
|
||||||
|
class OffscreenQmlSurface;
|
||||||
|
|
||||||
|
class RenderableWebEntityItem : public WebEntityItem {
|
||||||
|
public:
|
||||||
|
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||||
|
|
||||||
|
RenderableWebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
|
||||||
|
~RenderableWebEntityItem();
|
||||||
|
|
||||||
|
virtual void render(RenderArgs* args);
|
||||||
|
virtual void setSourceUrl(const QString& value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
OffscreenQmlSurface* _webSurface{ nullptr };
|
||||||
|
QMetaObject::Connection _connection;
|
||||||
|
uint32_t _texture{ 0 };
|
||||||
|
QMutex _textureLock;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // hifi_RenderableWebEntityItem_h
|
|
@ -86,6 +86,7 @@ EntityItemProperties::EntityItemProperties() :
|
||||||
CONSTRUCT_PROPERTY(keyLightDirection, ZoneEntityItem::DEFAULT_KEYLIGHT_DIRECTION),
|
CONSTRUCT_PROPERTY(keyLightDirection, ZoneEntityItem::DEFAULT_KEYLIGHT_DIRECTION),
|
||||||
CONSTRUCT_PROPERTY(name, ENTITY_ITEM_DEFAULT_NAME),
|
CONSTRUCT_PROPERTY(name, ENTITY_ITEM_DEFAULT_NAME),
|
||||||
CONSTRUCT_PROPERTY(backgroundMode, BACKGROUND_MODE_INHERIT),
|
CONSTRUCT_PROPERTY(backgroundMode, BACKGROUND_MODE_INHERIT),
|
||||||
|
CONSTRUCT_PROPERTY(sourceUrl, ""),
|
||||||
|
|
||||||
_id(UNKNOWN_ENTITY_ID),
|
_id(UNKNOWN_ENTITY_ID),
|
||||||
_idSet(false),
|
_idSet(false),
|
||||||
|
@ -328,6 +329,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||||
CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_AMBIENT_INTENSITY, keyLightAmbientIntensity);
|
CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_AMBIENT_INTENSITY, keyLightAmbientIntensity);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, keyLightDirection);
|
CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, keyLightDirection);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode);
|
CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode);
|
||||||
|
CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl);
|
||||||
|
|
||||||
changedProperties += _stage.getChangedProperties();
|
changedProperties += _stage.getChangedProperties();
|
||||||
changedProperties += _atmosphere.getChangedProperties();
|
changedProperties += _atmosphere.getChangedProperties();
|
||||||
|
@ -409,6 +411,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightAmbientIntensity);
|
COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightAmbientIntensity);
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(keyLightDirection);
|
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(keyLightDirection);
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(backgroundMode, getBackgroundModeAsString());
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(backgroundMode, getBackgroundModeAsString());
|
||||||
|
COPY_PROPERTY_TO_QSCRIPTVALUE(sourceUrl);
|
||||||
|
|
||||||
// Sitting properties support
|
// Sitting properties support
|
||||||
if (!skipDefaults) {
|
if (!skipDefaults) {
|
||||||
|
@ -510,6 +513,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
|
||||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(keyLightAmbientIntensity, setKeyLightAmbientIntensity);
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(keyLightAmbientIntensity, setKeyLightAmbientIntensity);
|
||||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(keyLightDirection, setKeyLightDirection);
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(keyLightDirection, setKeyLightDirection);
|
||||||
COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(backgroundMode, BackgroundMode);
|
COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(backgroundMode, BackgroundMode);
|
||||||
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(sourceUrl, setSourceUrl);
|
||||||
|
|
||||||
_stage.copyFromScriptValue(object, _defaultSettings);
|
_stage.copyFromScriptValue(object, _defaultSettings);
|
||||||
_atmosphere.copyFromScriptValue(object, _defaultSettings);
|
_atmosphere.copyFromScriptValue(object, _defaultSettings);
|
||||||
|
@ -666,7 +670,11 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
||||||
APPEND_ENTITY_PROPERTY(PROP_LOCKED, appendValue, properties.getLocked());
|
APPEND_ENTITY_PROPERTY(PROP_LOCKED, appendValue, properties.getLocked());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, appendValue, properties.getUserData());
|
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, appendValue, properties.getUserData());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, appendValue, properties.getSimulatorID());
|
APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, appendValue, properties.getSimulatorID());
|
||||||
|
|
||||||
|
if (properties.getType() == EntityTypes::Web) {
|
||||||
|
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, appendValue, properties.getSourceUrl());
|
||||||
|
}
|
||||||
|
|
||||||
if (properties.getType() == EntityTypes::Text) {
|
if (properties.getType() == EntityTypes::Text) {
|
||||||
APPEND_ENTITY_PROPERTY(PROP_TEXT, appendValue, properties.getText());
|
APPEND_ENTITY_PROPERTY(PROP_TEXT, appendValue, properties.getText());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, appendValue, properties.getLineHeight());
|
APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, appendValue, properties.getLineHeight());
|
||||||
|
@ -922,6 +930,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
||||||
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_USER_DATA, setUserData);
|
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_USER_DATA, setUserData);
|
||||||
READ_ENTITY_PROPERTY_UUID_TO_PROPERTIES(PROP_SIMULATOR_ID, setSimulatorID);
|
READ_ENTITY_PROPERTY_UUID_TO_PROPERTIES(PROP_SIMULATOR_ID, setSimulatorID);
|
||||||
|
|
||||||
|
if (properties.getType() == EntityTypes::Web) {
|
||||||
|
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_SOURCE_URL, setSourceUrl);
|
||||||
|
}
|
||||||
|
|
||||||
if (properties.getType() == EntityTypes::Text) {
|
if (properties.getType() == EntityTypes::Text) {
|
||||||
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_TEXT, setText);
|
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_TEXT, setText);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight);
|
||||||
|
@ -1073,6 +1085,7 @@ void EntityItemProperties::markAllChanged() {
|
||||||
_atmosphere.markAllChanged();
|
_atmosphere.markAllChanged();
|
||||||
_skybox.markAllChanged();
|
_skybox.markAllChanged();
|
||||||
|
|
||||||
|
_sourceUrlChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The maximum bounding cube for the entity, independent of it's rotation.
|
/// The maximum bounding cube for the entity, independent of it's rotation.
|
||||||
|
|
|
@ -52,6 +52,7 @@ class EntityItemProperties {
|
||||||
friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods
|
friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||||
friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods
|
friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||||
friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods
|
friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||||
|
friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||||
friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods
|
friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||||
public:
|
public:
|
||||||
EntityItemProperties();
|
EntityItemProperties();
|
||||||
|
@ -139,6 +140,7 @@ public:
|
||||||
DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup);
|
DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup);
|
||||||
DEFINE_PROPERTY_GROUP(Atmosphere, atmosphere, AtmospherePropertyGroup);
|
DEFINE_PROPERTY_GROUP(Atmosphere, atmosphere, AtmospherePropertyGroup);
|
||||||
DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup);
|
DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup);
|
||||||
|
DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString);
|
||||||
|
|
||||||
static QString getBackgroundModeString(BackgroundMode mode);
|
static QString getBackgroundModeString(BackgroundMode mode);
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,10 @@ enum EntityPropertyList {
|
||||||
PROP_SKYBOX_URL = PROP_ANIMATION_FPS,
|
PROP_SKYBOX_URL = PROP_ANIMATION_FPS,
|
||||||
PROP_STAGE_AUTOMATIC_HOURDAY = PROP_ANIMATION_FRAME_INDEX,
|
PROP_STAGE_AUTOMATIC_HOURDAY = PROP_ANIMATION_FRAME_INDEX,
|
||||||
|
|
||||||
|
// Aliases/Piggyback properties for Web. These properties intentionally reuse the enum values for
|
||||||
|
// other properties which will never overlap with each other.
|
||||||
|
PROP_SOURCE_URL = PROP_MODEL_URL,
|
||||||
|
|
||||||
// WARNING!!! DO NOT ADD PROPS_xxx here unless you really really meant to.... Add them UP above
|
// WARNING!!! DO NOT ADD PROPS_xxx here unless you really really meant to.... Add them UP above
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "ParticleEffectEntityItem.h"
|
#include "ParticleEffectEntityItem.h"
|
||||||
#include "SphereEntityItem.h"
|
#include "SphereEntityItem.h"
|
||||||
#include "TextEntityItem.h"
|
#include "TextEntityItem.h"
|
||||||
|
#include "WebEntityItem.h"
|
||||||
#include "ZoneEntityItem.h"
|
#include "ZoneEntityItem.h"
|
||||||
#include "LineEntityItem.h"
|
#include "LineEntityItem.h"
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ const QString ENTITY_TYPE_NAME_UNKNOWN = "Unknown";
|
||||||
// Register Entity the default implementations of entity types here...
|
// Register Entity the default implementations of entity types here...
|
||||||
REGISTER_ENTITY_TYPE(Model)
|
REGISTER_ENTITY_TYPE(Model)
|
||||||
REGISTER_ENTITY_TYPE(Box)
|
REGISTER_ENTITY_TYPE(Box)
|
||||||
|
REGISTER_ENTITY_TYPE(Web)
|
||||||
REGISTER_ENTITY_TYPE(Sphere)
|
REGISTER_ENTITY_TYPE(Sphere)
|
||||||
REGISTER_ENTITY_TYPE(Light)
|
REGISTER_ENTITY_TYPE(Light)
|
||||||
REGISTER_ENTITY_TYPE(Text)
|
REGISTER_ENTITY_TYPE(Text)
|
||||||
|
|
|
@ -37,6 +37,7 @@ public:
|
||||||
Text,
|
Text,
|
||||||
ParticleEffect,
|
ParticleEffect,
|
||||||
Zone,
|
Zone,
|
||||||
|
Web,
|
||||||
Line,
|
Line,
|
||||||
LAST = Line
|
LAST = Line
|
||||||
} EntityType;
|
} EntityType;
|
||||||
|
|
153
libraries/entities/src/WebEntityItem.cpp
Normal file
153
libraries/entities/src/WebEntityItem.cpp
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/05/12
|
||||||
|
// Copyright 2013 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 "WebEntityItem.h"
|
||||||
|
|
||||||
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <ByteCountCoding.h>
|
||||||
|
#include <PlaneShape.h>
|
||||||
|
|
||||||
|
#include "EntityTree.h"
|
||||||
|
#include "EntityTreeElement.h"
|
||||||
|
#include "EntitiesLogging.h"
|
||||||
|
|
||||||
|
|
||||||
|
const QString WebEntityItem::DEFAULT_SOURCE_URL("http://www.google.com");
|
||||||
|
|
||||||
|
EntityItem* WebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||||
|
EntityItem* result = new WebEntityItem(entityID, properties);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebEntityItem::WebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
|
||||||
|
EntityItem(entityItemID)
|
||||||
|
{
|
||||||
|
_type = EntityTypes::Web;
|
||||||
|
_created = properties.getCreated();
|
||||||
|
setProperties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f;
|
||||||
|
|
||||||
|
void WebEntityItem::setDimensions(const glm::vec3& value) {
|
||||||
|
// NOTE: Web Entities always have a "depth" of 1cm.
|
||||||
|
_dimensions = glm::vec3(value.x, value.y, WEB_ENTITY_ITEM_FIXED_DEPTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityItemProperties WebEntityItem::getProperties() const {
|
||||||
|
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
|
||||||
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl);
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||||
|
bool somethingChanged = false;
|
||||||
|
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||||
|
|
||||||
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl);
|
||||||
|
|
||||||
|
if (somethingChanged) {
|
||||||
|
bool wantDebug = false;
|
||||||
|
if (wantDebug) {
|
||||||
|
uint64_t now = usecTimestampNow();
|
||||||
|
int elapsed = now - getLastEdited();
|
||||||
|
qCDebug(entities) << "WebEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
||||||
|
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||||
|
}
|
||||||
|
setLastEdited(properties._lastEdited);
|
||||||
|
}
|
||||||
|
|
||||||
|
return somethingChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||||
|
ReadBitstreamToTreeParams& args,
|
||||||
|
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
||||||
|
|
||||||
|
int bytesRead = 0;
|
||||||
|
const unsigned char* dataAt = data;
|
||||||
|
|
||||||
|
READ_ENTITY_PROPERTY_STRING(PROP_SOURCE_URL, setSourceUrl);
|
||||||
|
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||||
|
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||||
|
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||||
|
requestedProperties += PROP_SOURCE_URL;
|
||||||
|
return requestedProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||||
|
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||||
|
EntityPropertyFlags& requestedProperties,
|
||||||
|
EntityPropertyFlags& propertyFlags,
|
||||||
|
EntityPropertyFlags& propertiesDidntFit,
|
||||||
|
int& propertyCount,
|
||||||
|
OctreeElement::AppendState& appendState) const {
|
||||||
|
|
||||||
|
bool successPropertyFits = true;
|
||||||
|
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, appendValue, _sourceUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
|
||||||
|
void** intersectedObject, bool precisionPicking) const {
|
||||||
|
|
||||||
|
RayIntersectionInfo rayInfo;
|
||||||
|
rayInfo._rayStart = origin;
|
||||||
|
rayInfo._rayDirection = direction;
|
||||||
|
rayInfo._rayLength = std::numeric_limits<float>::max();
|
||||||
|
|
||||||
|
PlaneShape plane;
|
||||||
|
|
||||||
|
const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f);
|
||||||
|
glm::vec3 normal = _rotation * UNROTATED_NORMAL;
|
||||||
|
plane.setNormal(normal);
|
||||||
|
plane.setPoint(getPosition()); // the position is definitely a point on our plane
|
||||||
|
|
||||||
|
bool intersects = plane.findRayIntersection(rayInfo);
|
||||||
|
|
||||||
|
if (intersects) {
|
||||||
|
glm::vec3 hitAt = origin + (direction * rayInfo._hitDistance);
|
||||||
|
// now we know the point the ray hit our plane
|
||||||
|
|
||||||
|
glm::mat4 rotation = glm::mat4_cast(getRotation());
|
||||||
|
glm::mat4 translation = glm::translate(getPosition());
|
||||||
|
glm::mat4 entityToWorldMatrix = translation * rotation;
|
||||||
|
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
|
||||||
|
|
||||||
|
glm::vec3 dimensions = getDimensions();
|
||||||
|
glm::vec3 registrationPoint = getRegistrationPoint();
|
||||||
|
glm::vec3 corner = -(dimensions * registrationPoint);
|
||||||
|
AABox entityFrameBox(corner, dimensions);
|
||||||
|
|
||||||
|
glm::vec3 entityFrameHitAt = glm::vec3(worldToEntityMatrix * glm::vec4(hitAt, 1.0f));
|
||||||
|
|
||||||
|
intersects = entityFrameBox.contains(entityFrameHitAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersects) {
|
||||||
|
distance = rayInfo._hitDistance;
|
||||||
|
}
|
||||||
|
return intersects;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebEntityItem::setSourceUrl(const QString& value) {
|
||||||
|
if (_sourceUrl != value) {
|
||||||
|
_sourceUrl = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& WebEntityItem::getSourceUrl() const { return _sourceUrl; }
|
59
libraries/entities/src/WebEntityItem.h
Normal file
59
libraries/entities/src/WebEntityItem.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/05/12
|
||||||
|
// Copyright 2013 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_WebEntityItem_h
|
||||||
|
#define hifi_WebEntityItem_h
|
||||||
|
|
||||||
|
#include "EntityItem.h"
|
||||||
|
|
||||||
|
class WebEntityItem : public EntityItem {
|
||||||
|
public:
|
||||||
|
static const QString DEFAULT_SOURCE_URL;
|
||||||
|
|
||||||
|
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||||
|
|
||||||
|
WebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
|
||||||
|
|
||||||
|
ALLOW_INSTANTIATION // This class can be instantiated
|
||||||
|
|
||||||
|
/// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately
|
||||||
|
virtual void setDimensions(const glm::vec3& value);
|
||||||
|
virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; }
|
||||||
|
|
||||||
|
// methods for getting/setting all properties of an entity
|
||||||
|
virtual EntityItemProperties getProperties() const;
|
||||||
|
virtual bool setProperties(const EntityItemProperties& properties);
|
||||||
|
|
||||||
|
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||||
|
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
|
||||||
|
|
||||||
|
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||||
|
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||||
|
EntityPropertyFlags& requestedProperties,
|
||||||
|
EntityPropertyFlags& propertyFlags,
|
||||||
|
EntityPropertyFlags& propertiesDidntFit,
|
||||||
|
int& propertyCount,
|
||||||
|
OctreeElement::AppendState& appendState) const;
|
||||||
|
|
||||||
|
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||||
|
ReadBitstreamToTreeParams& args,
|
||||||
|
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
|
||||||
|
|
||||||
|
virtual bool supportsDetailedRayIntersection() const { return true; }
|
||||||
|
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
|
||||||
|
void** intersectedObject, bool precisionPicking) const;
|
||||||
|
|
||||||
|
virtual void setSourceUrl(const QString& value);
|
||||||
|
const QString& getSourceUrl() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QString _sourceUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_WebEntityItem_h
|
20
libraries/render-utils/src/AbstractViewStateInterface.cpp
Normal file
20
libraries/render-utils/src/AbstractViewStateInterface.cpp
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis 2015/05/13
|
||||||
|
// 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 "AbstractViewStateInterface.h"
|
||||||
|
|
||||||
|
static AbstractViewStateInterface* INSTANCE{nullptr};
|
||||||
|
|
||||||
|
AbstractViewStateInterface* AbstractViewStateInterface::instance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbstractViewStateInterface::setInstance(AbstractViewStateInterface* instance) {
|
||||||
|
INSTANCE = instance;
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
#define hifi_AbstractViewStateInterface_h
|
#define hifi_AbstractViewStateInterface_h
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
class Transform;
|
class Transform;
|
||||||
class QThread;
|
class QThread;
|
||||||
|
@ -37,7 +40,7 @@ public:
|
||||||
/// overrides environment data
|
/// overrides environment data
|
||||||
virtual void overrideEnvironmentData(const EnvironmentData& newData) = 0;
|
virtual void overrideEnvironmentData(const EnvironmentData& newData) = 0;
|
||||||
virtual void endOverrideEnvironmentData() = 0;
|
virtual void endOverrideEnvironmentData() = 0;
|
||||||
|
|
||||||
/// gets the shadow view frustum for rendering the view state
|
/// gets the shadow view frustum for rendering the view state
|
||||||
virtual ViewFrustum* getShadowViewFrustum() = 0;
|
virtual ViewFrustum* getShadowViewFrustum() = 0;
|
||||||
|
|
||||||
|
@ -53,6 +56,12 @@ public:
|
||||||
virtual PickRay computePickRay(float x, float y) const = 0;
|
virtual PickRay computePickRay(float x, float y) const = 0;
|
||||||
|
|
||||||
virtual const glm::vec3& getAvatarPosition() const = 0;
|
virtual const glm::vec3& getAvatarPosition() const = 0;
|
||||||
|
|
||||||
|
virtual void postLambdaEvent(std::function<void()> f) = 0;
|
||||||
|
virtual qreal getDevicePixelRatio() = 0;
|
||||||
|
|
||||||
|
static AbstractViewStateInterface* instance();
|
||||||
|
static void setInstance(AbstractViewStateInterface* instance);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
376
libraries/render-utils/src/OffscreenQmlSurface.cpp
Normal file
376
libraries/render-utils/src/OffscreenQmlSurface.cpp
Normal file
|
@ -0,0 +1,376 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015-05-13
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
#include "OffscreenQmlSurface.h"
|
||||||
|
|
||||||
|
#include <QOpenGLFramebufferObject>
|
||||||
|
#include <QOpenGLDebugLogger>
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <QtQml>
|
||||||
|
#include "AbstractViewStateInterface.h"
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
||||||
|
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
||||||
|
|
||||||
|
// Time between receiving a request to render the offscreen UI actually triggering
|
||||||
|
// the render. Could possibly be increased depending on the framerate we expect to
|
||||||
|
// achieve.
|
||||||
|
static const int SMALL_INTERVAL = 5;
|
||||||
|
|
||||||
|
OffscreenQmlSurface::OffscreenQmlSurface() {
|
||||||
|
}
|
||||||
|
|
||||||
|
OffscreenQmlSurface::~OffscreenQmlSurface() {
|
||||||
|
// Make sure the context is current while doing cleanup. Note that we use the
|
||||||
|
// offscreen surface here because passing 'this' at this point is not safe: the
|
||||||
|
// underlying platform window may already be destroyed. To avoid all the trouble, use
|
||||||
|
// another surface that is valid for sure.
|
||||||
|
makeCurrent();
|
||||||
|
|
||||||
|
// Delete the render control first since it will free the scenegraph resources.
|
||||||
|
// Destroy the QQuickWindow only afterwards.
|
||||||
|
delete _renderControl;
|
||||||
|
|
||||||
|
delete _qmlComponent;
|
||||||
|
delete _quickWindow;
|
||||||
|
delete _qmlEngine;
|
||||||
|
|
||||||
|
doneCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
||||||
|
OffscreenGlCanvas::create(shareContext);
|
||||||
|
|
||||||
|
makeCurrent();
|
||||||
|
|
||||||
|
// Create a QQuickWindow that is associated with out render control. Note that this
|
||||||
|
// window never gets created or shown, meaning that it will never get an underlying
|
||||||
|
// native (platform) window.
|
||||||
|
QQuickWindow::setDefaultAlphaBuffer(true);
|
||||||
|
_quickWindow = new QQuickWindow(_renderControl);
|
||||||
|
_quickWindow->setColor(QColor(255, 255, 255, 0));
|
||||||
|
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
|
||||||
|
// Create a QML engine.
|
||||||
|
_qmlEngine = new QQmlEngine;
|
||||||
|
if (!_qmlEngine->incubationController()) {
|
||||||
|
_qmlEngine->setIncubationController(_quickWindow->incubationController());
|
||||||
|
}
|
||||||
|
|
||||||
|
// When Quick says there is a need to render, we will not render immediately. Instead,
|
||||||
|
// a timer with a small interval is used to get better performance.
|
||||||
|
_updateTimer.setSingleShot(true);
|
||||||
|
_updateTimer.setInterval(SMALL_INTERVAL);
|
||||||
|
connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick);
|
||||||
|
|
||||||
|
// Now hook up the signals. For simplicy we don't differentiate between
|
||||||
|
// renderRequested (only render is needed, no sync) and sceneChanged (polish and sync
|
||||||
|
// is needed too).
|
||||||
|
connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenQmlSurface::requestRender);
|
||||||
|
connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenQmlSurface::requestUpdate);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{
|
||||||
|
qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject();
|
||||||
|
});
|
||||||
|
connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] {
|
||||||
|
qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem();
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_qmlComponent = new QQmlComponent(_qmlEngine);
|
||||||
|
// Initialize the render control and our OpenGL resources.
|
||||||
|
makeCurrent();
|
||||||
|
_renderControl->initialize(&_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::resize(const QSize& newSize) {
|
||||||
|
// Qt bug in 5.4 forces this check of pixel ratio,
|
||||||
|
// even though we're rendering offscreen.
|
||||||
|
qreal pixelRatio = 1.0;
|
||||||
|
if (_renderControl && _renderControl->_renderWindow) {
|
||||||
|
pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
|
||||||
|
} else {
|
||||||
|
pixelRatio = AbstractViewStateInterface::instance()->getDevicePixelRatio();
|
||||||
|
}
|
||||||
|
QSize newOffscreenSize = newSize * pixelRatio;
|
||||||
|
if (newOffscreenSize == _fboCache.getSize()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear out any fbos with the old size
|
||||||
|
makeCurrent();
|
||||||
|
qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
|
||||||
|
_fboCache.setSize(newSize * pixelRatio);
|
||||||
|
|
||||||
|
if (_quickWindow) {
|
||||||
|
_quickWindow->setGeometry(QRect(QPoint(), newSize));
|
||||||
|
_quickWindow->contentItem()->setSize(newSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Update our members
|
||||||
|
if (_rootItem) {
|
||||||
|
_rootItem->setSize(newSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
doneCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickItem* OffscreenQmlSurface::getRootItem() {
|
||||||
|
return _rootItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
|
||||||
|
_qmlEngine->setBaseUrl(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
|
||||||
|
_qmlComponent->loadUrl(qmlSource);
|
||||||
|
if (_qmlComponent->isLoading()) {
|
||||||
|
connect(_qmlComponent, &QQmlComponent::statusChanged, this,
|
||||||
|
[this, f](QQmlComponent::Status){
|
||||||
|
finishQmlLoad(f);
|
||||||
|
});
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return finishQmlLoad(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::requestUpdate() {
|
||||||
|
_polish = true;
|
||||||
|
if (!_updateTimer.isActive()) {
|
||||||
|
_updateTimer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::requestRender() {
|
||||||
|
if (!_updateTimer.isActive()) {
|
||||||
|
_updateTimer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
|
||||||
|
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
|
||||||
|
if (_qmlComponent->isError()) {
|
||||||
|
QList<QQmlError> errorList = _qmlComponent->errors();
|
||||||
|
foreach(const QQmlError& error, errorList) {
|
||||||
|
qWarning() << error.url() << error.line() << error;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlContext* newContext = new QQmlContext(_qmlEngine, qApp);
|
||||||
|
QObject* newObject = _qmlComponent->beginCreate(newContext);
|
||||||
|
if (_qmlComponent->isError()) {
|
||||||
|
QList<QQmlError> errorList = _qmlComponent->errors();
|
||||||
|
foreach(const QQmlError& error, errorList)
|
||||||
|
qWarning() << error.url() << error.line() << error;
|
||||||
|
if (!_rootItem) {
|
||||||
|
qFatal("Unable to finish loading QML root");
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
f(newContext, newObject);
|
||||||
|
_qmlComponent->completeCreate();
|
||||||
|
|
||||||
|
|
||||||
|
// All quick items should be focusable
|
||||||
|
QQuickItem* newItem = qobject_cast<QQuickItem*>(newObject);
|
||||||
|
if (newItem) {
|
||||||
|
// Make sure we make items focusable (critical for
|
||||||
|
// supporting keyboard shortcuts)
|
||||||
|
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we already have a root, just set a couple of flags and the ancestry
|
||||||
|
if (_rootItem) {
|
||||||
|
// Allow child windows to be destroyed from JS
|
||||||
|
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
|
||||||
|
newObject->setParent(_rootItem);
|
||||||
|
if (newItem) {
|
||||||
|
newItem->setParentItem(_rootItem);
|
||||||
|
}
|
||||||
|
return newObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newItem) {
|
||||||
|
qFatal("Could not load object as root item");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// The root item is ready. Associate it with the window.
|
||||||
|
_rootItem = newItem;
|
||||||
|
_rootItem->setParentItem(_quickWindow->contentItem());
|
||||||
|
_rootItem->setSize(_quickWindow->renderTargetSize());
|
||||||
|
return _rootItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::updateQuick() {
|
||||||
|
if (_paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!makeCurrent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polish, synchronize and render the next frame (into our fbo). In this example
|
||||||
|
// everything happens on the same thread and therefore all three steps are performed
|
||||||
|
// in succession from here. In a threaded setup the render() call would happen on a
|
||||||
|
// separate thread.
|
||||||
|
if (_polish) {
|
||||||
|
_renderControl->polishItems();
|
||||||
|
_renderControl->sync();
|
||||||
|
_polish = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QOpenGLFramebufferObject* fbo = _fboCache.getReadyFbo();
|
||||||
|
|
||||||
|
_quickWindow->setRenderTarget(fbo);
|
||||||
|
fbo->bind();
|
||||||
|
|
||||||
|
glClearColor(0, 0, 0, 1);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
_renderControl->render();
|
||||||
|
// FIXME The web browsers seem to be leaving GL in an error state.
|
||||||
|
// Need a debug context with sync logging to figure out why.
|
||||||
|
// for now just clear the errors
|
||||||
|
glGetError();
|
||||||
|
// Q_ASSERT(!glGetError());
|
||||||
|
|
||||||
|
_quickWindow->resetOpenGLState();
|
||||||
|
|
||||||
|
QOpenGLFramebufferObject::bindDefault();
|
||||||
|
// Force completion of all the operations before we emit the texture as being ready for use
|
||||||
|
glFinish();
|
||||||
|
|
||||||
|
emit textureUpdated(fbo->texture());
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
|
||||||
|
vec2 sourceSize;
|
||||||
|
if (dynamic_cast<QWidget*>(sourceObject)) {
|
||||||
|
sourceSize = toGlm(((QWidget*)sourceObject)->size());
|
||||||
|
} else if (dynamic_cast<QWindow*>(sourceObject)) {
|
||||||
|
sourceSize = toGlm(((QWindow*)sourceObject)->size());
|
||||||
|
}
|
||||||
|
vec2 offscreenPosition = toGlm(sourcePosition);
|
||||||
|
offscreenPosition /= sourceSize;
|
||||||
|
offscreenPosition *= vec2(toGlm(_quickWindow->size()));
|
||||||
|
return QPointF(offscreenPosition.x, offscreenPosition.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Event handling customization
|
||||||
|
//
|
||||||
|
|
||||||
|
bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* event) {
|
||||||
|
// Only intercept events while we're in an active state
|
||||||
|
if (_paused) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
// Don't intercept our own events, or we enter an infinite recursion
|
||||||
|
QObject* recurseTest = originalDestination;
|
||||||
|
while (recurseTest) {
|
||||||
|
Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow);
|
||||||
|
recurseTest = recurseTest->parent();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
switch (event->type()) {
|
||||||
|
case QEvent::Resize: {
|
||||||
|
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
|
||||||
|
QGLWidget* widget = dynamic_cast<QGLWidget*>(originalDestination);
|
||||||
|
if (widget) {
|
||||||
|
this->resize(resizeEvent->size());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case QEvent::KeyPress:
|
||||||
|
case QEvent::KeyRelease: {
|
||||||
|
event->ignore();
|
||||||
|
if (QCoreApplication::sendEvent(_quickWindow, event)) {
|
||||||
|
return event->isAccepted();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case QEvent::Wheel: {
|
||||||
|
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);
|
||||||
|
QWheelEvent mappedEvent(
|
||||||
|
mapWindowToUi(wheelEvent->pos(), originalDestination),
|
||||||
|
wheelEvent->delta(), wheelEvent->buttons(),
|
||||||
|
wheelEvent->modifiers(), wheelEvent->orientation());
|
||||||
|
mappedEvent.ignore();
|
||||||
|
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
||||||
|
return mappedEvent.isAccepted();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall through
|
||||||
|
case QEvent::MouseButtonDblClick:
|
||||||
|
case QEvent::MouseButtonPress:
|
||||||
|
case QEvent::MouseButtonRelease:
|
||||||
|
case QEvent::MouseMove: {
|
||||||
|
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
||||||
|
QPointF originalPos = mouseEvent->localPos();
|
||||||
|
QPointF transformedPos = _mouseTranslator(originalPos);
|
||||||
|
transformedPos = mapWindowToUi(transformedPos, originalDestination);
|
||||||
|
QMouseEvent mappedEvent(mouseEvent->type(),
|
||||||
|
transformedPos,
|
||||||
|
mouseEvent->screenPos(), mouseEvent->button(),
|
||||||
|
mouseEvent->buttons(), mouseEvent->modifiers());
|
||||||
|
if (event->type() == QEvent::MouseMove) {
|
||||||
|
_qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
|
||||||
|
}
|
||||||
|
mappedEvent.ignore();
|
||||||
|
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
||||||
|
return mappedEvent.isAccepted();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::lockTexture(int texture) {
|
||||||
|
_fboCache.lockTexture(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::releaseTexture(int texture) {
|
||||||
|
_fboCache.releaseTexture(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::pause() {
|
||||||
|
_paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::resume() {
|
||||||
|
_paused = false;
|
||||||
|
requestRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OffscreenQmlSurface::isPaused() const {
|
||||||
|
return _paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::setProxyWindow(QWindow* window) {
|
||||||
|
_renderControl->_renderWindow = window;
|
||||||
|
}
|
||||||
|
|
108
libraries/render-utils/src/OffscreenQmlSurface.h
Normal file
108
libraries/render-utils/src/OffscreenQmlSurface.h
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015-04-04
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#ifndef hifi_OffscreenQmlSurface_h
|
||||||
|
#define hifi_OffscreenQmlSurface_h
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQmlComponent>
|
||||||
|
#include <QQuickItem>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QQuickRenderControl>
|
||||||
|
#include <QQuickImageProvider>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <GLMHelpers.h>
|
||||||
|
#include <ThreadHelpers.h>
|
||||||
|
|
||||||
|
#include "OffscreenGlCanvas.h"
|
||||||
|
#include "FboCache.h"
|
||||||
|
|
||||||
|
class OffscreenQmlSurface : public OffscreenGlCanvas {
|
||||||
|
Q_OBJECT
|
||||||
|
protected:
|
||||||
|
class QMyQuickRenderControl : public QQuickRenderControl {
|
||||||
|
protected:
|
||||||
|
QWindow* renderWindow(QPoint* offset) Q_DECL_OVERRIDE{
|
||||||
|
if (nullptr == _renderWindow) {
|
||||||
|
return QQuickRenderControl::renderWindow(offset);
|
||||||
|
}
|
||||||
|
if (nullptr != offset) {
|
||||||
|
offset->rx() = offset->ry() = 0;
|
||||||
|
}
|
||||||
|
return _renderWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWindow* _renderWindow{ nullptr };
|
||||||
|
friend class OffscreenQmlSurface;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
OffscreenQmlSurface();
|
||||||
|
virtual ~OffscreenQmlSurface();
|
||||||
|
|
||||||
|
using MouseTranslator = std::function<QPointF(const QPointF&)>;
|
||||||
|
|
||||||
|
void create(QOpenGLContext* context);
|
||||||
|
void resize(const QSize& size);
|
||||||
|
QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
|
||||||
|
QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) {
|
||||||
|
return load(QUrl(qmlSourceFile), f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional values for event handling
|
||||||
|
void setProxyWindow(QWindow* window);
|
||||||
|
void setMouseTranslator(MouseTranslator mouseTranslator) {
|
||||||
|
_mouseTranslator = mouseTranslator;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
bool isPaused() const;
|
||||||
|
|
||||||
|
void setBaseUrl(const QUrl& baseUrl);
|
||||||
|
QQuickItem* getRootItem();
|
||||||
|
|
||||||
|
virtual bool eventFilter(QObject* originalDestination, QEvent* event);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void textureUpdated(GLuint texture);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void requestUpdate();
|
||||||
|
void requestRender();
|
||||||
|
void lockTexture(int texture);
|
||||||
|
void releaseTexture(int texture);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
|
||||||
|
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateQuick();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QQuickWindow* _quickWindow{ nullptr };
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMyQuickRenderControl* _renderControl{ new QMyQuickRenderControl };
|
||||||
|
QQmlEngine* _qmlEngine{ nullptr };
|
||||||
|
QQmlComponent* _qmlComponent{ nullptr };
|
||||||
|
QQuickItem* _rootItem{ nullptr };
|
||||||
|
QTimer _updateTimer;
|
||||||
|
FboCache _fboCache;
|
||||||
|
bool _polish{ true };
|
||||||
|
bool _paused{ true };
|
||||||
|
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p; } };
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -16,14 +16,6 @@
|
||||||
#include "MessageDialog.h"
|
#include "MessageDialog.h"
|
||||||
|
|
||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
|
||||||
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
|
||||||
|
|
||||||
// Time between receiving a request to render the offscreen UI actually triggering
|
|
||||||
// the render. Could possibly be increased depending on the framerate we expect to
|
|
||||||
// achieve.
|
|
||||||
static const int SMALL_INTERVAL = 5;
|
|
||||||
|
|
||||||
class OffscreenUiRoot : public QQuickItem {
|
class OffscreenUiRoot : public QQuickItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -36,6 +28,25 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This hack allows the QML UI to work with keys that are also bound as
|
||||||
|
// shortcuts at the application level. However, it seems as though the
|
||||||
|
// bound actions are still getting triggered. At least for backspace.
|
||||||
|
// Not sure why.
|
||||||
|
//
|
||||||
|
// However, the problem may go away once we switch to the new menu system,
|
||||||
|
// so I think it's OK for the time being.
|
||||||
|
bool OffscreenUi::shouldSwallowShortcut(QEvent* event) {
|
||||||
|
Q_ASSERT(event->type() == QEvent::ShortcutOverride);
|
||||||
|
QObject* focusObject = _quickWindow->focusObject();
|
||||||
|
if (focusObject != _quickWindow && focusObject != getRootItem()) {
|
||||||
|
//qDebug() << "Swallowed shortcut " << static_cast<QKeyEvent*>(event)->key();
|
||||||
|
event->accept();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
OffscreenUiRoot::OffscreenUiRoot(QQuickItem* parent) : QQuickItem(parent) {
|
OffscreenUiRoot::OffscreenUiRoot(QQuickItem* parent) : QQuickItem(parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,378 +59,15 @@ OffscreenUi::OffscreenUi() {
|
||||||
}
|
}
|
||||||
|
|
||||||
OffscreenUi::~OffscreenUi() {
|
OffscreenUi::~OffscreenUi() {
|
||||||
// Make sure the context is current while doing cleanup. Note that we use the
|
|
||||||
// offscreen surface here because passing 'this' at this point is not safe: the
|
|
||||||
// underlying platform window may already be destroyed. To avoid all the trouble, use
|
|
||||||
// another surface that is valid for sure.
|
|
||||||
makeCurrent();
|
|
||||||
|
|
||||||
// Delete the render control first since it will free the scenegraph resources.
|
|
||||||
// Destroy the QQuickWindow only afterwards.
|
|
||||||
delete _renderControl;
|
|
||||||
|
|
||||||
delete _qmlComponent;
|
|
||||||
delete _quickWindow;
|
|
||||||
delete _qmlEngine;
|
|
||||||
|
|
||||||
doneCurrent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenUi::create(QOpenGLContext* shareContext) {
|
|
||||||
OffscreenGlCanvas::create(shareContext);
|
|
||||||
|
|
||||||
makeCurrent();
|
|
||||||
|
|
||||||
// Create a QQuickWindow that is associated with out render control. Note that this
|
|
||||||
// window never gets created or shown, meaning that it will never get an underlying
|
|
||||||
// native (platform) window.
|
|
||||||
QQuickWindow::setDefaultAlphaBuffer(true);
|
|
||||||
_quickWindow = new QQuickWindow(_renderControl);
|
|
||||||
_quickWindow->setColor(QColor(255, 255, 255, 0));
|
|
||||||
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
|
|
||||||
// Create a QML engine.
|
|
||||||
_qmlEngine = new QQmlEngine;
|
|
||||||
if (!_qmlEngine->incubationController()) {
|
|
||||||
_qmlEngine->setIncubationController(_quickWindow->incubationController());
|
|
||||||
}
|
|
||||||
|
|
||||||
// When Quick says there is a need to render, we will not render immediately. Instead,
|
|
||||||
// a timer with a small interval is used to get better performance.
|
|
||||||
_updateTimer.setSingleShot(true);
|
|
||||||
_updateTimer.setInterval(SMALL_INTERVAL);
|
|
||||||
connect(&_updateTimer, &QTimer::timeout, this, &OffscreenUi::updateQuick);
|
|
||||||
|
|
||||||
// Now hook up the signals. For simplicy we don't differentiate between
|
|
||||||
// renderRequested (only render is needed, no sync) and sceneChanged (polish and sync
|
|
||||||
// is needed too).
|
|
||||||
connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenUi::requestRender);
|
|
||||||
connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenUi::requestUpdate);
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{
|
|
||||||
qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject();
|
|
||||||
});
|
|
||||||
connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] {
|
|
||||||
qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem();
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_qmlComponent = new QQmlComponent(_qmlEngine);
|
|
||||||
// Initialize the render control and our OpenGL resources.
|
|
||||||
makeCurrent();
|
|
||||||
_renderControl->initialize(&_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::addImportPath(const QString& path) {
|
|
||||||
_qmlEngine->addImportPath(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::resize(const QSize& newSize) {
|
|
||||||
makeCurrent();
|
|
||||||
|
|
||||||
qreal pixelRatio = _renderControl->_renderWindow ? _renderControl->_renderWindow->devicePixelRatio() : 1.0;
|
|
||||||
QSize newOffscreenSize = newSize * pixelRatio;
|
|
||||||
if (newOffscreenSize == _fboCache.getSize()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear out any fbos with the old size
|
|
||||||
qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
|
|
||||||
_fboCache.setSize(newSize * pixelRatio);
|
|
||||||
|
|
||||||
if (_quickWindow) {
|
|
||||||
_quickWindow->setGeometry(QRect(QPoint(), newSize));
|
|
||||||
_quickWindow->contentItem()->setSize(newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Update our members
|
|
||||||
if (_rootItem) {
|
|
||||||
_rootItem->setSize(newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
doneCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
QQuickItem* OffscreenUi::getRootItem() {
|
|
||||||
return _rootItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::setBaseUrl(const QUrl& baseUrl) {
|
|
||||||
_qmlEngine->setBaseUrl(baseUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject* OffscreenUi::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
|
|
||||||
_qmlComponent->loadUrl(qmlSource);
|
|
||||||
if (_qmlComponent->isLoading()) {
|
|
||||||
connect(_qmlComponent, &QQmlComponent::statusChanged, this,
|
|
||||||
[this, f](QQmlComponent::Status){
|
|
||||||
finishQmlLoad(f);
|
|
||||||
});
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return finishQmlLoad(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::requestUpdate() {
|
|
||||||
_polish = true;
|
|
||||||
if (!_updateTimer.isActive()) {
|
|
||||||
_updateTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::requestRender() {
|
|
||||||
if (!_updateTimer.isActive()) {
|
|
||||||
_updateTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject* OffscreenUi::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
|
|
||||||
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
|
|
||||||
if (_qmlComponent->isError()) {
|
|
||||||
QList<QQmlError> errorList = _qmlComponent->errors();
|
|
||||||
foreach(const QQmlError& error, errorList) {
|
|
||||||
qWarning() << error.url() << error.line() << error;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QQmlContext* newContext = new QQmlContext(_qmlEngine, qApp);
|
|
||||||
QObject* newObject = _qmlComponent->beginCreate(newContext);
|
|
||||||
if (_qmlComponent->isError()) {
|
|
||||||
QList<QQmlError> errorList = _qmlComponent->errors();
|
|
||||||
foreach(const QQmlError& error, errorList)
|
|
||||||
qWarning() << error.url() << error.line() << error;
|
|
||||||
if (!_rootItem) {
|
|
||||||
qFatal("Unable to finish loading QML root");
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
f(newContext, newObject);
|
|
||||||
_qmlComponent->completeCreate();
|
|
||||||
|
|
||||||
|
|
||||||
// All quick items should be focusable
|
|
||||||
QQuickItem* newItem = qobject_cast<QQuickItem*>(newObject);
|
|
||||||
if (newItem) {
|
|
||||||
// Make sure we make items focusable (critical for
|
|
||||||
// supporting keyboard shortcuts)
|
|
||||||
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we already have a root, just set a couple of flags and the ancestry
|
|
||||||
if (_rootItem) {
|
|
||||||
// Allow child windows to be destroyed from JS
|
|
||||||
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
|
|
||||||
newObject->setParent(_rootItem);
|
|
||||||
if (newItem) {
|
|
||||||
newItem->setParentItem(_rootItem);
|
|
||||||
}
|
|
||||||
return newObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newItem) {
|
|
||||||
qFatal("Could not load object as root item");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
// The root item is ready. Associate it with the window.
|
|
||||||
_rootItem = newItem;
|
|
||||||
_rootItem->setParentItem(_quickWindow->contentItem());
|
|
||||||
_rootItem->setSize(_quickWindow->renderTargetSize());
|
|
||||||
return _rootItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OffscreenUi::updateQuick() {
|
|
||||||
if (_paused) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!makeCurrent()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Polish, synchronize and render the next frame (into our fbo). In this example
|
|
||||||
// everything happens on the same thread and therefore all three steps are performed
|
|
||||||
// in succession from here. In a threaded setup the render() call would happen on a
|
|
||||||
// separate thread.
|
|
||||||
if (_polish) {
|
|
||||||
_renderControl->polishItems();
|
|
||||||
_renderControl->sync();
|
|
||||||
_polish = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QOpenGLFramebufferObject* fbo = _fboCache.getReadyFbo();
|
|
||||||
|
|
||||||
_quickWindow->setRenderTarget(fbo);
|
|
||||||
fbo->bind();
|
|
||||||
|
|
||||||
glClearColor(0, 0, 0, 1);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
||||||
|
|
||||||
_renderControl->render();
|
|
||||||
// FIXME The web browsers seem to be leaving GL in an error state.
|
|
||||||
// Need a debug context with sync logging to figure out why.
|
|
||||||
// for now just clear the errors
|
|
||||||
glGetError();
|
|
||||||
// Q_ASSERT(!glGetError());
|
|
||||||
|
|
||||||
_quickWindow->resetOpenGLState();
|
|
||||||
|
|
||||||
QOpenGLFramebufferObject::bindDefault();
|
|
||||||
// Force completion of all the operations before we emit the texture as being ready for use
|
|
||||||
glFinish();
|
|
||||||
|
|
||||||
emit textureUpdated(fbo->texture());
|
|
||||||
}
|
|
||||||
|
|
||||||
QPointF OffscreenUi::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
|
|
||||||
vec2 sourceSize;
|
|
||||||
if (dynamic_cast<QWidget*>(sourceObject)) {
|
|
||||||
sourceSize = toGlm(((QWidget*)sourceObject)->size());
|
|
||||||
} else if (dynamic_cast<QWindow*>(sourceObject)) {
|
|
||||||
sourceSize = toGlm(((QWindow*)sourceObject)->size());
|
|
||||||
}
|
|
||||||
vec2 offscreenPosition = toGlm(sourcePosition);
|
|
||||||
offscreenPosition /= sourceSize;
|
|
||||||
offscreenPosition *= vec2(toGlm(_quickWindow->size()));
|
|
||||||
return QPointF(offscreenPosition.x, offscreenPosition.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This hack allows the QML UI to work with keys that are also bound as
|
|
||||||
// shortcuts at the application level. However, it seems as though the
|
|
||||||
// bound actions are still getting triggered. At least for backspace.
|
|
||||||
// Not sure why.
|
|
||||||
//
|
|
||||||
// However, the problem may go away once we switch to the new menu system,
|
|
||||||
// so I think it's OK for the time being.
|
|
||||||
bool OffscreenUi::shouldSwallowShortcut(QEvent* event) {
|
|
||||||
Q_ASSERT(event->type() == QEvent::ShortcutOverride);
|
|
||||||
QObject* focusObject = _quickWindow->focusObject();
|
|
||||||
if (focusObject != _quickWindow && focusObject != _rootItem) {
|
|
||||||
//qDebug() << "Swallowed shortcut " << static_cast<QKeyEvent*>(event)->key();
|
|
||||||
event->accept();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Event handling customization
|
|
||||||
//
|
|
||||||
|
|
||||||
bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
|
|
||||||
// Only intercept events while we're in an active state
|
|
||||||
if (_paused) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
// Don't intercept our own events, or we enter an infinite recursion
|
|
||||||
QObject* recurseTest = originalDestination;
|
|
||||||
while (recurseTest) {
|
|
||||||
Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow);
|
|
||||||
recurseTest = recurseTest->parent();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
switch (event->type()) {
|
|
||||||
case QEvent::Resize: {
|
|
||||||
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
|
|
||||||
QGLWidget* widget = dynamic_cast<QGLWidget*>(originalDestination);
|
|
||||||
if (widget) {
|
|
||||||
this->resize(resizeEvent->size());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case QEvent::KeyPress:
|
|
||||||
case QEvent::KeyRelease: {
|
|
||||||
event->ignore();
|
|
||||||
if (QCoreApplication::sendEvent(_quickWindow, event)) {
|
|
||||||
return event->isAccepted();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case QEvent::Wheel: {
|
|
||||||
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);
|
|
||||||
QWheelEvent mappedEvent(
|
|
||||||
mapWindowToUi(wheelEvent->pos(), originalDestination),
|
|
||||||
wheelEvent->delta(), wheelEvent->buttons(),
|
|
||||||
wheelEvent->modifiers(), wheelEvent->orientation());
|
|
||||||
mappedEvent.ignore();
|
|
||||||
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
|
||||||
return mappedEvent.isAccepted();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall through
|
|
||||||
case QEvent::MouseButtonDblClick:
|
|
||||||
case QEvent::MouseButtonPress:
|
|
||||||
case QEvent::MouseButtonRelease:
|
|
||||||
case QEvent::MouseMove: {
|
|
||||||
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
|
||||||
QPointF originalPos = mouseEvent->localPos();
|
|
||||||
QPointF transformedPos = _mouseTranslator(originalPos);
|
|
||||||
transformedPos = mapWindowToUi(transformedPos, originalDestination);
|
|
||||||
QMouseEvent mappedEvent(mouseEvent->type(),
|
|
||||||
transformedPos,
|
|
||||||
mouseEvent->screenPos(), mouseEvent->button(),
|
|
||||||
mouseEvent->buttons(), mouseEvent->modifiers());
|
|
||||||
if (event->type() == QEvent::MouseMove) {
|
|
||||||
_qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
|
|
||||||
}
|
|
||||||
mappedEvent.ignore();
|
|
||||||
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
|
||||||
return mappedEvent.isAccepted();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::lockTexture(int texture) {
|
|
||||||
_fboCache.lockTexture(texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::releaseTexture(int texture) {
|
|
||||||
_fboCache.releaseTexture(texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::pause() {
|
|
||||||
_paused = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::resume() {
|
|
||||||
_paused = false;
|
|
||||||
requestRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OffscreenUi::isPaused() const {
|
|
||||||
return _paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::setProxyWindow(QWindow* window) {
|
|
||||||
_renderControl->_renderWindow = window;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
|
void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
|
||||||
QQuickItem* item = _rootItem->findChild<QQuickItem*>(name);
|
QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);
|
||||||
// First load?
|
// First load?
|
||||||
if (!item) {
|
if (!item) {
|
||||||
load(url, f);
|
load(url, f);
|
||||||
item = _rootItem->findChild<QQuickItem*>(name);
|
item = getRootItem()->findChild<QQuickItem*>(name);
|
||||||
}
|
}
|
||||||
if (item) {
|
if (item) {
|
||||||
item->setEnabled(true);
|
item->setEnabled(true);
|
||||||
|
@ -427,11 +75,11 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
|
void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
|
||||||
QQuickItem* item = _rootItem->findChild<QQuickItem*>(name);
|
QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);
|
||||||
// First load?
|
// First load?
|
||||||
if (!item) {
|
if (!item) {
|
||||||
load(url, f);
|
load(url, f);
|
||||||
item = _rootItem->findChild<QQuickItem*>(name);
|
item = getRootItem()->findChild<QQuickItem*>(name);
|
||||||
}
|
}
|
||||||
if (item) {
|
if (item) {
|
||||||
item->setEnabled(!item->isEnabled());
|
item->setEnabled(!item->isEnabled());
|
||||||
|
|
|
@ -12,25 +12,10 @@
|
||||||
#ifndef hifi_OffscreenUi_h
|
#ifndef hifi_OffscreenUi_h
|
||||||
#define hifi_OffscreenUi_h
|
#define hifi_OffscreenUi_h
|
||||||
|
|
||||||
#include <QQmlEngine>
|
#include "OffscreenQmlSurface.h"
|
||||||
#include <QQmlComponent>
|
|
||||||
#include <QQuickItem>
|
|
||||||
#include <QQuickWindow>
|
|
||||||
#include <QQuickRenderControl>
|
|
||||||
#include <QQuickImageProvider>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QMessageBox>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include <GLMHelpers.h>
|
|
||||||
#include <ThreadHelpers.h>
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
|
||||||
#include "OffscreenGlCanvas.h"
|
|
||||||
#include "FboCache.h"
|
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
#define HIFI_QML_DECL \
|
#define HIFI_QML_DECL \
|
||||||
private: \
|
private: \
|
||||||
|
@ -96,53 +81,15 @@ private:
|
||||||
offscreenUi->load(QML, f); \
|
offscreenUi->load(QML, f); \
|
||||||
}
|
}
|
||||||
|
|
||||||
class OffscreenUi : public OffscreenGlCanvas, public Dependency {
|
class OffscreenUi : public OffscreenQmlSurface, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
class QMyQuickRenderControl : public QQuickRenderControl {
|
|
||||||
protected:
|
|
||||||
QWindow* renderWindow(QPoint* offset) Q_DECL_OVERRIDE{
|
|
||||||
if (nullptr == _renderWindow) {
|
|
||||||
return QQuickRenderControl::renderWindow(offset);
|
|
||||||
}
|
|
||||||
if (nullptr != offset) {
|
|
||||||
offset->rx() = offset->ry() = 0;
|
|
||||||
}
|
|
||||||
return _renderWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
QWindow* _renderWindow{ nullptr };
|
|
||||||
friend class OffscreenUi;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using MouseTranslator = std::function<QPointF(const QPointF&)>;
|
|
||||||
OffscreenUi();
|
OffscreenUi();
|
||||||
virtual ~OffscreenUi();
|
virtual ~OffscreenUi();
|
||||||
void create(QOpenGLContext* context);
|
|
||||||
void resize(const QSize& size);
|
|
||||||
QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
|
|
||||||
QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) {
|
|
||||||
return load(QUrl(qmlSourceFile), f);
|
|
||||||
}
|
|
||||||
void show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
|
void show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
|
||||||
void toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
|
void toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
|
||||||
void setBaseUrl(const QUrl& baseUrl);
|
|
||||||
void addImportPath(const QString& path);
|
|
||||||
//QQmlContext* getQmlContext();
|
|
||||||
QQuickItem* getRootItem();
|
|
||||||
void pause();
|
|
||||||
void resume();
|
|
||||||
bool isPaused() const;
|
|
||||||
void setProxyWindow(QWindow* window);
|
|
||||||
bool shouldSwallowShortcut(QEvent* event);
|
bool shouldSwallowShortcut(QEvent* event);
|
||||||
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
|
|
||||||
virtual bool eventFilter(QObject* originalDestination, QEvent* event);
|
|
||||||
void setMouseTranslator(MouseTranslator mouseTranslator) {
|
|
||||||
_mouseTranslator = mouseTranslator;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Messagebox replacement functions
|
// Messagebox replacement functions
|
||||||
using ButtonCallback = std::function<void(QMessageBox::StandardButton)>;
|
using ButtonCallback = std::function<void(QMessageBox::StandardButton)>;
|
||||||
|
@ -168,33 +115,6 @@ public:
|
||||||
static void critical(const QString& title, const QString& text,
|
static void critical(const QString& title, const QString& text,
|
||||||
ButtonCallback callback = NO_OP_CALLBACK,
|
ButtonCallback callback = NO_OP_CALLBACK,
|
||||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok);
|
QMessageBox::StandardButtons buttons = QMessageBox::Ok);
|
||||||
|
|
||||||
private:
|
|
||||||
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void updateQuick();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void requestUpdate();
|
|
||||||
void requestRender();
|
|
||||||
void lockTexture(int texture);
|
|
||||||
void releaseTexture(int texture);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void textureUpdated(GLuint texture);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QMyQuickRenderControl* _renderControl{ new QMyQuickRenderControl };
|
|
||||||
QQuickWindow* _quickWindow{ nullptr };
|
|
||||||
QQmlEngine* _qmlEngine{ nullptr };
|
|
||||||
QQmlComponent* _qmlComponent{ nullptr };
|
|
||||||
QQuickItem* _rootItem{ nullptr };
|
|
||||||
QTimer _updateTimer;
|
|
||||||
FboCache _fboCache;
|
|
||||||
bool _polish{ true };
|
|
||||||
bool _paused{ true };
|
|
||||||
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p; } };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue