This commit is contained in:
howard-stearns 2016-08-29 09:44:06 -07:00
commit 3aa387ddb9
142 changed files with 6508 additions and 1946 deletions

View file

@ -18,6 +18,7 @@
* [oglplus](http://oglplus.org/) ~> 0.63
* [OpenVR](https://github.com/ValveSoftware/openvr) ~> 0.91 (Win32 only)
* [Polyvox](http://www.volumesoffun.com/) ~> 0.2.1
* [QuaZip](http://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1
* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3
* [soxr](http://soxr.sourceforge.net) ~> 0.1.1
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3

View file

@ -204,6 +204,7 @@ set_property(DIRECTORY PROPERTY EP_PREFIX ${EXTERNAL_PROJECT_PREFIX})
setup_externals_binary_dir()
option(USE_NSIGHT "Attempt to find the nSight libraries" 1)
option(GET_QUAZIP "Get QuaZip library automatically as external project" 1)
if (WIN32)

View file

@ -392,7 +392,6 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) {
const int16_t* nextSoundOutput = NULL;
if (_avatarSound) {
const QByteArray& soundByteArray = _avatarSound->getByteArray();
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
+ _numAvatarSoundSentBytes);
@ -442,6 +441,10 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) {
audioPacket->writePrimitive(headOrientation);
} else if (nextSoundOutput) {
// write the codec
QString codecName;
audioPacket->writeString(codecName);
// assume scripted avatar audio is mono and set channel flag to zero
audioPacket->writePrimitive((quint8)0);

55
cmake/externals/quazip/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,55 @@
set(EXTERNAL_NAME quazip)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
cmake_policy(SET CMP0046 OLD)
include(ExternalProject)
if (WIN32)
# windows shell does not like backslashes expanded on the command line,
# so convert all backslashes in the QT path to forward slashes
string(REPLACE \\ / QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
elseif ($ENV{QT_CMAKE_PREFIX_PATH})
set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
endif ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://s3-us-west-1.amazonaws.com/hifi-production/dependencies/quazip-0.7.2.zip
URL_MD5 2955176048a31262c09259ca8d309d19
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=<INSTALL_DIR>/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
)
add_dependencies(quazip zlib)
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES
FOLDER "hidden/externals"
INSTALL_NAME_DIR ${INSTALL_DIR}/lib
BUILD_WITH_INSTALL_RPATH True)
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of QuaZip include directories")
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIR} CACHE PATH "List of QuaZip include directories")
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/lib CACHE FILEPATH "Location of QuaZip DLL")
if (APPLE)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library")
elseif (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library")
else ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.so CACHE FILEPATH "Location of QuaZip release library")
endif ()
include(SelectLibraryConfigurations)
select_library_configurations(${EXTERNAL_NAME_UPPER})
# Force selected libraries into the cache
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE FILEPATH "Location of QuaZip libraries")
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of QuaZip libraries")

View file

@ -0,0 +1,16 @@
#
# Copyright 2015 High Fidelity, Inc.
# Created by Leonardo Murillo on 2015/11/20
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_QUAZIP)
add_dependency_external_projects(quazip)
find_package(QuaZip REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${QUAZIP_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES})
if (WIN32)
add_paths_to_fixup_libs(${QUAZIP_DLL_PATH})
endif ()
endmacro()

View file

@ -0,0 +1,29 @@
#
# FindQuaZip.h
# StackManagerQt/cmake/modules
#
# Created by Mohammed Nafees.
# Copyright (c) 2014 High Fidelity. All rights reserved.
#
# QUAZIP_FOUND - QuaZip library was found
# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir
# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR)
# QUAZIP_LIBRARIES - List of QuaZip libraries
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("quazip")
if (WIN32)
find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS})
elseif (APPLE)
find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS})
else ()
find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES quazip HINTS ${QUAZIP_SEARCH_DIRS})
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_INCLUDE_DIRS)
mark_as_advanced(QUAZIP_INCLUDE_DIRS QUAZIP_SEARCH_DIRS)

View file

@ -218,16 +218,22 @@ ScrollingWindow {
onIconChanged: {
console.log("New icon: " + icon)
}
onNewViewRequested:{
onNewViewRequested: {
var component = Qt.createComponent("Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView)
}
}
Component.onCompleted: {
desktop.initWebviewProfileHandlers(webview.profile)
}
//profile: desktop.browserProfile
}
} // item
Keys.onPressed: {
switch(event.key) {
case Qt.Key_L:

View file

@ -0,0 +1,103 @@
//
// MarketplaceComboBox.qml
//
// Created by Elisa Lupin-Jimenez on 3 Aug 2016
// 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
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebChannel 1.0
import QtWebEngine 1.1
import QtWebSockets 1.0
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
import "controls"
import "controls-uit" as Controls
import "styles"
import "styles-uit"
Rectangle {
HifiConstants { id: hifi }
id: marketplaceComboBox
anchors.fill: parent
color: hifi.colors.baseGrayShadow
property var currentUrl: "https://metaverse.highfidelity.com/marketplace"
Controls.WebView {
id: webview
url: currentUrl
anchors.top: switchMarketView.bottom
width: parent.width
height: parent.height - 40
focus: true
Timer {
id: zipTimer
running: false
repeat: false
interval: 1500
property var handler;
onTriggered: handler();
}
property var autoCancel: 'var element = $("a.btn.cancel");
element.click();'
onNewViewRequested: {
var component = Qt.createComponent("Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView);
if (File.isZippedFbx(desktop.currentUrl)) {
zipTimer.handler = function() {
newWindow.destroy();
runJavaScript(autoCancel);
}
zipTimer.start();
}
}
property var simpleDownload: 'var element = $("a.download-file");
element.removeClass("download-file");
element.removeAttr("download");'
onLinkHovered: {
desktop.currentUrl = hoveredUrl;
// add an error message for non-fbx files
if (File.isZippedFbx(desktop.currentUrl)) {
runJavaScript(simpleDownload, function(){console.log("ran the JS");});
}
}
}
Controls.ComboBox {
id: switchMarketView
anchors.top: parent.top
anchors.right: parent.right
colorScheme: hifi.colorSchemes.dark
width: 200
height: 40
visible: true
model: ["Marketplace", "Clara.io"]
onCurrentIndexChanged: {
if (currentIndex === 0) { webview.url = "https://metaverse.highfidelity.com/marketplace"; }
if (currentIndex === 1) { webview.url = "https://clara.io/library"; }
}
}
Controls.Label {
id: switchMarketLabel
anchors.verticalCenter: switchMarketView.verticalCenter
anchors.right: switchMarketView.left
color: hifi.colors.white
text: "Explore interesting content from: "
}
}

View file

@ -1,13 +1,16 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls 1.4
import QtWebChannel 1.0
import QtWebEngine 1.1
import QtWebSockets 1.0
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
import "windows" as Windows
import "controls"
import "controls-uit" as Controls
import "styles"
import "styles-uit"
Windows.Window {
id: root
@ -23,6 +26,8 @@ Windows.Window {
property var eventBridge;
property var component;
property var dynamicContent;
onSourceChanged: {
if (dynamicContent) {
dynamicContent.destroy();

View file

@ -18,6 +18,7 @@ import "." as VrControls
FocusScope {
id: root
HifiConstants { id: hifi }
property alias model: comboBox.model;
property alias comboBox: comboBox

View file

@ -13,6 +13,7 @@ import QtQuick 2.5
import "../styles-uit"
RalewaySemiBold {
HifiConstants { id: hifi }
property int colorScheme: hifi.colorSchemes.light
size: hifi.fontSizes.inputLabel

View file

@ -25,6 +25,8 @@ WebEngineView {
});
}
// FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6
Timer {
id: urlReplacementTimer
@ -59,11 +61,6 @@ WebEngineView {
}
}
onNewViewRequested:{
var component = Qt.createComponent("../Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView)
}
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6
// See https://bugreports.qt.io/browse/QTBUG-49521

View file

@ -55,9 +55,11 @@ WebEngineView {
}
onNewViewRequested:{
if (desktop) {
var component = Qt.createComponent("../Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView)
request.openIn(newWindow.webView);
}
}
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6

View file

@ -20,6 +20,8 @@ OriginalDesktop.Desktop {
onEntered: ApplicationCompositor.reticleOverDesktop = true
onExited: ApplicationCompositor.reticleOverDesktop = false
acceptedButtons: Qt.NoButton
}
// The tool window, one instance
@ -71,6 +73,39 @@ OriginalDesktop.Desktop {
});
}
// Accept a download through the webview
property bool webViewProfileSetup: false
property string currentUrl: ""
property string adaptedPath: ""
property string tempDir: ""
function initWebviewProfileHandlers(profile) {
console.log("The webview url in desktop is: " + currentUrl);
if (webViewProfileSetup) return;
webViewProfileSetup = true;
profile.downloadRequested.connect(function(download){
console.log("Download start: " + download.state);
adaptedPath = File.convertUrlToPath(currentUrl);
tempDir = File.getTempDir();
console.log("Temp dir created: " + tempDir);
download.path = tempDir + "/" + adaptedPath;
console.log("Path where object should download: " + download.path);
download.accept();
if (download.state === WebEngineDownloadItem.DownloadInterrupted) {
console.log("download failed to complete");
}
})
profile.downloadFinished.connect(function(download){
if (download.state === WebEngineDownloadItem.DownloadCompleted) {
File.runUnzip(download.path, currentUrl);
} else {
console.log("The download was corrupted, state: " + download.state);
}
})
}
// Create or fetch a toolbar with the given name
function getToolbar(name) {
var result = toolbars[name];

View file

@ -57,6 +57,7 @@
#include <display-plugins/DisplayPlugin.h>
#include <EntityScriptingInterface.h>
#include <ErrorDialog.h>
#include <FileScriptingInterface.h>
#include <Finally.h>
#include <FramebufferCache.h>
#include <gpu/Batch.h>
@ -1219,7 +1220,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_defaultSkyboxAmbientTexture = textureCache->getImageTexture(skyboxAmbientUrl, NetworkTexture::CUBE_TEXTURE, { { "generateIrradiance", true } });
_defaultSkybox->setCubemap(_defaultSkyboxTexture);
_defaultSkybox->setColor({ 1.0, 1.0, 1.0 });
EntityItem::setEntitiesShouldFadeFunction([this]() {
SharedNodePointer entityServerNode = DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::EntityServer);
@ -1578,6 +1578,9 @@ void Application::initializeUi() {
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
rootContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
FileScriptingInterface* fileDownload = new FileScriptingInterface(engine);
rootContext->setContextProperty("File", fileDownload);
connect(fileDownload, &FileScriptingInterface::unzipSuccess, this, &Application::showAssetServerWidget);
rootContext->setContextProperty("MyAvatar", getMyAvatar());
rootContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data());
rootContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
@ -2024,7 +2027,6 @@ bool Application::importJSONFromURL(const QString& urlString) {
}
bool Application::importSVOFromURL(const QString& urlString) {
emit svoImportRequested(urlString);
return true;
}
@ -2149,13 +2151,15 @@ bool Application::event(QEvent* event) {
// handle custom URL
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
QUrl url = fileEvent->url();
if (!url.isEmpty()) {
QString urlString = url.toString();
if (canAcceptURL(urlString)) {
return acceptURL(urlString);
}
}
@ -4322,34 +4326,42 @@ namespace render {
sceneKeyLight->setIntensity(DEFAULT_SKYBOX_INTENSITY);
sceneKeyLight->setAmbientIntensity(DEFAULT_SKYBOX_AMBIENT_INTENSITY);
sceneKeyLight->setDirection(DEFAULT_SKYBOX_DIRECTION);
// fall through: render a skybox, if available
// fall through: render a skybox (if available), or the defaults (if requested)
}
case model::SunSkyStage::SKY_BOX: {
auto skybox = skyStage->getSkybox();
if (skybox) {
if (!skybox->empty()) {
PerformanceTimer perfTimer("skybox");
skybox->render(batch, args->getViewFrustum());
break;
}
// fall through: render defaults, if available
// fall through: render defaults (if requested)
}
case model::SunSkyStage::SKY_DEFAULT_AMBIENT_TEXTURE: {
if (Menu::getInstance()->isOptionChecked(MenuOption::DefaultSkybox)) {
auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage();
auto sceneKeyLight = scene->getKeyLight();
auto defaultSkyboxAmbientTexture = qApp->getDefaultSkyboxAmbientTexture();
// do not set the ambient sphere - it peaks too high, and causes flashing when turning
// set the ambient sphere uniformly - the defaultSkyboxAmbientTexture has peaks that cause flashing when turning
sceneKeyLight->setAmbientSphere(DependencyManager::get<TextureCache>()->getWhiteTexture()->getIrradiance());
sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture);
// fall through: render defaults skybox
} else {
break;
}
// fall through: render defaults, if available
}
case model::SunSkyStage::SKY_DEFAULT_TEXTURE:
if (Menu::getInstance()->isOptionChecked(MenuOption::DefaultSkybox)) {
qApp->getDefaultSkybox()->render(batch, args->getViewFrustum());
}
break;
// Any other cases require no extra rendering
case model::SunSkyStage::NO_BACKGROUND:
default:
// this line intentionally left blank
break;
}
}
@ -5021,7 +5033,6 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
}
bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
@ -5119,11 +5130,11 @@ void Application::toggleRunningScriptsWidget() const {
//}
}
void Application::showAssetServerWidget(QString filePath) {
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
return;
}
static const QUrl url { "AssetServer.qml" };
auto startUpload = [=](QQmlContext* context, QObject* newObject){

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

@ -59,6 +59,8 @@ const float DISPLAYNAME_ALPHA = 1.0f;
const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f;
const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534;
namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
return ItemKey::Builder::opaqueShape();
@ -851,15 +853,33 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
}
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
glm::quat rotation;
_skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation);
return Quaternions::Y_180 * rotation;
if (index == SENSOR_TO_WORLD_MATRIX_INDEX) {
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
bool success;
Transform avatarTransform;
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
return glmExtractRotation(invAvatarMat * sensorToWorldMatrix);
} else {
glm::quat rotation;
_skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation);
return Quaternions::Y_180 * rotation;
}
}
glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
glm::vec3 translation;
_skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation);
return Quaternions::Y_180 * translation;
if (index == SENSOR_TO_WORLD_MATRIX_INDEX) {
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
bool success;
Transform avatarTransform;
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
return extractTranslation(invAvatarMat * sensorToWorldMatrix);
} else {
glm::vec3 translation;
_skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation);
return Quaternions::Y_180 * translation;
}
}
int Avatar::getJointIndex(const QString& name) const {

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

@ -107,7 +107,6 @@ MyAvatar::MyAvatar(RigPointer rig) :
_hmdSensorOrientation(),
_hmdSensorPosition(),
_bodySensorMatrix(),
_sensorToWorldMatrix(),
_goToPending(false),
_goToPosition(),
_goToOrientation(),
@ -511,13 +510,9 @@ void MyAvatar::simulate(float deltaTime) {
updateAvatarEntities();
}
// thread-safe
glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
return _sensorToWorldMatrixCache.get();
}
// As far as I know no HMD system supports a play area of a kilometer in radius.
// As far as I know no HMD system supports a play area of a kilometer in radius.
static const float MAX_HMD_ORIGIN_DISTANCE = 1000.0f;
// Pass a recent sample of the HMD to the avatar.
// This can also update the avatar's position to follow the HMD
// as it moves through the world.
@ -526,7 +521,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
_hmdSensorMatrix = hmdSensorMatrix;
auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix);
if (newHmdSensorPosition != _hmdSensorPosition &&
if (newHmdSensorPosition != _hmdSensorPosition &&
glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) {
qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition;
// Ignore unreasonable HMD sensor data

View file

@ -79,8 +79,6 @@ class MyAvatar : public Avatar {
Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose)
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
@ -110,9 +108,6 @@ public:
const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; }
// thread safe
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
Q_INVOKABLE QVariant getOrientationVar() const;
@ -415,6 +410,10 @@ private:
bool _useSnapTurn { true };
bool _clearOverlayWhenMoving { true };
// working copy of sensorToWorldMatrix.
// See AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
glm::mat4 _sensorToWorldMatrix;
// cache of the current HMD sensor position and orientation
// in sensor space.
glm::mat4 _hmdSensorMatrix;
@ -427,10 +426,6 @@ private:
// in sensor space.
glm::mat4 _bodySensorMatrix;
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
glm::mat4 _sensorToWorldMatrix;
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
struct FollowHelper {
FollowHelper();

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

@ -53,15 +53,18 @@ namespace AvatarDataPacket {
// NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure.
PACKED_BEGIN struct Header {
float position[3]; // skeletal model's position
float globalPosition[3]; // avatar's position
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
float lookAtPosition[3]; // world space position that eyes are focusing on.
float audioLoudness; // current loundess of microphone
float position[3]; // skeletal model's position
float globalPosition[3]; // avatar's position
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
float lookAtPosition[3]; // world space position that eyes are focusing on.
float audioLoudness; // current loundess of microphone
uint8_t sensorToWorldQuat[6]; // 6 byte compressed quaternion part of sensor to world matrix
uint16_t sensorToWorldScale; // uniform scale of sensor to world matrix
float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
uint8_t flags;
} PACKED_END;
const size_t HEADER_SIZE = 49;
const size_t HEADER_SIZE = 69;
// only present if HAS_REFERENTIAL flag is set in header.flags
PACKED_BEGIN struct ParentInfo {
@ -93,6 +96,9 @@ namespace AvatarDataPacket {
*/
}
static const int TRANSLATION_COMPRESSION_RADIX = 12;
static const int SENSOR_TO_WORLD_SCALE_RADIX = 10;
#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0)
AvatarData::AvatarData() :
@ -210,6 +216,14 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
header->lookAtPosition[2] = _headData->_lookAtPosition.z;
header->audioLoudness = _headData->_audioLoudness;
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
packOrientationQuatToSixBytes(header->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix));
glm::vec3 scale = extractScale(sensorToWorldMatrix);
packFloatScalarToSignedTwoByteFixed((uint8_t*)&header->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX);
header->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0];
header->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1];
header->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2];
setSemiNibbleAt(header->flags, KEY_STATE_START_BIT, _keyState);
// hand state
bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG;
@ -346,8 +360,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
*destinationBuffer++ = validity;
}
const int TRANSLATION_COMPRESSION_RADIX = 12;
validityBit = 0;
validity = *validityPosition++;
for (int i = 0; i < _jointData.size(); i ++) {
@ -500,6 +512,15 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
}
_headData->_audioLoudness = audioLoudness;
glm::quat sensorToWorldQuat;
unpackOrientationQuatFromSixBytes(header->sensorToWorldQuat, sensorToWorldQuat);
float sensorToWorldScale;
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&header->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
glm::vec3 sensorToWorldTrans(header->sensorToWorldTrans[0], header->sensorToWorldTrans[1], header->sensorToWorldTrans[2]);
glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
_sensorToWorldMatrixCache.set(sensorToWorldMatrix);
{ // bitFlags and face data
uint8_t bitItems = header->flags;
@ -616,7 +637,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
// each joint translation component is stored in 6 bytes.
const int COMPRESSED_TRANSLATION_SIZE = 6;
PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE);
const int TRANSLATION_COMPRESSION_RADIX = 12;
for (int i = 0; i < numJoints; i++) {
JointData& data = _jointData[i];
@ -1718,6 +1738,11 @@ AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
return result;
}
// thread-safe
glm::mat4 AvatarData::getSensorToWorldMatrix() const {
return _sensorToWorldMatrixCache.get();
}
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) {
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);

View file

@ -54,6 +54,7 @@ typedef unsigned long long quint64;
#include <SpatiallyNestable.h>
#include <NumericalConstants.h>
#include <Packed.h>
#include <ThreadSafeValueCache.h>
#include "AABox.h"
#include "HeadData.h"
@ -171,6 +172,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
public:
static const QString FRAME_NAME;
@ -351,6 +354,9 @@ public:
void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
// thread safe
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
public slots:
void sendAvatarDataPacket();
void sendIdentityPacket();
@ -425,6 +431,9 @@ protected:
bool _avatarEntityDataLocallyEdited { false };
bool _avatarEntityDataChanged { false };
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
private:
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
static QUrl _defaultFullAvatarModelUrl;

View file

@ -113,17 +113,19 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() {
void EntityTreeRenderer::clear() {
leaveAllEntities();
// unload and stop the engine
if (_entitiesScriptEngine) {
// Unload and stop the engine here (instead of in its deleter) to
// avoid marshalling unload signals back to this thread
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop();
}
// reset the engine
if (_wantScripts && !_shuttingDown) {
resetEntitiesScriptEngine();
}
// remove all entities from the scene
auto scene = _viewState->getMain3DScene();
render::PendingChanges pendingChanges;
foreach(auto entity, _entitiesInScene) {
@ -132,6 +134,10 @@ void EntityTreeRenderer::clear() {
scene->enqueuePendingChanges(pendingChanges);
_entitiesInScene.clear();
// reset the zone to the default (while we load the next scene)
_bestZone = nullptr;
applyZonePropertiesToScene(_bestZone);
OctreeRenderer::clear();
}
@ -499,35 +505,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 +525,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 +541,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

@ -11,6 +11,7 @@
#include <QMouseEvent>
#include <QQuickItem>
#include <QQuickWindow>
#include <QQmlContext>
#include <QOpenGLContext>
#include <glm/gtx/quaternion.hpp>
@ -26,7 +27,6 @@
#include "EntityTreeRenderer.h"
const float DPI = 30.47f;
const float METERS_TO_INCHES = 39.3701f;
static uint32_t _currentWebCount { 0 };
// Don't allow more than 100 concurrent web views
@ -34,6 +34,9 @@ static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 100;
// If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer
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) };
entity->setProperties(properties);
@ -67,6 +70,7 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
_webSurface->load("WebView.qml");
_webSurface->resume();
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
_texture = textureId;
});
@ -87,7 +91,7 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
QTouchEvent::TouchPoint point;
point.setId(event.getID());
point.setState(Qt::TouchPointReleased);
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * DPI);
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
QPointF windowPoint(windowPos.x, windowPos.y);
point.setPos(windowPoint);
QList<QTouchEvent::TouchPoint> touchPoints;
@ -99,6 +103,19 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
return true;
}
glm::vec2 RenderableWebEntityItem::getWindowSize() const {
glm::vec2 dims = glm::vec2(getDimensions());
dims *= METERS_TO_INCHES * _dpi;
// ensure no side is never larger then MAX_WINDOW_SIZE
float max = (dims.x > dims.y) ? dims.x : dims.y;
if (max > MAX_WINDOW_SIZE) {
dims *= MAX_WINDOW_SIZE / max;
}
return dims;
}
void RenderableWebEntityItem::render(RenderArgs* args) {
checkFading();
@ -124,12 +141,13 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
}
_lastRenderTime = usecTimestampNow();
glm::vec2 dims = glm::vec2(getDimensions());
dims *= METERS_TO_INCHES * DPI;
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(dims.x, dims.y));
_webSurface->resize(QSize(windowSize.x, windowSize.y));
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
Q_ASSERT(getType() == EntityTypes::Web);
@ -147,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));
}
@ -185,7 +208,7 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) {
return;
}
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * DPI);
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
QPointF windowPoint(windowPos.x, windowPos.y);
if (event.getType() == PointerEvent::Move) {
@ -274,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,9 +44,12 @@ public:
SIMPLE_RENDERABLE();
virtual bool isTransparent() override;
private:
bool buildWebSurface(EntityTreeRenderer* renderer);
void destroyWebSurface();
glm::vec2 getWindowSize() const;
OffscreenQmlSurface* _webSurface{ nullptr };
QMetaObject::Connection _connection;

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

@ -335,6 +335,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID);
CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape);
CHECK_PROPERTY_CHANGE(PROP_DPI, dpi);
changedProperties += _animation.getChangedProperties();
changedProperties += _keyLight.getChangedProperties();
@ -504,6 +505,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
// Web only
if (_type == EntityTypes::Web) {
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi);
}
// PolyVoxel only
@ -726,6 +728,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly);
COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID);
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
_lastEdited = usecTimestampNow();
}
@ -903,6 +907,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool);
ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool);
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
// FIXME - these are not yet handled
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
@ -1065,6 +1071,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
if (properties.getType() == EntityTypes::Web) {
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl());
APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI());
}
if (properties.getType() == EntityTypes::Text) {
@ -1364,6 +1371,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
if (properties.getType() == EntityTypes::Web) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI);
}
if (properties.getType() == EntityTypes::Text) {
@ -1642,6 +1650,8 @@ void EntityItemProperties::markAllChanged() {
_clientOnlyChanged = true;
_owningAvatarIDChanged = true;
_dpiChanged = true;
}
// The minimum bounding box for the entity.
@ -1977,6 +1987,10 @@ QList<QString> EntityItemProperties::listChangedProperties() {
out += "ghostingAllowed";
}
if (dpiChanged()) {
out += "dpi";
}
if (shapeChanged()) {
out += "shape";
}

View file

@ -215,6 +215,8 @@ public:
DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false);
DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID);
DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI);
static QString getBackgroundModeString(BackgroundMode mode);

View file

@ -73,4 +73,6 @@ const bool ENTITY_ITEM_DEFAULT_BILLBOARDED = false;
const QString ENTITY_ITEM_DEFAULT_NAME = QString("");
const uint16_t ENTITY_ITEM_DEFAULT_DPI = 30;
#endif // hifi_EntityItemPropertiesDefaults_h

View file

@ -176,6 +176,7 @@ enum EntityPropertyList {
PROP_OWNING_AVATAR_ID, // doesn't go over wire
PROP_SHAPE,
PROP_DPI,
PROP_LOCAL_VELOCITY, // only used to convert values to and from scripts
PROP_LOCAL_ANGULAR_VELOCITY, // only used to convert values to and from scripts

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

@ -11,6 +11,7 @@
#include <glm/gtx/transform.hpp>
#include <QDebug>
#include <QJsonDocument>
#include <ByteCountCoding.h>
#include <GeometryUtil.h>
@ -30,6 +31,7 @@ EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const Ent
WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
_type = EntityTypes::Web;
_dpi = ENTITY_ITEM_DEFAULT_DPI;
}
const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f;
@ -42,6 +44,7 @@ void WebEntityItem::setDimensions(const glm::vec3& value) {
EntityItemProperties WebEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI);
return properties;
}
@ -50,6 +53,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI);
if (somethingChanged) {
bool wantDebug = false;
@ -74,6 +78,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI);
return bytesRead;
}
@ -83,6 +88,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_SOURCE_URL;
requestedProperties += PROP_DPI;
return requestedProperties;
}
@ -96,6 +102,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl);
APPEND_ENTITY_PROPERTY(PROP_DPI, _dpi);
}
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
@ -123,3 +130,11 @@ void WebEntityItem::setSourceUrl(const QString& value) {
}
const QString& WebEntityItem::getSourceUrl() const { return _sourceUrl; }
void WebEntityItem::setDPI(uint16_t value) {
_dpi = value;
}
uint16_t WebEntityItem::getDPI() const {
return _dpi;
}

View file

@ -54,8 +54,14 @@ public:
virtual void setSourceUrl(const QString& value);
const QString& getSourceUrl() const;
virtual bool wantsHandControllerPointerEvents() const override { return true; }
void setDPI(uint16_t value);
uint16_t getDPI() const;
protected:
QString _sourceUrl;
uint16_t _dpi;
};
#endif // hifi_WebEntityItem_h

View file

@ -166,7 +166,7 @@ private:
QMyQuickRenderControl* _renderControl{ nullptr };
FramebufferPtr _fbo;
RenderbufferPtr _depthStencil;
TextureRecycler _textures;
TextureRecycler _textures { true };
GLTextureEscrow _escrow;
uint64_t _lastRenderTime{ 0 };
@ -399,6 +399,8 @@ void OffscreenQmlRenderThread::render() {
glGetError();
}
Context::Bound(oglplus::Texture::Target::_2D, *texture).GenerateMipmap();
// FIXME probably unecessary
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
_quickWindow->resetOpenGLState();

View file

@ -509,16 +509,28 @@ TexturePtr TextureRecycler::getNextTexture() {
using namespace oglplus;
if (_readyTextures.empty()) {
TexturePtr newTexture(new Texture());
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
.MinFilter(TextureMinFilter::Linear)
.MagFilter(TextureMagFilter::Linear)
.WrapS(TextureWrap::ClampToEdge)
.WrapT(TextureWrap::ClampToEdge)
.Image2D(
0, PixelDataInternalFormat::RGBA8,
_size.x, _size.y,
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr
);
if (_useMipmaps) {
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
.MinFilter(TextureMinFilter::LinearMipmapLinear)
.MagFilter(TextureMagFilter::Linear)
.WrapS(TextureWrap::ClampToEdge)
.WrapT(TextureWrap::ClampToEdge)
.Anisotropy(8.0f)
.LODBias(-0.2f)
.Image2D(0, PixelDataInternalFormat::RGBA8,
_size.x, _size.y,
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr);
} else {
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
.MinFilter(TextureMinFilter::Linear)
.MagFilter(TextureMagFilter::Linear)
.WrapS(TextureWrap::ClampToEdge)
.WrapT(TextureWrap::ClampToEdge)
.Image2D(0, PixelDataInternalFormat::RGBA8,
_size.x, _size.y,
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr);
}
GLuint texId = GetName(*newTexture);
_allTextures[texId] = TexInfo{ newTexture, _size };
_readyTextures.push(newTexture);

View file

@ -190,6 +190,7 @@ using BasicFramebufferWrapperPtr = std::shared_ptr<BasicFramebufferWrapper>;
class TextureRecycler {
public:
TextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {}
void setSize(const uvec2& size);
void clear();
TexturePtr getNextTexture();
@ -212,4 +213,5 @@ private:
Map _allTextures;
Queue _readyTextures;
uvec2 _size{ 1920, 1080 };
bool _useMipmaps;
};

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

@ -676,6 +676,10 @@ void sphericalHarmonicsEvaluateDirection(float * result, int order, const glm::
}
bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<glm::vec3> & output, const uint order) {
int width = cubeTexture.getWidth();
if(width != cubeTexture.getHeight()) {
return false;
}
const uint sqOrder = order*order;
// allocate memory for calculations
@ -684,8 +688,6 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
std::vector<float> resultG(sqOrder);
std::vector<float> resultB(sqOrder);
int width, height;
// initialize values
float fWt = 0.0f;
for(uint i=0; i < sqOrder; i++) {
@ -696,11 +698,16 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
}
std::vector<float> shBuff(sqOrder);
std::vector<float> shBuffB(sqOrder);
// get width and height
width = height = cubeTexture.getWidth();
if(width != height) {
return false;
// We trade accuracy for speed by breaking the image into 32x32 parts
// and approximating the distance for all the pixels in each part to be
// the distance to the part's center.
int numDivisionsPerSide = 32;
if (width < numDivisionsPerSide) {
numDivisionsPerSide = width;
}
int stride = width / numDivisionsPerSide;
int halfStride = stride / 2;
// for each face of cube texture
for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) {
@ -718,11 +725,11 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
// step between two texels for range [-1, 1]
float invWidthBy2 = 2.0f / float(width);
for(int y=0; y < width; y++) {
for(int y=halfStride; y < width-halfStride; y += stride) {
// texture coordinate V in range [-1 to 1]
const float fV = negativeBound + float(y) * invWidthBy2;
for(int x=0; x < width; x++) {
for(int x=halfStride; x < width - halfStride; x += stride) {
// texture coordinate U in range [-1 to 1]
const float fU = negativeBound + float(x) * invWidthBy2;
@ -785,32 +792,37 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
sphericalHarmonicsEvaluateDirection(shBuff.data(), order, dir);
// index of texel in texture
uint pixOffsetIndex = (x + y * width) * numComponents;
// get color from texture and map to range [0, 1]
glm::vec3 clr(ColorUtils::sRGB8ToLinearFloat(data[pixOffsetIndex]),
ColorUtils::sRGB8ToLinearFloat(data[pixOffsetIndex + 1]),
ColorUtils::sRGB8ToLinearFloat(data[pixOffsetIndex + 2]));
float red { 0.0f };
float green { 0.0f };
float blue { 0.0f };
for (int i = 0; i < stride; ++i) {
for (int j = 0; j < stride; ++j) {
int k = (int)(x + i - halfStride + (y + j - halfStride) * width) * numComponents;
red += ColorUtils::sRGB8ToLinearFloat(data[k]);
green += ColorUtils::sRGB8ToLinearFloat(data[k + 1]);
blue += ColorUtils::sRGB8ToLinearFloat(data[k + 2]);
}
}
glm::vec3 clr(red, green, blue);
// scale color and add to previously accumulated coefficients
sphericalHarmonicsScale(shBuffB.data(), order,
shBuff.data(), clr.r * fDiffSolid);
sphericalHarmonicsAdd(resultR.data(), order,
resultR.data(), shBuffB.data());
sphericalHarmonicsScale(shBuffB.data(), order,
shBuff.data(), clr.g * fDiffSolid);
sphericalHarmonicsAdd(resultG.data(), order,
resultG.data(), shBuffB.data());
sphericalHarmonicsScale(shBuffB.data(), order,
shBuff.data(), clr.b * fDiffSolid);
sphericalHarmonicsAdd(resultB.data(), order,
resultB.data(), shBuffB.data());
// red
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.r * fDiffSolid);
sphericalHarmonicsAdd(resultR.data(), order, resultR.data(), shBuffB.data());
// green
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.g * fDiffSolid);
sphericalHarmonicsAdd(resultG.data(), order, resultG.data(), shBuffB.data());
// blue
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.b * fDiffSolid);
sphericalHarmonicsAdd(resultB.data(), order, resultB.data(), shBuffB.data());
}
}
}
// final scale for coefficients
const float fNormProj = (4.0f * glm::pi<float>()) / fWt;
const float fNormProj = (4.0f * glm::pi<float>()) / (fWt * (float)(stride * stride));
sphericalHarmonicsScale(resultR.data(), order, resultR.data(), fNormProj);
sphericalHarmonicsScale(resultG.data(), order, resultG.data(), fNormProj);
sphericalHarmonicsScale(resultB.data(), order, resultB.data(), fNormProj);

View file

@ -330,9 +330,22 @@ bool Geometry::areTexturesLoaded() const {
if (!_areTexturesLoaded) {
for (auto& material : _materials) {
// Check if material textures are loaded
if (std::any_of(material->_textures.cbegin(), material->_textures.cend(),
[](const NetworkMaterial::Textures::value_type& it) { return it.texture && !it.texture->isLoaded(); })) {
bool materialMissingTexture = std::any_of(material->_textures.cbegin(), material->_textures.cend(),
[](const NetworkMaterial::Textures::value_type& it) {
auto texture = it.texture;
if (!texture) {
return false;
}
// Failed texture downloads need to be considered as 'loaded'
// or the object will never fade in
bool finished = texture->isLoaded() || texture->isFailed();
if (!finished) {
return true;
}
return false;
});
if (materialMissingTexture) {
return false;
}

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

@ -49,6 +49,11 @@ void Skybox::updateSchemaBuffer() const {
}
}
void Skybox::clear() {
_schemaBuffer.edit<Schema>().color = vec3(0);
setCubemap(nullptr);
}
void Skybox::prepare(gpu::Batch& batch, int textureSlot, int bufferSlot) const {
if (bufferSlot > -1) {
batch.setUniformBuffer(bufferSlot, _schemaBuffer);

View file

@ -35,7 +35,8 @@ public:
void setCubemap(const gpu::TexturePointer& cubemap);
const gpu::TexturePointer& getCubemap() const { return _cubemap; }
virtual void clear() { setCubemap(nullptr); }
virtual bool empty() { return _schemaBuffer.get<Schema>().color == vec3(0) && !_cubemap; }
virtual void clear();
void prepare(gpu::Batch& batch, int textureSlot = SKYBOX_SKYMAP_SLOT, int bufferSlot = SKYBOX_CONSTANTS_SLOT) const;
virtual void render(gpu::Batch& batch, const ViewFrustum& frustum) const;
@ -50,7 +51,7 @@ protected:
class Schema {
public:
glm::vec3 color { 1.0f, 1.0f, 1.0f };
glm::vec3 color { 0.0f, 0.0f, 0.0f };
float blend { 0.0f };
};

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;
@ -282,6 +280,9 @@ public:
/// Checks whether the resource has loaded.
virtual bool isLoaded() const { return _loaded; }
/// Checks whether the resource has failed to download.
virtual bool isFailed() const { return _failedToLoad; }
/// For loading resources, returns the number of bytes received.
qint64 getBytesReceived() const { return _bytesReceived; }

View file

@ -47,12 +47,12 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityAdd:
case PacketType::EntityEdit:
case PacketType::EntityData:
return VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS;
return VERSION_WEB_ENTITIES_SUPPORT_DPI;
case PacketType::AvatarIdentity:
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AbsoluteSixByteRotations);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SensorToWorldMat);
case PacketType::ICEServerHeartbeat:
return 18; // ICE Server Heartbeat signing
case PacketType::AssetGetInfo:

View file

@ -186,12 +186,14 @@ const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59;
const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60;
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61;
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62;
const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63;
enum class AvatarMixerPacketVersion : PacketVersion {
TranslationSupport = 17,
SoftAttachmentSupport,
AvatarEntities,
AbsoluteSixByteRotations
AbsoluteSixByteRotations,
SensorToWorldMat
};
enum class DomainConnectRequestVersion : PacketVersion {

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

@ -38,6 +38,7 @@ public:
void parse(const QString& userDataJson);
bool ready();
bool enabled() { return _enabled; }
void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation);
const gpu::ShaderPointer& getShader() const { return _shader; }

View file

@ -26,6 +26,10 @@ ProceduralSkybox::ProceduralSkybox() : model::Skybox() {
_procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
}
bool ProceduralSkybox::empty() {
return !_procedural.enabled() && Skybox::empty();
}
void ProceduralSkybox::clear() {
// Parse and prepare a procedural with no shaders to release textures
parse(QString());

View file

@ -20,13 +20,14 @@
class ProceduralSkybox: public model::Skybox {
public:
ProceduralSkybox();
virtual ~ProceduralSkybox() {};
~ProceduralSkybox() override {};
void parse(const QString& userData) { _procedural.parse(userData); }
virtual void clear() override;
bool empty() override;
void clear() override;
virtual void render(gpu::Batch& batch, const ViewFrustum& frustum) const override;
void render(gpu::Batch& batch, const ViewFrustum& frustum) const override;
static void render(gpu::Batch& batch, const ViewFrustum& frustum, const ProceduralSkybox& skybox);
protected:

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,10 +530,21 @@ void ModelMeshPartPayload::startFade() {
void ModelMeshPartPayload::render(RenderArgs* args) const {
PerformanceTimer perfTimer("ModelMeshPartPayload::render");
if (!_model->_readyWhenAdded || !_model->_isVisible || !_hasStartedFade) {
if (!_model->addedToScene() || !_model->isVisible()) {
return; // bail asap
}
// If we didn't start the fade in, check if we are ready to now....
if (!_hasStartedFade && _model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
const_cast<ModelMeshPartPayload&>(*this).startFade();
}
// If we still didn't start the fade in, bail
if (!_hasStartedFade) {
return;
}
// When an individual mesh parts like this finishes its fade, we will mark the Model as
// having render items that need updating
bool nextIsFading = _isFading ? isStillFading() : false;

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

@ -1,3 +1,15 @@
set(TARGET_NAME script-engine)
setup_hifi_library(Gui Network Script ScriptTools WebSockets Widgets)
target_zlib()
add_dependency_external_projects(quazip)
find_package(QuaZip REQUIRED)
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES})
if (WIN32)
add_paths_to_fixup_libs(${QUAZIP_DLL_PATH})
endif ()
link_hifi_libraries(shared networking octree gpu ui procedural model model-networking recording avatars fbx entities controllers animation audio physics)

View file

@ -0,0 +1,145 @@
//
// FileScriptingInterface.cpp
// libraries/script-engine/src
//
// Created by Elisa Lupin-Jimenez on 6/28/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 <QTemporaryDir>
#include <QDir>
#include <QFile>
#include <QDebug>
#include <QBuffer>
#include <QTextCodec>
#include <QIODevice>
#include <QUrl>
#include <QByteArray>
#include <QString>
#include <QFileInfo>
#include <quazip5/quazip.h>
#include <quazip5/JlCompress.h>
#include "ResourceManager.h"
#include "FileScriptingInterface.h"
FileScriptingInterface::FileScriptingInterface(QObject* parent) : QObject(parent) {
// nothing for now
}
void FileScriptingInterface::runUnzip(QString path, QUrl url) {
qDebug() << "Url that was downloaded: " + url.toString();
qDebug() << "Path where download is saved: " + path;
QString fileName = "/" + path.section("/", -1);
QString tempDir = path;
tempDir.remove(fileName);
qDebug() << "Temporary directory at: " + tempDir;
if (!isTempDir(tempDir)) {
qDebug() << "Temporary directory mismatch; risk of losing files";
return;
}
QString file = unzipFile(path, tempDir);
if (file != "") {
qDebug() << "Object file to upload: " + file;
QUrl url = QUrl::fromLocalFile(file);
emit unzipSuccess(url.toString());
} else {
qDebug() << "unzip failed";
}
qDebug() << "Removing temporary directory at: " + tempDir;
QDir(tempDir).removeRecursively();
}
// fix to check that we are only referring to a temporary directory
bool FileScriptingInterface::isTempDir(QString tempDir) {
QString folderName = "/" + tempDir.section("/", -1);
QString tempContainer = tempDir;
tempContainer.remove(folderName);
QTemporaryDir test;
QString testDir = test.path();
folderName = "/" + testDir.section("/", -1);
QString testContainer = testDir;
testContainer.remove(folderName);
if (testContainer == tempContainer) return true;
return false;
}
bool FileScriptingInterface::isZippedFbx(QUrl url) {
if (url.toString().contains(".zip") && url.toString().contains("fbx")) return true;
return false;
}
// this function is not in use
QString FileScriptingInterface::getTempDir() {
QTemporaryDir dir;
dir.setAutoRemove(false);
return dir.path();
// do something to delete this temp dir later
}
QString FileScriptingInterface::convertUrlToPath(QUrl url) {
QString newUrl;
QString oldUrl = url.toString();
newUrl = oldUrl.section("filename=", 1, 1);
qDebug() << "Filename should be: " + newUrl;
return newUrl;
}
// this function is not in use
void FileScriptingInterface::downloadZip(QString path, const QString link) {
QUrl url = QUrl(link);
auto request = ResourceManager::createResourceRequest(nullptr, url);
connect(request, &ResourceRequest::finished, this, [this, path]{
unzipFile(path, ""); // so intellisense isn't mad
});
request->send();
}
QString FileScriptingInterface::unzipFile(QString path, QString tempDir) {
QDir dir(path);
QString dirName = dir.path();
QString target = tempDir + "/model_repo";
QStringList list = JlCompress::extractDir(dirName, target);
qDebug() << list;
if (!list.isEmpty()) {
return list.front();
} else {
qDebug() << "Extraction failed";
return "";
}
}
// this function is not in use
void FileScriptingInterface::recursiveFileScan(QFileInfo file, QString* dirName) {
/*if (!file.isDir()) {
qDebug() << "Regular file logged: " + file.fileName();
return;
}*/
QFileInfoList files;
if (file.fileName().contains(".zip")) {
qDebug() << "Extracting archive: " + file.fileName();
JlCompress::extractDir(file.fileName());
}
files = file.dir().entryInfoList();
/*if (files.empty()) {
files = JlCompress::getFileList(file.fileName());
}*/
foreach (QFileInfo file, files) {
qDebug() << "Looking into file: " + file.fileName();
recursiveFileScan(file, dirName);
}
return;
}

View file

@ -0,0 +1,43 @@
//
// FileScriptingInterface.h
// libraries/script-engine/src
//
// Created by Elisa Lupin-Jimenez on 6/28/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_FileScriptingInterface_h
#define hifi_FileScriptingInterface_h
#include <QtCore/QObject>
#include <QFileInfo>
#include <QString>
class FileScriptingInterface : public QObject {
Q_OBJECT
public:
FileScriptingInterface(QObject* parent);
public slots:
bool isZippedFbx(QUrl url);
QString convertUrlToPath(QUrl url);
void runUnzip(QString path, QUrl url);
QString getTempDir();
signals:
void unzipSuccess(QString url);
private:
bool isTempDir(QString tempDir);
QString unzipFile(QString path, QString tempDir);
void recursiveFileScan(QFileInfo file, QString* dirName);
void downloadZip(QString path, const QString link);
};
#endif // hifi_FileScriptingInterface_h

View file

@ -49,6 +49,7 @@
#include "BatchLoader.h"
#include "DataViewClass.h"
#include "EventTypes.h"
#include "FileScriptingInterface.h" // unzip project
#include "MenuItemProperties.h"
#include "ScriptAudioInjector.h"
#include "ScriptCache.h"
@ -501,6 +502,9 @@ void ScriptEngine::init() {
registerGlobalObject("Mat4", &_mat4Library);
registerGlobalObject("Uuid", &_uuidLibrary);
registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());
registerGlobalObject("File", new FileScriptingInterface(this));
qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue);
qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue);

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);
}

Some files were not shown because too many files have changed in this diff Show more