mirror of
https://github.com/JulianGro/overte.git
synced 2025-05-17 21:52:19 +02:00
405 lines
14 KiB
C++
405 lines
14 KiB
C++
//
|
|
// EntityTreeRenderer.cpp
|
|
// interface/src
|
|
//
|
|
// Created by Brad Hefta-Gaub on 12/6/13.
|
|
// 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 <glm/gtx/quaternion.hpp>
|
|
|
|
#include <FBXReader.h>
|
|
|
|
#include "InterfaceConfig.h"
|
|
|
|
#include <BoxEntityItem.h>
|
|
#include <ModelEntityItem.h>
|
|
#include <PerfStat.h>
|
|
|
|
|
|
#include "Menu.h"
|
|
#include "EntityTreeRenderer.h"
|
|
|
|
#include "RenderableBoxEntityItem.h"
|
|
#include "RenderableModelEntityItem.h"
|
|
#include "RenderableSphereEntityItem.h"
|
|
|
|
|
|
QThread* EntityTreeRenderer::getMainThread() {
|
|
return Application::getInstance()->getEntities()->thread();
|
|
}
|
|
|
|
|
|
|
|
EntityTreeRenderer::EntityTreeRenderer() :
|
|
OctreeRenderer() {
|
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory)
|
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory)
|
|
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
|
|
}
|
|
|
|
EntityTreeRenderer::~EntityTreeRenderer() {
|
|
}
|
|
|
|
void EntityTreeRenderer::clear() {
|
|
OctreeRenderer::clear();
|
|
}
|
|
|
|
void EntityTreeRenderer::init() {
|
|
OctreeRenderer::init();
|
|
static_cast<EntityTree*>(_tree)->setFBXService(this);
|
|
}
|
|
|
|
void EntityTreeRenderer::setTree(Octree* newTree) {
|
|
OctreeRenderer::setTree(newTree);
|
|
static_cast<EntityTree*>(_tree)->setFBXService(this);
|
|
}
|
|
|
|
void EntityTreeRenderer::update() {
|
|
if (_tree) {
|
|
EntityTree* tree = static_cast<EntityTree*>(_tree);
|
|
tree->update();
|
|
}
|
|
}
|
|
|
|
void EntityTreeRenderer::render(RenderMode renderMode) {
|
|
OctreeRenderer::render(renderMode);
|
|
deleteReleasedModels(); // seems like as good as any other place to do some memory cleanup
|
|
}
|
|
|
|
const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(const EntityItem* entityItem) {
|
|
const FBXGeometry* result = NULL;
|
|
|
|
if (entityItem->getType() == EntityTypes::Model) {
|
|
const RenderableModelEntityItem* constModelEntityItem = dynamic_cast<const RenderableModelEntityItem*>(entityItem);
|
|
RenderableModelEntityItem* modelEntityItem = const_cast<RenderableModelEntityItem*>(constModelEntityItem);
|
|
assert(modelEntityItem); // we need this!!!
|
|
Model* model = modelEntityItem->getModel(this);
|
|
if (model) {
|
|
result = &model->getGeometry()->getFBXGeometry();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityItem) {
|
|
const Model* result = NULL;
|
|
if (entityItem->getType() == EntityTypes::Model) {
|
|
const RenderableModelEntityItem* constModelEntityItem = dynamic_cast<const RenderableModelEntityItem*>(entityItem);
|
|
RenderableModelEntityItem* modelEntityItem = const_cast<RenderableModelEntityItem*>(constModelEntityItem);
|
|
assert(modelEntityItem); // we need this!!!
|
|
|
|
result = modelEntityItem->getModel(this);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void renderElementProxy(EntityTreeElement* entityTreeElement) {
|
|
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float)TREE_SCALE;
|
|
float elementSize = entityTreeElement->getScale() * (float)TREE_SCALE;
|
|
glColor3f(1.0f, 0.0f, 0.0f);
|
|
glPushMatrix();
|
|
glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z);
|
|
glutWireCube(elementSize);
|
|
glPopMatrix();
|
|
|
|
bool displayElementChildProxies = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementChildProxies);
|
|
|
|
if (displayElementChildProxies) {
|
|
// draw the children
|
|
float halfSize = elementSize / 2.0f;
|
|
float quarterSize = elementSize / 4.0f;
|
|
glColor3f(1.0f, 1.0f, 0.0f);
|
|
glPushMatrix();
|
|
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
|
|
glutWireCube(halfSize);
|
|
glPopMatrix();
|
|
|
|
glColor3f(1.0f, 0.0f, 1.0f);
|
|
glPushMatrix();
|
|
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
|
|
glutWireCube(halfSize);
|
|
glPopMatrix();
|
|
|
|
glColor3f(0.0f, 1.0f, 0.0f);
|
|
glPushMatrix();
|
|
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
|
|
glutWireCube(halfSize);
|
|
glPopMatrix();
|
|
|
|
glColor3f(0.0f, 0.0f, 1.0f);
|
|
glPushMatrix();
|
|
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
|
|
glutWireCube(halfSize);
|
|
glPopMatrix();
|
|
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
glPushMatrix();
|
|
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
|
|
glutWireCube(halfSize);
|
|
glPopMatrix();
|
|
|
|
glColor3f(0.0f, 0.5f, 0.5f);
|
|
glPushMatrix();
|
|
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
|
|
glutWireCube(halfSize);
|
|
glPopMatrix();
|
|
|
|
glColor3f(0.5f, 0.0f, 0.0f);
|
|
glPushMatrix();
|
|
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
|
|
glutWireCube(halfSize);
|
|
glPopMatrix();
|
|
|
|
glColor3f(0.0f, 0.5f, 0.0f);
|
|
glPushMatrix();
|
|
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
|
|
glutWireCube(halfSize);
|
|
glPopMatrix();
|
|
}
|
|
}
|
|
|
|
float EntityTreeRenderer::distanceToCamera(const glm::vec3& center, const ViewFrustum& viewFrustum) const {
|
|
glm::vec3 temp = viewFrustum.getPosition() - center;
|
|
float distanceToVoxelCenter = sqrtf(glm::dot(temp, temp));
|
|
return distanceToVoxelCenter;
|
|
}
|
|
|
|
// TODO: This could be optimized to be a table, or something that doesn't require recalculation on every
|
|
// render call for every entity
|
|
// TODO: This is essentially the same logic used to render voxels, but since models are more detailed then voxels
|
|
// I've added a voxelToModelRatio that adjusts how much closer to a model you have to be to see it.
|
|
bool EntityTreeRenderer::shouldRenderEntity(float largestDimension, float distanceToCamera) const {
|
|
const float voxelToModelRatio = 4.0f; // must be this many times closer to a model than a voxel to see it.
|
|
float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale();
|
|
int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust();
|
|
|
|
float scale = (float)TREE_SCALE;
|
|
float visibleDistanceAtScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, voxelSizeScale) / voxelToModelRatio;
|
|
|
|
while (scale > largestDimension) {
|
|
scale /= 2.0f;
|
|
visibleDistanceAtScale /= 2.0f;
|
|
}
|
|
|
|
if (scale < largestDimension) {
|
|
visibleDistanceAtScale *= 2.0f;
|
|
}
|
|
|
|
return (distanceToCamera <= visibleDistanceAtScale);
|
|
}
|
|
|
|
void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* args) {
|
|
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
|
|
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
|
|
if (!isShadowMode && displayModelBounds) {
|
|
PerformanceTimer perfTimer("renderProxies");
|
|
|
|
AACube maxCube = entity->getMaximumAACube();
|
|
AACube minCube = entity->getMinimumAACube();
|
|
AABox entityBox = entity->getAABox();
|
|
|
|
maxCube.scale((float)TREE_SCALE);
|
|
minCube.scale((float)TREE_SCALE);
|
|
entityBox.scale((float)TREE_SCALE);
|
|
|
|
glm::vec3 maxCenter = maxCube.calcCenter();
|
|
glm::vec3 minCenter = minCube.calcCenter();
|
|
glm::vec3 entityBoxCenter = entityBox.calcCenter();
|
|
glm::vec3 entityBoxScale = entityBox.getScale();
|
|
|
|
// draw the max bounding cube
|
|
glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
|
|
glPushMatrix();
|
|
glTranslatef(maxCenter.x, maxCenter.y, maxCenter.z);
|
|
glutWireCube(maxCube.getScale());
|
|
glPopMatrix();
|
|
|
|
// draw the min bounding cube
|
|
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
|
|
glPushMatrix();
|
|
glTranslatef(minCenter.x, minCenter.y, minCenter.z);
|
|
glutWireCube(minCube.getScale());
|
|
glPopMatrix();
|
|
|
|
// draw the entityBox bounding box
|
|
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
|
|
glPushMatrix();
|
|
glTranslatef(entityBoxCenter.x, entityBoxCenter.y, entityBoxCenter.z);
|
|
glScalef(entityBoxScale.x, entityBoxScale.y, entityBoxScale.z);
|
|
glutWireCube(1.0f);
|
|
glPopMatrix();
|
|
|
|
|
|
glm::vec3 position = entity->getPosition() * (float)TREE_SCALE;
|
|
glm::vec3 center = entity->getCenter() * (float)TREE_SCALE;
|
|
glm::vec3 dimensions = entity->getDimensions() * (float)TREE_SCALE;
|
|
glm::quat rotation = entity->getRotation();
|
|
|
|
glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
|
|
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);
|
|
|
|
|
|
glPushMatrix();
|
|
glm::vec3 positionToCenter = center - position;
|
|
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
|
|
glScalef(dimensions.x, dimensions.y, dimensions.z);
|
|
glutWireCube(1.0f);
|
|
glPopMatrix();
|
|
glPopMatrix();
|
|
|
|
/*
|
|
glPushMatrix();
|
|
// draw the model relative bounding box
|
|
glm::vec3 axis = glm::axis(rotation);
|
|
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
|
glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z);
|
|
glColor3f(0.0f, 1.0f, 0.0f);
|
|
glutWireCube(1.0);
|
|
|
|
glPopMatrix();
|
|
*/
|
|
|
|
}
|
|
}
|
|
|
|
void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
|
|
args->_elementsTouched++;
|
|
// actually render it here...
|
|
// we need to iterate the actual entityItems of the element
|
|
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
|
|
|
|
QList<EntityItem*>& entityItems = entityTreeElement->getEntities();
|
|
|
|
|
|
uint16_t numberOfEntities = entityItems.size();
|
|
|
|
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
|
|
bool displayElementProxy = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementProxy);
|
|
|
|
|
|
|
|
if (!isShadowMode && displayElementProxy && numberOfEntities > 0) {
|
|
renderElementProxy(entityTreeElement);
|
|
}
|
|
|
|
for (uint16_t i = 0; i < numberOfEntities; i++) {
|
|
EntityItem* entityItem = entityItems[i];
|
|
|
|
if (entityItem->isVisible()) {
|
|
// render entityItem
|
|
AABox entityBox = entityItem->getAABox();
|
|
|
|
entityBox.scale(TREE_SCALE);
|
|
|
|
// TODO: some entity types (like lights) might want to be rendered even
|
|
// when they are outside of the view frustum...
|
|
float distance = distanceToCamera(entityBox.calcCenter(), *args->_viewFrustum);
|
|
if (shouldRenderEntity(entityBox.getLargestDimension(), distance) &&
|
|
args->_viewFrustum->boxInFrustum(entityBox) != ViewFrustum::OUTSIDE) {
|
|
|
|
|
|
renderProxies(entityItem, args);
|
|
|
|
Glower* glower = NULL;
|
|
if (entityItem->getGlowLevel() > 0.0f) {
|
|
glower = new Glower(entityItem->getGlowLevel());
|
|
}
|
|
entityItem->render(args);
|
|
if (glower) {
|
|
delete glower;
|
|
}
|
|
} else {
|
|
args->_itemsOutOfView++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float EntityTreeRenderer::getSizeScale() const {
|
|
return Menu::getInstance()->getVoxelSizeScale();
|
|
}
|
|
|
|
int EntityTreeRenderer::getBoundaryLevelAdjust() const {
|
|
return Menu::getInstance()->getBoundaryLevelAdjust();
|
|
}
|
|
|
|
|
|
void EntityTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
|
|
static_cast<EntityTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
|
|
}
|
|
|
|
Model* EntityTreeRenderer::allocateModel(const QString& url) {
|
|
Model* model = NULL;
|
|
// Make sure we only create and delete models on the thread that owns the EntityTreeRenderer
|
|
if (QThread::currentThread() != thread()) {
|
|
QMetaObject::invokeMethod(this, "allocateModel", Qt::BlockingQueuedConnection,
|
|
Q_RETURN_ARG(Model*, model),
|
|
Q_ARG(const QString&, url));
|
|
|
|
return model;
|
|
}
|
|
model = new Model();
|
|
model->init();
|
|
model->setURL(QUrl(url));
|
|
return model;
|
|
}
|
|
|
|
Model* EntityTreeRenderer::updateModel(Model* original, const QString& newUrl) {
|
|
Model* model = NULL;
|
|
|
|
// The caller shouldn't call us if the URL doesn't need to change. But if they
|
|
// do, we just return their original back to them.
|
|
if (!original || (QUrl(newUrl) == original->getURL())) {
|
|
return original;
|
|
}
|
|
|
|
// Before we do any creating or deleting, make sure we're on our renderer thread
|
|
if (QThread::currentThread() != thread()) {
|
|
QMetaObject::invokeMethod(this, "updateModel", Qt::BlockingQueuedConnection,
|
|
Q_RETURN_ARG(Model*, model),
|
|
Q_ARG(Model*, original),
|
|
Q_ARG(const QString&, newUrl));
|
|
|
|
return model;
|
|
}
|
|
|
|
// at this point we know we need to replace the model, and we know we're on the
|
|
// correct thread, so we can do all our work.
|
|
if (original) {
|
|
delete original; // delete the old model...
|
|
}
|
|
|
|
// create the model and correctly initialize it with the new url
|
|
model = new Model();
|
|
model->init();
|
|
model->setURL(QUrl(newUrl));
|
|
|
|
return model;
|
|
}
|
|
|
|
void EntityTreeRenderer::releaseModel(Model* model) {
|
|
// If we're not on the renderer's thread, then remember this model to be deleted later
|
|
if (QThread::currentThread() != thread()) {
|
|
_releasedModels << model;
|
|
} else { // otherwise just delete it right away
|
|
delete model;
|
|
}
|
|
}
|
|
|
|
void EntityTreeRenderer::deleteReleasedModels() {
|
|
if (_releasedModels.size() > 0) {
|
|
foreach(Model* model, _releasedModels) {
|
|
delete model;
|
|
}
|
|
_releasedModels.clear();
|
|
}
|
|
}
|
|
|
|
|