mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 22:33:34 +02:00
Merge pull request #7056 from samcake/red
Render Scene Octree : A Minute of Arc
This commit is contained in:
commit
4faeea7c3f
42 changed files with 3176 additions and 826 deletions
examples
interface/src
libraries
entities-renderer/src
RenderableEntityItem.hRenderableModelEntityItem.hRenderableParticleEffectEntityItem.cppRenderableParticleEffectEntityItem.hRenderablePolyVoxEntityItem.hRenderableZoneEntityItem.cppRenderableZoneEntityItem.h
entities/src
gpu/src/gpu
octree/src
render-utils/src
render/src/render
|
@ -14,7 +14,7 @@ var Entities, Script, print, Vec3, MyAvatar, Camera, Quat;
|
|||
// NOTE: to test the full rendering of the specified number of objects (as opposed to
|
||||
// testing LOD filtering), you may want to set LOD to manual maximum visibility.
|
||||
|
||||
var LIFETIME = 20;
|
||||
var LIFETIME = 120;
|
||||
var ROWS_X = 17;
|
||||
var ROWS_Y = 10;
|
||||
var ROWS_Z = 10;
|
||||
|
|
99
examples/utilities/tools/debugRenderCulling.js
Normal file
99
examples/utilities/tools/debugRenderCulling.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// debugRenderOctree.js
|
||||
// examples/utilities/tools
|
||||
//
|
||||
// Sam Gateau
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include("cookies.js");
|
||||
|
||||
var panel = new Panel(10, 300);
|
||||
var drawOctree = Render.RenderDeferredTask.DrawSceneOctree;
|
||||
Render.RenderDeferredTask.DrawSceneOctree.enabled = true;
|
||||
Render.RenderDeferredTask.DrawItemSelection.enabled = true;
|
||||
|
||||
panel.newCheckbox("Show Octree Cells",
|
||||
function(value) { Render.RenderDeferredTask.DrawSceneOctree.showVisibleCells = value; },
|
||||
function() { return (Render.RenderDeferredTask.DrawSceneOctree.showVisibleCells); },
|
||||
function(value) { return (value); }
|
||||
);
|
||||
panel.newCheckbox("Show Empty Cells",
|
||||
function(value) { Render.RenderDeferredTask.DrawSceneOctree.showEmptyCells = value; },
|
||||
function() { return (Render.RenderDeferredTask.DrawSceneOctree.showEmptyCells); },
|
||||
function(value) { return (value); }
|
||||
);
|
||||
panel.newCheckbox("Freeze Frustum",
|
||||
function(value) { Render.RenderDeferredTask.FetchSceneSelection.freezeFrustum = value; Render.RenderDeferredTask.CullSceneSelection.freezeFrustum = value; },
|
||||
function() { return (Render.RenderDeferredTask.FetchSceneSelection.freezeFrustum); },
|
||||
function(value) { return (value); }
|
||||
);
|
||||
panel.newCheckbox("Show Inside Items",
|
||||
function(value) { Render.RenderDeferredTask.DrawItemSelection.showInsideItems = value; },
|
||||
function() { return (Render.RenderDeferredTask.DrawItemSelection.showInsideItems); },
|
||||
function(value) { return (value); }
|
||||
);
|
||||
|
||||
panel.newCheckbox("Show Inside Subcell Items",
|
||||
function(value) { Render.RenderDeferredTask.DrawItemSelection.showInsideSubcellItems = value; },
|
||||
function() { return (Render.RenderDeferredTask.DrawItemSelection.showInsideSubcellItems); },
|
||||
function(value) { return (value); }
|
||||
);
|
||||
|
||||
panel.newCheckbox("Show Partial Items",
|
||||
function(value) { Render.RenderDeferredTask.DrawItemSelection.showPartialItems = value; },
|
||||
function() { return (Render.RenderDeferredTask.DrawItemSelection.showPartialItems); },
|
||||
function(value) { return (value); }
|
||||
);
|
||||
|
||||
panel.newCheckbox("Show Partial Subcell Items",
|
||||
function(value) { Render.RenderDeferredTask.DrawItemSelection.showPartialSubcellItems = value; },
|
||||
function() { return (Render.RenderDeferredTask.DrawItemSelection.showPartialSubcellItems); },
|
||||
function(value) { return (value); }
|
||||
);
|
||||
|
||||
/*
|
||||
panel.newSlider('Cells Free / Allocated', -1, 1,
|
||||
function(value) { value; }, // setter
|
||||
function() { return Render.RenderDeferredTask.DrawSceneOctree.numFreeCells; }, // getter
|
||||
function(value) { return value; });
|
||||
|
||||
this.update = function () {
|
||||
var numFree = Render.RenderDeferredTask.DrawSceneOctree.numFreeCells;
|
||||
var numAlloc = Render.RenderDeferredTask.DrawSceneOctree.numAllocatedCells;
|
||||
var title = [
|
||||
' ' + name,
|
||||
numFree + ' / ' + numAlloc
|
||||
].join('\t');
|
||||
|
||||
widget.editTitle({ text: title });
|
||||
slider.setMaxValue(numAlloc);
|
||||
};
|
||||
*/
|
||||
function mouseMoveEvent(event) {
|
||||
panel.mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
function mousePressEvent(event) {
|
||||
panel.mousePressEvent(event);
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
panel.mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
|
||||
function scriptEnding() {
|
||||
panel.destroy();
|
||||
Render.RenderDeferredTask.DrawSceneOctree.enabled = false;
|
||||
Render.RenderDeferredTask.DrawItemSelection.enabled = false;
|
||||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
|
|
@ -3661,7 +3661,7 @@ public:
|
|||
static render::ItemID _item; // unique WorldBoxRenderData
|
||||
};
|
||||
|
||||
render::ItemID WorldBoxRenderData::_item = 0;
|
||||
render::ItemID WorldBoxRenderData::_item { render::Item::INVALID_ITEM_ID };
|
||||
|
||||
namespace render {
|
||||
template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); }
|
||||
|
|
|
@ -479,7 +479,7 @@ private:
|
|||
|
||||
quint64 _lastFaceTrackerUpdate;
|
||||
|
||||
render::ScenePointer _main3DScene{ new render::Scene() };
|
||||
render::ScenePointer _main3DScene{ new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) };
|
||||
render::EnginePointer _renderEngine{ new render::Engine() };
|
||||
gpu::ContextPointer _gpuContext; // initialized during window creation
|
||||
|
||||
|
|
|
@ -239,7 +239,7 @@ protected:
|
|||
|
||||
virtual void updatePalms();
|
||||
|
||||
render::ItemID _renderItemID;
|
||||
render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID };
|
||||
|
||||
ThreadSafeValueCache<glm::vec3> _leftPalmPositionCache { glm::vec3() };
|
||||
ThreadSafeValueCache<glm::quat> _leftPalmRotationCache { glm::quat() };
|
||||
|
|
|
@ -92,7 +92,7 @@ public:
|
|||
protected:
|
||||
float updatePulse();
|
||||
|
||||
render::ItemID _renderItemID;
|
||||
render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID };
|
||||
|
||||
bool _isLoaded;
|
||||
float _alpha;
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include <render/Scene.h>
|
||||
#include <EntityItem.h>
|
||||
#include "AbstractViewStateInterface.h"
|
||||
|
||||
|
||||
// These or the icon "name" used by the render item status value, they correspond to the atlas texture used by the DrawItemStatus
|
||||
// job in the current rendering pipeline defined as of now (11/2015) in render-utils/RenderDeferredTask.cpp.
|
||||
|
@ -66,8 +68,22 @@ public:
|
|||
pendingChanges.removeItem(_myItem);
|
||||
}
|
||||
|
||||
void notifyChanged() {
|
||||
if (_myItem == render::Item::INVALID_ITEM_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
render::PendingChanges pendingChanges;
|
||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||
|
||||
pendingChanges.updateItem<RenderableEntityItemProxy>(_myItem, [](RenderableEntityItemProxy& data) {
|
||||
});
|
||||
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
}
|
||||
|
||||
private:
|
||||
render::ItemID _myItem;
|
||||
render::ItemID _myItem { render::Item::INVALID_ITEM_ID };
|
||||
};
|
||||
|
||||
|
||||
|
@ -75,6 +91,8 @@ private:
|
|||
public: \
|
||||
virtual bool addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override { return _renderHelper.addToScene(self, scene, pendingChanges); } \
|
||||
virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override { _renderHelper.removeFromScene(self, scene, pendingChanges); } \
|
||||
virtual void locationChanged() override { EntityItem::locationChanged(); _renderHelper.notifyChanged(); } \
|
||||
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); _renderHelper.notifyChanged(); } \
|
||||
private: \
|
||||
SimpleRenderableEntityItem _renderHelper;
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ private:
|
|||
QVector<QVector<glm::vec3>> _points;
|
||||
bool _dimensionsInitialized = true;
|
||||
|
||||
render::ItemID _myMetaItem;
|
||||
render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
|
||||
|
||||
bool _showCollisionHull = false;
|
||||
|
||||
|
|
|
@ -310,3 +310,14 @@ void RenderableParticleEffectEntityItem::createPipelines() {
|
|||
_texturedPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableParticleEffectEntityItem::notifyBoundChanged() {
|
||||
if (_renderItemId == render::Item::INVALID_ITEM_ID) {
|
||||
return;
|
||||
}
|
||||
render::PendingChanges pendingChanges;
|
||||
pendingChanges.updateItem<ParticlePayloadData>(_renderItemId, [](ParticlePayloadData& payload) {
|
||||
});
|
||||
|
||||
_scene->enqueuePendingChanges(pendingChanges);
|
||||
}
|
|
@ -29,10 +29,15 @@ public:
|
|||
virtual void removeFromScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges) override;
|
||||
|
||||
protected:
|
||||
virtual void locationChanged() override { EntityItem::locationChanged(); notifyBoundChanged(); }
|
||||
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
|
||||
|
||||
void notifyBoundChanged();
|
||||
|
||||
void createPipelines();
|
||||
|
||||
render::ScenePointer _scene;
|
||||
render::ItemID _renderItemId;
|
||||
render::ItemID _renderItemId{ render::Item::INVALID_ITEM_ID };
|
||||
|
||||
NetworkTexturePointer _texture;
|
||||
gpu::PipelinePointer _untexturedPipeline;
|
||||
|
|
|
@ -129,7 +129,7 @@ private:
|
|||
NetworkTexturePointer _zTexture;
|
||||
|
||||
const int MATERIAL_GPU_SLOT = 3;
|
||||
render::ItemID _myItem;
|
||||
render::ItemID _myItem{ render::Item::INVALID_ITEM_ID };
|
||||
static gpu::PipelinePointer _pipeline;
|
||||
|
||||
ShapeInfo _shapeInfo;
|
||||
|
|
|
@ -234,3 +234,17 @@ void RenderableZoneEntityItem::removeFromScene(EntityItemPointer self, std::shar
|
|||
_model->removeFromScene(scene, pendingChanges);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RenderableZoneEntityItem::notifyBoundChanged() {
|
||||
if (_myMetaItem == render::Item::INVALID_ITEM_ID) {
|
||||
return;
|
||||
}
|
||||
render::PendingChanges pendingChanges;
|
||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||
|
||||
pendingChanges.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [](RenderableZoneEntityItemMeta& data) {
|
||||
});
|
||||
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@ public:
|
|||
virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
|
||||
|
||||
private:
|
||||
virtual void locationChanged() override { EntityItem::locationChanged(); notifyBoundChanged(); }
|
||||
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
|
||||
void notifyBoundChanged();
|
||||
|
||||
Model* getModel();
|
||||
void initialSimulation();
|
||||
void updateGeometry();
|
||||
|
@ -50,7 +54,7 @@ private:
|
|||
Model* _model;
|
||||
bool _needsInitialSimulation;
|
||||
|
||||
render::ItemID _myMetaItem;
|
||||
render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
|
||||
};
|
||||
|
||||
#endif // hifi_RenderableZoneEntityItem_h
|
||||
|
|
|
@ -1206,7 +1206,6 @@ void EntityItem::setDimensions(const glm::vec3& value) {
|
|||
return;
|
||||
}
|
||||
setScale(value);
|
||||
requiresRecalcBoxes();
|
||||
}
|
||||
|
||||
/// The maximum bounding cube for the entity, independent of it's rotation.
|
||||
|
@ -1946,3 +1945,8 @@ void EntityItem::locationChanged() {
|
|||
requiresRecalcBoxes();
|
||||
SpatiallyNestable::locationChanged(); // tell all the children, also
|
||||
}
|
||||
|
||||
void EntityItem::dimensionsChanged() {
|
||||
requiresRecalcBoxes();
|
||||
SpatiallyNestable::dimensionsChanged(); // Do what you have to do
|
||||
}
|
||||
|
|
|
@ -251,8 +251,9 @@ public:
|
|||
const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity
|
||||
|
||||
/// registration point as ratio of entity
|
||||
void setRegistrationPoint(const glm::vec3& value)
|
||||
{ _registrationPoint = glm::clamp(value, 0.0f, 1.0f); requiresRecalcBoxes(); }
|
||||
void setRegistrationPoint(const glm::vec3& value) {
|
||||
_registrationPoint = glm::clamp(value, 0.0f, 1.0f); dimensionsChanged(); // Registration Point affects the bounding box
|
||||
}
|
||||
|
||||
bool hasAngularVelocity() const { return getAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; }
|
||||
bool hasLocalAngularVelocity() const { return getLocalAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; }
|
||||
|
@ -424,6 +425,8 @@ protected:
|
|||
void setActionDataInternal(QByteArray actionData);
|
||||
|
||||
virtual void locationChanged() override;
|
||||
virtual void dimensionsChanged() override;
|
||||
|
||||
EntityTypes::EntityType _type;
|
||||
quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity,
|
||||
// and physics changes
|
||||
|
|
|
@ -10,35 +10,6 @@
|
|||
!>
|
||||
<@if not GPU_COLOR_SLH@>
|
||||
<@def GPU_COLOR_SLH@>
|
||||
<!
|
||||
float colorComponentToLinear(float cs) {
|
||||
// sRGB to linear conversion
|
||||
// { cs / 12.92, cs <= 0.04045
|
||||
// cl = {
|
||||
// { ((cs + 0.055)/1.055)^2.4, cs > 0.04045
|
||||
// constants:
|
||||
// T = 0.04045
|
||||
// A = 1 / 1.055 = 0.94786729857
|
||||
// B = 0.055 * A = 0.05213270142
|
||||
// C = 1 / 12.92 = 0.0773993808
|
||||
// G = 2.4
|
||||
const float T = 0.04045;
|
||||
const float A = 0.947867;
|
||||
const float B = 0.052132;
|
||||
const float C = 0.077399;
|
||||
const float G = 2.4;
|
||||
|
||||
if (cs > T) {
|
||||
return pow((cs * A + B), G);
|
||||
} else {
|
||||
return cs * C;
|
||||
}
|
||||
}
|
||||
|
||||
vec3 colorToLinear(vec3 srgb) {
|
||||
return vec3(colorComponentToLinear(srgb.x), colorComponentToLinear(srgb.y), colorComponentToLinear(srgb.z));
|
||||
}
|
||||
!>
|
||||
|
||||
vec3 colorToLinearRGB(vec3 srgb) {
|
||||
const float GAMMA_22 = 2.2;
|
||||
|
@ -49,4 +20,27 @@ vec4 colorToLinearRGBA(vec4 srgba) {
|
|||
return vec4(colorToLinearRGB(srgba.xyz), srgba.w);
|
||||
}
|
||||
|
||||
<@func declareColorWheel()@>
|
||||
vec3 colorWheel(float normalizedHue) {
|
||||
float v = normalizedHue * 6.f;
|
||||
if (v < 0.f) {
|
||||
return vec3(1.f, 0.f, 0.f);
|
||||
} else if (v < 1.f) {
|
||||
return vec3(1.f, v, 0.f);
|
||||
} else if (v < 2.f) {
|
||||
return vec3(1.f - (v-1.f), 1.f, 0.f);
|
||||
} else if (v < 3.f) {
|
||||
return vec3(0.f, 1.f, (v-2.f));
|
||||
} else if (v < 4.f) {
|
||||
return vec3(0.f, 1.f - (v-3.f), 1.f );
|
||||
} else if (v < 5.f) {
|
||||
return vec3((v-4.f), 0.f, 1.f );
|
||||
} else if (v < 6.f) {
|
||||
return vec3(1.f, 0.f, 1.f - (v-5.f));
|
||||
} else {
|
||||
return vec3(1.f, 0.f, 0.f);
|
||||
}
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@endif@>
|
|
@ -810,3 +810,9 @@ float ViewFrustum::calculateRenderAccuracy(const AABox& bounds, float octreeSize
|
|||
float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) {
|
||||
return voxelSizeScale / powf(2, renderLevel);
|
||||
}
|
||||
|
||||
float ViewFrustum::getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) const {
|
||||
const float maxScale = (float)TREE_SCALE;
|
||||
float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO;
|
||||
return atan(maxScale / visibleDistanceAtMaxScale);
|
||||
}
|
||||
|
|
|
@ -125,6 +125,11 @@ public:
|
|||
float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
|
||||
int boundaryLevelAdjust = 0) const;
|
||||
|
||||
float getAccuracyAngle(float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const;
|
||||
|
||||
enum PlaneIndex { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE, NUM_PLANES };
|
||||
|
||||
const ::Plane* getPlanes() const { return _planes; }
|
||||
private:
|
||||
// Used for keyhole calculations
|
||||
ViewFrustum::location pointInKeyhole(const glm::vec3& point) const;
|
||||
|
@ -160,7 +165,6 @@ private:
|
|||
float _fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES;
|
||||
glm::vec4 _corners[8];
|
||||
glm::vec3 _cornersWorld[8];
|
||||
enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE };
|
||||
::Plane _planes[6]; // How will this be used?
|
||||
|
||||
const char* debugPlaneName (int plane) const;
|
||||
|
|
|
@ -37,7 +37,7 @@ public:
|
|||
protected:
|
||||
std::shared_ptr<AnimDebugDrawData> _animDebugDrawData;
|
||||
std::shared_ptr<AnimDebugDrawPayload> _animDebugDrawPayload;
|
||||
render::ItemID _itemID;
|
||||
render::ItemID _itemID{ render::Item::INVALID_ITEM_ID };
|
||||
|
||||
static gpu::PipelinePointer _pipeline;
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include "render/DrawTask.h"
|
||||
#include "render/DrawStatus.h"
|
||||
#include "render/DrawSceneOctree.h"
|
||||
#include "AmbientOcclusionEffect.h"
|
||||
#include "AntialiasingEffect.h"
|
||||
#include "ToneMappingEffect.h"
|
||||
|
@ -50,18 +51,35 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
|||
// Prepare the ShapePipelines
|
||||
ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>();
|
||||
initDeferredPipelines(*shapePlumber);
|
||||
|
||||
// CPU: Fetch the renderOpaques
|
||||
const auto fetchedOpaques = addJob<FetchItems>("FetchOpaque");
|
||||
const auto culledOpaques = addJob<CullItems<RenderDetails::OPAQUE_ITEM>>("CullOpaque", fetchedOpaques, cullFunctor);
|
||||
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", culledOpaques);
|
||||
|
||||
// CPU only, create the list of renderedTransparents items
|
||||
const auto fetchedTransparents = addJob<FetchItems>("FetchTransparent", FetchItems(
|
||||
ItemFilter::Builder::transparentShape().withoutLayered()));
|
||||
const auto culledTransparents =
|
||||
addJob<CullItems<RenderDetails::TRANSLUCENT_ITEM>>("CullTransparent", fetchedTransparents, cullFunctor);
|
||||
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", culledTransparents, DepthSortItems(false));
|
||||
// CPU jobs:
|
||||
// Fetch and cull the items from the scene
|
||||
auto sceneFilter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
|
||||
const auto sceneSelection = addJob<FetchSpatialTree>("FetchSceneSelection", sceneFilter);
|
||||
const auto culledSceneSelection = addJob<CullSpatialSelection>("CullSceneSelection", sceneSelection, cullFunctor, RenderDetails::OPAQUE_ITEM, sceneFilter);
|
||||
|
||||
// Multi filter visible items into different buckets
|
||||
const int NUM_FILTERS = 3;
|
||||
const int OPAQUE_SHAPE_BUCKET = 0;
|
||||
const int TRANSPARENT_SHAPE_BUCKET = 1;
|
||||
const int LIGHT_BUCKET = 2;
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray triageFilters = { {
|
||||
ItemFilter::Builder::opaqueShape(),
|
||||
ItemFilter::Builder::transparentShape(),
|
||||
ItemFilter::Builder::light()
|
||||
} };
|
||||
const auto filteredItemsBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterSceneSelection", culledSceneSelection, triageFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
|
||||
// Extract / Sort opaques / Transparents / Lights / Overlays
|
||||
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", filteredItemsBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", filteredItemsBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto lights = filteredItemsBuckets[LIGHT_BUCKET];
|
||||
|
||||
// Overlays are not culled because we want to make sure they are seen
|
||||
// Could be considered a bug in the current cullfunctor
|
||||
const auto overlayOpaques = addJob<FetchItems>("FetchOverlayOpaque", ItemFilter::Builder::opaqueShapeLayered());
|
||||
const auto fetchedOverlayOpaques = addJob<FetchItems>("FetchOverlayTransparents", ItemFilter::Builder::transparentShapeLayered());
|
||||
const auto overlayTransparents = addJob<DepthSortItems>("DepthSortTransparentOverlay", fetchedOverlayOpaques, DepthSortItems(false));
|
||||
|
||||
// GPU Jobs: Start preparing the deferred and lighting buffer
|
||||
addJob<PrepareDeferred>("PrepareDeferred");
|
||||
|
@ -79,7 +97,7 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
|||
addJob<AmbientOcclusionEffect>("AmbientOcclusion");
|
||||
|
||||
// Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now.
|
||||
addJob<DrawLight>("DrawLight", cullFunctor);
|
||||
addJob<DrawLight>("DrawLight", lights);
|
||||
|
||||
// DeferredBuffer is complete, now let's shade it into the LightingBuffer
|
||||
addJob<RenderDeferred>("RenderDeferred");
|
||||
|
@ -93,22 +111,35 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
|||
// Lighting Buffer ready for tone mapping
|
||||
addJob<ToneMappingDeferred>("ToneMapping");
|
||||
|
||||
// Debugging Deferred buffer job
|
||||
addJob<DebugDeferredBuffer>("DebugDeferredBuffer");
|
||||
// Overlays
|
||||
addJob<DrawOverlay3D>("DrawOverlay3DOpaque", overlayOpaques, true);
|
||||
addJob<DrawOverlay3D>("DrawOverlay3DTransparent", overlayTransparents, false);
|
||||
|
||||
// Status icon rendering job
|
||||
|
||||
// Debugging stages
|
||||
{
|
||||
// Grab a texture map representing the different status icons and assign that to the drawStatsuJob
|
||||
auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg";
|
||||
auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath);
|
||||
addJob<DrawStatus>("DrawStatus", opaques, DrawStatus(statusIconMap));
|
||||
// Debugging Deferred buffer job
|
||||
addJob<DebugDeferredBuffer>("DebugDeferredBuffer");
|
||||
|
||||
// Scene Octree Debuging job
|
||||
{
|
||||
addJob<DrawSceneOctree>("DrawSceneOctree", sceneSelection);
|
||||
addJob<DrawItemSelection>("DrawItemSelection", sceneSelection);
|
||||
}
|
||||
|
||||
// Status icon rendering job
|
||||
{
|
||||
// Grab a texture map representing the different status icons and assign that to the drawStatsuJob
|
||||
auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg";
|
||||
auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath);
|
||||
addJob<DrawStatus>("DrawStatus", opaques, DrawStatus(statusIconMap));
|
||||
}
|
||||
}
|
||||
|
||||
addJob<DrawOverlay3D>("DrawOverlay3DOpaque", ItemFilter::Builder::opaqueShape().withLayered());
|
||||
addJob<DrawOverlay3D>("DrawOverlay3DTransparent", ItemFilter::Builder::transparentShape().withLayered());
|
||||
|
||||
addJob<HitEffect>("HitEffect");
|
||||
// FIXME: Hit effect is never used, let's hide it for now, probably a more generic way to add custom post process effects
|
||||
// addJob<HitEffect>("HitEffect");
|
||||
|
||||
// Blit!
|
||||
addJob<Blit>("Blit");
|
||||
}
|
||||
|
||||
|
@ -130,13 +161,14 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend
|
|||
}
|
||||
};
|
||||
|
||||
void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems) {
|
||||
void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
auto config = std::static_pointer_cast<Config>(renderContext->jobConfig);
|
||||
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
|
||||
batch.setViewportTransform(args->_viewport);
|
||||
batch.setStateScissorRect(args->_viewport);
|
||||
|
@ -158,28 +190,19 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont
|
|||
});
|
||||
}
|
||||
|
||||
DrawOverlay3D::DrawOverlay3D(ItemFilter filter) : _filter{ filter }, _shapePlumber{ std::make_shared<ShapePlumber>() } {
|
||||
DrawOverlay3D::DrawOverlay3D(bool opaque) :
|
||||
_shapePlumber(std::make_shared<ShapePlumber>()),
|
||||
_opaquePass(opaque) {
|
||||
initOverlay3DPipelines(*_shapePlumber);
|
||||
}
|
||||
|
||||
void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
|
||||
void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const render::ItemBounds& inItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
// render backgrounds
|
||||
auto& scene = sceneContext->_scene;
|
||||
auto& items = scene->getMasterBucket().at(_filter);
|
||||
|
||||
auto config = std::static_pointer_cast<Config>(renderContext->jobConfig);
|
||||
|
||||
ItemIDsBounds inItems;
|
||||
inItems.reserve(items.size());
|
||||
for (auto id : items) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (item.getKey().isVisible() && (item.getLayer() == 1)) {
|
||||
inItems.emplace_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
config->setNumDrawn((int)inItems.size());
|
||||
emit config->numDrawnChanged();
|
||||
|
||||
|
@ -189,7 +212,7 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon
|
|||
// Clear the framebuffer without stereo
|
||||
// Needs to be distinct from the other batch because using the clear call
|
||||
// while stereo is enabled triggers a warning
|
||||
{
|
||||
if (_opaquePass) {
|
||||
gpu::Batch batch;
|
||||
batch.enableStereo(false);
|
||||
batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, true);
|
||||
|
@ -262,7 +285,7 @@ void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const
|
|||
auto& items = scene->getMasterBucket().at(ItemFilter::Builder::background());
|
||||
|
||||
|
||||
ItemIDsBounds inItems;
|
||||
ItemBounds inItems;
|
||||
inItems.reserve(items.size());
|
||||
for (auto id : items) {
|
||||
inItems.emplace_back(id);
|
||||
|
|
|
@ -43,6 +43,7 @@ class DrawConfig : public render::Job::Config {
|
|||
Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)
|
||||
Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty)
|
||||
public:
|
||||
|
||||
int getNumDrawn() { return numDrawn; }
|
||||
void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); }
|
||||
|
||||
|
@ -59,12 +60,12 @@ protected:
|
|||
class DrawDeferred {
|
||||
public:
|
||||
using Config = DrawConfig;
|
||||
using JobModel = render::Job::ModelI<DrawDeferred, render::ItemIDsBounds, Config>;
|
||||
using JobModel = render::Job::ModelI<DrawDeferred, render::ItemBounds, Config>;
|
||||
|
||||
DrawDeferred(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {}
|
||||
|
||||
void configure(const Config& config) { _maxDrawn = config.maxDrawn; }
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemIDsBounds& inItems);
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems);
|
||||
|
||||
protected:
|
||||
render::ShapePlumberPointer _shapePlumber;
|
||||
|
@ -110,17 +111,17 @@ protected:
|
|||
class DrawOverlay3D {
|
||||
public:
|
||||
using Config = DrawOverlay3DConfig;
|
||||
using JobModel = render::Job::Model<DrawOverlay3D, Config>;
|
||||
using JobModel = render::Job::ModelI<DrawOverlay3D, render::ItemBounds, Config>;
|
||||
|
||||
DrawOverlay3D(render::ItemFilter filter);
|
||||
DrawOverlay3D(bool opaque);
|
||||
|
||||
void configure(const Config& config) { _maxDrawn = config.maxDrawn; }
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems);
|
||||
|
||||
protected:
|
||||
render::ItemFilter _filter;
|
||||
render::ShapePlumberPointer _shapePlumber;
|
||||
int _maxDrawn; // initialized by Config
|
||||
bool _opaquePass{ true };
|
||||
};
|
||||
|
||||
class Blit {
|
||||
|
|
337
libraries/render/src/render/CullTask.cpp
Normal file
337
libraries/render/src/render/CullTask.cpp
Normal file
|
@ -0,0 +1,337 @@
|
|||
//
|
||||
// CullTask.cpp
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Sam Gateau on 2/2/16.
|
||||
// Copyright 2016 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 "CullTask.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <gpu/Context.h>
|
||||
|
||||
using namespace render;
|
||||
|
||||
void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
|
||||
const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
RenderArgs* args = renderContext->args;
|
||||
ViewFrustum* frustum = args->_viewFrustum;
|
||||
|
||||
details._considered += inItems.size();
|
||||
|
||||
// Culling / LOD
|
||||
for (auto item : inItems) {
|
||||
if (item.bound.isNull()) {
|
||||
outItems.emplace_back(item); // One more Item to render
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: some entity types (like lights) might want to be rendered even
|
||||
// when they are outside of the view frustum...
|
||||
bool outOfView;
|
||||
{
|
||||
PerformanceTimer perfTimer("boxInFrustum");
|
||||
outOfView = frustum->boxInFrustum(item.bound) == ViewFrustum::OUTSIDE;
|
||||
}
|
||||
if (!outOfView) {
|
||||
bool bigEnoughToRender;
|
||||
{
|
||||
PerformanceTimer perfTimer("shouldRender");
|
||||
bigEnoughToRender = cullFunctor(args, item.bound);
|
||||
}
|
||||
if (bigEnoughToRender) {
|
||||
outItems.emplace_back(item); // One more Item to render
|
||||
} else {
|
||||
details._tooSmall++;
|
||||
}
|
||||
} else {
|
||||
details._outOfView++;
|
||||
}
|
||||
}
|
||||
details._rendered += outItems.size();
|
||||
}
|
||||
|
||||
struct ItemBoundSort {
|
||||
float _centerDepth = 0.0f;
|
||||
float _nearDepth = 0.0f;
|
||||
float _farDepth = 0.0f;
|
||||
ItemID _id = 0;
|
||||
AABox _bounds;
|
||||
|
||||
ItemBoundSort() {}
|
||||
ItemBoundSort(float centerDepth, float nearDepth, float farDepth, ItemID id, const AABox& bounds) : _centerDepth(centerDepth), _nearDepth(nearDepth), _farDepth(farDepth), _id(id), _bounds(bounds) {}
|
||||
};
|
||||
|
||||
struct FrontToBackSort {
|
||||
bool operator() (const ItemBoundSort& left, const ItemBoundSort& right) {
|
||||
return (left._centerDepth < right._centerDepth);
|
||||
}
|
||||
};
|
||||
|
||||
struct BackToFrontSort {
|
||||
bool operator() (const ItemBoundSort& left, const ItemBoundSort& right) {
|
||||
return (left._centerDepth > right._centerDepth);
|
||||
}
|
||||
};
|
||||
|
||||
void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
||||
// Allocate and simply copy
|
||||
outItems.clear();
|
||||
outItems.reserve(inItems.size());
|
||||
|
||||
|
||||
// Make a local dataset of the center distance and closest point distance
|
||||
std::vector<ItemBoundSort> itemBoundSorts;
|
||||
itemBoundSorts.reserve(outItems.size());
|
||||
|
||||
for (auto itemDetails : inItems) {
|
||||
auto item = scene->getItem(itemDetails.id);
|
||||
auto bound = itemDetails.bound; // item.getBound();
|
||||
float distance = args->_viewFrustum->distanceToCamera(bound.calcCenter());
|
||||
|
||||
itemBoundSorts.emplace_back(ItemBoundSort(distance, distance, distance, itemDetails.id, bound));
|
||||
}
|
||||
|
||||
// sort against Z
|
||||
if (frontToBack) {
|
||||
FrontToBackSort frontToBackSort;
|
||||
std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), frontToBackSort);
|
||||
} else {
|
||||
BackToFrontSort backToFrontSort;
|
||||
std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), backToFrontSort);
|
||||
}
|
||||
|
||||
// FInally once sorted result to a list of itemID
|
||||
for (auto& item : itemBoundSorts) {
|
||||
outItems.emplace_back(ItemBound(item._id, item._bounds));
|
||||
}
|
||||
}
|
||||
|
||||
void DepthSortItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems);
|
||||
}
|
||||
|
||||
|
||||
void FetchItems::configure(const Config& config) {
|
||||
}
|
||||
|
||||
void FetchItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
auto& scene = sceneContext->_scene;
|
||||
|
||||
outItems.clear();
|
||||
|
||||
const auto& bucket = scene->getMasterBucket();
|
||||
const auto& items = bucket.find(_filter);
|
||||
if (items != bucket.end()) {
|
||||
outItems.reserve(items->second.size());
|
||||
for (auto& id : items->second) {
|
||||
auto& item = scene->getItem(id);
|
||||
outItems.emplace_back(ItemBound(id, item.getBound()));
|
||||
}
|
||||
}
|
||||
|
||||
std::static_pointer_cast<Config>(renderContext->jobConfig)->numItems = (int)outItems.size();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FetchSpatialTree::configure(const Config& config) {
|
||||
_justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
|
||||
_freezeFrustum = config.freezeFrustum;
|
||||
_lodAngle = config.lodAngle;
|
||||
}
|
||||
|
||||
void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
RenderArgs* args = renderContext->args;
|
||||
auto& scene = sceneContext->_scene;
|
||||
|
||||
// start fresh
|
||||
outSelection.clear();
|
||||
|
||||
// Eventually use a frozen frustum
|
||||
auto queryFrustum = *args->_viewFrustum;
|
||||
if (_freezeFrustum) {
|
||||
if (_justFrozeFrustum) {
|
||||
_justFrozeFrustum = false;
|
||||
_frozenFrutstum = *args->_viewFrustum;
|
||||
}
|
||||
queryFrustum = _frozenFrutstum;
|
||||
}
|
||||
|
||||
// Octree selection!
|
||||
|
||||
float angle = glm::degrees(queryFrustum.getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
|
||||
|
||||
|
||||
scene->getSpatialTree().selectCellItems(outSelection, _filter, queryFrustum, angle);
|
||||
|
||||
}
|
||||
|
||||
void CullSpatialSelection::configure(const Config& config) {
|
||||
_justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
|
||||
_freezeFrustum = config.freezeFrustum;
|
||||
}
|
||||
|
||||
void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
|
||||
const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
RenderArgs* args = renderContext->args;
|
||||
auto& scene = sceneContext->_scene;
|
||||
|
||||
auto& details = args->_details.edit(_detailType);
|
||||
details._considered += inSelection.numItems();
|
||||
|
||||
// Eventually use a frozen frustum
|
||||
auto argFrustum = args->_viewFrustum;
|
||||
if (_freezeFrustum) {
|
||||
if (_justFrozeFrustum) {
|
||||
_justFrozeFrustum = false;
|
||||
_frozenFrutstum = *args->_viewFrustum;
|
||||
}
|
||||
args->_viewFrustum = &_frozenFrutstum; // replace the true view frustum by the frozen one
|
||||
}
|
||||
|
||||
// Culling Frustum / solidAngle test helper class
|
||||
struct Test {
|
||||
CullFunctor _functor;
|
||||
RenderArgs* _args;
|
||||
RenderDetails::Item& _renderDetails;
|
||||
glm::vec3 _eyePos;
|
||||
float _squareTanAlpha;
|
||||
|
||||
Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) :
|
||||
_functor(functor),
|
||||
_args(pargs),
|
||||
_renderDetails(renderDetails)
|
||||
{
|
||||
// FIXME: Keep this code here even though we don't use it yet
|
||||
/*_eyePos = _args->_viewFrustum->getPosition();
|
||||
float a = glm::degrees(_args->_viewFrustum->getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust));
|
||||
auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees
|
||||
angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree
|
||||
auto tanAlpha = tan(angle);
|
||||
_squareTanAlpha = (float)(tanAlpha * tanAlpha);
|
||||
*/
|
||||
}
|
||||
|
||||
bool frustumTest(const AABox& bound) {
|
||||
if (_args->_viewFrustum->boxInFrustum(bound) == ViewFrustum::OUTSIDE) {
|
||||
_renderDetails._outOfView++;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool solidAngleTest(const AABox& bound) {
|
||||
// FIXME: Keep this code here even though we don't use it yet
|
||||
//auto eyeToPoint = bound.calcCenter() - _eyePos;
|
||||
//auto boundSize = bound.getDimensions();
|
||||
//float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha;
|
||||
//if (test < 0.0f) {
|
||||
if (!_functor(_args, bound)) {
|
||||
_renderDetails._tooSmall++;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
Test test(_cullFunctor, args, details);
|
||||
|
||||
// Now we have a selection of items to render
|
||||
outItems.clear();
|
||||
outItems.reserve(inSelection.numItems());
|
||||
|
||||
// Now get the bound, and
|
||||
// filter individually against the _filter
|
||||
// visibility cull if partially selected ( octree cell contianing it was partial)
|
||||
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
|
||||
|
||||
// inside & fit items: easy, just filter
|
||||
{
|
||||
PerformanceTimer perfTimer("insideFitItems");
|
||||
for (auto id : inSelection.insideItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inside & subcell items: filter & distance cull
|
||||
{
|
||||
PerformanceTimer perfTimer("insideSmallItems");
|
||||
for (auto id : inSelection.insideSubcellItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
if (test.solidAngleTest(itemBound.bound)) {
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// partial & fit items: filter & frustum cull
|
||||
{
|
||||
PerformanceTimer perfTimer("partialFitItems");
|
||||
for (auto id : inSelection.partialItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
if (test.frustumTest(itemBound.bound)) {
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// partial & subcell items:: filter & frutum cull & solidangle cull
|
||||
{
|
||||
PerformanceTimer perfTimer("partialSmallItems");
|
||||
for (auto id : inSelection.partialSubcellItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
if (test.frustumTest(itemBound.bound)) {
|
||||
if (test.solidAngleTest(itemBound.bound)) {
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
details._rendered += outItems.size();
|
||||
|
||||
|
||||
// Restore frustum if using the frozen one:
|
||||
if (_freezeFrustum) {
|
||||
args->_viewFrustum = argFrustum;
|
||||
}
|
||||
|
||||
std::static_pointer_cast<Config>(renderContext->jobConfig)->numItems = (int)outItems.size();
|
||||
}
|
236
libraries/render/src/render/CullTask.h
Normal file
236
libraries/render/src/render/CullTask.h
Normal file
|
@ -0,0 +1,236 @@
|
|||
//
|
||||
// CullTask.h
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Sam Gateau on 2/2/16.
|
||||
// Copyright 2016 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_render_CullTask_h
|
||||
#define hifi_render_CullTask_h
|
||||
|
||||
#include "Engine.h"
|
||||
#include "ViewFrustum.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
using CullFunctor = std::function<bool(const RenderArgs*, const AABox&)>;
|
||||
|
||||
void cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
|
||||
const ItemBounds& inItems, ItemBounds& outItems);
|
||||
void depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems);
|
||||
|
||||
class FetchItemsConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int numItems READ getNumItems)
|
||||
public:
|
||||
int getNumItems() { return numItems; }
|
||||
|
||||
int numItems{ 0 };
|
||||
};
|
||||
|
||||
class FetchItems {
|
||||
public:
|
||||
using Config = FetchItemsConfig;
|
||||
using JobModel = Job::ModelO<FetchItems, ItemBounds, Config>;
|
||||
|
||||
FetchItems() {}
|
||||
FetchItems(const ItemFilter& filter) : _filter(filter) {}
|
||||
|
||||
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
|
||||
|
||||
void configure(const Config& config);
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemBounds& outItems);
|
||||
};
|
||||
|
||||
|
||||
template<RenderDetails::Type T>
|
||||
class CullItems {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<CullItems<T>, ItemBounds, ItemBounds>;
|
||||
|
||||
CullItems(CullFunctor cullFunctor) : _cullFunctor{ cullFunctor } {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
const auto& args = renderContext->args;
|
||||
auto& details = args->_details.edit(T);
|
||||
outItems.clear();
|
||||
outItems.reserve(inItems.size());
|
||||
render::cullItems(renderContext, _cullFunctor, details, inItems, outItems);
|
||||
}
|
||||
|
||||
protected:
|
||||
CullFunctor _cullFunctor;
|
||||
};
|
||||
|
||||
|
||||
class FetchSpatialTreeConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int numItems READ getNumItems)
|
||||
Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum)
|
||||
Q_PROPERTY(float LODAngle MEMBER lodAngle NOTIFY dirty)
|
||||
|
||||
public:
|
||||
int numItems{ 0 };
|
||||
int getNumItems() { return numItems; }
|
||||
|
||||
bool freezeFrustum{ false };
|
||||
|
||||
float lodAngle{ 2.0 };
|
||||
public slots:
|
||||
void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); }
|
||||
|
||||
signals:
|
||||
void dirty();
|
||||
};
|
||||
|
||||
class FetchSpatialTree {
|
||||
bool _freezeFrustum{ false }; // initialized by Config
|
||||
bool _justFrozeFrustum{ false };
|
||||
ViewFrustum _frozenFrutstum;
|
||||
float _lodAngle;
|
||||
public:
|
||||
using Config = FetchSpatialTreeConfig;
|
||||
using JobModel = Job::ModelO<FetchSpatialTree, ItemSpatialTree::ItemSelection, Config>;
|
||||
|
||||
FetchSpatialTree() {}
|
||||
FetchSpatialTree(const ItemFilter& filter) : _filter(filter) {}
|
||||
|
||||
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
|
||||
|
||||
void configure(const Config& config);
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection);
|
||||
};
|
||||
|
||||
class CullSpatialSelectionConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int numItems READ getNumItems)
|
||||
Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum)
|
||||
public:
|
||||
int numItems{ 0 };
|
||||
int getNumItems() { return numItems; }
|
||||
|
||||
bool freezeFrustum{ false };
|
||||
public slots:
|
||||
void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); }
|
||||
|
||||
signals:
|
||||
void dirty();
|
||||
};
|
||||
|
||||
class CullSpatialSelection {
|
||||
bool _freezeFrustum{ false }; // initialized by Config
|
||||
bool _justFrozeFrustum{ false };
|
||||
ViewFrustum _frozenFrutstum;
|
||||
public:
|
||||
using Config = CullSpatialSelectionConfig;
|
||||
using JobModel = Job::ModelIO<CullSpatialSelection, ItemSpatialTree::ItemSelection, ItemBounds, Config>;
|
||||
|
||||
CullSpatialSelection(CullFunctor cullFunctor, RenderDetails::Type type, const ItemFilter& filter) :
|
||||
_cullFunctor{ cullFunctor },
|
||||
_detailType(type),
|
||||
_filter(filter) {}
|
||||
|
||||
CullSpatialSelection(CullFunctor cullFunctor) :
|
||||
_cullFunctor{ cullFunctor } {}
|
||||
|
||||
CullFunctor _cullFunctor;
|
||||
RenderDetails::Type _detailType{ RenderDetails::OPAQUE_ITEM };
|
||||
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
|
||||
|
||||
void configure(const Config& config);
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems);
|
||||
};
|
||||
|
||||
class FilterItemSelectionConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int numItems READ getNumItems)
|
||||
public:
|
||||
int numItems{ 0 };
|
||||
int getNumItems() { return numItems; }
|
||||
};
|
||||
|
||||
class FilterItemSelection {
|
||||
public:
|
||||
using Config = FilterItemSelectionConfig;
|
||||
using JobModel = Job::ModelIO<FilterItemSelection, ItemBounds, ItemBounds, Config>;
|
||||
|
||||
FilterItemSelection() {}
|
||||
FilterItemSelection(const ItemFilter& filter) :
|
||||
_filter(filter) {}
|
||||
|
||||
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
|
||||
|
||||
void configure(const Config& config);
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems);
|
||||
};
|
||||
|
||||
class MultiFilterItemConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int numItems READ getNumItems)
|
||||
public:
|
||||
int numItems{ 0 };
|
||||
int getNumItems() { return numItems; }
|
||||
};
|
||||
|
||||
template < class T, int NUM >
|
||||
class VaryingArray : public std::array<Varying, NUM> {
|
||||
public:
|
||||
VaryingArray() {
|
||||
for (size_t i = 0; i < NUM; i++) {
|
||||
(*this)[i] = Varying(T());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <int NUM_FILTERS>
|
||||
class MultiFilterItem {
|
||||
public:
|
||||
using ItemFilterArray = std::array<ItemFilter, NUM_FILTERS>;
|
||||
using ItemBoundsArray = VaryingArray<ItemBounds, NUM_FILTERS>;
|
||||
using Config = MultiFilterItemConfig;
|
||||
using JobModel = Job::ModelIO<MultiFilterItem, ItemBounds, ItemBoundsArray, Config>;
|
||||
|
||||
MultiFilterItem() {}
|
||||
MultiFilterItem(const ItemFilterArray& filters) :
|
||||
_filters(filters) {}
|
||||
|
||||
ItemFilterArray _filters;
|
||||
|
||||
void configure(const Config& config) {}
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBoundsArray& outItems) {
|
||||
auto& scene = sceneContext->_scene;
|
||||
|
||||
// Clear previous values
|
||||
for (size_t i = 0; i < NUM_FILTERS; i++) {
|
||||
outItems[i].template edit<ItemBounds>().clear();
|
||||
}
|
||||
|
||||
// For each item, filter it into the buckets
|
||||
for (auto itemBound : inItems) {
|
||||
auto& item = scene->getItem(itemBound.id);
|
||||
auto itemKey = item.getKey();
|
||||
for (size_t i = 0; i < NUM_FILTERS; i++) {
|
||||
if (_filters[i].test(itemKey)) {
|
||||
outItems[i].template edit<ItemBounds>().emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DepthSortItems {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<DepthSortItems, ItemBounds, ItemBounds>;
|
||||
|
||||
bool _frontToBack;
|
||||
DepthSortItems(bool frontToBack = true) : _frontToBack(frontToBack) {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // hifi_render_CullTask_h;
|
278
libraries/render/src/render/DrawSceneOctree.cpp
Normal file
278
libraries/render/src/render/DrawSceneOctree.cpp
Normal file
|
@ -0,0 +1,278 @@
|
|||
//
|
||||
// DrawSceneOctree.cpp
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Sam Gateau on 1/25/16.
|
||||
// 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 "DrawSceneOctree.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include <RenderArgs.h>
|
||||
|
||||
#include <gpu/Context.h>
|
||||
#include <gpu/StandardShaderLib.h>
|
||||
|
||||
|
||||
#include "drawCellBounds_vert.h"
|
||||
#include "drawCellBounds_frag.h"
|
||||
#include "drawLODReticle_frag.h"
|
||||
|
||||
#include "drawItemBounds_vert.h"
|
||||
#include "drawItemBounds_frag.h"
|
||||
|
||||
using namespace render;
|
||||
|
||||
|
||||
const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() {
|
||||
if (!_drawCellBoundsPipeline) {
|
||||
auto vs = gpu::Shader::createVertex(std::string(drawCellBounds_vert));
|
||||
auto ps = gpu::Shader::createPixel(std::string(drawCellBounds_frag));
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
|
||||
_drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation");
|
||||
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
|
||||
state->setDepthTest(true, false, gpu::LESS_EQUAL);
|
||||
|
||||
// Blend on transparent
|
||||
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
|
||||
|
||||
// Good to go add the brand new pipeline
|
||||
_drawCellBoundsPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
return _drawCellBoundsPipeline;
|
||||
}
|
||||
|
||||
const gpu::PipelinePointer DrawSceneOctree::getDrawLODReticlePipeline() {
|
||||
if (!_drawLODReticlePipeline) {
|
||||
auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS();
|
||||
auto ps = gpu::Shader::createPixel(std::string(drawLODReticle_frag));
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
|
||||
// _drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation");
|
||||
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
|
||||
// Blend on transparent
|
||||
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
|
||||
|
||||
// Good to go add the brand new pipeline
|
||||
_drawLODReticlePipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
return _drawLODReticlePipeline;
|
||||
}
|
||||
|
||||
void DrawSceneOctree::configure(const Config& config) {
|
||||
_showVisibleCells = config.showVisibleCells;
|
||||
_showEmptyCells = config.showEmptyCells;
|
||||
}
|
||||
|
||||
|
||||
void DrawSceneOctree::run(const SceneContextPointer& sceneContext,
|
||||
const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
RenderArgs* args = renderContext->args;
|
||||
auto& scene = sceneContext->_scene;
|
||||
|
||||
std::static_pointer_cast<Config>(renderContext->jobConfig)->numAllocatedCells = (int)scene->getSpatialTree().getNumAllocatedCells();
|
||||
std::static_pointer_cast<Config>(renderContext->jobConfig)->numFreeCells = (int)scene->getSpatialTree().getNumFreeCells();
|
||||
|
||||
|
||||
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
|
||||
glm::mat4 projMat;
|
||||
Transform viewMat;
|
||||
args->_viewFrustum->evalProjectionMatrix(projMat);
|
||||
args->_viewFrustum->evalViewTransform(viewMat);
|
||||
batch.setViewportTransform(args->_viewport);
|
||||
|
||||
batch.setProjectionTransform(projMat);
|
||||
batch.setViewTransform(viewMat);
|
||||
batch.setModelTransform(Transform());
|
||||
|
||||
// bind the one gpu::Pipeline we need
|
||||
batch.setPipeline(getDrawCellBoundsPipeline());
|
||||
|
||||
if (_showVisibleCells) {
|
||||
|
||||
for (const auto& cellID : inSelection.cellSelection.insideCells) {
|
||||
auto cell = scene->getSpatialTree().getConcreteCell(cellID);
|
||||
auto cellLoc = cell.getlocation();
|
||||
glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth);
|
||||
|
||||
bool doDraw = true;
|
||||
if (cell.isBrickEmpty() || !cell.hasBrick()) {
|
||||
if (!_showEmptyCells) {
|
||||
doDraw = false;
|
||||
}
|
||||
cellLocation.w *= -1;
|
||||
}
|
||||
if (doDraw) {
|
||||
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
|
||||
batch.draw(gpu::LINES, 24, 0);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& cellID : inSelection.cellSelection.partialCells) {
|
||||
auto cell = scene->getSpatialTree().getConcreteCell(cellID);
|
||||
auto cellLoc = cell.getlocation();
|
||||
glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth);
|
||||
|
||||
bool doDraw = true;
|
||||
if (cell.isBrickEmpty() || !cell.hasBrick()) {
|
||||
if (!_showEmptyCells) {
|
||||
doDraw = false;
|
||||
}
|
||||
cellLocation.w *= -1;
|
||||
}
|
||||
if (doDraw) {
|
||||
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
|
||||
batch.draw(gpu::LINES, 24, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Draw the LOD Reticle
|
||||
{
|
||||
float angle = glm::degrees(args->_viewFrustum->getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
|
||||
Transform crosshairModel;
|
||||
crosshairModel.setTranslation(glm::vec3(0.0, 0.0, -1000.0));
|
||||
crosshairModel.setScale(1000.0 * tan(glm::radians(angle))); // Scaling at the actual tan of the lod angle => Multiplied by TWO
|
||||
batch.setViewTransform(Transform());
|
||||
batch.setModelTransform(crosshairModel);
|
||||
batch.setPipeline(getDrawLODReticlePipeline());
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const gpu::PipelinePointer DrawItemSelection::getDrawItemBoundPipeline() {
|
||||
if (!_drawItemBoundPipeline) {
|
||||
auto vs = gpu::Shader::createVertex(std::string(drawItemBounds_vert));
|
||||
auto ps = gpu::Shader::createPixel(std::string(drawItemBounds_frag));
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
|
||||
_drawItemBoundPosLoc = program->getUniforms().findLocation("inBoundPos");
|
||||
_drawItemBoundDimLoc = program->getUniforms().findLocation("inBoundDim");
|
||||
|
||||
_drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation");
|
||||
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
|
||||
state->setDepthTest(true, false, gpu::LESS_EQUAL);
|
||||
|
||||
// Blend on transparent
|
||||
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
|
||||
|
||||
// Good to go add the brand new pipeline
|
||||
_drawItemBoundPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
return _drawItemBoundPipeline;
|
||||
}
|
||||
|
||||
void DrawItemSelection::configure(const Config& config) {
|
||||
_showInsideItems = config.showInsideItems;
|
||||
_showInsideSubcellItems = config.showInsideSubcellItems;
|
||||
_showPartialItems = config.showPartialItems;
|
||||
_showPartialSubcellItems = config.showPartialSubcellItems;
|
||||
}
|
||||
|
||||
|
||||
void DrawItemSelection::run(const SceneContextPointer& sceneContext,
|
||||
const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
RenderArgs* args = renderContext->args;
|
||||
auto& scene = sceneContext->_scene;
|
||||
|
||||
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
|
||||
glm::mat4 projMat;
|
||||
Transform viewMat;
|
||||
args->_viewFrustum->evalProjectionMatrix(projMat);
|
||||
args->_viewFrustum->evalViewTransform(viewMat);
|
||||
batch.setViewportTransform(args->_viewport);
|
||||
|
||||
batch.setProjectionTransform(projMat);
|
||||
batch.setViewTransform(viewMat);
|
||||
batch.setModelTransform(Transform());
|
||||
|
||||
// bind the one gpu::Pipeline we need
|
||||
batch.setPipeline(getDrawItemBoundPipeline());
|
||||
|
||||
if (_showInsideItems) {
|
||||
for (const auto& itemID : inSelection.insideItems) {
|
||||
auto& item = scene->getItem(itemID);
|
||||
auto itemBound = item.getBound();
|
||||
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
|
||||
glm::ivec4 cellLocation(0, 0, 0, itemCell.depth);
|
||||
|
||||
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
|
||||
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
|
||||
batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
|
||||
|
||||
batch.draw(gpu::LINES, 24, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (_showInsideSubcellItems) {
|
||||
for (const auto& itemID : inSelection.insideSubcellItems) {
|
||||
auto& item = scene->getItem(itemID);
|
||||
auto itemBound = item.getBound();
|
||||
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
|
||||
glm::ivec4 cellLocation(0, 0, 1, itemCell.depth);
|
||||
|
||||
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
|
||||
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
|
||||
batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
|
||||
|
||||
batch.draw(gpu::LINES, 24, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (_showPartialItems) {
|
||||
for (const auto& itemID : inSelection.partialItems) {
|
||||
auto& item = scene->getItem(itemID);
|
||||
auto itemBound = item.getBound();
|
||||
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
|
||||
glm::ivec4 cellLocation(0, 0, 0, itemCell.depth);
|
||||
|
||||
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
|
||||
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
|
||||
batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
|
||||
|
||||
batch.draw(gpu::LINES, 24, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (_showPartialSubcellItems) {
|
||||
for (const auto& itemID : inSelection.partialSubcellItems) {
|
||||
auto& item = scene->getItem(itemID);
|
||||
auto itemBound = item.getBound();
|
||||
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
|
||||
glm::ivec4 cellLocation(0, 0, 1, itemCell.depth);
|
||||
batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
|
||||
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
|
||||
batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
|
||||
|
||||
batch.draw(gpu::LINES, 24, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
130
libraries/render/src/render/DrawSceneOctree.h
Normal file
130
libraries/render/src/render/DrawSceneOctree.h
Normal file
|
@ -0,0 +1,130 @@
|
|||
//
|
||||
// DrawSceneOctree.h
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Sam Gateau on 1/25/16.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_render_DrawSceneOctree_h
|
||||
#define hifi_render_DrawSceneOctree_h
|
||||
|
||||
#include "DrawTask.h"
|
||||
#include "gpu/Batch.h"
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
namespace render {
|
||||
class DrawSceneOctreeConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty())
|
||||
Q_PROPERTY(bool showVisibleCells MEMBER showVisibleCells WRITE setShowVisibleCells)
|
||||
Q_PROPERTY(bool showEmptyCells MEMBER showEmptyCells WRITE setShowEmptyCells)
|
||||
Q_PROPERTY(int numAllocatedCells READ getNumAllocatedCells)
|
||||
Q_PROPERTY(int numFreeCells READ getNumFreeCells)
|
||||
|
||||
public:
|
||||
|
||||
DrawSceneOctreeConfig() : Job::Config(false) {}
|
||||
|
||||
bool showVisibleCells{ true };
|
||||
bool showEmptyCells{ false };
|
||||
|
||||
int numAllocatedCells{ 0 };
|
||||
int numFreeCells{ 0 };
|
||||
|
||||
int getNumAllocatedCells() const { return numAllocatedCells; }
|
||||
int getNumFreeCells() const { return numFreeCells; }
|
||||
|
||||
public slots:
|
||||
void setShowVisibleCells(bool show) { showVisibleCells = show; emit dirty(); }
|
||||
void setShowEmptyCells(bool show) { showEmptyCells = show; emit dirty(); }
|
||||
|
||||
signals:
|
||||
void dirty();
|
||||
};
|
||||
|
||||
class DrawSceneOctree {
|
||||
|
||||
int _drawCellLocationLoc;
|
||||
gpu::PipelinePointer _drawCellBoundsPipeline;
|
||||
gpu::BufferPointer _cells;
|
||||
|
||||
gpu::PipelinePointer _drawLODReticlePipeline;
|
||||
|
||||
int _drawItemBoundPosLoc = -1;
|
||||
int _drawItemBoundDimLoc = -1;
|
||||
gpu::PipelinePointer _drawItemBoundPipeline;
|
||||
|
||||
bool _showVisibleCells; // initialized by Config
|
||||
bool _showEmptyCells; // initialized by Config
|
||||
|
||||
public:
|
||||
using Config = DrawSceneOctreeConfig;
|
||||
using JobModel = Job::ModelI<DrawSceneOctree, ItemSpatialTree::ItemSelection, Config>;
|
||||
|
||||
DrawSceneOctree() {}
|
||||
|
||||
void configure(const Config& config);
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& selection);
|
||||
|
||||
const gpu::PipelinePointer getDrawCellBoundsPipeline();
|
||||
const gpu::PipelinePointer getDrawLODReticlePipeline();
|
||||
const gpu::PipelinePointer getDrawItemBoundPipeline();
|
||||
};
|
||||
|
||||
|
||||
class DrawItemSelectionConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty())
|
||||
Q_PROPERTY(bool showInsideItems MEMBER showInsideItems WRITE setShowInsideItems)
|
||||
Q_PROPERTY(bool showInsideSubcellItems MEMBER showInsideSubcellItems WRITE setShowInsideSubcellItems)
|
||||
Q_PROPERTY(bool showPartialItems MEMBER showPartialItems WRITE setShowPartialItems)
|
||||
Q_PROPERTY(bool showPartialSubcellItems MEMBER showPartialSubcellItems WRITE setShowPartialSubcellItems)
|
||||
public:
|
||||
|
||||
DrawItemSelectionConfig() : Job::Config(false) {}
|
||||
|
||||
bool showInsideItems{ true };
|
||||
bool showInsideSubcellItems{ true };
|
||||
bool showPartialItems{ true };
|
||||
bool showPartialSubcellItems{ true };
|
||||
|
||||
public slots:
|
||||
void setShowInsideItems(bool show) { showInsideItems = show; emit dirty(); }
|
||||
void setShowInsideSubcellItems(bool show) { showInsideSubcellItems = show; emit dirty(); }
|
||||
void setShowPartialItems(bool show) { showPartialItems = show; emit dirty(); }
|
||||
void setShowPartialSubcellItems(bool show) { showPartialSubcellItems = show; emit dirty(); }
|
||||
|
||||
signals:
|
||||
void dirty();
|
||||
};
|
||||
|
||||
class DrawItemSelection {
|
||||
|
||||
int _drawItemBoundPosLoc = -1;
|
||||
int _drawItemBoundDimLoc = -1;
|
||||
int _drawCellLocationLoc = -1;
|
||||
gpu::PipelinePointer _drawItemBoundPipeline;
|
||||
|
||||
bool _showInsideItems; // initialized by Config
|
||||
bool _showInsideSubcellItems; // initialized by Config
|
||||
bool _showPartialItems; // initialized by Config
|
||||
bool _showPartialSubcellItems; // initialized by Config
|
||||
|
||||
public:
|
||||
using Config = DrawItemSelectionConfig;
|
||||
using JobModel = Job::ModelI<DrawItemSelection, ItemSpatialTree::ItemSelection, Config>;
|
||||
|
||||
DrawItemSelection() {}
|
||||
|
||||
void configure(const Config& config);
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& selection);
|
||||
|
||||
const gpu::PipelinePointer getDrawItemBoundPipeline();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // hifi_render_DrawStatus_h
|
|
@ -43,6 +43,7 @@ const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() {
|
|||
|
||||
_drawItemBoundPosLoc = program->getUniforms().findLocation("inBoundPos");
|
||||
_drawItemBoundDimLoc = program->getUniforms().findLocation("inBoundDim");
|
||||
_drawItemCellLocLoc = program->getUniforms().findLocation("inCellLocation");
|
||||
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
|
||||
|
@ -104,7 +105,7 @@ void DrawStatus::configure(const Config& config) {
|
|||
|
||||
void DrawStatus::run(const SceneContextPointer& sceneContext,
|
||||
const RenderContextPointer& renderContext,
|
||||
const ItemIDsBounds& inItems) {
|
||||
const ItemBounds& inItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
@ -121,20 +122,30 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
|
|||
if (!_itemStatus) {
|
||||
_itemStatus = std::make_shared<gpu::Buffer>();;
|
||||
}
|
||||
if (!_itemCells) {
|
||||
_itemCells = std::make_shared<gpu::Buffer>();;
|
||||
}
|
||||
|
||||
_itemBounds->resize((inItems.size() * sizeof(AABox)));
|
||||
_itemStatus->resize((inItems.size() * NUM_STATUS_VEC4_PER_ITEM * sizeof(glm::vec4)));
|
||||
_itemCells->resize((inItems.size() * sizeof(Octree::Location)));
|
||||
|
||||
AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
|
||||
glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
|
||||
Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
|
||||
for (auto& item : inItems) {
|
||||
if (!item.bounds.isInvalid()) {
|
||||
if (!item.bounds.isNull()) {
|
||||
(*itemAABox) = item.bounds;
|
||||
if (!item.bound.isInvalid()) {
|
||||
if (!item.bound.isNull()) {
|
||||
(*itemAABox) = item.bound;
|
||||
} else {
|
||||
(*itemAABox).setBox(item.bounds.getCorner(), 0.1f);
|
||||
(*itemAABox).setBox(item.bound.getCorner(), 0.1f);
|
||||
}
|
||||
|
||||
|
||||
auto& itemScene = scene->getItem(item.id);
|
||||
|
||||
(*itemCell) = scene->getSpatialTree().getCellLocation(itemScene.getCell());
|
||||
|
||||
auto itemStatusPointer = itemScene.getStatus();
|
||||
if (itemStatusPointer) {
|
||||
// Query the current status values, this is where the statusGetter lambda get called
|
||||
|
@ -159,6 +170,7 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
|
|||
|
||||
nbItems++;
|
||||
itemAABox++;
|
||||
itemCell++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -184,6 +196,7 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
|
|||
|
||||
AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
|
||||
glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
|
||||
Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
|
||||
|
||||
const unsigned int VEC3_ADRESS_OFFSET = 3;
|
||||
|
||||
|
@ -191,8 +204,15 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
|
|||
for (int i = 0; i < nbItems; i++) {
|
||||
batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*) (itemAABox + i));
|
||||
batch._glUniform3fv(_drawItemBoundDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET);
|
||||
|
||||
|
||||
glm::ivec4 cellLocation(itemCell->pos.x, itemCell->pos.y, itemCell->pos.z, itemCell->depth);
|
||||
|
||||
batch._glUniform4iv(_drawItemCellLocLoc, 1, ((const int*)(&cellLocation)));
|
||||
|
||||
|
||||
batch.draw(gpu::LINES, 24, 0);
|
||||
itemCell++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,11 +21,11 @@ namespace render {
|
|||
Q_PROPERTY(bool showDisplay MEMBER showDisplay WRITE setShowDisplay)
|
||||
Q_PROPERTY(bool showNetwork MEMBER showNetwork WRITE setShowNetwork)
|
||||
public:
|
||||
DrawStatusConfig() : Job::Config(false) {}
|
||||
DrawStatusConfig() : Job::Config(false) {} // FIXME FOR debug
|
||||
|
||||
void dirtyHelper();
|
||||
|
||||
bool showDisplay{ false };
|
||||
bool showDisplay{ true }; // FIXME FOR debug
|
||||
bool showNetwork{ false };
|
||||
|
||||
public slots:
|
||||
|
@ -39,13 +39,13 @@ namespace render {
|
|||
class DrawStatus {
|
||||
public:
|
||||
using Config = DrawStatusConfig;
|
||||
using JobModel = Job::ModelI<DrawStatus, ItemIDsBounds, Config>;
|
||||
using JobModel = Job::ModelI<DrawStatus, ItemBounds, Config>;
|
||||
|
||||
DrawStatus() {}
|
||||
DrawStatus(const gpu::TexturePointer statusIconMap) { setStatusIconMap(statusIconMap); }
|
||||
|
||||
void configure(const Config& config);
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems);
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems);
|
||||
|
||||
const gpu::PipelinePointer getDrawItemBoundsPipeline();
|
||||
const gpu::PipelinePointer getDrawItemStatusPipeline();
|
||||
|
@ -59,6 +59,7 @@ namespace render {
|
|||
|
||||
int _drawItemBoundPosLoc = -1;
|
||||
int _drawItemBoundDimLoc = -1;
|
||||
int _drawItemCellLocLoc = -1;
|
||||
int _drawItemStatusPosLoc = -1;
|
||||
int _drawItemStatusDimLoc = -1;
|
||||
int _drawItemStatusValue0Loc = -1;
|
||||
|
@ -68,6 +69,7 @@ namespace render {
|
|||
gpu::PipelinePointer _drawItemBoundsPipeline;
|
||||
gpu::PipelinePointer _drawItemStatusPipeline;
|
||||
gpu::BufferPointer _itemBounds;
|
||||
gpu::BufferPointer _itemCells;
|
||||
gpu::BufferPointer _itemStatus;
|
||||
gpu::TexturePointer _statusIconMap;
|
||||
};
|
||||
|
|
|
@ -20,112 +20,7 @@
|
|||
|
||||
using namespace render;
|
||||
|
||||
void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
|
||||
const ItemIDsBounds& inItems, ItemIDsBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
RenderArgs* args = renderContext->args;
|
||||
ViewFrustum* frustum = args->_viewFrustum;
|
||||
|
||||
details._considered += inItems.size();
|
||||
|
||||
// Culling / LOD
|
||||
for (auto item : inItems) {
|
||||
if (item.bounds.isNull()) {
|
||||
outItems.emplace_back(item); // One more Item to render
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: some entity types (like lights) might want to be rendered even
|
||||
// when they are outside of the view frustum...
|
||||
bool outOfView;
|
||||
{
|
||||
PerformanceTimer perfTimer("boxInFrustum");
|
||||
outOfView = frustum->boxInFrustum(item.bounds) == ViewFrustum::OUTSIDE;
|
||||
}
|
||||
if (!outOfView) {
|
||||
bool bigEnoughToRender;
|
||||
{
|
||||
PerformanceTimer perfTimer("shouldRender");
|
||||
bigEnoughToRender = cullFunctor(args, item.bounds);
|
||||
}
|
||||
if (bigEnoughToRender) {
|
||||
outItems.emplace_back(item); // One more Item to render
|
||||
} else {
|
||||
details._tooSmall++;
|
||||
}
|
||||
} else {
|
||||
details._outOfView++;
|
||||
}
|
||||
}
|
||||
details._rendered += outItems.size();
|
||||
}
|
||||
|
||||
struct ItemBound {
|
||||
float _centerDepth = 0.0f;
|
||||
float _nearDepth = 0.0f;
|
||||
float _farDepth = 0.0f;
|
||||
ItemID _id = 0;
|
||||
AABox _bounds;
|
||||
|
||||
ItemBound() {}
|
||||
ItemBound(float centerDepth, float nearDepth, float farDepth, ItemID id, const AABox& bounds) : _centerDepth(centerDepth), _nearDepth(nearDepth), _farDepth(farDepth), _id(id), _bounds(bounds) {}
|
||||
};
|
||||
|
||||
struct FrontToBackSort {
|
||||
bool operator() (const ItemBound& left, const ItemBound& right) {
|
||||
return (left._centerDepth < right._centerDepth);
|
||||
}
|
||||
};
|
||||
|
||||
struct BackToFrontSort {
|
||||
bool operator() (const ItemBound& left, const ItemBound& right) {
|
||||
return (left._centerDepth > right._centerDepth);
|
||||
}
|
||||
};
|
||||
|
||||
void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemIDsBounds& inItems, ItemIDsBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
||||
// Allocate and simply copy
|
||||
outItems.clear();
|
||||
outItems.reserve(inItems.size());
|
||||
|
||||
|
||||
// Make a local dataset of the center distance and closest point distance
|
||||
std::vector<ItemBound> itemBounds;
|
||||
itemBounds.reserve(outItems.size());
|
||||
|
||||
for (auto itemDetails : inItems) {
|
||||
auto item = scene->getItem(itemDetails.id);
|
||||
auto bound = itemDetails.bounds; // item.getBound();
|
||||
float distance = args->_viewFrustum->distanceToCamera(bound.calcCenter());
|
||||
|
||||
itemBounds.emplace_back(ItemBound(distance, distance, distance, itemDetails.id, bound));
|
||||
}
|
||||
|
||||
// sort against Z
|
||||
if (frontToBack) {
|
||||
FrontToBackSort frontToBackSort;
|
||||
std::sort (itemBounds.begin(), itemBounds.end(), frontToBackSort);
|
||||
} else {
|
||||
BackToFrontSort backToFrontSort;
|
||||
std::sort (itemBounds.begin(), itemBounds.end(), backToFrontSort);
|
||||
}
|
||||
|
||||
// FInally once sorted result to a list of itemID
|
||||
for (auto& itemBound : itemBounds) {
|
||||
outItems.emplace_back(ItemIDAndBounds(itemBound._id, itemBound._bounds));
|
||||
}
|
||||
}
|
||||
|
||||
void render::renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems) {
|
||||
void render::renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) {
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
@ -152,7 +47,7 @@ void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, cons
|
|||
}
|
||||
|
||||
void render::renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
|
||||
const ShapePlumberPointer& shapeContext, const ItemIDsBounds& inItems, int maxDrawnItems) {
|
||||
const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems) {
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
@ -166,58 +61,20 @@ void render::renderShapes(const SceneContextPointer& sceneContext, const RenderC
|
|||
}
|
||||
}
|
||||
|
||||
void FetchItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemIDsBounds& outItems) {
|
||||
auto& scene = sceneContext->_scene;
|
||||
|
||||
outItems.clear();
|
||||
|
||||
const auto& bucket = scene->getMasterBucket();
|
||||
const auto& items = bucket.find(_filter);
|
||||
if (items != bucket.end()) {
|
||||
outItems.reserve(items->second.size());
|
||||
for (auto& id : items->second) {
|
||||
auto& item = scene->getItem(id);
|
||||
outItems.emplace_back(ItemIDAndBounds(id, item.getBound()));
|
||||
}
|
||||
}
|
||||
|
||||
std::static_pointer_cast<Config>(renderContext->jobConfig)->numItems = (int)outItems.size();
|
||||
}
|
||||
|
||||
void DepthSortItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ItemIDsBounds& outItems) {
|
||||
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems);
|
||||
}
|
||||
|
||||
void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
|
||||
void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inLights) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
// render lights
|
||||
auto& scene = sceneContext->_scene;
|
||||
auto& items = scene->getMasterBucket().at(ItemFilter::Builder::light());
|
||||
|
||||
ItemIDsBounds inItems;
|
||||
inItems.reserve(items.size());
|
||||
for (auto id : items) {
|
||||
auto item = scene->getItem(id);
|
||||
inItems.emplace_back(ItemIDAndBounds(id, item.getBound()));
|
||||
}
|
||||
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
auto& details = args->_details.edit(RenderDetails::OTHER_ITEM);
|
||||
ItemIDsBounds culledItems;
|
||||
culledItems.reserve(inItems.size());
|
||||
cullItems(renderContext, _cullFunctor, details, inItems, culledItems);
|
||||
|
||||
// render lights
|
||||
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
|
||||
args->_batch = &batch;
|
||||
renderItems(sceneContext, renderContext, culledItems);
|
||||
renderItems(sceneContext, renderContext, inLights);
|
||||
args->_batch = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void PipelineSortShapes::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ShapesIDsBounds& outShapes) {
|
||||
void PipelineSortShapes::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ShapesIDsBounds& outShapes) {
|
||||
auto& scene = sceneContext->_scene;
|
||||
outShapes.clear();
|
||||
|
||||
|
@ -225,7 +82,7 @@ void PipelineSortShapes::run(const SceneContextPointer& sceneContext, const Rend
|
|||
auto key = scene->getItem(item.id).getShapeKey();
|
||||
auto outItems = outShapes.find(key);
|
||||
if (outItems == outShapes.end()) {
|
||||
outItems = outShapes.insert(std::make_pair(key, ItemIDsBounds{})).first;
|
||||
outItems = outShapes.insert(std::make_pair(key, ItemBounds{})).first;
|
||||
outItems->second.reserve(inItems.size());
|
||||
}
|
||||
|
||||
|
@ -245,7 +102,7 @@ void DepthSortShapes::run(const SceneContextPointer& sceneContext, const RenderC
|
|||
auto& inItems = pipeline.second;
|
||||
auto outItems = outShapes.find(pipeline.first);
|
||||
if (outItems == outShapes.end()) {
|
||||
outItems = outShapes.insert(std::make_pair(pipeline.first, ItemIDsBounds{})).first;
|
||||
outItems = outShapes.insert(std::make_pair(pipeline.first, ItemBounds{})).first;
|
||||
}
|
||||
|
||||
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems->second);
|
||||
|
|
|
@ -13,94 +13,38 @@
|
|||
#define hifi_render_DrawTask_h
|
||||
|
||||
#include "Engine.h"
|
||||
#include "CullTask.h"
|
||||
#include "ShapePipeline.h"
|
||||
#include "gpu/Batch.h"
|
||||
|
||||
|
||||
namespace render {
|
||||
|
||||
using CullFunctor = std::function<bool(const RenderArgs*, const AABox&)>;
|
||||
|
||||
void cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
|
||||
const ItemIDsBounds& inItems, ItemIDsBounds& outItems);
|
||||
void depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemIDsBounds& inItems, ItemIDsBounds& outItems);
|
||||
void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems);
|
||||
void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemIDsBounds& inItems, int maxDrawnItems = -1);
|
||||
|
||||
class FetchItemsConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int numItems READ getNumItems)
|
||||
public:
|
||||
int getNumItems() { return numItems; }
|
||||
|
||||
int numItems{ 0 };
|
||||
};
|
||||
|
||||
class FetchItems {
|
||||
public:
|
||||
using Config = FetchItemsConfig;
|
||||
using JobModel = Job::ModelO<FetchItems, ItemIDsBounds, Config>;
|
||||
|
||||
FetchItems() {}
|
||||
FetchItems(const ItemFilter& filter) : _filter(filter) {}
|
||||
|
||||
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
|
||||
|
||||
void configure(const Config& config) {}
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemIDsBounds& outItems);
|
||||
};
|
||||
|
||||
template<RenderDetails::Type T>
|
||||
class CullItems {
|
||||
public:
|
||||
CullItems(CullFunctor cullFunctor) : _cullFunctor{ cullFunctor } {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ItemIDsBounds& outItems) {
|
||||
const auto& args = renderContext->args;
|
||||
auto& details = args->_details.edit(T);
|
||||
outItems.clear();
|
||||
outItems.reserve(inItems.size());
|
||||
render::cullItems(renderContext, _cullFunctor, details, inItems, outItems);
|
||||
}
|
||||
|
||||
using JobModel = Job::ModelIO<CullItems<T>, ItemIDsBounds, ItemIDsBounds>;
|
||||
|
||||
protected:
|
||||
CullFunctor _cullFunctor;
|
||||
};
|
||||
|
||||
class DepthSortItems {
|
||||
public:
|
||||
bool _frontToBack;
|
||||
DepthSortItems(bool frontToBack = true) : _frontToBack(frontToBack) {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ItemIDsBounds& outItems);
|
||||
using JobModel = Job::ModelIO<DepthSortItems, ItemIDsBounds, ItemIDsBounds>;
|
||||
};
|
||||
void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems);
|
||||
void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
|
||||
|
||||
class DrawLight {
|
||||
public:
|
||||
DrawLight(CullFunctor cullFunctor) : _cullFunctor{ cullFunctor } {}
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext);
|
||||
using JobModel = Job::Model<DrawLight>;
|
||||
using JobModel = Job::ModelI<DrawLight, ItemBounds>;
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inLights);
|
||||
protected:
|
||||
CullFunctor _cullFunctor;
|
||||
};
|
||||
|
||||
class PipelineSortShapes {
|
||||
public:
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems, ShapesIDsBounds& outShapes);
|
||||
using JobModel = Job::ModelIO<PipelineSortShapes, ItemIDsBounds, ShapesIDsBounds>;
|
||||
using JobModel = Job::ModelIO<PipelineSortShapes, ItemBounds, ShapesIDsBounds>;
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ShapesIDsBounds& outShapes);
|
||||
};
|
||||
|
||||
class DepthSortShapes {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<DepthSortShapes, ShapesIDsBounds, ShapesIDsBounds>;
|
||||
|
||||
bool _frontToBack;
|
||||
DepthSortShapes(bool frontToBack = true) : _frontToBack(frontToBack) {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapesIDsBounds& inShapes, ShapesIDsBounds& outShapes);
|
||||
using JobModel = Job::ModelIO<DepthSortShapes, ShapesIDsBounds, ShapesIDsBounds>;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
73
libraries/render/src/render/Item.cpp
Normal file
73
libraries/render/src/render/Item.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// Item.cpp
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Sam Gateau on 1/26/16.
|
||||
// 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 "Item.h"
|
||||
|
||||
#include <numeric>
|
||||
#include "gpu/Batch.h"
|
||||
|
||||
using namespace render;
|
||||
|
||||
const Item::Status::Value Item::Status::Value::INVALID = Item::Status::Value();
|
||||
|
||||
const float Item::Status::Value::RED = 0.0f;
|
||||
const float Item::Status::Value::YELLOW = 60.0f;
|
||||
const float Item::Status::Value::GREEN = 120.0f;
|
||||
const float Item::Status::Value::CYAN = 180.0f;
|
||||
const float Item::Status::Value::BLUE = 240.0f;
|
||||
const float Item::Status::Value::MAGENTA = 300.0f;
|
||||
|
||||
void Item::Status::Value::setScale(float scale) {
|
||||
_scale = (std::numeric_limits<unsigned short>::max() -1) * 0.5f * (1.0f + std::max(std::min(scale, 1.0f), 0.0f));
|
||||
}
|
||||
|
||||
void Item::Status::Value::setColor(float hue) {
|
||||
// Convert the HUe from range [0, 360] to signed normalized value
|
||||
const float HUE_MAX = 360.0f;
|
||||
_color = (std::numeric_limits<unsigned char>::max()) * (std::max(std::min(hue, HUE_MAX), 0.0f) / HUE_MAX);
|
||||
}
|
||||
void Item::Status::Value::setIcon(unsigned char icon) {
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
Item::Status::Values Item::Status::getCurrentValues() const {
|
||||
Values currentValues(_values.size());
|
||||
auto currentValue = currentValues.begin();
|
||||
for (auto& getter : _values) {
|
||||
(*currentValue) = getter();
|
||||
currentValue++;
|
||||
}
|
||||
return currentValues;
|
||||
}
|
||||
|
||||
void Item::PayloadInterface::addStatusGetter(const Status::Getter& getter) {
|
||||
if (!_status) {
|
||||
_status = std::make_shared<Status>();
|
||||
}
|
||||
_status->addGetter(getter);
|
||||
}
|
||||
|
||||
void Item::PayloadInterface::addStatusGetters(const Status::Getters& getters) {
|
||||
if (!_status) {
|
||||
_status = std::make_shared<Status>();
|
||||
}
|
||||
for (auto& g : getters) {
|
||||
_status->addGetter(g);
|
||||
}
|
||||
}
|
||||
|
||||
void Item::resetPayload(const PayloadPointer& payload) {
|
||||
if (!payload) {
|
||||
kill();
|
||||
} else {
|
||||
_payload = payload;
|
||||
_key = _payload->getKey();
|
||||
}
|
||||
}
|
464
libraries/render/src/render/Item.h
Normal file
464
libraries/render/src/render/Item.h
Normal file
|
@ -0,0 +1,464 @@
|
|||
//
|
||||
// Item.h
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Sam Gateau on 1/26/16.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_render_Item_h
|
||||
#define hifi_render_Item_h
|
||||
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <RenderArgs.h>
|
||||
|
||||
#include "model/Material.h"
|
||||
#include "ShapePipeline.h"
|
||||
|
||||
|
||||
namespace render {
|
||||
|
||||
class Context;
|
||||
|
||||
// Key is the KEY to filter Items and create specialized lists
|
||||
class ItemKey {
|
||||
public:
|
||||
enum FlagBit {
|
||||
TYPE_SHAPE = 0, // Item is a Shape
|
||||
TYPE_LIGHT, // Item is a Light
|
||||
TRANSLUCENT, // Transparent and not opaque, for some odd reason TRANSPARENCY doesn't work...
|
||||
VIEW_SPACE, // Transformed in view space, and not in world space
|
||||
DYNAMIC, // Dynamic and bound will change unlike static item
|
||||
DEFORMED, // Deformed within bound, not solid
|
||||
INVISIBLE, // Visible or not? could be just here to cast shadow
|
||||
SHADOW_CASTER, // Item cast shadows
|
||||
PICKABLE, // Item can be picked/selected
|
||||
LAYERED, // Item belongs to one of the layers different from the default layer
|
||||
|
||||
SMALLER,
|
||||
|
||||
NUM_FLAGS, // Not a valid flag
|
||||
};
|
||||
typedef std::bitset<NUM_FLAGS> Flags;
|
||||
|
||||
// The key is the Flags
|
||||
Flags _flags;
|
||||
|
||||
ItemKey() : _flags(0) {}
|
||||
ItemKey(const Flags& flags) : _flags(flags) {}
|
||||
|
||||
bool operator== (const ItemKey& rhs) const { return _flags == rhs._flags; }
|
||||
bool operator!= (const ItemKey& rhs) const { return _flags != rhs._flags; }
|
||||
|
||||
class Builder {
|
||||
friend class ItemKey;
|
||||
Flags _flags{ 0 };
|
||||
public:
|
||||
Builder() {}
|
||||
|
||||
ItemKey build() const { return ItemKey(_flags); }
|
||||
|
||||
Builder& withTypeShape() { _flags.set(TYPE_SHAPE); return (*this); }
|
||||
Builder& withTypeLight() { _flags.set(TYPE_LIGHT); return (*this); }
|
||||
Builder& withTransparent() { _flags.set(TRANSLUCENT); return (*this); }
|
||||
Builder& withViewSpace() { _flags.set(VIEW_SPACE); return (*this); }
|
||||
Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); }
|
||||
Builder& withDeformed() { _flags.set(DEFORMED); return (*this); }
|
||||
Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); }
|
||||
Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); }
|
||||
Builder& withPickable() { _flags.set(PICKABLE); return (*this); }
|
||||
Builder& withLayered() { _flags.set(LAYERED); return (*this); }
|
||||
|
||||
// Convenient standard keys that we will keep on using all over the place
|
||||
static Builder opaqueShape() { return Builder().withTypeShape(); }
|
||||
static Builder transparentShape() { return Builder().withTypeShape().withTransparent(); }
|
||||
static Builder light() { return Builder().withTypeLight(); }
|
||||
static Builder background() { return Builder().withViewSpace().withLayered(); }
|
||||
};
|
||||
ItemKey(const Builder& builder) : ItemKey(builder._flags) {}
|
||||
|
||||
bool isShape() const { return _flags[TYPE_SHAPE]; }
|
||||
bool isLight() const { return _flags[TYPE_LIGHT]; }
|
||||
|
||||
bool isOpaque() const { return !_flags[TRANSLUCENT]; }
|
||||
bool isTransparent() const { return _flags[TRANSLUCENT]; }
|
||||
|
||||
bool isWorldSpace() const { return !_flags[VIEW_SPACE]; }
|
||||
bool isViewSpace() const { return _flags[VIEW_SPACE]; }
|
||||
|
||||
bool isStatic() const { return !_flags[DYNAMIC]; }
|
||||
bool isDynamic() const { return _flags[DYNAMIC]; }
|
||||
|
||||
bool isRigid() const { return !_flags[DEFORMED]; }
|
||||
bool isDeformed() const { return _flags[DEFORMED]; }
|
||||
|
||||
bool isVisible() const { return !_flags[INVISIBLE]; }
|
||||
bool isInvisible() const { return _flags[INVISIBLE]; }
|
||||
|
||||
bool isShadowCaster() const { return _flags[SHADOW_CASTER]; }
|
||||
|
||||
bool isPickable() const { return _flags[PICKABLE]; }
|
||||
|
||||
bool isLayered() const { return _flags[LAYERED]; }
|
||||
|
||||
// Probably not public, flags used by the scene
|
||||
bool isSmall() const { return _flags[SMALLER]; }
|
||||
void setSmaller(bool smaller) { (smaller ? _flags.set(SMALLER) : _flags.reset(SMALLER)); }
|
||||
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const ItemKey& itemKey) {
|
||||
debug << "[ItemKey: isOpaque:" << itemKey.isOpaque()
|
||||
<< ", isStatic:" << itemKey.isStatic()
|
||||
<< ", isWorldSpace:" << itemKey.isWorldSpace()
|
||||
<< "]";
|
||||
return debug;
|
||||
}
|
||||
|
||||
class ItemFilter {
|
||||
public:
|
||||
ItemKey::Flags _value{ 0 };
|
||||
ItemKey::Flags _mask{ 0 };
|
||||
|
||||
|
||||
ItemFilter(const ItemKey::Flags& value = ItemKey::Flags(0), const ItemKey::Flags& mask = ItemKey::Flags(0)) : _value(value), _mask(mask) {}
|
||||
|
||||
class Builder {
|
||||
friend class ItemFilter;
|
||||
ItemKey::Flags _value{ 0 };
|
||||
ItemKey::Flags _mask{ 0 };
|
||||
public:
|
||||
Builder() {}
|
||||
|
||||
ItemFilter build() const { return ItemFilter(_value, _mask); }
|
||||
|
||||
Builder& withTypeShape() { _value.set(ItemKey::TYPE_SHAPE); _mask.set(ItemKey::TYPE_SHAPE); return (*this); }
|
||||
Builder& withTypeLight() { _value.set(ItemKey::TYPE_LIGHT); _mask.set(ItemKey::TYPE_LIGHT); return (*this); }
|
||||
|
||||
Builder& withOpaque() { _value.reset(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
|
||||
Builder& withTransparent() { _value.set(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
|
||||
|
||||
Builder& withWorldSpace() { _value.reset(ItemKey::VIEW_SPACE); _mask.set(ItemKey::VIEW_SPACE); return (*this); }
|
||||
Builder& withViewSpace() { _value.set(ItemKey::VIEW_SPACE); _mask.set(ItemKey::VIEW_SPACE); return (*this); }
|
||||
|
||||
Builder& withStatic() { _value.reset(ItemKey::DYNAMIC); _mask.set(ItemKey::DYNAMIC); return (*this); }
|
||||
Builder& withDynamic() { _value.set(ItemKey::DYNAMIC); _mask.set(ItemKey::DYNAMIC); return (*this); }
|
||||
|
||||
Builder& withRigid() { _value.reset(ItemKey::DEFORMED); _mask.set(ItemKey::DEFORMED); return (*this); }
|
||||
Builder& withDeformed() { _value.set(ItemKey::DEFORMED); _mask.set(ItemKey::DEFORMED); return (*this); }
|
||||
|
||||
Builder& withVisible() { _value.reset(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); }
|
||||
Builder& withInvisible() { _value.set(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); }
|
||||
|
||||
Builder& withNoShadowCaster() { _value.reset(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); }
|
||||
Builder& withShadowCaster() { _value.set(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); }
|
||||
|
||||
Builder& withPickable() { _value.set(ItemKey::PICKABLE); _mask.set(ItemKey::PICKABLE); return (*this); }
|
||||
|
||||
Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
|
||||
Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
|
||||
|
||||
// Convenient standard keys that we will keep on using all over the place
|
||||
static Builder visibleWorldItems() { return Builder().withVisible().withWorldSpace(); }
|
||||
static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); }
|
||||
static Builder transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace(); }
|
||||
static Builder light() { return Builder().withTypeLight(); }
|
||||
static Builder background() { return Builder().withViewSpace().withLayered(); }
|
||||
static Builder opaqueShapeLayered() { return Builder().withTypeShape().withOpaque().withWorldSpace().withLayered(); }
|
||||
static Builder transparentShapeLayered() { return Builder().withTypeShape().withTransparent().withWorldSpace().withLayered(); }
|
||||
};
|
||||
|
||||
ItemFilter(const Builder& builder) : ItemFilter(builder._value, builder._mask) {}
|
||||
|
||||
// Item Filter operator testing if a key pass the filter
|
||||
bool test(const ItemKey& key) const { return (key._flags & _mask) == (_value & _mask); }
|
||||
|
||||
class Less {
|
||||
public:
|
||||
bool operator() (const ItemFilter& left, const ItemFilter& right) const {
|
||||
if (left._value.to_ulong() == right._value.to_ulong()) {
|
||||
return left._mask.to_ulong() < right._mask.to_ulong();
|
||||
} else {
|
||||
return left._value.to_ulong() < right._value.to_ulong();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const ItemFilter& me) {
|
||||
debug << "[ItemFilter: opaqueShape:" << me.test(ItemKey::Builder::opaqueShape().build())
|
||||
<< "]";
|
||||
return debug;
|
||||
}
|
||||
|
||||
using ItemID = uint32_t;
|
||||
using ItemCell = int32_t;
|
||||
|
||||
class Item {
|
||||
public:
|
||||
typedef std::vector<Item> Vector;
|
||||
typedef ItemID ID;
|
||||
|
||||
static const ID INVALID_ITEM_ID = 0;
|
||||
static const ItemCell INVALID_CELL = -1;
|
||||
|
||||
// Bound is the AABBox fully containing this item
|
||||
typedef AABox Bound;
|
||||
|
||||
// Status records the life history and performances of this item while performing at rendering and updating.
|
||||
// This is Used for monitoring and dynamically adjust the quality
|
||||
class Status {
|
||||
public:
|
||||
|
||||
// Status::Value class is the data used to represent the transient information of a status as a square icon
|
||||
// The "icon" is a square displayed in the 3D scene over the render::Item AABB center.
|
||||
// It can be scaled in the range [0, 1] and the color hue in the range [0, 360] representing the color wheel hue
|
||||
class Value {
|
||||
unsigned short _scale = 0xFFFF;
|
||||
unsigned char _color = 0xFF;
|
||||
unsigned char _icon = 0xFF;
|
||||
public:
|
||||
const static Value INVALID; // Invalid value meanss the status won't show
|
||||
|
||||
Value() {}
|
||||
Value(float scale, float hue, unsigned char icon = 0xFF) { setScale(scale); setColor(hue); setIcon(icon); }
|
||||
|
||||
// It can be scaled in the range [0, 1]
|
||||
void setScale(float scale);
|
||||
// the color hue in the range [0, 360] representing the color wheel hue
|
||||
void setColor(float hue);
|
||||
// the icon to display in the range [0, 255], where 0 means no icon, just filled quad and anything else would
|
||||
// hopefully have an icon available to display (see DrawStatusJob)
|
||||
void setIcon(unsigned char icon);
|
||||
|
||||
// Standard color Hue
|
||||
static const float RED; // 0.0f;
|
||||
static const float YELLOW; // 60.0f;
|
||||
static const float GREEN; // 120.0f;
|
||||
static const float CYAN; // 180.0f;
|
||||
static const float BLUE; // 240.0f;
|
||||
static const float MAGENTA; // 300.0f;
|
||||
|
||||
// Retreive the Value data tightely packed as an int
|
||||
int getPackedData() const { return *((const int*) this); }
|
||||
};
|
||||
|
||||
typedef std::function<Value()> Getter;
|
||||
typedef std::vector<Getter> Getters;
|
||||
|
||||
Getters _values;
|
||||
|
||||
void addGetter(const Getter& getter) { _values.push_back(getter); }
|
||||
|
||||
size_t getNumValues() const { return _values.size(); }
|
||||
|
||||
using Values = std::vector <Value>;
|
||||
Values getCurrentValues() const;
|
||||
};
|
||||
typedef std::shared_ptr<Status> StatusPointer;
|
||||
|
||||
// Update Functor
|
||||
class UpdateFunctorInterface {
|
||||
public:
|
||||
virtual ~UpdateFunctorInterface() {}
|
||||
};
|
||||
typedef std::shared_ptr<UpdateFunctorInterface> UpdateFunctorPointer;
|
||||
|
||||
// Payload is whatever is in this Item and implement the Payload Interface
|
||||
class PayloadInterface {
|
||||
public:
|
||||
virtual const ItemKey getKey() const = 0;
|
||||
virtual const Bound getBound() const = 0;
|
||||
virtual int getLayer() const = 0;
|
||||
|
||||
virtual void render(RenderArgs* args) = 0;
|
||||
|
||||
virtual const ShapeKey getShapeKey() const = 0;
|
||||
|
||||
~PayloadInterface() {}
|
||||
|
||||
// Status interface is local to the base class
|
||||
const StatusPointer& getStatus() const { return _status; }
|
||||
void addStatusGetter(const Status::Getter& getter);
|
||||
void addStatusGetters(const Status::Getters& getters);
|
||||
|
||||
protected:
|
||||
StatusPointer _status;
|
||||
|
||||
friend class Item;
|
||||
virtual void update(const UpdateFunctorPointer& functor) = 0;
|
||||
};
|
||||
typedef std::shared_ptr<PayloadInterface> PayloadPointer;
|
||||
|
||||
Item() {}
|
||||
~Item() {}
|
||||
|
||||
// Main scene / item managment interface Reset/Update/Kill
|
||||
void resetPayload(const PayloadPointer& payload);
|
||||
void resetCell(ItemCell cell, bool _small) { _cell = cell; _key.setSmaller(_small); }
|
||||
void update(const UpdateFunctorPointer& updateFunctor) { _payload->update(updateFunctor); } // Communicate update to the payload
|
||||
void kill() { _payload.reset(); _key._flags.reset(); _cell = INVALID_CELL; } // Kill means forget the payload and key and cell
|
||||
|
||||
// Check heuristic key
|
||||
const ItemKey& getKey() const { return _key; }
|
||||
|
||||
// Check spatial cell
|
||||
const ItemCell& getCell() const { return _cell; }
|
||||
|
||||
// Payload Interface
|
||||
|
||||
// Get the bound of the item expressed in world space (or eye space depending on the key.isWorldSpace())
|
||||
const Bound getBound() const { return _payload->getBound(); }
|
||||
|
||||
// Get the layer where the item belongs. 0 by default meaning NOT LAYERED
|
||||
int getLayer() const { return _payload->getLayer(); }
|
||||
|
||||
// Render call for the item
|
||||
void render(RenderArgs* args) const { _payload->render(args); }
|
||||
|
||||
// Shape Type Interface
|
||||
const ShapeKey getShapeKey() const { return _payload->getShapeKey(); }
|
||||
|
||||
// Access the status
|
||||
const StatusPointer& getStatus() const { return _payload->getStatus(); }
|
||||
|
||||
protected:
|
||||
PayloadPointer _payload;
|
||||
ItemKey _key;
|
||||
ItemCell _cell{ INVALID_CELL };
|
||||
|
||||
friend class Scene;
|
||||
};
|
||||
|
||||
|
||||
typedef Item::UpdateFunctorInterface UpdateFunctorInterface;
|
||||
typedef Item::UpdateFunctorPointer UpdateFunctorPointer;
|
||||
typedef std::vector<UpdateFunctorPointer> UpdateFunctors;
|
||||
|
||||
template <class T> class UpdateFunctor : public Item::UpdateFunctorInterface {
|
||||
public:
|
||||
typedef std::function<void(T&)> Func;
|
||||
Func _func;
|
||||
|
||||
UpdateFunctor(Func func): _func(func) {}
|
||||
~UpdateFunctor() {}
|
||||
};
|
||||
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const Item& item) {
|
||||
debug << "[Item: _key:" << item.getKey() << ", bounds:" << item.getBound() << "]";
|
||||
return debug;
|
||||
}
|
||||
|
||||
// THe Payload class is the real Payload to be used
|
||||
// THis allow anything to be turned into a Payload as long as the required interface functions are available
|
||||
// When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff"
|
||||
// of the Payload interface
|
||||
template <class T> const ItemKey payloadGetKey(const std::shared_ptr<T>& payloadData) { return ItemKey(); }
|
||||
template <class T> const Item::Bound payloadGetBound(const std::shared_ptr<T>& payloadData) { return Item::Bound(); }
|
||||
template <class T> int payloadGetLayer(const std::shared_ptr<T>& payloadData) { return 0; }
|
||||
template <class T> void payloadRender(const std::shared_ptr<T>& payloadData, RenderArgs* args) { }
|
||||
|
||||
// Shape type interface
|
||||
// This allows shapes to characterize their pipeline via a ShapeKey, to be picked with a subclass of Shape.
|
||||
// When creating a new shape payload you need to create a specialized version, or the ShapeKey will be ownPipeline,
|
||||
// implying that the shape will setup its own pipeline without the use of the ShapeKey.
|
||||
template <class T> const ShapeKey shapeGetShapeKey(const std::shared_ptr<T>& payloadData) { return ShapeKey::Builder::ownPipeline(); }
|
||||
|
||||
template <class T> class Payload : public Item::PayloadInterface {
|
||||
public:
|
||||
typedef std::shared_ptr<T> DataPointer;
|
||||
typedef UpdateFunctor<T> Updater;
|
||||
|
||||
Payload(const DataPointer& data) : _data(data) {}
|
||||
|
||||
// Payload general interface
|
||||
virtual const ItemKey getKey() const { return payloadGetKey<T>(_data); }
|
||||
virtual const Item::Bound getBound() const { return payloadGetBound<T>(_data); }
|
||||
virtual int getLayer() const { return payloadGetLayer<T>(_data); }
|
||||
|
||||
|
||||
virtual void render(RenderArgs* args) { payloadRender<T>(_data, args); }
|
||||
|
||||
// Shape Type interface
|
||||
virtual const ShapeKey getShapeKey() const { return shapeGetShapeKey<T>(_data); }
|
||||
|
||||
protected:
|
||||
DataPointer _data;
|
||||
|
||||
// Update mechanics
|
||||
virtual void update(const UpdateFunctorPointer& functor) { std::static_pointer_cast<Updater>(functor)->_func((*_data)); }
|
||||
friend class Item;
|
||||
};
|
||||
|
||||
// Let's show how to make a simple FooPayload example:
|
||||
/*
|
||||
class Foo {
|
||||
public:
|
||||
mutable ItemKey _myownKey;
|
||||
void makeMywnKey() const {
|
||||
_myownKey = ItemKey::Builder().withTypeShape().build();
|
||||
}
|
||||
|
||||
const Item::Bound evaluateMyBound() {
|
||||
// Do stuff here to get your final Bound
|
||||
return Item::Bound();
|
||||
}
|
||||
};
|
||||
|
||||
typedef Payload<Foo> FooPayload;
|
||||
typedef std::shared_ptr<Foo> FooPointer;
|
||||
|
||||
// In a Source file, not a header, implement the Payload interface function specialized for Foo:
|
||||
template <> const ItemKey payloadGetKey(const FooPointer& foo) {
|
||||
// Foo's way of provinding its Key
|
||||
foo->makeMyKey();
|
||||
return foo->_myownKey;
|
||||
}
|
||||
template <> const Item::Bound payloadGetBound(const FooPointer& foo) {
|
||||
// evaluate Foo's own bound
|
||||
return foo->evaluateMyBound();
|
||||
}
|
||||
|
||||
// In this example, do not specialize the payloadRender call which means the compiler will use the default version which does nothing
|
||||
|
||||
*/
|
||||
// End of the example
|
||||
|
||||
typedef Item::PayloadPointer PayloadPointer;
|
||||
typedef std::vector< PayloadPointer > Payloads;
|
||||
|
||||
// A few typedefs for standard containers of ItemIDs
|
||||
using ItemIDs = std::vector<ItemID>;
|
||||
using ItemIDSet = std::set<ItemID>;
|
||||
|
||||
// Handy type to just pass the ID and the bound of an item
|
||||
class ItemBound {
|
||||
public:
|
||||
ItemBound(ItemID id) : id(id) { }
|
||||
ItemBound(ItemID id, const AABox& bound) : id(id), bound(bound) { }
|
||||
|
||||
ItemID id;
|
||||
AABox bound;
|
||||
};
|
||||
// many Item Bounds in a vector
|
||||
using ItemBounds = std::vector<ItemBound>;
|
||||
|
||||
// A map of items by ShapeKey to optimize rendering pipeline assignments
|
||||
using ShapesIDsBounds = std::unordered_map<ShapeKey, ItemBounds, ShapeKey::Hash, ShapeKey::KeyEqual>;
|
||||
|
||||
}
|
||||
|
||||
#endif // hifi_render_Item_h
|
|
@ -56,62 +56,6 @@ void ItemBucketMap::allocateStandardOpaqueTranparentBuckets() {
|
|||
(*this)[ItemFilter::Builder::transparentShape().withLayered()];
|
||||
}
|
||||
|
||||
const Item::Status::Value Item::Status::Value::INVALID = Item::Status::Value();
|
||||
|
||||
const float Item::Status::Value::RED = 0.0f;
|
||||
const float Item::Status::Value::YELLOW = 60.0f;
|
||||
const float Item::Status::Value::GREEN = 120.0f;
|
||||
const float Item::Status::Value::CYAN = 180.0f;
|
||||
const float Item::Status::Value::BLUE = 240.0f;
|
||||
const float Item::Status::Value::MAGENTA = 300.0f;
|
||||
|
||||
void Item::Status::Value::setScale(float scale) {
|
||||
_scale = (std::numeric_limits<unsigned short>::max() -1) * 0.5f * (1.0f + std::max(std::min(scale, 1.0f), 0.0f));
|
||||
}
|
||||
|
||||
void Item::Status::Value::setColor(float hue) {
|
||||
// Convert the HUe from range [0, 360] to signed normalized value
|
||||
const float HUE_MAX = 360.0f;
|
||||
_color = (std::numeric_limits<unsigned char>::max()) * (std::max(std::min(hue, HUE_MAX), 0.0f) / HUE_MAX);
|
||||
}
|
||||
void Item::Status::Value::setIcon(unsigned char icon) {
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
Item::Status::Values Item::Status::getCurrentValues() const {
|
||||
Values currentValues(_values.size());
|
||||
auto currentValue = currentValues.begin();
|
||||
for (auto& getter : _values) {
|
||||
(*currentValue) = getter();
|
||||
currentValue++;
|
||||
}
|
||||
return currentValues;
|
||||
}
|
||||
|
||||
void Item::PayloadInterface::addStatusGetter(const Status::Getter& getter) {
|
||||
if (!_status) {
|
||||
_status = std::make_shared<Status>();
|
||||
}
|
||||
_status->addGetter(getter);
|
||||
}
|
||||
|
||||
void Item::PayloadInterface::addStatusGetters(const Status::Getters& getters) {
|
||||
if (!_status) {
|
||||
_status = std::make_shared<Status>();
|
||||
}
|
||||
for (auto& g : getters) {
|
||||
_status->addGetter(g);
|
||||
}
|
||||
}
|
||||
|
||||
void Item::resetPayload(const PayloadPointer& payload) {
|
||||
if (!payload) {
|
||||
kill();
|
||||
} else {
|
||||
_payload = payload;
|
||||
_key = _payload->getKey();
|
||||
}
|
||||
}
|
||||
|
||||
void PendingChanges::resetItem(ItemID id, const PayloadPointer& payload) {
|
||||
_resetItems.push_back(id);
|
||||
|
@ -127,7 +71,6 @@ void PendingChanges::updateItem(ItemID id, const UpdateFunctorPointer& functor)
|
|||
_updateFunctors.push_back(functor);
|
||||
}
|
||||
|
||||
|
||||
void PendingChanges::merge(PendingChanges& changes) {
|
||||
_resetItems.insert(_resetItems.end(), changes._resetItems.begin(), changes._resetItems.end());
|
||||
_resetPayloads.insert(_resetPayloads.end(), changes._resetPayloads.begin(), changes._resetPayloads.end());
|
||||
|
@ -136,7 +79,9 @@ void PendingChanges::merge(PendingChanges& changes) {
|
|||
_updateFunctors.insert(_updateFunctors.end(), changes._updateFunctors.begin(), changes._updateFunctors.end());
|
||||
}
|
||||
|
||||
Scene::Scene() {
|
||||
Scene::Scene(glm::vec3 origin, float size) :
|
||||
_masterSpatialTree(origin, size)
|
||||
{
|
||||
_items.push_back(Item()); // add the itemID #0 to nothing
|
||||
_masterBucketMap.allocateStandardOpaqueTranparentBuckets();
|
||||
}
|
||||
|
@ -186,29 +131,68 @@ void Scene::processPendingChangesQueue() {
|
|||
}
|
||||
|
||||
void Scene::resetItems(const ItemIDs& ids, Payloads& payloads) {
|
||||
auto resetID = ids.begin();
|
||||
|
||||
auto resetPayload = payloads.begin();
|
||||
for (;resetID != ids.end(); resetID++, resetPayload++) {
|
||||
auto& item = _items[(*resetID)];
|
||||
for (auto resetID : ids) {
|
||||
// Access the true item
|
||||
auto& item = _items[resetID];
|
||||
auto oldKey = item.getKey();
|
||||
auto oldCell = item.getCell();
|
||||
|
||||
// Reset the item with a new payload
|
||||
item.resetPayload(*resetPayload);
|
||||
auto newKey = item.getKey();
|
||||
|
||||
_masterBucketMap.reset((*resetID), oldKey, item.getKey());
|
||||
|
||||
// Reset the item in the Bucket map
|
||||
_masterBucketMap.reset(resetID, oldKey, newKey);
|
||||
|
||||
// Reset the item in the spatial tree
|
||||
auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), resetID, newKey);
|
||||
item.resetCell(newCell, newKey.isSmall());
|
||||
|
||||
// next loop
|
||||
resetPayload++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Scene::removeItems(const ItemIDs& ids) {
|
||||
for (auto removedID :ids) {
|
||||
_masterBucketMap.erase(removedID, _items[removedID].getKey());
|
||||
_items[removedID].kill();
|
||||
// Access the true item
|
||||
auto& item = _items[removedID];
|
||||
auto oldCell = item.getCell();
|
||||
auto oldKey = item.getKey();
|
||||
|
||||
// Remove from Bucket map
|
||||
_masterBucketMap.erase(removedID, item.getKey());
|
||||
|
||||
// Remove from spatial tree
|
||||
_masterSpatialTree.removeItem(oldCell, oldKey, removedID);
|
||||
|
||||
// Kill it
|
||||
item.kill();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::updateItems(const ItemIDs& ids, UpdateFunctors& functors) {
|
||||
auto updateID = ids.begin();
|
||||
|
||||
auto updateFunctor = functors.begin();
|
||||
for (;updateID != ids.end(); updateID++, updateFunctor++) {
|
||||
_items[(*updateID)].update((*updateFunctor));
|
||||
for (auto updateID : ids) {
|
||||
// Access the true item
|
||||
auto& item = _items[updateID];
|
||||
auto oldCell = item.getCell();
|
||||
auto oldKey = item.getKey();
|
||||
|
||||
// Update it
|
||||
_items[updateID].update((*updateFunctor));
|
||||
|
||||
auto newKey = item.getKey();
|
||||
|
||||
// Update the citem in the spatial tree if needed
|
||||
auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), updateID, newKey);
|
||||
item.resetCell(newCell, newKey.isSmall());
|
||||
|
||||
// next loop
|
||||
updateFunctor++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,434 +12,12 @@
|
|||
#ifndef hifi_render_Scene_h
|
||||
#define hifi_render_Scene_h
|
||||
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <RenderArgs.h>
|
||||
|
||||
#include "model/Material.h"
|
||||
#include "ShapePipeline.h"
|
||||
#include "Item.h"
|
||||
#include "SpatialTree.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
class Context;
|
||||
|
||||
// Key is the KEY to filter Items and create specialized lists
|
||||
class ItemKey {
|
||||
public:
|
||||
enum FlagBit {
|
||||
TYPE_SHAPE = 0, // Item is a Shape
|
||||
TYPE_LIGHT, // Item is a Light
|
||||
TRANSLUCENT, // Transparent and not opaque, for some odd reason TRANSPARENCY doesn't work...
|
||||
VIEW_SPACE, // Transformed in view space, and not in world space
|
||||
DYNAMIC, // Dynamic and bound will change unlike static item
|
||||
DEFORMED, // Deformed within bound, not solid
|
||||
INVISIBLE, // Visible or not? could be just here to cast shadow
|
||||
SHADOW_CASTER, // Item cast shadows
|
||||
PICKABLE, // Item can be picked/selected
|
||||
LAYERED, // Item belongs to one of the layers different from the default layer
|
||||
|
||||
NUM_FLAGS, // Not a valid flag
|
||||
};
|
||||
typedef std::bitset<NUM_FLAGS> Flags;
|
||||
|
||||
// The key is the Flags
|
||||
Flags _flags;
|
||||
|
||||
ItemKey() : _flags(0) {}
|
||||
ItemKey(const Flags& flags) : _flags(flags) {}
|
||||
|
||||
class Builder {
|
||||
friend class ItemKey;
|
||||
Flags _flags{ 0 };
|
||||
public:
|
||||
Builder() {}
|
||||
|
||||
ItemKey build() const { return ItemKey(_flags); }
|
||||
|
||||
Builder& withTypeShape() { _flags.set(TYPE_SHAPE); return (*this); }
|
||||
Builder& withTypeLight() { _flags.set(TYPE_LIGHT); return (*this); }
|
||||
Builder& withTransparent() { _flags.set(TRANSLUCENT); return (*this); }
|
||||
Builder& withViewSpace() { _flags.set(VIEW_SPACE); return (*this); }
|
||||
Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); }
|
||||
Builder& withDeformed() { _flags.set(DEFORMED); return (*this); }
|
||||
Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); }
|
||||
Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); }
|
||||
Builder& withPickable() { _flags.set(PICKABLE); return (*this); }
|
||||
Builder& withLayered() { _flags.set(LAYERED); return (*this); }
|
||||
|
||||
// Convenient standard keys that we will keep on using all over the place
|
||||
static Builder opaqueShape() { return Builder().withTypeShape(); }
|
||||
static Builder transparentShape() { return Builder().withTypeShape().withTransparent(); }
|
||||
static Builder light() { return Builder().withTypeLight(); }
|
||||
static Builder background() { return Builder().withViewSpace().withLayered(); }
|
||||
};
|
||||
ItemKey(const Builder& builder) : ItemKey(builder._flags) {}
|
||||
|
||||
bool isShape() const { return _flags[TYPE_SHAPE]; }
|
||||
bool isLight() const { return _flags[TYPE_LIGHT]; }
|
||||
|
||||
bool isOpaque() const { return !_flags[TRANSLUCENT]; }
|
||||
bool isTransparent() const { return _flags[TRANSLUCENT]; }
|
||||
|
||||
bool isWorldSpace() const { return !_flags[VIEW_SPACE]; }
|
||||
bool isViewSpace() const { return _flags[VIEW_SPACE]; }
|
||||
|
||||
bool isStatic() const { return !_flags[DYNAMIC]; }
|
||||
bool isDynamic() const { return _flags[DYNAMIC]; }
|
||||
|
||||
bool isRigid() const { return !_flags[DEFORMED]; }
|
||||
bool isDeformed() const { return _flags[DEFORMED]; }
|
||||
|
||||
bool isVisible() const { return !_flags[INVISIBLE]; }
|
||||
bool isInvisible() const { return _flags[INVISIBLE]; }
|
||||
|
||||
bool isShadowCaster() const { return _flags[SHADOW_CASTER]; }
|
||||
|
||||
bool isPickable() const { return _flags[PICKABLE]; }
|
||||
|
||||
bool isLayered() const { return _flags[LAYERED]; }
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const ItemKey& itemKey) {
|
||||
debug << "[ItemKey: isOpaque:" << itemKey.isOpaque()
|
||||
<< ", isStatic:" << itemKey.isStatic()
|
||||
<< ", isWorldSpace:" << itemKey.isWorldSpace()
|
||||
<< "]";
|
||||
return debug;
|
||||
}
|
||||
|
||||
class ItemFilter {
|
||||
public:
|
||||
ItemKey::Flags _value{ 0 };
|
||||
ItemKey::Flags _mask{ 0 };
|
||||
|
||||
|
||||
ItemFilter(const ItemKey::Flags& value = ItemKey::Flags(0), const ItemKey::Flags& mask = ItemKey::Flags(0)) : _value(value), _mask(mask) {}
|
||||
|
||||
class Builder {
|
||||
friend class ItemFilter;
|
||||
ItemKey::Flags _value{ 0 };
|
||||
ItemKey::Flags _mask{ 0 };
|
||||
public:
|
||||
Builder() {}
|
||||
|
||||
ItemFilter build() const { return ItemFilter(_value, _mask); }
|
||||
|
||||
Builder& withTypeShape() { _value.set(ItemKey::TYPE_SHAPE); _mask.set(ItemKey::TYPE_SHAPE); return (*this); }
|
||||
Builder& withTypeLight() { _value.set(ItemKey::TYPE_LIGHT); _mask.set(ItemKey::TYPE_LIGHT); return (*this); }
|
||||
|
||||
Builder& withOpaque() { _value.reset(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
|
||||
Builder& withTransparent() { _value.set(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); }
|
||||
|
||||
Builder& withWorldSpace() { _value.reset(ItemKey::VIEW_SPACE); _mask.set(ItemKey::VIEW_SPACE); return (*this); }
|
||||
Builder& withViewSpace() { _value.set(ItemKey::VIEW_SPACE); _mask.set(ItemKey::VIEW_SPACE); return (*this); }
|
||||
|
||||
Builder& withStatic() { _value.reset(ItemKey::DYNAMIC); _mask.set(ItemKey::DYNAMIC); return (*this); }
|
||||
Builder& withDynamic() { _value.set(ItemKey::DYNAMIC); _mask.set(ItemKey::DYNAMIC); return (*this); }
|
||||
|
||||
Builder& withRigid() { _value.reset(ItemKey::DEFORMED); _mask.set(ItemKey::DEFORMED); return (*this); }
|
||||
Builder& withDeformed() { _value.set(ItemKey::DEFORMED); _mask.set(ItemKey::DEFORMED); return (*this); }
|
||||
|
||||
Builder& withVisible() { _value.reset(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); }
|
||||
Builder& withInvisible() { _value.set(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); }
|
||||
|
||||
Builder& withNoShadowCaster() { _value.reset(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); }
|
||||
Builder& withShadowCaster() { _value.set(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); }
|
||||
|
||||
Builder& withPickable() { _value.set(ItemKey::PICKABLE); _mask.set(ItemKey::PICKABLE); return (*this); }
|
||||
|
||||
Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
|
||||
Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); }
|
||||
|
||||
// Convenient standard keys that we will keep on using all over the place
|
||||
static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); }
|
||||
static Builder transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace(); }
|
||||
static Builder light() { return Builder().withTypeLight(); }
|
||||
static Builder background() { return Builder().withViewSpace().withLayered(); }
|
||||
};
|
||||
|
||||
ItemFilter(const Builder& builder) : ItemFilter(builder._value, builder._mask) {}
|
||||
|
||||
// Item Filter operator testing if a key pass the filter
|
||||
bool test(const ItemKey& key) const { return (key._flags & _mask) == (_value & _mask); }
|
||||
|
||||
class Less {
|
||||
public:
|
||||
bool operator() (const ItemFilter& left, const ItemFilter& right) const {
|
||||
if (left._value.to_ulong() == right._value.to_ulong()) {
|
||||
return left._mask.to_ulong() < right._mask.to_ulong();
|
||||
} else {
|
||||
return left._value.to_ulong() < right._value.to_ulong();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const ItemFilter& me) {
|
||||
debug << "[ItemFilter: opaqueShape:" << me.test(ItemKey::Builder::opaqueShape().build())
|
||||
<< "]";
|
||||
return debug;
|
||||
}
|
||||
|
||||
|
||||
class Item {
|
||||
public:
|
||||
typedef std::vector<Item> Vector;
|
||||
typedef unsigned int ID;
|
||||
|
||||
static const ID INVALID_ITEM_ID = 0;
|
||||
|
||||
// Bound is the AABBox fully containing this item
|
||||
typedef AABox Bound;
|
||||
|
||||
// Status records the life history and performances of this item while performing at rendering and updating.
|
||||
// This is Used for monitoring and dynamically adjust the quality
|
||||
class Status {
|
||||
public:
|
||||
|
||||
// Status::Value class is the data used to represent the transient information of a status as a square icon
|
||||
// The "icon" is a square displayed in the 3D scene over the render::Item AABB center.
|
||||
// It can be scaled in the range [0, 1] and the color hue in the range [0, 360] representing the color wheel hue
|
||||
class Value {
|
||||
unsigned short _scale = 0xFFFF;
|
||||
unsigned char _color = 0xFF;
|
||||
unsigned char _icon = 0xFF;
|
||||
public:
|
||||
const static Value INVALID; // Invalid value meanss the status won't show
|
||||
|
||||
Value() {}
|
||||
Value(float scale, float hue, unsigned char icon = 0xFF) { setScale(scale); setColor(hue); setIcon(icon); }
|
||||
|
||||
// It can be scaled in the range [0, 1]
|
||||
void setScale(float scale);
|
||||
// the color hue in the range [0, 360] representing the color wheel hue
|
||||
void setColor(float hue);
|
||||
// the icon to display in the range [0, 255], where 0 means no icon, just filled quad and anything else would
|
||||
// hopefully have an icon available to display (see DrawStatusJob)
|
||||
void setIcon(unsigned char icon);
|
||||
|
||||
// Standard color Hue
|
||||
static const float RED; // 0.0f;
|
||||
static const float YELLOW; // 60.0f;
|
||||
static const float GREEN; // 120.0f;
|
||||
static const float CYAN; // 180.0f;
|
||||
static const float BLUE; // 240.0f;
|
||||
static const float MAGENTA; // 300.0f;
|
||||
|
||||
// Retreive the Value data tightely packed as an int
|
||||
int getPackedData() const { return *((const int*) this); }
|
||||
};
|
||||
|
||||
typedef std::function<Value()> Getter;
|
||||
typedef std::vector<Getter> Getters;
|
||||
|
||||
Getters _values;
|
||||
|
||||
void addGetter(const Getter& getter) { _values.push_back(getter); }
|
||||
|
||||
size_t getNumValues() const { return _values.size(); }
|
||||
|
||||
using Values = std::vector <Value>;
|
||||
Values getCurrentValues() const;
|
||||
};
|
||||
typedef std::shared_ptr<Status> StatusPointer;
|
||||
|
||||
// Update Functor
|
||||
class UpdateFunctorInterface {
|
||||
public:
|
||||
virtual ~UpdateFunctorInterface() {}
|
||||
};
|
||||
typedef std::shared_ptr<UpdateFunctorInterface> UpdateFunctorPointer;
|
||||
|
||||
// Payload is whatever is in this Item and implement the Payload Interface
|
||||
class PayloadInterface {
|
||||
public:
|
||||
virtual const ItemKey getKey() const = 0;
|
||||
virtual const Bound getBound() const = 0;
|
||||
virtual int getLayer() const = 0;
|
||||
|
||||
virtual void render(RenderArgs* args) = 0;
|
||||
|
||||
virtual const ShapeKey getShapeKey() const = 0;
|
||||
|
||||
~PayloadInterface() {}
|
||||
|
||||
// Status interface is local to the base class
|
||||
const StatusPointer& getStatus() const { return _status; }
|
||||
void addStatusGetter(const Status::Getter& getter);
|
||||
void addStatusGetters(const Status::Getters& getters);
|
||||
|
||||
protected:
|
||||
StatusPointer _status;
|
||||
|
||||
friend class Item;
|
||||
virtual void update(const UpdateFunctorPointer& functor) = 0;
|
||||
};
|
||||
typedef std::shared_ptr<PayloadInterface> PayloadPointer;
|
||||
|
||||
Item() {}
|
||||
~Item() {}
|
||||
|
||||
// Main scene / item managment interface Reset/Update/Kill
|
||||
void resetPayload(const PayloadPointer& payload);
|
||||
void update(const UpdateFunctorPointer& updateFunctor) { _payload->update(updateFunctor); } // Communicate update to the payload
|
||||
void kill() { _payload.reset(); _key._flags.reset(); } // Kill means forget the payload and key
|
||||
|
||||
// Check heuristic key
|
||||
const ItemKey& getKey() const { return _key; }
|
||||
|
||||
// Payload Interface
|
||||
|
||||
// Get the bound of the item expressed in world space (or eye space depending on the key.isWorldSpace())
|
||||
const Bound getBound() const { return _payload->getBound(); }
|
||||
|
||||
// Get the layer where the item belongs. 0 by default meaning NOT LAYERED
|
||||
int getLayer() const { return _payload->getLayer(); }
|
||||
|
||||
// Render call for the item
|
||||
void render(RenderArgs* args) const { _payload->render(args); }
|
||||
|
||||
// Shape Type Interface
|
||||
const ShapeKey getShapeKey() const { return _payload->getShapeKey(); }
|
||||
|
||||
// Access the status
|
||||
const StatusPointer& getStatus() const { return _payload->getStatus(); }
|
||||
|
||||
protected:
|
||||
PayloadPointer _payload;
|
||||
ItemKey _key;
|
||||
|
||||
friend class Scene;
|
||||
};
|
||||
|
||||
|
||||
typedef Item::UpdateFunctorInterface UpdateFunctorInterface;
|
||||
typedef Item::UpdateFunctorPointer UpdateFunctorPointer;
|
||||
typedef std::vector<UpdateFunctorPointer> UpdateFunctors;
|
||||
|
||||
template <class T> class UpdateFunctor : public Item::UpdateFunctorInterface {
|
||||
public:
|
||||
typedef std::function<void(T&)> Func;
|
||||
Func _func;
|
||||
|
||||
UpdateFunctor(Func func): _func(func) {}
|
||||
~UpdateFunctor() {}
|
||||
};
|
||||
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const Item& item) {
|
||||
debug << "[Item: _key:" << item.getKey() << ", bounds:" << item.getBound() << "]";
|
||||
return debug;
|
||||
}
|
||||
|
||||
// THe Payload class is the real Payload to be used
|
||||
// THis allow anything to be turned into a Payload as long as the required interface functions are available
|
||||
// When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff"
|
||||
// of the Payload interface
|
||||
template <class T> const ItemKey payloadGetKey(const std::shared_ptr<T>& payloadData) { return ItemKey(); }
|
||||
template <class T> const Item::Bound payloadGetBound(const std::shared_ptr<T>& payloadData) { return Item::Bound(); }
|
||||
template <class T> int payloadGetLayer(const std::shared_ptr<T>& payloadData) { return 0; }
|
||||
template <class T> void payloadRender(const std::shared_ptr<T>& payloadData, RenderArgs* args) { }
|
||||
|
||||
// Shape type interface
|
||||
// This allows shapes to characterize their pipeline via a ShapeKey, to be picked with a subclass of Shape.
|
||||
// When creating a new shape payload you need to create a specialized version, or the ShapeKey will be ownPipeline,
|
||||
// implying that the shape will setup its own pipeline without the use of the ShapeKey.
|
||||
template <class T> const ShapeKey shapeGetShapeKey(const std::shared_ptr<T>& payloadData) { return ShapeKey::Builder::ownPipeline(); }
|
||||
|
||||
template <class T> class Payload : public Item::PayloadInterface {
|
||||
public:
|
||||
typedef std::shared_ptr<T> DataPointer;
|
||||
typedef UpdateFunctor<T> Updater;
|
||||
|
||||
Payload(const DataPointer& data) : _data(data) {}
|
||||
|
||||
// Payload general interface
|
||||
virtual const ItemKey getKey() const { return payloadGetKey<T>(_data); }
|
||||
virtual const Item::Bound getBound() const { return payloadGetBound<T>(_data); }
|
||||
virtual int getLayer() const { return payloadGetLayer<T>(_data); }
|
||||
|
||||
|
||||
virtual void render(RenderArgs* args) { payloadRender<T>(_data, args); }
|
||||
|
||||
// Shape Type interface
|
||||
virtual const ShapeKey getShapeKey() const { return shapeGetShapeKey<T>(_data); }
|
||||
|
||||
protected:
|
||||
DataPointer _data;
|
||||
|
||||
// Update mechanics
|
||||
virtual void update(const UpdateFunctorPointer& functor) { std::static_pointer_cast<Updater>(functor)->_func((*_data)); }
|
||||
friend class Item;
|
||||
};
|
||||
|
||||
// Let's show how to make a simple FooPayload example:
|
||||
/*
|
||||
class Foo {
|
||||
public:
|
||||
mutable ItemKey _myownKey;
|
||||
void makeMywnKey() const {
|
||||
_myownKey = ItemKey::Builder().withTypeShape().build();
|
||||
}
|
||||
|
||||
const Item::Bound evaluateMyBound() {
|
||||
// Do stuff here to get your final Bound
|
||||
return Item::Bound();
|
||||
}
|
||||
};
|
||||
|
||||
typedef Payload<Foo> FooPayload;
|
||||
typedef std::shared_ptr<Foo> FooPointer;
|
||||
|
||||
// In a Source file, not a header, implement the Payload interface function specialized for Foo:
|
||||
template <> const ItemKey payloadGetKey(const FooPointer& foo) {
|
||||
// Foo's way of provinding its Key
|
||||
foo->makeMyKey();
|
||||
return foo->_myownKey;
|
||||
}
|
||||
template <> const Item::Bound payloadGetBound(const FooPointer& foo) {
|
||||
// evaluate Foo's own bound
|
||||
return foo->evaluateMyBound();
|
||||
}
|
||||
|
||||
// In this example, do not specialize the payloadRender call which means the compiler will use the default version which does nothing
|
||||
|
||||
*/
|
||||
// End of the example
|
||||
|
||||
typedef Item::PayloadPointer PayloadPointer;
|
||||
typedef std::vector< PayloadPointer > Payloads;
|
||||
|
||||
// A few typedefs for standard containers of ItemIDs
|
||||
typedef Item::ID ItemID;
|
||||
typedef std::vector<ItemID> ItemIDs;
|
||||
typedef std::set<ItemID> ItemIDSet;
|
||||
|
||||
class ItemIDAndBounds {
|
||||
public:
|
||||
ItemIDAndBounds(ItemID id) : id(id) { }
|
||||
ItemIDAndBounds(ItemID id, const AABox& bounds) : id(id), bounds(bounds) { }
|
||||
|
||||
ItemID id;
|
||||
AABox bounds;
|
||||
};
|
||||
|
||||
// A list of items to be passed between rendering jobs
|
||||
using ItemIDsBounds = std::vector<ItemIDAndBounds>;
|
||||
|
||||
// A map of items by ShapeKey to optimize rendering pipeline assignments
|
||||
using ShapesIDsBounds = std::unordered_map<ShapeKey, ItemIDsBounds, ShapeKey::Hash, ShapeKey::KeyEqual>;
|
||||
|
||||
|
||||
// A map of ItemIDSets allowing to create bucket lists of items which are filtering correctly
|
||||
// A map of ItemIDSets allowing to create bucket lists of items which are filtered according to their key
|
||||
class ItemBucketMap : public std::map<ItemFilter, ItemIDSet, ItemFilter::Less> {
|
||||
public:
|
||||
|
||||
|
@ -490,7 +68,7 @@ typedef std::queue<PendingChanges> PendingChangesQueue;
|
|||
// Items are notified accordingly on any update message happening
|
||||
class Scene {
|
||||
public:
|
||||
Scene();
|
||||
Scene(glm::vec3 origin, float size);
|
||||
~Scene() {}
|
||||
|
||||
/// This call is thread safe, can be called from anywhere to allocate a new ID
|
||||
|
@ -499,8 +77,8 @@ public:
|
|||
/// Enqueue change batch to the scene
|
||||
void enqueuePendingChanges(const PendingChanges& pendingChanges);
|
||||
|
||||
/// Access the main bucketmap of items
|
||||
const ItemBucketMap& getMasterBucket() const { return _masterBucketMap; }
|
||||
// Process the penging changes equeued
|
||||
void processPendingChangesQueue();
|
||||
|
||||
/// Access a particular item form its ID
|
||||
/// WARNING, There is No check on the validity of the ID, so this could return a bad Item
|
||||
|
@ -508,9 +86,12 @@ public:
|
|||
|
||||
size_t getNumItems() const { return _items.size(); }
|
||||
|
||||
/// Access the main bucketmap of items
|
||||
const ItemBucketMap& getMasterBucket() const { return _masterBucketMap; }
|
||||
|
||||
void processPendingChangesQueue();
|
||||
|
||||
/// Access the item spatial tree
|
||||
const ItemSpatialTree& getSpatialTree() const { return _masterSpatialTree; }
|
||||
|
||||
protected:
|
||||
// Thread safe elements that can be accessed from anywhere
|
||||
std::atomic<unsigned int> _IDAllocator{ 1 }; // first valid itemID will be One
|
||||
|
@ -522,6 +103,8 @@ protected:
|
|||
std::mutex _itemsMutex;
|
||||
Item::Vector _items;
|
||||
ItemBucketMap _masterBucketMap;
|
||||
ItemSpatialTree _masterSpatialTree;
|
||||
|
||||
|
||||
void resetItems(const ItemIDs& ids, Payloads& payloads);
|
||||
void removeItems(const ItemIDs& ids);
|
||||
|
@ -530,8 +113,6 @@ protected:
|
|||
friend class Engine;
|
||||
};
|
||||
|
||||
|
||||
|
||||
typedef std::shared_ptr<Scene> ScenePointer;
|
||||
typedef std::vector<ScenePointer> Scenes;
|
||||
|
||||
|
|
76
libraries/render/src/render/SceneOctree.slh
Normal file
76
libraries/render/src/render/SceneOctree.slh
Normal file
|
@ -0,0 +1,76 @@
|
|||
<!
|
||||
// render/SceneOctree.slh
|
||||
//
|
||||
// Created by Sam Gateau on 1/29/16.
|
||||
// Copyright 2016 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
|
||||
!>
|
||||
<@if not RENDER_OCTREE_SLH@>
|
||||
<@def RENDER_OCTREE_SLH@>
|
||||
|
||||
const float _size = 32768.0;
|
||||
const float _invSize = 1.0 / _size;
|
||||
const vec3 _origin = vec3(-16384.0);
|
||||
|
||||
float getSize() { return _size; }
|
||||
vec3 getOrigin() { return _origin; }
|
||||
|
||||
const int MAX_DEPTH = 15;
|
||||
const float DEPTH_DIM[16] = float[16](
|
||||
1.0,
|
||||
2.0,
|
||||
4.0,
|
||||
8.0,
|
||||
16.0,
|
||||
32.0,
|
||||
64.0,
|
||||
128.0,
|
||||
256.0,
|
||||
512.0,
|
||||
1024.0,
|
||||
2048.0,
|
||||
4096.0,
|
||||
8192.0,
|
||||
16384.0,
|
||||
32768.0 );
|
||||
const float INV_DEPTH_DIM[16] = float[16](
|
||||
1.0,
|
||||
1.0 / 2.0,
|
||||
1.0 / 4.0,
|
||||
1.0 / 8.0,
|
||||
1.0 / 16.0,
|
||||
1.0 / 32.0,
|
||||
1.0 / 64.0,
|
||||
1.0 / 128.0,
|
||||
1.0 / 256.0,
|
||||
1.0 / 512.0,
|
||||
1.0 / 1024.0,
|
||||
1.0 / 2048.0,
|
||||
1.0 / 4096.0,
|
||||
1.0 / 8192.0,
|
||||
1.0 / 16384.0,
|
||||
1.0 / 32768.0 );
|
||||
|
||||
int getDepthDimension(int depth) { return 1 << depth; }
|
||||
float getDepthDimensionf(int depth) { return DEPTH_DIM[depth]; }
|
||||
float getInvDepthDimension(int depth) { return INV_DEPTH_DIM[depth]; }
|
||||
|
||||
float getCellWidth(int depth) { return _size * getInvDepthDimension(depth); }
|
||||
float getInvCellWidth(int depth) { return getDepthDimensionf(depth) * _invSize; }
|
||||
|
||||
vec3 evalPos(ivec3 coord, int depth) {
|
||||
return getOrigin() + vec3(coord) * getCellWidth(depth);
|
||||
}
|
||||
vec3 evalPosDepthWidth(ivec3 coord, float cellWidth) {
|
||||
return getOrigin() + vec3(coord) * cellWidth;
|
||||
}
|
||||
|
||||
vec4 evalBound(ivec4 loc) {
|
||||
float cellWidth = getCellWidth(loc.w);
|
||||
return vec4(evalPosDepthWidth(loc.xyz, cellWidth), cellWidth);
|
||||
}
|
||||
|
||||
|
||||
<@endif@>
|
591
libraries/render/src/render/SpatialTree.cpp
Normal file
591
libraries/render/src/render/SpatialTree.cpp
Normal file
|
@ -0,0 +1,591 @@
|
|||
//
|
||||
// SpatialTree.h
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Sam Gateau on 1/25/16.
|
||||
// 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 "SpatialTree.h"
|
||||
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
|
||||
using namespace render;
|
||||
|
||||
|
||||
const float Octree::INV_DEPTH_DIM[] = {
|
||||
1.0f,
|
||||
1.0f / 2.0f,
|
||||
1.0f / 4.0f,
|
||||
1.0f / 8.0f,
|
||||
1.0f / 16.0f,
|
||||
1.0f / 32.0f,
|
||||
1.0f / 64.0f,
|
||||
1.0f / 128.0f,
|
||||
1.0f / 256.0f,
|
||||
1.0f / 512.0f,
|
||||
1.0f / 1024.0f,
|
||||
1.0f / 2048.0f,
|
||||
1.0f / 4096.0f,
|
||||
1.0f / 8192.0f,
|
||||
1.0f / 16384.0f,
|
||||
1.0f / 32768.0f };
|
||||
|
||||
/*
|
||||
const float Octree::COORD_SUBCELL_WIDTH[] = { // 2 ^ MAX_DEPTH / 2 ^ (depth + 1)
|
||||
16384.0f,
|
||||
8192.0f,
|
||||
4096.0f,
|
||||
2048.0f,
|
||||
1024.0f,
|
||||
512.0f,
|
||||
256.0f,
|
||||
128.0f,
|
||||
64.0f,
|
||||
32.0f,
|
||||
16.0f,
|
||||
8.0f,
|
||||
4.0f,
|
||||
2.0f,
|
||||
1.0f,
|
||||
0.5f };
|
||||
*/
|
||||
|
||||
Octree::Location::vector Octree::Location::pathTo(const Location& dest) {
|
||||
Location current{ dest };
|
||||
vector path(dest.depth + 1);
|
||||
path[dest.depth] = dest;
|
||||
while (current.depth > 0) {
|
||||
current = current.parent();
|
||||
path[current.depth] = current;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
Octree::Location Octree::Location::evalFromRange(const Coord3& minCoord, const Coord3& maxCoord, Depth rangeDepth) {
|
||||
Depth depthOffset = MAX_DEPTH - rangeDepth;
|
||||
Depth depth = depthOffset;
|
||||
Coord3 mask(depthBitmask(depth));
|
||||
|
||||
while (depth < rangeDepth) {
|
||||
Coord3 nextMask = mask | depthBitmask(depth + 1);
|
||||
if ((minCoord & nextMask) != (maxCoord & nextMask)) {
|
||||
break;
|
||||
}
|
||||
mask = nextMask;
|
||||
depth++;
|
||||
}
|
||||
|
||||
if (depth == 0) {
|
||||
return Location();
|
||||
} else {
|
||||
// Location depth and coordinate are found, need to bring the coordinate from sourceDepth to destinationDepth
|
||||
auto sourceCoord = (minCoord & mask);
|
||||
return Location(sourceCoord >> Coord3(rangeDepth - depth), depth);
|
||||
}
|
||||
}
|
||||
|
||||
Octree::Indices Octree::indexConcreteCellPath(const Locations& path) const {
|
||||
Index currentIndex = ROOT_CELL;
|
||||
Indices cellPath(1, currentIndex);
|
||||
|
||||
// Start the path after the root cell so at #1
|
||||
for (size_t p = 1; p < path.size(); p++) {
|
||||
auto& location = path[p];
|
||||
auto nextIndex = getConcreteCell(currentIndex).child(location.octant());
|
||||
if (nextIndex == INVALID_CELL) {
|
||||
break;
|
||||
}
|
||||
|
||||
// One more cell index on the path, moving on
|
||||
currentIndex = nextIndex;
|
||||
cellPath.push_back(currentIndex);
|
||||
}
|
||||
|
||||
return cellPath;
|
||||
}
|
||||
|
||||
Octree::Index Octree::allocateCell(Index parent, const Location& location) {
|
||||
|
||||
if (_cells[parent].hasChild(location.octant())) {
|
||||
return _cells[parent].child(location.octant());
|
||||
}
|
||||
assert(_cells[parent].getlocation().child(location.octant()) == location);
|
||||
|
||||
Index newIndex;
|
||||
if (_freeCells.empty()) {
|
||||
newIndex = (Index)_cells.size();
|
||||
if (newIndex >= MAXIMUM_INDEX) {
|
||||
// abort! we are trying to go overboard with the total number of allocated bricks
|
||||
return INVALID_CELL;
|
||||
}
|
||||
_cells.push_back(Cell(parent, location));
|
||||
} else {
|
||||
newIndex = _freeCells.back();
|
||||
_freeCells.pop_back();
|
||||
_cells[newIndex] = Cell(parent, location);
|
||||
}
|
||||
|
||||
_cells[parent].setChild(location.octant(), newIndex);
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
|
||||
void Octree::freeCell(Index index) {
|
||||
if (checkCellIndex(index)) {
|
||||
auto & cell = _cells[index];
|
||||
cell.free();
|
||||
_freeCells.push_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
void Octree::cleanCellBranch(Index index) {
|
||||
auto& cell = editCell(index);
|
||||
|
||||
// Free the brick
|
||||
if (cell.isBrickEmpty()) {
|
||||
if (cell.hasBrick()) {
|
||||
freeBrick(cell.brick());
|
||||
cell.setBrick(INVALID_CELL);
|
||||
}
|
||||
} else {
|
||||
// If the brick is still filled, stop clearing
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Free the cell ?
|
||||
Index parentIdx = cell.parent();
|
||||
if (!cell.hasParent()) {
|
||||
if (index == ROOT_CELL) {
|
||||
// Stop here, this is the root cell!
|
||||
return;
|
||||
} else {
|
||||
// This is not expected
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool traverseParent = false;
|
||||
if (!cell.hasChildren()) {
|
||||
editCell(parentIdx).setChild(cell.getlocation().octant(), INVALID_CELL);
|
||||
freeCell(index);
|
||||
traverseParent = true;
|
||||
}
|
||||
|
||||
if (traverseParent) {
|
||||
cleanCellBranch(parentIdx);
|
||||
}
|
||||
}
|
||||
|
||||
Octree::Indices Octree::indexCellPath(const Locations& path) {
|
||||
// First through the allocated cells
|
||||
Indices cellPath = indexConcreteCellPath(path);
|
||||
|
||||
// Catch up from the last allocated cell on the path
|
||||
auto currentIndex = cellPath.back();
|
||||
|
||||
for (int l = (Index) cellPath.size(); l < (Index) path.size(); l++) {
|
||||
auto& location = path[l];
|
||||
|
||||
// Allocate the new index & connect it to the parent
|
||||
auto newIndex = allocateCell(currentIndex, location);
|
||||
|
||||
// One more cell index on the path, moving on
|
||||
currentIndex = newIndex;
|
||||
cellPath.push_back(currentIndex);
|
||||
|
||||
// Except !!! if we actually couldn't allocate anymore
|
||||
if (newIndex == INVALID_CELL) {
|
||||
// no more cellID available, stop allocating
|
||||
// THe last index added is INVALID_CELL so the caller will know we failed allocating everything
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cellPath;
|
||||
}
|
||||
|
||||
Octree::Index Octree::allocateBrick() {
|
||||
if (_freeBricks.empty()) {
|
||||
Index brickIdx = (int)_bricks.size();
|
||||
if (brickIdx >= MAXIMUM_INDEX) {
|
||||
// abort! we are trying to go overboard with the total number of allocated bricks
|
||||
assert(false);
|
||||
// This should never happen because Bricks are allocated along with the cells and there
|
||||
// is already a cap on the cells allocation
|
||||
return INVALID_CELL;
|
||||
}
|
||||
_bricks.push_back(Brick());
|
||||
return brickIdx;
|
||||
} else {
|
||||
Index brickIdx = _freeBricks.back();
|
||||
_freeBricks.pop_back();
|
||||
return brickIdx;
|
||||
}
|
||||
}
|
||||
|
||||
void Octree::freeBrick(Index index) {
|
||||
if (checkBrickIndex(index)) {
|
||||
auto & brick = _bricks[index];
|
||||
brick.free();
|
||||
_freeBricks.push_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
Octree::Index Octree::accessCellBrick(Index cellID, const CellBrickAccessor& accessor, bool createBrick) {
|
||||
assert(checkCellIndex(cellID));
|
||||
auto& cell = editCell(cellID);
|
||||
|
||||
// Allocate a brick if needed
|
||||
if (!cell.hasBrick()) {
|
||||
if (!createBrick) {
|
||||
return INVALID_CELL;
|
||||
}
|
||||
auto newBrick = allocateBrick();
|
||||
if (newBrick == INVALID_CELL) {
|
||||
// This should never happen but just in case...
|
||||
return INVALID_CELL;
|
||||
}
|
||||
cell.setBrick(newBrick);
|
||||
}
|
||||
|
||||
// Access the brick
|
||||
auto brickID = cell.brick();
|
||||
auto& brick = _bricks[brickID];
|
||||
|
||||
// Execute the accessor
|
||||
accessor(cell, brick, brickID);
|
||||
|
||||
return brickID;
|
||||
}
|
||||
|
||||
Octree::Locations ItemSpatialTree::evalLocations(const ItemBounds& bounds) const {
|
||||
Locations locations;
|
||||
locations.reserve(bounds.size());
|
||||
for (auto& bound : bounds) {
|
||||
if (!bound.bound.isNull()) {
|
||||
locations.emplace_back(evalLocation(bound.bound));
|
||||
} else {
|
||||
locations.emplace_back(Location());
|
||||
}
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
||||
ItemSpatialTree::Index ItemSpatialTree::insertItem(Index cellIdx, const ItemKey& key, const ItemID& item) {
|
||||
// Add the item to the brick (and a brick if needed)
|
||||
accessCellBrick(cellIdx, [&](Cell& cell, Brick& brick, Octree::Index cellID) {
|
||||
auto& itemIn = (key.isSmall() ? brick.subcellItems : brick.items);
|
||||
|
||||
itemIn.push_back(item);
|
||||
|
||||
cell.setBrickFilled();
|
||||
}, true);
|
||||
|
||||
return cellIdx;
|
||||
}
|
||||
|
||||
bool ItemSpatialTree::updateItem(Index cellIdx, const ItemKey& oldKey, const ItemKey& key, const ItemID& item) {
|
||||
// In case we missed that one, nothing to do
|
||||
if (cellIdx == INVALID_CELL) {
|
||||
return true;
|
||||
}
|
||||
auto success = false;
|
||||
|
||||
// only if key changed
|
||||
assert(oldKey != key);
|
||||
|
||||
// Get to the brick where the item is and update where it s stored
|
||||
accessCellBrick(cellIdx, [&](Cell& cell, Brick& brick, Octree::Index cellID) {
|
||||
auto& itemIn = (key.isSmall() ? brick.subcellItems : brick.items);
|
||||
auto& itemOut = (oldKey.isSmall() ? brick.subcellItems : brick.items);
|
||||
|
||||
itemIn.push_back(item);
|
||||
itemOut.erase(std::find(itemOut.begin(), itemOut.end(), item));
|
||||
}, false); // do not create brick!
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ItemSpatialTree::removeItem(Index cellIdx, const ItemKey& key, const ItemID& item) {
|
||||
// In case we missed that one, nothing to do
|
||||
if (cellIdx == INVALID_CELL) {
|
||||
return true;
|
||||
}
|
||||
auto success = false;
|
||||
|
||||
// Remove the item from the brick
|
||||
bool emptyCell = false;
|
||||
accessCellBrick(cellIdx, [&](Cell& cell, Brick& brick, Octree::Index brickID) {
|
||||
auto& itemList = (key.isSmall() ? brick.subcellItems : brick.items);
|
||||
|
||||
itemList.erase(std::find(itemList.begin(), itemList.end(), item));
|
||||
|
||||
if (brick.items.empty() && brick.subcellItems.empty()) {
|
||||
cell.setBrickEmpty();
|
||||
emptyCell = true;
|
||||
}
|
||||
success = true;
|
||||
}, false); // do not create brick!
|
||||
|
||||
// Because we know the cell is now empty, lets try to clean the octree here
|
||||
if (emptyCell) {
|
||||
cleanCellBranch(cellIdx);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
ItemSpatialTree::Index ItemSpatialTree::resetItem(Index oldCell, const ItemKey& oldKey, const AABox& bound, const ItemID& item, ItemKey& newKey) {
|
||||
auto newCell = INVALID_CELL;
|
||||
if (!newKey.isViewSpace()) {
|
||||
auto minCoordf = evalCoordf(bound.getMinimumPoint());
|
||||
auto maxCoordf = evalCoordf(bound.getMaximumPoint());
|
||||
Coord3 minCoord(minCoordf);
|
||||
Coord3 maxCoord(maxCoordf);
|
||||
auto location = Location::evalFromRange(minCoord, maxCoord);
|
||||
|
||||
// Compare range size vs cell location size and tag itemKey accordingly
|
||||
// If Item bound fits in sub cell then tag as small
|
||||
auto rangeSizef = maxCoordf - minCoordf;
|
||||
float cellHalfSize = 0.5f * getCellWidth(location.depth);
|
||||
bool subcellItem = std::max(std::max(rangeSizef.x, rangeSizef.y), rangeSizef.z) < cellHalfSize;
|
||||
if (subcellItem) {
|
||||
newKey.setSmaller(subcellItem);
|
||||
} else {
|
||||
newKey.setSmaller(false);
|
||||
}
|
||||
|
||||
newCell = indexCell(location);
|
||||
} else {
|
||||
// A very rare case, if we were adding items with boundary semantic expressed in view space
|
||||
}
|
||||
|
||||
// Did we fail finding a cell for the item?
|
||||
if (newCell == INVALID_CELL) {
|
||||
// Remove the item where it was
|
||||
if (oldCell != INVALID_CELL) {
|
||||
removeItem(oldCell, oldKey, item);
|
||||
}
|
||||
return newCell;
|
||||
}
|
||||
// Staying in the same cell
|
||||
else if (newCell == oldCell) {
|
||||
// Did the key changed, if yes update
|
||||
if (newKey._flags != oldKey._flags) {
|
||||
updateItem(newCell, oldKey, newKey, item);
|
||||
return newCell;
|
||||
}
|
||||
return newCell;
|
||||
}
|
||||
// do we know about this item ?
|
||||
else if (oldCell == INVALID_CELL) {
|
||||
insertItem(newCell, newKey, item);
|
||||
return newCell;
|
||||
}
|
||||
// A true update of cell is required
|
||||
else {
|
||||
// Add the item to the brick (and a brick if needed)
|
||||
insertItem(newCell, newKey, item);
|
||||
|
||||
// And remove it from the previous one
|
||||
removeItem(oldCell, oldKey, item);
|
||||
|
||||
return newCell;
|
||||
}
|
||||
}
|
||||
|
||||
int Octree::select(CellSelection& selection, const FrustumSelector& selector) const {
|
||||
|
||||
Index cellID = ROOT_CELL;
|
||||
return selectTraverse(cellID, selection, selector);
|
||||
}
|
||||
|
||||
|
||||
Octree::Location::Intersection Octree::Location::intersectCell(const Location& cell, const Coord4f frustum[6]) {
|
||||
const Coord3f CornerOffsets[8] = {
|
||||
{ 0.0, 0.0, 0.0 },
|
||||
{ 1.0, 0.0, 0.0 },
|
||||
{ 0.0, 1.0, 0.0 },
|
||||
{ 1.0, 1.0, 0.0 },
|
||||
{ 0.0, 0.0, 1.0 },
|
||||
{ 1.0, 0.0, 1.0 },
|
||||
{ 0.0, 1.0, 1.0 },
|
||||
{ 1.0, 1.0, 1.0 },
|
||||
};
|
||||
|
||||
struct Tool {
|
||||
static int normalToIndex(const Coord3f& n) {
|
||||
int index = 0;
|
||||
if (n.x >= 0.0f) index |= 1;
|
||||
if (n.y >= 0.0f) index |= 2;
|
||||
if (n.z >= 0.0f) index |= 4;
|
||||
return index;
|
||||
}
|
||||
|
||||
static bool halfPlaneTest(const Coord4f& plane, const Coord3f& pos) {
|
||||
return (glm::dot(plane, Coord4f(pos, 1.0f)) >= 0.0f);
|
||||
}
|
||||
};
|
||||
|
||||
Coord3f cellSize = Coord3f(Octree::getInvDepthDimension(cell.depth));
|
||||
Coord3f cellPos = Coord3f(cell.pos) * cellSize;
|
||||
|
||||
bool partialFlag = false;
|
||||
for (int p = 0; p < ViewFrustum::NUM_PLANES; p++) {
|
||||
Coord4f plane = frustum[p];
|
||||
Coord3f planeNormal(plane);
|
||||
|
||||
int index = Tool::normalToIndex(planeNormal);
|
||||
|
||||
auto negTestPoint = cellPos + cellSize * CornerOffsets[index];
|
||||
|
||||
if (!Tool::halfPlaneTest(plane, negTestPoint)) {
|
||||
return Outside;
|
||||
}
|
||||
|
||||
index = Tool::normalToIndex(-planeNormal);
|
||||
|
||||
auto posTestPoint = cellPos + cellSize * CornerOffsets[index];
|
||||
|
||||
if (!Tool::halfPlaneTest(plane, posTestPoint)) {
|
||||
partialFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (partialFlag) {
|
||||
return Intersect;
|
||||
}
|
||||
|
||||
return Inside;
|
||||
}
|
||||
|
||||
int Octree::selectTraverse(Index cellID, CellSelection& selection, const FrustumSelector& selector) const {
|
||||
int numSelectedsIn = (int) selection.size();
|
||||
auto cell = getConcreteCell(cellID);
|
||||
|
||||
auto cellLocation = cell.getlocation();
|
||||
|
||||
auto intersection = Octree::Location::intersectCell(cellLocation, selector.frustum);
|
||||
switch (intersection) {
|
||||
case Octree::Location::Outside:
|
||||
// cell is outside, stop traversing this branch
|
||||
return 0;
|
||||
break;
|
||||
case Octree::Location::Inside: {
|
||||
// traverse all the Cell Branch and collect items in the selection
|
||||
selectBranch(cellID, selection, selector);
|
||||
break;
|
||||
}
|
||||
case Octree::Location::Intersect:
|
||||
default: {
|
||||
// Cell is partially in
|
||||
|
||||
// Test for lod
|
||||
auto cellLocation = cell.getlocation();
|
||||
float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth));
|
||||
if (lod < 0.0f) {
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Select this cell partially in frustum
|
||||
selectCellBrick(cellID, selection, false);
|
||||
|
||||
// then traverse deeper
|
||||
for (int i = 0; i < NUM_OCTANTS; i++) {
|
||||
Index subCellID = cell.child((Link)i);
|
||||
if (subCellID != INVALID_CELL) {
|
||||
selectTraverse(subCellID, selection, selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (int) selection.size() - numSelectedsIn;
|
||||
}
|
||||
|
||||
|
||||
int Octree::selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const {
|
||||
int numSelectedsIn = (int) selection.size();
|
||||
auto cell = getConcreteCell(cellID);
|
||||
|
||||
auto cellLocation = cell.getlocation();
|
||||
float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth));
|
||||
if (lod < 0.0f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Select this cell fully inside the frustum
|
||||
selectCellBrick(cellID, selection, true);
|
||||
|
||||
// then traverse deeper
|
||||
for (int i = 0; i < NUM_OCTANTS; i++) {
|
||||
Index subCellID = cell.child((Link)i);
|
||||
if (subCellID != INVALID_CELL) {
|
||||
selectBranch(subCellID, selection, selector);
|
||||
}
|
||||
}
|
||||
|
||||
return (int) selection.size() - numSelectedsIn;
|
||||
}
|
||||
|
||||
int Octree::selectCellBrick(Index cellID, CellSelection& selection, bool inside) const {
|
||||
int numSelectedsIn = (int) selection.size();
|
||||
auto cell = getConcreteCell(cellID);
|
||||
selection.cells(inside).push_back(cellID);
|
||||
|
||||
if (!cell.isBrickEmpty()) {
|
||||
// Collect the items of this cell
|
||||
selection.bricks(inside).push_back(cell.brick());
|
||||
}
|
||||
|
||||
return (int) selection.size() - numSelectedsIn;
|
||||
}
|
||||
|
||||
|
||||
int ItemSpatialTree::selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const {
|
||||
auto worldPlanes = frustum.getPlanes();
|
||||
FrustumSelector selector;
|
||||
for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) {
|
||||
::Plane octPlane;
|
||||
octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH));
|
||||
selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient());
|
||||
}
|
||||
|
||||
selector.eyePos = evalCoordf(frustum.getPosition(), ROOT_DEPTH);
|
||||
selector.setAngle(glm::radians(lodAngle));
|
||||
|
||||
return Octree::select(selection, selector);
|
||||
}
|
||||
|
||||
int ItemSpatialTree::selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const {
|
||||
selectCells(selection.cellSelection, frustum, lodAngle);
|
||||
|
||||
// Just grab the items in every selected bricks
|
||||
for (auto brickId : selection.cellSelection.insideBricks) {
|
||||
auto& brickItems = getConcreteBrick(brickId).items;
|
||||
selection.insideItems.insert(selection.insideItems.end(), brickItems.begin(), brickItems.end());
|
||||
|
||||
auto& brickSubcellItems = getConcreteBrick(brickId).subcellItems;
|
||||
selection.insideSubcellItems.insert(selection.insideSubcellItems.end(), brickSubcellItems.begin(), brickSubcellItems.end());
|
||||
}
|
||||
|
||||
for (auto brickId : selection.cellSelection.partialBricks) {
|
||||
auto& brickItems = getConcreteBrick(brickId).items;
|
||||
selection.partialItems.insert(selection.partialItems.end(), brickItems.begin(), brickItems.end());
|
||||
|
||||
auto& brickSubcellItems = getConcreteBrick(brickId).subcellItems;
|
||||
selection.partialSubcellItems.insert(selection.partialSubcellItems.end(), brickSubcellItems.begin(), brickSubcellItems.end());
|
||||
}
|
||||
|
||||
return (int) selection.numItems();
|
||||
}
|
||||
|
||||
|
||||
|
459
libraries/render/src/render/SpatialTree.h
Normal file
459
libraries/render/src/render/SpatialTree.h
Normal file
|
@ -0,0 +1,459 @@
|
|||
//
|
||||
// SpatialTree.h
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Sam Gateau on 1/25/16.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_render_Octree_h
|
||||
#define hifi_render_Octree_h
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/bit.hpp>
|
||||
#include <AABox.h>
|
||||
|
||||
// maybe we could avoid the Item inclusion here for the OCtree class?
|
||||
#include "Item.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
class Brick {
|
||||
public:
|
||||
std::vector<ItemID> items;
|
||||
std::vector<ItemID> subcellItems;
|
||||
|
||||
void free() {};
|
||||
};
|
||||
|
||||
class Octree {
|
||||
public:
|
||||
|
||||
using LinkStorage = int8_t;
|
||||
enum Link : LinkStorage {
|
||||
|
||||
Octant_L_B_N = 0,
|
||||
Octant_R_B_N = 1,
|
||||
Octant_L_T_N = 2,
|
||||
Octant_R_T_N = 3,
|
||||
Octant_L_B_F = 4,
|
||||
Octant_R_B_F = 5,
|
||||
Octant_L_T_F = 6,
|
||||
Octant_R_T_F = 7,
|
||||
|
||||
NUM_OCTANTS,
|
||||
|
||||
Parent = NUM_OCTANTS,
|
||||
BrickLink = Parent + 1,
|
||||
|
||||
NUM_LINKS = BrickLink + 1,
|
||||
|
||||
|
||||
XAxis = 0x01,
|
||||
YAxis = 0x02,
|
||||
ZAxis = 0x04,
|
||||
|
||||
NoLink = 0x0F,
|
||||
};
|
||||
|
||||
using Octant = Link;
|
||||
|
||||
|
||||
// Depth, Dim, Volume
|
||||
// {0, 1, 1}
|
||||
// {1, 2, 8}
|
||||
// {2, 4, 64}
|
||||
// {3, 8, 512}
|
||||
// {4, 16, 4096}
|
||||
// {5, 32, 32768}
|
||||
// {6, 64, 262144}
|
||||
// {7, 128, 2097152}
|
||||
// {8, 256, 16777216}
|
||||
// {9, 512, 134217728}
|
||||
// {10, 1024, 1073741824}
|
||||
// {11, 2048, 8589934592}
|
||||
// {12, 4096, 68719476736}
|
||||
// {13, 8192, 549755813888}
|
||||
// {14, 16384, 4398046511104}
|
||||
// {15, 32768, 35184372088832}
|
||||
// {16, 65536, 281474976710656}
|
||||
|
||||
// Max depth is 15 => 32Km root down to 1m cells
|
||||
using Depth = int8_t;
|
||||
static const Depth ROOT_DEPTH { 0 };
|
||||
static const Depth MAX_DEPTH { 15 };
|
||||
static const Depth METRIC_COORD_DEPTH { 15 };
|
||||
static const float INV_DEPTH_DIM[Octree::MAX_DEPTH + 1];
|
||||
|
||||
static int getDepthDimension(Depth depth) { return 1 << depth; }
|
||||
static float getDepthDimensionf(Depth depth) { return (float) getDepthDimension(depth); }
|
||||
static float getInvDepthDimension(Depth depth) { return INV_DEPTH_DIM[depth]; }
|
||||
static float getCoordSubcellWidth(Depth depth) { return (1.7320f * getInvDepthDimension(depth) * 0.5f); }
|
||||
|
||||
// Need 16bits integer coordinates on each axes: 32768 cell positions
|
||||
using Coord = int16_t;
|
||||
using Coord3 = glm::i16vec3;
|
||||
using Coord4 = glm::i16vec4;
|
||||
using Coord3f = glm::vec3;
|
||||
using Coord4f = glm::vec4;
|
||||
|
||||
static Coord depthBitmask(Depth depth) { return Coord(1 << (MAX_DEPTH - depth)); }
|
||||
|
||||
static Depth coordToDepth(Coord length) {
|
||||
Depth depth = MAX_DEPTH;
|
||||
while (length) {
|
||||
length >>= 1;
|
||||
depth--;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
|
||||
class Location {
|
||||
void assertValid() {
|
||||
assert((pos.x >= 0) && (pos.y >= 0) && (pos.z >= 0));
|
||||
assert((pos.x < (1 << depth)) && (pos.y < (1 << depth)) && (pos.z < (1 << depth)));
|
||||
}
|
||||
public:
|
||||
using vector = std::vector< Location >;
|
||||
|
||||
Location() {}
|
||||
Location(const Coord3& xyz, Depth d) : pos(xyz), depth(d) { assertValid(); }
|
||||
Location(Depth d) : pos(0), depth(d) { assertValid(); }
|
||||
|
||||
Coord3 pos { 0 };
|
||||
uint8_t spare { 0 };
|
||||
Depth depth { ROOT_DEPTH };
|
||||
|
||||
Coord3f getCenter() const {
|
||||
Coord3f center = (Coord3f(pos) + Coord3f(0.5f)) * Coord3f(Octree::getInvDepthDimension(depth));
|
||||
return center;
|
||||
}
|
||||
|
||||
bool operator== (const Location& other) const { return pos == other.pos && depth == other.depth; }
|
||||
|
||||
// Eval the octant of this cell relative to its parent
|
||||
Octant octant() const { return Octant((pos.x & 1) | ((pos.y & 1) << 1) | ((pos.z & 1) << 2)); }
|
||||
Coord3 octantAxes(Link octant) const { return Coord3((Coord)bool(octant & XAxis), (Coord)bool(octant & YAxis), (Coord)bool(octant & ZAxis)); }
|
||||
|
||||
// Get the Parent cell Location of this cell
|
||||
Location parent() const { return Location{ (pos >> Coord3(1)), Depth(depth <= 0 ? 0 : depth - 1) }; }
|
||||
Location child(Link octant) const { return Location{ (pos << Coord3(1)) | octantAxes(octant), Depth(depth + 1) }; }
|
||||
|
||||
|
||||
// Eval the list of locations (the path) from root to the destination.
|
||||
// the root cell is included
|
||||
static vector pathTo(const Location& destination);
|
||||
|
||||
// Eval the location best fitting the specified range
|
||||
static Location evalFromRange(const Coord3& minCoord, const Coord3& maxCoord, Depth rangeDepth = MAX_DEPTH);
|
||||
|
||||
// Eval the intersection test against a frustum
|
||||
enum Intersection {
|
||||
Outside = 0,
|
||||
Intersect,
|
||||
Inside,
|
||||
};
|
||||
static Intersection intersectCell(const Location& cell, const Coord4f frustum[6]);
|
||||
};
|
||||
using Locations = Location::vector;
|
||||
|
||||
// Cell or Brick Indexing
|
||||
using Index = ItemCell; // int32_t
|
||||
static const Index INVALID_CELL = -1;
|
||||
static const Index ROOT_CELL = 0;
|
||||
|
||||
// With a maximum of INT_MAX(2 ^ 31) cells in our octree
|
||||
static const Index MAXIMUM_INDEX = INT_MAX; // For fun, test setting this to 100 and this should still works with only the allocated maximum number of cells
|
||||
|
||||
using Indices = std::vector<Index>;
|
||||
|
||||
// the cell description
|
||||
class Cell {
|
||||
public:
|
||||
|
||||
enum BitFlags : uint8_t {
|
||||
HasChildren = 0x01,
|
||||
BrickFilled = 0x02,
|
||||
};
|
||||
|
||||
void free() { _location = Location(); for (auto& link : _links) { link = INVALID_CELL; } }
|
||||
|
||||
const Location& getlocation() const { return _location; }
|
||||
|
||||
Index parent() const { return _links[Parent]; }
|
||||
bool hasParent() const { return parent() != INVALID_CELL; }
|
||||
|
||||
Index child(Link octant) const { return _links[octant]; }
|
||||
bool hasChild(Link octant) const { return child(octant) != INVALID_CELL; }
|
||||
bool hasChildren() const { return (_location.spare & HasChildren); }
|
||||
void setChild(Link octant, Index child) {
|
||||
_links[octant] = child;
|
||||
if (child != INVALID_CELL) {
|
||||
_location.spare |= HasChildren;
|
||||
} else {
|
||||
if (!checkHasChildren()) {
|
||||
_location.spare &= ~HasChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Index brick() const { return _links[BrickLink]; }
|
||||
bool hasBrick() const { return _links[BrickLink] != INVALID_CELL; }
|
||||
void setBrick(Index brick) { _links[BrickLink] = brick; }
|
||||
|
||||
void setBrickFilled() { _location.spare |= BrickFilled; }
|
||||
void setBrickEmpty() { _location.spare &= ~BrickFilled; }
|
||||
bool isBrickEmpty() const { return !(_location.spare & BrickFilled); }
|
||||
|
||||
Cell() :
|
||||
_links({ { INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL } })
|
||||
{}
|
||||
|
||||
Cell(Index parent, Location loc) :
|
||||
_links({ { INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, INVALID_CELL, parent, INVALID_CELL } }),
|
||||
_location(loc)
|
||||
{}
|
||||
|
||||
private:
|
||||
std::array<Index, NUM_LINKS> _links;
|
||||
Location _location;
|
||||
|
||||
bool checkHasChildren() const {
|
||||
for (LinkStorage octant = Octant_L_B_N; octant < NUM_OCTANTS; octant++) {
|
||||
if (hasChild((Link)octant)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
using Cells = std::vector< Cell >;
|
||||
|
||||
using Bricks = std::vector< Brick >;
|
||||
|
||||
bool checkCellIndex(Index index) const { return (index >= 0) && (index < (Index) _cells.size()); }
|
||||
bool checkBrickIndex(Index index) const { return ((index >= 0) && (index < (Index) _bricks.size())); }
|
||||
|
||||
Octree() {};
|
||||
|
||||
// Clean a cell branch starting from the leave:
|
||||
// Check that the cell brick is empty, if so free it else stop
|
||||
// Check that the cell has no children, if so free itself else stop
|
||||
// Apply the same logic to the parent cell
|
||||
void cleanCellBranch(Index index);
|
||||
|
||||
// Indexing/Allocating the cells as the tree gets populated
|
||||
// Return the cell Index/Indices at the specified location/path, allocate all the cells on the path from the root if needed
|
||||
Indices indexCellPath(const Locations& path);
|
||||
Index indexCell(const Location& loc) { return indexCellPath(Location::pathTo(loc)).back(); }
|
||||
|
||||
// Same as indexCellPath except that NO cells are allocated, only the COncrete cells previously allocated
|
||||
// the returned indices stops at the last existing cell on the requested path.
|
||||
Indices indexConcreteCellPath(const Locations& path) const;
|
||||
|
||||
// Get the cell location from the CellID
|
||||
Location getCellLocation(Index cellID) const {
|
||||
if (checkCellIndex(cellID)) {
|
||||
return getConcreteCell(cellID).getlocation();
|
||||
}
|
||||
return Location();
|
||||
}
|
||||
|
||||
// Reach a concrete cell
|
||||
const Cell& getConcreteCell(Index index) const {
|
||||
assert(checkCellIndex(index));
|
||||
return _cells[index];
|
||||
}
|
||||
|
||||
// Let s talk about the Cell Bricks now
|
||||
using CellBrickAccessor = std::function<void(Cell& cell, Brick& brick, Index brickIdx)>;
|
||||
|
||||
// acces a cell (must be concrete), then call the brick accessor if the brick exists ( or is just created if authorized to)
|
||||
// This returns the Brick index
|
||||
Index accessCellBrick(Index cellID, const CellBrickAccessor& accessor, bool createBrick = true);
|
||||
|
||||
const Brick& getConcreteBrick(Index index) const {
|
||||
assert(checkBrickIndex(index));
|
||||
return _bricks[index];
|
||||
}
|
||||
|
||||
// Cell Selection and traversal
|
||||
|
||||
class CellSelection {
|
||||
public:
|
||||
Indices insideCells;
|
||||
Indices insideBricks;
|
||||
Indices partialCells;
|
||||
Indices partialBricks;
|
||||
|
||||
Indices& cells(bool inside) { return (inside ? insideCells : partialCells); }
|
||||
Indices& bricks(bool inside) { return (inside ? insideBricks : partialBricks); }
|
||||
|
||||
size_t size() const { return insideBricks.size() + partialBricks.size(); }
|
||||
|
||||
void clear() {
|
||||
insideCells.clear();
|
||||
insideBricks.clear();
|
||||
partialCells.clear();
|
||||
partialBricks.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class FrustumSelector {
|
||||
public:
|
||||
Coord4f frustum[6];
|
||||
Coord3f eyePos;
|
||||
float angle;
|
||||
float squareTanAlpha;
|
||||
|
||||
const float MAX_LOD_ANGLE = glm::radians(45.0f);
|
||||
const float MIN_LOD_ANGLE = glm::radians(1.0f / 60.0f);
|
||||
|
||||
void setAngle(float a) {
|
||||
angle = std::max(MIN_LOD_ANGLE, std::min(MAX_LOD_ANGLE, a));
|
||||
auto tanAlpha = tan(angle);
|
||||
squareTanAlpha = (float)(tanAlpha * tanAlpha);
|
||||
}
|
||||
|
||||
float testSolidAngle(const Coord3f& point, float size) const {
|
||||
auto eyeToPoint = point - eyePos;
|
||||
return (size * size / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha;
|
||||
}
|
||||
};
|
||||
|
||||
int select(CellSelection& selection, const FrustumSelector& selector) const;
|
||||
int selectTraverse(Index cellID, CellSelection& selection, const FrustumSelector& selector) const;
|
||||
int selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const;
|
||||
int selectCellBrick(Index cellID, CellSelection& selection, bool inside) const;
|
||||
|
||||
|
||||
int getNumAllocatedCells() const { return (int)_cells.size(); }
|
||||
int getNumFreeCells() const { return (int)_freeCells.size(); }
|
||||
|
||||
protected:
|
||||
Index allocateCell(Index parent, const Location& location);
|
||||
void freeCell(Index index);
|
||||
|
||||
Index allocateBrick();
|
||||
void freeBrick(Index index);
|
||||
|
||||
Cell& editCell(Index index) {
|
||||
assert(checkCellIndex(index));
|
||||
return _cells[index];
|
||||
}
|
||||
|
||||
|
||||
// Octree members
|
||||
Cells _cells = Cells(1, Cell()); // start with only the Cell root
|
||||
Bricks _bricks;
|
||||
Indices _freeCells; // stack of free cells to be reused for allocation
|
||||
Indices _freeBricks; // stack of free bricks to be reused for allocation
|
||||
};
|
||||
}
|
||||
|
||||
// CLose the namespace here before including the Item in the picture, maybe Octre could stay pure of it
|
||||
namespace render {
|
||||
|
||||
// An octree of Items organizing them efficiently for culling
|
||||
// The octree only cares about the bound & the key of an item to store it a the right cell location
|
||||
class ItemSpatialTree : public Octree {
|
||||
float _size { 32768.0f };
|
||||
float _invSize { 1.0f / _size };
|
||||
glm::vec3 _origin { -16384.0f };
|
||||
|
||||
void init(glm::vec3 origin, float size) {
|
||||
_size = size;
|
||||
_invSize = 1.0f / _size;
|
||||
_origin = origin;
|
||||
}
|
||||
public:
|
||||
// THe overall size and origin of the tree are defined at creation
|
||||
ItemSpatialTree(glm::vec3 origin, float size) { init(origin, size); }
|
||||
|
||||
float getSize() const { return _size; }
|
||||
const glm::vec3& getOrigin() const { return _origin; }
|
||||
|
||||
float getCellWidth(Depth depth) const { return _size * getInvDepthDimension(depth); }
|
||||
float getInvCellWidth(Depth depth) const { return getDepthDimensionf(depth) * _invSize; }
|
||||
|
||||
float getCellHalfDiagonalSquare(Depth depth) const {
|
||||
float cellHalfWidth = 0.5f * getCellWidth(depth);
|
||||
return 3.0f * cellHalfWidth * cellHalfWidth;
|
||||
}
|
||||
|
||||
glm::vec3 evalPos(const Coord3& coord, Depth depth = Octree::METRIC_COORD_DEPTH) const {
|
||||
return getOrigin() + glm::vec3(coord) * getCellWidth(depth);
|
||||
}
|
||||
glm::vec3 evalPos(const Coord3& coord, float cellWidth) const {
|
||||
return getOrigin() + glm::vec3(coord) * cellWidth;
|
||||
}
|
||||
|
||||
Coord3 evalCoord(const glm::vec3& pos, Depth depth = Octree::METRIC_COORD_DEPTH) const {
|
||||
auto npos = (pos - getOrigin());
|
||||
return Coord3(npos * getInvCellWidth(depth)); // Truncate fractional part
|
||||
}
|
||||
Coord3f evalCoordf(const glm::vec3& pos, Depth depth = Octree::METRIC_COORD_DEPTH) const {
|
||||
auto npos = (pos - getOrigin());
|
||||
return Coord3f(npos * getInvCellWidth(depth));
|
||||
}
|
||||
|
||||
// Bound to Location
|
||||
AABox evalBound(const Location& loc) const {
|
||||
float cellWidth = getCellWidth(loc.depth);
|
||||
return AABox(evalPos(loc.pos, cellWidth), cellWidth);
|
||||
}
|
||||
Location evalLocation(const AABox& bound) const {
|
||||
return Location::evalFromRange(evalCoord(bound.getMinimumPoint()), evalCoord(bound.getMaximumPoint()));
|
||||
}
|
||||
Locations evalLocations(const ItemBounds& bounds) const;
|
||||
|
||||
// Managing itemsInserting items in cells
|
||||
// Cells need to have been allocated first calling indexCell
|
||||
Index insertItem(Index cellIdx, const ItemKey& key, const ItemID& item);
|
||||
bool updateItem(Index cellIdx, const ItemKey& oldKey, const ItemKey& key, const ItemID& item);
|
||||
bool removeItem(Index cellIdx, const ItemKey& key, const ItemID& item);
|
||||
|
||||
Index resetItem(Index oldCell, const ItemKey& oldKey, const AABox& bound, const ItemID& item, ItemKey& newKey);
|
||||
|
||||
// Selection and traverse
|
||||
int selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const;
|
||||
|
||||
class ItemSelection {
|
||||
public:
|
||||
CellSelection cellSelection;
|
||||
ItemIDs insideItems;
|
||||
ItemIDs insideSubcellItems;
|
||||
ItemIDs partialItems;
|
||||
ItemIDs partialSubcellItems;
|
||||
|
||||
ItemIDs& items(bool inside) { return (inside ? insideItems : partialItems); }
|
||||
ItemIDs& subcellItems(bool inside) { return (inside ? insideSubcellItems : partialSubcellItems); }
|
||||
|
||||
size_t insideNumItems() const { return insideItems.size() + insideSubcellItems.size(); }
|
||||
size_t partialNumItems() const { return partialItems.size() + partialSubcellItems.size(); }
|
||||
size_t numItems() const { return insideNumItems() + partialNumItems(); }
|
||||
|
||||
void clear() {
|
||||
cellSelection.clear();
|
||||
insideItems.clear();
|
||||
insideSubcellItems.clear();
|
||||
partialItems.clear();
|
||||
partialSubcellItems.clear();
|
||||
}
|
||||
};
|
||||
|
||||
int selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // hifi_render_Octree_h
|
|
@ -37,7 +37,7 @@ public:
|
|||
template <class T> Varying(const T& data) : _concept(std::make_shared<Model<T>>(data)) {}
|
||||
|
||||
template <class T> T& edit() { return std::static_pointer_cast<Model<T>>(_concept)->_data; }
|
||||
template <class T> const T& get() { return std::static_pointer_cast<const Model<T>>(_concept)->_data; }
|
||||
template <class T> const T& get() const { return std::static_pointer_cast<const Model<T>>(_concept)->_data; }
|
||||
|
||||
protected:
|
||||
class Concept {
|
||||
|
|
20
libraries/render/src/render/drawCellBounds.slf
Normal file
20
libraries/render/src/render/drawCellBounds.slf
Normal file
|
@ -0,0 +1,20 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// drawCellBounds.slf
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Sam Gateau on 1/25/2016.
|
||||
// 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
|
||||
//
|
||||
|
||||
in vec4 varColor;
|
||||
out vec4 outFragColor;
|
||||
|
||||
|
||||
void main(void) {
|
||||
outFragColor = varColor;
|
||||
}
|
67
libraries/render/src/render/drawCellBounds.slv
Normal file
67
libraries/render/src/render/drawCellBounds.slv
Normal file
|
@ -0,0 +1,67 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// drawCellBounds.slv
|
||||
// Vertex shader
|
||||
//
|
||||
// Created by Sam Gateau on 1/25/2016
|
||||
// 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 gpu/Transform.slh@>
|
||||
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
<@include gpu/Color.slh@>
|
||||
<$declareColorWheel()$>
|
||||
<@include SceneOctree.slh@>
|
||||
|
||||
uniform ivec4 inCellLocation;
|
||||
|
||||
out vec4 varColor;
|
||||
|
||||
|
||||
void main(void) {
|
||||
const vec4 UNIT_BOX[8] = vec4[8](
|
||||
vec4(0.0, 0.0, 0.0, 1.0),
|
||||
vec4(1.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 1.0, 0.0, 1.0),
|
||||
vec4(1.0, 1.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 1.0, 1.0),
|
||||
vec4(1.0, 0.0, 1.0, 1.0),
|
||||
vec4(0.0, 1.0, 1.0, 1.0),
|
||||
vec4(1.0, 1.0, 1.0, 1.0)
|
||||
);
|
||||
const int UNIT_BOX_LINE_INDICES[24] = int[24](
|
||||
0, 1,
|
||||
1, 3,
|
||||
3, 2,
|
||||
2, 0,
|
||||
4, 5,
|
||||
5, 7,
|
||||
7, 6,
|
||||
6, 4,
|
||||
2, 6,
|
||||
3, 7,
|
||||
0, 4,
|
||||
1, 5
|
||||
);
|
||||
vec4 pos = UNIT_BOX[UNIT_BOX_LINE_INDICES[gl_VertexID]];
|
||||
|
||||
int cellIsEmpty = sign(inCellLocation.w);
|
||||
ivec4 cellLocation = ivec4(inCellLocation.xyz, (inCellLocation.w < 0 ? -inCellLocation.w : inCellLocation.w));
|
||||
vec4 cellBound = evalBound(cellLocation);
|
||||
|
||||
pos.xyz = cellBound.xyz + vec3(cellBound.w) * pos.xyz;
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToClipPos(cam, obj, pos, gl_Position)$>
|
||||
|
||||
varColor = vec4(colorWheel(fract(float(inCellLocation.w) / 5.0)), 0.8 + 0.2 * cellIsEmpty);
|
||||
}
|
|
@ -12,9 +12,18 @@
|
|||
//
|
||||
|
||||
in vec4 varColor;
|
||||
in vec2 varTexcoord;
|
||||
out vec4 outFragColor;
|
||||
|
||||
|
||||
void main(void) {
|
||||
outFragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
float var = step(fract(varTexcoord.x * varTexcoord.y * 1.0), 0.5);
|
||||
|
||||
if (varColor.a == 0) {
|
||||
outFragColor = vec4(mix(vec3(0.0), varColor.xyz, var), mix(0.0, 1.0, var));
|
||||
|
||||
} else {
|
||||
outFragColor = vec4(mix(vec3(1.0), varColor.xyz, var), varColor.a);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,22 +13,28 @@
|
|||
//
|
||||
|
||||
<@include gpu/Transform.slh@>
|
||||
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
<@include gpu/Color.slh@>
|
||||
<$declareColorWheel()$>
|
||||
|
||||
uniform vec3 inBoundPos;
|
||||
uniform vec3 inBoundDim;
|
||||
uniform ivec4 inCellLocation;
|
||||
|
||||
out vec4 varColor;
|
||||
out vec2 varTexcoord;
|
||||
|
||||
void main(void) {
|
||||
const vec4 UNIT_BOX[8] = vec4[8](
|
||||
vec4(0.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 0.0, 0.0),
|
||||
vec4(1.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 1.0, 0.0, 1.0),
|
||||
vec4(1.0, 1.0, 0.0, 1.0),
|
||||
vec4(1.0, 1.0, 0.0, 2.0),
|
||||
vec4(0.0, 0.0, 1.0, 1.0),
|
||||
vec4(1.0, 0.0, 1.0, 1.0),
|
||||
vec4(0.0, 1.0, 1.0, 1.0),
|
||||
vec4(1.0, 1.0, 1.0, 1.0)
|
||||
vec4(1.0, 0.0, 1.0, 2.0),
|
||||
vec4(0.0, 1.0, 1.0, 2.0),
|
||||
vec4(1.0, 1.0, 1.0, 3.0)
|
||||
);
|
||||
const int UNIT_BOX_LINE_INDICES[24] = int[24](
|
||||
0, 1,
|
||||
|
@ -44,14 +50,18 @@ void main(void) {
|
|||
0, 4,
|
||||
1, 5
|
||||
);
|
||||
vec4 pos = UNIT_BOX[UNIT_BOX_LINE_INDICES[gl_VertexID]];
|
||||
vec4 cubeVec = UNIT_BOX[UNIT_BOX_LINE_INDICES[gl_VertexID]];
|
||||
|
||||
pos.xyz = inBoundPos + inBoundDim * pos.xyz;
|
||||
vec4 pos = vec4(inBoundPos + inBoundDim * cubeVec.xyz, 1.0);
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToClipPos(cam, obj, pos, gl_Position)$>
|
||||
|
||||
// varTexcoord = (pos.xy + 1) * 0.5;
|
||||
bool subcell = bool((inCellLocation.z));
|
||||
float cellDepth = float(inCellLocation.w);
|
||||
varColor = vec4(colorWheel(fract(cellDepth / 5.0)), 1.0 - float(subcell));
|
||||
varTexcoord = vec2(cubeVec.w, length(inBoundDim));
|
||||
|
||||
}
|
26
libraries/render/src/render/drawLODReticle.slf
Normal file
26
libraries/render/src/render/drawLODReticle.slf
Normal file
|
@ -0,0 +1,26 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// Draw the LOD reticle used to visualize the current LOD angle
|
||||
//
|
||||
// Created by Sam Gateau on 2/4/16
|
||||
// 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
|
||||
//
|
||||
|
||||
in vec2 varTexCoord0;
|
||||
out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
vec2 circlePos = 2.0 * ( varTexCoord0.xy * 2.0 - vec2(1.0) );
|
||||
float radius = length(circlePos);
|
||||
|
||||
float lodEdge = step(abs(1.0 - radius), 0.05);
|
||||
|
||||
float cellEdge = step(abs(2.0 - radius), 0.05);
|
||||
|
||||
outFragColor = vec4(lodEdge * vec3(1.0, 1.0, 0.0) + cellEdge * vec3(0.0, 1.0, 1.0), lodEdge + 0.5 * cellEdge);
|
||||
}
|
Loading…
Reference in a new issue