// // 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 #include #include #include #include #include #include #include #include #include #include #include #include "EntityTreeRenderer.h" const int FIXED_FONT_POINT_SIZE = 40; const float DPI = 30.47; const float METERS_TO_INCHES = 39.3701; EntityItem* RenderableWebEntityItem::factory(const QUuid& entityID, const EntityItemProperties& properties) { return new RenderableWebEntityItem(entityID, properties); } RenderableWebEntityItem::RenderableWebEntityItem(const QUuid& 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? 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(); } }); auto forwardMouseEvent = [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId) { // Ignore mouse interaction if we're locked if (this->getLocked()) { return; } if (intersection.entityID == getID()) { if (event->button() == Qt::MouseButton::RightButton) { if (event->type() == QEvent::MouseButtonRelease) { AbstractViewStateInterface::instance()->postLambdaEvent([this] { QMetaObject::invokeMethod(_webSurface->getRootItem(), "goBack"); }); } return; } if (event->button() == Qt::MouseButton::MiddleButton) { if (event->type() == QEvent::MouseButtonRelease) { AbstractViewStateInterface::instance()->postLambdaEvent([this] { _webSurface->getRootItem()->setProperty("url", _sourceUrl); }); } return; } // Map the intersection point to an actual offscreen pixel glm::vec3 point = intersection.intersection; point -= getPosition(); point = glm::inverse(getRotation()) * point; point /= _dimensions; point += 0.5f; point.y = 1.0f - point.y; point *= _dimensions * METERS_TO_INCHES * DPI; // Forward the mouse event. QMouseEvent mappedEvent(event->type(), QPoint((int)point.x, (int)point.y), event->screenPos(), event->button(), event->buttons(), event->modifiers()); QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent); } }; EntityTreeRenderer* renderer = static_cast(args->_renderer); QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent); QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent); QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent); } 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(); 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()->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); }); } } }