Merged with master

This commit is contained in:
Olivier Prat 2017-11-13 14:33:59 +01:00
commit dd6b7b04c2
72 changed files with 2552 additions and 1259 deletions

1
.gitignore vendored
View file

@ -64,6 +64,7 @@ gvr-interface/libs/*
# ignore files for various dev environments
TAGS
*.sw[po]
*.qmlc
# ignore QML compilation output
*.qmlc

View file

@ -5,43 +5,41 @@ set(EXTERNAL_NAME hifiAudioCodec)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (NOT ANDROID)
if (WIN32 OR APPLE)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip
URL_MD5 23ec3fe51eaa155ea159a4971856fc13
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
else ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux.zip
URL_MD5 7d37914a18aa4de971d2f45dd3043bde
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
endif()
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
if (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
elseif(APPLE)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
elseif(NOT ANDROID)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
endif()
if (WIN32)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-win-2.0.zip)
set(DOWNLOAD_MD5 9199d4dbd6b16bed736b235efe980e67)
elseif (APPLE)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-mac-2.0.zip)
set(DOWNLOAD_MD5 21649881e7d5dc94f922179be96f76ba)
elseif (ANDROID)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-android-2.0.zip)
set(DOWNLOAD_MD5 aef2a852600d498d58aa586668191683)
elseif (UNIX)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux-2.0.zip)
set(DOWNLOAD_MD5 67fb7755f9bcafb98a9fceea53bc7481)
else()
return()
endif()
ExternalProject_Add(
${EXTERNAL_NAME}
URL ${DOWNLOAD_URL}
URL_MD5 ${DOWNLOAD_MD5}
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
if (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
else()
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
endif()

View file

@ -11,8 +11,11 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import "../../styles-uit"
import "../../controls-uit"
import "../dialogs"
Rectangle {
id: newModelDialog
@ -25,6 +28,15 @@ Rectangle {
property bool punctuationMode: false
property bool keyboardRasied: false
function errorMessageBox(message) {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
}
Item {
id: column1
anchors.rightMargin: 10
@ -98,7 +110,6 @@ Rectangle {
CheckBox {
id: dynamic
text: qsTr("Dynamic")
}
Row {
@ -117,6 +128,7 @@ Rectangle {
Text {
id: text2
width: 160
x: dynamic.width / 2
color: "#ffffff"
text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors")
wrapMode: Text.WordWrap
@ -139,15 +151,40 @@ Rectangle {
ComboBox {
id: collisionType
property int priorIndex: 0
property string staticMeshCollisionText: "Exact - All polygons"
property var collisionArray: ["No Collision",
"Basic - Whole model",
"Good - Sub-meshes",
staticMeshCollisionText,
"Box",
"Sphere"]
width: 200
z: 100
transformOrigin: Item.Center
model: ["No Collision",
"Basic - Whole model",
"Good - Sub-meshes",
"Exact - All polygons",
"Box",
"Sphere"]
model: collisionArray
onCurrentIndexChanged: {
if (collisionArray[currentIndex] === staticMeshCollisionText) {
if (dynamic.checked) {
currentIndex = priorIndex;
errorMessageBox("Models with Automatic Collisions set to \""
+ staticMeshCollisionText + "\" cannot be dynamic.");
//--EARLY EXIT--( Can't have a static mesh model that's dynamic )
return;
}
dynamic.enabled = false;
} else {
dynamic.enabled = true;
}
priorIndex = currentIndex;
}
}
Row {
@ -155,10 +192,10 @@ Rectangle {
width: 200
height: 400
spacing: 5
anchors {
rightMargin: 15
}
anchors.horizontalCenter: column3.horizontalCenter
anchors.horizontalCenterOffset: -20
Button {
id: button1
text: qsTr("Add")

View file

@ -54,7 +54,9 @@ void LODManager::autoAdjustLOD(float batchTime, float engineRunTime, float delta
float renderTime = batchTime + OVERLAY_AND_SWAP_TIME_BUDGET;
float maxTime = glm::max(renderTime, engineRunTime);
const float BLEND_TIMESCALE = 0.3f; // sec
float blend = BLEND_TIMESCALE / deltaTimeSec;
const float MIN_DELTA_TIME = 0.001f;
const float safeDeltaTime = glm::max(deltaTimeSec, MIN_DELTA_TIME);
float blend = BLEND_TIMESCALE / safeDeltaTime;
if (blend > 1.0f) {
blend = 1.0f;
}

View file

@ -71,6 +71,12 @@ bool SelectionScriptingInterface::removeFromSelectedItemsList(const QString& lis
return false;
}
bool SelectionScriptingInterface::clearSelectedItemsList(const QString& listName) {
_selectedItemsListMap.insert(listName, GameplayObjects());
emit selectedItemsListChanged(listName);
return true;
}
template <class T> bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) {
GameplayObjects currentList = _selectedItemsListMap.value(listName);
currentList.addToGameplayObjects(idToAdd);

View file

@ -56,11 +56,45 @@ public:
GameplayObjects getList(const QString& listName);
/**jsdoc
* Prints out the list of avatars, entities and overlays stored in a particular selection.
* @function Selection.printList
* @param listName {string} name of the selection
*/
Q_INVOKABLE void printList(const QString& listName);
/**jsdoc
* Removes a named selection from the list of selections.
* @function Selection.removeListFromMap
* @param listName {string} name of the selection
* @returns {bool} true if the selection existed and was successfully removed.
*/
Q_INVOKABLE bool removeListFromMap(const QString& listName);
/**jsdoc
* Add an item in a selection.
* @function Selection.addToSelectedItemsList
* @param listName {string} name of the selection
* @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay")
* @param id {EntityID} the Id of the item to add to the selection
* @returns {bool} true if the item was successfully added.
*/
Q_INVOKABLE bool addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id);
/**jsdoc
* Remove an item from a selection.
* @function Selection.removeFromSelectedItemsList
* @param listName {string} name of the selection
* @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay")
* @param id {EntityID} the Id of the item to remove
* @returns {bool} true if the item was successfully removed.
*/
Q_INVOKABLE bool removeFromSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id);
/**jsdoc
* Remove all items from a selection.
* @function Selection.clearSelectedItemsList
* @param listName {string} name of the selection
* @returns {bool} true if the item was successfully cleared.
*/
Q_INVOKABLE bool clearSelectedItemsList(const QString& listName);
signals:
void selectedItemsListChanged(const QString& listName);

View file

@ -38,8 +38,6 @@ ContextOverlayInterface::ContextOverlayInterface() {
_tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
_selectionScriptingInterface = DependencyManager::get<SelectionScriptingInterface>();
_selectionToSceneHandler.initialize("contextOverlayHighlightList");
_entityPropertyFlags += PROP_POSITION;
_entityPropertyFlags += PROP_ROTATION;
_entityPropertyFlags += PROP_MARKETPLACE_ID;
@ -66,12 +64,20 @@ ContextOverlayInterface::ContextOverlayInterface() {
}
});
connect(entityScriptingInterface, &EntityScriptingInterface::deletingEntity, this, &ContextOverlayInterface::deletingEntity);
connect(&qApp->getOverlays(), &Overlays::mousePressOnOverlay, this, &ContextOverlayInterface::contextOverlays_mousePressOnOverlay);
connect(&qApp->getOverlays(), &Overlays::hoverEnterOverlay, this, &ContextOverlayInterface::contextOverlays_hoverEnterOverlay);
connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay);
connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged);
{
render::Transaction transaction;
initializeSelectionToSceneHandler(_selectionToSceneHandlers[0], "contextOverlayHighlightList", transaction);
for (auto i = 1; i < MAX_SELECTION_COUNT; i++) {
auto selectionName = QString("highlightList") + QString::number(i);
initializeSelectionToSceneHandler(_selectionToSceneHandlers[i], selectionName, transaction);
}
const render::ScenePointer& scene = qApp->getMain3DScene();
scene->enqueueTransaction(transaction);
}
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
@ -79,6 +85,12 @@ ContextOverlayInterface::ContextOverlayInterface() {
_challengeOwnershipTimeoutTimer.setSingleShot(true);
}
void ContextOverlayInterface::initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction) {
handler.initialize(selectionName);
connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &handler, &SelectionToSceneHandler::selectedItemsListChanged);
transaction.resetSelectionHighlight(selectionName.toStdString());
}
static const uint32_t MOUSE_HW_ID = 0;
static const uint32_t LEFT_HAND_HW_ID = 1;
static const xColor CONTEXT_OVERLAY_COLOR = { 255, 255, 255 };

View file

@ -47,6 +47,7 @@ class ContextOverlayInterface : public QObject, public Dependency {
OverlayID _contextOverlayID { UNKNOWN_OVERLAY_ID };
std::shared_ptr<Image3DOverlay> _contextOverlay { nullptr };
public:
ContextOverlayInterface();
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
@ -75,6 +76,11 @@ private slots:
void handleChallengeOwnershipReplyPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
private:
enum {
MAX_SELECTION_COUNT = 16
};
bool _verboseLogging { true };
bool _enabled { true };
EntityItemID _currentEntityWithContextOverlay{};
@ -90,8 +96,9 @@ private:
void disableEntityHighlight(const EntityItemID& entityItemID);
void deletingEntity(const EntityItemID& entityItemID);
void initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction);
SelectionToSceneHandler _selectionToSceneHandler;
SelectionToSceneHandler _selectionToSceneHandlers[MAX_SELECTION_COUNT];
Q_INVOKABLE void startChallengeOwnershipTimer();
QTimer _challengeOwnershipTimeoutTimer;

View file

@ -39,7 +39,12 @@ static std::mutex rigRegistryMutex;
static bool isEqual(const glm::vec3& u, const glm::vec3& v) {
const float EPSILON = 0.0001f;
return glm::length(u - v) / glm::length(u) <= EPSILON;
float uLen = glm::length(u);
if (uLen == 0.0f) {
return glm::length(v) <= EPSILON;
} else {
return (glm::length(u - v) / uLen) <= EPSILON;
}
}
static bool isEqual(const glm::quat& p, const glm::quat& q) {

View file

@ -453,7 +453,9 @@ void Avatar::applyPositionDelta(const glm::vec3& delta) {
void Avatar::measureMotionDerivatives(float deltaTime) {
PerformanceTimer perfTimer("derivatives");
// linear
float invDeltaTime = 1.0f / deltaTime;
const float MIN_DELTA_TIME = 0.001f;
const float safeDeltaTime = glm::max(deltaTime, MIN_DELTA_TIME);
float invDeltaTime = 1.0f / safeDeltaTime;
// Floating point error prevents us from computing velocity in a naive way
// (e.g. vel = (pos - oldPos) / dt) so instead we use _positionOffsetAccumulator.
glm::vec3 velocity = _positionDeltaAccumulator * invDeltaTime;

View file

@ -15,21 +15,7 @@
#include <string>
#include <memory>
#include <queue>
/* VS2010 defines stdint.h, but not inttypes.h */
#if defined(_MSC_VER)
typedef signed char int8_t;
typedef signed short int16_t;
typedef signed int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef signed long long int64_t;
typedef unsigned long long quint64;
#define PRId64 "I64d"
#else
#include <inttypes.h>
#endif
#include <vector>
#include <glm/glm.hpp>

View file

@ -110,6 +110,7 @@ static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = {
GL_SHORT,
GL_UNSIGNED_SHORT,
GL_BYTE,
GL_UNSIGNED_BYTE,
GL_UNSIGNED_BYTE
};

View file

@ -212,6 +212,9 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
case gpu::NUINT8:
result = GL_RGBA8;
break;
case gpu::NUINT2:
result = GL_RGBA2;
break;
case gpu::NINT8:
result = GL_RGBA8_SNORM;
break;
@ -498,6 +501,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
break;
}
case gpu::COMPRESSED:
case gpu::NUINT2:
case gpu::NUM_TYPES: { // quiet compiler
Q_UNREACHABLE();
}
@ -548,6 +552,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
break;
}
case gpu::COMPRESSED:
case gpu::NUINT2:
case gpu::NUM_TYPES: { // quiet compiler
Q_UNREACHABLE();
}
@ -660,6 +665,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
texel.format = GL_RGBA;
texel.internalFormat = GL_RGBA8_SNORM;
break;
case gpu::NUINT2:
texel.format = GL_RGBA;
texel.internalFormat = GL_RGBA2;
break;
case gpu::NUINT32:
case gpu::NINT32:
case gpu::COMPRESSED:

View file

@ -19,6 +19,8 @@ const Element Element::COLOR_SRGBA_32{ VEC4, NUINT8, SRGBA };
const Element Element::COLOR_BGRA_32{ VEC4, NUINT8, BGRA };
const Element Element::COLOR_SBGRA_32{ VEC4, NUINT8, SBGRA };
const Element Element::COLOR_RGBA_2{ VEC4, NUINT2, RGBA };
const Element Element::COLOR_COMPRESSED_RED{ TILE4x4, COMPRESSED, COMPRESSED_BC4_RED };
const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGB };
const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA };

View file

@ -38,6 +38,7 @@ enum Type : uint8_t {
NUINT16,
NINT8,
NUINT8,
NUINT2,
COMPRESSED,
@ -309,6 +310,7 @@ public:
static const Element COLOR_SRGBA_32;
static const Element COLOR_BGRA_32;
static const Element COLOR_SBGRA_32;
static const Element COLOR_RGBA_2;
static const Element COLOR_R11G11B10;
static const Element COLOR_RGB9E5;
static const Element COLOR_COMPRESSED_RED;

View file

@ -220,7 +220,7 @@ uint32 Framebuffer::getRenderBufferSubresource(uint32 slot) const {
}
}
bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) {
bool Framebuffer::assignDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) {
if (isSwapchain()) {
return false;
}
@ -244,20 +244,59 @@ bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const For
// assign the new one
_depthStencilBuffer = TextureView(texture, subresource, format);
_bufferMask = ( _bufferMask & ~BUFFER_DEPTHSTENCIL);
if (texture) {
if (format.getSemantic() == gpu::DEPTH) {
_bufferMask |= BUFFER_DEPTH;
} else if (format.getSemantic() == gpu::STENCIL) {
_bufferMask |= BUFFER_STENCIL;
} else if (format.getSemantic() == gpu::DEPTH_STENCIL) {
_bufferMask |= BUFFER_DEPTHSTENCIL;
}
}
return true;
}
bool Framebuffer::setDepthBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) {
if (assignDepthStencilBuffer(texture, format, subresource)) {
_bufferMask = (_bufferMask & ~BUFFER_DEPTHSTENCIL);
if (texture) {
if (format.getSemantic() == gpu::DEPTH || format.getSemantic() == gpu::DEPTH_STENCIL) {
_bufferMask |= BUFFER_DEPTH;
} else {
return false;
}
}
return true;
}
return false;
}
bool Framebuffer::setStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) {
if (assignDepthStencilBuffer(texture, format, subresource)) {
_bufferMask = (_bufferMask & ~BUFFER_DEPTHSTENCIL);
if (texture) {
if (format.getSemantic() == gpu::STENCIL || format.getSemantic() == gpu::DEPTH_STENCIL) {
_bufferMask |= BUFFER_STENCIL;
} else {
return false;
}
}
return true;
}
return false;
}
bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) {
if (assignDepthStencilBuffer(texture, format, subresource)) {
_bufferMask = (_bufferMask & ~BUFFER_DEPTHSTENCIL);
if (texture) {
if (format.getSemantic() == gpu::DEPTH) {
_bufferMask |= BUFFER_DEPTH;
} else if (format.getSemantic() == gpu::STENCIL) {
_bufferMask |= BUFFER_STENCIL;
} else if (format.getSemantic() == gpu::DEPTH_STENCIL) {
_bufferMask |= BUFFER_DEPTHSTENCIL;
}
}
return true;
}
return false;
}
TexturePointer Framebuffer::getDepthStencilBuffer() const {
if (isSwapchain()) {
return TexturePointer();

View file

@ -107,6 +107,8 @@ public:
TexturePointer getRenderBuffer(uint32 slot) const;
uint32 getRenderBufferSubresource(uint32 slot) const;
bool setDepthBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0);
bool setStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0);
bool setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0);
TexturePointer getDepthStencilBuffer() const;
uint32 getDepthStencilBufferSubresource() const;
@ -168,6 +170,7 @@ protected:
uint16 _numSamples = 0;
void updateSize(const TexturePointer& texture);
bool assignDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource);
// Non exposed
Framebuffer(const Framebuffer& framebuffer) = delete;

View file

@ -193,13 +193,17 @@ TransformObject getTransformObject() {
}
<@endfunc@>
<@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@>
{ // transformModelToClipPos
<@func transformModelToMonoClipPos(cameraTransform, objectTransform, modelPos, clipPos)@>
{ // transformModelToMonoClipPos
vec4 eyeWAPos;
<$transformModelToEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos)$>
<$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos;
}
<@endfunc@>
<@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@>
{ // transformModelToClipPos
<$transformModelToMonoClipPos($cameraTransform$, $objectTransform$, $modelPos$, $clipPos$)$>
<$transformStereoClipsSpace($cameraTransform$, $clipPos$)$>
}
<@endfunc@>

View file

@ -12,21 +12,7 @@
#ifndef hifi_OctreeQuery_h
#define hifi_OctreeQuery_h
/* VS2010 defines stdint.h, but not inttypes.h */
#if defined(_MSC_VER)
typedef signed char int8_t;
typedef signed short int16_t;
typedef signed int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef signed long long int64_t;
typedef unsigned long long quint64;
#define PRId64 "I64d"
#else
#include <inttypes.h>
#endif
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>

View file

@ -16,6 +16,40 @@
#include "ShapeFactory.h"
#include "BulletUtil.h"
class StaticMeshShape : public btBvhTriangleMeshShape {
public:
StaticMeshShape() = delete;
StaticMeshShape(btTriangleIndexVertexArray* dataArray)
: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) {
assert(_dataArray);
}
~StaticMeshShape() {
assert(_dataArray);
IndexedMeshArray& meshes = _dataArray->getIndexedMeshArray();
for (int32_t i = 0; i < meshes.size(); ++i) {
btIndexedMesh mesh = meshes[i];
mesh.m_numTriangles = 0;
delete [] mesh.m_triangleIndexBase;
mesh.m_triangleIndexBase = nullptr;
mesh.m_numVertices = 0;
delete [] mesh.m_vertexBase;
mesh.m_vertexBase = nullptr;
}
meshes.clear();
delete _dataArray;
_dataArray = nullptr;
}
private:
// the StaticMeshShape owns its vertex/index data
btTriangleIndexVertexArray* _dataArray;
};
// the dataArray must be created before we create the StaticMeshShape
// These are the same normalized directions used by the btShapeHull class.
// 12 points for the face centers of a dodecahedron plus another 30 points
// for the midpoints the edges, for a total of 42.
@ -230,23 +264,6 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) {
return dataArray;
}
// util method
void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) {
assert(dataArray);
IndexedMeshArray& meshes = dataArray->getIndexedMeshArray();
for (int32_t i = 0; i < meshes.size(); ++i) {
btIndexedMesh mesh = meshes[i];
mesh.m_numTriangles = 0;
delete [] mesh.m_triangleIndexBase;
mesh.m_triangleIndexBase = nullptr;
mesh.m_numVertices = 0;
delete [] mesh.m_vertexBase;
mesh.m_vertexBase = nullptr;
}
meshes.clear();
delete dataArray;
}
const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
btCollisionShape* shape = NULL;
int type = info.getType();
@ -431,7 +448,6 @@ void ShapeFactory::deleteShape(const btCollisionShape* shape) {
assert(shape);
// ShapeFactory is responsible for deleting all shapes, even the const ones that are stored
// in the ShapeManager, so we must cast to non-const here when deleting.
// so we cast to non-const here when deleting memory.
btCollisionShape* nonConstShape = const_cast<btCollisionShape*>(shape);
if (nonConstShape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(nonConstShape);
@ -448,14 +464,3 @@ void ShapeFactory::deleteShape(const btCollisionShape* shape) {
}
delete nonConstShape;
}
// the dataArray must be created before we create the StaticMeshShape
ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray)
: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) {
assert(dataArray);
}
ShapeFactory::StaticMeshShape::~StaticMeshShape() {
deleteStaticMeshArray(_dataArray);
_dataArray = nullptr;
}

View file

@ -17,25 +17,11 @@
#include <ShapeInfo.h>
// translates between ShapeInfo and btShape
// The ShapeFactory assembles and correctly disassembles btCollisionShapes.
namespace ShapeFactory {
const btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
void deleteShape(const btCollisionShape* shape);
//btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info);
//void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray);
class StaticMeshShape : public btBvhTriangleMeshShape {
public:
StaticMeshShape() = delete;
StaticMeshShape(btTriangleIndexVertexArray* dataArray);
~StaticMeshShape();
private:
// the StaticMeshShape owns its vertex/index data
btTriangleIndexVertexArray* _dataArray;
};
};
#endif // hifi_ShapeFactory_h

View file

@ -32,7 +32,7 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
if (info.getType() == SHAPE_TYPE_NONE) {
return nullptr;
}
DoubleHashKey key = info.getHash();
HashKey key = info.getHash();
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
shapeRef->refCount++;
@ -50,7 +50,7 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
}
// private helper method
bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) {
bool ShapeManager::releaseShapeByKey(const HashKey& key) {
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
if (shapeRef->refCount > 0) {
@ -88,7 +88,7 @@ bool ShapeManager::releaseShape(const btCollisionShape* shape) {
void ShapeManager::collectGarbage() {
int numShapes = _pendingGarbage.size();
for (int i = 0; i < numShapes; ++i) {
DoubleHashKey& key = _pendingGarbage[i];
HashKey& key = _pendingGarbage[i];
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef && shapeRef->refCount == 0) {
ShapeFactory::deleteShape(shapeRef->shape);
@ -99,7 +99,7 @@ void ShapeManager::collectGarbage() {
}
int ShapeManager::getNumReferences(const ShapeInfo& info) const {
DoubleHashKey key = info.getHash();
HashKey key = info.getHash();
const ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
return shapeRef->refCount;

View file

@ -17,7 +17,29 @@
#include <ShapeInfo.h>
#include "DoubleHashKey.h"
#include "HashKey.h"
// The ShapeManager handles the ref-counting on shared shapes:
//
// Each object added to the physics simulation gets a corresponding btRigidBody.
// The body has a btCollisionShape that represents the contours of its collision
// surface. Multiple bodies may have the same shape. Rather than create a unique
// btCollisionShape instance for every body with a particular shape we can instead
// use a single shape instance for all of the bodies. This is called "shape
// sharing".
//
// When body needs a new shape a description of ths shape (ShapeInfo) is assembled
// and a request is sent to the ShapeManager for a corresponding btCollisionShape
// pointer. The ShapeManager will compute a hash of the ShapeInfo's data and use
// that to find the shape in its map. If it finds one it increments the ref-count
// and returns the pointer. If not it asks the ShapeFactory to create it, adds an
// entry in the map with a ref-count of 1, and returns the pointer.
//
// When a body stops using a shape the ShapeManager must be informed so it can
// decrement its ref-count. When a ref-count drops to zero the ShapeManager
// doesn't delete it right away. Instead it puts the shape's key on a list delete
// later. When that list grows big enough the ShapeManager will remove any matching
// entries that still have zero ref-count.
class ShapeManager {
public:
@ -41,18 +63,19 @@ public:
bool hasShape(const btCollisionShape* shape) const;
private:
bool releaseShapeByKey(const DoubleHashKey& key);
bool releaseShapeByKey(const HashKey& key);
class ShapeReference {
public:
int refCount;
const btCollisionShape* shape;
DoubleHashKey key;
HashKey key;
ShapeReference() : refCount(0), shape(nullptr) {}
};
btHashMap<DoubleHashKey, ShapeReference> _shapeMap;
btAlignedObjectArray<DoubleHashKey> _pendingGarbage;
// btHashMap is required because it supports memory alignment of the btCollisionShapes
btHashMap<HashKey, ShapeReference> _shapeMap;
btAlignedObjectArray<HashKey> _pendingGarbage;
};
#endif // hifi_ShapeManager_h

View file

@ -14,6 +14,7 @@
#include <gpu/Context.h>
std::string BackgroundStage::_stageName { "BACKGROUND_STAGE"};
const BackgroundStage::Index BackgroundStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX };
BackgroundStage::Index BackgroundStage::findBackground(const BackgroundPointer& background) const {
auto found = _backgroundMap.find(background);

View file

@ -27,7 +27,7 @@ public:
static const std::string& getName() { return _stageName; }
using Index = render::indexed_container::Index;
static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX };
static const Index INVALID_INDEX;
static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; }
using BackgroundPointer = model::SunSkyStagePointer;

View file

@ -14,6 +14,7 @@
#include <gpu/Context.h>
std::string HazeStage::_stageName { "HAZE_STAGE"};
const HazeStage::Index HazeStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX };
FetchHazeStage::FetchHazeStage() {
_haze = std::make_shared<model::Haze>();

View file

@ -28,7 +28,7 @@ public:
static const std::string& getName() { return _stageName; }
using Index = render::indexed_container::Index;
static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX };
static const Index INVALID_INDEX;
static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; }
using HazePointer = model::HazePointer;

View file

@ -1,5 +1,5 @@
// Outline.slf
// Add outline effect based on two zbuffers : one containing the total scene z and another
// Highlight.slf
// Add highlight effect based on two zbuffers : one containing the total scene z and another
// with the z of only the objects to be outlined.
// This is the version without the fill effect inside the silhouette.
//
@ -9,5 +9,5 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include Outline.slh@>
<@include Highlight.slh@>
<$main(0)$>

View file

@ -1,7 +1,7 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
<!
// Outline.slh
// Highlight.slh
// fragment shader
//
// Created by Olivier Prat on 9/7/17.
@ -13,14 +13,14 @@
<@include DeferredTransform.slh@>
<$declareDeferredFrameTransform()$>
<@include Outline_shared.slh@>
<@include Highlight_shared.slh@>
uniform outlineParamsBuffer {
OutlineParameters params;
uniform highlightParamsBuffer {
HighlightParameters params;
};
uniform sampler2D sceneDepthMap;
uniform sampler2D outlinedDepthMap;
uniform sampler2D highlightedDepthMap;
in vec2 varTexCoord0;
out vec4 outFragColor;
@ -35,30 +35,26 @@ void main(void) {
// We offset by half a texel to be centered on the depth sample. If we don't do this
// the blur will have a different width between the left / right sides and top / bottom
// sides of the silhouette
vec2 halfTexel = getInvWidthHeight() / 2;
vec2 texCoord0 = varTexCoord0+halfTexel;
float outlinedDepth = texture(outlinedDepthMap, texCoord0).x;
float highlightedDepth = texture(highlightedDepthMap, varTexCoord0).x;
float intensity = 0.0;
if (outlinedDepth < FAR_Z) {
// We're not on the far plane so we are on the outlined object, thus no outline to do!
if (highlightedDepth < FAR_Z) {
// We're not on the far plane so we are on the highlighted object, thus no outline to do!
<@if IS_FILLED@>
// But we need to fill the interior
float sceneDepth = texture(sceneDepthMap, texCoord0).x;
float sceneDepth = texture(sceneDepthMap, varTexCoord0).x;
// Transform to linear depth for better precision
outlinedDepth = -evalZeyeFromZdb(outlinedDepth);
highlightedDepth = -evalZeyeFromZdb(highlightedDepth);
sceneDepth = -evalZeyeFromZdb(sceneDepth);
// Are we occluded?
if (sceneDepth < (outlinedDepth-LINEAR_DEPTH_BIAS)) {
intensity = params._fillOpacityOccluded;
} else {
intensity = params._fillOpacityUnoccluded;
}
intensity = sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS) ? params._occludedFillOpacity : params._unoccludedFillOpacity;
<@else@>
discard;
<@endif@>
} else {
vec2 halfTexel = getInvWidthHeight() / 2;
vec2 texCoord0 = varTexCoord0+halfTexel;
float weight = 0.0;
vec2 deltaUv = params._size / params._blurKernelSize;
vec2 lineStartUv = texCoord0 - params._size / 2.0;
@ -74,9 +70,9 @@ void main(void) {
for (x=0 ; x<params._blurKernelSize ; x++) {
if (uv.x>=0.0 && uv.x<=1.0)
{
outlinedDepth = texture(outlinedDepthMap, uv).x;
intensity += (outlinedDepth < FAR_Z) ? 1.0 : 0.0;
weight += 1.f;
highlightedDepth = texture(highlightedDepthMap, uv).x;
intensity += (highlightedDepth < FAR_Z) ? 1.0 : 0.0;
weight += 1.0;
}
uv.x += deltaUv.x;
}

View file

@ -0,0 +1,562 @@
//
// HighlightEffect.cpp
// render-utils/src/
//
// Created by Olivier Prat on 08/08/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "HighlightEffect.h"
#include "GeometryCache.h"
#include "CubeProjectedPolygon.h"
#include <render/FilterTask.h>
#include <render/SortTask.h>
#include "gpu/Context.h"
#include "gpu/StandardShaderLib.h"
#include <sstream>
#include "surfaceGeometry_copyDepth_frag.h"
#include "debug_deferred_buffer_vert.h"
#include "debug_deferred_buffer_frag.h"
#include "Highlight_frag.h"
#include "Highlight_filled_frag.h"
#include "Highlight_aabox_vert.h"
#include "nop_frag.h"
using namespace render;
#define OUTLINE_STENCIL_MASK 1
HighlightRessources::HighlightRessources() {
}
void HighlightRessources::update(const gpu::FramebufferPointer& primaryFrameBuffer) {
auto newFrameSize = glm::ivec2(primaryFrameBuffer->getSize());
// If the buffer size changed, we need to delete our FBOs and recreate them at the
// new correct dimensions.
if (_frameSize != newFrameSize) {
_frameSize = newFrameSize;
allocateDepthBuffer(primaryFrameBuffer);
allocateColorBuffer(primaryFrameBuffer);
} else {
if (!_depthFrameBuffer) {
allocateDepthBuffer(primaryFrameBuffer);
}
if (!_colorFrameBuffer) {
allocateColorBuffer(primaryFrameBuffer);
}
}
}
void HighlightRessources::allocateColorBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) {
_colorFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithStencil"));
_colorFrameBuffer->setRenderBuffer(0, primaryFrameBuffer->getRenderBuffer(0));
_colorFrameBuffer->setStencilBuffer(_depthStencilTexture, _depthStencilTexture->getTexelFormat());
}
void HighlightRessources::allocateDepthBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) {
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL);
_depthStencilTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, _frameSize.x, _frameSize.y));
_depthFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("highlightDepth"));
_depthFrameBuffer->setDepthStencilBuffer(_depthStencilTexture, depthFormat);
}
gpu::FramebufferPointer HighlightRessources::getDepthFramebuffer() {
assert(_depthFrameBuffer);
return _depthFrameBuffer;
}
gpu::TexturePointer HighlightRessources::getDepthTexture() {
return _depthStencilTexture;
}
gpu::FramebufferPointer HighlightRessources::getColorFramebuffer() {
assert(_colorFrameBuffer);
return _colorFrameBuffer;
}
HighlightSharedParameters::HighlightSharedParameters() {
_highlightIds.fill(render::HighlightStage::INVALID_INDEX);
}
float HighlightSharedParameters::getBlurPixelWidth(const render::HighlightStyle& style, int frameBufferHeight) {
return ceilf(style.outlineWidth * frameBufferHeight / 400.0f);
}
PrepareDrawHighlight::PrepareDrawHighlight() {
_ressources = std::make_shared<HighlightRessources>();
}
void PrepareDrawHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
auto destinationFrameBuffer = inputs;
_ressources->update(destinationFrameBuffer);
outputs = _ressources;
}
gpu::PipelinePointer DrawHighlightMask::_stencilMaskPipeline;
gpu::PipelinePointer DrawHighlightMask::_stencilMaskFillPipeline;
DrawHighlightMask::DrawHighlightMask(unsigned int highlightIndex,
render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters) :
_highlightPassIndex{ highlightIndex },
_shapePlumber { shapePlumber },
_sharedParameters{ parameters } {
}
void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
auto& inShapes = inputs.get0();
if (!_stencilMaskPipeline || !_stencilMaskFillPipeline) {
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_ZERO, gpu::State::STENCIL_OP_REPLACE));
state->setColorWriteMask(false, false, false, false);
state->setCullMode(gpu::State::CULL_FRONT);
gpu::StatePointer fillState = gpu::StatePointer(new gpu::State());
fillState->setDepthTest(false, false, gpu::LESS_EQUAL);
fillState->setStencilTest(true, 0xFF, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE));
fillState->setColorWriteMask(false, false, false, false);
fillState->setCullMode(gpu::State::CULL_FRONT);
auto vs = gpu::Shader::createVertex(std::string(Highlight_aabox_vert));
auto ps = gpu::Shader::createPixel(std::string(nop_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*program, slotBindings);
_stencilMaskPipeline = gpu::Pipeline::create(program, state);
_stencilMaskFillPipeline = gpu::Pipeline::create(program, fillState);
}
if (!_boundsBuffer) {
_boundsBuffer = std::make_shared<gpu::Buffer>(sizeof(render::ItemBound));
}
auto highlightStage = renderContext->_scene->getStage<render::HighlightStage>(render::HighlightStage::getName());
auto highlightId = _sharedParameters->_highlightIds[_highlightPassIndex];
if (!inShapes.empty() && !render::HighlightStage::isIndexInvalid(highlightId)) {
auto ressources = inputs.get1();
auto& highlight = highlightStage->getHighlight(highlightId);
RenderArgs* args = renderContext->args;
ShapeKey::Builder defaultKeyBuilder;
// Render full screen
outputs = args->_viewport;
// 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
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setFramebuffer(ressources->getDepthFramebuffer());
batch.clearDepthStencilFramebuffer(1.0f, 0);
});
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
render::ItemBounds itemBounds;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch;
auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder);
auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned());
// Setup camera, projection and viewport for all items
batch.setViewportTransform(args->_viewport);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
std::vector<ShapeKey> skinnedShapeKeys{};
// Iterate through all inShapes and render the unskinned
args->_shapePipeline = maskPipeline;
batch.setPipeline(maskPipeline->pipeline);
for (const auto& items : inShapes) {
itemBounds.insert(itemBounds.end(), items.second.begin(), items.second.end());
if (items.first.isSkinned()) {
skinnedShapeKeys.push_back(items.first);
} else {
renderItems(renderContext, items.second);
}
}
// Reiterate to render the skinned
args->_shapePipeline = maskSkinnedPipeline;
batch.setPipeline(maskSkinnedPipeline->pipeline);
for (const auto& key : skinnedShapeKeys) {
renderItems(renderContext, inShapes.at(key));
}
args->_shapePipeline = nullptr;
args->_batch = nullptr;
});
_boundsBuffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data());
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
// Setup camera, projection and viewport for all items
batch.setViewportTransform(args->_viewport);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
// Draw stencil mask with object bounding boxes
const auto highlightWidthLoc = _stencilMaskPipeline->getProgram()->getUniforms().findLocation("outlineWidth");
const auto securityMargin = 2.0f;
const float blurPixelWidth = 2.0f * securityMargin * HighlightSharedParameters::getBlurPixelWidth(highlight._style, args->_viewport.w);
const auto framebufferSize = ressources->getSourceFrameSize();
auto stencilPipeline = highlight._style.isFilled() ? _stencilMaskFillPipeline : _stencilMaskPipeline;
batch.setPipeline(stencilPipeline);
batch.setResourceBuffer(0, _boundsBuffer);
batch._glUniform2f(highlightWidthLoc, blurPixelWidth / framebufferSize.x, blurPixelWidth / framebufferSize.y);
static const int NUM_VERTICES_PER_CUBE = 36;
batch.draw(gpu::TRIANGLES, NUM_VERTICES_PER_CUBE * (gpu::uint32) itemBounds.size(), 0);
});
} else {
// Highlight rect should be null as there are no highlighted shapes
outputs = glm::ivec4(0, 0, 0, 0);
}
}
gpu::PipelinePointer DrawHighlight::_pipeline;
gpu::PipelinePointer DrawHighlight::_pipelineFilled;
DrawHighlight::DrawHighlight(unsigned int highlightIndex, HighlightSharedParametersPointer parameters) :
_highlightPassIndex{ highlightIndex },
_sharedParameters{ parameters } {
}
void DrawHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) {
auto highlightFrameBuffer = inputs.get1();
auto highlightRect = inputs.get3();
if (highlightFrameBuffer && highlightRect.z>0 && highlightRect.w>0) {
auto sceneDepthBuffer = inputs.get2();
const auto frameTransform = inputs.get0();
auto highlightedDepthTexture = highlightFrameBuffer->getDepthTexture();
auto destinationFrameBuffer = highlightFrameBuffer->getColorFramebuffer();
auto framebufferSize = glm::ivec2(highlightedDepthTexture->getDimensions());
if (sceneDepthBuffer) {
auto args = renderContext->args;
auto highlightStage = renderContext->_scene->getStage<render::HighlightStage>(render::HighlightStage::getName());
auto highlightId = _sharedParameters->_highlightIds[_highlightPassIndex];
if (!render::HighlightStage::isIndexInvalid(highlightId)) {
auto& highlight = highlightStage->getHighlight(highlightId);
auto pipeline = getPipeline(highlight._style);
{
auto& shaderParameters = _configuration.edit();
shaderParameters._color = highlight._style.color;
shaderParameters._intensity = highlight._style.outlineIntensity * (highlight._style.isOutlineSmooth ? 2.0f : 1.0f);
shaderParameters._unoccludedFillOpacity = highlight._style.unoccludedFillOpacity;
shaderParameters._occludedFillOpacity = highlight._style.occludedFillOpacity;
shaderParameters._threshold = highlight._style.isOutlineSmooth ? 1.0f : 1e-3f;
shaderParameters._blurKernelSize = std::min(7, std::max(2, (int)floorf(highlight._style.outlineWidth * 3 + 0.5f)));
// Size is in normalized screen height. We decide that for highlight width = 1, this is equal to 1/400.
auto size = highlight._style.outlineWidth / 400.0f;
shaderParameters._size.x = (size * framebufferSize.y) / framebufferSize.x;
shaderParameters._size.y = size;
}
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setFramebuffer(destinationFrameBuffer);
batch.setViewportTransform(args->_viewport);
batch.setProjectionTransform(glm::mat4());
batch.resetViewTransform();
batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport));
batch.setPipeline(pipeline);
batch.setUniformBuffer(HIGHLIGHT_PARAMS_SLOT, _configuration);
batch.setUniformBuffer(FRAME_TRANSFORM_SLOT, frameTransform->getFrameTransformBuffer());
batch.setResourceTexture(SCENE_DEPTH_MAP_SLOT, sceneDepthBuffer->getPrimaryDepthTexture());
batch.setResourceTexture(HIGHLIGHTED_DEPTH_MAP_SLOT, highlightedDepthTexture);
batch.draw(gpu::TRIANGLE_STRIP, 4);
});
}
}
}
}
const gpu::PipelinePointer& DrawHighlight::getPipeline(const render::HighlightStyle& style) {
if (!_pipeline) {
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setDepthTest(gpu::State::DepthTest(false, false));
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL));
auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS();
auto ps = gpu::Shader::createPixel(std::string(Highlight_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding("highlightParamsBuffer", HIGHLIGHT_PARAMS_SLOT));
slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", FRAME_TRANSFORM_SLOT));
slotBindings.insert(gpu::Shader::Binding("sceneDepthMap", SCENE_DEPTH_MAP_SLOT));
slotBindings.insert(gpu::Shader::Binding("highlightedDepthMap", HIGHLIGHTED_DEPTH_MAP_SLOT));
gpu::Shader::makeProgram(*program, slotBindings);
_pipeline = gpu::Pipeline::create(program, state);
ps = gpu::Shader::createPixel(std::string(Highlight_filled_frag));
program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::makeProgram(*program, slotBindings);
_pipelineFilled = gpu::Pipeline::create(program, state);
}
return style.isFilled() ? _pipelineFilled : _pipeline;
}
DebugHighlight::DebugHighlight() {
_geometryDepthId = DependencyManager::get<GeometryCache>()->allocateID();
}
DebugHighlight::~DebugHighlight() {
auto geometryCache = DependencyManager::get<GeometryCache>();
if (geometryCache) {
geometryCache->releaseID(_geometryDepthId);
}
}
void DebugHighlight::configure(const Config& config) {
_isDisplayEnabled = config.viewMask;
}
void DebugHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& input) {
const auto highlightRessources = input.get0();
const auto highlightRect = input.get1();
if (_isDisplayEnabled && highlightRessources && highlightRect.z>0 && highlightRect.w>0) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.setViewportTransform(args->_viewport);
batch.setFramebuffer(highlightRessources->getColorFramebuffer());
const auto geometryBuffer = DependencyManager::get<GeometryCache>();
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat, true);
batch.setModelTransform(Transform());
const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f);
batch.setPipeline(getDepthPipeline());
batch.setResourceTexture(0, highlightRessources->getDepthTexture());
const glm::vec2 bottomLeft(-1.0f, -1.0f);
const glm::vec2 topRight(1.0f, 1.0f);
geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryDepthId);
batch.setResourceTexture(0, nullptr);
});
}
}
void DebugHighlight::initializePipelines() {
static const std::string VERTEX_SHADER{ debug_deferred_buffer_vert };
static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag };
static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" };
static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER);
Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO,
"Could not find source placeholder");
auto state = std::make_shared<gpu::State>();
state->setDepthTest(gpu::State::DepthTest(false, false));
state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL));
const auto vs = gpu::Shader::createVertex(VERTEX_SHADER);
// Depth shader
{
static const std::string DEPTH_SHADER{
"vec4 getFragmentColor() {"
" float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x;"
" Zdb = 1.0-(1.0-Zdb)*100;"
" return vec4(Zdb, Zdb, Zdb, 1.0); "
"}"
};
auto fragmentShader = FRAGMENT_SHADER;
fragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), DEPTH_SHADER);
const auto ps = gpu::Shader::createPixel(fragmentShader);
const auto program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding("depthMap", 0));
gpu::Shader::makeProgram(*program, slotBindings);
_depthPipeline = gpu::Pipeline::create(program, state);
}
}
const gpu::PipelinePointer& DebugHighlight::getDepthPipeline() {
if (!_depthPipeline) {
initializePipelines();
}
return _depthPipeline;
}
void SelectionToHighlight::run(const render::RenderContextPointer& renderContext, Outputs& outputs) {
auto scene = renderContext->_scene;
auto highlightStage = scene->getStage<render::HighlightStage>(render::HighlightStage::getName());
outputs.clear();
_sharedParameters->_highlightIds.fill(render::HighlightStage::INVALID_INDEX);
for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) {
std::ostringstream stream;
if (i > 0) {
stream << "highlightList" << i;
} else {
stream << "contextOverlayHighlightList";
}
auto selectionName = stream.str();
if (!scene->isSelectionEmpty(selectionName)) {
auto highlightId = highlightStage->getHighlightIdBySelection(selectionName);
if (!render::HighlightStage::isIndexInvalid(highlightId)) {
_sharedParameters->_highlightIds[outputs.size()] = highlightId;
outputs.emplace_back(selectionName);
}
}
}
}
void ExtractSelectionName::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
if (_highlightPassIndex < inputs.size()) {
outputs = inputs[_highlightPassIndex];
} else {
outputs.clear();
}
}
DrawHighlightTask::DrawHighlightTask() {
}
void DrawHighlightTask::configure(const Config& config) {
}
void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) {
const auto items = inputs.getN<Inputs>(0).get<RenderFetchCullSortTask::BucketList>();
const auto sceneFrameBuffer = inputs.getN<Inputs>(1);
const auto primaryFramebuffer = inputs.getN<Inputs>(2);
const auto deferredFrameTransform = inputs.getN<Inputs>(3);
// Prepare the ShapePipeline
auto shapePlumber = std::make_shared<ShapePlumber>();
{
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setColorWriteMask(false, false, false, false);
initMaskPipelines(*shapePlumber, state);
}
auto sharedParameters = std::make_shared<HighlightSharedParameters>();
const auto highlightSelectionNames = task.addJob<SelectionToHighlight>("SelectionToHighlight", sharedParameters);
// Prepare for highlight group rendering.
const auto highlightRessources = task.addJob<PrepareDrawHighlight>("PrepareHighlight", primaryFramebuffer);
render::Varying highlight0Rect;
for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) {
const auto selectionName = task.addJob<ExtractSelectionName>("ExtractSelectionName", highlightSelectionNames, i);
const auto groupItems = addSelectItemJobs(task, selectionName, items);
const auto highlightedItemIDs = task.addJob<render::MetaToSubItems>("HighlightMetaToSubItemIDs", groupItems);
const auto highlightedItems = task.addJob<render::IDsToBounds>("HighlightMetaToSubItems", highlightedItemIDs);
// Sort
const auto sortedPipelines = task.addJob<render::PipelineSortShapes>("HighlightPipelineSort", highlightedItems);
const auto sortedBounds = task.addJob<render::DepthSortShapes>("HighlightDepthSort", sortedPipelines);
// Draw depth of highlighted objects in separate buffer
std::string name;
{
std::ostringstream stream;
stream << "HighlightMask" << i;
name = stream.str();
}
const auto drawMaskInputs = DrawHighlightMask::Inputs(sortedBounds, highlightRessources).asVarying();
const auto highlightedRect = task.addJob<DrawHighlightMask>(name, drawMaskInputs, i, shapePlumber, sharedParameters);
if (i == 0) {
highlight0Rect = highlightedRect;
}
// Draw highlight
{
std::ostringstream stream;
stream << "HighlightEffect" << i;
name = stream.str();
}
const auto drawHighlightInputs = DrawHighlight::Inputs(deferredFrameTransform, highlightRessources, sceneFrameBuffer, highlightedRect).asVarying();
task.addJob<DrawHighlight>(name, drawHighlightInputs, i, sharedParameters);
}
// Debug highlight
const auto debugInputs = DebugHighlight::Inputs(highlightRessources, const_cast<const render::Varying&>(highlight0Rect)).asVarying();
task.addJob<DebugHighlight>("HighlightDebug", debugInputs);
}
const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const render::Varying& selectionName,
const RenderFetchCullSortTask::BucketList& items) {
const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE];
const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE];
const auto& metas = items[RenderFetchCullSortTask::META];
const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), selectionName).asVarying();
const auto selectedMetas = task.addJob<SelectItems>("MetaSelection", selectMetaInput);
const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas, selectionName).asVarying();
const auto selectedMetasAndOpaques = task.addJob<SelectItems>("OpaqueSelection", selectMetaAndOpaqueInput);
const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, selectionName).asVarying();
return task.addJob<SelectItems>("TransparentSelection", selectItemInput);
}
#include "model_shadow_vert.h"
#include "skin_model_shadow_vert.h"
#include "model_shadow_frag.h"
void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) {
auto modelVertex = gpu::Shader::createVertex(std::string(model_shadow_vert));
auto modelPixel = gpu::Shader::createPixel(std::string(model_shadow_frag));
gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel);
shapePlumber.addPipeline(
ShapeKey::Filter::Builder().withoutSkinned(),
modelProgram, state);
auto skinVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert));
gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, modelPixel);
shapePlumber.addPipeline(
ShapeKey::Filter::Builder().withSkinned(),
skinProgram, state);
}

View file

@ -0,0 +1,226 @@
//
// HighlightEffect.h
// render-utils/src/
//
// Created by Olivier Prat on 08/08/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_render_utils_HighlightEffect_h
#define hifi_render_utils_HighlightEffect_h
#include <render/Engine.h>
#include <render/HighlightStage.h>
#include <render/RenderFetchCullSortTask.h>
#include "DeferredFramebuffer.h"
#include "DeferredFrameTransform.h"
class HighlightRessources {
public:
HighlightRessources();
gpu::FramebufferPointer getDepthFramebuffer();
gpu::TexturePointer getDepthTexture();
gpu::FramebufferPointer getColorFramebuffer();
// Update the source framebuffer size which will drive the allocation of all the other resources.
void update(const gpu::FramebufferPointer& primaryFrameBuffer);
const glm::ivec2& getSourceFrameSize() const { return _frameSize; }
protected:
gpu::FramebufferPointer _depthFrameBuffer;
gpu::FramebufferPointer _colorFrameBuffer;
gpu::TexturePointer _depthStencilTexture;
glm::ivec2 _frameSize;
void allocateColorBuffer(const gpu::FramebufferPointer& primaryFrameBuffer);
void allocateDepthBuffer(const gpu::FramebufferPointer& primaryFrameBuffer);
};
using HighlightRessourcesPointer = std::shared_ptr<HighlightRessources>;
class HighlightSharedParameters {
public:
enum {
MAX_PASS_COUNT = 8
};
HighlightSharedParameters();
std::array<render::HighlightStage::Index, MAX_PASS_COUNT> _highlightIds;
static float getBlurPixelWidth(const render::HighlightStyle& style, int frameBufferHeight);
};
using HighlightSharedParametersPointer = std::shared_ptr<HighlightSharedParameters>;
class PrepareDrawHighlight {
public:
using Inputs = gpu::FramebufferPointer;
using Outputs = HighlightRessourcesPointer;
using JobModel = render::Job::ModelIO<PrepareDrawHighlight, Inputs, Outputs>;
PrepareDrawHighlight();
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs);
private:
HighlightRessourcesPointer _ressources;
};
class SelectionToHighlight {
public:
using Outputs = std::vector<std::string>;
using JobModel = render::Job::ModelO<SelectionToHighlight, Outputs>;
SelectionToHighlight(HighlightSharedParametersPointer parameters) : _sharedParameters{ parameters } {}
void run(const render::RenderContextPointer& renderContext, Outputs& outputs);
private:
HighlightSharedParametersPointer _sharedParameters;
};
class ExtractSelectionName {
public:
using Inputs = SelectionToHighlight::Outputs;
using Outputs = std::string;
using JobModel = render::Job::ModelIO<ExtractSelectionName, Inputs, Outputs>;
ExtractSelectionName(unsigned int highlightIndex) : _highlightPassIndex{ highlightIndex } {}
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs);
private:
unsigned int _highlightPassIndex;
};
class DrawHighlightMask {
public:
using Inputs = render::VaryingSet2<render::ShapeBounds, HighlightRessourcesPointer>;
using Outputs = glm::ivec4;
using JobModel = render::Job::ModelIO<DrawHighlightMask, Inputs, Outputs>;
DrawHighlightMask(unsigned int highlightIndex, render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters);
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs);
protected:
unsigned int _highlightPassIndex;
render::ShapePlumberPointer _shapePlumber;
HighlightSharedParametersPointer _sharedParameters;
gpu::BufferPointer _boundsBuffer;
static gpu::PipelinePointer _stencilMaskPipeline;
static gpu::PipelinePointer _stencilMaskFillPipeline;
};
class DrawHighlight {
public:
using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, HighlightRessourcesPointer, DeferredFramebufferPointer, glm::ivec4>;
using Config = render::Job::Config;
using JobModel = render::Job::ModelI<DrawHighlight, Inputs, Config>;
DrawHighlight(unsigned int highlightIndex, HighlightSharedParametersPointer parameters);
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
private:
#include "Highlight_shared.slh"
enum {
SCENE_DEPTH_MAP_SLOT = 0,
HIGHLIGHTED_DEPTH_MAP_SLOT,
HIGHLIGHT_PARAMS_SLOT = 0,
FRAME_TRANSFORM_SLOT,
};
using HighlightConfigurationBuffer = gpu::StructBuffer<HighlightParameters>;
static const gpu::PipelinePointer& getPipeline(const render::HighlightStyle& style);
static gpu::PipelinePointer _pipeline;
static gpu::PipelinePointer _pipelineFilled;
unsigned int _highlightPassIndex;
HighlightParameters _parameters;
HighlightSharedParametersPointer _sharedParameters;
HighlightConfigurationBuffer _configuration;
};
class DebugHighlightConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(bool viewMask MEMBER viewMask NOTIFY dirty)
public:
bool viewMask{ false };
signals:
void dirty();
};
class DebugHighlight {
public:
using Inputs = render::VaryingSet2<HighlightRessourcesPointer, glm::ivec4>;
using Config = DebugHighlightConfig;
using JobModel = render::Job::ModelI<DebugHighlight, Inputs, Config>;
DebugHighlight();
~DebugHighlight();
void configure(const Config& config);
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
private:
gpu::PipelinePointer _depthPipeline;
int _geometryDepthId{ 0 };
bool _isDisplayEnabled{ false };
const gpu::PipelinePointer& getDepthPipeline();
void initializePipelines();
};
class DrawHighlightTask {
public:
using Inputs = render::VaryingSet4<RenderFetchCullSortTask::BucketList, DeferredFramebufferPointer, gpu::FramebufferPointer, DeferredFrameTransformPointer>;
using Config = render::Task::Config;
using JobModel = render::Task::ModelI<DrawHighlightTask, Inputs, Config>;
DrawHighlightTask();
void configure(const Config& config);
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs);
private:
static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state);
static const render::Varying addSelectItemJobs(JobModel& task, const render::Varying& selectionName, const RenderFetchCullSortTask::BucketList& items);
};
#endif // hifi_render_utils_HighlightEffect_h

View file

@ -0,0 +1,104 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// Draw and transform the fed vertex position with the standard MVP stack
// and offset the vertices by a certain amount in the vertex direction
//
// Created by Olivier Prat on 11/02/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include gpu/Transform.slh@>
<$declareStandardTransform()$>
struct ItemBound {
vec4 id_boundPos;
vec4 boundDim_s;
};
#if defined(GPU_GL410)
uniform samplerBuffer ssbo0Buffer;
ItemBound getItemBound(int i) {
int offset = 2 * i;
ItemBound bound;
bound.id_boundPos = texelFetch(ssbo0Buffer, offset);
bound.boundDim_s = texelFetch(ssbo0Buffer, offset + 1);
return bound;
}
#else
layout(std140) buffer ssbo0Buffer {
ItemBound bounds[];
};
ItemBound getItemBound(int i) {
ItemBound bound = bounds[i];
return bound;
}
#endif
uniform vec2 outlineWidth;
void main(void) {
const vec3 UNIT_BOX_VERTICES[8] = vec3[8](
vec3(0.0, 1.0, 0.0),
vec3(1.0, 1.0, 0.0),
vec3(1.0, 0.0, 0.0),
vec3(0.0, 0.0, 0.0),
vec3(0.0, 1.0, 1.0),
vec3(1.0, 1.0, 1.0),
vec3(1.0, 0.0, 1.0),
vec3(0.0, 0.0, 1.0)
);
const vec3 UNIT_BOX_NORMALS[8] = vec3[8](
vec3(-1.0, 1.0, -1.0),
vec3(1.0, 1.0, -1.0),
vec3(1.0, -1.0, -1.0),
vec3(-1.0, -1.0, -1.0),
vec3(-1.0, 1.0, 1.0),
vec3(1.0, 1.0, 1.0),
vec3(1.0, -1.0, 1.0),
vec3(-1.0, -1.0, 1.0)
);
const int NUM_VERTICES_PER_CUBE = 36;
const int UNIT_BOX_TRIANGLE_INDICES[NUM_VERTICES_PER_CUBE] = int[NUM_VERTICES_PER_CUBE](
0, 1, 2,
0, 2, 3,
3, 2, 6,
3, 6, 7,
7, 6, 5,
7, 5, 4,
4, 5, 1,
4, 1, 0,
1, 5, 6,
1, 6, 2,
4, 0, 3,
4, 3, 7
);
int boundID = gl_VertexID / NUM_VERTICES_PER_CUBE;
int vertexID = gl_VertexID - boundID * NUM_VERTICES_PER_CUBE;
int triangleIndex = UNIT_BOX_TRIANGLE_INDICES[vertexID];
vec3 cubeVec = UNIT_BOX_VERTICES[triangleIndex];
ItemBound bound = getItemBound(boundID);
vec3 boundPos = bound.id_boundPos.yzw;
vec3 boundDim = bound.boundDim_s.xyz;
vec4 pos = vec4(boundPos + boundDim * cubeVec.xyz, 1.0);
// standard transform
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
<$transformModelToMonoClipPos(cam, obj, pos, gl_Position)$>
// Offset the vertex to take into account the outline width
pos.xyz += UNIT_BOX_NORMALS[triangleIndex];
vec4 offsetPosition;
<$transformModelToMonoClipPos(cam, obj, pos, offsetPosition)$>
gl_Position.xy += normalize(offsetPosition.xy-gl_Position.xy) * outlineWidth * gl_Position.w;
<$transformStereoClipsSpace(cam, gl_Position)$>
}

View file

@ -1,5 +1,5 @@
// Outline_filled.slf
// Add outline effect based on two zbuffers : one containing the total scene z and another
// Highlight_filled.slf
// Add highlight effect based on two zbuffers : one containing the total scene z and another
// with the z of only the objects to be outlined.
// This is the version with the fill effect inside the silhouette.
//
@ -9,5 +9,5 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include Outline.slh@>
<@include Highlight.slh@>
<$main(1)$>

View file

@ -0,0 +1,30 @@
// glsl / C++ compatible source as interface for highlight
#ifdef __cplusplus
# define TVEC2 glm::vec2
# define TVEC3 glm::vec3
# define TVEC4 glm::vec4
#else
# define TVEC2 vec2
# define TVEC3 vec3
# define TVEC4 vec4
#endif
struct HighlightParameters
{
TVEC3 _color;
float _intensity;
TVEC2 _size;
float _unoccludedFillOpacity;
float _occludedFillOpacity;
float _threshold;
int _blurKernelSize;
float padding2;
float padding3;
};
// <@if 1@>
// Trigger Scribe include
// <@endif@> <!def that !>
//

View file

@ -14,6 +14,7 @@
#include "LightStage.h"
std::string LightStage::_stageName { "LIGHT_STAGE"};
const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX };
LightStage::LightStage() {
}

View file

@ -32,7 +32,7 @@ public:
static const std::string& getName() { return _stageName; }
using Index = render::indexed_container::Index;
static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX };
static const Index INVALID_INDEX;
static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; }
using LightPointer = model::LightPointer;

View file

@ -1,371 +0,0 @@
//
// OutlineEffect.cpp
// render-utils/src/
//
// Created by Olivier Prat on 08/08/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "OutlineEffect.h"
#include "GeometryCache.h"
#include <render/FilterTask.h>
#include <render/SortTask.h>
#include "gpu/Context.h"
#include "gpu/StandardShaderLib.h"
#include "surfaceGeometry_copyDepth_frag.h"
#include "debug_deferred_buffer_vert.h"
#include "debug_deferred_buffer_frag.h"
#include "Outline_frag.h"
#include "Outline_filled_frag.h"
using namespace render;
extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state);
OutlineFramebuffer::OutlineFramebuffer() {
}
void OutlineFramebuffer::update(const gpu::TexturePointer& colorBuffer) {
// If the depth buffer or size changed, we need to delete our FBOs and recreate them at the
// new correct dimensions.
if (_depthTexture) {
auto newFrameSize = glm::ivec2(colorBuffer->getDimensions());
if (_frameSize != newFrameSize) {
_frameSize = newFrameSize;
clear();
}
}
}
void OutlineFramebuffer::clear() {
_depthFramebuffer.reset();
_depthTexture.reset();
}
void OutlineFramebuffer::allocate() {
auto width = _frameSize.x;
auto height = _frameSize.y;
auto format = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH);
_depthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(format, width, height));
_depthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("outlineDepth"));
_depthFramebuffer->setDepthStencilBuffer(_depthTexture, format);
}
gpu::FramebufferPointer OutlineFramebuffer::getDepthFramebuffer() {
if (!_depthFramebuffer) {
allocate();
}
return _depthFramebuffer;
}
gpu::TexturePointer OutlineFramebuffer::getDepthTexture() {
if (!_depthTexture) {
allocate();
}
return _depthTexture;
}
void DrawOutlineDepth::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
auto& inShapes = inputs.get0();
auto& deferredFrameBuffer = inputs.get1();
if (!inShapes.empty()) {
RenderArgs* args = renderContext->args;
ShapeKey::Builder defaultKeyBuilder;
if (!_outlineFramebuffer) {
_outlineFramebuffer = std::make_shared<OutlineFramebuffer>();
}
_outlineFramebuffer->update(deferredFrameBuffer->getDeferredColorTexture());
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch;
batch.setFramebuffer(_outlineFramebuffer->getDepthFramebuffer());
// Clear it
batch.clearFramebuffer(
gpu::Framebuffer::BUFFER_DEPTH,
vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, false);
// Setup camera, projection and viewport for all items
batch.setViewportTransform(args->_viewport);
batch.setStateScissorRect(args->_viewport);
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
auto shadowPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder);
auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned());
std::vector<ShapeKey> skinnedShapeKeys{};
// Iterate through all inShapes and render the unskinned
args->_shapePipeline = shadowPipeline;
batch.setPipeline(shadowPipeline->pipeline);
for (auto items : inShapes) {
if (items.first.isSkinned()) {
skinnedShapeKeys.push_back(items.first);
}
else {
renderItems(renderContext, items.second);
}
}
// Reiterate to render the skinned
args->_shapePipeline = shadowSkinnedPipeline;
batch.setPipeline(shadowSkinnedPipeline->pipeline);
for (const auto& key : skinnedShapeKeys) {
renderItems(renderContext, inShapes.at(key));
}
args->_shapePipeline = nullptr;
args->_batch = nullptr;
});
output = _outlineFramebuffer;
} else {
output = nullptr;
}
}
DrawOutline::DrawOutline() {
}
void DrawOutline::configure(const Config& config) {
_color = config.color;
_blurKernelSize = std::min(10, std::max(2, (int)floorf(config.width*2 + 0.5f)));
// Size is in normalized screen height. We decide that for outline width = 1, this is equal to 1/400.
_size = config.width / 400.f;
_fillOpacityUnoccluded = config.fillOpacityUnoccluded;
_fillOpacityOccluded = config.fillOpacityOccluded;
_threshold = config.glow ? 1.f : 1e-3f;
_intensity = config.intensity * (config.glow ? 2.f : 1.f);
}
void DrawOutline::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) {
auto outlineFrameBuffer = inputs.get1();
if (outlineFrameBuffer) {
auto sceneDepthBuffer = inputs.get2();
const auto frameTransform = inputs.get0();
auto outlinedDepthTexture = outlineFrameBuffer->getDepthTexture();
auto destinationFrameBuffer = inputs.get3();
auto framebufferSize = glm::ivec2(outlinedDepthTexture->getDimensions());
if (!_primaryWithoutDepthBuffer || framebufferSize!=_frameBufferSize) {
// Failing to recreate this frame buffer when the screen has been resized creates a bug on Mac
_primaryWithoutDepthBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithoutDepth"));
_primaryWithoutDepthBuffer->setRenderBuffer(0, destinationFrameBuffer->getRenderBuffer(0));
_frameBufferSize = framebufferSize;
}
if (sceneDepthBuffer) {
const auto OPACITY_EPSILON = 5e-3f;
auto pipeline = getPipeline(_fillOpacityUnoccluded>OPACITY_EPSILON || _fillOpacityOccluded>OPACITY_EPSILON);
auto args = renderContext->args;
{
auto& configuration = _configuration.edit();
configuration._color = _color;
configuration._intensity = _intensity;
configuration._fillOpacityUnoccluded = _fillOpacityUnoccluded;
configuration._fillOpacityOccluded = _fillOpacityOccluded;
configuration._threshold = _threshold;
configuration._blurKernelSize = _blurKernelSize;
configuration._size.x = _size * _frameBufferSize.y / _frameBufferSize.x;
configuration._size.y = _size;
}
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setFramebuffer(_primaryWithoutDepthBuffer);
batch.setViewportTransform(args->_viewport);
batch.setProjectionTransform(glm::mat4());
batch.resetViewTransform();
batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_frameBufferSize, args->_viewport));
batch.setPipeline(pipeline);
batch.setUniformBuffer(OUTLINE_PARAMS_SLOT, _configuration);
batch.setUniformBuffer(FRAME_TRANSFORM_SLOT, frameTransform->getFrameTransformBuffer());
batch.setResourceTexture(SCENE_DEPTH_SLOT, sceneDepthBuffer->getPrimaryDepthTexture());
batch.setResourceTexture(OUTLINED_DEPTH_SLOT, outlinedDepthTexture);
batch.draw(gpu::TRIANGLE_STRIP, 4);
// Restore previous frame buffer
batch.setFramebuffer(destinationFrameBuffer);
});
}
}
}
const gpu::PipelinePointer& DrawOutline::getPipeline(bool isFilled) {
if (!_pipeline) {
auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS();
auto ps = gpu::Shader::createPixel(std::string(Outline_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding("outlineParamsBuffer", OUTLINE_PARAMS_SLOT));
slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", FRAME_TRANSFORM_SLOT));
slotBindings.insert(gpu::Shader::Binding("sceneDepthMap", SCENE_DEPTH_SLOT));
slotBindings.insert(gpu::Shader::Binding("outlinedDepthMap", OUTLINED_DEPTH_SLOT));
gpu::Shader::makeProgram(*program, slotBindings);
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setDepthTest(gpu::State::DepthTest(false, false));
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
_pipeline = gpu::Pipeline::create(program, state);
ps = gpu::Shader::createPixel(std::string(Outline_filled_frag));
program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::makeProgram(*program, slotBindings);
_pipelineFilled = gpu::Pipeline::create(program, state);
}
return isFilled ? _pipelineFilled : _pipeline;
}
DebugOutline::DebugOutline() {
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
}
DebugOutline::~DebugOutline() {
auto geometryCache = DependencyManager::get<GeometryCache>();
if (geometryCache) {
geometryCache->releaseID(_geometryId);
}
}
void DebugOutline::configure(const Config& config) {
_isDisplayDepthEnabled = config.viewOutlinedDepth;
}
void DebugOutline::run(const render::RenderContextPointer& renderContext, const Inputs& input) {
const auto outlineFramebuffer = input;
if (_isDisplayDepthEnabled && outlineFramebuffer) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setViewportTransform(args->_viewport);
const auto geometryBuffer = DependencyManager::get<GeometryCache>();
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat, true);
batch.setModelTransform(Transform());
batch.setPipeline(getDebugPipeline());
batch.setResourceTexture(0, outlineFramebuffer->getDepthTexture());
const glm::vec4 color(1.0f, 0.5f, 0.2f, 1.0f);
const glm::vec2 bottomLeft(-1.0f, -1.0f);
const glm::vec2 topRight(1.0f, 1.0f);
geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryId);
batch.setResourceTexture(0, nullptr);
});
}
}
const gpu::PipelinePointer& DebugOutline::getDebugPipeline() {
if (!_debugPipeline) {
static const std::string VERTEX_SHADER{ debug_deferred_buffer_vert };
static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag };
static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" };
static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER);
Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO,
"Could not find source placeholder");
static const std::string DEFAULT_DEPTH_SHADER{
"vec4 getFragmentColor() {"
" float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x;"
" Zdb = 1.0-(1.0-Zdb)*100;"
" return vec4(Zdb, Zdb, Zdb, 1.0);"
" }"
};
auto bakedFragmentShader = FRAGMENT_SHADER;
bakedFragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), DEFAULT_DEPTH_SHADER);
static const auto vs = gpu::Shader::createVertex(VERTEX_SHADER);
const auto ps = gpu::Shader::createPixel(bakedFragmentShader);
const auto program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding("depthMap", 0));
gpu::Shader::makeProgram(*program, slotBindings);
auto state = std::make_shared<gpu::State>();
state->setDepthTest(gpu::State::DepthTest(false));
_debugPipeline = gpu::Pipeline::create(program, state);
}
return _debugPipeline;
}
DrawOutlineTask::DrawOutlineTask() {
}
void DrawOutlineTask::configure(const Config& config) {
}
void DrawOutlineTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) {
const auto input = inputs.get<Inputs>();
const auto selectedMetas = inputs.getN<Inputs>(0);
const auto shapePlumber = input.get1();
const auto sceneFrameBuffer = inputs.getN<Inputs>(2);
const auto primaryFramebuffer = inputs.getN<Inputs>(3);
const auto deferredFrameTransform = inputs.getN<Inputs>(4);
// Prepare the ShapePipeline
ShapePlumberPointer shapePlumberZPass = std::make_shared<ShapePlumber>();
{
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setColorWriteMask(false, false, false, false);
initZPassPipelines(*shapePlumberZPass, state);
}
const auto outlinedItemIDs = task.addJob<render::MetaToSubItems>("OutlineMetaToSubItemIDs", selectedMetas);
const auto outlinedItems = task.addJob<render::IDsToBounds>("OutlineMetaToSubItems", outlinedItemIDs, true);
// Sort
const auto sortedPipelines = task.addJob<render::PipelineSortShapes>("OutlinePipelineSort", outlinedItems);
const auto sortedShapes = task.addJob<render::DepthSortShapes>("OutlineDepthSort", sortedPipelines);
// Draw depth of outlined objects in separate buffer
const auto drawOutlineDepthInputs = DrawOutlineDepth::Inputs(sortedShapes, sceneFrameBuffer).asVarying();
const auto outlinedFrameBuffer = task.addJob<DrawOutlineDepth>("OutlineDepth", drawOutlineDepthInputs, shapePlumberZPass);
// Draw outline
const auto drawOutlineInputs = DrawOutline::Inputs(deferredFrameTransform, outlinedFrameBuffer, sceneFrameBuffer, primaryFramebuffer).asVarying();
task.addJob<DrawOutline>("OutlineEffect", drawOutlineInputs);
// Debug outline
task.addJob<DebugOutline>("OutlineDebug", outlinedFrameBuffer);
}

View file

@ -1,183 +0,0 @@
//
// OutlineEffect.h
// render-utils/src/
//
// Created by Olivier Prat on 08/08/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_render_utils_OutlineEffect_h
#define hifi_render_utils_OutlineEffect_h
#include <render/Engine.h>
#include "DeferredFramebuffer.h"
#include "DeferredFrameTransform.h"
class OutlineFramebuffer {
public:
OutlineFramebuffer();
gpu::FramebufferPointer getDepthFramebuffer();
gpu::TexturePointer getDepthTexture();
// Update the source framebuffer size which will drive the allocation of all the other resources.
void update(const gpu::TexturePointer& colorBuffer);
const glm::ivec2& getSourceFrameSize() const { return _frameSize; }
protected:
void clear();
void allocate();
gpu::FramebufferPointer _depthFramebuffer;
gpu::TexturePointer _depthTexture;
glm::ivec2 _frameSize;
};
using OutlineFramebufferPointer = std::shared_ptr<OutlineFramebuffer>;
class DrawOutlineDepth {
public:
using Inputs = render::VaryingSet2<render::ShapeBounds, DeferredFramebufferPointer>;
// Output will contain outlined objects only z-depth texture and the input primary buffer but without the primary depth buffer
using Outputs = OutlineFramebufferPointer;
using JobModel = render::Job::ModelIO<DrawOutlineDepth, Inputs, Outputs>;
DrawOutlineDepth(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {}
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output);
protected:
render::ShapePlumberPointer _shapePlumber;
OutlineFramebufferPointer _outlineFramebuffer;
};
class DrawOutlineConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(bool glow MEMBER glow NOTIFY dirty)
Q_PROPERTY(float width MEMBER width NOTIFY dirty)
Q_PROPERTY(float intensity MEMBER intensity NOTIFY dirty)
Q_PROPERTY(float colorR READ getColorR WRITE setColorR NOTIFY dirty)
Q_PROPERTY(float colorG READ getColorG WRITE setColorG NOTIFY dirty)
Q_PROPERTY(float colorB READ getColorB WRITE setColorB NOTIFY dirty)
Q_PROPERTY(float fillOpacityUnoccluded MEMBER fillOpacityUnoccluded NOTIFY dirty)
Q_PROPERTY(float fillOpacityOccluded MEMBER fillOpacityOccluded NOTIFY dirty)
public:
void setColorR(float value) { color.r = value; emit dirty(); }
float getColorR() const { return color.r; }
void setColorG(float value) { color.g = value; emit dirty(); }
float getColorG() const { return color.g; }
void setColorB(float value) { color.b = value; emit dirty(); }
float getColorB() const { return color.b; }
glm::vec3 color{ 1.f, 0.7f, 0.2f };
float width{ 2.0f };
float intensity{ 0.9f };
float fillOpacityUnoccluded{ 0.0f };
float fillOpacityOccluded{ 0.0f };
bool glow{ false };
signals:
void dirty();
};
class DrawOutline {
public:
using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, OutlineFramebufferPointer, DeferredFramebufferPointer, gpu::FramebufferPointer>;
using Config = DrawOutlineConfig;
using JobModel = render::Job::ModelI<DrawOutline, Inputs, Config>;
DrawOutline();
void configure(const Config& config);
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
private:
enum {
SCENE_DEPTH_SLOT = 0,
OUTLINED_DEPTH_SLOT,
OUTLINE_PARAMS_SLOT = 0,
FRAME_TRANSFORM_SLOT
};
#include "Outline_shared.slh"
using OutlineConfigurationBuffer = gpu::StructBuffer<OutlineParameters>;
const gpu::PipelinePointer& getPipeline(bool isFilled);
gpu::FramebufferPointer _primaryWithoutDepthBuffer;
glm::ivec2 _frameBufferSize {0, 0};
gpu::PipelinePointer _pipeline;
gpu::PipelinePointer _pipelineFilled;
OutlineConfigurationBuffer _configuration;
glm::vec3 _color;
float _size;
int _blurKernelSize;
float _intensity;
float _fillOpacityUnoccluded;
float _fillOpacityOccluded;
float _threshold;
};
class DebugOutlineConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(bool viewOutlinedDepth MEMBER viewOutlinedDepth NOTIFY dirty)
public:
bool viewOutlinedDepth{ false };
signals:
void dirty();
};
class DebugOutline {
public:
using Inputs = OutlineFramebufferPointer;
using Config = DebugOutlineConfig;
using JobModel = render::Job::ModelI<DebugOutline, Inputs, Config>;
DebugOutline();
~DebugOutline();
void configure(const Config& config);
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
private:
const gpu::PipelinePointer& getDebugPipeline();
gpu::PipelinePointer _debugPipeline;
int _geometryId{ 0 };
bool _isDisplayDepthEnabled{ false };
};
class DrawOutlineTask {
public:
using Inputs = render::VaryingSet5<render::ItemBounds, render::ShapePlumberPointer, DeferredFramebufferPointer, gpu::FramebufferPointer, DeferredFrameTransformPointer>;
using Config = render::Task::Config;
using JobModel = render::Task::ModelI<DrawOutlineTask, Inputs, Config>;
DrawOutlineTask();
void configure(const Config& config);
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs);
};
#endif // hifi_render_utils_OutlineEffect_h

View file

@ -1,28 +0,0 @@
// glsl / C++ compatible source as interface for Outline
#ifdef __cplusplus
# define VEC2 glm::vec2
# define VEC3 glm::vec3
#else
# define VEC2 vec2
# define VEC3 vec3
#endif
struct OutlineParameters
{
VEC3 _color;
float _intensity;
VEC2 _size;
float _fillOpacityUnoccluded;
float _fillOpacityOccluded;
float _threshold;
int _blurKernelSize;
float padding2;
float padding3;
};
// <@if 1@>
// Trigger Scribe include
// <@endif@> <!def that !>
//

View file

@ -35,16 +35,18 @@
#include "TextureCache.h"
#include "ZoneRenderer.h"
#include "FadeEffect.h"
#include "RenderUtilsLogging.h"
#include "AmbientOcclusionEffect.h"
#include "AntialiasingEffect.h"
#include "ToneMappingEffect.h"
#include "SubsurfaceScattering.h"
#include "DrawHaze.h"
#include "OutlineEffect.h"
#include "HighlightEffect.h"
#include <gpu/StandardShaderLib.h>
#include <sstream>
using namespace render;
extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false);
@ -58,6 +60,18 @@ void RenderDeferredTask::configure(const Config& config)
{
}
const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, const char* selectionName,
const render::Varying& metas,
const render::Varying& opaques,
const render::Varying& transparents) {
const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), std::string()).asVarying();
const auto selectedMetas = task.addJob<SelectItems>("MetaSelection", selectMetaInput, selectionName);
const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas, std::string()).asVarying();
const auto selectedMetasAndOpaques = task.addJob<SelectItems>("OpaqueSelection", selectMetaAndOpaqueInput, selectionName);
const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, std::string()).asVarying();
return task.addJob<SelectItems>("TransparentSelection", selectItemInput, selectionName);
}
void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) {
const auto& items = input.get<Input>();
auto fadeEffect = DependencyManager::get<FadeEffect>();
@ -95,15 +109,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
// draw a stencil mask in hidden regions of the framebuffer.
task.addJob<PrepareStencil>("PrepareStencil", primaryFramebuffer);
// Select items that need to be outlined
const auto selectionName = "contextOverlayHighlightList";
const auto selectMetaInput = SelectItems::Inputs(metas, Varying()).asVarying();
const auto selectedMetas = task.addJob<SelectItems>("PassTestMetaSelection", selectMetaInput, selectionName);
const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas).asVarying();
const auto selectedMetasAndOpaques = task.addJob<SelectItems>("PassTestOpaqueSelection", selectMetaAndOpaqueInput, selectionName);
const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques).asVarying();
const auto selectedItems = task.addJob<SelectItems>("PassTestTransparentSelection", selectItemInput, selectionName);
// Render opaque objects in DeferredBuffer
const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).asVarying();
task.addJob<DrawStateSortDeferred>("DrawOpaqueDeferred", opaqueInputs, shapePlumber);
@ -178,10 +183,15 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer).asVarying();
task.addJob<ToneMappingDeferred>("ToneMapping", toneMappingInputs);
const auto outlineRangeTimer = task.addJob<BeginGPURangeTimer>("BeginOutlineRangeTimer", "Outline");
const auto outlineInputs = DrawOutlineTask::Inputs(selectedItems, shapePlumber, deferredFramebuffer, primaryFramebuffer, deferredFrameTransform).asVarying();
task.addJob<DrawOutlineTask>("DrawOutline", outlineInputs);
task.addJob<EndGPURangeTimer>("EndOutlineRangeTimer", outlineRangeTimer);
const auto outlineRangeTimer = task.addJob<BeginGPURangeTimer>("BeginHighlightRangeTimer", "Highlight");
// Select items that need to be outlined
const auto selectionBaseName = "contextOverlayHighlightList";
const auto selectedItems = addSelectItemJobs(task, selectionBaseName, metas, opaques, transparents);
const auto outlineInputs = DrawHighlightTask::Inputs(items.get0(), deferredFramebuffer, primaryFramebuffer, deferredFrameTransform).asVarying();
task.addJob<DrawHighlightTask>("DrawHighlight", outlineInputs);
task.addJob<EndGPURangeTimer>("HighlightRangeTimer", outlineRangeTimer);
{ // DEbug the bounds of the rendered items, still look at the zbuffer
task.addJob<DrawBounds>("DrawMetaBounds", metas);
@ -191,6 +201,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
task.addJob<DrawBounds>("DrawLightBounds", lights);
task.addJob<DrawBounds>("DrawZones", zones);
task.addJob<DrawFrustums>("DrawFrustums");
// Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true
task.addJob<DrawBounds>("DrawSelectionBounds", selectedItems);
}
// Layered Overlays
@ -237,9 +250,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
}
task.addJob<DebugZoneLighting>("DrawZoneStack", deferredFrameTransform);
// Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true
task.addJob<DrawBounds>("DrawSelectionBounds", selectedItems);
}
// AA job to be revisited
@ -455,6 +465,7 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer
auto blitFbo = renderArgs->_blitFramebuffer;
if (!blitFbo) {
qCWarning(renderutils) << "Blit::run - no blit frame buffer.";
return;
}

View file

@ -232,6 +232,10 @@ public:
void configure(const Config& config);
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs);
private:
static const render::Varying addSelectItemJobs(JobModel& task, const char* selectionName,
const render::Varying& metas, const render::Varying& opaques, const render::Varying& transparents);
};
#endif // hifi_RenderDeferredTask_h

View file

@ -15,6 +15,7 @@
#include "BackgroundStage.h"
#include "HazeStage.h"
#include <render/TransitionStage.h>
#include <render/HighlightStage.h>
#include "DeferredLightingEffect.h"
void UpdateSceneTask::build(JobModel& task, const render::Varying& input, render::Varying& output) {
@ -22,6 +23,7 @@ void UpdateSceneTask::build(JobModel& task, const render::Varying& input, render
task.addJob<BackgroundStageSetup>("BackgroundStageSetup");
task.addJob<HazeStageSetup>("HazeStageSetup");
task.addJob<render::TransitionStageSetup>("TransitionStageSetup");
task.addJob<render::HighlightStageSetup>("HighlightStageSetup");
task.addJob<DefaultLightingSetup>("DefaultLightingSetup");

View file

@ -57,7 +57,12 @@ void SliceItems::run(const RenderContextPointer& renderContext, const ItemBounds
}
void SelectItems::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) {
auto selection = renderContext->_scene->getSelection(_name);
auto selectionName{ _name };
if (!inputs.get2().empty()) {
selectionName = inputs.get2();
}
auto selection = renderContext->_scene->getSelection(selectionName);
const auto& selectedItems = selection.getItems();
const auto& inItems = inputs.get0();
const auto itemsToAppend = inputs[1];
@ -118,8 +123,9 @@ void MetaToSubItems::run(const RenderContextPointer& renderContext, const ItemBo
for (auto idBound : inItems) {
auto& item = scene->getItem(idBound.id);
item.fetchMetaSubItems(outItems);
if (item.exist()) {
item.fetchMetaSubItems(outItems);
}
}
}
@ -132,8 +138,9 @@ void IDsToBounds::run(const RenderContextPointer& renderContext, const ItemIDs&
if (!_disableAABBs) {
for (auto id : inItems) {
auto& item = scene->getItem(id);
outItems.emplace_back(ItemBound{ id, item.getBound() });
if (item.exist()) {
outItems.emplace_back(ItemBound{ id, item.getBound() });
}
}
} else {
for (auto id : inItems) {

View file

@ -113,12 +113,13 @@ namespace render {
// Keep items belonging to the job selection
class SelectItems {
public:
using Inputs = VaryingSet2<ItemBounds, ItemBounds>;
using Inputs = VaryingSet3<ItemBounds, ItemBounds, std::string>;
using JobModel = Job::ModelIO<SelectItems, Inputs, ItemBounds>;
std::string _name;
SelectItems() {}
SelectItems(const Selection::Name& name) : _name(name) {}
void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems);
};

View file

@ -0,0 +1,130 @@
#include "HighlightStage.h"
using namespace render;
std::string HighlightStage::_name("Highlight");
const HighlightStage::Index HighlightStage::INVALID_INDEX{ render::indexed_container::INVALID_INDEX };
HighlightStage::Index HighlightStage::addHighlight(const std::string& selectionName, const HighlightStyle& style) {
Highlight outline{ selectionName, style };
Index id;
id = _highlights.newElement(outline);
_activeHighlightIds.push_back(id);
return id;
}
void HighlightStage::removeHighlight(Index index) {
HighlightIdList::iterator idIterator = std::find(_activeHighlightIds.begin(), _activeHighlightIds.end(), index);
if (idIterator != _activeHighlightIds.end()) {
_activeHighlightIds.erase(idIterator);
}
if (!_highlights.isElementFreed(index)) {
_highlights.freeElement(index);
}
}
Index HighlightStage::getHighlightIdBySelection(const std::string& selectionName) const {
for (auto outlineId : _activeHighlightIds) {
const auto& outline = _highlights.get(outlineId);
if (outline._selectionName == selectionName) {
return outlineId;
}
}
return INVALID_INDEX;
}
const HighlightStyle& HighlightStageConfig::getStyle() const {
auto styleIterator = _styles.find(_selectionName);
if (styleIterator != _styles.end()) {
return styleIterator->second;
} else {
auto insertion = _styles.insert(SelectionStyles::value_type{ _selectionName, HighlightStyle{} });
return insertion.first->second;
}
}
HighlightStyle& HighlightStageConfig::editStyle() {
auto styleIterator = _styles.find(_selectionName);
if (styleIterator != _styles.end()) {
return styleIterator->second;
} else {
auto insertion = _styles.insert(SelectionStyles::value_type{ _selectionName, HighlightStyle{} });
return insertion.first->second;
}
}
void HighlightStageConfig::setSelectionName(const QString& name) {
_selectionName = name.toStdString();
emit dirty();
}
void HighlightStageConfig::setOutlineSmooth(bool isSmooth) {
editStyle().isOutlineSmooth = isSmooth;
emit dirty();
}
void HighlightStageConfig::setColorRed(float value) {
editStyle().color.r = value;
emit dirty();
}
void HighlightStageConfig::setColorGreen(float value) {
editStyle().color.g = value;
emit dirty();
}
void HighlightStageConfig::setColorBlue(float value) {
editStyle().color.b = value;
emit dirty();
}
void HighlightStageConfig::setOutlineWidth(float value) {
editStyle().outlineWidth = value;
emit dirty();
}
void HighlightStageConfig::setOutlineIntensity(float value) {
editStyle().outlineIntensity = value;
emit dirty();
}
void HighlightStageConfig::setUnoccludedFillOpacity(float value) {
editStyle().unoccludedFillOpacity = value;
emit dirty();
}
void HighlightStageConfig::setOccludedFillOpacity(float value) {
editStyle().occludedFillOpacity = value;
emit dirty();
}
HighlightStageSetup::HighlightStageSetup() {
}
void HighlightStageSetup::configure(const Config& config) {
// Copy the styles here but update the stage with the new styles in run to be sure everything is
// thread safe...
_styles = config._styles;
}
void HighlightStageSetup::run(const render::RenderContextPointer& renderContext) {
auto stage = renderContext->_scene->getStage<HighlightStage>(HighlightStage::getName());
if (!stage) {
stage = std::make_shared<HighlightStage>();
renderContext->_scene->resetStage(HighlightStage::getName(), stage);
}
if (!_styles.empty()) {
render::Transaction transaction;
for (const auto& selection : _styles) {
auto& selectionName = selection.first;
auto& selectionStyle = selection.second;
transaction.resetSelectionHighlight(selectionName, selectionStyle);
}
renderContext->_scene->enqueueTransaction(transaction);
_styles.clear();
}
}

View file

@ -0,0 +1,135 @@
//
// HighlightStage.h
// Created by Olivier Prat on 07/07/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_render_utils_HighlightStage_h
#define hifi_render_utils_HighlightStage_h
#include "Stage.h"
#include "Engine.h"
#include "IndexedContainer.h"
#include "HighlightStyle.h"
namespace render {
// Highlight stage to set up HighlightStyle-related effects
class HighlightStage : public Stage {
public:
class Highlight {
public:
Highlight(const std::string& selectionName, const HighlightStyle& style) : _selectionName{ selectionName }, _style{ style } { }
std::string _selectionName;
HighlightStyle _style;
};
static const std::string& getName() { return _name; }
using Index = render::indexed_container::Index;
static const Index INVALID_INDEX;
using HighlightIdList = render::indexed_container::Indices;
static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; }
bool checkHighlightId(Index index) const { return _highlights.checkIndex(index); }
const Highlight& getHighlight(Index index) const { return _highlights.get(index); }
Highlight& editHighlight(Index index) { return _highlights.edit(index); }
Index addHighlight(const std::string& selectionName, const HighlightStyle& style = HighlightStyle());
Index getHighlightIdBySelection(const std::string& selectionName) const;
void removeHighlight(Index index);
HighlightIdList::iterator begin() { return _activeHighlightIds.begin(); }
HighlightIdList::iterator end() { return _activeHighlightIds.end(); }
private:
using Highlights = render::indexed_container::IndexedVector<Highlight>;
static std::string _name;
Highlights _highlights;
HighlightIdList _activeHighlightIds;
};
using HighlightStagePointer = std::shared_ptr<HighlightStage>;
class HighlightStageConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(QString selectionName READ getSelectionName WRITE setSelectionName NOTIFY dirty)
Q_PROPERTY(bool isOutlineSmooth READ isOutlineSmooth WRITE setOutlineSmooth NOTIFY dirty)
Q_PROPERTY(float colorR READ getColorRed WRITE setColorRed NOTIFY dirty)
Q_PROPERTY(float colorG READ getColorGreen WRITE setColorGreen NOTIFY dirty)
Q_PROPERTY(float colorB READ getColorBlue WRITE setColorBlue NOTIFY dirty)
Q_PROPERTY(float outlineWidth READ getOutlineWidth WRITE setOutlineWidth NOTIFY dirty)
Q_PROPERTY(float outlineIntensity READ getOutlineIntensity WRITE setOutlineIntensity NOTIFY dirty)
Q_PROPERTY(float unoccludedFillOpacity READ getUnoccludedFillOpacity WRITE setUnoccludedFillOpacity NOTIFY dirty)
Q_PROPERTY(float occludedFillOpacity READ getOccludedFillOpacity WRITE setOccludedFillOpacity NOTIFY dirty)
public:
using SelectionStyles = std::map<std::string, HighlightStyle>;
QString getSelectionName() const { return QString(_selectionName.c_str()); }
void setSelectionName(const QString& name);
bool isOutlineSmooth() const { return getStyle().isOutlineSmooth; }
void setOutlineSmooth(bool isSmooth);
float getColorRed() const { return getStyle().color.r; }
void setColorRed(float value);
float getColorGreen() const { return getStyle().color.g; }
void setColorGreen(float value);
float getColorBlue() const { return getStyle().color.b; }
void setColorBlue(float value);
float getOutlineWidth() const { return getStyle().outlineWidth; }
void setOutlineWidth(float value);
float getOutlineIntensity() const { return getStyle().outlineIntensity; }
void setOutlineIntensity(float value);
float getUnoccludedFillOpacity() const { return getStyle().unoccludedFillOpacity; }
void setUnoccludedFillOpacity(float value);
float getOccludedFillOpacity() const { return getStyle().occludedFillOpacity; }
void setOccludedFillOpacity(float value);
std::string _selectionName{ "contextOverlayHighlightList" };
mutable SelectionStyles _styles;
const HighlightStyle& getStyle() const;
HighlightStyle& editStyle();
signals:
void dirty();
};
class HighlightStageSetup {
public:
using Config = HighlightStageConfig;
using JobModel = render::Job::Model<HighlightStageSetup, Config>;
HighlightStageSetup();
void configure(const Config& config);
void run(const RenderContextPointer& renderContext);
protected:
HighlightStageConfig::SelectionStyles _styles;
};
}
#endif // hifi_render_utils_HighlightStage_h

View file

@ -0,0 +1,38 @@
//
// HighlightStyle.h
// Created by Olivier Prat on 11/06/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_render_utils_HighlightStyle_h
#define hifi_render_utils_HighlightStyle_h
#include <glm/vec3.hpp>
#include <string>
namespace render {
// This holds the configuration for a particular outline style
class HighlightStyle {
public:
bool isFilled() const {
return unoccludedFillOpacity > 5e-3f || occludedFillOpacity > 5e-3f;
}
glm::vec3 color{ 1.f, 0.7f, 0.2f };
float outlineWidth{ 2.0f };
float outlineIntensity{ 0.9f };
float unoccludedFillOpacity{ 0.0f };
float occludedFillOpacity{ 0.0f };
bool isOutlineSmooth{ false };
};
}
#endif // hifi_render_utils_HighlightStyle_h

View file

@ -14,6 +14,7 @@
#include <gpu/Batch.h>
#include "Logging.h"
#include "TransitionStage.h"
#include "HighlightStage.h"
using namespace render;
@ -54,6 +55,18 @@ void Transaction::resetSelection(const Selection& selection) {
_resetSelections.emplace_back(selection);
}
void Transaction::resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style) {
_highlightResets.emplace_back(HighlightReset{ selectionName, style });
}
void Transaction::removeHighlightFromSelection(const std::string& selectionName) {
_highlightRemoves.emplace_back(selectionName);
}
void Transaction::querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func) {
_highlightQueries.emplace_back(HighlightQuery{ selectionName, func });
}
void Transaction::merge(const Transaction& transaction) {
_resetItems.insert(_resetItems.end(), transaction._resetItems.begin(), transaction._resetItems.end());
_removedItems.insert(_removedItems.end(), transaction._removedItems.begin(), transaction._removedItems.end());
@ -62,6 +75,9 @@ void Transaction::merge(const Transaction& transaction) {
_addedTransitions.insert(_addedTransitions.end(), transaction._addedTransitions.begin(), transaction._addedTransitions.end());
_queriedTransitions.insert(_queriedTransitions.end(), transaction._queriedTransitions.begin(), transaction._queriedTransitions.end());
_reAppliedTransitions.insert(_reAppliedTransitions.end(), transaction._reAppliedTransitions.begin(), transaction._reAppliedTransitions.end());
_highlightResets.insert(_highlightResets.end(), transaction._highlightResets.begin(), transaction._highlightResets.end());
_highlightRemoves.insert(_highlightRemoves.end(), transaction._highlightRemoves.begin(), transaction._highlightRemoves.end());
_highlightQueries.insert(_highlightQueries.end(), transaction._highlightQueries.begin(), transaction._highlightQueries.end());
}
@ -176,6 +192,10 @@ void Scene::processTransactionFrame(const Transaction& transaction) {
// resets and potential NEW items
resetSelections(transaction._resetSelections);
}
resetHighlights(transaction._highlightResets);
removeHighlights(transaction._highlightRemoves);
queryHighlights(transaction._highlightQueries);
}
void Scene::resetItems(const Transaction::Resets& transactions) {
@ -316,6 +336,50 @@ void Scene::queryTransitionItems(const Transaction::TransitionQueries& transacti
}
}
void Scene::resetHighlights(const Transaction::HighlightResets& transactions) {
auto outlineStage = getStage<HighlightStage>(HighlightStage::getName());
for (auto& transaction : transactions) {
const auto& selectionName = std::get<0>(transaction);
const auto& newStyle = std::get<1>(transaction);
auto outlineId = outlineStage->getHighlightIdBySelection(selectionName);
if (HighlightStage::isIndexInvalid(outlineId)) {
outlineStage->addHighlight(selectionName, newStyle);
} else {
outlineStage->editHighlight(outlineId)._style = newStyle;
}
}
}
void Scene::removeHighlights(const Transaction::HighlightRemoves& transactions) {
auto outlineStage = getStage<HighlightStage>(HighlightStage::getName());
for (auto& selectionName : transactions) {
auto outlineId = outlineStage->getHighlightIdBySelection(selectionName);
if (!HighlightStage::isIndexInvalid(outlineId)) {
outlineStage->removeHighlight(outlineId);
}
}
}
void Scene::queryHighlights(const Transaction::HighlightQueries& transactions) {
auto outlineStage = getStage<HighlightStage>(HighlightStage::getName());
for (auto& transaction : transactions) {
const auto& selectionName = std::get<0>(transaction);
const auto& func = std::get<1>(transaction);
auto outlineId = outlineStage->getHighlightIdBySelection(selectionName);
if (!HighlightStage::isIndexInvalid(outlineId)) {
func(&outlineStage->editHighlight(outlineId)._style);
} else {
func(nullptr);
}
}
}
void Scene::collectSubItems(ItemID parentId, ItemIDs& subItems) const {
// Access the true item
auto& item = _items[parentId];
@ -362,7 +426,7 @@ void Scene::resetItemTransition(ItemID itemId) {
}
}
// THis fucntion is thread safe
// This function is thread safe
Selection Scene::getSelection(const Selection::Name& name) const {
std::unique_lock<std::mutex> lock(_selectionsMutex);
auto found = _selections.find(name);
@ -373,6 +437,17 @@ Selection Scene::getSelection(const Selection::Name& name) const {
}
}
// This function is thread safe
bool Scene::isSelectionEmpty(const Selection::Name& name) const {
std::unique_lock<std::mutex> lock(_selectionsMutex);
auto found = _selections.find(name);
if (found == _selections.end()) {
return false;
} else {
return (*found).second.isEmpty();
}
}
void Scene::resetSelections(const Transaction::SelectionResets& transactions) {
for (auto selection : transactions) {
auto found = _selections.find(selection.getName());

View file

@ -17,6 +17,7 @@
#include "Stage.h"
#include "Selection.h"
#include "Transition.h"
#include "HighlightStyle.h"
namespace render {
@ -37,6 +38,7 @@ class Transaction {
public:
typedef std::function<void(ItemID, const Transition*)> TransitionQueryFunc;
typedef std::function<void(HighlightStyle const*)> SelectionHighlightQueryFunc;
Transaction() {}
~Transaction() {}
@ -61,6 +63,10 @@ public:
// Selection transactions
void resetSelection(const Selection& selection);
void resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style = HighlightStyle());
void removeHighlightFromSelection(const std::string& selectionName);
void querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func);
void merge(const Transaction& transaction);
// Checkers if there is work to do when processing the transaction
@ -75,6 +81,9 @@ protected:
using TransitionQuery = std::tuple<ItemID, TransitionQueryFunc>;
using TransitionReApply = ItemID;
using SelectionReset = Selection;
using HighlightReset = std::tuple<std::string, HighlightStyle>;
using HighlightRemove = std::string;
using HighlightQuery = std::tuple<std::string, SelectionHighlightQueryFunc>;
using Resets = std::vector<Reset>;
using Removes = std::vector<Remove>;
@ -83,6 +92,9 @@ protected:
using TransitionQueries = std::vector<TransitionQuery>;
using TransitionReApplies = std::vector<TransitionReApply>;
using SelectionResets = std::vector<SelectionReset>;
using HighlightResets = std::vector<HighlightReset>;
using HighlightRemoves = std::vector<HighlightRemove>;
using HighlightQueries = std::vector<HighlightQuery>;
Resets _resetItems;
Removes _removedItems;
@ -91,6 +103,9 @@ protected:
TransitionQueries _queriedTransitions;
TransitionReApplies _reAppliedTransitions;
SelectionResets _resetSelections;
HighlightResets _highlightResets;
HighlightRemoves _highlightRemoves;
HighlightQueries _highlightQueries;
};
typedef std::queue<Transaction> TransactionQueue;
@ -102,6 +117,7 @@ typedef std::queue<Transaction> TransactionQueue;
// Items are notified accordingly on any update message happening
class Scene {
public:
Scene(glm::vec3 origin, float size);
~Scene();
@ -127,6 +143,10 @@ public:
// Thread safe
Selection getSelection(const Selection::Name& name) const;
// Check if a particular selection is empty (returns true if doesn't exist)
// Thread safe
bool isSelectionEmpty(const Selection::Name& name) const;
// This next call are NOT threadsafe, you have to call them from the correct thread to avoid any potential issues
// Access a particular item form its ID
@ -187,6 +207,9 @@ protected:
void transitionItems(const Transaction::TransitionAdds& transactions);
void reApplyTransitions(const Transaction::TransitionReApplies& transactions);
void queryTransitionItems(const Transaction::TransitionQueries& transactions);
void resetHighlights(const Transaction::HighlightResets& transactions);
void removeHighlights(const Transaction::HighlightRemoves& transactions);
void queryHighlights(const Transaction::HighlightQueries& transactions);
void collectSubItems(ItemID parentId, ItemIDs& subItems) const;

View file

@ -76,17 +76,25 @@ void render::depthSortItems(const RenderContextPointer& renderContext, bool fron
}
// Finally once sorted result to a list of itemID
// Finally once sorted result to a list of itemID and keep uniques
render::ItemID previousID = Item::INVALID_ITEM_ID;
if (!bounds) {
for (auto& item : itemBoundSorts) {
outItems.emplace_back(ItemBound(item._id, item._bounds));
if (item._id != previousID) {
outItems.emplace_back(ItemBound(item._id, item._bounds));
previousID = item._id;
}
}
} else if (!itemBoundSorts.empty()) {
if (bounds->isNull()) {
*bounds = itemBoundSorts.front()._bounds;
}
for (auto& item : itemBoundSorts) {
*bounds += item._bounds;
outItems.emplace_back(ItemBound(item._id, item._bounds));
if (item._id != previousID) {
outItems.emplace_back(ItemBound(item._id, item._bounds));
previousID = item._id;
*bounds += item._bounds;
}
}
}
}

View file

@ -5,6 +5,7 @@
using namespace render;
std::string TransitionStage::_name("Transition");
const TransitionStage::Index TransitionStage::INVALID_INDEX{ indexed_container::INVALID_INDEX };
TransitionStage::Index TransitionStage::addTransition(ItemID itemId, Transition::Type type, ItemID boundId) {
Transition transition;

View file

@ -25,7 +25,7 @@ namespace render {
static const std::string& getName() { return _name; }
using Index = indexed_container::Index;
static const Index INVALID_INDEX{ indexed_container::INVALID_INDEX };
static const Index INVALID_INDEX;
using TransitionIdList = indexed_container::Indices;
static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; }

View file

@ -1,48 +0,0 @@
//
// DoubleHashKey.cpp
// libraries/shared/src
//
// Created by Andrew Meadows 2014.11.02
// 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 "DoubleHashKey.h"
const uint32_t NUM_PRIMES = 64;
const uint32_t PRIMES[] = {
4194301U, 4194287U, 4194277U, 4194271U, 4194247U, 4194217U, 4194199U, 4194191U,
4194187U, 4194181U, 4194173U, 4194167U, 4194143U, 4194137U, 4194131U, 4194107U,
4194103U, 4194023U, 4194011U, 4194007U, 4193977U, 4193971U, 4193963U, 4193957U,
4193939U, 4193929U, 4193909U, 4193869U, 4193807U, 4193803U, 4193801U, 4193789U,
4193759U, 4193753U, 4193743U, 4193701U, 4193663U, 4193633U, 4193573U, 4193569U,
4193551U, 4193549U, 4193531U, 4193513U, 4193507U, 4193459U, 4193447U, 4193443U,
4193417U, 4193411U, 4193393U, 4193389U, 4193381U, 4193377U, 4193369U, 4193359U,
4193353U, 4193327U, 4193309U, 4193303U, 4193297U, 4193279U, 4193269U, 4193263U
};
uint32_t DoubleHashKey::hashFunction(uint32_t value, uint32_t primeIndex) {
uint32_t hash = PRIMES[primeIndex % NUM_PRIMES] * (value + 1U);
hash += ~(hash << 15);
hash ^= (hash >> 10);
hash += (hash << 3);
hash ^= (hash >> 6);
hash += ~(hash << 11);
return hash ^ (hash >> 16);
}
uint32_t DoubleHashKey::hashFunction2(uint32_t value) {
uint32_t hash = 0x811c9dc5U;
for (uint32_t i = 0; i < 4; i++ ) {
uint32_t byte = (value << (i * 8)) >> (24 - i * 8);
hash = ( hash ^ byte ) * 0x01000193U;
}
return hash;
}
void DoubleHashKey::computeHash(uint32_t value, uint32_t primeIndex) {
_hash = DoubleHashKey::hashFunction(value, primeIndex);
_hash2 = DoubleHashKey::hashFunction2(value);
}

View file

@ -1,49 +0,0 @@
//
// DoubleHashKey.h
// libraries/shared/src
//
// Created by Andrew Meadows 2014.11.02
// 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_DoubleHashKey_h
#define hifi_DoubleHashKey_h
#include <stdint.h>
// DoubleHashKey for use with btHashMap
class DoubleHashKey {
public:
static uint32_t hashFunction(uint32_t value, uint32_t primeIndex);
static uint32_t hashFunction2(uint32_t value);
DoubleHashKey() : _hash(0), _hash2(0) { }
DoubleHashKey(uint32_t value, uint32_t primeIndex = 0) :
_hash(hashFunction(value, primeIndex)),
_hash2(hashFunction2(value)) {
}
void clear() { _hash = 0; _hash2 = 0; }
bool isNull() const { return _hash == 0 && _hash2 == 0; }
bool equals(const DoubleHashKey& other) const {
return _hash == other._hash && _hash2 == other._hash2;
}
void computeHash(uint32_t value, uint32_t primeIndex = 0);
uint32_t getHash() const { return _hash; }
uint32_t getHash2() const { return _hash2; }
void setHash(uint32_t hash) { _hash = hash; }
void setHash2(uint32_t hash2) { _hash2 = hash2; }
private:
uint32_t _hash;
uint32_t _hash2;
};
#endif // hifi_DoubleHashKey_h

View file

@ -0,0 +1,67 @@
//
// HashKey.cpp
// libraries/shared/src
//
// Created by Andrew Meadows 2017.10.25
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "HashKey.h"
#include "NumericalConstants.h"
const uint8_t NUM_PRIMES = 64;
const uint64_t PRIMES[] = {
4194301UL, 4194287UL, 4194277UL, 4194271UL, 4194247UL, 4194217UL, 4194199UL, 4194191UL,
4194187UL, 4194181UL, 4194173UL, 4194167UL, 4194143UL, 4194137UL, 4194131UL, 4194107UL,
4194103UL, 4194023UL, 4194011UL, 4194007UL, 4193977UL, 4193971UL, 4193963UL, 4193957UL,
4193939UL, 4193929UL, 4193909UL, 4193869UL, 4193807UL, 4193803UL, 4193801UL, 4193789UL,
4193759UL, 4193753UL, 4193743UL, 4193701UL, 4193663UL, 4193633UL, 4193573UL, 4193569UL,
4193551UL, 4193549UL, 4193531UL, 4193513UL, 4193507UL, 4193459UL, 4193447UL, 4193443UL,
4193417UL, 4193411UL, 4193393UL, 4193389UL, 4193381UL, 4193377UL, 4193369UL, 4193359UL,
4193353UL, 4193327UL, 4193309UL, 4193303UL, 4193297UL, 4193279UL, 4193269UL, 4193263UL
};
// this hash function inspired by Squirrel Eiserloh's GDC2017 talk: "Noise-Based RNG"
uint64_t squirrel3_64(uint64_t data, uint8_t primeIndex) {
constexpr uint64_t BIT_NOISE1 = 2760725261486592643UL;
constexpr uint64_t BIT_NOISE2 = 6774464464027632833UL;
constexpr uint64_t BIT_NOISE3 = 5545331650366059883UL;
// blend prime numbers into the hash to prevent dupes
// when hashing the same set of numbers in a different order
uint64_t hash = PRIMES[primeIndex % NUM_PRIMES] * data;
hash *= BIT_NOISE1;
hash ^= (hash >> 16);
hash += BIT_NOISE2;
hash ^= (hash << 16);
hash *= BIT_NOISE3;
return hash ^ (hash >> 16);
}
constexpr float QUANTIZED_VALUES_PER_METER = 250.0f;
// static
float HashKey::getNumQuantizedValuesPerMeter() {
return QUANTIZED_VALUES_PER_METER;
}
void HashKey::hashUint64(uint64_t data) {
_hash += squirrel3_64(data, ++_hashCount);
}
void HashKey::hashFloat(float data) {
_hash += squirrel3_64((uint64_t)((int64_t)(data * QUANTIZED_VALUES_PER_METER)), ++_hashCount);
}
void HashKey::hashVec3(const glm::vec3& data) {
_hash += squirrel3_64((uint64_t)((int64_t)(data[0] * QUANTIZED_VALUES_PER_METER)), ++_hashCount);
_hash *= squirrel3_64((uint64_t)((int64_t)(data[1] * QUANTIZED_VALUES_PER_METER)), ++_hashCount);
_hash ^= squirrel3_64((uint64_t)((int64_t)(data[2] * QUANTIZED_VALUES_PER_METER)), ++_hashCount);
}

View file

@ -0,0 +1,52 @@
//
// HashKey.h
// libraries/shared/src
//
// Created by Andrew Meadows 2017.10.25
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_HashKey_h
#define hifi_HashKey_h
#include <cstddef>
#include <stdint.h>
#include <glm/glm.hpp>
// HashKey for use with btHashMap which requires a particular API for its keys. In particular it
// requires its Key to implement these methods:
//
// bool Key::equals()
// int32_t Key::getHash()
//
// The important thing about the HashKey implementation is that while getHash() returns 32-bits,
// internally HashKey stores a 64-bit hash which is used for the equals() comparison. This allows
// btHashMap to insert "dupe" 32-bit keys to different "values".
class HashKey {
public:
static float getNumQuantizedValuesPerMeter();
// These two methods are required by btHashMap.
bool equals(const HashKey& other) const { return _hash == other._hash; }
int32_t getHash() const { return (int32_t)((uint32_t)_hash); }
void clear() { _hash = _hashCount = 0; }
bool isNull() const { return _hash == 0 && _hashCount == 0; }
void hashUint64(uint64_t data);
void hashFloat(float data);
void hashVec3(const glm::vec3& data);
uint64_t getHash64() const { return _hash; } // for debug/test purposes
private:
uint64_t _hash { 0 };
uint8_t _hashCount { 0 };
};
#endif // hifi_HashKey_h

View file

@ -59,8 +59,12 @@ public:
_max = other._max;
}
double totalSamples = _samples + other._samples;
_average = _average * ((double)_samples / totalSamples)
+ other._average * ((double)other._samples / totalSamples);
if (totalSamples > 0) {
_average = _average * ((double)_samples / totalSamples)
+ other._average * ((double)other._samples / totalSamples);
} else {
_average = 0.0f;
}
_samples += other._samples;
}

View file

@ -53,7 +53,7 @@ void ShapeInfo::clear() {
_triangleIndices.clear();
_halfExtents = glm::vec3(0.0f);
_offset = glm::vec3(0.0f);
_doubleHashKey.clear();
_hashKey.clear();
_type = SHAPE_TYPE_NONE;
}
@ -87,14 +87,14 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
default:
break;
}
_doubleHashKey.clear();
_hashKey.clear();
}
void ShapeInfo::setBox(const glm::vec3& halfExtents) {
_url = "";
_type = SHAPE_TYPE_BOX;
setHalfExtents(halfExtents);
_doubleHashKey.clear();
_hashKey.clear();
}
void ShapeInfo::setSphere(float radius) {
@ -102,12 +102,12 @@ void ShapeInfo::setSphere(float radius) {
_type = SHAPE_TYPE_SPHERE;
radius = glm::max(radius, MIN_HALF_EXTENT);
_halfExtents = glm::vec3(radius);
_doubleHashKey.clear();
_hashKey.clear();
}
void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) {
_pointCollection = pointCollection;
_doubleHashKey.clear();
_hashKey.clear();
}
void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
@ -116,12 +116,12 @@ void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
radius = glm::max(radius, MIN_HALF_EXTENT);
halfHeight = glm::max(halfHeight, 0.0f);
_halfExtents = glm::vec3(radius, halfHeight, radius);
_doubleHashKey.clear();
_hashKey.clear();
}
void ShapeInfo::setOffset(const glm::vec3& offset) {
_offset = offset;
_doubleHashKey.clear();
_hashKey.clear();
}
uint32_t ShapeInfo::getNumSubShapes() const {
@ -256,119 +256,46 @@ bool ShapeInfo::contains(const glm::vec3& point) const {
}
}
const DoubleHashKey& ShapeInfo::getHash() const {
const HashKey& ShapeInfo::getHash() const {
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) {
bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET;
if (_hashKey.isNull() && _type != SHAPE_TYPE_NONE) {
// The key is not yet cached therefore we must compute it.
// compute hash1
// TODO?: provide lookup table for hash/hash2 of _type rather than recompute?
uint32_t primeIndex = 0;
_doubleHashKey.computeHash((uint32_t)_type, primeIndex++);
_hashKey.hashUint64((uint64_t)_type);
if (_type != SHAPE_TYPE_SIMPLE_HULL) {
// compute hash1
uint32_t hash = _doubleHashKey.getHash();
for (int j = 0; j < 3; ++j) {
// NOTE: 0.49f is used to bump the float up almost half a millimeter
// so the cast to int produces a round() effect rather than a floor()
hash ^= DoubleHashKey::hashFunction(
(uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f),
primeIndex++);
if (useOffset) {
hash ^= DoubleHashKey::hashFunction(
(uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f),
primeIndex++);
}
}
_doubleHashKey.setHash(hash);
// compute hash2
hash = _doubleHashKey.getHash2();
for (int j = 0; j < 3; ++j) {
// NOTE: 0.49f is used to bump the float up almost half a millimeter
// so the cast to int produces a round() effect rather than a floor()
uint32_t floatHash = DoubleHashKey::hashFunction2(
(uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f));
if (useOffset) {
floatHash ^= DoubleHashKey::hashFunction2(
(uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f));
}
hash += ~(floatHash << 17);
hash ^= (floatHash >> 11);
hash += (floatHash << 4);
hash ^= (floatHash >> 7);
hash += ~(floatHash << 10);
hash = (hash << 16) | (hash >> 16);
}
_doubleHashKey.setHash2(hash);
_hashKey.hashVec3(_halfExtents);
_hashKey.hashVec3(_offset);
} else {
// TODO: we could avoid hashing all of these points if we were to supply the ShapeInfo with a unique
// descriptive string. Shapes that are uniquely described by their type and URL could just put their
// url in the description.
assert(_pointCollection.size() == (size_t)1);
const PointList & points = _pointCollection.back();
const int numPoints = (int)points.size();
uint32_t hash = _doubleHashKey.getHash();
uint32_t hash2 = _doubleHashKey.getHash2();
for (int pointIndex = 0; pointIndex < numPoints; ++pointIndex) {
// compute hash1 & 2
const glm::vec3 &curPoint = points[pointIndex];
for (int vecCompIndex = 0; vecCompIndex < 3; ++vecCompIndex) {
// NOTE: 0.49f is used to bump the float up almost half a millimeter
// so the cast to int produces a round() effect rather than a floor()
uint32_t valueToHash = (uint32_t)(curPoint[vecCompIndex] * MILLIMETERS_PER_METER + copysignf(1.0f, curPoint[vecCompIndex]) * 0.49f);
hash ^= DoubleHashKey::hashFunction(valueToHash, primeIndex++);
uint32_t floatHash = DoubleHashKey::hashFunction2(valueToHash);
if (useOffset) {
const uint32_t offsetValToHash = (uint32_t)(_offset[vecCompIndex] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[vecCompIndex])* 0.49f);
hash ^= DoubleHashKey::hashFunction(offsetValToHash, primeIndex++);
floatHash ^= DoubleHashKey::hashFunction2(offsetValToHash);
}
hash2 += ~(floatHash << 17);
hash2 ^= (floatHash >> 11);
hash2 += (floatHash << 4);
hash2 ^= (floatHash >> 7);
hash2 += ~(floatHash << 10);
hash2 = (hash2 << 16) | (hash2 >> 16);
}
for (int i = 0; i < numPoints; ++i) {
_hashKey.hashVec3(points[i]);
}
_doubleHashKey.setHash(hash);
_doubleHashKey.setHash2(hash2);
}
QString url = _url.toString();
if (!url.isEmpty()) {
// fold the urlHash into both parts
QByteArray baUrl = url.toLocal8Bit();
uint32_t urlHash = qChecksum(baUrl.data(), baUrl.size());
_doubleHashKey.setHash(_doubleHashKey.getHash() ^ urlHash);
_doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ urlHash);
_hashKey.hashUint64((uint64_t)urlHash);
}
uint32_t numHulls = 0;
if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_SIMPLE_COMPOUND) {
numHulls = (uint32_t)_pointCollection.size();
uint64_t numHulls = (uint64_t)_pointCollection.size();
_hashKey.hashUint64(numHulls);
} else if (_type == SHAPE_TYPE_SIMPLE_HULL) {
numHulls = 1;
}
if (numHulls > 0) {
uint32_t hash = DoubleHashKey::hashFunction(numHulls, primeIndex++);
_doubleHashKey.setHash(_doubleHashKey.getHash() ^ hash);
hash = DoubleHashKey::hashFunction2(numHulls);
_doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ hash);
_hashKey.hashUint64(1);
}
}
return _doubleHashKey;
return _hashKey;
}
void ShapeInfo::setHalfExtents(const glm::vec3& halfExtents) {
_halfExtents = glm::max(halfExtents, glm::vec3(MIN_HALF_EXTENT));
_hashKey.clear();
}

View file

@ -18,7 +18,7 @@
#include <glm/glm.hpp>
#include <glm/gtx/norm.hpp>
#include "DoubleHashKey.h"
#include "HashKey.h"
const float MIN_SHAPE_OFFSET = 0.001f; // offsets less than 1mm will be ignored
@ -89,7 +89,7 @@ public:
/// For compound shapes it will only return whether it is inside the bounding box
bool contains(const glm::vec3& point) const;
const DoubleHashKey& getHash() const;
const HashKey& getHash() const;
protected:
void setHalfExtents(const glm::vec3& halfExtents);
@ -99,7 +99,7 @@ protected:
TriangleIndices _triangleIndices;
glm::vec3 _halfExtents = glm::vec3(0.0f);
glm::vec3 _offset = glm::vec3(0.0f);
mutable DoubleHashKey _doubleHashKey;
mutable HashKey _hashKey;
ShapeType _type = SHAPE_TYPE_NONE;
};

View file

@ -10,6 +10,7 @@
//
#include <algorithm>
#include <array>
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
@ -504,16 +505,17 @@ const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_PROJECTED_POLYGON_VERT
{6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left
};
CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const {
template <typename TBOX>
CubeProjectedPolygon ViewFrustum::computeProjectedPolygon(const TBOX& box) const {
const glm::vec3& bottomNearRight = box.getCorner();
glm::vec3 topFarLeft = box.calcTopFarLeft();
int lookUp = ((_position.x < bottomNearRight.x) ) // 1 = right | compute 6-bit
+ ((_position.x > topFarLeft.x ) << 1) // 2 = left | code to
+ ((_position.y < bottomNearRight.y) << 2) // 4 = bottom | classify camera
+ ((_position.y > topFarLeft.y ) << 3) // 8 = top | with respect to
+ ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining
+ ((_position.z > topFarLeft.z ) << 5); // 32 = back/far | planes
int lookUp = ((_position.x < bottomNearRight.x)) // 1 = right | compute 6-bit
+ ((_position.x > topFarLeft.x) << 1) // 2 = left | code to
+ ((_position.y < bottomNearRight.y) << 2) // 4 = bottom | classify camera
+ ((_position.y > topFarLeft.y) << 3) // 8 = top | with respect to
+ ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining
+ ((_position.z > topFarLeft.z) << 5); // 32 = back/far | planes
int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices
@ -524,8 +526,8 @@ CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const {
bool anyPointsInView = false; // assume the worst!
if (vertexCount) {
allPointsInView = true; // assume the best!
for(int i = 0; i < vertexCount; i++) {
int vertexNum = hullVertexLookup[lookUp][i+1];
for (int i = 0; i < vertexCount; i++) {
int vertexNum = hullVertexLookup[lookUp][i + 1];
glm::vec3 point = box.getVertex((BoxVertex)vertexNum);
glm::vec2 projectedPoint = projectPoint(point, pointInView);
allPointsInView = allPointsInView && pointInView;
@ -538,24 +540,24 @@ CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const {
// NOTE: This clipping does not improve our overall performance. It basically causes more polygons to
// end up in the same quad/half and so the polygon lists get longer, and that's more calls to polygon.occludes()
if ( (projectedPolygon.getMaxX() > PolygonClip::RIGHT_OF_CLIPPING_WINDOW ) ||
(projectedPolygon.getMaxY() > PolygonClip::TOP_OF_CLIPPING_WINDOW ) ||
(projectedPolygon.getMaxX() < PolygonClip::LEFT_OF_CLIPPING_WINDOW ) ||
(projectedPolygon.getMaxY() < PolygonClip::BOTTOM_OF_CLIPPING_WINDOW) ) {
(projectedPolygon.getMaxY() > PolygonClip::TOP_OF_CLIPPING_WINDOW ) ||
(projectedPolygon.getMaxX() < PolygonClip::LEFT_OF_CLIPPING_WINDOW ) ||
(projectedPolygon.getMaxY() < PolygonClip::BOTTOM_OF_CLIPPING_WINDOW) ) {
CoverageRegion::_clippedPolygons++;
CoverageRegion::_clippedPolygons++;
glm::vec2* clippedVertices;
int clippedVertexCount;
PolygonClip::clipToScreen(projectedPolygon.getVertices(), vertexCount, clippedVertices, clippedVertexCount);
glm::vec2* clippedVertices;
int clippedVertexCount;
PolygonClip::clipToScreen(projectedPolygon.getVertices(), vertexCount, clippedVertices, clippedVertexCount);
// Now reset the vertices of our projectedPolygon object
projectedPolygon.setVertexCount(clippedVertexCount);
for(int i = 0; i < clippedVertexCount; i++) {
projectedPolygon.setVertex(i, clippedVertices[i]);
}
delete[] clippedVertices;
// Now reset the vertices of our projectedPolygon object
projectedPolygon.setVertexCount(clippedVertexCount);
for(int i = 0; i < clippedVertexCount; i++) {
projectedPolygon.setVertex(i, clippedVertices[i]);
}
delete[] clippedVertices;
lookUp += PROJECTION_CLIPPED;
lookUp += PROJECTION_CLIPPED;
}
***/
}
@ -568,6 +570,97 @@ CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const {
return projectedPolygon;
}
CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const {
return computeProjectedPolygon(box);
}
CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const {
return computeProjectedPolygon(box);
}
bool ViewFrustum::getProjectedRect(const AABox& box, glm::vec2& bottomLeft, glm::vec2& topRight) const {
using Edge = std::pair<int, int>;
const int VERTEX_COUNT = 8;
const int EDGE_COUNT = 12;
// In theory, after clipping a box with a plane, only 4 new vertices at max
// should be created but due to potential imprecisions (edge almost parallel to
// near plane for instance) there might be more
const int MAX_VERTEX_COUNT = VERTEX_COUNT + 4 + 2;
std::array<glm::vec3, MAX_VERTEX_COUNT> vertices;
std::array<Edge, EDGE_COUNT> boxEdges{ {
Edge{BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR},
Edge{TOP_LEFT_NEAR, TOP_RIGHT_NEAR},
Edge{BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR},
Edge{TOP_LEFT_FAR, TOP_RIGHT_FAR},
Edge{BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR},
Edge{BOTTOM_LEFT_FAR, TOP_LEFT_FAR},
Edge{BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR},
Edge{BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR},
Edge{BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR},
Edge{TOP_LEFT_NEAR, TOP_LEFT_FAR},
Edge{BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR},
Edge{TOP_RIGHT_NEAR, TOP_RIGHT_FAR}
} };
std::array<float, VERTEX_COUNT> distancesToNearPlane;
std::bitset<MAX_VERTEX_COUNT> areVerticesInside;
int vertexCount = VERTEX_COUNT;
int i;
// Clip the hull with the near plane.
const auto& nearPlane = _planes[NEAR_PLANE];
for (i = 0; i < VERTEX_COUNT; i++) {
vertices[i] = box.getVertex(static_cast<BoxVertex>(i));
distancesToNearPlane[i] = nearPlane.distance(vertices[i]);
}
for (i = 0; i < EDGE_COUNT; i++) {
const auto& edgeVertexIndices = boxEdges[i];
const auto& startVertex = vertices[edgeVertexIndices.first];
const auto& endVertex = vertices[edgeVertexIndices.second];
float startVertexDistance = distancesToNearPlane[edgeVertexIndices.first];
float endVertexDistance = distancesToNearPlane[edgeVertexIndices.second];
bool isStartPointInside = startVertexDistance >= 0.0f;
bool isEndPointInside = endVertexDistance >= 0.0f;
areVerticesInside.set(edgeVertexIndices.first, isStartPointInside);
areVerticesInside.set(edgeVertexIndices.second, isEndPointInside);
if (isStartPointInside != isEndPointInside) {
// One of the two vertices is behind the near plane so add a new clipped vertex
// add tag it as projectable.
vertices[vertexCount] = startVertex + (endVertex - startVertex) * (startVertexDistance / (startVertexDistance - endVertexDistance));
areVerticesInside.set(vertexCount);
vertexCount++;
}
}
// Project points that are inside
bottomLeft.x = std::numeric_limits<float>::max();
bottomLeft.y = std::numeric_limits<float>::max();
topRight.x = -std::numeric_limits<float>::max();
topRight.y = -std::numeric_limits<float>::max();
for (i = 0; i < vertexCount; i++) {
if (areVerticesInside[i]) {
bool isPointInside;
auto projectedPoint = projectPoint(vertices[i], isPointInside);
bottomLeft.x = std::min(bottomLeft.x, projectedPoint.x);
bottomLeft.y = std::min(bottomLeft.y, projectedPoint.y);
topRight.x = std::max(topRight.x, projectedPoint.x);
topRight.y = std::max(topRight.y, projectedPoint.y);
}
}
bottomLeft.x = glm::clamp(bottomLeft.x, -1.0f, 1.0f);
bottomLeft.y = glm::clamp(bottomLeft.y, -1.0f, 1.0f);
topRight.x = glm::clamp(topRight.x, -1.0f, 1.0f);
topRight.y = glm::clamp(topRight.y, -1.0f, 1.0f);
return areVerticesInside.any();
}
// Similar strategy to getProjectedPolygon() we use the knowledge of camera position relative to the
// axis-aligned voxels to determine which of the voxels vertices must be the furthest. No need for
// squares and square-roots. Just compares.

View file

@ -119,6 +119,8 @@ public:
glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const;
CubeProjectedPolygon getProjectedPolygon(const AACube& box) const;
CubeProjectedPolygon getProjectedPolygon(const AABox& box) const;
bool getProjectedRect(const AABox& box, glm::vec2& bottomLeft, glm::vec2& topRight) const;
void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const;
float distanceToCamera(const glm::vec3& point) const;
@ -169,6 +171,10 @@ private:
// Used to project points
glm::mat4 _ourModelViewProjectionMatrix;
template <typename TBOX>
CubeProjectedPolygon computeProjectedPolygon(const TBOX& box) const;
};
using ViewFrustumPointer = std::shared_ptr<ViewFrustum>;

View file

@ -10,13 +10,24 @@
(function() { // BEGIN LOCAL_SCOPE
//check if script already running.
var scriptData = ScriptDiscoveryService.getRunning();
var scripts = scriptData.filter(function (datum) { return datum.name === 'debugWindow.js'; });
if (scripts.length >= 2) {
//2nd instance of the script is too much
ScriptDiscoveryService.stopScript(scripts[1].url);
return;
}
// Set up the qml ui
var qml = Script.resolvePath('debugWindow.qml');
var window = new OverlayWindow({
title: 'Debug Window',
source: qml,
width: 400, height: 900,
});
window.setPosition(25, 50);
window.closed.connect(function() { Script.stop(); });

View file

@ -0,0 +1,160 @@
//
// debugHighlight.js
// developer/utilities/render
//
// Olivier Prat, created on 08/08/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Set up the qml ui
var qml = Script.resolvePath('highlight.qml');
var window = new OverlayWindow({
title: 'Highlight',
source: qml,
width: 400,
height: 400,
});
window.closed.connect(function() { Script.stop(); });
"use strict";
// Created by Sam Gondelman on 9/7/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
(function() { // BEGIN LOCAL_SCOPE
var END_DIMENSIONS = {
x: 0.15,
y: 0.15,
z: 0.15
};
var COLOR = {red: 97, green: 247, blue: 255};
var end = {
type: "sphere",
dimensions: END_DIMENSIONS,
color: COLOR,
ignoreRayIntersection: true,
alpha: 1.0,
visible: true
}
var COLOR2 = {red: 247, green: 97, blue: 255};
var end2 = {
type: "sphere",
dimensions: END_DIMENSIONS,
color: COLOR2,
ignoreRayIntersection: true,
alpha: 1.0,
visible: true
}
var highlightGroupIndex = 0
var isSelectionAddEnabled = false
var isSelectionEnabled = false
var renderStates = [{name: "test", end: end}];
var defaultRenderStates = [{name: "test", distance: 20.0, end: end2}];
var time = 0
var ray = LaserPointers.createLaserPointer({
joint: "Mouse",
filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS | RayPick.PICK_AVATARS | RayPick.PICK_INVISIBLE | RayPick.PICK_NONCOLLIDABLE,
renderStates: renderStates,
defaultRenderStates: defaultRenderStates,
enabled: false
});
function getSelectionName() {
var selectionName = "contextOverlayHighlightList"
if (highlightGroupIndex>0) {
selectionName += highlightGroupIndex
}
return selectionName
}
function fromQml(message) {
tokens = message.split(' ')
print("Received '"+message+"' from hightlight.qml")
if (tokens[0]=="highlight") {
highlightGroupIndex = parseInt(tokens[1])
print("Switching to highlight group "+highlightGroupIndex)
} else if (tokens[0]=="pick") {
isSelectionEnabled = tokens[1]=='true'
print("Ray picking set to "+isSelectionEnabled.toString())
if (isSelectionEnabled) {
LaserPointers.enableLaserPointer(ray)
} else {
LaserPointers.disableLaserPointer(ray)
}
time = 0
} else if (tokens[0]=="add") {
isSelectionAddEnabled = tokens[1]=='true'
print("Add to selection set to "+isSelectionAddEnabled.toString())
if (!isSelectionAddEnabled) {
Selection.clearSelectedItemsList(getSelectionName())
}
}
}
window.fromQml.connect(fromQml);
function cleanup() {
LaserPointers.removeLaserPointer(ray);
}
Script.scriptEnding.connect(cleanup);
var prevID = 0
var prevType = ""
var selectedID = 0
var selectedType = ""
function update(deltaTime) {
// you have to do this repeatedly because there's a bug but I'll fix it
LaserPointers.setRenderState(ray, "test");
var result = LaserPointers.getPrevRayPickResult(ray);
var selectionName = getSelectionName()
if (isSelectionEnabled && result.type != RayPick.INTERSECTED_NONE) {
time += deltaTime
if (result.objectID != prevID) {
var typeName = ""
if (result.type == RayPick.INTERSECTED_ENTITY) {
typeName = "entity"
} else if (result.type == RayPick.INTERSECTED_OVERLAY) {
typeName = "overlay"
} else if (result.type == RayPick.INTERSECTED_AVATAR) {
typeName = "avatar"
}
prevID = result.objectID;
prevType = typeName;
time = 0
} else if (time>1.0 && prevID!=selectedID) {
if (prevID != 0 && !isSelectionAddEnabled) {
Selection.removeFromSelectedItemsList(selectionName, selectedType, selectedID)
}
selectedID = prevID
selectedType = prevType
Selection.addToSelectedItemsList(selectionName, selectedType, selectedID)
print("HIGHLIGHT " + highlightGroupIndex + " picked type: " + result.type + ", id: " + result.objectID);
}
} else {
if (prevID != 0 && !isSelectionAddEnabled) {
Selection.removeFromSelectedItemsList(selectionName, prevType, prevID)
}
prevID = 0
selectedID = 0
time = 0
}
}
Script.update.connect(update);
}()); // END LOCAL_SCOPE

View file

@ -1,20 +0,0 @@
//
// debugOutline.js
// developer/utilities/render
//
// Olivier Prat, created on 08/08/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Set up the qml ui
var qml = Script.resolvePath('outline.qml');
var window = new OverlayWindow({
title: 'Outline',
source: qml,
width: 285,
height: 370,
});
window.closed.connect(function() { Script.stop(); });

View file

@ -0,0 +1,177 @@
//
// highlight.qml
// developer/utilities/render
//
// Olivier Prat, created on 08/08/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import "qrc:///qml/styles-uit"
import "qrc:///qml/controls-uit" as HifiControls
import "configSlider"
Rectangle {
id: root
HifiConstants { id: hifi;}
color: hifi.colors.baseGray;
anchors.margins: hifi.dimensions.contentMargin.x
property var debugConfig: Render.getConfig("RenderMainView.HighlightDebug")
property var highlightConfig: Render.getConfig("UpdateScene.HighlightStageSetup")
signal sendToScript(var message);
Column {
id: col
spacing: 10
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: hifi.dimensions.contentMargin.x
Row {
spacing: 10
anchors.left: parent.left
anchors.right: parent.right
HifiControls.CheckBox {
id: debug
text: "View Mask"
checked: root.debugConfig["viewMask"]
onCheckedChanged: {
root.debugConfig["viewMask"] = checked;
}
}
HifiControls.CheckBox {
text: "Hover select"
checked: false
onCheckedChanged: {
sendToScript("pick "+checked.toString())
}
}
HifiControls.CheckBox {
text: "Add to selection"
checked: false
onCheckedChanged: {
sendToScript("add "+checked.toString())
}
}
}
HifiControls.ComboBox {
id: box
width: 350
z: 999
editable: true
colorScheme: hifi.colorSchemes.dark
model: [
"contextOverlayHighlightList",
"highlightList1",
"highlightList2",
"highlightList3",
"highlightList4"]
label: ""
Timer {
id: postpone
interval: 100; running: false; repeat: false
onTriggered: { paramWidgetLoader.sourceComponent = paramWidgets }
}
onCurrentIndexChanged: {
root.highlightConfig["selectionName"] = model[currentIndex];
sendToScript("highlight "+currentIndex)
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 100ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 100
postpone.start()
}
}
Loader {
id: paramWidgetLoader
sourceComponent: paramWidgets
width: 350
}
Component {
id: paramWidgets
Column {
spacing: 10
anchors.margins: hifi.dimensions.contentMargin.x
HifiControls.Label {
text: "Outline"
}
Column {
spacing: 10
anchors.left: parent.left
anchors.right: parent.right
HifiControls.CheckBox {
text: "Smooth"
checked: root.highlightConfig["isOutlineSmooth"]
onCheckedChanged: {
root.highlightConfig["isOutlineSmooth"] = checked;
}
}
Repeater {
model: ["Width:outlineWidth:5.0:0.0",
"Intensity:outlineIntensity:1.0:0.0"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: false
config: root.highlightConfig
property: modelData.split(":")[1]
max: modelData.split(":")[2]
min: modelData.split(":")[3]
}
}
}
Separator {}
HifiControls.Label {
text: "Color"
}
Repeater {
model: ["Red:colorR:1.0:0.0",
"Green:colorG:1.0:0.0",
"Blue:colorB:1.0:0.0"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: false
config: root.highlightConfig
property: modelData.split(":")[1]
max: modelData.split(":")[2]
min: modelData.split(":")[3]
}
}
Separator {}
HifiControls.Label {
text: "Fill Opacity"
}
Repeater {
model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0",
"Occluded:occludedFillOpacity:1.0:0.0"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: false
config: root.highlightConfig
property: modelData.split(":")[1]
max: modelData.split(":")[2]
min: modelData.split(":")[3]
}
}
}
}
}
}

View file

@ -0,0 +1,116 @@
//
// highlightPage.qml
// developer/utilities/render
//
// Olivier Prat, created on 08/08/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import "../configSlider"
import "qrc:///qml/styles-uit"
import "qrc:///qml/controls-uit" as HifiControls
Rectangle {
id: root
property var highlightIndex: 0
property var drawConfig: Render.getConfig("RenderMainView.HighlightEffect"+highlightIndex)
HifiConstants { id: hifi;}
anchors.margins: hifi.dimensions.contentMargin.x
Column {
spacing: 5
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: hifi.dimensions.contentMargin.x
HifiControls.CheckBox {
id: glow
text: "Glow"
checked: root.drawConfig["glow"]
onCheckedChanged: {
root.drawConfig["glow"] = checked;
}
}
Repeater {
model: ["Width:width:5.0:0.0",
"Intensity:intensity:1.0:0.0"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: false
config: root.drawConfig
property: modelData.split(":")[1]
max: modelData.split(":")[2]
min: modelData.split(":")[3]
anchors.left: parent.left
anchors.right: parent.right
}
}
GroupBox {
title: "Color"
anchors.left: parent.left
anchors.right: parent.right
Column {
spacing: 10
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: hifi.dimensions.contentMargin.x
Repeater {
model: ["Red:colorR:1.0:0.0",
"Green:colorG:1.0:0.0",
"Blue:colorB:1.0:0.0"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: false
config: root.drawConfig
property: modelData.split(":")[1]
max: modelData.split(":")[2]
min: modelData.split(":")[3]
anchors.left: parent.left
anchors.right: parent.right
}
}
}
}
GroupBox {
title: "Fill Opacity"
anchors.left: parent.left
anchors.right: parent.right
Column {
spacing: 10
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: hifi.dimensions.contentMargin.x
Repeater {
model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0",
"Occluded:occludedFillOpacity:1.0:0.0"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: false
config: root.drawConfig
property: modelData.split(":")[1]
max: modelData.split(":")[2]
min: modelData.split(":")[3]
anchors.left: parent.left
anchors.right: parent.right
}
}
}
}
}
}

View file

@ -0,0 +1 @@
HighlightPage 1.0 HighlightPage.qml

View file

@ -1,119 +0,0 @@
//
// outline.qml
// developer/utilities/render
//
// Olivier Prat, created on 08/08/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import "configSlider"
Item {
id: root
property var debugConfig: Render.getConfig("RenderMainView.OutlineDebug")
property var drawConfig: Render.getConfig("RenderMainView.OutlineEffect")
Column {
spacing: 8
CheckBox {
text: "View Outlined Depth"
checked: root.debugConfig["viewOutlinedDepth"]
onCheckedChanged: {
root.debugConfig["viewOutlinedDepth"] = checked;
}
}
CheckBox {
text: "Glow"
checked: root.drawConfig["glow"]
onCheckedChanged: {
root.drawConfig["glow"] = checked;
}
}
ConfigSlider {
label: "Width"
integral: false
config: root.drawConfig
property: "width"
max: 15.0
min: 0.0
width: 280
}
ConfigSlider {
label: "Intensity"
integral: false
config: root.drawConfig
property: "intensity"
max: 1.0
min: 0.0
width: 280
}
GroupBox {
title: "Color"
width: 280
Column {
spacing: 8
ConfigSlider {
label: "Red"
integral: false
config: root.drawConfig
property: "colorR"
max: 1.0
min: 0.0
width: 270
}
ConfigSlider {
label: "Green"
integral: false
config: root.drawConfig
property: "colorG"
max: 1.0
min: 0.0
width: 270
}
ConfigSlider {
label: "Blue"
integral: false
config: root.drawConfig
property: "colorB"
max: 1.0
min: 0.0
width: 270
}
}
}
GroupBox {
title: "Fill Opacity"
width: 280
Column {
spacing: 8
ConfigSlider {
label: "Unoccluded"
integral: false
config: root.drawConfig
property: "fillOpacityUnoccluded"
max: 1.0
min: 0.0
width: 270
}
ConfigSlider {
label: "Occluded"
integral: false
config: root.drawConfig
property: "fillOpacityOccluded"
max: 1.0
min: 0.0
width: 270
}
}
}
}
}

View file

@ -65,6 +65,13 @@ Item {
label: "tone and post",
color: "#FF0000"
}
,
{
object: Render.getConfig("RenderMainView.OutlineRangeTimer"),
prop: "gpuRunTime",
label: "outline",
color: "#FFFF00"
}
]
}
PlotPerf {
@ -105,6 +112,13 @@ Item {
label: "tone and post",
color: "#FF0000"
}
,
{
object: Render.getConfig("RenderMainView.OutlineRangeTimer"),
prop: "batchRunTime",
label: "outline",
color: "#FFFF00"
}
]
}
}

View file

@ -555,20 +555,21 @@
</form>
<fieldset class="zone-group zone-section haze-section property gen fstuple">
<div class="tuple">
<div><label>Range<span class="unit">m</span></label><input type="number" id="property-zone-haze-range" min="5" max="10000" step="5"></div>
<div>
<label>Range<span class="unit">m</span></label>
<input type="number" id="property-zone-haze-range" min="5" max="10000" step="5">
</div>
</div>
</fieldset>
<fieldset class="zone-group zone-section haze-section property rgb fstuple">
<div class="zone-group zone-section haze-section property checkbox">
<input type="checkbox" id="property-zone-haze-altitude-effect">
<label for="property-zone-haze-altitude-effect">Use Altitude</label>
</div>
<fieldset class="zone-group zone-section haze-section property gen fstuple">
<div class="tuple">
<div><label>Base<span class="unit">m</span></label><input type="number" id="property-zone-haze-base" min="-1000" max="1000" step="10"></div>
<div><label>Ceiling<span class="unit">m</span></label><input type="number" id="property-zone-haze-ceiling" min="-1000" max="5000" step="10"></div>
</div>
</fieldset>
<fieldset class="zone-group zone-section haze-section property gen fstuple">
<div class="tuple">
<div><label>Base<span class="unit">m</span></label><input type="number" id="property-zone-haze-base" min="-1000" max="1000" step="10"></div>
<div><label>Ceiling<span class="unit">m</span></label><input type="number" id="property-zone-haze-ceiling" min="-1000" max="5000" step="10"></div>
</div>
</fieldset>
<fieldset class="zone-group zone-section haze-section property rgb fstuple">
<div class="color-picker" id="property-zone-haze-color"></div>
@ -582,6 +583,18 @@
<label for="property-zone-haze-blend-in-color-blue">Blue:</label></div>
</div>
</fieldset>
<fieldset class="zone-group zone-section haze-section property gen fstuple">
<div class="tuple">
<div>
<br>
<table>
<td><label>Background Blend&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.0</label></td>
<td><input type="range" class="slider" id="property-zone-haze-background-blend" min="0.0" max="1.0" step="0.01" value="0.0"></td>
<td><label>1.0</label></td>
</table>
</div>
</div>
</fieldset>
<div class="zone-group zone-section haze-section property checkbox">
<input type="checkbox" id="property-zone-haze-enable-light-blend">
<label for="property-zone-haze-enable-light-blend">Enable Glare</label>
@ -600,20 +613,21 @@
</fieldset>
<fieldset class="zone-group zone-section haze-section property gen fstuple">
<div class="tuple">
<div><label>Glare Angle<span class="unit">deg</span></label><input type="number" id="property-zone-haze-blend-angle" min="0" max="180" step="1"></div>
</div>
</fieldset>
<fieldset class="zone-group zone-section haze-section property gen fstuple">
<div>
<label>Background Blend</label>
<input type="number" id="property-zone-haze-background-blend" min="0.0" max="1.0" step="0.01">
<div>
<br>
<table>
<td><label>Glare Angle&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0</label></td>
<td><input type="range" class="slider" id="property-zone-haze-blend-angle" min="0" max="180" step="1"></td>
<td><label>180</label>
</table>
</div>
</div>
</fieldset>
<div class="zone-group zone-section haze-section property checkbox">
<input type="checkbox" id="property-zone-haze-attenuate-keylight">
<label for="property-zone-haze-attenuate-keylight">Attenuate Keylight</label>
</div>
<fieldset class="zone-group zone-section haze-section property gen fstuple">
<div class="zone-group zone-section haze-section property checkbox">
<input type="checkbox" id="property-zone-haze-attenuate-keylight">
<label for="property-zone-haze-attenuate-keylight">Attenuate Keylight</label>
</div>
<div class="tuple">
<div><label>Range<span class="unit">m</span></label><input type="number" id="property-zone-haze-keylight-range"
min="5" max="1000000" step="5"></div>

View file

@ -14,7 +14,7 @@
#include <btBulletDynamicsCommon.h>
#include <LinearMath/btHashMap.h>
#include <DoubleHashKey.h>
#include <HashKey.h>
#include <ShapeInfo.h>
#include <ShapeFactory.h>
#include <StreamUtils.h>
@ -23,108 +23,78 @@
QTEST_MAIN(ShapeInfoTests)
// Enable this to manually run testHashCollisions
// (NOT a regular unit test; takes ~40 secs to run on an i7)
//#define MANUAL_TEST true
void ShapeInfoTests::testHashFunctions() {
#if MANUAL_TEST
int maxTests = 10000000;
ShapeInfo info;
btHashMap<btHashInt, uint32_t> hashes;
btHashMap<HashKey, int32_t> hashes;
uint32_t bits[32];
uint32_t masks[32];
for (int i = 0; i < 32; ++i) {
const int32_t NUM_HASH_BITS = 32;
uint32_t bits[NUM_HASH_BITS];
uint32_t masks[NUM_HASH_BITS];
for (int i = 0; i < NUM_HASH_BITS; ++i) {
bits[i] = 0;
masks[i] = 1U << i;
masks[i] = 1UL << i;
}
float deltaLength = 0.002f;
float endLength = 100.0f;
float deltaLength = 1.0f / (HashKey::getNumQuantizedValuesPerMeter() - 3.0f);
float endLength = 2000.0f * deltaLength;
int numSteps = (int)(endLength / deltaLength);
int testCount = 0;
int numCollisions = 0;
btClock timer;
for (int x = 1; x < numSteps && testCount < maxTests; ++x) {
float radiusX = (float)x * deltaLength;
for (int i = 1; i < numSteps && testCount < maxTests; ++i) {
float radiusX = (float)i * deltaLength;
int32_t* hashPtr;
// test sphere
info.setSphere(radiusX);
++testCount;
DoubleHashKey key = info.getHash();
uint32_t* hashPtr = hashes.find(key.getHash());
if (hashPtr && *hashPtr == key.getHash2()) {
std::cout << testCount << " hash collision radiusX = " << radiusX
<< " h1 = 0x" << std::hex << key.getHash()
<< " h2 = 0x" << std::hex << key.getHash2()
<< std::endl;
HashKey key = info.getHash();
hashPtr = hashes.find(key);
if (hashPtr) {
std::cout << testCount << " hash collision sphere radius = " << radiusX
<< " h = 0x" << std::hex << key.getHash() << " : 0x" << *hashPtr
<< std::dec << std::endl;
++numCollisions;
assert(false);
} else {
hashes.insert(key.getHash(), key.getHash2());
hashes.insert(key, key.getHash());
}
for (int k = 0; k < 32; ++k) {
if (masks[k] & key.getHash2()) {
++bits[k];
// track bit distribution counts to evaluate hash function randomness
for (int j = 0; j < NUM_HASH_BITS; ++j) {
if (masks[j] & key.getHash()) {
++bits[j];
}
}
for (int y = 1; y < numSteps && testCount < maxTests; ++y) {
float radiusY = (float)y * deltaLength;
/* TODO: reimplement Cylinder and Capsule shapes
// test cylinder and capsule
int types[] = { CYLINDER_SHAPE_PROXYTYPE, CAPSULE_SHAPE_PROXYTYPE };
for (int i = 0; i < 2; ++i) {
switch(types[i]) {
case CYLINDER_SHAPE_PROXYTYPE: {
info.setCylinder(radiusX, radiusY);
break;
}
case CAPSULE_SHAPE_PROXYTYPE: {
info.setCapsuleY(radiusX, radiusY);
break;
}
}
++testCount;
key = info.getHash();
hashPtr = hashes.find(key.getHash());
if (hashPtr && *hashPtr == key.getHash2()) {
std::cout << testCount << " hash collision radiusX = " << radiusX << " radiusY = " << radiusY
<< " h1 = 0x" << std::hex << key.getHash()
<< " h2 = 0x" << std::hex << key.getHash2()
<< std::endl;
++numCollisions;
assert(false);
} else {
hashes.insert(key.getHash(), key.getHash2());
}
for (int k = 0; k < 32; ++k) {
if (masks[k] & key.getHash2()) {
++bits[k];
}
}
}
*/
for (int z = 1; z < numSteps && testCount < maxTests; ++z) {
float radiusZ = (float)z * deltaLength;
// test box
info.setBox(glm::vec3(radiusX, radiusY, radiusZ));
++testCount;
DoubleHashKey key = info.getHash();
hashPtr = hashes.find(key.getHash());
if (hashPtr && *hashPtr == key.getHash2()) {
std::cout << testCount << " hash collision radiusX = " << radiusX
<< " radiusY = " << radiusY << " radiusZ = " << radiusZ
<< " h1 = 0x" << std::hex << key.getHash()
<< " h2 = 0x" << std::hex << key.getHash2()
<< std::endl;
HashKey key = info.getHash();
hashPtr = hashes.find(key);
if (hashPtr) {
std::cout << testCount << " hash collision box dimensions = < " << radiusX
<< ", " << radiusY << ", " << radiusZ << " >"
<< " h = 0x" << std::hex << key.getHash() << " : 0x" << *hashPtr << " : 0x" << key.getHash64()
<< std::dec << std::endl;
++numCollisions;
assert(false);
} else {
hashes.insert(key.getHash(), key.getHash2());
hashes.insert(key, key.getHash());
}
for (int k = 0; k < 32; ++k) {
if (masks[k] & key.getHash2()) {
// track bit distribution counts to evaluate hash function randomness
for (int k = 0; k < NUM_HASH_BITS; ++k) {
if (masks[k] & key.getHash()) {
++bits[k];
}
}
@ -135,7 +105,8 @@ void ShapeInfoTests::testHashFunctions() {
std::cout << msec << " msec with " << numCollisions << " collisions out of " << testCount << " hashes" << std::endl;
// print out distribution of bits
for (int i = 0; i < 32; ++i) {
// ideally the numbers in each bin will be about the same
for (int i = 0; i < NUM_HASH_BITS; ++i) {
std::cout << "bit 0x" << std::hex << masks[i] << std::dec << " = " << bits[i] << std::endl;
}
QCOMPARE(numCollisions, 0);
@ -146,15 +117,14 @@ void ShapeInfoTests::testBoxShape() {
ShapeInfo info;
glm::vec3 halfExtents(1.23f, 4.56f, 7.89f);
info.setBox(halfExtents);
DoubleHashKey key = info.getHash();
HashKey key = info.getHash();
const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info);
QCOMPARE(shape != nullptr, true);
ShapeInfo otherInfo = info;
DoubleHashKey otherKey = otherInfo.getHash();
HashKey otherKey = otherInfo.getHash();
QCOMPARE(key.getHash(), otherKey.getHash());
QCOMPARE(key.getHash2(), otherKey.getHash2());
delete shape;
}
@ -163,15 +133,14 @@ void ShapeInfoTests::testSphereShape() {
ShapeInfo info;
float radius = 1.23f;
info.setSphere(radius);
DoubleHashKey key = info.getHash();
HashKey key = info.getHash();
const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info);
QCOMPARE(shape != nullptr, true);
ShapeInfo otherInfo = info;
DoubleHashKey otherKey = otherInfo.getHash();
HashKey otherKey = otherInfo.getHash();
QCOMPARE(key.getHash(), otherKey.getHash());
QCOMPARE(key.getHash2(), otherKey.getHash2());
delete shape;
}
@ -182,15 +151,14 @@ void ShapeInfoTests::testCylinderShape() {
float radius = 1.23f;
float height = 4.56f;
info.setCylinder(radius, height);
DoubleHashKey key = info.getHash();
HashKey key = info.getHash();
btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info);
QCOMPARE(shape != nullptr, true);
ShapeInfo otherInfo = info;
DoubleHashKey otherKey = otherInfo.getHash();
HashKey otherKey = otherInfo.getHash();
QCOMPARE(key.getHash(), otherKey.getHash());
QCOMPARE(key.getHash2(), otherKey.getHash2());
delete shape;
*/
@ -202,15 +170,14 @@ void ShapeInfoTests::testCapsuleShape() {
float radius = 1.23f;
float height = 4.56f;
info.setCapsule(radius, height);
DoubleHashKey key = info.getHash();
HashKey key = info.getHash();
btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info);
QCOMPARE(shape != nullptr, true);
ShapeInfo otherInfo = info;
DoubleHashKey otherKey = otherInfo.getHash();
HashKey otherKey = otherInfo.getHash();
QCOMPARE(key.getHash(), otherKey.getHash());
QCOMPARE(key.getHash2(), otherKey.getHash2());
delete shape;
*/

View file

@ -18,10 +18,6 @@
//#include "BulletTestUtils.h"
//#include "../QTestExtensions.h"
// Enable this to manually run testHashCollisions
// (NOT a regular unit test; takes ~17 secs to run on an i7)
#define MANUAL_TEST false
class ShapeInfoTests : public QObject {
Q_OBJECT
private slots: