Merge branch 'master' of https://github.com/highfidelity/hifi into fixAgentAvatarAudio

This commit is contained in:
Brad Hefta-Gaub 2016-08-26 10:57:27 -07:00
commit 6e26916b65
90 changed files with 2498 additions and 982 deletions
interface/src
libraries
scripts
tests/physics

View file

@ -137,7 +137,7 @@ namespace MenuOption {
const QString Overlays = "Overlays";
const QString PackageModel = "Package Model...";
const QString Pair = "Pair";
const QString PhysicsShowHulls = "Draw Collision Hulls";
const QString PhysicsShowHulls = "Draw Collision Shapes";
const QString PhysicsShowOwned = "Highlight Simulation Ownership";
const QString PipelineWarnings = "Log Render Pipeline Warnings";
const QString Preferences = "General...";

View file

@ -367,7 +367,7 @@ void AvatarManager::addAvatarToSimulation(Avatar* avatar) {
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
// we don't add to the simulation now, we put it on a list to be added later
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);

View file

@ -17,7 +17,7 @@
#include "AvatarMotionState.h"
#include "BulletUtil.h"
AvatarMotionState::AvatarMotionState(Avatar* avatar, btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
AvatarMotionState::AvatarMotionState(Avatar* avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
assert(_avatar);
_type = MOTIONSTATE_TYPE_AVATAR;
if (_shape) {
@ -47,7 +47,7 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
}
// virtual and protected
btCollisionShape* AvatarMotionState::computeNewShape() {
const btCollisionShape* AvatarMotionState::computeNewShape() {
ShapeInfo shapeInfo;
_avatar->computeShapeInfo(shapeInfo);
return getShapeManager()->getShape(shapeInfo);

View file

@ -20,7 +20,7 @@ class Avatar;
class AvatarMotionState : public ObjectMotionState {
public:
AvatarMotionState(Avatar* avatar, btCollisionShape* shape);
AvatarMotionState(Avatar* avatar, const btCollisionShape* shape);
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
@ -72,7 +72,7 @@ protected:
~AvatarMotionState();
virtual bool isReadyToComputeShape() const override { return true; }
virtual btCollisionShape* computeNewShape() override;
virtual const btCollisionShape* computeNewShape() override;
// The AvatarMotionState keeps a RAW backpointer to its Avatar because all AvatarMotionState
// instances are "owned" by their corresponding Avatar instance and are deleted in the Avatar dtor.

View file

@ -28,6 +28,7 @@
static const float DPI = 30.47f;
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
QString const Web3DOverlay::TYPE = "web3d";
@ -106,7 +107,11 @@ void Web3DOverlay::render(RenderArgs* args) {
batch.setModelTransform(transform);
auto geometryCache = DependencyManager::get<GeometryCache>();
geometryCache->bindSimpleSRGBTexturedUnlitNoTexAlphaProgram(batch);
if (color.a < OPAQUE_ALPHA_THRESHOLD) {
geometryCache->bindTransparentWebBrowserProgram(batch);
} else {
geometryCache->bindOpaqueWebBrowserProgram(batch);
}
geometryCache->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color);
batch.setResourceTexture(0, args->_whiteTexture); // restore default white color after me
}

View file

@ -1,3 +1,5 @@
set(TARGET_NAME animation)
setup_hifi_library(Network Script)
link_hifi_libraries(shared model fbx)
target_nsight()

View file

@ -13,6 +13,7 @@
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <SharedUtil.h>
#include <shared/NsightHelpers.h>
#include "ElbowConstraint.h"
#include "SwingTwistConstraint.h"
@ -144,9 +145,11 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
accumulator.clearAndClean();
}
float maxError = FLT_MAX;
int numLoops = 0;
const int MAX_IK_LOOPS = 4;
while (numLoops < MAX_IK_LOOPS) {
const int MAX_IK_LOOPS = 16;
const float MAX_ERROR_TOLERANCE = 0.1f; // cm
while (maxError > MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) {
++numLoops;
// solve all targets
@ -173,6 +176,18 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
}
}
// compute maxError
maxError = 0.0f;
for (size_t i = 0; i < targets.size(); i++) {
if (targets[i].getType() == IKTarget::Type::RotationAndPosition || targets[i].getType() == IKTarget::Type::HmdHead ||
targets[i].getType() == IKTarget::Type::HipsRelativeRotationAndPosition) {
float error = glm::length(absolutePoses[targets[i].getIndex()].trans - targets[i].getTranslation());
if (error > maxError) {
maxError = error;
}
}
}
}
// finally set the relative rotation of each tip to agree with absolute target rotation
@ -285,8 +300,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
if (angle > MIN_ADJUSTMENT_ANGLE) {
// reduce angle by a fraction (for stability)
const float fraction = 0.5f;
angle *= fraction;
const float FRACTION = 0.5f;
angle *= FRACTION;
deltaRotation = glm::angleAxis(angle, axis);
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
@ -308,7 +323,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
float dotSign = copysignf(1.0f, twistPart.w);
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, fraction)) * deltaRotation;
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, FRACTION)) * deltaRotation;
}
}
}
@ -369,6 +384,7 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
//virtual
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
if (dt > MAX_OVERLAY_DT) {
dt = MAX_OVERLAY_DT;
@ -377,6 +393,9 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
if (_relativePoses.size() != underPoses.size()) {
loadPoses(underPoses);
} else {
PROFILE_RANGE_EX("ik/relax", 0xffff00ff, 0);
// relax toward underPoses
// HACK: this relaxation needs to be constant per-frame rather than per-realtime
// in order to prevent IK "flutter" for bad FPS. The bad news is that the good parts
@ -410,9 +429,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
}
if (!_relativePoses.empty()) {
// build a list of targets from _targetVarVec
std::vector<IKTarget> targets;
computeTargets(animVars, targets, underPoses);
{
PROFILE_RANGE_EX("ik/computeTargets", 0xffff00ff, 0);
computeTargets(animVars, targets, underPoses);
}
if (targets.empty()) {
// no IK targets but still need to enforce constraints
@ -425,64 +448,76 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
++constraintItr;
}
} else {
// shift hips according to the _hipsOffset from the previous frame
float offsetLength = glm::length(_hipsOffset);
const float MIN_HIPS_OFFSET_LENGTH = 0.03f;
if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) {
// but only if offset is long enough
float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength);
if (_hipsParentIndex == -1) {
// the hips are the root so _hipsOffset is in the correct frame
_relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans + scaleFactor * _hipsOffset;
} else {
// the hips are NOT the root so we need to transform _hipsOffset into hips local-frame
glm::quat hipsFrameRotation = _relativePoses[_hipsParentIndex].rot;
int index = _skeleton->getParentIndex(_hipsParentIndex);
while (index != -1) {
hipsFrameRotation *= _relativePoses[index].rot;
index = _skeleton->getParentIndex(index);
{
PROFILE_RANGE_EX("ik/shiftHips", 0xffff00ff, 0);
// shift hips according to the _hipsOffset from the previous frame
float offsetLength = glm::length(_hipsOffset);
const float MIN_HIPS_OFFSET_LENGTH = 0.03f;
if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) {
// but only if offset is long enough
float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength);
if (_hipsParentIndex == -1) {
// the hips are the root so _hipsOffset is in the correct frame
_relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans + scaleFactor * _hipsOffset;
} else {
// the hips are NOT the root so we need to transform _hipsOffset into hips local-frame
glm::quat hipsFrameRotation = _relativePoses[_hipsParentIndex].rot;
int index = _skeleton->getParentIndex(_hipsParentIndex);
while (index != -1) {
hipsFrameRotation *= _relativePoses[index].rot;
index = _skeleton->getParentIndex(index);
}
_relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans
+ glm::inverse(glm::normalize(hipsFrameRotation)) * (scaleFactor * _hipsOffset);
}
_relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans
+ glm::inverse(glm::normalize(hipsFrameRotation)) * (scaleFactor * _hipsOffset);
}
}
solveWithCyclicCoordinateDescent(targets);
// measure new _hipsOffset for next frame
// by looking for discrepancies between where a targeted endEffector is
// and where it wants to be (after IK solutions are done)
glm::vec3 newHipsOffset = Vectors::ZERO;
for (auto& target: targets) {
int targetIndex = target.getIndex();
if (targetIndex == _headIndex && _headIndex != -1) {
// special handling for headTarget
if (target.getType() == IKTarget::Type::RotationOnly) {
// we want to shift the hips to bring the underPose closer
// to where the head happens to be (overpose)
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans;
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under);
} else if (target.getType() == IKTarget::Type::HmdHead) {
// we want to shift the hips to bring the head to its designated position
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
_hipsOffset += target.getTranslation() - actual;
// and ignore all other targets
newHipsOffset = _hipsOffset;
break;
}
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans;
glm::vec3 targetPosition = target.getTranslation();
newHipsOffset += targetPosition - actualPosition;
}
{
PROFILE_RANGE_EX("ik/ccd", 0xffff00ff, 0);
solveWithCyclicCoordinateDescent(targets);
}
// smooth transitions by relaxing _hipsOffset toward the new value
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f;
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
_hipsOffset += (newHipsOffset - _hipsOffset) * tau;
{
PROFILE_RANGE_EX("ik/measureHipsOffset", 0xffff00ff, 0);
// measure new _hipsOffset for next frame
// by looking for discrepancies between where a targeted endEffector is
// and where it wants to be (after IK solutions are done)
glm::vec3 newHipsOffset = Vectors::ZERO;
for (auto& target: targets) {
int targetIndex = target.getIndex();
if (targetIndex == _headIndex && _headIndex != -1) {
// special handling for headTarget
if (target.getType() == IKTarget::Type::RotationOnly) {
// we want to shift the hips to bring the underPose closer
// to where the head happens to be (overpose)
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans;
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under);
} else if (target.getType() == IKTarget::Type::HmdHead) {
// we want to shift the hips to bring the head to its designated position
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
_hipsOffset += target.getTranslation() - actual;
// and ignore all other targets
newHipsOffset = _hipsOffset;
break;
}
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans;
glm::vec3 targetPosition = target.getTranslation();
newHipsOffset += targetPosition - actualPosition;
}
}
// smooth transitions by relaxing _hipsOffset toward the new value
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f;
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
_hipsOffset += (newHipsOffset - _hipsOffset) * tau;
}
}
}
return _relativePoses;

View file

@ -499,35 +499,18 @@ ModelPointer EntityTreeRenderer::getModelForEntityItem(EntityItemPointer entityI
return result;
}
const FBXGeometry* EntityTreeRenderer::getCollisionGeometryForEntity(EntityItemPointer entityItem) {
const FBXGeometry* result = NULL;
if (entityItem->getType() == EntityTypes::Model) {
std::shared_ptr<RenderableModelEntityItem> modelEntityItem =
std::dynamic_pointer_cast<RenderableModelEntityItem>(entityItem);
if (modelEntityItem->hasCompoundShapeURL()) {
ModelPointer model = modelEntityItem->getModel(this);
if (model && model->isCollisionLoaded()) {
result = &model->getCollisionFBXGeometry();
}
}
}
return result;
}
void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
std::static_pointer_cast<EntityTree>(_tree)->processEraseMessage(message, sourceNode);
}
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl, float loadingPriority) {
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, float loadingPriority) {
ModelPointer model = nullptr;
// Only create and delete models on the thread that owns the EntityTreeRenderer
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "allocateModel", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(ModelPointer, model),
Q_ARG(const QString&, url),
Q_ARG(const QString&, collisionUrl));
Q_ARG(const QString&, url));
return model;
}
@ -536,7 +519,6 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString
model->setLoadingPriority(loadingPriority);
model->init();
model->setURL(QUrl(url));
model->setCollisionModelURL(QUrl(collisionUrl));
return model;
}
@ -553,7 +535,6 @@ ModelPointer EntityTreeRenderer::updateModel(ModelPointer model, const QString&
}
model->setURL(QUrl(newUrl));
model->setCollisionModelURL(QUrl(collisionUrl));
return model;
}

View file

@ -65,7 +65,6 @@ public:
virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) override;
virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) override;
virtual const FBXGeometry* getCollisionGeometryForEntity(EntityItemPointer entityItem) override;
/// clears the tree
virtual void clear() override;
@ -74,7 +73,7 @@ public:
void reloadEntityScripts();
/// if a renderable entity item needs a model, we will allocate it for them
Q_INVOKABLE ModelPointer allocateModel(const QString& url, const QString& collisionUrl, float loadingPriority = 0.0f);
Q_INVOKABLE ModelPointer allocateModel(const QString& url, float loadingPriority = 0.0f);
/// if a renderable entity item needs to update the URL of a model, we will handle that for the entity
Q_INVOKABLE ModelPointer updateModel(ModelPointer original, const QString& newUrl, const QString& collisionUrl);

View file

@ -17,6 +17,7 @@
#include <glm/gtx/transform.hpp>
#include <AbstractViewStateInterface.h>
#include <CollisionRenderMeshCache.h>
#include <Model.h>
#include <PerfStat.h>
#include <render/Scene.h>
@ -28,6 +29,9 @@
#include "RenderableModelEntityItem.h"
#include "RenderableEntityItem.h"
static CollisionRenderMeshCache collisionMeshCache;
EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity{ new RenderableModelEntityItem(entityID, properties.getDimensionsInitialized()) };
entity->setProperties(properties);
@ -214,21 +218,21 @@ namespace render {
bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges) {
_myMetaItem = scene->allocateID();
auto renderData = std::make_shared<RenderableModelEntityItemMeta>(self);
auto renderPayload = std::make_shared<RenderableModelEntityItemMeta::Payload>(renderData);
pendingChanges.resetItem(_myMetaItem, renderPayload);
if (_model) {
render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
// note: we don't care if the model fails to add items, we always added our meta item and therefore we return
// true so that the system knows our meta item is in the scene!
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull);
// note: we don't mind if the model fails to add, we'll retry (in render()) until it succeeds
_model->addToScene(scene, pendingChanges, statusGetters);
}
// we've successfully added _myMetaItem so we always return true
return true;
}
@ -415,19 +419,35 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
// Remap textures for the next frame to avoid flicker
remapTextures();
// check to see if when we added our models to the scene they were ready, if they were not ready, then
// fix them up in the scene
bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0
&& getShapeType() == SHAPE_TYPE_COMPOUND;
if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) {
_showCollisionHull = shouldShowCollisionHull;
// update whether the model should be showing collision mesh (this may flag for fixupInScene)
bool showingCollisionGeometry = (bool)(args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS);
if (showingCollisionGeometry != _showCollisionGeometry) {
ShapeType type = getShapeType();
_showCollisionGeometry = showingCollisionGeometry;
if (_showCollisionGeometry && type != SHAPE_TYPE_STATIC_MESH && type != SHAPE_TYPE_NONE) {
// NOTE: it is OK if _collisionMeshKey is nullptr
model::MeshPointer mesh = collisionMeshCache.getMesh(_collisionMeshKey);
// NOTE: the model will render the collisionGeometry if it has one
_model->setCollisionMesh(mesh);
} else {
// release mesh
if (_collisionMeshKey) {
collisionMeshCache.releaseMesh(_collisionMeshKey);
}
// clear model's collision geometry
model::MeshPointer mesh = nullptr;
_model->setCollisionMesh(mesh);
}
}
if (_model->needsFixupInScene()) {
render::PendingChanges pendingChanges;
_model->removeFromScene(scene, pendingChanges);
render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull);
_model->addToScene(scene, pendingChanges, statusGetters);
scene->enqueuePendingChanges(pendingChanges);
}
@ -471,14 +491,13 @@ ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
if (!getModelURL().isEmpty()) {
// If we don't have a model, allocate one *immediately*
if (!_model) {
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL(), renderer->getEntityLoadingPriority(*this));
_model = _myRenderer->allocateModel(getModelURL(), renderer->getEntityLoadingPriority(*this));
_needsInitialSimulation = true;
// If we need to change URLs, update it *after rendering* (to avoid access violations)
} else if ((QUrl(getModelURL()) != _model->getURL() || QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) {
} else if (QUrl(getModelURL()) != _model->getURL()) {
QMetaObject::invokeMethod(_myRenderer, "updateModel", Qt::QueuedConnection,
Q_ARG(ModelPointer, _model),
Q_ARG(const QString&, getModelURL()),
Q_ARG(const QString&, getCompoundShapeURL()));
Q_ARG(const QString&, getModelURL()));
_needsInitialSimulation = true;
}
// Else we can just return the _model
@ -546,6 +565,18 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
face, surfaceNormal, extraInfo, precisionPicking);
}
void RenderableModelEntityItem::setShapeType(ShapeType type) {
ModelEntityItem::setShapeType(type);
if (_shapeType == SHAPE_TYPE_COMPOUND) {
if (!_compoundShapeResource && !_compoundShapeURL.isEmpty()) {
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(getCompoundShapeURL());
}
} else if (_compoundShapeResource && !_compoundShapeURL.isEmpty()) {
// the compoundURL has been set but the shapeType does not agree
_compoundShapeResource.reset();
}
}
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
auto currentCompoundShapeURL = getCompoundShapeURL();
ModelEntityItem::setCompoundShapeURL(url);
@ -555,6 +586,9 @@ void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
if (tree) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
}
if (_shapeType == SHAPE_TYPE_COMPOUND) {
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(url);
}
}
}
@ -562,7 +596,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
ShapeType type = getShapeType();
if (type == SHAPE_TYPE_COMPOUND) {
if (!_model || _model->getCollisionURL().isEmpty()) {
if (!_model || _compoundShapeURL.isEmpty()) {
EntityTreePointer tree = getTree();
if (tree) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
@ -575,15 +609,18 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
return false;
}
if (_model->isLoaded() && _model->isCollisionLoaded()) {
// we have both URLs AND both geometries AND they are both fully loaded.
if (_needsInitialSimulation) {
// the _model's offset will be wrong until _needsInitialSimulation is false
PerformanceTimer perfTimer("_model->simulate");
doInitialModelSimulation();
if (_model->isLoaded()) {
if (_compoundShapeResource && _compoundShapeResource->isLoaded()) {
// we have both URLs AND both geometries AND they are both fully loaded.
if (_needsInitialSimulation) {
// the _model's offset will be wrong until _needsInitialSimulation is false
PerformanceTimer perfTimer("_model->simulate");
doInitialModelSimulation();
}
return true;
} else if (!_compoundShapeURL.isEmpty()) {
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(_compoundShapeURL);
}
return true;
}
// the model is still being downloaded.
@ -594,7 +631,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
return true;
}
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
const uint32_t TRIANGLE_STRIDE = 3;
const uint32_t QUAD_STRIDE = 4;
@ -605,10 +642,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
// should never fall in here when collision model not fully loaded
// hence we assert that all geometries exist and are loaded
assert(_model && _model->isLoaded() && _model->isCollisionLoaded());
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded());
const FBXGeometry& collisionGeometry = _compoundShapeResource->getFBXGeometry();
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
pointCollection.clear();
uint32_t i = 0;
@ -684,15 +721,14 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size();
// multiply each point by scale before handing the point-set off to the physics engine.
// also determine the extents of the collision model.
glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint());
for (int32_t i = 0; i < pointCollection.size(); i++) {
for (int32_t j = 0; j < pointCollection[i].size(); j++) {
// compensate for registration
pointCollection[i][j] += _model->getOffset();
// scale so the collision points match the model points
pointCollection[i][j] *= scaleToFit;
// back compensate for registration so we can apply that offset to the shapeInfo later
pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + _model->getOffset()) - registrationOffset;
}
}
info.setParams(type, dimensions, _compoundShapeURL);
shapeInfo.setParams(type, dimensions, _compoundShapeURL);
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
// should never fall in here when model not fully loaded
assert(_model && _model->isLoaded());
@ -705,29 +741,31 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
const FBXGeometry& fbxGeometry = _model->getFBXGeometry();
int numFbxMeshes = fbxGeometry.meshes.size();
int totalNumVertices = 0;
glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
for (int i = 0; i < numFbxMeshes; i++) {
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
if (mesh.clusters.size() > 0) {
const FBXCluster& cluster = mesh.clusters.at(0);
auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex);
localTransforms.push_back(jointMatrix * cluster.inverseBindMatrix);
// we backtranslate by the registration offset so we can apply that offset to the shapeInfo later
localTransforms.push_back(invRegistraionOffset * jointMatrix * cluster.inverseBindMatrix);
} else {
glm::mat4 identity;
localTransforms.push_back(identity);
localTransforms.push_back(invRegistraionOffset);
}
totalNumVertices += mesh.vertices.size();
}
const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6;
if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) {
qWarning() << "model" << getModelURL() << "has too many vertices" << totalNumVertices << "and will collide as a box.";
info.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions);
shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions);
return;
}
auto& meshes = _model->getGeometry()->getMeshes();
int32_t numMeshes = (int32_t)(meshes.size());
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
pointCollection.clear();
if (type == SHAPE_TYPE_SIMPLE_COMPOUND) {
pointCollection.resize(numMeshes);
@ -735,7 +773,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
pointCollection.resize(1);
}
ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices();
triangleIndices.clear();
Extents extents;
@ -909,17 +947,30 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
}
}
info.setParams(type, 0.5f * dimensions, _modelURL);
shapeInfo.setParams(type, 0.5f * dimensions, _modelURL);
} else {
ModelEntityItem::computeShapeInfo(info);
info.setParams(type, 0.5f * dimensions);
adjustShapeInfoByRegistration(info);
ModelEntityItem::computeShapeInfo(shapeInfo);
shapeInfo.setParams(type, 0.5f * dimensions);
}
// finally apply the registration offset to the shapeInfo
adjustShapeInfoByRegistration(shapeInfo);
}
void RenderableModelEntityItem::setCollisionShape(const btCollisionShape* shape) {
const void* key = static_cast<const void*>(shape);
if (_collisionMeshKey != key) {
if (_collisionMeshKey) {
collisionMeshCache.releaseMesh(_collisionMeshKey);
}
_collisionMeshKey = key;
// toggle _showCollisionGeometry forces re-evaluation later
_showCollisionGeometry = !_showCollisionGeometry;
}
}
bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
if (EntityItem::contains(point) && _model && _model->isCollisionLoaded()) {
return _model->getCollisionFBXGeometry().convexHullContains(worldToEntity(point));
if (EntityItem::contains(point) && _model && _compoundShapeResource && _compoundShapeResource->isLoaded()) {
return _compoundShapeResource->getFBXGeometry().convexHullContains(worldToEntity(point));
}
return false;

View file

@ -56,10 +56,13 @@ public:
virtual bool needsToCallUpdate() const override;
virtual void update(const quint64& now) override;
virtual void setShapeType(ShapeType type) override;
virtual void setCompoundShapeURL(const QString& url) override;
virtual bool isReadyToComputeShape() override;
virtual void computeShapeInfo(ShapeInfo& info) override;
virtual void computeShapeInfo(ShapeInfo& shapeInfo) override;
void setCollisionShape(const btCollisionShape* shape) override;
virtual bool contains(const glm::vec3& point) const override;
@ -98,6 +101,7 @@ private:
QVariantMap parseTexturesToMap(QString textures);
void remapTextures();
GeometryResource::Pointer _compoundShapeResource;
ModelPointer _model = nullptr;
bool _needsInitialSimulation = true;
bool _needsModelReload = true;
@ -112,11 +116,11 @@ private:
render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
bool _showCollisionHull = false;
bool getAnimationFrame();
bool _needsJointSimulation { false };
bool _showCollisionGeometry { false };
const void* _collisionMeshKey { nullptr };
};
#endif // hifi_RenderableModelEntityItem_h

View file

@ -35,6 +35,7 @@ static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 100;
static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
static int MAX_WINDOW_SIZE = 4096;
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity{ new RenderableWebEntityItem(entityID) };
@ -144,7 +145,7 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
glm::vec2 windowSize = getWindowSize();
// The offscreen surface is idempotent for resizes (bails early
// if it's a no-op), so it's safe to just call resize every frame
// if it's a no-op), so it's safe to just call resize every frame
// without worrying about excessive overhead.
_webSurface->resize(QSize(windowSize.x, windowSize.y));
@ -164,9 +165,14 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
}
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio);
DependencyManager::get<GeometryCache>()->bindSimpleSRGBTexturedUnlitNoTexAlphaProgram(batch);
if (fadeRatio < OPAQUE_ALPHA_THRESHOLD) {
DependencyManager::get<GeometryCache>()->bindTransparentWebBrowserProgram(batch);
} else {
DependencyManager::get<GeometryCache>()->bindOpaqueWebBrowserProgram(batch);
}
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio));
}
@ -291,3 +297,9 @@ void RenderableWebEntityItem::update(const quint64& now) {
destroyWebSurface();
}
}
bool RenderableWebEntityItem::isTransparent() {
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
return fadeRatio < OPAQUE_ALPHA_THRESHOLD;
}

View file

@ -44,6 +44,8 @@ public:
SIMPLE_RENDERABLE();
virtual bool isTransparent() override;
private:
bool buildWebSurface(EntityTreeRenderer* renderer);
void destroyWebSurface();

View file

@ -122,7 +122,7 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
_model->removeFromScene(scene, pendingChanges);
render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
_model->addToScene(scene, pendingChanges, false);
_model->addToScene(scene, pendingChanges);
scene->enqueuePendingChanges(pendingChanges);

View file

@ -2213,4 +2213,4 @@ void EntityItem::globalizeProperties(EntityItemProperties& properties, const QSt
}
QUuid empty;
properties.setParentID(empty);
}
}

View file

@ -44,6 +44,7 @@ class EntityTreeElementExtraEncodeData;
class EntityActionInterface;
class EntityItemProperties;
class EntityTree;
class btCollisionShape;
typedef std::shared_ptr<EntityTree> EntityTreePointer;
typedef std::shared_ptr<EntityActionInterface> EntityActionPointer;
typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer;
@ -324,6 +325,8 @@ public:
/// return preferred shape type (actual physical shape may differ)
virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; }
virtual void setCollisionShape(const btCollisionShape* shape) {}
// updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags
virtual void updateRegistrationPoint(const glm::vec3& value);
void updatePosition(const glm::vec3& value);

View file

@ -40,7 +40,6 @@ class EntityItemFBXService {
public:
virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) = 0;
virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) = 0;
virtual const FBXGeometry* getCollisionGeometryForEntity(EntityItemPointer entityItem) = 0;
};

View file

@ -282,8 +282,8 @@ ShapeType ModelEntityItem::computeTrueShapeType() const {
type = SHAPE_TYPE_COMPOUND;
}
if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) {
// no compoundURL set --> fall back to NONE
type = SHAPE_TYPE_NONE;
// no compoundURL set --> fall back to SIMPLE_COMPOUND
type = SHAPE_TYPE_SIMPLE_COMPOUND;
}
return type;
}

View file

@ -180,10 +180,10 @@ public:
using Index = int;
BufferPointer _buffer;
Size _offset;
Size _size;
Element _element;
uint16 _stride;
Size _offset { 0 };
Size _size { 0 };
Element _element { DEFAULT_ELEMENT };
uint16 _stride { 0 };
BufferView(const BufferView& view) = default;
BufferView& operator=(const BufferView& view) = default;

View file

@ -111,13 +111,13 @@ public:
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
signals:
void finished(bool success);
private:
void startWatching();
void stopWatching();
signals:
void finished(bool success);
private slots:
void resourceFinished(bool success);
void resourceRefreshed();

View file

@ -130,7 +130,7 @@ protected:
void evalVertexStream();
};
typedef std::shared_ptr< Mesh > MeshPointer;
using MeshPointer = std::shared_ptr< Mesh >;
class Geometry {

View file

@ -226,8 +226,6 @@ private:
void resetResourceCounters();
void removeResource(const QUrl& url, qint64 size = 0);
void getResourceAsynchronously(const QUrl& url);
static int _requestLimit;
static int _requestsActive;

View file

@ -1,5 +1,5 @@
set(TARGET_NAME physics)
setup_hifi_library()
link_hifi_libraries(shared fbx entities)
link_hifi_libraries(shared fbx entities model)
target_bullet()

View file

@ -0,0 +1,213 @@
//
// CollisionRenderMeshCache.cpp
// libraries/physcis/src
//
// Created by Andrew Meadows 2016.07.13
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CollisionRenderMeshCache.h"
#include <cassert>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionShapes/btShapeHull.h>
#include <ShapeInfo.h> // for MAX_HULL_POINTS
const int32_t MAX_HULL_INDICES = 6 * MAX_HULL_POINTS;
const int32_t MAX_HULL_NORMALS = MAX_HULL_INDICES;
float tempVertices[MAX_HULL_NORMALS];
model::Index tempIndexBuffer[MAX_HULL_INDICES];
bool copyShapeToMesh(const btTransform& transform, const btConvexShape* shape,
gpu::BufferView& vertices, gpu::BufferView& indices, gpu::BufferView& parts,
gpu::BufferView& normals) {
assert(shape);
btShapeHull hull(shape);
if (!hull.buildHull(shape->getMargin())) {
return false;
}
int32_t numHullIndices = hull.numIndices();
assert(numHullIndices <= MAX_HULL_INDICES);
int32_t numHullVertices = hull.numVertices();
assert(numHullVertices <= MAX_HULL_POINTS);
{ // new part
model::Mesh::Part part;
part._startIndex = (model::Index)indices.getNumElements();
part._numIndices = (model::Index)numHullIndices;
// FIXME: the render code cannot handle the case where part._baseVertex != 0
//part._baseVertex = vertices.getNumElements(); // DOES NOT WORK
part._baseVertex = 0;
gpu::BufferView::Size numBytes = sizeof(model::Mesh::Part);
const gpu::Byte* data = reinterpret_cast<const gpu::Byte*>(&part);
parts._buffer->append(numBytes, data);
parts._size = parts._buffer->getSize();
}
const int32_t SIZE_OF_VEC3 = 3 * sizeof(float);
model::Index indexOffset = (model::Index)vertices.getNumElements();
{ // new indices
const uint32_t* hullIndices = hull.getIndexPointer();
// FIXME: the render code cannot handle the case where part._baseVertex != 0
// so we must add an offset to each index
for (int32_t i = 0; i < numHullIndices; ++i) {
tempIndexBuffer[i] = hullIndices[i] + indexOffset;
}
const gpu::Byte* data = reinterpret_cast<const gpu::Byte*>(tempIndexBuffer);
gpu::BufferView::Size numBytes = (gpu::BufferView::Size)(sizeof(model::Index) * numHullIndices);
indices._buffer->append(numBytes, data);
indices._size = indices._buffer->getSize();
}
{ // new vertices
const btVector3* hullVertices = hull.getVertexPointer();
assert(numHullVertices <= MAX_HULL_POINTS);
for (int32_t i = 0; i < numHullVertices; ++i) {
btVector3 transformedPoint = transform * hullVertices[i];
memcpy(tempVertices + 3 * i, transformedPoint.m_floats, SIZE_OF_VEC3);
}
gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices);
const gpu::Byte* data = reinterpret_cast<const gpu::Byte*>(tempVertices);
vertices._buffer->append(numBytes, data);
vertices._size = vertices._buffer->getSize();
}
{ // new normals
// compute average point
btVector3 avgVertex(0.0f, 0.0f, 0.0f);
const btVector3* hullVertices = hull.getVertexPointer();
for (int i = 0; i < numHullVertices; ++i) {
avgVertex += hullVertices[i];
}
avgVertex = transform * (avgVertex * (1.0f / (float)numHullVertices));
for (int i = 0; i < numHullVertices; ++i) {
btVector3 norm = (transform * hullVertices[i] - avgVertex).normalize();
memcpy(tempVertices + 3 * i, norm.m_floats, SIZE_OF_VEC3);
}
gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices);
const gpu::Byte* data = reinterpret_cast<const gpu::Byte*>(tempVertices);
normals._buffer->append(numBytes, data);
normals._size = vertices._buffer->getSize();
}
return true;
}
model::MeshPointer createMeshFromShape(const void* pointer) {
model::MeshPointer mesh;
if (!pointer) {
return mesh;
}
// pointer must be a const btCollisionShape* (cast to void*), but it only
// needs to be valid here when its render mesh is created, after this call
// the cache doesn't care what happens to the shape behind the pointer
const btCollisionShape* shape = static_cast<const btCollisionShape*>(pointer);
int32_t shapeType = shape->getShapeType();
if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE || shape->isConvex()) {
// allocate buffers for it
gpu::BufferView vertices(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
gpu::BufferView indices(new gpu::Buffer(), gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::INDEX));
gpu::BufferView parts(new gpu::Buffer(), gpu::Element(gpu::VEC4, gpu::UINT32, gpu::PART));
gpu::BufferView normals(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
int32_t numSuccesses = 0;
if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE) {
const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shape);
int32_t numSubShapes = compoundShape->getNumChildShapes();
for (int32_t i = 0; i < numSubShapes; ++i) {
const btCollisionShape* childShape = compoundShape->getChildShape(i);
if (childShape->isConvex()) {
const btConvexShape* convexShape = static_cast<const btConvexShape*>(childShape);
if (copyShapeToMesh(compoundShape->getChildTransform(i), convexShape, vertices, indices, parts, normals)) {
numSuccesses++;
}
}
}
} else {
// shape is convex
const btConvexShape* convexShape = static_cast<const btConvexShape*>(shape);
btTransform transform;
transform.setIdentity();
if (copyShapeToMesh(transform, convexShape, vertices, indices, parts, normals)) {
numSuccesses++;
}
}
if (numSuccesses > 0) {
mesh = std::make_shared<model::Mesh>();
mesh->setVertexBuffer(vertices);
mesh->setIndexBuffer(indices);
mesh->setPartBuffer(parts);
mesh->addAttribute(gpu::Stream::NORMAL, normals);
} else {
// TODO: log failure message here
}
}
return mesh;
}
CollisionRenderMeshCache::CollisionRenderMeshCache() {
}
CollisionRenderMeshCache::~CollisionRenderMeshCache() {
_meshMap.clear();
_pendingGarbage.clear();
}
model::MeshPointer CollisionRenderMeshCache::getMesh(CollisionRenderMeshCache::Key key) {
model::MeshPointer mesh;
if (key) {
CollisionMeshMap::const_iterator itr = _meshMap.find(key);
if (itr == _meshMap.end()) {
// make mesh and add it to map
mesh = createMeshFromShape(key);
if (mesh) {
_meshMap.insert(std::make_pair(key, mesh));
}
} else {
mesh = itr->second;
}
}
const uint32_t MAX_NUM_PENDING_GARBAGE = 20;
if (_pendingGarbage.size() > MAX_NUM_PENDING_GARBAGE) {
collectGarbage();
}
return mesh;
}
bool CollisionRenderMeshCache::releaseMesh(CollisionRenderMeshCache::Key key) {
if (!key) {
return false;
}
CollisionMeshMap::const_iterator itr = _meshMap.find(key);
if (itr != _meshMap.end()) {
_pendingGarbage.push_back(key);
return true;
}
return false;
}
void CollisionRenderMeshCache::collectGarbage() {
uint32_t numShapes = (uint32_t)_pendingGarbage.size();
for (uint32_t i = 0; i < numShapes; ++i) {
CollisionRenderMeshCache::Key key = _pendingGarbage[i];
CollisionMeshMap::const_iterator itr = _meshMap.find(key);
if (itr != _meshMap.end()) {
if ((*itr).second.use_count() == 1) {
// we hold the only reference
_meshMap.erase(itr);
}
}
}
_pendingGarbage.clear();
}

View file

@ -0,0 +1,48 @@
//
// CollisionRenderMeshCache.h
// libraries/physcis/src
//
// Created by Andrew Meadows 2016.07.13
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CollisionRenderMeshCache_h
#define hifi_CollisionRenderMeshCache_h
#include <memory>
#include <vector>
#include <unordered_map>
#include <model/Geometry.h>
class CollisionRenderMeshCache {
public:
using Key = const void*; // must actually be a const btCollisionShape*
CollisionRenderMeshCache();
~CollisionRenderMeshCache();
/// \return pointer to geometry
model::MeshPointer getMesh(Key key);
/// \return true if geometry was found and released
bool releaseMesh(Key key);
/// delete geometries that have zero references
void collectGarbage();
// validation methods
uint32_t getNumMeshes() const { return (uint32_t)_meshMap.size(); }
bool hasMesh(Key key) const { return _meshMap.find(key) == _meshMap.end(); }
private:
using CollisionMeshMap = std::unordered_map<Key, model::MeshPointer>;
CollisionMeshMap _meshMap;
std::vector<Key> _pendingGarbage;
};
#endif // hifi_CollisionRenderMeshCache_h

View file

@ -46,7 +46,7 @@ bool entityTreeIsLocked() {
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) :
ObjectMotionState(shape),
ObjectMotionState(nullptr),
_entityPtr(entity),
_entity(entity.get()),
_serverPosition(0.0f),
@ -71,6 +71,9 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
assert(_entity);
assert(entityTreeIsLocked());
setMass(_entity->computeMass());
// we need the side-effects of EntityMotionState::setShape() so we call it explicitly here
// rather than pass the legit shape pointer to the ObjectMotionState ctor above.
setShape(shape);
}
EntityMotionState::~EntityMotionState() {
@ -264,13 +267,20 @@ bool EntityMotionState::isReadyToComputeShape() const {
}
// virtual and protected
btCollisionShape* EntityMotionState::computeNewShape() {
const btCollisionShape* EntityMotionState::computeNewShape() {
ShapeInfo shapeInfo;
assert(entityTreeIsLocked());
_entity->computeShapeInfo(shapeInfo);
return getShapeManager()->getShape(shapeInfo);
}
void EntityMotionState::setShape(const btCollisionShape* shape) {
if (_shape != shape) {
ObjectMotionState::setShape(shape);
_entity->setCollisionShape(_shape);
}
}
bool EntityMotionState::isCandidateForOwnership() const {
assert(_body);
assert(_entity);

View file

@ -88,9 +88,10 @@ protected:
bool entityTreeIsLocked() const;
#endif
virtual bool isReadyToComputeShape() const override;
virtual btCollisionShape* computeNewShape() override;
virtual void setMotionType(PhysicsMotionType motionType);
bool isReadyToComputeShape() const override;
const btCollisionShape* computeNewShape() override;
void setShape(const btCollisionShape* shape) override;
void setMotionType(PhysicsMotionType motionType) override;
// In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be
// properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that

View file

@ -62,7 +62,7 @@ ShapeManager* ObjectMotionState::getShapeManager() {
return shapeManager;
}
ObjectMotionState::ObjectMotionState(btCollisionShape* shape) :
ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) :
_motionType(MOTION_TYPE_STATIC),
_shape(shape),
_body(nullptr),
@ -73,7 +73,7 @@ ObjectMotionState::ObjectMotionState(btCollisionShape* shape) :
ObjectMotionState::~ObjectMotionState() {
assert(!_body);
releaseShape();
setShape(nullptr);
_type = MOTIONSTATE_TYPE_INVALID;
}
@ -114,13 +114,6 @@ glm::vec3 ObjectMotionState::getBodyAngularVelocity() const {
return bulletToGLM(_body->getAngularVelocity());
}
void ObjectMotionState::releaseShape() {
if (_shape) {
shapeManager->releaseShape(_shape);
_shape = nullptr;
}
}
void ObjectMotionState::setMotionType(PhysicsMotionType motionType) {
_motionType = motionType;
}
@ -160,11 +153,21 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) {
_body = body;
if (_body) {
_body->setUserPointer(this);
assert(_body->getCollisionShape() == _shape);
}
updateCCDConfiguration();
}
}
void ObjectMotionState::setShape(const btCollisionShape* shape) {
if (_shape != shape) {
if (_shape) {
getShapeManager()->releaseShape(_shape);
}
_shape = shape;
}
}
void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
if (flags & Simulation::DIRTY_POSITION) {
btTransform worldTrans = _body->getWorldTransform();
@ -251,7 +254,7 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine*
if (!isReadyToComputeShape()) {
return false;
}
btCollisionShape* newShape = computeNewShape();
const btCollisionShape* newShape = computeNewShape();
if (!newShape) {
qCDebug(physics) << "Warning: failed to generate new shape!";
// failed to generate new shape! --> keep old shape and remove shape-change flag
@ -265,15 +268,15 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine*
return true;
}
}
getShapeManager()->releaseShape(_shape);
if (_shape != newShape) {
_shape = newShape;
_body->setCollisionShape(_shape);
updateCCDConfiguration();
} else {
// huh... the shape didn't actually change, so we clear the DIRTY_SHAPE flag
if (_shape == newShape) {
// the shape didn't actually change, so we clear the DIRTY_SHAPE flag
flags &= ~Simulation::DIRTY_SHAPE;
// and clear the reference we just created
getShapeManager()->releaseShape(_shape);
} else {
_body->setCollisionShape(const_cast<btCollisionShape*>(newShape));
setShape(newShape);
updateCCDConfiguration();
}
}
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {

View file

@ -78,7 +78,7 @@ public:
static void setShapeManager(ShapeManager* manager);
static ShapeManager* getShapeManager();
ObjectMotionState(btCollisionShape* shape);
ObjectMotionState(const btCollisionShape* shape);
~ObjectMotionState();
virtual void handleEasyChanges(uint32_t& flags);
@ -110,11 +110,9 @@ public:
virtual PhysicsMotionType computePhysicsMotionType() const = 0;
btCollisionShape* getShape() const { return _shape; }
const btCollisionShape* getShape() const { return _shape; }
btRigidBody* getRigidBody() const { return _body; }
void releaseShape();
virtual bool isMoving() const = 0;
// These pure virtual methods must be implemented for each MotionState type
@ -152,16 +150,17 @@ public:
protected:
virtual bool isReadyToComputeShape() const = 0;
virtual btCollisionShape* computeNewShape() = 0;
void setMotionType(PhysicsMotionType motionType);
virtual const btCollisionShape* computeNewShape() = 0;
virtual void setMotionType(PhysicsMotionType motionType);
void updateCCDConfiguration();
void setRigidBody(btRigidBody* body);
virtual void setShape(const btCollisionShape* shape);
MotionStateType _type = MOTIONSTATE_TYPE_INVALID; // type of MotionState
PhysicsMotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC
btCollisionShape* _shape;
const btCollisionShape* _shape;
btRigidBody* _body;
float _mass;

View file

@ -225,7 +225,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
<< "at" << entity->getPosition() << " will be reduced";
}
}
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
EntityMotionState* motionState = new EntityMotionState(shape, entity);
entity->setPhysicsInfo(static_cast<void*>(motionState));

View file

@ -76,7 +76,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
switch(motionType) {
case MOTION_TYPE_KINEMATIC: {
if (!body) {
btCollisionShape* shape = motionState->getShape();
btCollisionShape* shape = const_cast<btCollisionShape*>(motionState->getShape());
assert(shape);
body = new btRigidBody(mass, motionState, shape, inertia);
motionState->setRigidBody(body);
@ -93,7 +93,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
}
case MOTION_TYPE_DYNAMIC: {
mass = motionState->getMass();
btCollisionShape* shape = motionState->getShape();
btCollisionShape* shape = const_cast<btCollisionShape*>(motionState->getShape());
assert(shape);
shape->calculateLocalInertia(mass, inertia);
if (!body) {
@ -120,7 +120,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
default: {
if (!body) {
assert(motionState->getShape());
body = new btRigidBody(mass, motionState, motionState->getShape(), inertia);
body = new btRigidBody(mass, motionState, const_cast<btCollisionShape*>(motionState->getShape()), inertia);
motionState->setRigidBody(body);
} else {
body->setMassProps(mass, inertia);

View file

@ -10,7 +10,6 @@
//
#include <glm/gtx/norm.hpp>
#include <BulletCollision/CollisionShapes/btShapeHull.h>
#include <SharedUtil.h> // for MILLIMETERS_PER_METER
@ -248,7 +247,7 @@ void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) {
delete dataArray;
}
btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
btCollisionShape* shape = NULL;
int type = info.getType();
switch(type) {
@ -347,23 +346,39 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
}
if (shape) {
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
// this shape has an offset, which we support by wrapping the true shape
// in a btCompoundShape with a local transform
auto compound = new btCompoundShape();
btTransform trans;
trans.setIdentity();
trans.setOrigin(glmToBullet(info.getOffset()));
compound->addChildShape(trans, shape);
shape = compound;
// we need to apply an offset
btTransform offset;
offset.setIdentity();
offset.setOrigin(glmToBullet(info.getOffset()));
if (shape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
// this shape is already compound
// walk through the child shapes and adjust their transforms
btCompoundShape* compound = static_cast<btCompoundShape*>(shape);
int32_t numSubShapes = compound->getNumChildShapes();
for (int32_t i = 0; i < numSubShapes; ++i) {
compound->updateChildTransform(i, offset * compound->getChildTransform(i), false);
}
compound->recalculateLocalAabb();
} else {
// wrap this shape in a compound
auto compound = new btCompoundShape();
compound->addChildShape(offset, shape);
shape = compound;
}
}
}
return shape;
}
void ShapeFactory::deleteShape(btCollisionShape* shape) {
void ShapeFactory::deleteShape(const btCollisionShape* shape) {
assert(shape);
if (shape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(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);
const int numChildShapes = compoundShape->getNumChildShapes();
for (int i = 0; i < numChildShapes; i ++) {
btCollisionShape* childShape = compoundShape->getChildShape(i);
@ -375,7 +390,7 @@ void ShapeFactory::deleteShape(btCollisionShape* shape) {
}
}
}
delete shape;
delete nonConstShape;
}
// the dataArray must be created before we create the StaticMeshShape

View file

@ -20,8 +20,8 @@
// translates between ShapeInfo and btShape
namespace ShapeFactory {
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
void deleteShape(btCollisionShape* shape);
const btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
void deleteShape(const btCollisionShape* shape);
//btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info);
//void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray);

View file

@ -28,15 +28,15 @@ ShapeManager::~ShapeManager() {
_shapeMap.clear();
}
btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
if (info.getType() == SHAPE_TYPE_NONE) {
return NULL;
return nullptr;
}
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) {
// tiny shapes are not supported
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL;
return nullptr;
}
DoubleHashKey key = info.getHash();
@ -45,7 +45,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
shapeRef->refCount++;
return shapeRef->shape;
}
btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info);
const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info);
if (shape) {
ShapeReference newRef;
newRef.refCount = 1;

View file

@ -26,7 +26,7 @@ public:
~ShapeManager();
/// \return pointer to shape
btCollisionShape* getShape(const ShapeInfo& info);
const btCollisionShape* getShape(const ShapeInfo& info);
/// \return true if shape was found and released
bool releaseShape(const btCollisionShape* shape);
@ -43,11 +43,12 @@ public:
private:
bool releaseShapeByKey(const DoubleHashKey& key);
struct ShapeReference {
class ShapeReference {
public:
int refCount;
btCollisionShape* shape;
const btCollisionShape* shape;
DoubleHashKey key;
ShapeReference() : refCount(0), shape(NULL) {}
ShapeReference() : refCount(0), shape(nullptr) {}
};
btHashMap<DoubleHashKey, ShapeReference> _shapeMap;

View file

@ -30,9 +30,88 @@
#include "ssao_makePyramid_frag.h"
#include "ssao_makeOcclusion_frag.h"
#include "ssao_debugOcclusion_frag.h"
#include "ssao_makeHorizontalBlur_frag.h"
#include "ssao_makeVerticalBlur_frag.h"
AmbientOcclusionFramebuffer::AmbientOcclusionFramebuffer() {
}
void AmbientOcclusionFramebuffer::updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer) {
//If the depth buffer or size changed, we need to delete our FBOs
bool reset = false;
if ((_linearDepthTexture != linearDepthBuffer)) {
_linearDepthTexture = linearDepthBuffer;
reset = true;
}
if (_linearDepthTexture) {
auto newFrameSize = glm::ivec2(_linearDepthTexture->getDimensions());
if (_frameSize != newFrameSize) {
_frameSize = newFrameSize;
reset = true;
}
}
if (reset) {
clear();
}
}
void AmbientOcclusionFramebuffer::clear() {
_occlusionFramebuffer.reset();
_occlusionTexture.reset();
_occlusionBlurredFramebuffer.reset();
_occlusionBlurredTexture.reset();
}
gpu::TexturePointer AmbientOcclusionFramebuffer::getLinearDepthTexture() {
return _linearDepthTexture;
}
void AmbientOcclusionFramebuffer::allocate() {
auto width = _frameSize.x;
auto height = _frameSize.y;
_occlusionTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_occlusionFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
_occlusionFramebuffer->setRenderBuffer(0, _occlusionTexture);
_occlusionBlurredTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_occlusionBlurredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
_occlusionBlurredFramebuffer->setRenderBuffer(0, _occlusionBlurredTexture);
}
gpu::FramebufferPointer AmbientOcclusionFramebuffer::getOcclusionFramebuffer() {
if (!_occlusionFramebuffer) {
allocate();
}
return _occlusionFramebuffer;
}
gpu::TexturePointer AmbientOcclusionFramebuffer::getOcclusionTexture() {
if (!_occlusionTexture) {
allocate();
}
return _occlusionTexture;
}
gpu::FramebufferPointer AmbientOcclusionFramebuffer::getOcclusionBlurredFramebuffer() {
if (!_occlusionBlurredFramebuffer) {
allocate();
}
return _occlusionBlurredFramebuffer;
}
gpu::TexturePointer AmbientOcclusionFramebuffer::getOcclusionBlurredTexture() {
if (!_occlusionBlurredTexture) {
allocate();
}
return _occlusionBlurredTexture;
}
class GaussianDistribution {
public:
@ -90,15 +169,11 @@ public:
const int AmbientOcclusionEffect_FrameTransformSlot = 0;
const int AmbientOcclusionEffect_ParamsSlot = 1;
const int AmbientOcclusionEffect_DepthMapSlot = 0;
const int AmbientOcclusionEffect_PyramidMapSlot = 0;
const int AmbientOcclusionEffect_CameraCorrectionSlot = 2;
const int AmbientOcclusionEffect_LinearDepthMapSlot = 0;
const int AmbientOcclusionEffect_OcclusionMapSlot = 0;
AmbientOcclusionEffect::AmbientOcclusionEffect() {
FrameTransform frameTransform;
_frameTransformBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(FrameTransform), (const gpu::Byte*) &frameTransform));
Parameters parameters;
_parametersBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Parameters), (const gpu::Byte*) &parameters));
}
void AmbientOcclusionEffect::configure(const Config& config) {
@ -108,67 +183,75 @@ void AmbientOcclusionEffect::configure(const Config& config) {
const double RADIUS_POWER = 6.0;
const auto& radius = config.radius;
if (radius != getRadius()) {
auto& current = _parametersBuffer.edit<Parameters>().radiusInfo;
if (radius != _parametersBuffer->getRadius()) {
auto& current = _parametersBuffer->radiusInfo;
current.x = radius;
current.y = radius * radius;
current.z = (float)(1.0 / pow((double)radius, RADIUS_POWER));
}
if (config.obscuranceLevel != getObscuranceLevel()) {
auto& current = _parametersBuffer.edit<Parameters>().radiusInfo;
if (config.obscuranceLevel != _parametersBuffer->getObscuranceLevel()) {
auto& current = _parametersBuffer->radiusInfo;
current.w = config.obscuranceLevel;
}
if (config.falloffBias != getFalloffBias()) {
auto& current = _parametersBuffer.edit<Parameters>().ditheringInfo;
if (config.falloffBias != _parametersBuffer->getFalloffBias()) {
auto& current = _parametersBuffer->ditheringInfo;
current.z = config.falloffBias;
}
if (config.edgeSharpness != getEdgeSharpness()) {
auto& current = _parametersBuffer.edit<Parameters>().blurInfo;
if (config.edgeSharpness != _parametersBuffer->getEdgeSharpness()) {
auto& current = _parametersBuffer->blurInfo;
current.x = config.edgeSharpness;
}
if (config.blurDeviation != getBlurDeviation()) {
auto& current = _parametersBuffer.edit<Parameters>().blurInfo;
if (config.blurDeviation != _parametersBuffer->getBlurDeviation()) {
auto& current = _parametersBuffer->blurInfo;
current.z = config.blurDeviation;
shouldUpdateGaussian = true;
}
if (config.numSpiralTurns != getNumSpiralTurns()) {
auto& current = _parametersBuffer.edit<Parameters>().sampleInfo;
if (config.numSpiralTurns != _parametersBuffer->getNumSpiralTurns()) {
auto& current = _parametersBuffer->sampleInfo;
current.z = config.numSpiralTurns;
}
if (config.numSamples != getNumSamples()) {
auto& current = _parametersBuffer.edit<Parameters>().sampleInfo;
if (config.numSamples != _parametersBuffer->getNumSamples()) {
auto& current = _parametersBuffer->sampleInfo;
current.x = config.numSamples;
current.y = 1.0f / config.numSamples;
}
const auto& resolutionLevel = config.resolutionLevel;
if (resolutionLevel != getResolutionLevel()) {
auto& current = _parametersBuffer.edit<Parameters>().resolutionInfo;
current.x = (float)resolutionLevel;
// Communicate the change to the Framebuffer cache
DependencyManager::get<FramebufferCache>()->setAmbientOcclusionResolutionLevel(resolutionLevel);
if (config.fetchMipsEnabled != _parametersBuffer->isFetchMipsEnabled()) {
auto& current = _parametersBuffer->sampleInfo;
current.w = (float)config.fetchMipsEnabled;
}
if (config.blurRadius != getBlurRadius()) {
auto& current = _parametersBuffer.edit<Parameters>().blurInfo;
if (!_framebuffer) {
_framebuffer = std::make_shared<AmbientOcclusionFramebuffer>();
}
if (config.perspectiveScale != _parametersBuffer->getPerspectiveScale()) {
_parametersBuffer->resolutionInfo.z = config.perspectiveScale;
}
if (config.resolutionLevel != _parametersBuffer->getResolutionLevel()) {
auto& current = _parametersBuffer->resolutionInfo;
current.x = (float) config.resolutionLevel;
}
if (config.blurRadius != _parametersBuffer->getBlurRadius()) {
auto& current = _parametersBuffer->blurInfo;
current.y = (float)config.blurRadius;
shouldUpdateGaussian = true;
}
if (config.ditheringEnabled != isDitheringEnabled()) {
auto& current = _parametersBuffer.edit<Parameters>().ditheringInfo;
if (config.ditheringEnabled != _parametersBuffer->isDitheringEnabled()) {
auto& current = _parametersBuffer->ditheringInfo;
current.x = (float)config.ditheringEnabled;
}
if (config.borderingEnabled != isBorderingEnabled()) {
auto& current = _parametersBuffer.edit<Parameters>().ditheringInfo;
if (config.borderingEnabled != _parametersBuffer->isBorderingEnabled()) {
auto& current = _parametersBuffer->ditheringInfo;
current.w = (float)config.borderingEnabled;
}
@ -177,32 +260,6 @@ void AmbientOcclusionEffect::configure(const Config& config) {
}
}
const gpu::PipelinePointer& AmbientOcclusionEffect::getPyramidPipeline() {
if (!_pyramidPipeline) {
auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS();
auto ps = gpu::Shader::createPixel(std::string(ssao_makePyramid_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AmbientOcclusionEffect_DepthMapSlot));
gpu::Shader::makeProgram(*program, slotBindings);
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
// Stencil test the pyramid passe for objects pixels only, not the background
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
state->setColorWriteMask(true, false, false, false);
// Good to go add the brand new pipeline
_pyramidPipeline = gpu::Pipeline::create(program, state);
}
return _pyramidPipeline;
}
const gpu::PipelinePointer& AmbientOcclusionEffect::getOcclusionPipeline() {
if (!_occlusionPipeline) {
auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS();
@ -210,9 +267,11 @@ const gpu::PipelinePointer& AmbientOcclusionEffect::getOcclusionPipeline() {
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("pyramidMap"), AmbientOcclusionEffect_PyramidMapSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), AmbientOcclusionEffect_CameraCorrectionSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("pyramidMap"), AmbientOcclusionEffect_LinearDepthMapSlot));
gpu::Shader::makeProgram(*program, slotBindings);
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
@ -234,6 +293,7 @@ const gpu::PipelinePointer& AmbientOcclusionEffect::getHBlurPipeline() {
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), AmbientOcclusionEffect_CameraCorrectionSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), AmbientOcclusionEffect_OcclusionMapSlot));
gpu::Shader::makeProgram(*program, slotBindings);
@ -256,6 +316,7 @@ const gpu::PipelinePointer& AmbientOcclusionEffect::getVBlurPipeline() {
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), AmbientOcclusionEffect_CameraCorrectionSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), AmbientOcclusionEffect_OcclusionMapSlot));
@ -272,76 +333,50 @@ const gpu::PipelinePointer& AmbientOcclusionEffect::getVBlurPipeline() {
return _vBlurPipeline;
}
void AmbientOcclusionEffect::setDepthInfo(float nearZ, float farZ) {
_frameTransformBuffer.edit<FrameTransform>().depthInfo = glm::vec4(nearZ*farZ, farZ -nearZ, -farZ, 0.0f);
}
void AmbientOcclusionEffect::updateGaussianDistribution() {
auto coefs = _parametersBuffer.edit<Parameters>()._gaussianCoefs;
GaussianDistribution::evalSampling(coefs, Parameters::GAUSSIAN_COEFS_LENGTH, getBlurRadius(), getBlurDeviation());
auto coefs = _parametersBuffer->_gaussianCoefs;
GaussianDistribution::evalSampling(coefs, Parameters::GAUSSIAN_COEFS_LENGTH, _parametersBuffer->getBlurRadius(), _parametersBuffer->getBlurDeviation());
}
void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) {
#ifdef FIX_THE_FRAMEBUFFER_CACHE
void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
// FIXME: Different render modes should have different tasks
if (args->_renderMode != RenderArgs::DEFAULT_RENDER_MODE) {
return;
const auto& frameTransform = inputs.get0();
const auto& linearDepthFramebuffer = inputs.get2();
auto linearDepthTexture = linearDepthFramebuffer->getLinearDepthTexture();
auto sourceViewport = args->_viewport;
auto occlusionViewport = sourceViewport;
if (!_framebuffer) {
_framebuffer = std::make_shared<AmbientOcclusionFramebuffer>();
}
if (_parametersBuffer->getResolutionLevel() > 0) {
linearDepthTexture = linearDepthFramebuffer->getHalfLinearDepthTexture();
occlusionViewport = occlusionViewport >> _parametersBuffer->getResolutionLevel();
}
auto framebufferCache = DependencyManager::get<FramebufferCache>();
auto depthBuffer = framebufferCache->getPrimaryDepthTexture();
auto normalBuffer = framebufferCache->getDeferredNormalTexture();
auto pyramidFBO = framebufferCache->getDepthPyramidFramebuffer();
auto occlusionFBO = framebufferCache->getOcclusionFramebuffer();
auto occlusionBlurredFBO = framebufferCache->getOcclusionBlurredFramebuffer();
_framebuffer->updateLinearDepth(linearDepthTexture);
auto occlusionFBO = _framebuffer->getOcclusionFramebuffer();
auto occlusionBlurredFBO = _framebuffer->getOcclusionBlurredFramebuffer();
outputs.edit0() = _framebuffer;
outputs.edit1() = _parametersBuffer;
QSize framebufferSize = framebufferCache->getFrameBufferSize();
float sMin = args->_viewport.x / (float)framebufferSize.width();
float sWidth = args->_viewport.z / (float)framebufferSize.width();
float tMin = args->_viewport.y / (float)framebufferSize.height();
float tHeight = args->_viewport.w / (float)framebufferSize.height();
auto framebufferSize = _framebuffer->getSourceFrameSize();
float sMin = occlusionViewport.x / (float)framebufferSize.x;
float sWidth = occlusionViewport.z / (float)framebufferSize.x;
float tMin = occlusionViewport.y / (float)framebufferSize.y;
float tHeight = occlusionViewport.w / (float)framebufferSize.y;
auto resolutionLevel = getResolutionLevel();
// Update the depth info with near and far (same for stereo)
setDepthInfo(args->getViewFrustum().getNearClip(), args->getViewFrustum().getFarClip());
_frameTransformBuffer.edit<FrameTransform>().pixelInfo = args->_viewport;
//_parametersBuffer.edit<Parameters>()._ditheringInfo.y += 0.25f;
// Running in stero ?
bool isStereo = args->_context->isStereo();
if (!isStereo) {
// Eval the mono projection
mat4 monoProjMat;
args->getViewFrustum().evalProjectionMatrix(monoProjMat);
_frameTransformBuffer.edit<FrameTransform>().projection[0] = monoProjMat;
_frameTransformBuffer.edit<FrameTransform>().stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f);
} else {
mat4 projMats[2];
mat4 eyeViews[2];
args->_context->getStereoProjections(projMats);
args->_context->getStereoViews(eyeViews);
for (int i = 0; i < 2; i++) {
// Compose the mono Eye space to Stereo clip space Projection Matrix
auto sideViewMat = projMats[i] * eyeViews[i];
_frameTransformBuffer.edit<FrameTransform>().projection[i] = sideViewMat;
}
_frameTransformBuffer.edit<FrameTransform>().stereoInfo = glm::vec4(1.0f, (float)(args->_viewport.z >> 1), 0.0f, 1.0f);
}
auto pyramidPipeline = getPyramidPipeline();
auto occlusionPipeline = getOcclusionPipeline();
auto firstHBlurPipeline = getHBlurPipeline();
auto lastVBlurPipeline = getVBlurPipeline();
@ -351,7 +386,7 @@ void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext
_gpuTimer.begin(batch);
batch.setViewportTransform(args->_viewport);
batch.setViewportTransform(occlusionViewport);
batch.setProjectionTransform(glm::mat4());
batch.resetViewTransform();
@ -360,35 +395,22 @@ void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext
model.setScale(glm::vec3(sWidth, tHeight, 1.0f));
batch.setModelTransform(model);
batch.setUniformBuffer(AmbientOcclusionEffect_FrameTransformSlot, _frameTransformBuffer);
batch.setUniformBuffer(AmbientOcclusionEffect_FrameTransformSlot, frameTransform->getFrameTransformBuffer());
batch.setUniformBuffer(AmbientOcclusionEffect_ParamsSlot, _parametersBuffer);
// Pyramid pass
batch.setFramebuffer(pyramidFBO);
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(args->getViewFrustum().getFarClip(), 0.0f, 0.0f, 0.0f));
batch.setPipeline(pyramidPipeline);
batch.setResourceTexture(AmbientOcclusionEffect_DepthMapSlot, depthBuffer);
batch.draw(gpu::TRIANGLE_STRIP, 4);
// Make pyramid mips
batch.generateTextureMips(pyramidFBO->getRenderBuffer(0));
// Adjust Viewport for rendering resolution
if (resolutionLevel > 0) {
glm::ivec4 viewport(args->_viewport.x, args->_viewport.y, args->_viewport.z >> resolutionLevel, args->_viewport.w >> resolutionLevel);
batch.setViewportTransform(viewport);
}
// We need this with the mips levels
batch.generateTextureMips(_framebuffer->getLinearDepthTexture());
// Occlusion pass
batch.setFramebuffer(occlusionFBO);
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(1.0f));
batch.setPipeline(occlusionPipeline);
batch.setResourceTexture(AmbientOcclusionEffect_PyramidMapSlot, pyramidFBO->getRenderBuffer(0));
batch.setResourceTexture(AmbientOcclusionEffect_LinearDepthMapSlot, _framebuffer->getLinearDepthTexture());
batch.draw(gpu::TRIANGLE_STRIP, 4);
if (getBlurRadius() > 0) {
if (_parametersBuffer->getBlurRadius() > 0) {
// Blur 1st pass
batch.setFramebuffer(occlusionBlurredFBO);
batch.setPipeline(firstHBlurPipeline);
@ -402,10 +424,118 @@ void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext
batch.draw(gpu::TRIANGLE_STRIP, 4);
}
batch.setResourceTexture(AmbientOcclusionEffect_LinearDepthMapSlot, nullptr);
batch.setResourceTexture(AmbientOcclusionEffect_OcclusionMapSlot, nullptr);
_gpuTimer.end(batch);
});
// Update the timer
std::static_pointer_cast<Config>(renderContext->jobConfig)->gpuTime = _gpuTimer.getAverage();
#endif
}
DebugAmbientOcclusion::DebugAmbientOcclusion() {
}
void DebugAmbientOcclusion::configure(const Config& config) {
_showCursorPixel = config.showCursorPixel;
auto cursorPos = glm::vec2(_parametersBuffer->pixelInfo);
if (cursorPos != config.debugCursorTexcoord) {
_parametersBuffer->pixelInfo = glm::vec4(config.debugCursorTexcoord, 0.0f, 0.0f);
}
}
const gpu::PipelinePointer& DebugAmbientOcclusion::getDebugPipeline() {
if (!_debugPipeline) {
auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS();
auto ps = gpu::Shader::createPixel(std::string(ssao_debugOcclusion_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot));
slotBindings.insert(gpu::Shader::Binding(std::string("debugAmbientOcclusionBuffer"), 2));
slotBindings.insert(gpu::Shader::Binding(std::string("pyramidMap"), AmbientOcclusionEffect_LinearDepthMapSlot));
gpu::Shader::makeProgram(*program, slotBindings);
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setColorWriteMask(true, true, true, false);
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
// Good to go add the brand new pipeline
_debugPipeline = gpu::Pipeline::create(program, state);
}
return _debugPipeline;
}
void DebugAmbientOcclusion::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs) {
assert(renderContext->args);
assert(renderContext->args->hasViewFrustum());
if (!_showCursorPixel) {
return;
}
RenderArgs* args = renderContext->args;
const auto& frameTransform = inputs.get0();
const auto& linearDepthFramebuffer = inputs.get2();
const auto& ambientOcclusionUniforms = inputs.get3();
// Skip if AO is not started yet
if (!ambientOcclusionUniforms._buffer) {
return;
}
auto linearDepthTexture = linearDepthFramebuffer->getLinearDepthTexture();
auto sourceViewport = args->_viewport;
auto occlusionViewport = sourceViewport;
auto resolutionLevel = ambientOcclusionUniforms->getResolutionLevel();
if (resolutionLevel > 0) {
linearDepthTexture = linearDepthFramebuffer->getHalfLinearDepthTexture();
occlusionViewport = occlusionViewport >> ambientOcclusionUniforms->getResolutionLevel();
}
auto framebufferSize = glm::ivec2(linearDepthTexture->getDimensions());
float sMin = occlusionViewport.x / (float)framebufferSize.x;
float sWidth = occlusionViewport.z / (float)framebufferSize.x;
float tMin = occlusionViewport.y / (float)framebufferSize.y;
float tHeight = occlusionViewport.w / (float)framebufferSize.y;
auto debugPipeline = getDebugPipeline();
gpu::doInBatch(args->_context, [=](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setViewportTransform(sourceViewport);
batch.setProjectionTransform(glm::mat4());
batch.setViewTransform(Transform());
Transform model;
model.setTranslation(glm::vec3(sMin, tMin, 0.0f));
model.setScale(glm::vec3(sWidth, tHeight, 1.0f));
batch.setModelTransform(model);
batch.setUniformBuffer(AmbientOcclusionEffect_FrameTransformSlot, frameTransform->getFrameTransformBuffer());
batch.setUniformBuffer(AmbientOcclusionEffect_ParamsSlot, ambientOcclusionUniforms);
batch.setUniformBuffer(2, _parametersBuffer);
batch.setPipeline(debugPipeline);
batch.setResourceTexture(AmbientOcclusionEffect_LinearDepthMapSlot, linearDepthTexture);
batch.draw(gpu::TRIANGLE_STRIP, 4);
batch.setResourceTexture(AmbientOcclusionEffect_LinearDepthMapSlot, nullptr);
});
}

View file

@ -16,11 +16,49 @@
#include "render/DrawTask.h"
#include "DeferredFrameTransform.h"
#include "DeferredFramebuffer.h"
#include "SurfaceGeometryPass.h"
class AmbientOcclusionFramebuffer {
public:
AmbientOcclusionFramebuffer();
gpu::FramebufferPointer getOcclusionFramebuffer();
gpu::TexturePointer getOcclusionTexture();
gpu::FramebufferPointer getOcclusionBlurredFramebuffer();
gpu::TexturePointer getOcclusionBlurredTexture();
// Update the source framebuffer size which will drive the allocation of all the other resources.
void updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer);
gpu::TexturePointer getLinearDepthTexture();
const glm::ivec2& getSourceFrameSize() const { return _frameSize; }
protected:
void clear();
void allocate();
gpu::TexturePointer _linearDepthTexture;
gpu::FramebufferPointer _occlusionFramebuffer;
gpu::TexturePointer _occlusionTexture;
gpu::FramebufferPointer _occlusionBlurredFramebuffer;
gpu::TexturePointer _occlusionBlurredTexture;
glm::ivec2 _frameSize;
};
using AmbientOcclusionFramebufferPointer = std::shared_ptr<AmbientOcclusionFramebuffer>;
class AmbientOcclusionEffectConfig : public render::Job::Config::Persistent {
Q_OBJECT
Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty)
Q_PROPERTY(bool ditheringEnabled MEMBER ditheringEnabled NOTIFY dirty)
Q_PROPERTY(bool borderingEnabled MEMBER borderingEnabled NOTIFY dirty)
Q_PROPERTY(bool fetchMipsEnabled MEMBER fetchMipsEnabled NOTIFY dirty)
Q_PROPERTY(float radius MEMBER radius WRITE setRadius)
Q_PROPERTY(float obscuranceLevel MEMBER obscuranceLevel WRITE setObscuranceLevel)
Q_PROPERTY(float falloffBias MEMBER falloffBias WRITE setFalloffBias)
@ -49,72 +87,62 @@ public:
double getGpuTime() { return gpuTime; }
float radius{ 0.5f };
float perspectiveScale{ 1.0f };
float obscuranceLevel{ 0.5f }; // intensify or dim down the obscurance effect
float falloffBias{ 0.01f };
float edgeSharpness{ 1.0f };
float blurDeviation{ 2.5f };
float numSpiralTurns{ 7.0f }; // defining an angle span to distribute the samples ray directions
int numSamples{ 11 };
int numSamples{ 16 };
int resolutionLevel{ 1 };
int blurRadius{ 4 }; // 0 means no blurring
bool ditheringEnabled{ true }; // randomize the distribution of rays per pixel, should always be true
bool ditheringEnabled{ true }; // randomize the distribution of taps per pixel, should always be true
bool borderingEnabled{ true }; // avoid evaluating information from non existing pixels out of the frame, should always be true
bool fetchMipsEnabled{ true }; // fetch taps in sub mips to otpimize cache, should always be true
double gpuTime{ 0.0 };
signals:
void dirty();
};
namespace gpu {
template <class T> class UniformBuffer : public gpu::BufferView {
public:
static BufferPointer makeBuffer() {
T t;
return std::make_shared<gpu::Buffer>(sizeof(T), (const gpu::Byte*) &t);
}
~UniformBuffer<T>() {};
UniformBuffer<T>() : gpu::BufferView(makeBuffer()) {}
const T* operator ->() const { return &get<T>(); }
T* operator ->() {
return &edit<T>(0);
}
};
}
class AmbientOcclusionEffect {
public:
using Inputs = render::VaryingSet3<DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer>;
using Outputs = render::VaryingSet2<AmbientOcclusionFramebufferPointer, gpu::BufferView>;
using Config = AmbientOcclusionEffectConfig;
using JobModel = render::Job::Model<AmbientOcclusionEffect, Config>;
using JobModel = render::Job::ModelIO<AmbientOcclusionEffect, Inputs, Outputs, Config>;
AmbientOcclusionEffect();
void configure(const Config& config);
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs);
float getRadius() const { return _parametersBuffer.get<Parameters>().radiusInfo.x; }
float getObscuranceLevel() const { return _parametersBuffer.get<Parameters>().radiusInfo.w; }
float getFalloffBias() const { return (float)_parametersBuffer.get<Parameters>().ditheringInfo.z; }
float getEdgeSharpness() const { return (float)_parametersBuffer.get<Parameters>().blurInfo.x; }
float getBlurDeviation() const { return _parametersBuffer.get<Parameters>().blurInfo.z; }
float getNumSpiralTurns() const { return _parametersBuffer.get<Parameters>().sampleInfo.z; }
int getNumSamples() const { return (int)_parametersBuffer.get<Parameters>().sampleInfo.x; }
int getResolutionLevel() const { return _parametersBuffer.get<Parameters>().resolutionInfo.x; }
int getBlurRadius() const { return (int)_parametersBuffer.get<Parameters>().blurInfo.y; }
bool isDitheringEnabled() const { return _parametersBuffer.get<Parameters>().ditheringInfo.x; }
bool isBorderingEnabled() const { return _parametersBuffer.get<Parameters>().ditheringInfo.w; }
private:
void updateGaussianDistribution();
void setDepthInfo(float nearZ, float farZ);
typedef gpu::BufferView UniformBufferView;
// Class describing the uniform buffer with the transform info common to the AO shaders
// It s changing every frame
class FrameTransform {
public:
// Pixel info is { viemport width height and stereo on off}
glm::vec4 pixelInfo;
// Depth info is { n.f, f - n, -f}
glm::vec4 depthInfo;
// Stereo info
glm::vec4 stereoInfo { 0.0 };
// Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space
glm::mat4 projection[2];
FrameTransform() {}
};
gpu::BufferView _frameTransformBuffer;
// Class describing the uniform buffer with all the parameters common to the AO shaders
class Parameters {
public:
// Resolution info
glm::vec4 resolutionInfo { -1.0f, 0.0f, 0.0f, 0.0f };
glm::vec4 resolutionInfo { -1.0f, 0.0f, 1.0f, 0.0f };
// radius info is { R, R^2, 1 / R^6, ObscuranceScale}
glm::vec4 radiusInfo{ 0.5f, 0.5f * 0.5f, 1.0f / (0.25f * 0.25f * 0.25f), 1.0f };
// Dithering info
@ -126,22 +154,92 @@ private:
// gaussian distribution coefficients first is the sampling radius (max is 6)
const static int GAUSSIAN_COEFS_LENGTH = 8;
float _gaussianCoefs[GAUSSIAN_COEFS_LENGTH];
Parameters() {}
};
gpu::BufferView _parametersBuffer;
const gpu::PipelinePointer& getPyramidPipeline();
int getResolutionLevel() const { return resolutionInfo.x; }
float getRadius() const { return radiusInfo.x; }
float getPerspectiveScale() const { return resolutionInfo.z; }
float getObscuranceLevel() const { return radiusInfo.w; }
float getFalloffBias() const { return (float)ditheringInfo.z; }
float getEdgeSharpness() const { return (float)blurInfo.x; }
float getBlurDeviation() const { return blurInfo.z; }
float getNumSpiralTurns() const { return sampleInfo.z; }
int getNumSamples() const { return (int)sampleInfo.x; }
bool isFetchMipsEnabled() const { return sampleInfo.w; }
int getBlurRadius() const { return (int)blurInfo.y; }
bool isDitheringEnabled() const { return ditheringInfo.x; }
bool isBorderingEnabled() const { return ditheringInfo.w; }
};
using ParametersBuffer = gpu::UniformBuffer<Parameters>;
private:
void updateGaussianDistribution();
ParametersBuffer _parametersBuffer;
const gpu::PipelinePointer& getOcclusionPipeline();
const gpu::PipelinePointer& getHBlurPipeline(); // first
const gpu::PipelinePointer& getVBlurPipeline(); // second
gpu::PipelinePointer _pyramidPipeline;
gpu::PipelinePointer _occlusionPipeline;
gpu::PipelinePointer _hBlurPipeline;
gpu::PipelinePointer _vBlurPipeline;
AmbientOcclusionFramebufferPointer _framebuffer;
gpu::RangeTimer _gpuTimer;
friend class DebugAmbientOcclusion;
};
class DebugAmbientOcclusionConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty)
Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty)
public:
DebugAmbientOcclusionConfig() : render::Job::Config(true) {}
bool showCursorPixel{ false };
glm::vec2 debugCursorTexcoord{ 0.5f, 0.5f };
signals:
void dirty();
};
class DebugAmbientOcclusion {
public:
using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer, AmbientOcclusionEffect::ParametersBuffer>;
using Config = DebugAmbientOcclusionConfig;
using JobModel = render::Job::ModelI<DebugAmbientOcclusion, Inputs, Config>;
DebugAmbientOcclusion();
void configure(const Config& config);
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs);
private:
// Class describing the uniform buffer with all the parameters common to the debug AO shaders
class Parameters {
public:
// Pixel info
glm::vec4 pixelInfo { 0.0f, 0.0f, 0.0f, 0.0f };
Parameters() {}
};
gpu::UniformBuffer<Parameters> _parametersBuffer;
const gpu::PipelinePointer& getDebugPipeline();
gpu::PipelinePointer _debugPipeline;
bool _showCursorPixel{ false };
};
#endif // hifi_AmbientOcclusionEffect_h

View file

@ -202,14 +202,14 @@ static const std::string DEFAULT_DEBUG_SCATTERING_SHADER{
static const std::string DEFAULT_AMBIENT_OCCLUSION_SHADER{
"vec4 getFragmentColor() {"
" return vec4(vec3(texture(obscuranceMap, uv).x), 1.0);"
" return vec4(vec3(texture(obscuranceMap, uv).xyz), 1.0);"
// When drawing color " return vec4(vec3(texture(occlusionMap, uv).xyz), 1.0);"
// when drawing normal " return vec4(normalize(texture(occlusionMap, uv).xyz * 2.0 - vec3(1.0)), 1.0);"
// when drawing normal" return vec4(normalize(texture(occlusionMap, uv).xyz * 2.0 - vec3(1.0)), 1.0);"
" }"
};
static const std::string DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER{
"vec4 getFragmentColor() {"
" return vec4(vec3(texture(occlusionBlurredMap, uv).x), 1.0);"
" return vec4(vec3(texture(occlusionBlurredMap, uv).xyz), 1.0);"
" }"
};
@ -379,6 +379,7 @@ void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const Ren
auto& deferredFramebuffer = inputs.get0();
auto& linearDepthTarget = inputs.get1();
auto& surfaceGeometryFramebuffer = inputs.get2();
auto& ambientOcclusionFramebuffer = inputs.get3();
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.enableStereo(false);
@ -402,29 +403,51 @@ void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const Ren
batch.setPipeline(getPipeline(_mode, first));
batch.setResourceTexture(Albedo, deferredFramebuffer->getDeferredColorTexture());
batch.setResourceTexture(Normal, deferredFramebuffer->getDeferredNormalTexture());
batch.setResourceTexture(Specular, deferredFramebuffer->getDeferredSpecularTexture());
batch.setResourceTexture(Depth, deferredFramebuffer->getPrimaryDepthTexture());
batch.setResourceTexture(Lighting, deferredFramebuffer->getLightingTexture());
batch.setResourceTexture(Shadow, lightStage.lights[0]->shadow.framebuffer->getDepthStencilBuffer());
batch.setResourceTexture(LinearDepth, linearDepthTarget->getLinearDepthTexture());
batch.setResourceTexture(HalfLinearDepth, linearDepthTarget->getHalfLinearDepthTexture());
batch.setResourceTexture(HalfNormal, linearDepthTarget->getHalfNormalTexture());
batch.setResourceTexture(Curvature, surfaceGeometryFramebuffer->getCurvatureTexture());
batch.setResourceTexture(DiffusedCurvature, surfaceGeometryFramebuffer->getLowCurvatureTexture());
if (DependencyManager::get<DeferredLightingEffect>()->isAmbientOcclusionEnabled()) {
batch.setResourceTexture(AmbientOcclusion, framebufferCache->getOcclusionTexture());
} else {
// need to assign the white texture if ao is off
batch.setResourceTexture(AmbientOcclusion, textureCache->getWhiteTexture());
}
batch.setResourceTexture(AmbientOcclusionBlurred, framebufferCache->getOcclusionBlurredTexture());
if (deferredFramebuffer) {
batch.setResourceTexture(Albedo, deferredFramebuffer->getDeferredColorTexture());
batch.setResourceTexture(Normal, deferredFramebuffer->getDeferredNormalTexture());
batch.setResourceTexture(Specular, deferredFramebuffer->getDeferredSpecularTexture());
batch.setResourceTexture(Depth, deferredFramebuffer->getPrimaryDepthTexture());
batch.setResourceTexture(Lighting, deferredFramebuffer->getLightingTexture());
}
if (!lightStage.lights.empty()) {
batch.setResourceTexture(Shadow, lightStage.lights[0]->shadow.framebuffer->getDepthStencilBuffer());
}
if (linearDepthTarget) {
batch.setResourceTexture(LinearDepth, linearDepthTarget->getLinearDepthTexture());
batch.setResourceTexture(HalfLinearDepth, linearDepthTarget->getHalfLinearDepthTexture());
batch.setResourceTexture(HalfNormal, linearDepthTarget->getHalfNormalTexture());
}
if (surfaceGeometryFramebuffer) {
batch.setResourceTexture(Curvature, surfaceGeometryFramebuffer->getCurvatureTexture());
batch.setResourceTexture(DiffusedCurvature, surfaceGeometryFramebuffer->getLowCurvatureTexture());
}
if (ambientOcclusionFramebuffer) {
batch.setResourceTexture(AmbientOcclusion, ambientOcclusionFramebuffer->getOcclusionTexture());
batch.setResourceTexture(AmbientOcclusionBlurred, ambientOcclusionFramebuffer->getOcclusionBlurredTexture());
}
const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f);
const glm::vec2 bottomLeft(_size.x, _size.y);
const glm::vec2 topRight(_size.z, _size.w);
geometryBuffer->renderQuad(batch, bottomLeft, topRight, color);
batch.setResourceTexture(Albedo, nullptr);
batch.setResourceTexture(Normal, nullptr);
batch.setResourceTexture(Specular, nullptr);
batch.setResourceTexture(Depth, nullptr);
batch.setResourceTexture(Lighting, nullptr);
batch.setResourceTexture(Shadow, nullptr);
batch.setResourceTexture(LinearDepth, nullptr);
batch.setResourceTexture(HalfLinearDepth, nullptr);
batch.setResourceTexture(HalfNormal, nullptr);
batch.setResourceTexture(Curvature, nullptr);
batch.setResourceTexture(DiffusedCurvature, nullptr);
batch.setResourceTexture(AmbientOcclusion, nullptr);
batch.setResourceTexture(AmbientOcclusionBlurred, nullptr);
});
}

View file

@ -17,6 +17,7 @@
#include <render/DrawTask.h>
#include "DeferredFramebuffer.h"
#include "SurfaceGeometryPass.h"
#include "AmbientOcclusionEffect.h"
class DebugDeferredBufferConfig : public render::Job::Config {
Q_OBJECT
@ -36,7 +37,7 @@ signals:
class DebugDeferredBuffer {
public:
using Inputs = render::VaryingSet4<DeferredFramebufferPointer, LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, gpu::FramebufferPointer>;
using Inputs = render::VaryingSet4<DeferredFramebufferPointer, LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer>;
using Config = DebugDeferredBufferConfig;
using JobModel = render::Job::ModelI<DebugDeferredBuffer, Inputs, Config>;

View file

@ -403,7 +403,7 @@ void RenderDeferredSetup::run(const render::SceneContextPointer& sceneContext, c
const DeferredFramebufferPointer& deferredFramebuffer,
const LightingModelPointer& lightingModel,
const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer,
const gpu::FramebufferPointer& lowCurvatureNormalFramebuffer,
const AmbientOcclusionFramebufferPointer& ambientOcclusionFramebuffer,
const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource) {
auto args = renderContext->args;
@ -434,7 +434,7 @@ void RenderDeferredSetup::run(const render::SceneContextPointer& sceneContext, c
// FIXME: Different render modes should have different tasks
if (args->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && deferredLightingEffect->isAmbientOcclusionEnabled()) {
batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, framebufferCache->getOcclusionTexture());
batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, ambientOcclusionFramebuffer->getOcclusionTexture());
} else {
// need to assign the white texture if ao is off
batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, textureCache->getWhiteTexture());
@ -449,9 +449,6 @@ void RenderDeferredSetup::run(const render::SceneContextPointer& sceneContext, c
// Subsurface scattering specific
if (surfaceGeometryFramebuffer) {
batch.setResourceTexture(DEFERRED_BUFFER_CURVATURE_UNIT, surfaceGeometryFramebuffer->getCurvatureTexture());
}
if (lowCurvatureNormalFramebuffer) {
// batch.setResourceTexture(DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, lowCurvatureNormalFramebuffer->getRenderBuffer(0));
batch.setResourceTexture(DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, surfaceGeometryFramebuffer->getLowCurvatureTexture());
}
if (subsurfaceScatteringResource) {
@ -698,7 +695,7 @@ void RenderDeferred::run(const SceneContextPointer& sceneContext, const RenderCo
auto deferredFramebuffer = inputs.get1();
auto lightingModel = inputs.get2();
auto surfaceGeometryFramebuffer = inputs.get3();
auto lowCurvatureNormalFramebuffer = inputs.get4();
auto ssaoFramebuffer = inputs.get4();
auto subsurfaceScatteringResource = inputs.get5();
auto args = renderContext->args;
@ -706,7 +703,7 @@ void RenderDeferred::run(const SceneContextPointer& sceneContext, const RenderCo
_gpuTimer.begin(batch);
});
setupJob.run(sceneContext, renderContext, deferredTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, lowCurvatureNormalFramebuffer, subsurfaceScatteringResource);
setupJob.run(sceneContext, renderContext, deferredTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, ssaoFramebuffer, subsurfaceScatteringResource);
lightsJob.run(sceneContext, renderContext, deferredTransform, deferredFramebuffer, lightingModel);

View file

@ -30,6 +30,7 @@
#include "LightStage.h"
#include "SurfaceGeometryPass.h"
#include "SubsurfaceScattering.h"
#include "AmbientOcclusionEffect.h"
class RenderArgs;
struct LightLocations;
@ -138,7 +139,7 @@ public:
const DeferredFramebufferPointer& deferredFramebuffer,
const LightingModelPointer& lightingModel,
const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer,
const gpu::FramebufferPointer& lowCurvatureNormalFramebuffer,
const AmbientOcclusionFramebufferPointer& ambientOcclusionFramebuffer,
const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource);
};
@ -178,7 +179,7 @@ signals:
class RenderDeferred {
public:
using Inputs = render::VaryingSet6 < DeferredFrameTransformPointer, DeferredFramebufferPointer, LightingModelPointer, SurfaceGeometryFramebufferPointer, gpu::FramebufferPointer, SubsurfaceScatteringResourcePointer>;
using Inputs = render::VaryingSet6 < DeferredFrameTransformPointer, DeferredFramebufferPointer, LightingModelPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer, SubsurfaceScatteringResourcePointer>;
using Config = RenderDeferredConfig;
using JobModel = render::Job::ModelI<RenderDeferred, Inputs, Config>;

View file

@ -93,6 +93,13 @@ bool isStereo() {
float getStereoSideWidth(int resolutionLevel) {
return float(int(frameTransform._stereoInfo.y) >> resolutionLevel);
}
float getStereoSideHeight(int resolutionLevel) {
return float(int(frameTransform._pixelInfo.w) >> resolutionLevel);
}
vec2 getSideImageSize(int resolutionLevel) {
return vec2(float(int(frameTransform._stereoInfo.y) >> resolutionLevel), float(int(frameTransform._pixelInfo.w) >> resolutionLevel));
}
ivec4 getStereoSideInfo(int xPos, int resolutionLevel) {
int sideWidth = int(getStereoSideWidth(resolutionLevel));

View file

@ -22,10 +22,6 @@ void FramebufferCache::setFrameBufferSize(QSize frameBufferSize) {
if (_frameBufferSize != frameBufferSize) {
_frameBufferSize = frameBufferSize;
_selfieFramebuffer.reset();
_occlusionFramebuffer.reset();
_occlusionTexture.reset();
_occlusionBlurredFramebuffer.reset();
_occlusionBlurredTexture.reset();
{
std::unique_lock<std::mutex> lock(_mutex);
_cachedFramebuffers.clear();
@ -45,33 +41,6 @@ void FramebufferCache::createPrimaryFramebuffer() {
_selfieFramebuffer->setRenderBuffer(0, tex);
auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR);
resizeAmbientOcclusionBuffers();
}
void FramebufferCache::resizeAmbientOcclusionBuffers() {
_occlusionFramebuffer.reset();
_occlusionTexture.reset();
_occlusionBlurredFramebuffer.reset();
_occlusionBlurredTexture.reset();
auto width = _frameBufferSize.width() >> _AOResolutionLevel;
auto height = _frameBufferSize.height() >> _AOResolutionLevel;
auto colorFormat = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGB);
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
// auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
_occlusionTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler));
_occlusionFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
_occlusionFramebuffer->setRenderBuffer(0, _occlusionTexture);
// _occlusionFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat);
_occlusionBlurredTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler));
_occlusionBlurredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
_occlusionBlurredFramebuffer->setRenderBuffer(0, _occlusionBlurredTexture);
// _occlusionBlurredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat);
}
@ -98,40 +67,3 @@ gpu::FramebufferPointer FramebufferCache::getSelfieFramebuffer() {
}
return _selfieFramebuffer;
}
void FramebufferCache::setAmbientOcclusionResolutionLevel(int level) {
const int MAX_AO_RESOLUTION_LEVEL = 4;
level = std::max(0, std::min(level, MAX_AO_RESOLUTION_LEVEL));
if (level != _AOResolutionLevel) {
_AOResolutionLevel = level;
resizeAmbientOcclusionBuffers();
}
}
gpu::FramebufferPointer FramebufferCache::getOcclusionFramebuffer() {
if (!_occlusionFramebuffer) {
resizeAmbientOcclusionBuffers();
}
return _occlusionFramebuffer;
}
gpu::TexturePointer FramebufferCache::getOcclusionTexture() {
if (!_occlusionTexture) {
resizeAmbientOcclusionBuffers();
}
return _occlusionTexture;
}
gpu::FramebufferPointer FramebufferCache::getOcclusionBlurredFramebuffer() {
if (!_occlusionBlurredFramebuffer) {
resizeAmbientOcclusionBuffers();
}
return _occlusionBlurredFramebuffer;
}
gpu::TexturePointer FramebufferCache::getOcclusionBlurredTexture() {
if (!_occlusionBlurredTexture) {
resizeAmbientOcclusionBuffers();
}
return _occlusionBlurredTexture;
}

View file

@ -27,12 +27,6 @@ public:
void setFrameBufferSize(QSize frameBufferSize);
const QSize& getFrameBufferSize() const { return _frameBufferSize; }
void setAmbientOcclusionResolutionLevel(int level);
gpu::FramebufferPointer getOcclusionFramebuffer();
gpu::TexturePointer getOcclusionTexture();
gpu::FramebufferPointer getOcclusionBlurredFramebuffer();
gpu::TexturePointer getOcclusionBlurredTexture();
/// Returns the framebuffer object used to render selfie maps;
gpu::FramebufferPointer getSelfieFramebuffer();
@ -50,21 +44,10 @@ private:
gpu::FramebufferPointer _selfieFramebuffer;
gpu::FramebufferPointer _occlusionFramebuffer;
gpu::TexturePointer _occlusionTexture;
gpu::FramebufferPointer _occlusionBlurredFramebuffer;
gpu::TexturePointer _occlusionBlurredTexture;
QSize _frameBufferSize{ 100, 100 };
int _AOResolutionLevel = 1; // AO perform at half res
std::mutex _mutex;
std::list<gpu::FramebufferPointer> _cachedFramebuffers;
// Resize/reallocate the buffers used for AO
// the size of the AO buffers is scaled by the AOResolutionScale;
void resizeAmbientOcclusionBuffers();
};
#endif // hifi_FramebufferCache_h

View file

@ -35,7 +35,8 @@
#include "simple_vert.h"
#include "simple_textured_frag.h"
#include "simple_textured_unlit_frag.h"
#include "simple_srgb_textured_unlit_no_tex_alpha_frag.h"
#include "simple_opaque_web_browser_frag.h"
#include "simple_transparent_web_browser_frag.h"
#include "glowLine_vert.h"
#include "glowLine_geom.h"
#include "glowLine_frag.h"
@ -1763,25 +1764,55 @@ inline bool operator==(const SimpleProgramKey& a, const SimpleProgramKey& b) {
return a.getRaw() == b.getRaw();
}
void GeometryCache::bindSimpleSRGBTexturedUnlitNoTexAlphaProgram(gpu::Batch& batch) {
batch.setPipeline(getSimpleSRGBTexturedUnlitNoTexAlphaPipeline());
void GeometryCache::bindOpaqueWebBrowserProgram(gpu::Batch& batch) {
batch.setPipeline(getOpaqueWebBrowserProgram());
// Set a default normal map
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
DependencyManager::get<TextureCache>()->getNormalFittingTexture());
DependencyManager::get<TextureCache>()->getNormalFittingTexture());
}
gpu::PipelinePointer GeometryCache::getSimpleSRGBTexturedUnlitNoTexAlphaPipeline() {
// Compile the shaders, once
gpu::PipelinePointer GeometryCache::getOpaqueWebBrowserProgram() {
static std::once_flag once;
std::call_once(once, [&]() {
auto VS = gpu::Shader::createVertex(std::string(simple_vert));
auto PS = gpu::Shader::createPixel(std::string(simple_srgb_textured_unlit_no_tex_alpha_frag));
auto PS = gpu::Shader::createPixel(std::string(simple_opaque_web_browser_frag));
_simpleSRGBTexturedUnlitNoTexAlphaShader = gpu::Shader::createProgram(VS, PS);
_simpleOpaqueWebBrowserShader = gpu::Shader::createProgram(VS, PS);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
gpu::Shader::makeProgram(*_simpleSRGBTexturedUnlitNoTexAlphaShader, slotBindings);
gpu::Shader::makeProgram(*_simpleOpaqueWebBrowserShader, slotBindings);
auto state = std::make_shared<gpu::State>();
state->setCullMode(gpu::State::CULL_NONE);
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(false,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
_simpleOpaqueWebBrowserPipeline = gpu::Pipeline::create(_simpleOpaqueWebBrowserShader, state);
});
return _simpleOpaqueWebBrowserPipeline;
}
void GeometryCache::bindTransparentWebBrowserProgram(gpu::Batch& batch) {
batch.setPipeline(getTransparentWebBrowserProgram());
// Set a default normal map
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
DependencyManager::get<TextureCache>()->getNormalFittingTexture());
}
gpu::PipelinePointer GeometryCache::getTransparentWebBrowserProgram() {
static std::once_flag once;
std::call_once(once, [&]() {
auto VS = gpu::Shader::createVertex(std::string(simple_vert));
auto PS = gpu::Shader::createPixel(std::string(simple_transparent_web_browser_frag));
_simpleTransparentWebBrowserShader = gpu::Shader::createProgram(VS, PS);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
gpu::Shader::makeProgram(*_simpleTransparentWebBrowserShader, slotBindings);
auto state = std::make_shared<gpu::State>();
state->setCullMode(gpu::State::CULL_NONE);
state->setDepthTest(true, true, gpu::LESS_EQUAL);
@ -1789,10 +1820,10 @@ gpu::PipelinePointer GeometryCache::getSimpleSRGBTexturedUnlitNoTexAlphaPipeline
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
_simpleSRGBTexturedUnlitNoTexAlphaPipeline = gpu::Pipeline::create(_simpleSRGBTexturedUnlitNoTexAlphaShader, state);
_simpleTransparentWebBrowserPipeline = gpu::Pipeline::create(_simpleTransparentWebBrowserShader, state);
});
return _simpleSRGBTexturedUnlitNoTexAlphaPipeline;
return _simpleTransparentWebBrowserPipeline;
}
void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased) {

View file

@ -149,8 +149,7 @@ public:
int allocateID() { return _nextID++; }
static const int UNKNOWN_ID;
// Bind the pipeline and get the state to render static geometry
void bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool transparent = false, bool culled = true,
bool unlit = false, bool depthBias = false);
@ -158,8 +157,11 @@ public:
gpu::PipelinePointer getSimplePipeline(bool textured = false, bool transparent = false, bool culled = true,
bool unlit = false, bool depthBias = false);
void bindSimpleSRGBTexturedUnlitNoTexAlphaProgram(gpu::Batch& batch);
gpu::PipelinePointer getSimpleSRGBTexturedUnlitNoTexAlphaPipeline();
void bindOpaqueWebBrowserProgram(gpu::Batch& batch);
gpu::PipelinePointer getOpaqueWebBrowserProgram();
void bindTransparentWebBrowserProgram(gpu::Batch& batch);
gpu::PipelinePointer getTransparentWebBrowserProgram();
render::ShapePipelinePointer getOpaqueShapePipeline() { return GeometryCache::_simpleOpaquePipeline; }
render::ShapePipelinePointer getTransparentShapePipeline() { return GeometryCache::_simpleTransparentPipeline; }
@ -423,9 +425,11 @@ private:
gpu::PipelinePointer _glowLinePipeline;
QHash<SimpleProgramKey, gpu::PipelinePointer> _simplePrograms;
gpu::ShaderPointer _simpleSRGBTexturedUnlitNoTexAlphaShader;
gpu::PipelinePointer _simpleSRGBTexturedUnlitNoTexAlphaPipeline;
gpu::ShaderPointer _simpleOpaqueWebBrowserShader;
gpu::PipelinePointer _simpleOpaqueWebBrowserPipeline;
gpu::ShaderPointer _simpleTransparentWebBrowserShader;
gpu::PipelinePointer _simpleTransparentWebBrowserPipeline;
};
#endif // hifi_GeometryCache_h

View file

@ -46,11 +46,9 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr
}
}
MeshPartPayload::MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material, const Transform& transform, const Transform& offsetTransform) {
MeshPartPayload::MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material) {
updateMeshPart(mesh, partIndex);
updateMaterial(material);
updateTransform(transform, offsetTransform);
}
void MeshPartPayload::updateMeshPart(const std::shared_ptr<const model::Mesh>& drawMesh, int partIndex) {
@ -414,8 +412,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
// if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown
// to false to rebuild out mesh groups.
if (_meshIndex < 0 || _meshIndex >= (int)networkMeshes.size() || _meshIndex > geometry.meshes.size()) {
_model->_meshGroupsKnown = false; // regenerate these lists next time around.
_model->_readyWhenAdded = false; // in case any of our users are using scenes
_model->_needsFixupInScene = true; // trigger remove/add cycle
_model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
return ShapeKey::Builder::invalid();
}
@ -533,7 +530,7 @@ void ModelMeshPartPayload::startFade() {
void ModelMeshPartPayload::render(RenderArgs* args) const {
PerformanceTimer perfTimer("ModelMeshPartPayload::render");
if (!_model->_readyWhenAdded || !_model->_isVisible) {
if (!_model->addedToScene() || !_model->isVisible()) {
return; // bail asap
}

View file

@ -26,7 +26,7 @@ class Model;
class MeshPartPayload {
public:
MeshPartPayload() {}
MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material, const Transform& transform, const Transform& offsetTransform);
MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material);
typedef render::Payload<MeshPartPayload> Payload;
typedef Payload::DataPointer Pointer;

View file

@ -37,9 +37,9 @@ float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
#define HTTP_INVALID_COM "http://invalid.com"
const int NUM_COLLISION_HULL_COLORS = 24;
std::vector<model::MaterialPointer> _collisionHullMaterials;
std::vector<model::MaterialPointer> _collisionMaterials;
void initCollisionHullMaterials() {
void initCollisionMaterials() {
// generates bright colors in red, green, blue, yellow, magenta, and cyan spectrums
// (no browns, greys, or dark shades)
float component[NUM_COLLISION_HULL_COLORS] = {
@ -50,7 +50,7 @@ void initCollisionHullMaterials() {
1.0f, 1.0f, 1.0f, 1.0f,
0.8f, 0.6f, 0.4f, 0.2f
};
_collisionHullMaterials.reserve(NUM_COLLISION_HULL_COLORS);
_collisionMaterials.reserve(NUM_COLLISION_HULL_COLORS);
// each component gets the same cuve
// but offset by a multiple of one third the full width
@ -72,7 +72,7 @@ void initCollisionHullMaterials() {
material->setAlbedo(glm::vec3(red, green, blue));
material->setMetallic(0.02f);
material->setRoughness(0.5f);
_collisionHullMaterials.push_back(material);
_collisionMaterials.push_back(material);
}
}
}
@ -82,7 +82,6 @@ Model::Model(RigPointer rig, QObject* parent) :
_renderGeometry(),
_collisionGeometry(),
_renderWatcher(_renderGeometry),
_collisionWatcher(_collisionGeometry),
_translation(0.0f),
_rotation(),
_scale(1.0f, 1.0f, 1.0f),
@ -100,7 +99,6 @@ Model::Model(RigPointer rig, QObject* parent) :
_calculatedMeshPartBoxesValid(false),
_calculatedMeshBoxesValid(false),
_calculatedMeshTrianglesValid(false),
_meshGroupsKnown(false),
_isWireframe(false),
_rig(rig)
{
@ -112,7 +110,6 @@ Model::Model(RigPointer rig, QObject* parent) :
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished);
connect(&_collisionWatcher, &GeometryResourceWatcher::finished, this, &Model::loadCollisionModelURLFinished);
}
Model::~Model() {
@ -122,18 +119,11 @@ Model::~Model() {
AbstractViewStateInterface* Model::_viewState = NULL;
bool Model::needsFixupInScene() const {
if (readyToAddToScene()) {
if (_needsUpdateTextures && _renderGeometry->areTexturesLoaded()) {
_needsUpdateTextures = false;
return true;
}
if (!_readyWhenAdded) {
return true;
}
}
return false;
return (_needsFixupInScene || !_addedToScene) && !_needsReload && isLoaded();
}
// TODO?: should we combine translation and rotation into single method to avoid double-work?
// (figure out where we call these)
void Model::setTranslation(const glm::vec3& translation) {
_translation = translation;
updateRenderItems();
@ -172,7 +162,15 @@ void Model::setOffset(const glm::vec3& offset) {
}
void Model::updateRenderItems() {
if (!_addedToScene) {
return;
}
glm::vec3 scale = getScale();
if (_collisionGeometry) {
// _collisionGeometry is already scaled
scale = glm::vec3(1.0f);
}
_needsUpdateClusterMatrices = true;
_renderItemsNeedUpdate = false;
@ -180,7 +178,7 @@ void Model::updateRenderItems() {
// the application will ensure only the last lambda is actually invoked.
void* key = (void*)this;
std::weak_ptr<Model> weakSelf = shared_from_this();
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf]() {
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf, scale]() {
// do nothing, if the model has already been destroyed.
auto self = weakSelf.lock();
@ -191,7 +189,7 @@ void Model::updateRenderItems() {
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
Transform modelTransform;
modelTransform.setScale(self->_scale);
modelTransform.setScale(scale);
modelTransform.setTranslation(self->_translation);
modelTransform.setRotation(self->_rotation);
@ -203,10 +201,6 @@ void Model::updateRenderItems() {
modelMeshOffset.postTranslate(self->_offset);
}
// only apply offset only, collision mesh does not share the same unit scale as the FBX file's mesh.
Transform collisionMeshOffset;
collisionMeshOffset.postTranslate(self->_offset);
uint32_t deleteGeometryCounter = self->_deleteGeometryCounter;
render::PendingChanges pendingChanges;
@ -227,6 +221,9 @@ void Model::updateRenderItems() {
});
}
// collision mesh does not share the same unit scale as the FBX file's mesh: only apply offset
Transform collisionMeshOffset;
collisionMeshOffset.setIdentity();
foreach (auto itemID, self->_collisionRenderItems.keys()) {
pendingChanges.updateItem<MeshPartPayload>(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) {
// update the model transform for this render item.
@ -574,8 +571,8 @@ void Model::renderSetup(RenderArgs* args) {
}
}
if (!_meshGroupsKnown && isLoaded()) {
segregateMeshGroups();
if (!_addedToScene && isLoaded()) {
createRenderItemSet();
}
}
@ -596,43 +593,46 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scen
bool Model::addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges,
render::Item::Status::Getters& statusGetters,
bool showCollisionHull) {
if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) {
_showCollisionHull = showCollisionHull;
segregateMeshGroups();
render::Item::Status::Getters& statusGetters) {
bool readyToRender = _collisionGeometry || isLoaded();
if (!_addedToScene && readyToRender) {
createRenderItemSet();
}
bool somethingAdded = false;
if (_modelMeshRenderItems.empty()) {
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
if (statusGetters.size()) {
renderPayload->addStatusGetters(statusGetters);
if (_collisionGeometry) {
if (_collisionRenderItems.empty()) {
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
if (statusGetters.size()) {
renderPayload->addStatusGetters(statusGetters);
}
pendingChanges.resetItem(item, renderPayload);
_collisionRenderItems.insert(item, renderPayload);
}
pendingChanges.resetItem(item, renderPayload);
_modelMeshRenderItems.insert(item, renderPayload);
somethingAdded = true;
somethingAdded = !_collisionRenderItems.empty();
}
}
if (_collisionRenderItems.empty()) {
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
if (statusGetters.size()) {
renderPayload->addStatusGetters(statusGetters);
} else {
if (_modelMeshRenderItems.empty()) {
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
if (statusGetters.size()) {
renderPayload->addStatusGetters(statusGetters);
}
pendingChanges.resetItem(item, renderPayload);
_modelMeshRenderItems.insert(item, renderPayload);
}
pendingChanges.resetItem(item, renderPayload);
_collisionRenderItems.insert(item, renderPayload);
somethingAdded = true;
somethingAdded = !_modelMeshRenderItems.empty();
}
}
updateRenderItems();
_readyWhenAdded = readyToAddToScene();
if (somethingAdded) {
_addedToScene = true;
updateRenderItems();
_needsFixupInScene = false;
}
return somethingAdded;
}
@ -643,13 +643,13 @@ void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::Pendin
}
_modelMeshRenderItems.clear();
_modelMeshRenderItemsSet.clear();
foreach (auto item, _collisionRenderItems.keys()) {
pendingChanges.removeItem(item);
}
_collisionRenderItems.clear();
_collisionRenderItemsSet.clear();
_meshGroupsKnown = false;
_readyWhenAdded = false;
_addedToScene = false;
}
void Model::renderDebugMeshBoxes(gpu::Batch& batch) {
@ -804,6 +804,7 @@ int Model::getLastFreeJointIndex(int jointIndex) const {
void Model::setTextures(const QVariantMap& textures) {
if (isLoaded()) {
_needsUpdateTextures = true;
_needsFixupInScene = true;
_renderGeometry->setTextures(textures);
}
}
@ -825,8 +826,8 @@ void Model::setURL(const QUrl& url) {
_needsReload = true;
_needsUpdateTextures = true;
_meshGroupsKnown = false;
_visualGeometryRequestFailed = false;
_needsFixupInScene = true;
invalidCalculatedMeshBoxes();
deleteGeometry();
@ -843,23 +844,6 @@ void Model::loadURLFinished(bool success) {
emit setURLFinished(success);
}
void Model::setCollisionModelURL(const QUrl& url) {
if (_collisionUrl == url && _collisionWatcher.getURL() == url) {
return;
}
_collisionUrl = url;
_collisionGeometryRequestFailed = false;
_collisionWatcher.setResource(DependencyManager::get<ModelCache>()->getGeometryResource(url));
}
void Model::loadCollisionModelURLFinished(bool success) {
if (!success) {
_collisionGeometryRequestFailed = true;
}
emit setCollisionModelURLFinished(success);
}
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
}
@ -1236,21 +1220,21 @@ AABox Model::getRenderableMeshBound() const {
}
}
void Model::segregateMeshGroups() {
Geometry::Pointer geometry;
bool showingCollisionHull = false;
if (_showCollisionHull && _collisionGeometry) {
if (isCollisionLoaded()) {
geometry = _collisionGeometry;
showingCollisionHull = true;
} else {
return;
void Model::createRenderItemSet() {
if (_collisionGeometry) {
if (_collisionRenderItemsSet.empty()) {
createCollisionRenderItemSet();
}
} else {
assert(isLoaded());
geometry = _renderGeometry;
if (_modelMeshRenderItemsSet.empty()) {
createVisibleRenderItemSet();
}
}
const auto& meshes = geometry->getMeshes();
};
void Model::createVisibleRenderItemSet() {
assert(isLoaded());
const auto& meshes = _renderGeometry->getMeshes();
// all of our mesh vectors must match in size
if ((int)meshes.size() != _meshStates.size()) {
@ -1259,13 +1243,9 @@ void Model::segregateMeshGroups() {
}
// We should not have any existing renderItems if we enter this section of code
Q_ASSERT(_modelMeshRenderItems.isEmpty());
Q_ASSERT(_modelMeshRenderItemsSet.isEmpty());
Q_ASSERT(_collisionRenderItems.isEmpty());
Q_ASSERT(_collisionRenderItemsSet.isEmpty());
_modelMeshRenderItemsSet.clear();
_collisionRenderItemsSet.clear();
Transform transform;
transform.setTranslation(_translation);
@ -1280,60 +1260,117 @@ void Model::segregateMeshGroups() {
uint32_t numMeshes = (uint32_t)meshes.size();
for (uint32_t i = 0; i < numMeshes; i++) {
const auto& mesh = meshes.at(i);
if (mesh) {
if (!mesh) {
continue;
}
// Create the render payloads
int numParts = (int)mesh->getNumParts();
for (int partIndex = 0; partIndex < numParts; partIndex++) {
if (showingCollisionHull) {
if (_collisionHullMaterials.empty()) {
initCollisionHullMaterials();
}
_collisionRenderItemsSet << std::make_shared<MeshPartPayload>(mesh, partIndex, _collisionHullMaterials[partIndex % NUM_COLLISION_HULL_COLORS], transform, offset);
} else {
_modelMeshRenderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
}
shapeID++;
}
// Create the render payloads
int numParts = (int)mesh->getNumParts();
for (int partIndex = 0; partIndex < numParts; partIndex++) {
_modelMeshRenderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
shapeID++;
}
}
_meshGroupsKnown = true;
}
void Model::createCollisionRenderItemSet() {
assert((bool)_collisionGeometry);
if (_collisionMaterials.empty()) {
initCollisionMaterials();
}
const auto& meshes = _collisionGeometry->getMeshes();
// We should not have any existing renderItems if we enter this section of code
Q_ASSERT(_collisionRenderItemsSet.isEmpty());
Transform identity;
identity.setIdentity();
Transform offset;
offset.postTranslate(_offset);
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
uint32_t numMeshes = (uint32_t)meshes.size();
for (uint32_t i = 0; i < numMeshes; i++) {
const auto& mesh = meshes.at(i);
if (!mesh) {
continue;
}
// Create the render payloads
int numParts = (int)mesh->getNumParts();
for (int partIndex = 0; partIndex < numParts; partIndex++) {
model::MaterialPointer& material = _collisionMaterials[partIndex % NUM_COLLISION_HULL_COLORS];
auto payload = std::make_shared<MeshPartPayload>(mesh, partIndex, material);
payload->updateTransform(identity, offset);
_collisionRenderItemsSet << payload;
}
}
}
bool Model::isRenderable() const {
return !_meshStates.isEmpty() || (isLoaded() && _renderGeometry->getMeshes().empty());
}
bool Model::initWhenReady(render::ScenePointer scene) {
if (isActive() && isRenderable() && !_meshGroupsKnown && isLoaded()) {
segregateMeshGroups();
// NOTE: this only called by SkeletonModel
if (_addedToScene || !isRenderable()) {
return false;
}
render::PendingChanges pendingChanges;
createRenderItemSet();
Transform transform;
transform.setTranslation(_translation);
transform.setRotation(_rotation);
render::PendingChanges pendingChanges;
Transform offset;
offset.setScale(_scale);
offset.postTranslate(_offset);
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
_modelMeshRenderItems.insert(item, renderPayload);
pendingChanges.resetItem(item, renderPayload);
}
bool addedPendingChanges = false;
if (_collisionGeometry) {
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
_collisionRenderItems.insert(item, renderPayload);
pendingChanges.resetItem(item, renderPayload);
}
scene->enqueuePendingChanges(pendingChanges);
updateRenderItems();
_readyWhenAdded = true;
return true;
addedPendingChanges = !_collisionRenderItems.empty();
} else {
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
_modelMeshRenderItems.insert(item, renderPayload);
pendingChanges.resetItem(item, renderPayload);
}
addedPendingChanges = !_modelMeshRenderItems.empty();
}
return false;
_addedToScene = addedPendingChanges;
if (addedPendingChanges) {
scene->enqueuePendingChanges(pendingChanges);
// NOTE: updateRender items enqueues identical pendingChanges (using a lambda)
// so it looks like we're doing double work here, but I don't want to remove the call
// for fear there is some side effect we'll miss. -- Andrew 2016.07.21
// TODO: figure out if we really need this call to updateRenderItems() or not.
updateRenderItems();
}
return true;
}
class CollisionRenderGeometry : public Geometry {
public:
CollisionRenderGeometry(model::MeshPointer mesh) {
_fbxGeometry = std::make_shared<FBXGeometry>();
std::shared_ptr<GeometryMeshes> meshes = std::make_shared<GeometryMeshes>();
meshes->push_back(mesh);
_meshes = meshes;
_meshParts = std::shared_ptr<const GeometryMeshParts>();
}
};
void Model::setCollisionMesh(model::MeshPointer mesh) {
if (mesh) {
_collisionGeometry = std::make_shared<CollisionRenderGeometry>(mesh);
} else {
_collisionGeometry.reset();
}
_needsFixupInScene = true;
}
ModelBlender::ModelBlender() :

View file

@ -81,24 +81,20 @@ public:
// new Scene/Engine rendering support
void setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene);
bool needsFixupInScene() const;
bool readyToAddToScene(RenderArgs* renderArgs = nullptr) const {
return !_needsReload && isRenderable() && isActive();
}
bool needsReload() const { return _needsReload; }
bool initWhenReady(render::ScenePointer scene);
bool addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges,
bool showCollisionHull = false) {
render::PendingChanges& pendingChanges) {
auto getters = render::Item::Status::Getters(0);
return addToScene(scene, pendingChanges, getters, showCollisionHull);
return addToScene(scene, pendingChanges, getters);
}
bool addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges,
render::Item::Status::Getters& statusGetters,
bool showCollisionHull = false);
render::Item::Status::Getters& statusGetters);
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
void renderSetup(RenderArgs* args);
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _renderGeometry->getMeshes().empty()); }
bool isRenderable() const;
bool isVisible() const { return _isVisible; }
@ -114,7 +110,6 @@ public:
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
bool isLoaded() const { return (bool)_renderGeometry; }
bool isCollisionLoaded() const { return (bool)_collisionGeometry; }
void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; }
bool isWireframe() const { return _isWireframe; }
@ -141,13 +136,6 @@ public:
/// Provided as a convenience, will crash if !isLoaded()
// And so that getGeometry() isn't chained everywhere
const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return _renderGeometry->getFBXGeometry(); }
/// Provided as a convenience, will crash if !isCollisionLoaded()
const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return _collisionGeometry->getFBXGeometry(); }
// Set the model to use for collisions.
// Should only be called from the model's rendering thread to avoid access violations of changed geometry.
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
const QUrl& getCollisionURL() const { return _collisionUrl; }
bool isActive() const { return isLoaded(); }
@ -185,6 +173,7 @@ public:
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const;
/// \param jointIndex index of joint in model structure
/// \param rotation[out] rotation of joint in model-frame
/// \return true if joint exists
@ -239,18 +228,19 @@ public:
// returns 'true' if needs fullUpdate after geometry change
bool updateGeometry();
void setCollisionMesh(model::MeshPointer mesh);
void setLoadingPriority(float priority) { _loadingPriority = priority; }
public slots:
void loadURLFinished(bool success);
void loadCollisionModelURLFinished(bool success);
signals:
void setURLFinished(bool success);
void setCollisionModelURLFinished(bool success);
protected:
bool addedToScene() const { return _addedToScene; }
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
float getPupilDilation() const { return _pupilDilation; }
@ -282,10 +272,9 @@ protected:
bool getJointPosition(int jointIndex, glm::vec3& position) const;
Geometry::Pointer _renderGeometry; // only ever set by its watcher
Geometry::Pointer _collisionGeometry; // only ever set by its watcher
Geometry::Pointer _collisionGeometry;
GeometryResourceWatcher _renderWatcher;
GeometryResourceWatcher _collisionWatcher;
glm::vec3 _translation;
glm::quat _rotation;
@ -353,7 +342,6 @@ protected:
QVector<float> _blendshapeCoefficients;
QUrl _url;
QUrl _collisionUrl;
bool _isVisible;
gpu::Buffers _blendedVertexBuffers;
@ -376,10 +364,10 @@ protected:
void recalculateMeshBoxes(bool pickAgainstTriangles = false);
void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes
static model::MaterialPointer _collisionHullMaterial;
void createRenderItemSet();
void createVisibleRenderItemSet();
void createCollisionRenderItemSet();
bool _meshGroupsKnown;
bool _isWireframe;
@ -396,10 +384,10 @@ protected:
QSet<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItemsSet;
QMap<render::ItemID, render::PayloadPointer> _modelMeshRenderItems;
bool _readyWhenAdded { false };
bool _addedToScene { false }; // has been added to scene
bool _needsFixupInScene { true }; // needs to be removed/re-added to scene
bool _needsReload { true };
bool _needsUpdateClusterMatrices { true };
bool _showCollisionHull { false };
mutable bool _needsUpdateTextures { true };
friend class ModelMeshPartPayload;

View file

@ -124,10 +124,6 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
const auto linearDepthPassInputs = LinearDepthPass::Inputs(deferredFrameTransform, deferredFramebuffer).hasVarying();
const auto linearDepthPassOutputs = addJob<LinearDepthPass>("LinearDepth", linearDepthPassInputs);
const auto linearDepthTarget = linearDepthPassOutputs.getN<LinearDepthPass::Outputs>(0);
const auto linearDepthTexture = linearDepthPassOutputs.getN<LinearDepthPass::Outputs>(2);
const auto halfLinearDepthTexture = linearDepthPassOutputs.getN<LinearDepthPass::Outputs>(3);
const auto halfNormalTexture = linearDepthPassOutputs.getN<LinearDepthPass::Outputs>(4);
// Curvature pass
const auto surfaceGeometryPassInputs = SurfaceGeometryPass::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget).hasVarying();
@ -141,14 +137,17 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
const auto scatteringResource = addJob<SubsurfaceScattering>("Scattering");
// AO job
addJob<AmbientOcclusionEffect>("AmbientOcclusion");
const auto ambientOcclusionInputs = AmbientOcclusionEffect::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget).hasVarying();
const auto ambientOcclusionOutputs = addJob<AmbientOcclusionEffect>("AmbientOcclusion", ambientOcclusionInputs);
const auto ambientOcclusionFramebuffer = ambientOcclusionOutputs.getN<AmbientOcclusionEffect::Outputs>(0);
const auto ambientOcclusionUniforms = ambientOcclusionOutputs.getN<AmbientOcclusionEffect::Outputs>(1);
// Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now.
addJob<DrawLight>("DrawLight", lights);
const auto deferredLightingInputs = RenderDeferred::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel,
surfaceGeometryFramebuffer, lowCurvatureNormalFramebuffer, scatteringResource).hasVarying();
surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource).hasVarying();
// DeferredBuffer is complete, now let's shade it into the LightingBuffer
addJob<RenderDeferred>("RenderDeferred", deferredLightingInputs);
@ -175,11 +174,15 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
// Debugging stages
{
// Debugging Deferred buffer job
const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer));
addJob<DebugDeferredBuffer>("DebugDeferredBuffer", debugFramebuffers);
addJob<DebugSubsurfaceScattering>("DebugScattering", deferredLightingInputs);
// Debugging Deferred buffer job
const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, lowCurvatureNormalFramebuffer));
addJob<DebugDeferredBuffer>("DebugDeferredBuffer", debugFramebuffers);
const auto debugAmbientOcclusionInputs = DebugAmbientOcclusion::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget, ambientOcclusionUniforms).hasVarying();
addJob<DebugAmbientOcclusion>("DebugAmbientOcclusion", debugAmbientOcclusionInputs);
// Scene Octree Debuging job
{

View file

@ -61,6 +61,9 @@ void LinearDepthFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depth
void LinearDepthFramebuffer::clear() {
_linearDepthFramebuffer.reset();
_linearDepthTexture.reset();
_downsampleFramebuffer.reset();
_halfLinearDepthTexture.reset();
_halfNormalTexture.reset();
}
void LinearDepthFramebuffer::allocate() {
@ -71,7 +74,6 @@ void LinearDepthFramebuffer::allocate() {
// For Linear Depth:
_linearDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), width, height,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
// _linearDepthTexture->autoGenerateMips(1);
_linearDepthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
_linearDepthFramebuffer->setRenderBuffer(0, _linearDepthTexture);
_linearDepthFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat());
@ -79,6 +81,7 @@ void LinearDepthFramebuffer::allocate() {
// For Downsampling:
_halfLinearDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_halfLinearDepthTexture->autoGenerateMips(5);
_halfNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
@ -193,7 +196,7 @@ void LinearDepthPass::run(const render::SceneContextPointer& sceneContext, const
batch.setResourceTexture(DepthLinearPass_NormalMapSlot, normalTexture);
batch.setPipeline(downsamplePipeline);
batch.draw(gpu::TRIANGLE_STRIP, 4);
_gpuTimer.end(batch);
});

View file

@ -0,0 +1,30 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// simple_opaque_web_browser.frag
// fragment shader
//
// Created by Anthony Thibault on 7/25/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include gpu/Color.slh@>
<@include DeferredBufferWrite.slh@>
// the albedo texture
uniform sampler2D originalTexture;
// the interpolated normal
in vec3 _normal;
in vec4 _color;
in vec2 _texCoord0;
void main(void) {
vec4 texel = texture(originalTexture, _texCoord0.st);
texel = colorToLinearRGBA(texel);
packDeferredFragmentUnlit(normalize(_normal), 1.0, _color.rgb * texel.rgb);
}

View file

@ -2,7 +2,7 @@
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// simple_srgb_texture_unlit_no_tex_alpha.frag
// simple_transparent_web_browser.frag
// fragment shader
//
// Created by Anthony Thibault on 7/25/16.
@ -26,19 +26,11 @@ in vec2 _texCoord0;
void main(void) {
vec4 texel = texture(originalTexture, _texCoord0.st);
texel = colorToLinearRGBA(texel);
const float ALPHA_THRESHOLD = 0.999;
if (_color.a < ALPHA_THRESHOLD) {
packDeferredFragmentTranslucent(
normalize(_normal),
_color.a,
_color.rgb * texel.rgb,
DEFAULT_FRESNEL,
DEFAULT_ROUGHNESS);
} else {
packDeferredFragmentUnlit(
normalize(_normal),
1.0,
_color.rgb * texel.rgb);
}
packDeferredFragmentTranslucent(
normalize(_normal),
_color.a,
_color.rgb * texel.rgb,
DEFAULT_FRESNEL,
DEFAULT_ROUGHNESS);
}

View file

@ -30,13 +30,8 @@ vec2 unpackOcclusionDepth(vec3 raw) {
<@endfunc@>
<@func declareAmbientOcclusion()@>
struct AmbientOcclusionFrameTransform {
vec4 _pixelInfo;
vec4 _depthInfo;
vec4 _stereoInfo;
mat4 _projection[2];
};
<@include DeferredTransform.slh@>
<$declareDeferredFrameTransform()$>
struct AmbientOcclusionParams {
vec4 _resolutionInfo;
@ -47,52 +42,20 @@ struct AmbientOcclusionParams {
float _gaussianCoefs[8];
};
uniform ambientOcclusionFrameTransformBuffer {
AmbientOcclusionFrameTransform frameTransform;
};
uniform ambientOcclusionParamsBuffer {
AmbientOcclusionParams params;
};
float getPerspectiveScale() {
return (params._resolutionInfo.z);
}
int getResolutionLevel() {
return int(params._resolutionInfo.x);
}
vec2 getWidthHeight() {
return vec2(ivec2(frameTransform._pixelInfo.zw) >> getResolutionLevel());
}
float getProjScale() {
return getWidthHeight().y * frameTransform._projection[0][1][1] * 0.5;
}
mat4 getProjection(int side) {
return frameTransform._projection[side];
}
bool isStereo() {
return frameTransform._stereoInfo.x > 0.0f;
}
float getStereoSideWidth() {
return float(int(frameTransform._stereoInfo.y) >> getResolutionLevel());
}
ivec3 getStereoSideInfo(int xPos) {
int sideWidth = int(getStereoSideWidth());
return ivec3(xPos < sideWidth ? ivec2(0, 0) : ivec2(1, sideWidth), sideWidth);
}
float evalZeyeFromZdb(float depth) {
return frameTransform._depthInfo.x / (depth * frameTransform._depthInfo.y + frameTransform._depthInfo.z);
}
vec3 evalEyeNormal(vec3 C) {
//return normalize(cross(dFdy(C), dFdx(C)));
return normalize(cross(dFdx(C), dFdy(C)));
}
float getRadius() {
return params._radiusInfo.x;
}
@ -130,6 +93,10 @@ float getNumSpiralTurns() {
return params._sampleInfo.z;
}
int doFetchMips() {
return int(params._sampleInfo.w);
}
float getBlurEdgeSharpness() {
return params._blurInfo.x;
}
@ -163,6 +130,181 @@ float getBlurCoef(int c) {
<@endfunc@>
<@func declareSamplingDisk()@>
float getAngleDitheringWorldPos(in vec3 pixelWorldPos) {
vec3 worldPosFract = fract(pixelWorldPos * 1.0);
ivec3 pixelPos = ivec3(worldPosFract * 256);
return isDitheringEnabled() * ((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) + (3 * pixelPos.y ^ pixelPos.z + pixelPos.x * pixelPos.z)) * 10 + getFrameDithering();
}
float getAngleDithering(in ivec2 pixelPos) {
// Hash function used in the AlchemyAO paper
return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering();
}
float evalDiskRadius(float Zeye, vec2 imageSize) {
// Choose the screen-space sample radius
// proportional to the projected area of the sphere
float ssDiskRadius = -( getProjScale(getResolutionLevel()) * getRadius() / Zeye ) * getPerspectiveScale();
// clamp the disk to fit in the image otherwise too many unknown
ssDiskRadius = min(ssDiskRadius, imageSize.y * 0.5);
return ssDiskRadius;
}
const float TWO_PI = 6.28;
vec3 getUnitTapLocation(int sampleNumber, float spinAngle){
// Radius relative to ssR
float alpha = float(sampleNumber + 0.5) * getInvNumSamples();
float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle;
return vec3(cos(angle), sin(angle), alpha);
}
vec3 getTapLocation(int sampleNumber, float spinAngle, float outerRadius) {
vec3 tap = getUnitTapLocation(sampleNumber, spinAngle);
tap.xy *= tap.z;
tap *= outerRadius;
return tap;
}
vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, vec2 pixelPos, vec2 imageSize) {
vec3 tap = getTapLocation(sampleNumber, spinAngle, outerRadius);
vec2 tapPos = pixelPos + tap.xy;
if (!(isBorderingEnabled() > 0.0)) {
return tap;
}
bool redoTap = false;
if ((tapPos.x < 0.5)) {
tapPos.x = -tapPos.x;
redoTap = true;
} else if ((tapPos.x > imageSize.x - 0.5)) {
tapPos.x -= (imageSize.x - tapPos.x);
redoTap = true;
}
if ((tapPos.y < 0.5)) {
tapPos.y = -tapPos.y;
redoTap = true;
} else if ((tapPos.y > imageSize.y - 0.5)) {
tapPos.y -= (imageSize.y - tapPos.y);
redoTap = true;
}
/*
if ((tapPos.x < 0.5)) {
tapPos.x = 0.5;
redoTap = true;
} else if ((tapPos.x > imageSize.x - 0.5)) {
tapPos.x = imageSize.x - 0.5;
redoTap = true;
}
if ((tapPos.y < 0.5)) {
tapPos.y = 0.5;
redoTap = true;
} else if ((tapPos.y > imageSize.y - 0.5)) {
tapPos.y = imageSize.y - 0.5;
redoTap = true;
}
*/
if (redoTap) {
tap.xy = tapPos - pixelPos;
tap.z = length(tap.xy);
tap.z = 0;
}
return tap;
}
<@endfunc@>
<@func declareFetchDepthPyramidMap()@>
// the depth pyramid texture
uniform sampler2D pyramidMap;
float getZEye(ivec2 pixel, int level) {
return -texelFetch(pyramidMap, pixel, level).x;
}
const int LOG_MAX_OFFSET = 3;
const int MAX_MIP_LEVEL = 5;
int evalMipFromRadius(float radius) {
// mipLevel = floor(log(ssR / MAX_OFFSET));
return clamp(findMSB(int(radius)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL);
}
vec3 fetchTapUnfiltered(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) {
ivec2 ssP = ivec2(tap.xy) + ssC;
ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y);
vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize;
vec2 fetchUV = vec2(tapUV.x + side.w * 0.5 * (side.x - tapUV.x), tapUV.y);
vec3 P;
P.xy = tapUV;
P.z = -texture(pyramidMap, fetchUV).x;
return P;
}
vec3 fetchTap(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) {
int mipLevel = evalMipFromRadius(tap.z * doFetchMips());
ivec2 ssP = ivec2(tap.xy) + ssC;
ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y);
// We need to divide by 2^mipLevel to read the appropriately scaled coordinate from a MIP-map.
// Manually clamp to the texture size because texelFetch bypasses the texture unit
// ivec2 mipSize = textureSize(pyramidMap, mipLevel);
ivec2 mipSize = max(ivec2(imageSize) >> mipLevel, ivec2(1));
ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), mipSize - ivec2(1));
vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize;
vec2 fetchUV = vec2(tapUV.x + side.w * 0.5 * (side.x - tapUV.x), tapUV.y);
// vec2 tapUV = (vec2(mipP) + vec2(0.5)) / vec2(mipSize);
vec3 P;
P.xy = tapUV;
// P.z = -texelFetch(pyramidMap, mipP, mipLevel).x;
P.z = -textureLod(pyramidMap, fetchUV, float(mipLevel)).x;
return P;
}
<@endfunc@>
<@func declareEvalObscurance()@>
float evalAO(in vec3 C, in vec3 n_C, in vec3 Q) {
vec3 v = Q - C;
float vv = dot(v, v);
float vn = dot(v, n_C);
// Fall off function as recommended in SAO paper
const float epsilon = 0.01;
float f = max(getRadius2() - vv, 0.0);
return f * f * f * max((vn - getFalloffBias()) / (epsilon + vv), 0.0);
}
<@endfunc@>
<@func declareBlurPass(axis)@>
<$declarePackOcclusionDepth()$>
@ -188,7 +330,7 @@ vec2 evalTapWeightedValue(ivec3 side, int r, ivec2 ssC, float key) {
ivec2 tapOffset = <$axis$> * (r * RADIUS_SCALE);
ivec2 ssP = (ssC + tapOffset);
if ((ssP.x < side.y || ssP.x >= side.z + side.y) || (ssP.y < 0 || ssP.y >= int(getWidthHeight().y))) {
if ((ssP.x < side.y || ssP.x >= side.z + side.y) || (ssP.y < 0 || ssP.y >= int(getWidthHeight(getResolutionLevel()).y))) {
return vec2(0.0);
}
vec2 tapOZ = fetchOcclusionDepth(ssC + tapOffset);
@ -206,7 +348,7 @@ vec3 getBlurredOcclusion(vec2 coord) {
ivec2 ssC = ivec2(coord);
// Stereo side info
ivec3 side = getStereoSideInfo(ssC.x);
ivec4 side = getStereoSideInfo(ssC.x, getResolutionLevel());
vec3 rawSample;
vec2 occlusionDepth = fetchOcclusionDepthRaw(ssC, rawSample);
@ -220,11 +362,11 @@ vec3 getBlurredOcclusion(vec2 coord) {
int blurRadius = getBlurRadius();
// negative side first
for (int r = -blurRadius; r <= -1; ++r) {
weightedSums += evalTapWeightedValue(side, r, ssC, key);
weightedSums += evalTapWeightedValue(side.xyz, r, ssC, key);
}
// then positive side
for (int r = 1; r <= blurRadius; ++r) {
weightedSums += evalTapWeightedValue(side, r, ssC, key);
weightedSums += evalTapWeightedValue(side.xyz, r, ssC, key);
}
// Final normalization

View file

@ -0,0 +1,130 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// Created by Sam Gateau on 1/1/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include ssao.slh@>
<$declareAmbientOcclusion()$>
<$declareFetchDepthPyramidMap()$>
<$declareSamplingDisk()$>
<$declareEvalObscurance()$>
<$declarePackOcclusionDepth()$>
<@include gpu/Color.slh@>
<$declareColorWheel()$>
struct DebugParams{
vec4 pixelInfo;
};
uniform debugAmbientOcclusionBuffer {
DebugParams debugParams;
};
vec2 getDebugCursorTexcoord(){
return debugParams.pixelInfo.xy;
}
out vec4 outFragColor;
void main(void) {
vec2 imageSize = getSideImageSize(getResolutionLevel());
// In debug adjust the correct frag pixel based on base resolution
vec2 fragCoord = gl_FragCoord.xy;
if (getResolutionLevel() > 0) {
fragCoord /= float (1 << getResolutionLevel());
}
// Pixel Debugged
vec2 cursorUV = getDebugCursorTexcoord();
vec2 cursorPixelPos = cursorUV * imageSize;
ivec2 ssC = ivec2(cursorPixelPos);
// Fetch the z under the pixel (stereo or not)
float Zeye = getZEye(ssC, 0);
// Stereo side info
ivec4 side = getStereoSideInfo(ssC.x, getResolutionLevel());
// From now on, ssC is the pixel pos in the side
ssC.x -= side.y;
vec2 fragPos = (vec2(ssC) + vec2(0.5)) / imageSize;
// The position and normal of the pixel fragment in Eye space
vec3 Cp = evalEyePositionFromZeye(side.x, Zeye, fragPos);
vec3 Cn = evalEyeNormal(Cp);
// Choose the screen-space sample radius
float ssDiskRadius = evalDiskRadius(Cp.z, imageSize);
vec2 fragToCursor = cursorPixelPos - fragCoord.xy;
if (dot(fragToCursor,fragToCursor) > ssDiskRadius * ssDiskRadius) {
discard;
}
// Let's make noise
//float randomPatternRotationAngle = getAngleDithering(ssC);
vec3 wCp = (getViewInverse() * vec4(Cp, 1.0)).xyz;
float randomPatternRotationAngle = getAngleDitheringWorldPos(wCp);
// Accumulate the Obscurance for each samples
float sum = 0.0;
float keepTapRadius = 1.0;
int keepedMip = -1;
bool keep = false;
for (int i = 0; i < getNumSamples(); ++i) {
vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, cursorPixelPos, imageSize);
// The occluding point in camera space
vec2 fragToTap = vec2(ssC) + tap.xy - fragCoord.xy;
if (dot(fragToTap,fragToTap) < keepTapRadius) {
keep = true;
keepedMip = evalMipFromRadius(tap.z * doFetchMips());
}
vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize);
vec3 Q = evalEyePositionFromZeye(side.x, tapUVZ.z, tapUVZ.xy);
sum += float(tap.z > 0.0) * evalAO(Cp, Cn, Q);
}
float A = max(0.0, 1.0 - sum * getObscuranceScaling() * 5.0 * getInvNumSamples());
<! // KEEP IT for Debugging
// Bilateral box-filter over a quad for free, respecting depth edges
// (the difference that this makes is subtle)
if (abs(dFdx(Cp.z)) < 0.02) {
A -= dFdx(A) * ((ssC.x & 1) - 0.5);
}
if (abs(dFdy(Cp.z)) < 0.02) {
A -= dFdy(A) * ((ssC.y & 1) - 0.5);
}
!>
outFragColor = vec4(packOcclusionDepth(A, CSZToDephtKey(Cp.z)), 1.0);
if ((dot(fragToCursor,fragToCursor) < (100.0 * keepTapRadius * keepTapRadius) )) {
// outFragColor = vec4(vec3(A), 1.0);
outFragColor = vec4(vec3(A), 1.0);
return;
}
if (!keep) {
outFragColor = vec4(0.1);
} else {
outFragColor.rgb = colorWheel(float(keepedMip)/float(MAX_MIP_LEVEL));
}
}

View file

@ -11,129 +11,58 @@
<@include ssao.slh@>
<$declareAmbientOcclusion()$>
<$declareFetchDepthPyramidMap()$>
<$declareSamplingDisk()$>
<$declareEvalObscurance()$>
<$declarePackOcclusionDepth()$>
const int LOG_MAX_OFFSET = 3;
const int MAX_MIP_LEVEL = 5;
// the depth pyramid texture
uniform sampler2D pyramidMap;
float getZEye(ivec2 pixel) {
return -texelFetch(pyramidMap, pixel, getResolutionLevel()).x;
}
vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) {
// compute the view space position using the depth
// basically manually pick the proj matrix components to do the inverse
float Xe = (-Zeye * (texcoord.x * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][0] - frameTransform._projection[side][3][0]) / frameTransform._projection[side][0][0];
float Ye = (-Zeye * (texcoord.y * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][1] - frameTransform._projection[side][3][1]) / frameTransform._projection[side][1][1];
return vec3(Xe, Ye, Zeye);
}
out vec4 outFragColor;
uniform sampler2D normalMap;
float getAngleDithering(in ivec2 pixelPos) {
// Hash function used in the AlchemyAO paper
return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering();
}
const float TWO_PI = 6.28;
vec2 tapLocation(int sampleNumber, float spinAngle, out float ssR){
// Radius relative to ssR
float alpha = float(sampleNumber + 0.5) * getInvNumSamples();
float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle;
ssR = alpha;
return vec2(cos(angle), sin(angle));
}
vec3 getOffsetPosition(ivec3 side, ivec2 ssC, vec2 unitOffset, float ssR) {
// Derivation:
// mipLevel = floor(log(ssR / MAX_OFFSET));
int mipLevel = clamp(findMSB(int(ssR)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL);
ivec2 ssOffset = ivec2(ssR * unitOffset);
ivec2 ssP = ssOffset + ssC;
if (bool(isBorderingEnabled())) {
ssP.x = ((ssP.x < 0 || ssP.x >= side.z) ? ssC.x - ssOffset.x : ssP.x);
ssP.y = ((ssP.y < 0 || ssP.y >= int(getWidthHeight().y)) ? ssC.y - ssOffset.y : ssP.y);
}
ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y);
vec3 P;
// We need to divide by 2^mipLevel to read the appropriately scaled coordinate from a MIP-map.
// Manually clamp to the texture size because texelFetch bypasses the texture unit
ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), textureSize(pyramidMap, getResolutionLevel() + mipLevel) - ivec2(1));
P.z = -texelFetch(pyramidMap, mipP, getResolutionLevel() + mipLevel).r;
// Offset to pixel center
vec2 tapUV = (vec2(ssP) + vec2(0.5)) / float(side.z);
P = evalEyePositionFromZeye(side.x, P.z, tapUV);
return P;
}
float sampleAO(in ivec3 side, in ivec2 ssC, in vec3 C, in vec3 n_C, in float ssDiskRadius, in int tapIndex, in float randomPatternRotationAngle) {
// Offset on the unit disk, spun for this pixel
float ssR;
vec2 unitOffset = tapLocation(tapIndex, randomPatternRotationAngle, ssR);
ssR *= ssDiskRadius;
// The occluding point in camera space
vec3 Q = getOffsetPosition(side, ssC, unitOffset, ssR);
vec3 v = Q - C;
float vv = dot(v, v);
float vn = dot(v, n_C);
// Fall off function as recommended in SAO paper
const float epsilon = 0.01;
float f = max(getRadius2() - vv, 0.0);
return f * f * f * max((vn - getFalloffBias()) / (epsilon + vv), 0.0);
}
void main(void) {
vec2 imageSize = getSideImageSize(getResolutionLevel());
// Pixel being shaded
ivec2 ssC = ivec2(gl_FragCoord.xy);
vec2 fragCoord = gl_FragCoord.xy;
ivec2 ssC = ivec2(fragCoord.xy);
// Fetch the z under the pixel (stereo or not)
float Zeye = getZEye(ssC);
float Zeye = getZEye(ssC, 0);
// Stereo side info
ivec3 side = getStereoSideInfo(ssC.x);
ivec4 side = getStereoSideInfo(ssC.x, getResolutionLevel());
// From now on, ssC is the pixel pos in the side
ssC.x -= side.y;
vec2 fragPos = (vec2(ssC) + 0.5) / getStereoSideWidth();
vec2 fragPos = (vec2(ssC) + vec2(0.5)) / imageSize;
// The position and normal of the pixel fragment in Eye space
vec3 Cp = evalEyePositionFromZeye(side.x, Zeye, fragPos);
vec3 Cn = evalEyeNormal(Cp);
// Choose the screen-space sample radius
// proportional to the projected area of the sphere
float ssDiskRadius = -getProjScale() * getRadius() / Cp.z;
float ssDiskRadius = evalDiskRadius(Cp.z, imageSize);
// Let's make noise
float randomPatternRotationAngle = getAngleDithering(ssC);
//vec3 wCp = (getViewInverse() * vec4(Cp, 1.0)).xyz;
//float randomPatternRotationAngle = getAngleDitheringWorldPos(wCp);
// Accumulate the Obscurance for each samples
float sum = 0.0;
for (int i = 0; i < getNumSamples(); ++i) {
sum += sampleAO(side, ssC, Cp, Cn, ssDiskRadius, i, randomPatternRotationAngle);
vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, ssC, imageSize);
vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize);
vec3 Q = evalEyePositionFromZeye(side.x, tapUVZ.z, tapUVZ.xy);
sum += float(tap.z > 0.0) * evalAO(Cp, Cn, Q);
}
float A = max(0.0, 1.0 - sum * getObscuranceScaling() * 5.0 * getInvNumSamples());
<! // KEEP IT for Debugging
// KEEP IT for Debugging
// Bilateral box-filter over a quad for free, respecting depth edges
// (the difference that this makes is subtle)
if (abs(dFdx(Cp.z)) < 0.02) {
@ -142,20 +71,16 @@ void main(void) {
if (abs(dFdy(Cp.z)) < 0.02) {
A -= dFdy(A) * ((ssC.y & 1) - 0.5);
}
!>
outFragColor = vec4(packOcclusionDepth(A, CSZToDephtKey(Cp.z)), 1.0);
<! // KEEP IT for Debugging
// Debug Normal: outFragColor = vec4((Cn + vec3(1.0))* 0.5, 1.0);
// Debug Radius outFragColor = vec4(vec3(ssDiskRadius / 100.0), 1.0);
// Debug MaxMiplevel outFragColor = vec4(1.0 - vec3(float(clamp(findMSB(int(ssDiskRadius)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL))/ float(MAX_MIP_LEVEL)), 1.0);
// Debug OffsetPosition
float ssR;
vec2 unitOffset = tapLocation(int(getNumSamples() - 1), 0, ssR);
vec3 Q = getOffsetPosition(side, ssC, unitOffset, ssR * ssDiskRadius);
//outFragColor = vec4(vec3(Q.x / 10.0, Q.y / 2.0, -Q.z/ 3.0), 1.0);
vec3 v = normalize(Q - Cp);
outFragColor = vec4((v + vec3(1.0))* 0.5, 1.0);
!>
/* {
vec3 tap = getTapLocationClamped(2, randomPatternRotationAngle, ssDiskRadius, ssC, imageSize);
vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize);
vec2 fetchUV = vec2(tapUVZ.x + side.w * 0.5 * (side.x - tapUVZ.x), tapUVZ.y);
vec3 Q = evalEyePositionFromZeye(side.x, tapUVZ.z, tapUVZ.xy);
outFragColor = vec4(fetchUV, 0.0, 1.0);
}*/
}

View file

@ -12,7 +12,7 @@
#include "DoubleHashKey.h"
const uint32_t NUM_PRIMES = 64;
const uint32_t PRIMES[] = {
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,
@ -27,8 +27,8 @@ 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 << 3);
hash ^= (hash >> 6);
hash += ~(hash << 11);
return hash ^ (hash >> 16);
}

View file

@ -22,9 +22,9 @@ public:
DoubleHashKey() : _hash(0), _hash2(0) { }
DoubleHashKey(uint32_t value, uint32_t primeIndex = 0) :
_hash(hashFunction(value, primeIndex)),
_hash2(hashFunction2(value)) {
DoubleHashKey(uint32_t value, uint32_t primeIndex = 0) :
_hash(hashFunction(value, primeIndex)),
_hash2(hashFunction2(value)) {
}
void clear() { _hash = 0; _hash2 = 0; }

View file

@ -1,3 +1,6 @@
"use strict";
/* jslint vars: true, plusplus: true */
//
// defaultScripts.js
// examples
@ -8,25 +11,75 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var DEFAULT_SCRIPTS = [
"system/progress.js",
"system/away.js",
"system/users.js",
"system/mute.js",
"system/goto.js",
"system/hmd.js",
"system/marketplaces/marketplace.js",
"system/edit.js",
"system/mod.js",
"system/selectAudioDevice.js",
"system/notifications.js",
"system/controllers/handControllerGrab.js",
"system/controllers/handControllerPointer.js",
"system/controllers/squeezeHands.js",
"system/controllers/grab.js",
"system/controllers/teleport.js",
"system/controllers/toggleAdvancedMovementForHandControllers.js",
"system/dialTone.js",
"system/firstPersonHMD.js",
"system/snapshot.js"
];
Script.load("system/progress.js");
Script.load("system/away.js");
Script.load("system/users.js");
Script.load("system/mute.js");
Script.load("system/goto.js");
Script.load("system/hmd.js");
Script.load("system/marketplaces/marketplace.js");
Script.load("system/edit.js");
Script.load("system/mod.js");
Script.load("system/selectAudioDevice.js");
Script.load("system/notifications.js");
Script.load("system/controllers/handControllerGrab.js");
Script.load("system/controllers/handControllerPointer.js");
Script.load("system/controllers/squeezeHands.js");
Script.load("system/controllers/grab.js");
Script.load("system/controllers/teleport.js");
Script.load("system/controllers/toggleAdvancedMovementForHandControllers.js")
Script.load("system/dialTone.js");
Script.load("system/firstPersonHMD.js");
Script.load("system/snapshot.js");
// add a menu item for debugging
var MENU_CATEGORY = "Developer";
var MENU_ITEM = "Debug defaultScripts.js";
var debuggingDefaultScripts = false;
if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) {
Menu.addMenuItem({
menuName: MENU_CATEGORY,
menuItemName: MENU_ITEM,
isCheckable: true,
isChecked: false,
grouping: "Advanced"
});
}
// start all scripts
if (Menu.isOptionChecked(MENU_ITEM)) {
// we're debugging individual default scripts
// so we load each into its own ScriptEngine instance
debuggingDefaultScripts = true;
for (var i in DEFAULT_SCRIPTS) {
Script.load(DEFAULT_SCRIPTS[i]);
}
} else {
// include all default scripts into this ScriptEngine
for (var j in DEFAULT_SCRIPTS) {
Script.include(DEFAULT_SCRIPTS[j]);
}
}
function stopLoadedScripts() {
if (debuggingDefaultScripts) {
// remove debug script loads
var runningScripts = ScriptDiscoveryService.getRunning();
for (var i in runningScripts) {
var scriptName = runningScripts[i].name;
for (var j in DEFAULT_SCRIPTS) {
if (DEFAULT_SCRIPTS[j].slice(-scriptName.length) === scriptName) {
ScriptDiscoveryService.stopScript(runningScripts[i].url);
}
}
}
if (!Menu.isOptionChecked(MENU_ITEM)) {
Menu.removeMenuItem(MENU_CATEGORY, MENU_ITEM);
}
}
}
Script.scriptEnding.connect(stopLoadedScripts);

View file

@ -0,0 +1,88 @@
//
// surfaceGeometryPass.qml
//
// Created by Sam Gateau on 6/6/2016
// Copyright 2016 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"
import "../lib/plotperf"
Column {
spacing: 8
Column {
id: surfaceGeometry
spacing: 10
Column{
Repeater {
model: [
"Radius:radius:2.0:false",
"Level:obscuranceLevel:1.0:false",
"Num Taps:numSamples:32:true",
"Taps Spiral:numSpiralTurns:10.0:false",
"Falloff Bias:falloffBias:0.2:false",
"Edge Sharpness:edgeSharpness:1.0:false",
"Blur Radius:blurRadius:10.0:false",
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: (modelData.split(":")[3] == 'true')
config: Render.getConfig("AmbientOcclusion")
property: modelData.split(":")[1]
max: modelData.split(":")[2]
min: 0.0
}
}
}
Row{
Column {
Repeater {
model: [
"resolutionLevel:resolutionLevel",
"ditheringEnabled:ditheringEnabled",
"fetchMipsEnabled:fetchMipsEnabled",
"borderingEnabled:borderingEnabled"
]
CheckBox {
text: qsTr(modelData.split(":")[0])
checked: Render.getConfig("AmbientOcclusion")[modelData.split(":")[1]]
onCheckedChanged: { Render.getConfig("AmbientOcclusion")[modelData.split(":")[1]] = checked }
}
}
}
Column {
Repeater {
model: [
"debugEnabled:showCursorPixel"
]
CheckBox {
text: qsTr(modelData.split(":")[0])
checked: Render.getConfig("DebugAmbientOcclusion")[modelData.split(":")[1]]
onCheckedChanged: { Render.getConfig("DebugAmbientOcclusion")[modelData.split(":")[1]] = checked }
}
}
}
}
PlotPerf {
title: "Timing"
height: 50
object: Render.getConfig("AmbientOcclusion")
valueUnit: "ms"
valueScale: 1
valueNumDigits: "4"
plots: [
{
prop: "gpuTime",
label: "gpu",
color: "#FFFFFF"
}
]
}
}
}

View file

@ -0,0 +1,38 @@
//
// debugSurfaceGeometryPass.js
//
// Created by Sam Gateau on 6/6/2016
// Copyright 2016 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
//
// Set up the qml ui
var qml = Script.resolvePath('ambientOcclusionPass.qml');
var window = new OverlayWindow({
title: 'Ambient Occlusion Pass',
source: qml,
width: 400, height: 250,
});
window.setPosition(Window.innerWidth - 420, 50 + 550 + 50);
window.closed.connect(function() { Script.stop(); });
var moveDebugCursor = false;
Controller.mousePressEvent.connect(function (e) {
if (e.isMiddleButton) {
moveDebugCursor = true;
setDebugCursor(e.x, e.y);
}
});
Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; });
Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); });
function setDebugCursor(x, y) {
nx = (x / Window.innerWidth);
ny = 1.0 - ((y) / (Window.innerHeight - 32));
Render.getConfig("DebugAmbientOcclusion").debugCursorTexcoord = { x: nx, y: ny };
}

View file

@ -24,6 +24,7 @@ Column {
"Emissive:LightingModel:enableEmissive",
"Lightmap:LightingModel:enableLightmap",
"Background:LightingModel:enableBackground",
"ssao:AmbientOcclusion:enabled",
]
CheckBox {
text: modelData.split(":")[0]

View file

@ -1,8 +1,8 @@
"use strict";
/*jslint vars: true, plusplus: true*/
/*global HMD, AudioDevice, MyAvatar, Controller, Script, Overlays, print*/
//
// away.js
//
// examples
//
// Created by Howard Stearns 11/3/15
@ -13,9 +13,11 @@
//
// Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key.
// See MAIN CONTROL, below, for what "paused" actually does.
(function() { // BEGIN LOCAL_SCOPE
var OVERLAY_WIDTH = 1920;
var OVERLAY_HEIGHT = 1080;
var OVERLAY_RATIO = OVERLAY_WIDTH / OVERLAY_HEIGHT;
var OVERLAY_DATA = {
width: OVERLAY_WIDTH,
height: OVERLAY_HEIGHT,
@ -51,7 +53,11 @@ var AWAY_INTRO = {
var _animation = AnimationCache.prefetch(AWAY_INTRO.url);
function playAwayAnimation() {
MyAvatar.overrideAnimation(AWAY_INTRO.url, AWAY_INTRO.playbackRate, AWAY_INTRO.loopFlag, AWAY_INTRO.startFrame, AWAY_INTRO.endFrame);
MyAvatar.overrideAnimation(AWAY_INTRO.url,
AWAY_INTRO.playbackRate,
AWAY_INTRO.loopFlag,
AWAY_INTRO.startFrame,
AWAY_INTRO.endFrame);
}
function stopAwayAnimation() {
@ -74,8 +80,6 @@ function moveCloserToCamera(positionAtHUD) {
}
function showOverlay() {
var properties = {visible: true};
if (HMD.active) {
// make sure desktop version is hidden
Overlays.editOverlay(overlay, { visible: false });
@ -252,8 +256,9 @@ function maybeGoAway() {
}
}
// If the mouse has gone from captured, to non-captured state, then it likely means the person is still in the HMD, but
// tabbed away from the application (meaning they don't have mouse control) and they likely want to go into an away state
// If the mouse has gone from captured, to non-captured state, then it likely means the person is still in the HMD,
// but tabbed away from the application (meaning they don't have mouse control) and they likely want to go into
// an away state
if (Reticle.mouseCaptured !== wasMouseCaptured) {
wasMouseCaptured = !wasMouseCaptured;
if (!wasMouseCaptured) {
@ -298,3 +303,5 @@ Script.scriptEnding.connect(function () {
Controller.mousePressEvent.disconnect(goActive);
Controller.keyPressEvent.disconnect(maybeGoActive);
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
// grab.js
// examples
//
@ -9,11 +11,11 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global print, Mouse, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */
(function() { // BEGIN LOCAL_SCOPE
Script.include("../libraries/utils.js");
// objects that appear smaller than this can't be grabbed
var MAX_SOLID_ANGLE = 0.01;
var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed
var ZERO_VEC3 = {
x: 0,
@ -39,18 +41,6 @@ var DEFAULT_GRABBABLE_DATA = {
};
var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed
var ZERO_VEC3 = {
x: 0,
y: 0,
z: 0
};
var IDENTITY_QUAT = {
x: 0,
y: 0,
z: 0,
w: 0
};
var ACTION_TTL = 10; // seconds
var enabled = true;
@ -121,7 +111,7 @@ function mouseIntersectionWithPlane(pointOnPlane, planeNormal, event, maxDistanc
}
// Mouse class stores mouse click and drag info
Mouse = function() {
function Mouse() {
this.current = {
x: 0,
y: 0
@ -191,7 +181,7 @@ var mouse = new Mouse();
// Beacon class stores info for drawing a line at object's target position
Beacon = function() {
function Beacon() {
this.height = 0.10;
this.overlayID = Overlays.addOverlay("line3d", {
color: {
@ -243,7 +233,7 @@ var beacon = new Beacon();
// Grabber class stores and computes info for grab behavior
Grabber = function() {
function Grabber() {
this.isGrabbing = false;
this.entityID = null;
this.actionID = null;
@ -344,7 +334,6 @@ Grabber.prototype.pressEvent = function(event) {
mouse.startDrag(event);
var now = Date.now();
this.lastHeartBeat = 0;
var clickedEntity = pickResults.entityID;
@ -385,7 +374,7 @@ Grabber.prototype.pressEvent = function(event) {
beacon.updatePosition(this.startPosition);
if(!entityIsGrabbedByOther(this.entityID)){
if (!entityIsGrabbedByOther(this.entityID)) {
this.moveEvent(event);
}
@ -452,7 +441,7 @@ Grabber.prototype.moveEvent = function(event) {
// see if something added/restored gravity
var entityProperties = Entities.getEntityProperties(this.entityID);
if (Vec3.length(entityProperties.gravity) != 0) {
if (Vec3.length(entityProperties.gravity) !== 0.0) {
this.originalGravity = entityProperties.gravity;
}
this.currentPosition = entityProperties.position;
@ -500,7 +489,8 @@ Grabber.prototype.moveEvent = function(event) {
};
} else {
var cameraPosition = Camera.getPosition();
newPointOnPlane = mouseIntersectionWithPlane(this.pointOnPlane, this.planeNormal, mouse.current, this.maxDistance);
newPointOnPlane = mouseIntersectionWithPlane(
this.pointOnPlane, this.planeNormal, mouse.current, this.maxDistance);
var relativePosition = Vec3.subtract(newPointOnPlane, cameraPosition);
var distance = Vec3.length(relativePosition);
if (distance > this.maxDistance) {
@ -625,7 +615,7 @@ function editEvent(channel, message, sender, localOnly) {
return;
}
try {
data = JSON.parse(message);
var data = JSON.parse(message);
if ("enabled" in data) {
enabled = !data["enabled"];
}
@ -640,3 +630,5 @@ Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Messages.subscribe("edit-events");
Messages.messageReceived.connect(editEvent);
}()); // END LOCAL_SCOPE

View file

@ -1,4 +1,5 @@
"use strict";
// handControllerGrab.js
//
// Created by Eric Levin on 9/2/15
@ -10,7 +11,8 @@
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global setEntityCustomData, getEntityCustomData, vec3toStr, flatten, Xform */
(function() { // BEGIN LOCAL_SCOPE
Script.include("/~/system/libraries/utils.js");
Script.include("/~/system/libraries/Xform.js");
@ -26,8 +28,6 @@ var WANT_DEBUG_SEARCH_NAME = null;
// these tune time-averaging and "on" value for analog trigger
//
var SPARK_MODEL_SCALE_FACTOR = 0.75;
var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing
var TRIGGER_OFF_VALUE = 0.1;
var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab
@ -88,7 +88,6 @@ var COLORS_GRAB_DISTANCE_HOLD = {
};
var LINE_LENGTH = 500;
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
//
@ -129,7 +128,6 @@ var ZERO_VEC = {
var NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
// these control how long an abandoned pointer line or action will hang around
var LIFETIME = 10;
var ACTION_TTL = 15; // seconds
var ACTION_TTL_REFRESH = 5;
var PICKS_PER_SECOND_PER_HAND = 60;
@ -2643,3 +2641,5 @@ function cleanup() {
Script.scriptEnding.connect(cleanup);
Script.update.connect(update);
}()); // END LOCAL_SCOPE

View file

@ -1,6 +1,4 @@
"use strict";
/*jslint vars: true, plusplus: true*/
/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/
//
// handControllerPointer.js
@ -13,6 +11,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
// Control the "mouse" using hand controller. (HMD and desktop.)
// First-person only.
// Starts right handed, but switches to whichever is free: Whichever hand was NOT most recently squeezed.
@ -440,7 +440,7 @@ function clearSystemLaser() {
HMD.disableHandLasers(BOTH_HUD_LASERS);
systemLaserOn = false;
weMovedReticle = true;
Reticle.position = { x: -1, y: -1 };
Reticle.position = { x: -1, y: -1 };
}
function setColoredLaser() { // answer trigger state if lasers supported, else falsey.
var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW;
@ -508,3 +508,5 @@ Script.scriptEnding.connect(function () {
Script.clearInterval(settingsChecker);
OffscreenFlags.navigationFocusDisabled = false;
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// controllers/squeezeHands.js
//
@ -10,6 +12,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
var lastLeftTrigger = 0;
var lastRightTrigger = 0;
var leftHandOverlayAlpha = 0;
@ -82,3 +86,5 @@ function shutdown() {
Script.scriptEnding.connect(shutdown);
init();
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
// Created by james b. pollack @imgntn on 7/2/2016
// Copyright 2016 High Fidelity, Inc.
//
@ -6,6 +8,8 @@
// 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 inTeleportMode = false;
var SMOOTH_ARRIVAL_SPACING = 33;
@ -594,7 +598,6 @@ function getAvatarFootOffset() {
}
})
var myPosition = MyAvatar.position;
var offset = upperLeg + lowerLeg + foot + toe + toeTop;
offset = offset / 100;
return offset;
@ -737,4 +740,6 @@ var handleHandMessages = function(channel, message, sender) {
}
Messages.subscribe('Hifi-Teleport-Disabler');
Messages.messageReceived.connect(handleHandMessages);
Messages.messageReceived.connect(handleHandMessages);
}()); // END LOCAL_SCOPE

View file

@ -1,14 +1,18 @@
"use strict";
// Created by james b. pollack @imgntn on 8/18/2016
// Copyright 2016 High Fidelity, Inc.
//
//advanced movements settings are in individual controller json files
//what we do is check the status of the 'advance movement' checkbox when you enter HMD mode
//if 'advanced movement' is checked...we give you the defaults that are in the json.
//if 'advanced movement' is not checked... we override the advanced controls with basic ones.
// advanced movements settings are in individual controller json files
// what we do is check the status of the 'advance movement' checkbox when you enter HMD mode
// if 'advanced movement' is checked...we give you the defaults that are in the json.
// if 'advanced movement' is not checked... we override the advanced controls with basic ones.
//
// 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 mappingName, basicMapping, isChecked;
var TURN_RATE = 1000;
@ -138,4 +142,6 @@ HMD.displayModeChanged.connect(function(isHMDMode) {
}
}
});
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// dialTone.js
// examples
@ -10,9 +12,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
// setup the local sound we're going to use
var connectSound = SoundCache.getSound(Script.resolvePath("assets/sounds/hello.wav"));
var disconnectSound = SoundCache.getSound(Script.resolvePath("assets/sounds/goodbye.wav"));
var micMutedSound = SoundCache.getSound(Script.resolvePath("assets/sounds/goodbye.wav"));
@ -36,3 +38,5 @@ AudioDevice.muteToggled.connect(function () {
Audio.playSound(micMutedSound, soundOptions);
}
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
// newEditEntities.js
// examples
//
@ -11,13 +13,13 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton";
var SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
var EDIT_TOOLBAR = "com.highfidelity.interface.toolbar.edit";
/* globals SelectionDisplay, SelectionManager, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Toolbars,
progressDialog, tooltip, ParticleExplorerTool */
Script.include([
"libraries/stringHelpers.js",
"libraries/dataViewHelpers.js",
@ -94,7 +96,6 @@ var SHOULD_SHOW_PROPERTY_MENU = false;
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain.";
var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain.";
var mode = 0;
var isActive = false;
var IMPORTING_SVO_OVERLAY_WIDTH = 144;
@ -502,8 +503,6 @@ var orientation;
var intersection;
var SCALE_FACTOR = 200.0;
function rayPlaneIntersection(pickRay, point, normal) { //
//
// This version of the test returns the intersection of a line with a plane
@ -1726,3 +1725,5 @@ entityListTool.webView.webEventReceived.connect(function (data) {
}
}
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// firstPersonHMD.js
// system
@ -9,9 +11,13 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
// Automatically enter first person mode when entering HMD mode
HMD.displayModeChanged.connect(function(isHMDMode) {
if (isHMDMode) {
Camera.setModeString("first person");
}
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// goto.js
// scripts/system/
@ -9,6 +11,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
@ -38,3 +42,5 @@ Script.scriptEnding.connect(function () {
button.clicked.disconnect(onClicked);
DialogsManager.addressBarShown.disconnect(onAddressBarShown);
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// hmd.js
// scripts/system/
@ -9,6 +11,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
var headset; // The preferred headset. Default to the first one found in the following list.
var displayMenuName = "Display";
var desktopMenuItemName = "Desktop";
@ -36,13 +40,13 @@ if (headset) {
visible: true,
hoverState: 2,
defaultState: 0,
alpha: 0.9,
alpha: 0.9
});
onHmdChanged(HMD.active);
button.clicked.connect(onClicked);
HMD.displayModeChanged.connect(onHmdChanged);
Script.scriptEnding.connect(function () {
toolBar.removeButton("hmdToggle");
button.clicked.disconnect(onClicked);
@ -50,3 +54,4 @@ if (headset) {
});
}
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// clara.js
//
@ -9,6 +11,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
var toolIconUrl = Script.resolvePath("../assets/images/tools/");
var qml = Script.resolvePath("../../../resources/qml/MarketplaceComboBox.qml")
@ -77,3 +81,5 @@ Script.scriptEnding.connect(function () {
browseExamplesButton.clicked.disconnect(onClick);
marketplaceWindow.visibleChanged.disconnect(onExamplesWindowVisibilityChanged);
});
}()); // END LOCAL_SCOPE

View file

@ -8,6 +8,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
/* global WebTablet */
Script.include("../libraries/WebTablet.js");
@ -100,3 +102,5 @@ Script.scriptEnding.connect(function () {
browseExamplesButton.clicked.disconnect(onClick);
marketplaceWindow.visibleChanged.disconnect(onMarketplaceWindowVisibilityChanged);
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// mod.js
// scripts/system/
@ -9,6 +11,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
// grab the toolbar
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
@ -39,7 +43,7 @@ function removeOverlays() {
// enumerate the overlays and remove them
var modOverlayKeys = Object.keys(modOverlays);
for (i = 0; i < modOverlayKeys.length; ++i) {
for (var i = 0; i < modOverlayKeys.length; ++i) {
var avatarID = modOverlayKeys[i];
Overlays.deleteOverlay(modOverlays[avatarID]);
}
@ -72,7 +76,7 @@ function updateOverlays() {
var identifiers = AvatarList.getAvatarIdentifiers();
for (i = 0; i < identifiers.length; ++i) {
for (var i = 0; i < identifiers.length; ++i) {
var avatarID = identifiers[i];
if (avatarID === null) {
@ -138,7 +142,7 @@ function handleSelectedOverlay(clickedOverlay) {
// see this is one of our mod overlays
var modOverlayKeys = Object.keys(modOverlays)
for (i = 0; i < modOverlayKeys.length; ++i) {
for (var i = 0; i < modOverlayKeys.length; ++i) {
var avatarID = modOverlayKeys[i];
var modOverlay = modOverlays[avatarID];
@ -214,3 +218,5 @@ Script.scriptEnding.connect(function() {
removeOverlays();
triggerMapping.disable();
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// goto.js
// scripts/system/
@ -9,8 +11,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
(function() { // BEGIN LOCAL_SCOPE
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var button = toolBar.addButton({
objectName: "mute",
@ -33,7 +36,7 @@ function onMuteToggled() {
}
onMuteToggled();
function onClicked(){
var menuItem = "Mute Microphone";
var menuItem = "Mute Microphone";
Menu.setIsOptionChecked(menuItem, !Menu.isOptionChecked(menuItem));
}
button.clicked.connect(onClicked);
@ -44,3 +47,5 @@ Script.scriptEnding.connect(function () {
button.clicked.disconnect(onClicked);
AudioDevice.muteToggled.disconnect(onMuteToggled);
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// notifications.js
// Version 0.801
@ -56,6 +58,8 @@
// }
// }
(function() { // BEGIN LOCAL_SCOPE
Script.include("./libraries/soundArray.js");
var width = 340.0; //width of notification overlay
@ -105,7 +109,7 @@ var NotificationType = {
return NotificationType.UNKNOWN;
}
var preMenuItemName = menuItemName.substr(0, menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length);
for (type in this.properties) {
for (var type in this.properties) {
if (this.properties[type].text === preMenuItemName) {
return parseInt(type) + 1;
}
@ -120,7 +124,7 @@ var NotificationType = {
var randomSounds = new SoundArray({ localOnly: true }, true);
var numberOfSounds = 2;
for (var i = 1; i <= numberOfSounds; i++) {
randomSounds.addSound(Script.resolvePath("assets/sounds/notification-general"+ i + ".raw"));
}
@ -230,9 +234,9 @@ function calculate3DOverlayPositions(noticeWidth, noticeHeight, y) {
};
}
// Pushes data to each array and sets up data for 2nd dimension array
// Pushes data to each array and sets up data for 2nd dimension array
// to handle auxiliary data not carried by the overlay class
// specifically notification "heights", "times" of creation, and .
// specifically notification "heights", "times" of creation, and .
function notify(notice, button, height, imageProperties, image) {
var notificationText,
noticeWidth,
@ -584,7 +588,7 @@ function setup() {
isChecked: Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING)
});
Menu.addSeparator(MENU_NAME, "Play sounds for:");
for (type in NotificationType.properties) {
for (var type in NotificationType.properties) {
checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + (parseInt(type) + 1));
checked = checked === '' ? true : checked;
Menu.addMenuItem({
@ -620,7 +624,7 @@ LODManager.LODDecreased.connect(function() {
var warningText = "\n"
+ "Due to the complexity of the content, the \n"
+ "level of detail has been decreased. "
+ "You can now see: \n"
+ "You can now see: \n"
+ LODManager.getLODFeedbackText();
if (lodTextID == false) {
@ -641,3 +645,5 @@ Window.domainConnectionRefused.connect(onDomainConnectionRefused);
Window.snapshotTaken.connect(onSnapshotTaken);
setup();
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// progress.js
// examples
@ -11,7 +13,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
(function() { // BEGIN LOCAL_SCOPE
function debug() {
return;
@ -243,7 +245,7 @@
}
function updateProgressBarLocation() {
viewport = Controller.getViewportDimensions();
var viewport = Controller.getViewportDimensions();
windowWidth = viewport.x;
windowHeight = viewport.y;
@ -283,4 +285,5 @@
GlobalServices.updateDownloadInfo();
Script.setInterval(update, 1000/60);
Script.scriptEnding.connect(tearDown);
}());
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// audioDeviceExample.js
// examples
@ -11,6 +13,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
if (typeof String.prototype.startsWith != 'function') {
String.prototype.startsWith = function (str){
return this.slice(0, str.length) == str;
@ -62,11 +66,11 @@ function setupAudioMenus() {
for(var i = 0; i < outputDevices.length; i++) {
var thisDeviceSelected = (outputDevices[i] == selectedOutputDevice);
var menuItem = "Use " + outputDevices[i] + " for Output";
Menu.addMenuItem({
menuName: "Audio > Devices",
menuItemName: menuItem,
isCheckable: true,
isChecked: thisDeviceSelected
Menu.addMenuItem({
menuName: "Audio > Devices",
menuItemName: menuItem,
isCheckable: true,
isChecked: thisDeviceSelected
});
if (thisDeviceSelected) {
selectedOutputMenu = menuItem;
@ -86,11 +90,11 @@ function setupAudioMenus() {
for(var i = 0; i < inputDevices.length; i++) {
var thisDeviceSelected = (inputDevices[i] == selectedInputDevice);
var menuItem = "Use " + inputDevices[i] + " for Input";
Menu.addMenuItem({
menuName: "Audio > Devices",
menuItemName: menuItem,
isCheckable: true,
isChecked: thisDeviceSelected
Menu.addMenuItem({
menuName: "Audio > Devices",
menuItemName: menuItem,
isCheckable: true,
isChecked: thisDeviceSelected
});
if (thisDeviceSelected) {
selectedInputMenu = menuItem;
@ -212,3 +216,5 @@ Script.scriptEnding.connect(function () {
Menu.menuItemEvent.disconnect(menuItemEvent);
Script.update.disconnect(checkHMDAudio);
});
}()); // END LOCAL_SCOPE

View file

@ -7,6 +7,9 @@
// 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 SNAPSHOT_DELAY = 500; // 500ms
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var resetOverlays;
@ -152,3 +155,5 @@ Script.scriptEnding.connect(function () {
button.clicked.disconnect(onClicked);
Window.snapshotShared.disconnect(snapshotShared);
});
}()); // END LOCAL_SCOPE

View file

@ -1,3 +1,5 @@
"use strict";
//
// users.js
// examples
@ -9,6 +11,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
var PopUpMenu = function (properties) {
var value = properties.value,
promptOverlay,
@ -1189,3 +1193,5 @@ var usersWindow = (function () {
setUp();
Script.scriptEnding.connect(tearDown);
}());
}()); // END LOCAL_SCOPE

View file

@ -2,7 +2,7 @@
# Declare dependencies
macro (SETUP_TESTCASE_DEPENDENCIES)
target_bullet()
link_hifi_libraries(shared physics)
link_hifi_libraries(shared physics gpu model)
package_libraries_for_deployment()
endmacro ()

View file

@ -0,0 +1,277 @@
//
// CollisionRenderMeshCacheTests.cpp
// tests/physics/src
//
// Created by Andrew Meadows on 2014.10.30
// 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 "CollisionRenderMeshCacheTests.h"
#include <iostream>
#include <cstdlib>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionShapes/btShapeHull.h>
#include <CollisionRenderMeshCache.h>
#include <ShapeInfo.h> // for MAX_HULL_POINTS
#include "MeshUtil.cpp"
QTEST_MAIN(CollisionRenderMeshCacheTests)
const float INV_SQRT_THREE = 0.577350269f;
const uint32_t numSphereDirections = 6 + 8;
btVector3 sphereDirections[] = {
btVector3(1.0f, 0.0f, 0.0f),
btVector3(-1.0f, 0.0f, 0.0f),
btVector3(0.0f, 1.0f, 0.0f),
btVector3(0.0f, -1.0f, 0.0f),
btVector3(0.0f, 0.0f, 1.0f),
btVector3(0.0f, 0.0f, -1.0f),
btVector3(INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE),
btVector3(INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE),
btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE),
btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE),
btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE),
btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE),
btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE),
btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE)
};
float randomFloat() {
return 2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f;
}
btBoxShape* createBoxShape(const btVector3& extent) {
btBoxShape* shape = new btBoxShape(0.5f * extent);
return shape;
}
btConvexHullShape* createConvexHull(float radius) {
btConvexHullShape* hull = new btConvexHullShape();
for (uint32_t i = 0; i < numSphereDirections; ++i) {
btVector3 point = radius * sphereDirections[i];
hull->addPoint(point, false);
}
hull->recalcLocalAabb();
return hull;
}
void CollisionRenderMeshCacheTests::testShapeHullManifold() {
// make a box shape
btVector3 extent(1.0f, 2.0f, 3.0f);
btBoxShape* box = createBoxShape(extent);
// wrap it with a ShapeHull
btShapeHull hull(box);
const float MARGIN = 0.0f;
hull.buildHull(MARGIN);
// verify the vertex count is capped
uint32_t numVertices = (uint32_t)hull.numVertices();
QVERIFY(numVertices <= MAX_HULL_POINTS);
// verify the mesh is inside the radius
btVector3 halfExtents = box->getHalfExtentsWithMargin();
float ACCEPTABLE_EXTENTS_ERROR = 0.01f;
float maxRadius = halfExtents.length() + ACCEPTABLE_EXTENTS_ERROR;
const btVector3* meshVertices = hull.getVertexPointer();
for (uint32_t i = 0; i < numVertices; ++i) {
btVector3 vertex = meshVertices[i];
QVERIFY(vertex.length() <= maxRadius);
}
// verify the index count is capped
uint32_t numIndices = (uint32_t)hull.numIndices();
QVERIFY(numIndices < 6 * MAX_HULL_POINTS);
// verify the index count is a multiple of 3
QVERIFY(numIndices % 3 == 0);
// verify the mesh is closed
const uint32_t* meshIndices = hull.getIndexPointer();
bool isClosed = MeshUtil::isClosedManifold(meshIndices, numIndices);
QVERIFY(isClosed);
// verify the triangle normals are outward using right-hand-rule
const uint32_t INDICES_PER_TRIANGLE = 3;
for (uint32_t i = 0; i < numIndices; i += INDICES_PER_TRIANGLE) {
btVector3 A = meshVertices[meshIndices[i]];
btVector3 B = meshVertices[meshIndices[i+1]];
btVector3 C = meshVertices[meshIndices[i+2]];
btVector3 face = (B - A).cross(C - B);
btVector3 center = (A + B + C) / 3.0f;
QVERIFY(face.dot(center) > 0.0f);
}
// delete unmanaged memory
delete box;
}
void CollisionRenderMeshCacheTests::testCompoundShape() {
uint32_t numSubShapes = 3;
btVector3 centers[] = {
btVector3(1.0f, 0.0f, 0.0f),
btVector3(0.0f, -2.0f, 0.0f),
btVector3(0.0f, 0.0f, 3.0f),
};
float radii[] = { 3.0f, 2.0f, 1.0f };
btCompoundShape* compoundShape = new btCompoundShape();
for (uint32_t i = 0; i < numSubShapes; ++i) {
btTransform transform;
transform.setOrigin(centers[i]);
btConvexHullShape* hull = createConvexHull(radii[i]);
compoundShape->addChildShape(transform, hull);
}
// create the cache
CollisionRenderMeshCache cache;
QVERIFY(cache.getNumMeshes() == 0);
// get the mesh once
model::MeshPointer mesh = cache.getMesh(compoundShape);
QVERIFY((bool)mesh);
QVERIFY(cache.getNumMeshes() == 1);
// get the mesh again
model::MeshPointer mesh2 = cache.getMesh(compoundShape);
QVERIFY(mesh2 == mesh);
QVERIFY(cache.getNumMeshes() == 1);
// forget the mesh once
cache.releaseMesh(compoundShape);
mesh.reset();
QVERIFY(cache.getNumMeshes() == 1);
// collect garbage (should still cache mesh)
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 1);
// forget the mesh a second time (should still cache mesh)
cache.releaseMesh(compoundShape);
mesh2.reset();
QVERIFY(cache.getNumMeshes() == 1);
// collect garbage (should no longer cache mesh)
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 0);
// delete unmanaged memory
for (int i = 0; i < compoundShape->getNumChildShapes(); ++i) {
delete compoundShape->getChildShape(i);
}
delete compoundShape;
}
void CollisionRenderMeshCacheTests::testMultipleShapes() {
// shapeA is compound of hulls
uint32_t numSubShapes = 3;
btVector3 centers[] = {
btVector3(1.0f, 0.0f, 0.0f),
btVector3(0.0f, -2.0f, 0.0f),
btVector3(0.0f, 0.0f, 3.0f),
};
float radii[] = { 3.0f, 2.0f, 1.0f };
btCompoundShape* shapeA = new btCompoundShape();
for (uint32_t i = 0; i < numSubShapes; ++i) {
btTransform transform;
transform.setOrigin(centers[i]);
btConvexHullShape* hull = createConvexHull(radii[i]);
shapeA->addChildShape(transform, hull);
}
// shapeB is compound of boxes
btVector3 extents[] = {
btVector3(1.0f, 2.0f, 3.0f),
btVector3(2.0f, 3.0f, 1.0f),
btVector3(3.0f, 1.0f, 2.0f),
};
btCompoundShape* shapeB = new btCompoundShape();
for (uint32_t i = 0; i < numSubShapes; ++i) {
btTransform transform;
transform.setOrigin(centers[i]);
btBoxShape* box = createBoxShape(extents[i]);
shapeB->addChildShape(transform, box);
}
// shapeC is just a box
btVector3 extentC(7.0f, 3.0f, 5.0f);
btBoxShape* shapeC = createBoxShape(extentC);
// create the cache
CollisionRenderMeshCache cache;
QVERIFY(cache.getNumMeshes() == 0);
// get the meshes
model::MeshPointer meshA = cache.getMesh(shapeA);
model::MeshPointer meshB = cache.getMesh(shapeB);
model::MeshPointer meshC = cache.getMesh(shapeC);
QVERIFY((bool)meshA);
QVERIFY((bool)meshB);
QVERIFY((bool)meshC);
QVERIFY(cache.getNumMeshes() == 3);
// get the meshes again
model::MeshPointer meshA2 = cache.getMesh(shapeA);
model::MeshPointer meshB2 = cache.getMesh(shapeB);
model::MeshPointer meshC2 = cache.getMesh(shapeC);
QVERIFY(meshA == meshA2);
QVERIFY(meshB == meshB2);
QVERIFY(meshC == meshC2);
QVERIFY(cache.getNumMeshes() == 3);
// forget the meshes once
cache.releaseMesh(shapeA);
cache.releaseMesh(shapeB);
cache.releaseMesh(shapeC);
meshA2.reset();
meshB2.reset();
meshC2.reset();
QVERIFY(cache.getNumMeshes() == 3);
// collect garbage (should still cache mesh)
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 3);
// forget again, one mesh at a time...
// shapeA...
cache.releaseMesh(shapeA);
meshA.reset();
QVERIFY(cache.getNumMeshes() == 3);
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 2);
// shapeB...
cache.releaseMesh(shapeB);
meshB.reset();
QVERIFY(cache.getNumMeshes() == 2);
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 1);
// shapeC...
cache.releaseMesh(shapeC);
meshC.reset();
QVERIFY(cache.getNumMeshes() == 1);
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 0);
// delete unmanaged memory
for (int i = 0; i < shapeA->getNumChildShapes(); ++i) {
delete shapeA->getChildShape(i);
}
delete shapeA;
for (int i = 0; i < shapeB->getNumChildShapes(); ++i) {
delete shapeB->getChildShape(i);
}
delete shapeB;
delete shapeC;
}

View file

@ -0,0 +1,26 @@
//
// CollisionRenderMeshCacheTests.h
// tests/physics/src
//
// Created by Andrew Meadows on 2014.10.30
// 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_CollisionRenderMeshCacheTests_h
#define hifi_CollisionRenderMeshCacheTests_h
#include <QtTest/QtTest>
class CollisionRenderMeshCacheTests : public QObject {
Q_OBJECT
private slots:
void testShapeHullManifold();
void testCompoundShape();
void testMultipleShapes();
};
#endif // hifi_CollisionRenderMeshCacheTests_h

View file

@ -0,0 +1,45 @@
//
// MeshUtil.cpp
// tests/physics/src
//
// Created by Andrew Meadows 2016.07.14
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MeshUtil.h"
#include<unordered_map>
// returns false if any edge has only one adjacent triangle
bool MeshUtil::isClosedManifold(const uint32_t* meshIndices, uint32_t numIndices) {
using EdgeList = std::unordered_map<MeshUtil::TriangleEdge, uint32_t>;
EdgeList edges;
// count the triangles for each edge
const uint32_t TRIANGLE_STRIDE = 3;
for (uint32_t i = 0; i < numIndices; i += TRIANGLE_STRIDE) {
MeshUtil::TriangleEdge edge;
// the triangles indices are stored in sequential order
for (uint32_t j = 0; j < 3; ++j) {
edge.setIndices(meshIndices[i + j], meshIndices[i + ((j + 1) % 3)]);
EdgeList::iterator edgeEntry = edges.find(edge);
if (edgeEntry == edges.end()) {
edges.insert(std::pair<MeshUtil::TriangleEdge, uint32_t>(edge, 1));
} else {
edgeEntry->second += 1;
}
}
}
// scan for outside edge
for (auto& edgeEntry : edges) {
if (edgeEntry.second == 1) {
return false;
}
}
return true;
}

View file

@ -0,0 +1,61 @@
//
// MeshUtil.h
// tests/physics/src
//
// Created by Andrew Meadows 2016.07.14
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_MeshUtil_h
#define hifi_MeshUtil_h
#include <functional>
namespace MeshUtil {
class TriangleEdge {
public:
TriangleEdge() {}
TriangleEdge(uint32_t A, uint32_t B) {
setIndices(A, B);
}
void setIndices(uint32_t A, uint32_t B) {
if (A < B) {
_indexA = A;
_indexB = B;
} else {
_indexA = B;
_indexB = A;
}
}
bool operator==(const TriangleEdge& other) const {
return _indexA == other._indexA && _indexB == other._indexB;
}
uint32_t getIndexA() const { return _indexA; }
uint32_t getIndexB() const { return _indexB; }
private:
uint32_t _indexA { (uint32_t)(-1) };
uint32_t _indexB { (uint32_t)(-1) };
};
bool isClosedManifold(const uint32_t* meshIndices, uint32_t numIndices);
} // MeshUtil namespace
namespace std {
template <>
struct hash<MeshUtil::TriangleEdge> {
std::size_t operator()(const MeshUtil::TriangleEdge& edge) const {
// use Cantor's pairing function to generate a hash of ZxZ --> Z
uint32_t ab = edge.getIndexA() + edge.getIndexB();
return hash<uint32_t>()((ab * (ab + 1)) / 2 + edge.getIndexB());
}
};
}
#endif // hifi_MeshUtil_h

View file

@ -12,6 +12,7 @@
#include <iostream>
#include <ShapeManager.h>
#include <StreamUtils.h>
#include <Extents.h>
#include "ShapeManagerTests.h"
@ -197,6 +198,7 @@ void ShapeManagerTests::addCompoundShape() {
ShapeInfo::PointCollection pointCollection;
int numHulls = 5;
glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f);
Extents extents;
for (int i = 0; i < numHulls; ++i) {
glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal;
ShapeInfo::PointList pointList;
@ -204,13 +206,16 @@ void ShapeManagerTests::addCompoundShape() {
for (int j = 0; j < numHullPoints; ++j) {
glm::vec3 point = radius * tetrahedron[j] + offset;
pointList.push_back(point);
extents.addPoint(point);
}
pointCollection.push_back(pointList);
}
// create the ShapeInfo
ShapeInfo info;
info.setPointCollection(hulls);
glm::vec3 halfExtents = 0.5f * (extents.maximum - extents.minimum);
info.setParams(SHAPE_TYPE_COMPOUND, halfExtents);
info.setPointCollection(pointCollection);
// create the shape
ShapeManager shapeManager;