From 8707c76a6a20cacb50da84512627532d9a452c10 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 16:30:12 -0800 Subject: [PATCH] templatize the ViewFrustum-relative sort algorithm --- .../src/EntityTreeRenderer.cpp | 61 ++++----- libraries/shared/src/PrioritySortUtil.h | 126 ++++++++++++++++++ 2 files changed, 149 insertions(+), 38 deletions(-) create mode 100644 libraries/shared/src/PrioritySortUtil.h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 17d8a6e9be..1efcd863ab 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -38,6 +38,22 @@ #include "RenderableWebEntityItem.h" +// implement these methods BEFORE including PrioritySortUtil.h +namespace PrioritySortUtil { + glm::vec3 getObjectPosition(const EntityRendererPointer& object) { + return object->getEntity()->getPosition(); + } + + float getObjectRadius(const EntityRendererPointer& object) { + return 0.5f * object->getEntity()->getQueryAACube().getScale(); + } + + uint64_t getObjectAge(const EntityRendererPointer& object) { + return object->getUpdateTime(); + } +} +#include + size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; @@ -338,47 +354,16 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // we expect the cost to updating all renderables to exceed available time budget // so we first sort by priority and update in order until out of time uint64_t sortStart = usecTimestampNow(); - std::priority_queue sortedRenderables; + using SortableRenderer = PrioritySortUtil::Sortable; + std::priority_queue< SortableRenderer > sortedRenderables; { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + PrioritySortUtil::PriorityCalculator priorityCalculator(view); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); - glm::vec3 viewCenter = view.getPosition(); - glm::vec3 forward = view.getDirection(); - const float OUT_OF_VIEW_PENALTY = -10.0f; while (itr != _renderablesToUpdate.end()) { - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view - // (c) time since last update - EntityItemPointer entity = itr->second->getEntity(); - glm::vec3 entityPosition = entity->getPosition(); - glm::vec3 offset = entityPosition - viewCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - - float diameter = entity->getQueryAACube().getScale(); - float apparentSize = diameter / distance; - float cosineAngle = glm::dot(offset, forward) / distance; - float age = (float)(sortStart - itr->second->getUpdateTime()) / (float)(USECS_PER_SECOND); - - // NOTE: we are adding values of different units to get a single measure of "priority". - // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. - // These weights are pure magic tuning and should be hard coded in the relation below, - // but are currently exposed for anyone who would like to explore fine tuning: - const float APPARENT_SIZE_COEFFICIENT = 1.0f; - const float CENTER_SORT_COEFFICIENT = 0.5f; - const float AGE_SORT_COEFFICIENT = 0.25f; - float priority = APPARENT_SIZE_COEFFICIENT * apparentSize - + CENTER_SORT_COEFFICIENT * cosineAngle - + AGE_SORT_COEFFICIENT * age; - - // decrement priority of things outside keyhole - if (distance > view.getCenterRadius()) { - if (!view.sphereIntersectsFrustum(entityPosition, 0.5f * diameter)) { - priority += OUT_OF_VIEW_PENALTY; - } - } - - sortedRenderables.push(SortableEntityRenderer(itr->second, priority)); + float priority = priorityCalculator.computePriority(itr->second); + SortableRenderer entry(itr->second, priority); + sortedRenderables.push(entry); ++itr; } } @@ -398,7 +383,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderer = sortedRenderables.top().renderer; + const EntityRendererPointer& renderer = sortedRenderables.top().getObject(); renderer->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderer->getEntity()->getID()); sortedRenderables.pop(); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h new file mode 100644 index 0000000000..e084c6d1a5 --- /dev/null +++ b/libraries/shared/src/PrioritySortUtil.h @@ -0,0 +1,126 @@ +// +// PrioritySortUtil.h +// +// Created by Andrew Meadows on 2017-11-08 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once +#ifndef hifi_PrioritySortUtil_h +#define hifi_PrioritySortUtil_h + +#include +#include "ViewFrustum.h" + +namespace PrioritySortUtil { + // PrioritySortUtil is a helper template for sorting 3D objects relative to a ViewFrustum. + // To use this utility: + // + // (1) Declare and implent the following methods for your "object" type T: + // + // glm::vec3 PrioritySortUtil::getObjectPosition(const T&); + // float PrioritySortUtil::getObjectRadius(const T&); + // uint64_t PrioritySortUtil::getObjectAge(const T&); + // + // (2) Below the implementation in (1): + / + // #include + // + // (3) Create a PriorityCalculator instance: + // + // PrioritySortUtil::PriorityCalculator calculator(viewFrustum); + // + // (4) Loop over your objects and insert the into a priority_queue: + // + // std::priority_queue< PrioritySortUtil::Sortable > sortedObjects; + // for (T obj in objects) { + // float priority = calculator.computePriority(obj); + // PrioritySortUtil::Sortable entry(obj, priority); + // sortedObjects.push(entry); + // } + + template + class Sortable { + public: + Sortable(const T& object, float sortPriority) : _object(object), _priority(sortPriority) {} + const T& getObject() const { return _object; } + void setPriority(float priority) { _priority = priority; } + bool operator<(const Sortable& other) const { return _priority < other._priority; } + private: + T _object; + float _priority; + }; + + constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; + constexpr float DEFAULT_CENTER_COEF { 0.5f }; + constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; + + template + class PriorityCalculator { + public: + PriorityCalculator() = delete; + + PriorityCalculator(const ViewFrustum& view) : _view(view) { + cacheView(); + } + + PriorityCalculator(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) + : _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) + { + cacheView(); + } + + void setView(const ViewFrustum& view) { _view = view; } + + void setWeights(float angularWeight, float centerWeight, float ageWeight) { + _angularWeight = angularWeight; + _centerWeight = centerWeight; + _ageWeight = ageWeight; + } + + float computePriority(T& object) const { + // priority = weighted linear combination of multiple values: + // (a) angular size + // (b) proximity to center of view + // (c) time since last update + // where the relative "weights" are tuned to scale the contributing values into units of "priority". + + glm::vec3 position = PrioritySortUtil::getObjectPosition(object); + glm::vec3 offset = position - _viewPosition; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + float radius = PrioritySortUtil::getObjectRadius(object); + + float priority = _angularWeight * (radius / distance) + + _centerWeight * (glm::dot(offset, _viewForward) / distance) + + _ageWeight * (float)(usecTimestampNow() - PrioritySortUtil::getObjectAge(object)); + + // decrement priority of things outside keyhole + if (distance + radius > _viewRadius) { + if (!_view.sphereIntersectsFrustum(position, radius)) { + constexpr float OUT_OF_VIEW_PENALTY = -10.0f; + priority += OUT_OF_VIEW_PENALTY; + } + } + return priority; + } + + private: + void cacheView() { + // assuming we'll prioritize many objects: cache these values + _viewPosition = _view.getPosition(); + _viewForward = _view.getDirection(); + _viewRadius = _view.getCenterRadius(); + } + + ViewFrustum _view; + glm::vec3 _viewPosition; + glm::vec3 _viewForward; + float _viewRadius; + float _angularWeight { DEFAULT_ANGULAR_COEF }; + float _centerWeight { DEFAULT_CENTER_COEF }; + float _ageWeight { DEFAULT_AGE_COEF }; + }; +} +#endif // hifi_PrioritySortUtil_h