Merge branch 'master' into 21249

# Conflicts:
#	libraries/audio-client/src/AudioClient.cpp
This commit is contained in:
David Rowe 2017-04-26 13:24:18 +12:00
commit 4a5f84f4b9
365 changed files with 93627 additions and 2603 deletions
BUILD_ANDROID.md
assignment-client/src
cmake
externals/nvtt
modules
domain-server/src
interface
libraries
audio-client/src
display-plugins/src/display-plugins
entities-renderer
entities/src
gpu-gl/src/gpu
gpu/src/gpu
image
ktx/src/ktx
model-networking
model
networking/src
physics/src
procedural
render-utils

View file

@ -5,7 +5,7 @@ Please read the [general build guide](BUILD.md) for information on dependencies
You will need the following tools to build our Android targets.
* [cmake](http://www.cmake.org/download/) ~> 3.5.1
* [Qt](http://www.qt.io/download-open-source/#) ~> 5.5.1
* [Qt](http://www.qt.io/download-open-source/#) ~> 5.6.2
* [ant](http://ant.apache.org/bindownload.cgi) ~> 1.9.4
* [Android NDK](https://developer.android.com/tools/sdk/ndk/index.html) ~> r10d
* [Android SDK](http://developer.android.com/sdk/installing/index.html) ~> 24.4.1.1

View file

@ -31,6 +31,7 @@
#include <ScriptCache.h>
#include <ScriptEngines.h>
#include <SoundCache.h>
#include <UsersScriptingInterface.h>
#include <UUID.h>
#include <recording/ClipCache.h>
@ -75,6 +76,8 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT);
DependencyManager::set<RecordingScriptingInterface>();
DependencyManager::set<UsersScriptingInterface>();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
@ -351,6 +354,10 @@ void Agent::executeScript() {
// give this AvatarData object to the script engine
_scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data());
// give scripts access to the Users object
_scriptEngine->registerGlobalObject("Users", DependencyManager::get<UsersScriptingInterface>().data());
auto player = DependencyManager::get<recording::Deck>();
connect(player.data(), &recording::Deck::playbackStateChanged, [=] {
if (player->isPlaying()) {

View file

@ -1,91 +0,0 @@
//
// AssignmentAction.cpp
// assignment-client/src/
//
// Created by Seth Alves 2015-6-19
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntitySimulation.h"
#include "AssignmentAction.h"
AssignmentAction::AssignmentAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) :
EntityActionInterface(type, id),
_data(QByteArray()),
_active(false),
_ownerEntity(ownerEntity) {
}
AssignmentAction::~AssignmentAction() {
}
void AssignmentAction::removeFromSimulation(EntitySimulationPointer simulation) const {
withReadLock([&]{
simulation->removeAction(_id);
simulation->applyActionChanges();
});
}
QByteArray AssignmentAction::serialize() const {
QByteArray result;
withReadLock([&]{
result = _data;
});
return result;
}
void AssignmentAction::deserialize(QByteArray serializedArguments) {
withWriteLock([&]{
_data = serializedArguments;
});
}
bool AssignmentAction::updateArguments(QVariantMap arguments) {
qDebug() << "UNEXPECTED -- AssignmentAction::updateArguments called in assignment-client.";
return false;
}
QVariantMap AssignmentAction::getArguments() {
qDebug() << "UNEXPECTED -- AssignmentAction::getArguments called in assignment-client.";
return QVariantMap();
}
glm::vec3 AssignmentAction::getPosition() {
qDebug() << "UNEXPECTED -- AssignmentAction::getPosition called in assignment-client.";
return glm::vec3(0.0f);
}
void AssignmentAction::setPosition(glm::vec3 position) {
qDebug() << "UNEXPECTED -- AssignmentAction::setPosition called in assignment-client.";
}
glm::quat AssignmentAction::getRotation() {
qDebug() << "UNEXPECTED -- AssignmentAction::getRotation called in assignment-client.";
return glm::quat();
}
void AssignmentAction::setRotation(glm::quat rotation) {
qDebug() << "UNEXPECTED -- AssignmentAction::setRotation called in assignment-client.";
}
glm::vec3 AssignmentAction::getLinearVelocity() {
qDebug() << "UNEXPECTED -- AssignmentAction::getLinearVelocity called in assignment-client.";
return glm::vec3(0.0f);
}
void AssignmentAction::setLinearVelocity(glm::vec3 linearVelocity) {
qDebug() << "UNEXPECTED -- AssignmentAction::setLinearVelocity called in assignment-client.";
}
glm::vec3 AssignmentAction::getAngularVelocity() {
qDebug() << "UNEXPECTED -- AssignmentAction::getAngularVelocity called in assignment-client.";
return glm::vec3(0.0f);
}
void AssignmentAction::setAngularVelocity(glm::vec3 angularVelocity) {
qDebug() << "UNEXPECTED -- AssignmentAction::setAngularVelocity called in assignment-client.";
}

View file

@ -1,48 +0,0 @@
//
// AssignmentActionFactory.cpp
// assignment-client/src/
//
// Created by Seth Alves on 2015-6-19
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AssignmentActionFactory.h"
EntityActionPointer assignmentActionFactory(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) {
return EntityActionPointer(new AssignmentAction(type, id, ownerEntity));
}
EntityActionPointer AssignmentActionFactory::factory(EntityActionType type,
const QUuid& id,
EntityItemPointer ownerEntity,
QVariantMap arguments) {
EntityActionPointer action = assignmentActionFactory(type, id, ownerEntity);
if (action) {
bool ok = action->updateArguments(arguments);
if (ok) {
return action;
}
}
return nullptr;
}
EntityActionPointer AssignmentActionFactory::factoryBA(EntityItemPointer ownerEntity, QByteArray data) {
QDataStream serializedActionDataStream(data);
EntityActionType type;
QUuid id;
serializedActionDataStream >> type;
serializedActionDataStream >> id;
EntityActionPointer action = assignmentActionFactory(type, id, ownerEntity);
if (action) {
action->deserialize(data);
}
return action;
}

View file

@ -1,29 +0,0 @@
//
// AssignmentActionFactory.cpp
// assignment-client/src/
//
// Created by Seth Alves on 2015-6-19
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AssignmentActionFactory_h
#define hifi_AssignmentActionFactory_h
#include "EntityActionFactoryInterface.h"
#include "AssignmentAction.h"
class AssignmentActionFactory : public EntityActionFactoryInterface {
public:
AssignmentActionFactory() : EntityActionFactoryInterface() { }
virtual ~AssignmentActionFactory() { }
virtual EntityActionPointer factory(EntityActionType type,
const QUuid& id,
EntityItemPointer ownerEntity,
QVariantMap arguments) override;
virtual EntityActionPointer factoryBA(EntityItemPointer ownerEntity, QByteArray data) override;
};
#endif // hifi_AssignmentActionFactory_h

View file

@ -33,7 +33,7 @@
#include <UserActivityLoggerScriptingInterface.h>
#include "AssignmentFactory.h"
#include "AssignmentActionFactory.h"
#include "AssignmentDynamicFactory.h"
#include "AssignmentClient.h"
#include "AssignmentClientLogging.h"
@ -64,8 +64,8 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
auto animationCache = DependencyManager::set<AnimationCache>();
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>(false);
DependencyManager::registerInheritance<EntityActionFactoryInterface, AssignmentActionFactory>();
auto actionFactory = DependencyManager::set<AssignmentActionFactory>();
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
auto dynamicFactory = DependencyManager::set<AssignmentDynamicFactory>();
DependencyManager::set<ResourceScriptingInterface>();
DependencyManager::set<UserActivityLoggerScriptingInterface>();

View file

@ -0,0 +1,83 @@
//
// AssignmentDynamic.cpp
// assignment-client/src/
//
// Created by Seth Alves 2015-6-19
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntitySimulation.h"
#include "AssignmentDynamic.h"
AssignmentDynamic::AssignmentDynamic(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) :
EntityDynamicInterface(type, id),
_data(QByteArray()),
_active(false),
_ownerEntity(ownerEntity) {
}
AssignmentDynamic::~AssignmentDynamic() {
}
void AssignmentDynamic::removeFromSimulation(EntitySimulationPointer simulation) const {
withReadLock([&]{
simulation->removeDynamic(_id);
simulation->applyDynamicChanges();
});
}
QByteArray AssignmentDynamic::serialize() const {
QByteArray result;
withReadLock([&]{
result = _data;
});
return result;
}
void AssignmentDynamic::deserialize(QByteArray serializedArguments) {
withWriteLock([&]{
_data = serializedArguments;
});
}
bool AssignmentDynamic::updateArguments(QVariantMap arguments) {
qDebug() << "UNEXPECTED -- AssignmentDynamic::updateArguments called in assignment-client.";
return false;
}
QVariantMap AssignmentDynamic::getArguments() {
qDebug() << "UNEXPECTED -- AssignmentDynamic::getArguments called in assignment-client.";
return QVariantMap();
}
glm::vec3 AssignmentDynamic::getPosition() {
qDebug() << "UNEXPECTED -- AssignmentDynamic::getPosition called in assignment-client.";
return glm::vec3(0.0f);
}
glm::quat AssignmentDynamic::getRotation() {
qDebug() << "UNEXPECTED -- AssignmentDynamic::getRotation called in assignment-client.";
return glm::quat();
}
glm::vec3 AssignmentDynamic::getLinearVelocity() {
qDebug() << "UNEXPECTED -- AssignmentDynamic::getLinearVelocity called in assignment-client.";
return glm::vec3(0.0f);
}
void AssignmentDynamic::setLinearVelocity(glm::vec3 linearVelocity) {
qDebug() << "UNEXPECTED -- AssignmentDynamic::setLinearVelocity called in assignment-client.";
}
glm::vec3 AssignmentDynamic::getAngularVelocity() {
qDebug() << "UNEXPECTED -- AssignmentDynamic::getAngularVelocity called in assignment-client.";
return glm::vec3(0.0f);
}
void AssignmentDynamic::setAngularVelocity(glm::vec3 angularVelocity) {
qDebug() << "UNEXPECTED -- AssignmentDynamic::setAngularVelocity called in assignment-client.";
}

View file

@ -1,5 +1,5 @@
//
// AssignmentAction.h
// AssignmentDynamic.h
// assignment-client/src/
//
// Created by Seth Alves 2015-6-19
@ -8,21 +8,21 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// http://bulletphysics.org/Bullet/BulletFull/classbtActionInterface.html
// http://bulletphysics.org/Bullet/BulletFull/classbtDynamicInterface.html
#ifndef hifi_AssignmentAction_h
#define hifi_AssignmentAction_h
#ifndef hifi_AssignmentDynamic_h
#define hifi_AssignmentDynamic_h
#include <QUuid>
#include <EntityItem.h>
#include "EntityActionInterface.h"
#include "EntityDynamicInterface.h"
class AssignmentAction : public EntityActionInterface, public ReadWriteLockable {
class AssignmentDynamic : public EntityDynamicInterface, public ReadWriteLockable {
public:
AssignmentAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity);
virtual ~AssignmentAction();
AssignmentDynamic(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity);
virtual ~AssignmentDynamic();
virtual void removeFromSimulation(EntitySimulationPointer simulation) const override;
virtual EntityItemWeakPointer getOwnerEntity() const override { return _ownerEntity; }
@ -38,9 +38,7 @@ private:
protected:
virtual glm::vec3 getPosition() override;
virtual void setPosition(glm::vec3 position) override;
virtual glm::quat getRotation() override;
virtual void setRotation(glm::quat rotation) override;
virtual glm::vec3 getLinearVelocity() override;
virtual void setLinearVelocity(glm::vec3 linearVelocity) override;
virtual glm::vec3 getAngularVelocity() override;
@ -50,4 +48,4 @@ protected:
EntityItemWeakPointer _ownerEntity;
};
#endif // hifi_AssignmentAction_h
#endif // hifi_AssignmentDynamic_h

View file

@ -0,0 +1,48 @@
//
// AssignmentDynamcFactory.cpp
// assignment-client/src/
//
// Created by Seth Alves on 2015-6-19
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AssignmentDynamicFactory.h"
EntityDynamicPointer assignmentDynamicFactory(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) {
return EntityDynamicPointer(new AssignmentDynamic(type, id, ownerEntity));
}
EntityDynamicPointer AssignmentDynamicFactory::factory(EntityDynamicType type,
const QUuid& id,
EntityItemPointer ownerEntity,
QVariantMap arguments) {
EntityDynamicPointer dynamic = assignmentDynamicFactory(type, id, ownerEntity);
if (dynamic) {
bool ok = dynamic->updateArguments(arguments);
if (ok) {
return dynamic;
}
}
return nullptr;
}
EntityDynamicPointer AssignmentDynamicFactory::factoryBA(EntityItemPointer ownerEntity, QByteArray data) {
QDataStream serializedDynamicDataStream(data);
EntityDynamicType type;
QUuid id;
serializedDynamicDataStream >> type;
serializedDynamicDataStream >> id;
EntityDynamicPointer dynamic = assignmentDynamicFactory(type, id, ownerEntity);
if (dynamic) {
dynamic->deserialize(data);
}
return dynamic;
}

View file

@ -0,0 +1,29 @@
//
// AssignmentDynamicFactory.cpp
// assignment-client/src/
//
// Created by Seth Alves on 2015-6-19
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AssignmentDynamicFactory_h
#define hifi_AssignmentDynamicFactory_h
#include "EntityDynamicFactoryInterface.h"
#include "AssignmentDynamic.h"
class AssignmentDynamicFactory : public EntityDynamicFactoryInterface {
public:
AssignmentDynamicFactory() : EntityDynamicFactoryInterface() { }
virtual ~AssignmentDynamicFactory() { }
virtual EntityDynamicPointer factory(EntityDynamicType type,
const QUuid& id,
EntityItemPointer ownerEntity,
QVariantMap arguments) override;
virtual EntityDynamicPointer factoryBA(EntityItemPointer ownerEntity, QByteArray data) override;
};
#endif // hifi_AssignmentDynamicFactory_h

87
cmake/externals/nvtt/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,87 @@
include(ExternalProject)
include(SelectLibraryConfigurations)
set(EXTERNAL_NAME nvtt)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://s3.amazonaws.com/hifi-public/dependencies/nvtt-win-2.1.0.zip
URL_MD5 3ea6eeadbcc69071acf9c49ba565760e
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE PATH "Location of NVTT include directory")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Release/x64/nvtt.lib CACHE FILEPATH "Path to NVTT release library")
set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/Release>/x64" CACHE PATH "Location of NVTT release DLL")
else ()
if (ANDROID)
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
endif ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.zip
URL_MD5 81b8fa6a9ee3f986088eb6e2215d6a57
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
)
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "Location of NVTT include directory")
if (APPLE)
set(_LIB_EXT "dylib")
else ()
set(_LIB_EXT "so")
endif ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libnvtt.${_LIB_EXT} CACHE FILEPATH "Path to NVTT library")
if (APPLE)
# on OS X we have to use install_name_tool to fix the paths found in the NVTT shared libraries
# so that they can be found and linked during the linking phase
set(_NVTT_LIB_DIR "${INSTALL_DIR}/lib")
# first fix the install names of all present libraries
ExternalProject_Add_Step(
${EXTERNAL_NAME}
change-install-name
COMMENT "Calling install_name_tool on NVTT libraries to fix install name for dylib linking"
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_NVTT_LIB_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
DEPENDEES install
WORKING_DIRECTORY <INSTALL_DIR>
LOG 1
)
# then, for the main library (libnvtt) fix the paths to the dependency libraries (core, image, math)
ExternalProject_Add_Step(
${EXTERNAL_NAME}
change-dependency-paths
COMMENT "Calling install_name_tool on NVTT libraries to fix paths for dependency libraries"
COMMAND install_name_tool -change libnvimage.dylib ${INSTALL_DIR}/lib/libnvimage.dylib libnvtt.dylib
COMMAND install_name_tool -change libnvcore.dylib ${INSTALL_DIR}/lib/libnvcore.dylib libnvtt.dylib
COMMAND install_name_tool -change libnvmath.dylib ${INSTALL_DIR}/lib/libnvmath.dylib libnvtt.dylib
COMMAND install_name_tool -change libnvcore.dylib ${INSTALL_DIR}/lib/libnvcore.dylib libnvimage.dylib
COMMAND install_name_tool -change libnvmath.dylib ${INSTALL_DIR}/lib/libnvmath.dylib libnvimage.dylib
COMMAND install_name_tool -change libnvcore.dylib ${INSTALL_DIR}/lib/libnvcore.dylib libnvmath.dylib
DEPENDEES install
WORKING_DIRECTORY <INSTALL_DIR>/lib
LOG 1
)
endif ()
endif ()
# Hide this external target (for IDE users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")

View file

@ -0,0 +1,37 @@
#
# FindNVTT.cmake
#
# Try to find NVIDIA texture tools library and include path.
# Once done this will define
#
# NVTT_FOUND
# NVTT_INCLUDE_DIRS
# NVTT_LIBRARIES
# NVTT_DLL_PATH
#
# Created on 4/14/2017 by Stephen Birarda
# Copyright 2017 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("nvtt")
find_path(NVTT_INCLUDE_DIRS nvtt/nvtt.h PATH_SUFFIXES include HINTS ${NVTT_SEARCH_DIRS})
include(FindPackageHandleStandardArgs)
find_library(NVTT_LIBRARY_RELEASE nvtt PATH_SUFFIXES "lib" "Release.x64/lib" HINTS ${NVTT_SEARCH_DIRS})
find_library(NVTT_LIBRARY_DEBUG nvtt PATH_SUFFIXES "lib" "Debug.x64/lib" HINTS ${NVTT_SEARCH_DIRS})
include(SelectLibraryConfigurations)
select_library_configurations(NVTT)
if (WIN32)
find_path(NVTT_DLL_PATH nvtt.dll PATH_SUFFIXES "Release.x64/bin" HINTS ${NVTT_SEARCH_DIRS})
find_package_handle_standard_args(NVTT DEFAULT_MSG NVTT_INCLUDE_DIRS NVTT_LIBRARIES NVTT_DLL_PATH)
else ()
find_package_handle_standard_args(NVTT DEFAULT_MSG NVTT_INCLUDE_DIRS NVTT_LIBRARIES)
endif ()

View file

@ -435,23 +435,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
return SharedNodePointer();
}
QUuid hintNodeID;
// in case this is a node that's failing to connect
// double check we don't have a node whose sockets match exactly already in the list
limitedNodeList->eachNodeBreakable([&nodeConnection, &hintNodeID](const SharedNodePointer& node){
if (node->getPublicSocket() == nodeConnection.publicSockAddr
&& node->getLocalSocket() == nodeConnection.localSockAddr) {
// we have a node that already has these exact sockets - this occurs if a node
// is unable to connect to the domain
hintNodeID = node->getUUID();
return false;
}
return true;
});
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
// set the edit rights for this user
newNode->setPermissions(userPerms);
@ -479,11 +464,12 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
return newNode;
}
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
QUuid nodeID) {
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection) {
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
QUuid nodeID;
if (connectedPeer) {
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
nodeID = nodeConnection.connectUUID;
@ -493,10 +479,8 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
discoveredSocket = *connectedPeer->getActiveSocket();
}
} else {
// we got a connectUUID we didn't recognize, either use the hinted node ID or randomly generate a new one
if (nodeID.isNull()) {
nodeID = QUuid::createUuid();
}
// we got a connectUUID we didn't recognize, randomly generate a new one
nodeID = QUuid::createUuid();
}
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();

View file

@ -76,8 +76,7 @@ private:
SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection,
const QString& username,
const QByteArray& usernameSignature);
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
QUuid nodeID = QUuid());
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection);
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr);

View file

@ -194,7 +194,7 @@ link_hifi_libraries(
recording fbx networking model-networking entities avatars
audio audio-client animation script-engine physics
render-utils entities-renderer avatars-renderer ui auto-updater
controllers plugins
controllers plugins image
ui-plugins display-plugins input-plugins
${NON_ANDROID_LIBRARIES}
)

View file

@ -211,6 +211,11 @@ Item {
text: "Downloads: " + root.downloads + "/" + root.downloadLimit +
", Pending: " + root.downloadsPending;
}
StatText {
visible: root.expanded;
text: "Processing: " + root.processing +
", Pending: " + root.processingPending;
}
StatText {
visible: root.expanded && root.downloadUrls.length > 0;
text: "Download URLs:"

View file

@ -140,7 +140,7 @@
#include "devices/Leapmotion.h"
#include "DiscoverabilityManager.h"
#include "GLCanvas.h"
#include "InterfaceActionFactory.h"
#include "InterfaceDynamicFactory.h"
#include "InterfaceLogging.h"
#include "LODManager.h"
#include "ModelPackager.h"
@ -462,7 +462,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
DependencyManager::registerInheritance<EntityActionFactoryInterface, InterfaceActionFactory>();
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, InterfaceDynamicFactory>();
DependencyManager::registerInheritance<SpatialParentFinder, InterfaceParentFinder>();
// Set dependencies
@ -516,7 +516,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<OffscreenUi>();
DependencyManager::set<AutoUpdater>();
DependencyManager::set<PathUtils>();
DependencyManager::set<InterfaceActionFactory>();
DependencyManager::set<InterfaceDynamicFactory>();
DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<MessagesClient>();
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
@ -1455,8 +1455,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
QString skyboxUrl { PathUtils::resourcesPath() + "images/Default-Sky-9-cubemap.jpg" };
QString skyboxAmbientUrl { PathUtils::resourcesPath() + "images/Default-Sky-9-ambient.jpg" };
_defaultSkyboxTexture = textureCache->getImageTexture(skyboxUrl, NetworkTexture::CUBE_TEXTURE, { { "generateIrradiance", false } });
_defaultSkyboxAmbientTexture = textureCache->getImageTexture(skyboxAmbientUrl, NetworkTexture::CUBE_TEXTURE, { { "generateIrradiance", true } });
_defaultSkyboxTexture = textureCache->getImageTexture(skyboxUrl, image::TextureUsage::CUBE_TEXTURE, { { "generateIrradiance", false } });
_defaultSkyboxAmbientTexture = textureCache->getImageTexture(skyboxAmbientUrl, image::TextureUsage::CUBE_TEXTURE, { { "generateIrradiance", true } });
_defaultSkybox->setCubemap(_defaultSkyboxTexture);
@ -4450,7 +4450,7 @@ void Application::update(float deltaTime) {
_entitySimulation->setObjectsToChange(stillNeedChange);
});
_entitySimulation->applyActionChanges();
_entitySimulation->applyDynamicChanges();
avatarManager->getObjectsToRemoveFromPhysics(motionStates);
_physicsEngine->removeObjects(motionStates);
@ -4460,8 +4460,8 @@ void Application::update(float deltaTime) {
_physicsEngine->changeObjects(motionStates);
myAvatar->prepareForPhysicsSimulation();
_physicsEngine->forEachAction([&](EntityActionPointer action) {
action->prepareForPhysicsSimulation();
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
dynamic->prepareForPhysicsSimulation();
});
}
{
@ -6769,11 +6769,6 @@ void Application::updateDisplayMode() {
return;
}
UserActivityLogger::getInstance().logAction("changed_display_mode", {
{ "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" },
{ "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" }
});
auto offscreenUi = DependencyManager::get<OffscreenUi>();
// Make the switch atomic from the perspective of other threads
@ -6828,13 +6823,16 @@ void Application::updateDisplayMode() {
offscreenUi->getDesktop()->setProperty("repositionLocked", wasRepositionLocked);
}
bool isHmd = _displayPlugin->isHmd();
qCDebug(interfaceapp) << "Entering into" << (isHmd ? "HMD" : "Desktop") << "Mode";
// Only log/emit after a successful change
UserActivityLogger::getInstance().logAction("changed_display_mode", {
{ "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" },
{ "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" },
{ "hmd", isHmd }
});
emit activeDisplayPluginChanged();
if (_displayPlugin->isHmd()) {
qCDebug(interfaceapp) << "Entering into HMD Mode";
} else {
qCDebug(interfaceapp) << "Entering into Desktop Mode";
}
// reset the avatar, to set head and hand palms back to a reasonable default pose.
getMyAvatar()->reset(false);

View file

@ -1,82 +0,0 @@
//
// InterfaceActionFactory.cpp
// libraries/entities/src
//
// Created by Seth Alves on 2015-6-2
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <avatar/AvatarActionHold.h>
#include <ObjectActionOffset.h>
#include <ObjectActionSpring.h>
#include <ObjectActionTravelOriented.h>
#include <LogHandler.h>
#include "InterfaceActionFactory.h"
EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) {
switch (type) {
case ACTION_TYPE_NONE:
return EntityActionPointer();
case ACTION_TYPE_OFFSET:
return std::make_shared<ObjectActionOffset>(id, ownerEntity);
case ACTION_TYPE_SPRING:
return std::make_shared<ObjectActionSpring>(id, ownerEntity);
case ACTION_TYPE_HOLD:
return std::make_shared<AvatarActionHold>(id, ownerEntity);
case ACTION_TYPE_TRAVEL_ORIENTED:
return std::make_shared<ObjectActionTravelOriented>(id, ownerEntity);
}
Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity action type");
return EntityActionPointer();
}
EntityActionPointer InterfaceActionFactory::factory(EntityActionType type,
const QUuid& id,
EntityItemPointer ownerEntity,
QVariantMap arguments) {
EntityActionPointer action = interfaceActionFactory(type, id, ownerEntity);
if (action) {
bool ok = action->updateArguments(arguments);
if (ok) {
if (action->lifetimeIsOver()) {
return nullptr;
}
return action;
}
}
return nullptr;
}
EntityActionPointer InterfaceActionFactory::factoryBA(EntityItemPointer ownerEntity, QByteArray data) {
QDataStream serializedArgumentStream(data);
EntityActionType type;
QUuid id;
serializedArgumentStream >> type;
serializedArgumentStream >> id;
EntityActionPointer action = interfaceActionFactory(type, id, ownerEntity);
if (action) {
action->deserialize(data);
if (action->lifetimeIsOver()) {
static QString repeatedMessage =
LogHandler::getInstance().addRepeatedMessageRegex(".*factoryBA lifetimeIsOver during action creation.*");
qDebug() << "InterfaceActionFactory::factoryBA lifetimeIsOver during action creation --"
<< action->getExpires() << "<" << usecTimestampNow();
return nullptr;
}
}
return action;
}

View file

@ -0,0 +1,88 @@
//
// InterfaceDynamicFactory.cpp
// libraries/entities/src
//
// Created by Seth Alves on 2015-6-2
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <avatar/AvatarActionHold.h>
#include <avatar/AvatarActionFarGrab.h>
#include <ObjectActionOffset.h>
#include <ObjectActionSpring.h>
#include <ObjectActionTravelOriented.h>
#include <ObjectConstraintHinge.h>
#include <LogHandler.h>
#include "InterfaceDynamicFactory.h"
EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) {
switch (type) {
case DYNAMIC_TYPE_NONE:
return EntityDynamicPointer();
case DYNAMIC_TYPE_OFFSET:
return std::make_shared<ObjectActionOffset>(id, ownerEntity);
case DYNAMIC_TYPE_SPRING:
return std::make_shared<ObjectActionSpring>(id, ownerEntity);
case DYNAMIC_TYPE_HOLD:
return std::make_shared<AvatarActionHold>(id, ownerEntity);
case DYNAMIC_TYPE_TRAVEL_ORIENTED:
return std::make_shared<ObjectActionTravelOriented>(id, ownerEntity);
case DYNAMIC_TYPE_HINGE:
return std::make_shared<ObjectConstraintHinge>(id, ownerEntity);
case DYNAMIC_TYPE_FAR_GRAB:
return std::make_shared<AvatarActionFarGrab>(id, ownerEntity);
}
Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity dynamic type");
return EntityDynamicPointer();
}
EntityDynamicPointer InterfaceDynamicFactory::factory(EntityDynamicType type,
const QUuid& id,
EntityItemPointer ownerEntity,
QVariantMap arguments) {
EntityDynamicPointer dynamic = interfaceDynamicFactory(type, id, ownerEntity);
if (dynamic) {
bool ok = dynamic->updateArguments(arguments);
if (ok) {
if (dynamic->lifetimeIsOver()) {
return nullptr;
}
return dynamic;
}
}
return nullptr;
}
EntityDynamicPointer InterfaceDynamicFactory::factoryBA(EntityItemPointer ownerEntity, QByteArray data) {
QDataStream serializedArgumentStream(data);
EntityDynamicType type;
QUuid id;
serializedArgumentStream >> type;
serializedArgumentStream >> id;
EntityDynamicPointer dynamic = interfaceDynamicFactory(type, id, ownerEntity);
if (dynamic) {
dynamic->deserialize(data);
if (dynamic->lifetimeIsOver()) {
static QString repeatedMessage =
LogHandler::getInstance().addRepeatedMessageRegex(".*factoryBA lifetimeIsOver during dynamic creation.*");
qDebug() << "InterfaceDynamicFactory::factoryBA lifetimeIsOver during dynamic creation --"
<< dynamic->getExpires() << "<" << usecTimestampNow();
return nullptr;
}
}
return dynamic;
}

View file

@ -1,5 +1,5 @@
//
// InterfaceActionFactory.cpp
// InterfaceDynamicFactory.cpp
// interface/src/
//
// Created by Seth Alves on 2015-6-10
@ -9,21 +9,21 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_InterfaceActionFactory_h
#define hifi_InterfaceActionFactory_h
#ifndef hifi_InterfaceDynamicFactory_h
#define hifi_InterfaceDynamicFactory_h
#include "EntityActionFactoryInterface.h"
#include "EntityDynamicFactoryInterface.h"
class InterfaceActionFactory : public EntityActionFactoryInterface {
class InterfaceDynamicFactory : public EntityDynamicFactoryInterface {
public:
InterfaceActionFactory() : EntityActionFactoryInterface() { }
virtual ~InterfaceActionFactory() { }
virtual EntityActionPointer factory(EntityActionType type,
InterfaceDynamicFactory() : EntityDynamicFactoryInterface() { }
virtual ~InterfaceDynamicFactory() { }
virtual EntityDynamicPointer factory(EntityDynamicType type,
const QUuid& id,
EntityItemPointer ownerEntity,
QVariantMap arguments) override;
virtual EntityActionPointer factoryBA(EntityItemPointer ownerEntity,
virtual EntityDynamicPointer factoryBA(EntityItemPointer ownerEntity,
QByteArray data) override;
};
#endif // hifi_InterfaceActionFactory_h
#endif // hifi_InterfaceDynamicFactory_h

View file

@ -0,0 +1,64 @@
//
// AvatarActionFarGrab.cpp
// interface/src/avatar/
//
// Created by Seth Alves 2017-4-14
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AvatarActionFarGrab.h"
AvatarActionFarGrab::AvatarActionFarGrab(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectActionSpring(id, ownerEntity) {
_type = DYNAMIC_TYPE_FAR_GRAB;
#if WANT_DEBUG
qDebug() << "AvatarActionFarGrab::AvatarActionFarGrab";
#endif
}
AvatarActionFarGrab::~AvatarActionFarGrab() {
#if WANT_DEBUG
qDebug() << "AvatarActionFarGrab::~AvatarActionFarGrab";
#endif
}
QByteArray AvatarActionFarGrab::serialize() const {
QByteArray serializedActionArguments;
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
dataStream << DYNAMIC_TYPE_FAR_GRAB;
dataStream << getID();
dataStream << ObjectActionSpring::springVersion;
serializeParameters(dataStream);
return serializedActionArguments;
}
void AvatarActionFarGrab::deserialize(QByteArray serializedArguments) {
QDataStream dataStream(serializedArguments);
EntityDynamicType type;
dataStream >> type;
QUuid id;
dataStream >> id;
if (type != getType() || id != getID()) {
qDebug() << "AvatarActionFarGrab::deserialize type or ID don't match." << type << id << getID();
return;
}
uint16_t serializationVersion;
dataStream >> serializationVersion;
if (serializationVersion != ObjectActionSpring::springVersion) {
assert(false);
return;
}
deserializeParameters(serializedArguments, dataStream);
}

View file

@ -0,0 +1,27 @@
//
// AvatarActionFarGrab.h
// interface/src/avatar/
//
// Created by Seth Alves 2017-4-14
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AvatarActionFarGrab_h
#define hifi_AvatarActionFarGrab_h
#include <EntityItem.h>
#include <ObjectActionSpring.h>
class AvatarActionFarGrab : public ObjectActionSpring {
public:
AvatarActionFarGrab(const QUuid& id, EntityItemPointer ownerEntity);
virtual ~AvatarActionFarGrab();
QByteArray serialize() const override;
virtual void deserialize(QByteArray serializedArguments) override;
};
#endif // hifi_AvatarActionFarGrab_h

View file

@ -23,7 +23,7 @@ const int AvatarActionHold::velocitySmoothFrames = 6;
AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectActionSpring(id, ownerEntity)
{
_type = ACTION_TYPE_HOLD;
_type = DYNAMIC_TYPE_HOLD;
_measuredLinearVelocities.resize(AvatarActionHold::velocitySmoothFrames);
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
@ -323,28 +323,28 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
bool ignoreIK;
bool needUpdate = false;
bool somethingChanged = ObjectAction::updateArguments(arguments);
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
withReadLock([&]{
bool ok = true;
relativePosition = EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false);
relativePosition = EntityDynamicInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false);
if (!ok) {
relativePosition = _relativePosition;
}
ok = true;
relativeRotation = EntityActionInterface::extractQuatArgument("hold", arguments, "relativeRotation", ok, false);
relativeRotation = EntityDynamicInterface::extractQuatArgument("hold", arguments, "relativeRotation", ok, false);
if (!ok) {
relativeRotation = _relativeRotation;
}
ok = true;
timeScale = EntityActionInterface::extractFloatArgument("hold", arguments, "timeScale", ok, false);
timeScale = EntityDynamicInterface::extractFloatArgument("hold", arguments, "timeScale", ok, false);
if (!ok) {
timeScale = _linearTimeScale;
}
ok = true;
hand = EntityActionInterface::extractStringArgument("hold", arguments, "hand", ok, false);
hand = EntityDynamicInterface::extractStringArgument("hold", arguments, "hand", ok, false);
if (!ok || !(hand == "left" || hand == "right")) {
hand = _hand;
}
@ -353,20 +353,20 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
holderID = myAvatar->getSessionUUID();
ok = true;
kinematic = EntityActionInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
kinematic = EntityDynamicInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
if (!ok) {
kinematic = _kinematic;
}
ok = true;
kinematicSetVelocity = EntityActionInterface::extractBooleanArgument("hold", arguments,
kinematicSetVelocity = EntityDynamicInterface::extractBooleanArgument("hold", arguments,
"kinematicSetVelocity", ok, false);
if (!ok) {
kinematicSetVelocity = _kinematicSetVelocity;
}
ok = true;
ignoreIK = EntityActionInterface::extractBooleanArgument("hold", arguments, "ignoreIK", ok, false);
ignoreIK = EntityDynamicInterface::extractBooleanArgument("hold", arguments, "ignoreIK", ok, false);
if (!ok) {
ignoreIK = _ignoreIK;
}
@ -400,8 +400,8 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setActionDataDirty(true);
ownerEntity->setActionDataNeedsTransmit(true);
ownerEntity->setDynamicDataDirty(true);
ownerEntity->setDynamicDataNeedsTransmit(true);
}
});
}
@ -410,7 +410,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
}
QVariantMap AvatarActionHold::getArguments() {
QVariantMap arguments = ObjectAction::getArguments();
QVariantMap arguments = ObjectDynamic::getArguments();
withReadLock([&]{
arguments["holderID"] = _holderID;
arguments["relativePosition"] = glmToQMap(_relativePosition);
@ -429,7 +429,7 @@ QByteArray AvatarActionHold::serialize() const {
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
withReadLock([&]{
dataStream << ACTION_TYPE_HOLD;
dataStream << DYNAMIC_TYPE_HOLD;
dataStream << getID();
dataStream << AvatarActionHold::holdVersion;
@ -451,7 +451,7 @@ QByteArray AvatarActionHold::serialize() const {
void AvatarActionHold::deserialize(QByteArray serializedArguments) {
QDataStream dataStream(serializedArguments);
EntityActionType type;
EntityDynamicType type;
dataStream >> type;
assert(type == getType());

View file

@ -94,7 +94,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
PROFILE_RANGE(app, __FUNCTION__);
if (!_uiTexture) {
_uiTexture = gpu::TexturePointer(gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()));
_uiTexture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
_uiTexture->setSource(__FUNCTION__);
}
// Once we move UI rendering and screen rendering to different
@ -207,13 +207,13 @@ void ApplicationOverlay::buildFramebufferObject() {
auto width = uiSize.x;
auto height = uiSize.y;
if (!_overlayFramebuffer->getDepthStencilBuffer()) {
auto overlayDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(DEPTH_FORMAT, width, height, gpu::Texture::SINGLE_MIP, DEFAULT_SAMPLER));
auto overlayDepthTexture = gpu::Texture::createRenderBuffer(DEPTH_FORMAT, width, height, gpu::Texture::SINGLE_MIP, DEFAULT_SAMPLER);
_overlayFramebuffer->setDepthStencilBuffer(overlayDepthTexture, DEPTH_FORMAT);
}
if (!_overlayFramebuffer->getRenderBuffer(0)) {
const gpu::Sampler OVERLAY_SAMPLER(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP);
auto colorBuffer = gpu::TexturePointer(gpu::Texture::createRenderBuffer(COLOR_FORMAT, width, height, gpu::Texture::SINGLE_MIP, OVERLAY_SAMPLER));
auto colorBuffer = gpu::Texture::createRenderBuffer(COLOR_FORMAT, width, height, gpu::Texture::SINGLE_MIP, OVERLAY_SAMPLER);
_overlayFramebuffer->setRenderBuffer(0, colorBuffer);
}
}

View file

@ -31,6 +31,7 @@
#include "Menu.h"
#include "Util.h"
#include "SequenceNumberStats.h"
#include "StatTracker.h"
HIFI_QML_DEF(Stats)
@ -250,6 +251,9 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(downloads, loadingRequests.size());
STAT_UPDATE(downloadLimit, ResourceCache::getRequestLimit())
STAT_UPDATE(downloadsPending, ResourceCache::getPendingRequestCount());
STAT_UPDATE(processing, DependencyManager::get<StatTracker>()->getStat("Processing").toInt());
STAT_UPDATE(processingPending, DependencyManager::get<StatTracker>()->getStat("PendingProcessing").toInt());
// See if the active download urls have changed
bool shouldUpdateUrls = _downloads != _downloadUrls.size();

View file

@ -89,6 +89,8 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, downloadLimit, 0)
STATS_PROPERTY(int, downloadsPending, 0)
Q_PROPERTY(QStringList downloadUrls READ downloadUrls NOTIFY downloadUrlsChanged)
STATS_PROPERTY(int, processing, 0)
STATS_PROPERTY(int, processingPending, 0)
STATS_PROPERTY(int, triangles, 0)
STATS_PROPERTY(int, quads, 0)
STATS_PROPERTY(int, materialSwitches, 0)
@ -214,6 +216,8 @@ signals:
void downloadLimitChanged();
void downloadsPendingChanged();
void downloadUrlsChanged();
void processingChanged();
void processingPendingChanged();
void trianglesChanged();
void quadsChanged();
void materialSwitchesChanged();

View file

@ -298,7 +298,7 @@ void Web3DOverlay::render(RenderArgs* args) {
if (!_texture) {
auto webSurface = _webSurface;
_texture = gpu::TexturePointer(gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()));
_texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
_texture->setSource(__FUNCTION__);
}
OffscreenQmlSurface::TextureAndFence newTextureAndFence;

View file

@ -1006,8 +1006,7 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) {
_timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE;
}
emit inputReceived({ audioBuffer.data(), numSamples });
emit inputReceivedBuffer(audioBuffer); // FIXME: Almost a duplicate of inputReceived().
emit inputReceived(audioBuffer);
if (_noiseGate.openedInLastBlock()) {
emit noiseGateOpened();

View file

@ -354,12 +354,11 @@ void OpenGLDisplayPlugin::customizeContext() {
}
if ((image.width() > 0) && (image.height() > 0)) {
cursorData.texture.reset(
gpu::Texture::createStrict(
cursorData.texture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
cursorData.texture->setSource("cursor texture");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
cursorData.texture->setUsage(usage.build());

View file

@ -295,12 +295,11 @@ void HmdDisplayPlugin::internalPresent() {
image = image.mirrored();
image = image.convertToFormat(QImage::Format_RGBA8888);
if (!_previewTexture) {
_previewTexture.reset(
gpu::Texture::createStrict(
_previewTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_previewTexture->setSource("HMD Preview Texture");
_previewTexture->setUsage(gpu::Texture::Usage::Builder().withColor().build());
_previewTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));

View file

@ -1,7 +1,7 @@
set(TARGET_NAME entities-renderer)
AUTOSCRIBE_SHADER_LIB(gpu model procedural render render-utils)
setup_hifi_library(Widgets Network Script)
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils)
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils image)
target_bullet()

View file

@ -495,7 +495,7 @@ bool EntityTreeRenderer::applySkyboxAndHasAmbient() {
bool isAmbientSet = false;
if (_pendingAmbientTexture && !_ambientTexture) {
_ambientTexture = textureCache->getTexture(_ambientTextureURL, NetworkTexture::CUBE_TEXTURE);
_ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE);
}
if (_ambientTexture && _ambientTexture->isLoaded()) {
_pendingAmbientTexture = false;
@ -512,7 +512,7 @@ bool EntityTreeRenderer::applySkyboxAndHasAmbient() {
if (_pendingSkyboxTexture &&
(!_skyboxTexture || (_skyboxTexture->getURL() != _skyboxTextureURL))) {
_skyboxTexture = textureCache->getTexture(_skyboxTextureURL, NetworkTexture::CUBE_TEXTURE);
_skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::CUBE_TEXTURE);
}
if (_skyboxTexture && _skyboxTexture->isLoaded()) {
_pendingSkyboxTexture = false;

View file

@ -216,7 +216,7 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
if (!_texture) {
auto webSurface = _webSurface;
_texture = gpu::TexturePointer(gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()));
_texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
_texture->setSource(__FUNCTION__);
}
OffscreenQmlSurface::TextureAndFence newTextureAndFence;

View file

@ -1,332 +0,0 @@
//
// EntityActionInterface.cpp
// libraries/entities/src
//
// Created by Seth Alves on 2015-6-4
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*
+-----------------------+ +-------------------+ +------------------------------+
| | | | | |
| EntityActionInterface | | btActionInterface | | EntityActionFactoryInterface |
| (entities) | | (bullet) | | (entities) |
+-----------------------+ +-------------------+ +------------------------------+
| | | | |
+----+ +--+ +----------+ | |
| | | | |
+-------------------+ +--------------+ +------------------------+ +-------------------------+
| | | | | | | |
| AssignmentAction | | ObjectAction | | InterfaceActionFactory | | AssignmentActionFactory |
|(assignment client)| | (physics) | | (interface) | | (assignment client) |
+-------------------+ +--------------+ +------------------------+ +-------------------------+
|
|
|
+--------------------+
| |
| ObjectActionSpring |
| (physics) |
+--------------------+
An action is a callback which is registered with bullet. An action is called-back every physics
simulation step and can do whatever it wants with the various datastructures it has available. An
action, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that
point by a spring.
In this system, an action is a property of an EntityItem (rather, an EntityItem has a property which
encodes a list of actions). Each action has a type and some arguments. Actions can be created by a
script or when receiving information via an EntityTree data-stream (either over the network or from an
svo file).
In the interface, if an EntityItem has actions, this EntityItem will have pointers to ObjectAction
subclass (like ObjectActionSpring) instantiations. Code in the entities library affects an action-object
via the EntityActionInterface (which knows nothing about bullet). When the ObjectAction subclass
instance is created, it is registered as an action with bullet. Bullet will call into code in this
instance with the btActionInterface every physics-simulation step.
Because the action can exist next to the interface's EntityTree or the entity-server's EntityTree,
parallel versions of the factories and actions are needed.
In an entity-server, any type of action is instantiated as an AssignmentAction. This action isn't called
by bullet (which isn't part of an assignment-client). It does nothing but remember its type and its
arguments. This may change as we try to make the entity-server's simple physics simulation better, but
right now the AssignmentAction class is a place-holder.
The action-objects are instantiated by singleton (dependecy) subclasses of EntityActionFactoryInterface.
In the interface, the subclass is an InterfaceActionFactory and it will produce things like
ObjectActionSpring. In an entity-server the subclass is an AssignmentActionFactory and it always
produces AssignmentActions.
Depending on the action's type, it will have various arguments. When a script changes an argument of an
action, the argument-holding member-variables of ObjectActionSpring (in this example) are updated and
also serialized into _actionData in the EntityItem. Each subclass of ObjectAction knows how to serialize
and deserialize its own arguments. _actionData is what gets sent over the wire or saved in an svo file.
When a packet-reader receives data for _actionData, it will save it in the EntityItem; this causes the
deserializer in the ObjectAction subclass to be called with the new data, thereby updating its argument
variables. These argument variables are used by the code which is run when bullet does a callback.
*/
#include "EntityItem.h"
#include "EntityActionInterface.h"
EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeString) {
QString normalizedActionTypeString = actionTypeString.toLower().remove('-').remove('_');
if (normalizedActionTypeString == "none") {
return ACTION_TYPE_NONE;
}
if (normalizedActionTypeString == "offset") {
return ACTION_TYPE_OFFSET;
}
if (normalizedActionTypeString == "spring") {
return ACTION_TYPE_SPRING;
}
if (normalizedActionTypeString == "hold") {
return ACTION_TYPE_HOLD;
}
if (normalizedActionTypeString == "traveloriented") {
return ACTION_TYPE_TRAVEL_ORIENTED;
}
qCDebug(entities) << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString;
return ACTION_TYPE_NONE;
}
QString EntityActionInterface::actionTypeToString(EntityActionType actionType) {
switch(actionType) {
case ACTION_TYPE_NONE:
return "none";
case ACTION_TYPE_OFFSET:
return "offset";
case ACTION_TYPE_SPRING:
return "spring";
case ACTION_TYPE_HOLD:
return "hold";
case ACTION_TYPE_TRAVEL_ORIENTED:
return "travel-oriented";
}
assert(false);
return "none";
}
glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return glm::vec3(0.0f);
}
QVariant resultV = arguments[argumentName];
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map";
ok = false;
return glm::vec3(0.0f);
}
QVariantMap resultVM = resultV.toMap();
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z";
ok = false;
return glm::vec3(0.0f);
}
QVariant xV = resultVM["x"];
QVariant yV = resultVM["y"];
QVariant zV = resultVM["z"];
bool xOk = true;
bool yOk = true;
bool zOk = true;
float x = xV.toFloat(&xOk);
float y = yV.toFloat(&yOk);
float z = zV.toFloat(&zOk);
if (!xOk || !yOk || !zOk) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, and z of type float.";
ok = false;
return glm::vec3(0.0f);
}
if (x != x || y != y || z != z) {
// at least one of the values is NaN
ok = false;
return glm::vec3(0.0f);
}
return glm::vec3(x, y, z);
}
glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return glm::quat();
}
QVariant resultV = arguments[argumentName];
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map, not" << resultV.typeName();
ok = false;
return glm::quat();
}
QVariantMap resultVM = resultV.toMap();
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z") || !resultVM.contains("w")) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z, and w";
ok = false;
return glm::quat();
}
QVariant xV = resultVM["x"];
QVariant yV = resultVM["y"];
QVariant zV = resultVM["z"];
QVariant wV = resultVM["w"];
bool xOk = true;
bool yOk = true;
bool zOk = true;
bool wOk = true;
float x = xV.toFloat(&xOk);
float y = yV.toFloat(&yOk);
float z = zV.toFloat(&zOk);
float w = wV.toFloat(&wOk);
if (!xOk || !yOk || !zOk || !wOk) {
qCDebug(entities) << objectName << "argument" << argumentName
<< "must be a map with keys: x, y, z, and w of type float.";
ok = false;
return glm::quat();
}
if (x != x || y != y || z != z || w != w) {
// at least one of the components is NaN!
ok = false;
return glm::quat();
}
return glm::normalize(glm::quat(w, x, y, z));
}
float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return 0.0f;
}
QVariant variant = arguments[argumentName];
bool variantOk = true;
float value = variant.toFloat(&variantOk);
if (!variantOk || std::isnan(value)) {
ok = false;
return 0.0f;
}
return value;
}
int EntityActionInterface::extractIntegerArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return 0.0f;
}
QVariant variant = arguments[argumentName];
bool variantOk = true;
int value = variant.toInt(&variantOk);
if (!variantOk) {
ok = false;
return 0;
}
return value;
}
QString EntityActionInterface::extractStringArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return "";
}
return arguments[argumentName].toString();
}
bool EntityActionInterface::extractBooleanArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return false;
}
return arguments[argumentName].toBool();
}
QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType)
{
return stream << (quint16)entityActionType;
}
QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType)
{
quint16 actionTypeAsInt;
stream >> actionTypeAsInt;
entityActionType = (EntityActionType)actionTypeAsInt;
return stream;
}
QString serializedActionsToDebugString(QByteArray data) {
if (data.size() == 0) {
return QString();
}
QVector<QByteArray> serializedActions;
QDataStream serializedActionsStream(data);
serializedActionsStream >> serializedActions;
QString result;
foreach(QByteArray serializedAction, serializedActions) {
QDataStream serializedActionStream(serializedAction);
EntityActionType actionType;
QUuid actionID;
serializedActionStream >> actionType;
serializedActionStream >> actionID;
result += EntityActionInterface::actionTypeToString(actionType) + "-" + actionID.toString() + " ";
}
return result;
}

View file

@ -1,5 +1,5 @@
//
// EntityActionFactoryInterface.cpp
// EntityDynamicFactoryInterface.cpp
// libraries/entities/src
//
// Created by Seth Alves on 2015-6-2
@ -9,26 +9,26 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_EntityActionFactoryInterface_h
#define hifi_EntityActionFactoryInterface_h
#ifndef hifi_EntityDynamicFactoryInterface_h
#define hifi_EntityDynamicFactoryInterface_h
#include <DependencyManager.h>
#include "EntityActionInterface.h"
#include "EntityDynamicInterface.h"
class EntityActionFactoryInterface : public QObject, public Dependency {
class EntityDynamicFactoryInterface : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
EntityActionFactoryInterface() { }
virtual ~EntityActionFactoryInterface() { }
virtual EntityActionPointer factory(EntityActionType type,
EntityDynamicFactoryInterface() { }
virtual ~EntityDynamicFactoryInterface() { }
virtual EntityDynamicPointer factory(EntityDynamicType type,
const QUuid& id,
EntityItemPointer ownerEntity,
QVariantMap arguments) { assert(false); return nullptr; }
virtual EntityActionPointer factoryBA(EntityItemPointer ownerEntity,
virtual EntityDynamicPointer factoryBA(EntityItemPointer ownerEntity,
QByteArray data) { assert(false); return nullptr; }
};
#endif // hifi_EntityActionFactoryInterface_h
#endif // hifi_EntityDynamicFactoryInterface_h

View file

@ -0,0 +1,347 @@
//
// EntityDynamicInterface.cpp
// libraries/entities/src
//
// Created by Seth Alves on 2015-6-4
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*
+-------------------------+ +--------------------------------+
| | | |
| EntityDynamicsInterface | | EntityDynamicsFactoryInterface |
| (entities) | | (entities) |
+-------------------------+ +--------------------------------+
| | | |
+----+ +--+ | |
| | | |
+---------------------+ +----------------+ +--------------------------+ +---------------------------+
| | | | | | | |
| AssignmentDynamics | | ObjectDynamics | | InterfaceDynamicsFactory | | AssignmentDynamicsFactory |
|(assignment client) | | (physics) | | (interface) | | (assignment client) |
+---------------------+ +----------------+ +--------------------------+ +---------------------------+
| |
| |
+---------------------+ | |
| | | |
| btActionInterface | | |
| (bullet) | | |
+---------------------+ | |
| | |
+--------------+ +------------------+ +-------------------+
| | | | | |
| ObjectAction | | ObjectConstraint | | btTypedConstraint |
| (physics) | | (physics) --|--->| (bullet) |
+--------------+ +------------------+ +-------------------+
| |
| |
+--------------------+ +-----------------------+
| | | |
| ObjectActionSpring | | ObjectConstraintHinge |
| (physics) | | (physics) |
+--------------------+ +-----------------------+
An dynamic is a callback which is registered with bullet. An dynamic is called-back every physics
simulation step and can do whatever it wants with the various datastructures it has available. An
dynamic, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that
point by a spring.
In this system, an dynamic is a property of an EntityItem (rather, an EntityItem has a property which
encodes a list of dynamics). Each dynamic has a type and some arguments. Dynamics can be created by a
script or when receiving information via an EntityTree data-stream (either over the network or from an
svo file).
In the interface, if an EntityItem has dynamics, this EntityItem will have pointers to ObjectDynamic
subclass (like ObjectDynamicSpring) instantiations. Code in the entities library affects an dynamic-object
via the EntityDynamicInterface (which knows nothing about bullet). When the ObjectDynamic subclass
instance is created, it is registered as an dynamic with bullet. Bullet will call into code in this
instance with the btDynamicInterface every physics-simulation step.
Because the dynamic can exist next to the interface's EntityTree or the entity-server's EntityTree,
parallel versions of the factories and dynamics are needed.
In an entity-server, any type of dynamic is instantiated as an AssignmentDynamic. This dynamic isn't called
by bullet (which isn't part of an assignment-client). It does nothing but remember its type and its
arguments. This may change as we try to make the entity-server's simple physics simulation better, but
right now the AssignmentDynamic class is a place-holder.
The dynamic-objects are instantiated by singleton (dependecy) subclasses of EntityDynamicFactoryInterface.
In the interface, the subclass is an InterfaceDynamicFactory and it will produce things like
ObjectDynamicSpring. In an entity-server the subclass is an AssignmentDynamicFactory and it always
produces AssignmentDynamics.
Depending on the dynamic's type, it will have various arguments. When a script changes an argument of an
dynamic, the argument-holding member-variables of ObjectDynamicSpring (in this example) are updated and
also serialized into _dynamicData in the EntityItem. Each subclass of ObjectDynamic knows how to serialize
and deserialize its own arguments. _dynamicData is what gets sent over the wire or saved in an svo file.
When a packet-reader receives data for _dynamicData, it will save it in the EntityItem; this causes the
deserializer in the ObjectDynamic subclass to be called with the new data, thereby updating its argument
variables. These argument variables are used by the code which is run when bullet does a callback.
*/
#include "EntityItem.h"
#include "EntityDynamicInterface.h"
EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicTypeString) {
QString normalizedDynamicTypeString = dynamicTypeString.toLower().remove('-').remove('_');
if (normalizedDynamicTypeString == "none") {
return DYNAMIC_TYPE_NONE;
}
if (normalizedDynamicTypeString == "offset") {
return DYNAMIC_TYPE_OFFSET;
}
if (normalizedDynamicTypeString == "spring") {
return DYNAMIC_TYPE_SPRING;
}
if (normalizedDynamicTypeString == "hold") {
return DYNAMIC_TYPE_HOLD;
}
if (normalizedDynamicTypeString == "traveloriented") {
return DYNAMIC_TYPE_TRAVEL_ORIENTED;
}
if (normalizedDynamicTypeString == "hinge") {
return DYNAMIC_TYPE_HINGE;
}
if (normalizedDynamicTypeString == "fargrab") {
return DYNAMIC_TYPE_FAR_GRAB;
}
qCDebug(entities) << "Warning -- EntityDynamicInterface::dynamicTypeFromString got unknown dynamic-type name"
<< dynamicTypeString;
return DYNAMIC_TYPE_NONE;
}
QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicType) {
switch(dynamicType) {
case DYNAMIC_TYPE_NONE:
return "none";
case DYNAMIC_TYPE_OFFSET:
return "offset";
case DYNAMIC_TYPE_SPRING:
return "spring";
case DYNAMIC_TYPE_HOLD:
return "hold";
case DYNAMIC_TYPE_TRAVEL_ORIENTED:
return "travel-oriented";
case DYNAMIC_TYPE_HINGE:
return "hinge";
case DYNAMIC_TYPE_FAR_GRAB:
return "far-grab";
}
assert(false);
return "none";
}
glm::vec3 EntityDynamicInterface::extractVec3Argument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return glm::vec3(0.0f);
}
QVariant resultV = arguments[argumentName];
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map";
ok = false;
return glm::vec3(0.0f);
}
QVariantMap resultVM = resultV.toMap();
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z";
ok = false;
return glm::vec3(0.0f);
}
QVariant xV = resultVM["x"];
QVariant yV = resultVM["y"];
QVariant zV = resultVM["z"];
bool xOk = true;
bool yOk = true;
bool zOk = true;
float x = xV.toFloat(&xOk);
float y = yV.toFloat(&yOk);
float z = zV.toFloat(&zOk);
if (!xOk || !yOk || !zOk) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, and z of type float.";
ok = false;
return glm::vec3(0.0f);
}
if (x != x || y != y || z != z) {
// at least one of the values is NaN
ok = false;
return glm::vec3(0.0f);
}
return glm::vec3(x, y, z);
}
glm::quat EntityDynamicInterface::extractQuatArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return glm::quat();
}
QVariant resultV = arguments[argumentName];
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map, not" << resultV.typeName();
ok = false;
return glm::quat();
}
QVariantMap resultVM = resultV.toMap();
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z") || !resultVM.contains("w")) {
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z, and w";
ok = false;
return glm::quat();
}
QVariant xV = resultVM["x"];
QVariant yV = resultVM["y"];
QVariant zV = resultVM["z"];
QVariant wV = resultVM["w"];
bool xOk = true;
bool yOk = true;
bool zOk = true;
bool wOk = true;
float x = xV.toFloat(&xOk);
float y = yV.toFloat(&yOk);
float z = zV.toFloat(&zOk);
float w = wV.toFloat(&wOk);
if (!xOk || !yOk || !zOk || !wOk) {
qCDebug(entities) << objectName << "argument" << argumentName
<< "must be a map with keys: x, y, z, and w of type float.";
ok = false;
return glm::quat();
}
if (x != x || y != y || z != z || w != w) {
// at least one of the components is NaN!
ok = false;
return glm::quat();
}
return glm::normalize(glm::quat(w, x, y, z));
}
float EntityDynamicInterface::extractFloatArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return 0.0f;
}
QVariant variant = arguments[argumentName];
bool variantOk = true;
float value = variant.toFloat(&variantOk);
if (!variantOk || std::isnan(value)) {
ok = false;
return 0.0f;
}
return value;
}
int EntityDynamicInterface::extractIntegerArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return 0.0f;
}
QVariant variant = arguments[argumentName];
bool variantOk = true;
int value = variant.toInt(&variantOk);
if (!variantOk) {
ok = false;
return 0;
}
return value;
}
QString EntityDynamicInterface::extractStringArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return "";
}
return arguments[argumentName].toString();
}
bool EntityDynamicInterface::extractBooleanArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qCDebug(entities) << objectName << "requires argument:" << argumentName;
}
ok = false;
return false;
}
return arguments[argumentName].toBool();
}
QDataStream& operator<<(QDataStream& stream, const EntityDynamicType& entityDynamicType) {
return stream << (quint16)entityDynamicType;
}
QDataStream& operator>>(QDataStream& stream, EntityDynamicType& entityDynamicType) {
quint16 dynamicTypeAsInt;
stream >> dynamicTypeAsInt;
entityDynamicType = (EntityDynamicType)dynamicTypeAsInt;
return stream;
}
QString serializedDynamicsToDebugString(QByteArray data) {
if (data.size() == 0) {
return QString();
}
QVector<QByteArray> serializedDynamics;
QDataStream serializedDynamicsStream(data);
serializedDynamicsStream >> serializedDynamics;
QString result;
foreach(QByteArray serializedDynamic, serializedDynamics) {
QDataStream serializedDynamicStream(serializedDynamic);
EntityDynamicType dynamicType;
QUuid dynamicID;
serializedDynamicStream >> dynamicType;
serializedDynamicStream >> dynamicID;
result += EntityDynamicInterface::dynamicTypeToString(dynamicType) + "-" + dynamicID.toString() + " ";
}
return result;
}

View file

@ -1,5 +1,5 @@
//
// EntityActionInterface.h
// EntityDynamicInterface.h
// libraries/entities/src
//
// Created by Seth Alves on 2015-6-2
@ -9,8 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_EntityActionInterface_h
#define hifi_EntityActionInterface_h
#ifndef hifi_EntityDynamicInterface_h
#define hifi_EntityDynamicInterface_h
#include <memory>
#include <QUuid>
@ -23,22 +23,27 @@ using EntityItemWeakPointer = std::weak_ptr<EntityItem>;
class EntitySimulation;
using EntitySimulationPointer = std::shared_ptr<EntitySimulation>;
enum EntityActionType {
// keep these synchronized with actionTypeFromString and actionTypeToString
ACTION_TYPE_NONE = 0,
ACTION_TYPE_OFFSET = 1000,
ACTION_TYPE_SPRING = 2000,
ACTION_TYPE_HOLD = 3000,
ACTION_TYPE_TRAVEL_ORIENTED = 4000
enum EntityDynamicType {
// keep these synchronized with dynamicTypeFromString and dynamicTypeToString
DYNAMIC_TYPE_NONE = 0,
DYNAMIC_TYPE_OFFSET = 1000,
DYNAMIC_TYPE_SPRING = 2000,
DYNAMIC_TYPE_HOLD = 3000,
DYNAMIC_TYPE_TRAVEL_ORIENTED = 4000,
DYNAMIC_TYPE_HINGE = 5000,
DYNAMIC_TYPE_FAR_GRAB = 6000
};
class EntityActionInterface {
class EntityDynamicInterface {
public:
EntityActionInterface(EntityActionType type, const QUuid& id) : _id(id), _type(type) { }
virtual ~EntityActionInterface() { }
EntityDynamicInterface(EntityDynamicType type, const QUuid& id) : _id(id), _type(type) { }
virtual ~EntityDynamicInterface() { }
const QUuid& getID() const { return _id; }
EntityActionType getType() const { return _type; }
EntityDynamicType getType() const { return _type; }
virtual bool isAction() const { return false; }
virtual bool isConstraint() const { return false; }
virtual bool isReadyForAdd() const { return true; }
bool isActive() { return _active; }
@ -51,8 +56,8 @@ public:
virtual QByteArray serialize() const = 0;
virtual void deserialize(QByteArray serializedArguments) = 0;
static EntityActionType actionTypeFromString(QString actionTypeString);
static QString actionTypeToString(EntityActionType actionType);
static EntityDynamicType dynamicTypeFromString(QString dynamicTypeString);
static QString dynamicTypeToString(EntityDynamicType dynamicType);
virtual bool lifetimeIsOver() { return false; }
virtual quint64 getExpires() { return 0; }
@ -82,26 +87,26 @@ public:
protected:
virtual glm::vec3 getPosition() = 0;
virtual void setPosition(glm::vec3 position) = 0;
// virtual void setPosition(glm::vec3 position) = 0;
virtual glm::quat getRotation() = 0;
virtual void setRotation(glm::quat rotation) = 0;
// virtual void setRotation(glm::quat rotation) = 0;
virtual glm::vec3 getLinearVelocity() = 0;
virtual void setLinearVelocity(glm::vec3 linearVelocity) = 0;
virtual glm::vec3 getAngularVelocity() = 0;
virtual void setAngularVelocity(glm::vec3 angularVelocity) = 0;
QUuid _id;
EntityActionType _type;
EntityDynamicType _type;
bool _active { false };
bool _isMine { false }; // did this interface create / edit this action?
bool _isMine { false }; // did this interface create / edit this dynamic?
};
typedef std::shared_ptr<EntityActionInterface> EntityActionPointer;
typedef std::shared_ptr<EntityDynamicInterface> EntityDynamicPointer;
QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType);
QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType);
QDataStream& operator<<(QDataStream& stream, const EntityDynamicType& entityDynamicType);
QDataStream& operator>>(QDataStream& stream, EntityDynamicType& entityDynamicType);
QString serializedActionsToDebugString(QByteArray data);
QString serializedDynamicsToDebugString(QByteArray data);
#endif // hifi_EntityActionInterface_h
#endif // hifi_EntityDynamicInterface_h

View file

@ -30,7 +30,7 @@
#include "EntitiesLogging.h"
#include "EntityTree.h"
#include "EntitySimulation.h"
#include "EntityActionFactoryInterface.h"
#include "EntityDynamicFactoryInterface.h"
int EntityItem::_maxActionsDataSize = 800;
@ -280,7 +280,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL());
APPEND_ENTITY_PROPERTY(PROP_HREF, getHref());
APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription());
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getActionData());
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getDynamicData());
// convert AVATAR_SELF_ID to actual sessionUUID.
QUuid actualParentID = getParentID();
@ -821,7 +821,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL);
READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref);
READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription);
READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setActionData);
READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setDynamicData);
{ // parentID and parentJointIndex are also protected by simulation ownership
bool oldOverwrite = overwriteLocalData;
@ -1251,7 +1251,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getActionData);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getDynamicData);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentID, getParentID);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentJointIndex, getParentJointIndex);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube);
@ -1358,7 +1358,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setActionData);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setDynamicData);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, updateParentID);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube);
@ -1872,10 +1872,20 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
iAmHoldingThis = true;
}
// also, don't bootstrap our own avatar with a hold action
QList<EntityActionPointer> holdActions = getActionsOfType(ACTION_TYPE_HOLD);
QList<EntityActionPointer>::const_iterator i = holdActions.begin();
QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD);
QList<EntityDynamicPointer>::const_iterator i = holdActions.begin();
while (i != holdActions.end()) {
EntityActionPointer action = *i;
EntityDynamicPointer action = *i;
if (action->isMine()) {
iAmHoldingThis = true;
break;
}
i++;
}
QList<EntityDynamicPointer> farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB);
i = farGrabActions.begin();
while (i != farGrabActions.end()) {
EntityDynamicPointer action = *i;
if (action->isMine()) {
iAmHoldingThis = true;
break;
@ -1941,18 +1951,18 @@ void EntityItem::rememberHasSimulationOwnershipBid() const {
QString EntityItem::actionsToDebugString() {
QString result;
QVector<QByteArray> serializedActions;
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
const QUuid id = i.key();
EntityActionPointer action = _objectActions[id];
EntityActionType actionType = action->getType();
EntityDynamicPointer action = _objectActions[id];
EntityDynamicType actionType = action->getType();
result += QString("") + actionType + ":" + action->getID().toString() + " ";
i++;
}
return result;
}
bool EntityItem::addAction(EntitySimulationPointer simulation, EntityActionPointer action) {
bool EntityItem::addAction(EntitySimulationPointer simulation, EntityDynamicPointer action) {
bool result;
withWriteLock([&] {
checkWaitingToRemove(simulation);
@ -1960,7 +1970,7 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityActionPoint
result = addActionInternal(simulation, action);
if (result) {
action->setIsMine(true);
_actionDataDirty = true;
_dynamicDataDirty = true;
} else {
removeActionInternal(action->getID());
}
@ -1969,7 +1979,7 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityActionPoint
return result;
}
bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityActionPointer action) {
bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action) {
assert(action);
assert(simulation);
auto actionOwnerEntity = action->getOwnerEntity().lock();
@ -1979,7 +1989,7 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityAct
const QUuid& actionID = action->getID();
assert(!_objectActions.contains(actionID) || _objectActions[actionID] == action);
_objectActions[actionID] = action;
simulation->addAction(action);
simulation->addDynamic(action);
bool success;
QByteArray newDataCache;
@ -2003,14 +2013,13 @@ bool EntityItem::updateAction(EntitySimulationPointer simulation, const QUuid& a
return;
}
EntityActionPointer action = _objectActions[actionID];
EntityDynamicPointer action = _objectActions[actionID];
success = action->updateArguments(arguments);
if (success) {
action->setIsMine(true);
serializeActions(success, _allActionsDataCache);
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
} else {
qCDebug(entities) << "EntityItem::updateAction failed";
}
@ -2035,7 +2044,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
simulation = entityTree ? entityTree->getSimulation() : nullptr;
}
EntityActionPointer action = _objectActions[actionID];
EntityDynamicPointer action = _objectActions[actionID];
action->setOwnerEntity(nullptr);
action->setIsMine(false);
@ -2049,7 +2058,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
serializeActions(success, _allActionsDataCache);
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
setActionDataNeedsTransmit(true);
setDynamicDataNeedsTransmit(true);
return success;
}
return false;
@ -2057,10 +2066,10 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
bool EntityItem::clearActions(EntitySimulationPointer simulation) {
withWriteLock([&] {
QHash<QUuid, EntityActionPointer>::iterator i = _objectActions.begin();
QHash<QUuid, EntityDynamicPointer>::iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
const QUuid id = i.key();
EntityActionPointer action = _objectActions[id];
EntityDynamicPointer action = _objectActions[id];
i = _objectActions.erase(i);
action->setOwnerEntity(nullptr);
action->removeFromSimulation(simulation);
@ -2101,12 +2110,12 @@ void EntityItem::deserializeActionsInternal() {
serializedActionsStream >> serializedActions;
}
// Keep track of which actions got added or updated by the new actionData
// Keep track of which actions got added or updated by the new dynamicData
QSet<QUuid> updated;
foreach(QByteArray serializedAction, serializedActions) {
QDataStream serializedActionStream(serializedAction);
EntityActionType actionType;
EntityDynamicType actionType;
QUuid actionID;
serializedActionStream >> actionType;
serializedActionStream >> actionID;
@ -2115,7 +2124,7 @@ void EntityItem::deserializeActionsInternal() {
}
if (_objectActions.contains(actionID)) {
EntityActionPointer action = _objectActions[actionID];
EntityDynamicPointer action = _objectActions[actionID];
// TODO: make sure types match? there isn't currently a way to
// change the type of an existing action.
if (!action->isMine()) {
@ -2123,9 +2132,9 @@ void EntityItem::deserializeActionsInternal() {
}
updated << actionID;
} else {
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
auto actionFactory = DependencyManager::get<EntityDynamicFactoryInterface>();
EntityItemPointer entity = getThisPointer();
EntityActionPointer action = actionFactory->factoryBA(entity, serializedAction);
EntityDynamicPointer action = actionFactory->factoryBA(entity, serializedAction);
if (action) {
entity->addActionInternal(simulation, action);
updated << actionID;
@ -2140,15 +2149,15 @@ void EntityItem::deserializeActionsInternal() {
}
// remove any actions that weren't included in the new data.
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
QUuid id = i.key();
if (!updated.contains(id)) {
EntityActionPointer action = i.value();
EntityDynamicPointer action = i.value();
if (action->isMine()) {
// we just received an update that didn't include one of our actions. tell the server about it (again).
setActionDataNeedsTransmit(true);
setDynamicDataNeedsTransmit(true);
} else {
// don't let someone else delete my action.
_actionsToRemove << id;
@ -2167,7 +2176,7 @@ void EntityItem::deserializeActionsInternal() {
}
}
_actionDataDirty = true;
_dynamicDataDirty = true;
return;
}
@ -2179,15 +2188,15 @@ void EntityItem::checkWaitingToRemove(EntitySimulationPointer simulation) {
_actionsToRemove.clear();
}
void EntityItem::setActionData(QByteArray actionData) {
void EntityItem::setDynamicData(QByteArray dynamicData) {
withWriteLock([&] {
setActionDataInternal(actionData);
setDynamicDataInternal(dynamicData);
});
}
void EntityItem::setActionDataInternal(QByteArray actionData) {
if (_allActionsDataCache != actionData) {
_allActionsDataCache = actionData;
void EntityItem::setDynamicDataInternal(QByteArray dynamicData) {
if (_allActionsDataCache != dynamicData) {
_allActionsDataCache = dynamicData;
deserializeActionsInternal();
}
checkWaitingToRemove();
@ -2201,10 +2210,10 @@ void EntityItem::serializeActions(bool& success, QByteArray& result) const {
}
QVector<QByteArray> serializedActions;
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
const QUuid id = i.key();
EntityActionPointer action = _objectActions[id];
EntityDynamicPointer action = _objectActions[id];
QByteArray bytesForAction = action->serialize();
serializedActions << bytesForAction;
i++;
@ -2224,23 +2233,23 @@ void EntityItem::serializeActions(bool& success, QByteArray& result) const {
return;
}
const QByteArray EntityItem::getActionDataInternal() const {
if (_actionDataDirty) {
const QByteArray EntityItem::getDynamicDataInternal() const {
if (_dynamicDataDirty) {
bool success;
serializeActions(success, _allActionsDataCache);
if (success) {
_actionDataDirty = false;
_dynamicDataDirty = false;
}
}
return _allActionsDataCache;
}
const QByteArray EntityItem::getActionData() const {
const QByteArray EntityItem::getDynamicData() const {
QByteArray result;
if (_actionDataDirty) {
if (_dynamicDataDirty) {
withWriteLock([&] {
getActionDataInternal();
getDynamicDataInternal();
result = _allActionsDataCache;
});
} else {
@ -2255,9 +2264,9 @@ QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const {
QVariantMap result;
withReadLock([&] {
if (_objectActions.contains(actionID)) {
EntityActionPointer action = _objectActions[actionID];
EntityDynamicPointer action = _objectActions[actionID];
result = action->getArguments();
result["type"] = EntityActionInterface::actionTypeToString(action->getType());
result["type"] = EntityDynamicInterface::dynamicTypeToString(action->getType());
}
});
@ -2265,7 +2274,7 @@ QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const {
}
bool EntityItem::shouldSuppressLocationEdits() const {
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
if (i.value()->shouldSuppressLocationEdits()) {
return true;
@ -2276,12 +2285,12 @@ bool EntityItem::shouldSuppressLocationEdits() const {
return false;
}
QList<EntityActionPointer> EntityItem::getActionsOfType(EntityActionType typeToGet) const {
QList<EntityActionPointer> result;
QList<EntityDynamicPointer> EntityItem::getActionsOfType(EntityDynamicType typeToGet) const {
QList<EntityDynamicPointer> result;
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
EntityActionPointer action = i.value();
EntityDynamicPointer action = i.value();
if (action->getType() == typeToGet && action->isActive()) {
result += action;
}

View file

@ -36,17 +36,17 @@
#include "EntityTypes.h"
#include "SimulationOwner.h"
#include "SimulationFlags.h"
#include "EntityActionInterface.h"
#include "EntityDynamicInterface.h"
class EntitySimulation;
class EntityTreeElement;
class EntityTreeElementExtraEncodeData;
class EntityActionInterface;
class EntityDynamicInterface;
class EntityItemProperties;
class EntityTree;
class btCollisionShape;
typedef std::shared_ptr<EntityTree> EntityTreePointer;
typedef std::shared_ptr<EntityActionInterface> EntityActionPointer;
typedef std::shared_ptr<EntityDynamicInterface> EntityDynamicPointer;
typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer;
using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr<EntityTreeElementExtraEncodeData>;
@ -398,22 +398,22 @@ public:
void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; }
QString actionsToDebugString();
bool addAction(EntitySimulationPointer simulation, EntityActionPointer action);
bool addAction(EntitySimulationPointer simulation, EntityDynamicPointer action);
bool updateAction(EntitySimulationPointer simulation, const QUuid& actionID, const QVariantMap& arguments);
bool removeAction(EntitySimulationPointer simulation, const QUuid& actionID);
bool clearActions(EntitySimulationPointer simulation);
void setActionData(QByteArray actionData);
const QByteArray getActionData() const;
void setDynamicData(QByteArray dynamicData);
const QByteArray getDynamicData() const;
bool hasActions() const { return !_objectActions.empty(); }
QList<QUuid> getActionIDs() const { return _objectActions.keys(); }
QVariantMap getActionArguments(const QUuid& actionID) const;
void deserializeActions();
void setActionDataDirty(bool value) const { _actionDataDirty = value; }
bool actionDataDirty() const { return _actionDataDirty; }
void setDynamicDataDirty(bool value) const { _dynamicDataDirty = value; }
bool dynamicDataDirty() const { return _dynamicDataDirty; }
void setActionDataNeedsTransmit(bool value) const { _actionDataNeedsTransmit = value; }
bool actionDataNeedsTransmit() const { return _actionDataNeedsTransmit; }
void setDynamicDataNeedsTransmit(bool value) const { _dynamicDataNeedsTransmit = value; }
bool dynamicDataNeedsTransmit() const { return _dynamicDataNeedsTransmit; }
bool shouldSuppressLocationEdits() const;
@ -421,7 +421,7 @@ public:
const QUuid& getSourceUUID() const { return _sourceUUID; }
bool matchesSourceUUID(const QUuid& sourceUUID) const { return _sourceUUID == sourceUUID; }
QList<EntityActionPointer> getActionsOfType(EntityActionType typeToGet) const;
QList<EntityDynamicPointer> getActionsOfType(EntityDynamicType typeToGet) const;
// these are in the frame of this object
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override { return glm::quat(); }
@ -479,8 +479,8 @@ protected:
void setSimulated(bool simulated) { _simulated = simulated; }
const QByteArray getActionDataInternal() const;
void setActionDataInternal(QByteArray actionData);
const QByteArray getDynamicDataInternal() const;
void setDynamicDataInternal(QByteArray dynamicData);
virtual void locationChanged(bool tellPhysics = true) override;
virtual void dimensionsChanged() override;
@ -570,22 +570,22 @@ protected:
void* _physicsInfo { nullptr }; // set by EntitySimulation
bool _simulated; // set by EntitySimulation
bool addActionInternal(EntitySimulationPointer simulation, EntityActionPointer action);
bool addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action);
bool removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation = nullptr);
void deserializeActionsInternal();
void serializeActions(bool& success, QByteArray& result) const;
QHash<QUuid, EntityActionPointer> _objectActions;
QHash<QUuid, EntityDynamicPointer> _objectActions;
static int _maxActionsDataSize;
mutable QByteArray _allActionsDataCache;
// when an entity-server starts up, EntityItem::setActionData is called before the entity-tree is
// when an entity-server starts up, EntityItem::setDynamicData is called before the entity-tree is
// ready. This means we can't find our EntityItemPointer or add the action to the simulation. These
// are used to keep track of and work around this situation.
void checkWaitingToRemove(EntitySimulationPointer simulation = nullptr);
mutable QSet<QUuid> _actionsToRemove;
mutable bool _actionDataDirty { false };
mutable bool _actionDataNeedsTransmit { false };
mutable bool _dynamicDataDirty { false };
mutable bool _dynamicDataNeedsTransmit { false };
// _previouslyDeletedActions is used to avoid an action being re-added due to server round-trip lag
static quint64 _rememberDeletedActionTime;
mutable QHash<QUuid, quint64> _previouslyDeletedActions;

View file

@ -24,8 +24,8 @@
#include <model-networking/MeshProxy.h>
#include "EntitiesLogging.h"
#include "EntityActionFactoryInterface.h"
#include "EntityActionInterface.h"
#include "EntityDynamicFactoryInterface.h"
#include "EntityDynamicInterface.h"
#include "EntitySimulation.h"
#include "EntityTree.h"
#include "LightEntityItem.h"
@ -1133,7 +1133,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
PROFILE_RANGE(script_entities, __FUNCTION__);
QUuid actionID = QUuid::createUuid();
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
auto actionFactory = DependencyManager::get<EntityDynamicFactoryInterface>();
bool success = false;
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
// create this action even if the entity doesn't have physics info. it will often be the
@ -1142,11 +1142,11 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
// if (!entity->getPhysicsInfo()) {
// return false;
// }
EntityActionType actionType = EntityActionInterface::actionTypeFromString(actionTypeString);
if (actionType == ACTION_TYPE_NONE) {
EntityDynamicType dynamicType = EntityDynamicInterface::dynamicTypeFromString(actionTypeString);
if (dynamicType == DYNAMIC_TYPE_NONE) {
return false;
}
EntityActionPointer action = actionFactory->factory(actionType, actionID, entity, arguments);
EntityDynamicPointer action = actionFactory->factory(dynamicType, actionID, entity, arguments);
if (!action) {
return false;
}

View file

@ -279,25 +279,25 @@ void EntitySimulation::moveSimpleKinematics(const quint64& now) {
}
}
void EntitySimulation::addAction(EntityActionPointer action) {
void EntitySimulation::addDynamic(EntityDynamicPointer dynamic) {
QMutexLocker lock(&_mutex);
_actionsToAdd += action;
_dynamicsToAdd += dynamic;
}
void EntitySimulation::removeAction(const QUuid actionID) {
void EntitySimulation::removeDynamic(const QUuid dynamicID) {
QMutexLocker lock(&_mutex);
_actionsToRemove += actionID;
_dynamicsToRemove += dynamicID;
}
void EntitySimulation::removeActions(QList<QUuid> actionIDsToRemove) {
void EntitySimulation::removeDynamics(QList<QUuid> dynamicIDsToRemove) {
QMutexLocker lock(&_mutex);
foreach(QUuid uuid, actionIDsToRemove) {
_actionsToRemove.insert(uuid);
foreach(QUuid uuid, dynamicIDsToRemove) {
_dynamicsToRemove.insert(uuid);
}
}
void EntitySimulation::applyActionChanges() {
void EntitySimulation::applyDynamicChanges() {
QMutexLocker lock(&_mutex);
_actionsToAdd.clear();
_actionsToRemove.clear();
_dynamicsToAdd.clear();
_dynamicsToRemove.clear();
}

View file

@ -18,7 +18,7 @@
#include <PerfStat.h>
#include "EntityActionInterface.h"
#include "EntityDynamicInterface.h"
#include "EntityItem.h"
#include "EntityTree.h"
@ -59,10 +59,10 @@ public:
// friend class EntityTree;
virtual void addAction(EntityActionPointer action);
virtual void removeAction(const QUuid actionID);
virtual void removeActions(QList<QUuid> actionIDsToRemove);
virtual void applyActionChanges();
virtual void addDynamic(EntityDynamicPointer dynamic);
virtual void removeDynamic(const QUuid dynamicID);
virtual void removeDynamics(QList<QUuid> dynamicIDsToRemove);
virtual void applyDynamicChanges();
/// \param entity pointer to EntityItem to be added
/// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked
@ -103,8 +103,8 @@ protected:
SetOfEntities _entitiesToSort; // entities moved by simulation (and might need resort in EntityTree)
SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion
QList<EntityActionPointer> _actionsToAdd;
QSet<QUuid> _actionsToRemove;
QList<EntityDynamicPointer> _dynamicsToAdd;
QSet<QUuid> _dynamicsToRemove;
protected:
SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete)

View file

@ -808,7 +808,7 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
int index = changedProperties.indexOf("actionData");
if (index >= 0) {
QByteArray value = properties.getActionData();
QString changeHint = serializedActionsToDebugString(value);
QString changeHint = serializedDynamicsToDebugString(value);
changedProperties[index] = QString("actionData:") + changeHint;
}
}

View file

@ -76,10 +76,6 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
}
break;
case gpu::COMPRESSED_R:
result = GL_COMPRESSED_RED_RGTC1;
break;
case gpu::R11G11B10:
// the type should be float
result = GL_R11F_G11F_B10F;
@ -149,12 +145,6 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
case gpu::SRGBA:
result = GL_SRGB8; // standard 2.2 gamma correction color
break;
case gpu::COMPRESSED_RGB:
result = GL_COMPRESSED_RGB;
break;
case gpu::COMPRESSED_SRGB:
result = GL_COMPRESSED_SRGB;
break;
default:
qCWarning(gpugllogging) << "Unknown combination of texel format";
}
@ -217,29 +207,22 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
case gpu::SRGBA:
result = GL_SRGB8_ALPHA8; // standard 2.2 gamma correction color
break;
case gpu::COMPRESSED_RGBA:
result = GL_COMPRESSED_RGBA;
break;
case gpu::COMPRESSED_SRGBA:
result = GL_COMPRESSED_SRGB_ALPHA;
break;
// FIXME: WE will want to support this later
/*
case gpu::COMPRESSED_BC3_RGBA:
result = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
case gpu::COMPRESSED_BC4_RED:
result = GL_COMPRESSED_RED_RGTC1;
break;
case gpu::COMPRESSED_BC3_SRGBA:
case gpu::COMPRESSED_BC1_SRGB:
result = GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
break;
case gpu::COMPRESSED_BC1_SRGBA:
result = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
break;
case gpu::COMPRESSED_BC3_SRGBA:
result = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
break;
case gpu::COMPRESSED_BC7_RGBA:
result = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
case gpu::COMPRESSED_BC5_XY:
result = GL_COMPRESSED_RG_RGTC2;
break;
case gpu::COMPRESSED_BC7_SRGBA:
result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
break;
*/
default:
qCWarning(gpugllogging) << "Unknown combination of texel format";
@ -269,10 +252,6 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
texel.internalFormat = GL_R8;
break;
case gpu::COMPRESSED_R:
texel.internalFormat = GL_COMPRESSED_RED_RGTC1;
break;
case gpu::DEPTH:
texel.format = GL_DEPTH_COMPONENT;
texel.internalFormat = GL_DEPTH_COMPONENT32;
@ -315,12 +294,6 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
case gpu::RGBA:
texel.internalFormat = GL_RGB8;
break;
case gpu::COMPRESSED_RGB:
texel.internalFormat = GL_COMPRESSED_RGB;
break;
case gpu::COMPRESSED_SRGB:
texel.internalFormat = GL_COMPRESSED_SRGB;
break;
default:
qCWarning(gpugllogging) << "Unknown combination of texel format";
}
@ -359,30 +332,22 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
texel.internalFormat = GL_SRGB8_ALPHA8;
break;
case gpu::COMPRESSED_RGBA:
texel.internalFormat = GL_COMPRESSED_RGBA;
break;
case gpu::COMPRESSED_SRGBA:
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA;
case gpu::COMPRESSED_BC4_RED:
texel.internalFormat = GL_COMPRESSED_RED_RGTC1;
break;
// FIXME: WE will want to support this later
/*
case gpu::COMPRESSED_BC3_RGBA:
texel.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
case gpu::COMPRESSED_BC1_SRGB:
texel.internalFormat = GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
break;
case gpu::COMPRESSED_BC3_SRGBA:
case gpu::COMPRESSED_BC1_SRGBA:
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
break;
case gpu::COMPRESSED_BC3_SRGBA:
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
break;
case gpu::COMPRESSED_BC7_RGBA:
texel.internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
case gpu::COMPRESSED_BC5_XY:
texel.internalFormat = GL_COMPRESSED_RG_RGTC2;
break;
case gpu::COMPRESSED_BC7_SRGBA:
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
break;
*/
default:
qCWarning(gpugllogging) << "Unknown combination of texel format";
@ -403,10 +368,6 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()];
switch (dstFormat.getSemantic()) {
case gpu::COMPRESSED_R: {
texel.internalFormat = GL_COMPRESSED_RED_RGTC1;
break;
}
case gpu::RED:
case gpu::RGB:
case gpu::RGBA:
@ -564,12 +525,6 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
case gpu::SRGBA:
texel.internalFormat = GL_SRGB8; // standard 2.2 gamma correction color
break;
case gpu::COMPRESSED_RGB:
texel.internalFormat = GL_COMPRESSED_RGB;
break;
case gpu::COMPRESSED_SRGB:
texel.internalFormat = GL_COMPRESSED_SRGB;
break;
default:
qCWarning(gpugllogging) << "Unknown combination of texel format";
}
@ -646,11 +601,21 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
case gpu::SRGBA:
texel.internalFormat = GL_SRGB8_ALPHA8; // standard 2.2 gamma correction color
break;
case gpu::COMPRESSED_RGBA:
texel.internalFormat = GL_COMPRESSED_RGBA;
case gpu::COMPRESSED_BC4_RED:
texel.internalFormat = GL_COMPRESSED_RED_RGTC1;
break;
case gpu::COMPRESSED_SRGBA:
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA;
case gpu::COMPRESSED_BC1_SRGB:
texel.internalFormat = GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
break;
case gpu::COMPRESSED_BC1_SRGBA:
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
break;
case gpu::COMPRESSED_BC3_SRGBA:
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
break;
case gpu::COMPRESSED_BC5_XY:
texel.internalFormat = GL_COMPRESSED_RG_RGTC2;
break;
default:
qCWarning(gpugllogging) << "Unknown combination of texel format";

View file

@ -120,11 +120,12 @@ void GLTexture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, u
}
auto size = _gpuObject.evalMipDimensions(sourceMip);
auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face);
auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face);
if (mipData) {
GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), _gpuObject.getStoredMipFormat());
copyMipFaceLinesFromTexture(targetMip, face, size, 0, texelFormat.format, texelFormat.type, mipData->readData());
copyMipFaceLinesFromTexture(targetMip, face, size, 0, texelFormat.internalFormat, texelFormat.format, texelFormat.type, mipSize, mipData->readData());
} else {
qCDebug(gpugllogging) << "Missing mipData level=" << sourceMip << " face=" << (int)face << " for texture " << _gpuObject.source().c_str();
qCDebug(gpugllogging) << "Missing mipData level=" << sourceMip << " face=" << (int)face << " for texture " << _gpuObject.source().c_str();
}
}
@ -203,9 +204,11 @@ TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t t
auto transferDimensions = _parent._gpuObject.evalMipDimensions(sourceMip);
GLenum format;
GLenum internalFormat;
GLenum type;
GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_parent._gpuObject.getTexelFormat(), _parent._gpuObject.getStoredMipFormat());
format = texelFormat.format;
internalFormat = texelFormat.internalFormat;
type = texelFormat.type;
auto mipSize = _parent._gpuObject.getStoredMipFaceSize(sourceMip, face);
@ -236,7 +239,7 @@ TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t t
Backend::updateTextureTransferPendingSize(0, _transferSize);
_transferLambda = [=] {
_parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, format, type, _buffer.data());
_parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, internalFormat, format, type, _buffer.size(), _buffer.data());
std::vector<uint8_t> emptyVector;
_buffer.swap(emptyVector);
};

View file

@ -163,7 +163,7 @@ public:
protected:
virtual Size size() const = 0;
virtual void generateMips() const = 0;
virtual void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const = 0;
virtual void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const = 0;
virtual void copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const final;
GLTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
@ -177,7 +177,7 @@ public:
protected:
GLExternalTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
void generateMips() const override {}
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const override {}
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override {}
Size size() const override { return 0; }
};

View file

@ -50,7 +50,7 @@ public:
protected:
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
void generateMips() const override;
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const override;
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override;
virtual void syncSampler() const;
void withPreservedTexture(std::function<void()> f) const;
@ -105,7 +105,7 @@ public:
void promote() override;
void demote() override;
void populateTransferQueue() override;
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const override;
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override;
Size size() const override { return _size; }
Size _size { 0 };

View file

@ -69,9 +69,7 @@ GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
GLuint GL41Texture::allocate(const Texture& texture) {
GLuint result;
// FIXME technically GL 4.2, but OSX includes the ARB_texture_storage extension
glCreateTextures(getGLTextureType(texture), 1, &result);
//glGenTextures(1, &result);
glGenTextures(1, &result);
return result;
}
@ -94,12 +92,37 @@ void GL41Texture::generateMips() const {
(void)CHECK_GL_ERROR();
}
void GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const {
void GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const {
if (GL_TEXTURE_2D == _target) {
glTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
switch (internalFormat) {
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_RG_RGTC2:
glCompressedTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, internalFormat,
static_cast<GLsizei>(sourceSize), sourcePointer);
break;
default:
glTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
break;
}
} else if (GL_TEXTURE_CUBE_MAP == _target) {
auto target = GLTexture::CUBE_FACE_LAYOUT[face];
glTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
switch (internalFormat) {
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_RG_RGTC2:
glCompressedTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, internalFormat,
static_cast<GLsizei>(sourceSize), sourcePointer);
break;
default:
glTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
break;
}
} else {
assert(false);
}
@ -253,9 +276,9 @@ void GL41VariableAllocationTexture::allocateStorage(uint16 allocatedMip) {
}
void GL41VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const {
void GL41VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const {
withPreservedTexture([&] {
Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, format, type, sourcePointer);
Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer);
});
}
@ -280,7 +303,7 @@ void GL41VariableAllocationTexture::promote() {
withPreservedTexture([&] {
GLuint fbo { 0 };
glCreateFramebuffers(1, &fbo);
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
uint16_t mips = _gpuObject.getNumMips();

View file

@ -48,7 +48,7 @@ public:
protected:
GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
void generateMips() const override;
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const override;
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override;
virtual void syncSampler() const;
};

View file

@ -117,17 +117,47 @@ void GL45Texture::generateMips() const {
(void)CHECK_GL_ERROR();
}
void GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const {
void GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const {
if (GL_TEXTURE_2D == _target) {
glTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
switch (internalFormat) {
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_RG_RGTC2:
glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat,
static_cast<GLsizei>(sourceSize), sourcePointer);
break;
default:
glTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
break;
}
} else if (GL_TEXTURE_CUBE_MAP == _target) {
// DSA ARB does not work on AMD, so use EXT
// unless EXT is not available on the driver
if (glTextureSubImage2DEXT) {
auto target = GLTexture::CUBE_FACE_LAYOUT[face];
glTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
} else {
glTextureSubImage3D(_id, mip, 0, yOffset, face, size.x, size.y, 1, format, type, sourcePointer);
switch (internalFormat) {
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_RG_RGTC2:
if (glCompressedTextureSubImage2DEXT) {
auto target = GLTexture::CUBE_FACE_LAYOUT[face];
glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat,
static_cast<GLsizei>(sourceSize), sourcePointer);
} else {
glCompressedTextureSubImage3D(_id, mip, 0, yOffset, face, size.x, size.y, 1, internalFormat,
static_cast<GLsizei>(sourceSize), sourcePointer);
}
break;
default:
// DSA ARB does not work on AMD, so use EXT
// unless EXT is not available on the driver
if (glTextureSubImage2DEXT) {
auto target = GLTexture::CUBE_FACE_LAYOUT[face];
glTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
} else {
glTextureSubImage3D(_id, mip, 0, yOffset, face, size.x, size.y, 1, format, type, sourcePointer);
}
break;
}
} else {
Q_ASSERT(false);

View file

@ -19,6 +19,12 @@ const Element Element::COLOR_SRGBA_32{ VEC4, NUINT8, SRGBA };
const Element Element::COLOR_BGRA_32{ VEC4, NUINT8, BGRA };
const Element Element::COLOR_SBGRA_32{ VEC4, NUINT8, SBGRA };
const Element Element::COLOR_COMPRESSED_RED{ VEC4, NUINT8, COMPRESSED_BC4_RED };
const Element Element::COLOR_COMPRESSED_SRGB{ VEC4, NUINT8, COMPRESSED_BC1_SRGB };
const Element Element::COLOR_COMPRESSED_SRGBA_MASK{ VEC4, NUINT8, COMPRESSED_BC1_SRGBA };
const Element Element::COLOR_COMPRESSED_SRGBA{ VEC4, NUINT8, COMPRESSED_BC3_SRGBA };
const Element Element::COLOR_COMPRESSED_XY{ VEC4, NUINT8, COMPRESSED_BC5_XY };
const Element Element::COLOR_R11G11B10{ SCALAR, FLOAT, R11G11B10 };
const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA };
const Element Element::VEC2F_UV{ VEC2, FLOAT, UV };

View file

@ -157,20 +157,12 @@ enum Semantic {
// These are generic compression format smeantic for images
_FIRST_COMPRESSED,
COMPRESSED_R,
COMPRESSED_RGB,
COMPRESSED_RGBA,
COMPRESSED_SRGB,
COMPRESSED_SRGBA,
// FIXME: Will have to be supported later:
/*COMPRESSED_BC3_RGBA, // RGBA_S3TC_DXT5_EXT,
COMPRESSED_BC3_SRGBA, // SRGB_ALPHA_S3TC_DXT5_EXT
COMPRESSED_BC7_RGBA,
COMPRESSED_BC7_SRGBA, */
COMPRESSED_BC1_SRGB,
COMPRESSED_BC1_SRGBA,
COMPRESSED_BC3_SRGBA,
COMPRESSED_BC4_RED,
COMPRESSED_BC5_XY,
_LAST_COMPRESSED,
@ -237,6 +229,11 @@ public:
static const Element COLOR_BGRA_32;
static const Element COLOR_SBGRA_32;
static const Element COLOR_R11G11B10;
static const Element COLOR_COMPRESSED_RED;
static const Element COLOR_COMPRESSED_SRGB;
static const Element COLOR_COMPRESSED_SRGBA_MASK;
static const Element COLOR_COMPRESSED_SRGBA;
static const Element COLOR_COMPRESSED_XY;
static const Element VEC4F_COLOR_RGBA;
static const Element VEC2F_UV;
static const Element VEC2F_XY;

View file

@ -212,8 +212,8 @@ void Texture::MemoryStorage::assignMipFaceData(uint16 level, uint8 face, const s
}
}
Texture* Texture::createExternal(const ExternalRecycler& recycler, const Sampler& sampler) {
Texture* tex = new Texture(TextureUsageType::EXTERNAL);
TexturePointer Texture::createExternal(const ExternalRecycler& recycler, const Sampler& sampler) {
TexturePointer tex = std::make_shared<Texture>(TextureUsageType::EXTERNAL);
tex->_type = TEX_2D;
tex->_maxMipLevel = 0;
tex->_sampler = sampler;
@ -221,36 +221,36 @@ Texture* Texture::createExternal(const ExternalRecycler& recycler, const Sampler
return tex;
}
Texture* Texture::createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) {
TexturePointer Texture::createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler);
}
Texture* Texture::create1D(const Element& texelFormat, uint16 width, uint16 numMips, const Sampler& sampler) {
TexturePointer Texture::create1D(const Element& texelFormat, uint16 width, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_1D, texelFormat, width, 1, 1, 1, 0, numMips, sampler);
}
Texture* Texture::create2D(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) {
TexturePointer Texture::create2D(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler);
}
Texture* Texture::createStrict(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) {
TexturePointer Texture::createStrict(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::STRICT_RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler);
}
Texture* Texture::create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numMips, const Sampler& sampler) {
TexturePointer Texture::create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_3D, texelFormat, width, height, depth, 1, 0, numMips, sampler);
}
Texture* Texture::createCube(const Element& texelFormat, uint16 width, uint16 numMips, const Sampler& sampler) {
TexturePointer Texture::createCube(const Element& texelFormat, uint16 width, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_CUBE, texelFormat, width, width, 1, 1, 0, numMips, sampler);
}
Texture* Texture::create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler)
TexturePointer Texture::create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler)
{
Texture* tex = new Texture(usageType);
TexturePointer tex = std::make_shared<Texture>(usageType);
tex->_storage.reset(new MemoryStorage());
tex->_type = type;
tex->_storage->assignTexture(tex);
tex->_storage->assignTexture(tex.get());
tex->resize(type, texelFormat, width, height, depth, numSamples, numSlices, numMips);
tex->_sampler = sampler;
@ -434,7 +434,7 @@ void Texture::assignStoredMip(uint16 level, storage::StoragePointer& storage) {
// THen check that the mem texture passed make sense with its format
Size expectedSize = evalStoredMipSize(level, getStoredMipFormat());
auto size = storage->size();
if (storage->size() == expectedSize) {
if (storage->size() <= expectedSize) {
_storage->assignMipData(level, storage);
_stamp++;
} else if (size > expectedSize) {
@ -461,7 +461,7 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin
// THen check that the mem texture passed make sense with its format
Size expectedSize = evalStoredMipFaceSize(level, getStoredMipFormat());
auto size = storage->size();
if (size == expectedSize) {
if (size <= expectedSize) {
_storage->assignMipFaceData(level, face, storage);
_stamp++;
} else if (size > expectedSize) {
@ -752,7 +752,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
boffset = 0;
}
auto data = cubeTexture.accessStoredMipFace(0,face)->readData();
auto data = cubeTexture.accessStoredMipFace(0, face)->readData();
if (data == nullptr) {
continue;
}

View file

@ -328,13 +328,13 @@ public:
static const uint16 MAX_NUM_MIPS = 0;
static const uint16 SINGLE_MIP = 1;
static Texture* create1D(const Element& texelFormat, uint16 width, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* create2D(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* createCube(const Element& texelFormat, uint16 width, uint16 numMips = 1, const Sampler& sampler = Sampler());
static Texture* createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* createStrict(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* createExternal(const ExternalRecycler& recycler, const Sampler& sampler = Sampler());
static TexturePointer create1D(const Element& texelFormat, uint16 width, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static TexturePointer create2D(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static TexturePointer create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static TexturePointer createCube(const Element& texelFormat, uint16 width, uint16 numMips = 1, const Sampler& sampler = Sampler());
static TexturePointer createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static TexturePointer createStrict(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static TexturePointer createExternal(const ExternalRecycler& recycler, const Sampler& sampler = Sampler());
// After the texture has been created, it should be defined
bool isDefined() const { return _defined; }
@ -435,6 +435,8 @@ public:
// For convenience assign a source name
const std::string& source() const { return _source; }
void setSource(const std::string& source) { _source = source; }
const std::string& sourceHash() const { return _sourceHash; }
void setSourceHash(const std::string& sourceHash) { _sourceHash = sourceHash; }
// Potentially change the minimum mip (mostly for debugging purpose)
bool setMinMip(uint16 newMinMip);
@ -482,6 +484,7 @@ public:
// For Cube Texture, it's possible to generate the irradiance spherical harmonics and make them availalbe with the texture
bool generateIrradiance();
const SHPointer& getIrradiance(uint16 slice = 0) const { return _irradiance; }
void overrideIrradiance(SHPointer irradiance) { _irradiance = irradiance; }
bool isIrradianceValid() const { return _isIrradianceValid; }
// Own sampler
@ -502,7 +505,7 @@ public:
// Textures can be serialized directly to ktx data file, here is how
static ktx::KTXUniquePointer serialize(const Texture& texture);
static Texture* unserialize(const std::string& ktxFile, TextureUsageType usageType = TextureUsageType::RESOURCE, Usage usage = Usage(), const Sampler::Desc& sampler = Sampler::Desc());
static TexturePointer unserialize(const std::string& ktxFile, TextureUsageType usageType = TextureUsageType::RESOURCE, Usage usage = Usage(), const Sampler::Desc& sampler = Sampler::Desc());
static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header);
static bool evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat);
@ -518,6 +521,7 @@ protected:
std::weak_ptr<Texture> _fallback;
// Not strictly necessary, but incredibly useful for debugging
std::string _source;
std::string _sourceHash;
std::unique_ptr< Storage > _storage;
Stamp _stamp { 0 };
@ -552,7 +556,7 @@ protected:
bool _isIrradianceValid = false;
bool _defined = false;
static Texture* create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler);
static TexturePointer create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler);
Size resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips);
};

View file

@ -23,6 +23,7 @@ struct GPUKTXPayload {
Texture::Usage _usage;
TextureUsageType _usageType;
static std::string KEY;
static bool isGPUKTX(const ktx::KeyValue& val) {
return (val._key.compare(KEY) == 0);
@ -161,7 +162,13 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
keyval._usage = texture.getUsage();
keyval._usageType = texture.getUsageType();
ktx::KeyValues keyValues;
keyValues.emplace_back(ktx::KeyValue(GPUKTXPayload::KEY, sizeof(GPUKTXPayload), (ktx::Byte*) &keyval));
keyValues.emplace_back(ktx::KeyValue(GPUKTXPayload::KEY, sizeof(GPUKTXPayload), (ktx::Byte*) &keyval));
static const std::string SOURCE_HASH_KEY = "hifi.sourceHash";
auto hash = texture.sourceHash();
if (!hash.empty()) {
keyValues.emplace_back(ktx::KeyValue(SOURCE_HASH_KEY, static_cast<uint32>(hash.size()), (ktx::Byte*) hash.c_str()));
}
auto ktxBuffer = ktx::KTX::create(header, images, keyValues);
#if 0
@ -193,7 +200,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
return ktxBuffer;
}
Texture* Texture::unserialize(const std::string& ktxfile, TextureUsageType usageType, Usage usage, const Sampler::Desc& sampler) {
TexturePointer Texture::unserialize(const std::string& ktxfile, TextureUsageType usageType, Usage usage, const Sampler::Desc& sampler) {
std::unique_ptr<ktx::KTX> ktxPointer = ktx::KTX::create(ktx::StoragePointer { new storage::FileStorage(ktxfile.c_str()) });
if (!ktxPointer) {
return nullptr;
@ -260,6 +267,16 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat
header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RGBA, ktx::GLInternalFormat_Uncompressed::SRGB8_ALPHA8, ktx::GLBaseInternalFormat::RGBA);
} else if (texelFormat == Format::COLOR_R_8 && mipFormat == Format::COLOR_R_8) {
header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RED, ktx::GLInternalFormat_Uncompressed::R8, ktx::GLBaseInternalFormat::RED);
} else if (texelFormat == Format::COLOR_COMPRESSED_SRGB && mipFormat == Format::COLOR_COMPRESSED_SRGB) {
header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_S3TC_DXT1_EXT, ktx::GLBaseInternalFormat::RGB);
} else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_MASK && mipFormat == Format::COLOR_COMPRESSED_SRGBA_MASK) {
header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, ktx::GLBaseInternalFormat::RGBA);
} else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA && mipFormat == Format::COLOR_COMPRESSED_SRGBA) {
header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, ktx::GLBaseInternalFormat::RGBA);
} else if (texelFormat == Format::COLOR_COMPRESSED_RED && mipFormat == Format::COLOR_COMPRESSED_RED) {
header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1, ktx::GLBaseInternalFormat::RED);
} else if (texelFormat == Format::COLOR_COMPRESSED_XY && mipFormat == Format::COLOR_COMPRESSED_XY) {
header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2, ktx::GLBaseInternalFormat::RG);
} else {
return false;
}
@ -295,6 +312,25 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E
} else {
return false;
}
} else if (header.getGLFormat() == ktx::GLFormat::COMPRESSED_FORMAT && header.getGLType() == ktx::GLType::COMPRESSED_TYPE) {
if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
mipFormat = Format::COLOR_COMPRESSED_SRGB;
texelFormat = Format::COLOR_COMPRESSED_SRGB;
} else if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
mipFormat = Format::COLOR_COMPRESSED_SRGBA_MASK;
texelFormat = Format::COLOR_COMPRESSED_SRGBA_MASK;
} else if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
mipFormat = Format::COLOR_COMPRESSED_SRGBA;
texelFormat = Format::COLOR_COMPRESSED_SRGBA;
} else if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1) {
mipFormat = Format::COLOR_COMPRESSED_RED;
texelFormat = Format::COLOR_COMPRESSED_RED;
} else if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2) {
mipFormat = Format::COLOR_COMPRESSED_XY;
texelFormat = Format::COLOR_COMPRESSED_XY;
} else {
return false;
}
} else {
return false;
}

View file

@ -0,0 +1,11 @@
set(TARGET_NAME image)
setup_hifi_library()
link_hifi_libraries(shared gpu)
target_glm()
add_dependency_external_projects(nvtt)
find_package(NVTT REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${NVTT_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${NVTT_LIBRARIES})
add_paths_to_fixup_libs(${NVTT_DLL_PATH})

View file

@ -0,0 +1,934 @@
//
// Image.cpp
// image/src/image
//
// Created by Clement Brisset on 4/5/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Image.h"
#include <nvtt/nvtt.h>
#include <QUrl>
#include <QImage>
#include <QBuffer>
#include <QImageReader>
#include <Finally.h>
#include <Profile.h>
#include <StatTracker.h>
#include <GLMHelpers.h>
#include "ImageLogging.h"
using namespace gpu;
#define CPU_MIPMAPS 1
#define COMPRESS_COLOR_TEXTURES 0
#define COMPRESS_NORMALMAP_TEXTURES 0 // Disable Normalmap compression for now
#define COMPRESS_GRAYSCALE_TEXTURES 0
#define COMPRESS_CUBEMAP_TEXTURES 0 // Disable Cubemap compression for now
static const glm::uvec2 SPARSE_PAGE_SIZE(128);
static const glm::uvec2 MAX_TEXTURE_SIZE(4096);
bool DEV_DECIMATE_TEXTURES = false;
std::atomic<size_t> DECIMATED_TEXTURE_COUNT{ 0 };
std::atomic<size_t> RECTIFIED_TEXTURE_COUNT{ 0 };
bool needsSparseRectification(const glm::uvec2& size) {
// Don't attempt to rectify small textures (textures less than the sparse page size in any dimension)
if (glm::any(glm::lessThan(size, SPARSE_PAGE_SIZE))) {
return false;
}
// Don't rectify textures that are already an exact multiple of sparse page size
if (glm::uvec2(0) == (size % SPARSE_PAGE_SIZE)) {
return false;
}
// Texture is not sparse compatible, but is bigger than the sparse page size in both dimensions, rectify!
return true;
}
glm::uvec2 rectifyToSparseSize(const glm::uvec2& size) {
glm::uvec2 pages = ((size / SPARSE_PAGE_SIZE) + glm::clamp(size % SPARSE_PAGE_SIZE, glm::uvec2(0), glm::uvec2(1)));
glm::uvec2 result = pages * SPARSE_PAGE_SIZE;
return result;
}
namespace image {
TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) {
switch (type) {
case ALBEDO_TEXTURE:
return image::TextureUsage::createAlbedoTextureFromImage;
case EMISSIVE_TEXTURE:
return image::TextureUsage::createEmissiveTextureFromImage;
case LIGHTMAP_TEXTURE:
return image::TextureUsage::createLightmapTextureFromImage;
case CUBE_TEXTURE:
if (options.value("generateIrradiance", true).toBool()) {
return image::TextureUsage::createCubeTextureFromImage;
} else {
return image::TextureUsage::createCubeTextureFromImageWithoutIrradiance;
}
case BUMP_TEXTURE:
return image::TextureUsage::createNormalTextureFromBumpImage;
case NORMAL_TEXTURE:
return image::TextureUsage::createNormalTextureFromNormalImage;
case ROUGHNESS_TEXTURE:
return image::TextureUsage::createRoughnessTextureFromImage;
case GLOSS_TEXTURE:
return image::TextureUsage::createRoughnessTextureFromGlossImage;
case SPECULAR_TEXTURE:
return image::TextureUsage::createMetallicTextureFromImage;
case STRICT_TEXTURE:
return image::TextureUsage::createStrict2DTextureFromImage;
case DEFAULT_TEXTURE:
default:
return image::TextureUsage::create2DTextureFromImage;
}
}
gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, true);
}
gpu::TexturePointer TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, false);
}
gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, false);
}
gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, false);
}
gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, false);
}
gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureNormalMapFromImage(srcImage, srcImageName, false);
}
gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureNormalMapFromImage(srcImage, srcImageName, true);
}
gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false);
}
gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureGrayscaleFromImage(srcImage, srcImageName, true);
}
gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false);
}
gpu::TexturePointer TextureUsage::createCubeTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return processCubeTextureColorFromImage(srcImage, srcImageName, true);
}
gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(const QImage& srcImage, const std::string& srcImageName) {
return processCubeTextureColorFromImage(srcImage, srcImageName, false);
}
gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType) {
// Help the QImage loader by extracting the image file format from the url filename ext.
// Some tga are not created properly without it.
auto filenameExtension = filename.substr(filename.find_last_of('.') + 1);
QBuffer buffer;
buffer.setData(content);
QImageReader imageReader(&buffer, filenameExtension.c_str());
QImage image;
if (imageReader.canRead()) {
image = imageReader.read();
} else {
// Extension could be incorrect, try to detect the format from the content
QImageReader newImageReader;
newImageReader.setDecideFormatFromContent(true);
buffer.setData(content);
newImageReader.setDevice(&buffer);
if (newImageReader.canRead()) {
qCWarning(imagelogging) << "Image file" << filename.c_str() << "has extension" << filenameExtension.c_str()
<< "but is actually a" << qPrintable(newImageReader.format()) << "(recovering)";
image = newImageReader.read();
}
}
int imageWidth = image.width();
int imageHeight = image.height();
// Validate that the image loaded
if (imageWidth == 0 || imageHeight == 0 || image.format() == QImage::Format_Invalid) {
QString reason(image.format() == QImage::Format_Invalid ? "(Invalid Format)" : "(Size is invalid)");
qCWarning(imagelogging) << "Failed to load" << filename.c_str() << qPrintable(reason);
return nullptr;
}
// Validate the image is less than _maxNumPixels, and downscale if necessary
if (imageWidth * imageHeight > maxNumPixels) {
float scaleFactor = sqrtf(maxNumPixels / (float)(imageWidth * imageHeight));
int originalWidth = imageWidth;
int originalHeight = imageHeight;
imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f);
imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f);
QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
image.swap(newImage);
qCDebug(imagelogging).nospace() << "Downscaled " << filename.c_str() << " (" <<
QSize(originalWidth, originalHeight) << " to " <<
QSize(imageWidth, imageHeight) << ")";
}
auto loader = TextureUsage::getTextureLoaderForType(textureType);
auto texture = loader(image, filename);
return texture;
}
QImage processSourceImage(const QImage& srcImage, bool cubemap) {
PROFILE_RANGE(resource_parse, "processSourceImage");
const glm::uvec2 srcImageSize = toGlm(srcImage.size());
glm::uvec2 targetSize = srcImageSize;
while (glm::any(glm::greaterThan(targetSize, MAX_TEXTURE_SIZE))) {
targetSize /= 2;
}
if (targetSize != srcImageSize) {
++DECIMATED_TEXTURE_COUNT;
}
if (!cubemap && needsSparseRectification(targetSize)) {
++RECTIFIED_TEXTURE_COUNT;
targetSize = rectifyToSparseSize(targetSize);
}
if (DEV_DECIMATE_TEXTURES && glm::all(glm::greaterThanEqual(targetSize / SPARSE_PAGE_SIZE, glm::uvec2(2)))) {
targetSize /= 2;
}
if (targetSize != srcImageSize) {
PROFILE_RANGE(resource_parse, "processSourceImage Rectify");
qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y;
return srcImage.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
return srcImage;
}
struct MyOutputHandler : public nvtt::OutputHandler {
MyOutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {}
virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override {
_size = size;
_miplevel = miplevel;
_data = static_cast<gpu::Byte*>(malloc(size));
_current = _data;
}
virtual bool writeData(const void* data, int size) override {
assert(_current + size <= _data + _size);
memcpy(_current, data, size);
_current += size;
return true;
}
virtual void endImage() override {
if (_face >= 0) {
_texture->assignStoredMipFace(_miplevel, _face, _size, static_cast<const gpu::Byte*>(_data));
} else {
_texture->assignStoredMip(_miplevel, _size, static_cast<const gpu::Byte*>(_data));
}
free(_data);
_data = nullptr;
}
gpu::Byte* _data{ nullptr };
gpu::Byte* _current{ nullptr };
gpu::Texture* _texture{ nullptr };
int _miplevel = 0;
int _size = 0;
int _face = -1;
};
struct MyErrorHandler : public nvtt::ErrorHandler {
virtual void error(nvtt::Error e) override {
qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e);
}
};
void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateMips");
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
const int width = image.width(), height = image.height();
const void* data = static_cast<const void*>(image.constBits());
nvtt::TextureType textureType = nvtt::TextureType_2D;
nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB;
nvtt::WrapMode wrapMode = nvtt::WrapMode_Repeat;
nvtt::RoundMode roundMode = nvtt::RoundMode_None;
nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
float inputGamma = 2.2f;
float outputGamma = 2.2f;
nvtt::CompressionOptions compressionOptions;
compressionOptions.setQuality(nvtt::Quality_Production);
auto mipFormat = texture->getStoredMipFormat();
if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGB) {
compressionOptions.setFormat(nvtt::Format_BC1);
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA_MASK) {
alphaMode = nvtt::AlphaMode_Transparency;
compressionOptions.setFormat(nvtt::Format_BC1a);
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA) {
alphaMode = nvtt::AlphaMode_Transparency;
compressionOptions.setFormat(nvtt::Format_BC3);
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_RED) {
compressionOptions.setFormat(nvtt::Format_BC4);
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_XY) {
compressionOptions.setFormat(nvtt::Format_BC5);
} else if (mipFormat == gpu::Element::COLOR_RGBA_32) {
compressionOptions.setFormat(nvtt::Format_RGBA);
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPixelFormat(32,
0x000000FF,
0x0000FF00,
0x00FF0000,
0xFF000000);
inputGamma = 1.0f;
outputGamma = 1.0f;
} else if (mipFormat == gpu::Element::COLOR_BGRA_32) {
compressionOptions.setFormat(nvtt::Format_RGBA);
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPixelFormat(32,
0x00FF0000,
0x0000FF00,
0x000000FF,
0xFF000000);
inputGamma = 1.0f;
outputGamma = 1.0f;
} else if (mipFormat == gpu::Element::COLOR_SRGBA_32) {
compressionOptions.setFormat(nvtt::Format_RGBA);
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPixelFormat(32,
0x000000FF,
0x0000FF00,
0x00FF0000,
0xFF000000);
} else if (mipFormat == gpu::Element::COLOR_SBGRA_32) {
compressionOptions.setFormat(nvtt::Format_RGBA);
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPixelFormat(32,
0x00FF0000,
0x0000FF00,
0x000000FF,
0xFF000000);
} else if (mipFormat == gpu::Element::COLOR_R_8) {
compressionOptions.setFormat(nvtt::Format_RGB);
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPixelFormat(8, 0, 0, 0);
} else {
qCWarning(imagelogging) << "Unknown mip format";
Q_UNREACHABLE();
return;
}
nvtt::InputOptions inputOptions;
inputOptions.setTextureLayout(textureType, width, height);
inputOptions.setMipmapData(data, width, height);
inputOptions.setFormat(inputFormat);
inputOptions.setGamma(inputGamma, outputGamma);
inputOptions.setAlphaMode(alphaMode);
inputOptions.setWrapMode(wrapMode);
inputOptions.setRoundMode(roundMode);
inputOptions.setMipmapGeneration(true);
inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box);
nvtt::OutputOptions outputOptions;
outputOptions.setOutputHeader(false);
MyOutputHandler outputHandler(texture, face);
outputOptions.setOutputHandler(&outputHandler);
MyErrorHandler errorHandler;
outputOptions.setErrorHandler(&errorHandler);
nvtt::Compressor compressor;
compressor.process(inputOptions, compressionOptions, outputOptions);
#else
texture->autoGenerateMips(-1);
#endif
}
void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask) {
PROFILE_RANGE(resource_parse, "processTextureAlpha");
validAlpha = false;
alphaAsMask = true;
const uint8 OPAQUE_ALPHA = 255;
const uint8 TRANSPARENT_ALPHA = 0;
// Figure out if we can use a mask for alpha or not
int numOpaques = 0;
int numTranslucents = 0;
const int NUM_PIXELS = srcImage.width() * srcImage.height();
const int MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK = (int)(0.05f * (float)(NUM_PIXELS));
const QRgb* data = reinterpret_cast<const QRgb*>(srcImage.constBits());
for (int i = 0; i < NUM_PIXELS; ++i) {
auto alpha = qAlpha(data[i]);
if (alpha == OPAQUE_ALPHA) {
numOpaques++;
} else if (alpha != TRANSPARENT_ALPHA) {
if (++numTranslucents > MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK) {
alphaAsMask = false;
break;
}
}
}
validAlpha = (numOpaques != NUM_PIXELS);
}
gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isStrict) {
PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage");
QImage image = processSourceImage(srcImage, false);
bool validAlpha = image.hasAlphaChannel();
bool alphaAsMask = false;
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if (validAlpha) {
processTextureAlpha(image, validAlpha, alphaAsMask);
}
gpu::TexturePointer theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
#if CPU_MIPMAPS && COMPRESS_COLOR_TEXTURES
gpu::Element formatGPU;
if (validAlpha) {
formatGPU = alphaAsMask ? gpu::Element::COLOR_COMPRESSED_SRGBA_MASK : gpu::Element::COLOR_COMPRESSED_SRGBA;
} else {
formatGPU = gpu::Element::COLOR_COMPRESSED_SRGB;
}
gpu::Element formatMip = formatGPU;
#else
gpu::Element formatMip = gpu::Element::COLOR_SBGRA_32;
gpu::Element formatGPU = gpu::Element::COLOR_SRGBA_32;
#endif
if (isStrict) {
theTexture = gpu::Texture::createStrict(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
} else {
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
}
theTexture->setSource(srcImageName);
auto usage = gpu::Texture::Usage::Builder().withColor();
if (validAlpha) {
usage.withAlpha();
if (alphaAsMask) {
usage.withAlphaMask();
}
}
theTexture->setUsage(usage.build());
theTexture->setStoredMipFormat(formatMip);
generateMips(theTexture.get(), image);
}
return theTexture;
}
int clampPixelCoordinate(int coordinate, int maxCoordinate) {
return coordinate - ((int)(coordinate < 0) * coordinate) + ((int)(coordinate > maxCoordinate) * (maxCoordinate - coordinate));
}
const int RGBA_MAX = 255;
// transform -1 - 1 to 0 - 255 (from sobel value to rgb)
double mapComponent(double sobelValue) {
const double factor = RGBA_MAX / 2.0;
return (sobelValue + 1.0) * factor;
}
QImage processBumpMap(QImage& image) {
if (image.format() != QImage::Format_Grayscale8) {
image = image.convertToFormat(QImage::Format_Grayscale8);
}
// PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps
// The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image
const double pStrength = 2.0;
int width = image.width();
int height = image.height();
QImage result(width, height, QImage::Format_ARGB32);
for (int i = 0; i < width; i++) {
const int iNextClamped = clampPixelCoordinate(i + 1, width - 1);
const int iPrevClamped = clampPixelCoordinate(i - 1, width - 1);
for (int j = 0; j < height; j++) {
const int jNextClamped = clampPixelCoordinate(j + 1, height - 1);
const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1);
// surrounding pixels
const QRgb topLeft = image.pixel(iPrevClamped, jPrevClamped);
const QRgb top = image.pixel(iPrevClamped, j);
const QRgb topRight = image.pixel(iPrevClamped, jNextClamped);
const QRgb right = image.pixel(i, jNextClamped);
const QRgb bottomRight = image.pixel(iNextClamped, jNextClamped);
const QRgb bottom = image.pixel(iNextClamped, j);
const QRgb bottomLeft = image.pixel(iNextClamped, jPrevClamped);
const QRgb left = image.pixel(i, jPrevClamped);
// take their gray intensities
// since it's a grayscale image, the value of each component RGB is the same
const double tl = qRed(topLeft);
const double t = qRed(top);
const double tr = qRed(topRight);
const double r = qRed(right);
const double br = qRed(bottomRight);
const double b = qRed(bottom);
const double bl = qRed(bottomLeft);
const double l = qRed(left);
// apply the sobel filter
const double dX = (tr + pStrength * r + br) - (tl + pStrength * l + bl);
const double dY = (bl + pStrength * b + br) - (tl + pStrength * t + tr);
const double dZ = RGBA_MAX / pStrength;
glm::vec3 v(dX, dY, dZ);
glm::normalize(v);
// convert to rgb from the value obtained computing the filter
QRgb qRgbValue = qRgba(mapComponent(v.z), mapComponent(v.y), mapComponent(v.x), 1.0);
result.setPixel(i, j, qRgbValue);
}
}
return result;
}
gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, bool isBumpMap) {
PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage");
QImage image = processSourceImage(srcImage, false);
if (isBumpMap) {
image = processBumpMap(image);
}
// Make sure the normal map source image is ARGB32
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
gpu::TexturePointer theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
#if CPU_MIPMAPS && COMPRESS_NORMALMAP_TEXTURES
gpu::Element formatMip = gpu::Element::COLOR_COMPRESSED_XY;
gpu::Element formatGPU = gpu::Element::COLOR_COMPRESSED_XY;
#else
gpu::Element formatMip = gpu::Element::COLOR_RGBA_32;
gpu::Element formatGPU = gpu::Element::COLOR_RGBA_32;
#endif
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
generateMips(theTexture.get(), image);
}
return theTexture;
}
gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, bool isInvertedPixels) {
PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage");
QImage image = processSourceImage(srcImage, false);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if (isInvertedPixels) {
// Gloss turned into Rough
image.invertPixels(QImage::InvertRgba);
}
gpu::TexturePointer theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
#if CPU_MIPMAPS && COMPRESS_GRAYSCALE_TEXTURES
gpu::Element formatMip = gpu::Element::COLOR_COMPRESSED_RED;
gpu::Element formatGPU = gpu::Element::COLOR_COMPRESSED_RED;
#else
gpu::Element formatMip = gpu::Element::COLOR_R_8;
gpu::Element formatGPU = gpu::Element::COLOR_R_8;
#endif
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
generateMips(theTexture.get(), image);
}
return theTexture;
}
class CubeLayout {
public:
enum SourceProjection {
FLAT = 0,
EQUIRECTANGULAR,
};
int _type = FLAT;
int _widthRatio = 1;
int _heightRatio = 1;
class Face {
public:
int _x = 0;
int _y = 0;
bool _horizontalMirror = false;
bool _verticalMirror = false;
Face() {}
Face(int x, int y, bool horizontalMirror, bool verticalMirror) : _x(x), _y(y), _horizontalMirror(horizontalMirror), _verticalMirror(verticalMirror) {}
};
Face _faceXPos;
Face _faceXNeg;
Face _faceYPos;
Face _faceYNeg;
Face _faceZPos;
Face _faceZNeg;
CubeLayout(int wr, int hr, Face fXP, Face fXN, Face fYP, Face fYN, Face fZP, Face fZN) :
_type(FLAT),
_widthRatio(wr),
_heightRatio(hr),
_faceXPos(fXP),
_faceXNeg(fXN),
_faceYPos(fYP),
_faceYNeg(fYN),
_faceZPos(fZP),
_faceZNeg(fZN) {}
CubeLayout(int wr, int hr) :
_type(EQUIRECTANGULAR),
_widthRatio(wr),
_heightRatio(hr) {}
static const CubeLayout CUBEMAP_LAYOUTS[];
static const int NUM_CUBEMAP_LAYOUTS;
static int findLayout(int width, int height) {
// Find the layout of the cubemap in the 2D image
int foundLayout = -1;
for (int i = 0; i < NUM_CUBEMAP_LAYOUTS; i++) {
if ((height * CUBEMAP_LAYOUTS[i]._widthRatio) == (width * CUBEMAP_LAYOUTS[i]._heightRatio)) {
foundLayout = i;
break;
}
}
return foundLayout;
}
static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) {
QImage image(faceWidth, faceWidth, source.format());
glm::vec2 dstInvSize(1.0f / (float)image.width(), 1.0f / (float)image.height());
struct CubeToXYZ {
gpu::Texture::CubeFace _face;
CubeToXYZ(gpu::Texture::CubeFace face) : _face(face) {}
glm::vec3 xyzFrom(const glm::vec2& uv) {
auto faceDir = glm::normalize(glm::vec3(-1.0f + 2.0f * uv.x, -1.0f + 2.0f * uv.y, 1.0f));
switch (_face) {
case gpu::Texture::CubeFace::CUBE_FACE_BACK_POS_Z:
return glm::vec3(-faceDir.x, faceDir.y, faceDir.z);
case gpu::Texture::CubeFace::CUBE_FACE_FRONT_NEG_Z:
return glm::vec3(faceDir.x, faceDir.y, -faceDir.z);
case gpu::Texture::CubeFace::CUBE_FACE_LEFT_NEG_X:
return glm::vec3(faceDir.z, faceDir.y, faceDir.x);
case gpu::Texture::CubeFace::CUBE_FACE_RIGHT_POS_X:
return glm::vec3(-faceDir.z, faceDir.y, -faceDir.x);
case gpu::Texture::CubeFace::CUBE_FACE_BOTTOM_NEG_Y:
return glm::vec3(-faceDir.x, -faceDir.z, faceDir.y);
case gpu::Texture::CubeFace::CUBE_FACE_TOP_POS_Y:
default:
return glm::vec3(-faceDir.x, faceDir.z, -faceDir.y);
}
}
};
CubeToXYZ cubeToXYZ(face);
struct RectToXYZ {
RectToXYZ() {}
glm::vec2 uvFrom(const glm::vec3& xyz) {
auto flatDir = glm::normalize(glm::vec2(xyz.x, xyz.z));
auto uvRad = glm::vec2(atan2(flatDir.x, flatDir.y), asin(xyz.y));
const float LON_TO_RECT_U = 1.0f / (glm::pi<float>());
const float LAT_TO_RECT_V = 2.0f / glm::pi<float>();
return glm::vec2(0.5f * uvRad.x * LON_TO_RECT_U + 0.5f, 0.5f * uvRad.y * LAT_TO_RECT_V + 0.5f);
}
};
RectToXYZ rectToXYZ;
int srcFaceHeight = source.height();
int srcFaceWidth = source.width();
glm::vec2 dstCoord;
glm::ivec2 srcPixel;
for (int y = 0; y < faceWidth; ++y) {
dstCoord.y = 1.0f - (y + 0.5f) * dstInvSize.y; // Fill cube face images from top to bottom
for (int x = 0; x < faceWidth; ++x) {
dstCoord.x = (x + 0.5f) * dstInvSize.x;
auto xyzDir = cubeToXYZ.xyzFrom(dstCoord);
auto srcCoord = rectToXYZ.uvFrom(xyzDir);
srcPixel.x = floor(srcCoord.x * srcFaceWidth);
// Flip the vertical axis to QImage going top to bottom
srcPixel.y = floor((1.0f - srcCoord.y) * srcFaceHeight);
if (((uint32)srcPixel.x < (uint32)source.width()) && ((uint32)srcPixel.y < (uint32)source.height())) {
image.setPixel(x, y, source.pixel(QPoint(srcPixel.x, srcPixel.y)));
// Keep for debug, this is showing the dir as a color
// glm::u8vec4 rgba((xyzDir.x + 1.0)*0.5 * 256, (xyzDir.y + 1.0)*0.5 * 256, (xyzDir.z + 1.0)*0.5 * 256, 256);
// unsigned int val = 0xff000000 | (rgba.r) | (rgba.g << 8) | (rgba.b << 16);
// image.setPixel(x, y, val);
}
}
}
return image;
}
};
const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = {
// Here is the expected layout for the faces in an image with the 2/1 aspect ratio:
// THis is detected as an Equirectangular projection
// WIDTH
// <--------------------------->
// ^ +------+------+------+------+
// H | | | | |
// E | | | | |
// I | | | | |
// G +------+------+------+------+
// H | | | | |
// T | | | | |
// | | | | | |
// v +------+------+------+------+
//
// FaceWidth = width = height / 6
{ 2, 1 },
// Here is the expected layout for the faces in an image with the 1/6 aspect ratio:
//
// WIDTH
// <------>
// ^ +------+
// | | |
// | | +X |
// | | |
// H +------+
// E | |
// I | -X |
// G | |
// H +------+
// T | |
// | | +Y |
// | | |
// | +------+
// | | |
// | | -Y |
// | | |
// H +------+
// E | |
// I | +Z |
// G | |
// H +------+
// T | |
// | | -Z |
// | | |
// V +------+
//
// FaceWidth = width = height / 6
{ 1, 6,
{ 0, 0, true, false },
{ 0, 1, true, false },
{ 0, 2, false, true },
{ 0, 3, false, true },
{ 0, 4, true, false },
{ 0, 5, true, false }
},
// Here is the expected layout for the faces in an image with the 3/4 aspect ratio:
//
// <-----------WIDTH----------->
// ^ +------+------+------+------+
// | | | | | |
// | | | +Y | | |
// | | | | | |
// H +------+------+------+------+
// E | | | | |
// I | -X | -Z | +X | +Z |
// G | | | | |
// H +------+------+------+------+
// T | | | | |
// | | | -Y | | |
// | | | | | |
// V +------+------+------+------+
//
// FaceWidth = width / 4 = height / 3
{ 4, 3,
{ 2, 1, true, false },
{ 0, 1, true, false },
{ 1, 0, false, true },
{ 1, 2, false, true },
{ 3, 1, true, false },
{ 1, 1, true, false }
},
// Here is the expected layout for the faces in an image with the 4/3 aspect ratio:
//
// <-------WIDTH-------->
// ^ +------+------+------+
// | | | | |
// | | | +Y | |
// | | | | |
// H +------+------+------+
// E | | | |
// I | -X | -Z | +X |
// G | | | |
// H +------+------+------+
// T | | | |
// | | | -Y | |
// | | | | |
// | +------+------+------+
// | | | | |
// | | | +Z! | | <+Z is upside down!
// | | | | |
// V +------+------+------+
//
// FaceWidth = width / 3 = height / 4
{ 3, 4,
{ 2, 1, true, false },
{ 0, 1, true, false },
{ 1, 0, false, true },
{ 1, 2, false, true },
{ 1, 3, false, true },
{ 1, 1, true, false }
}
};
const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout);
gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance) {
PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage");
gpu::TexturePointer theTexture = nullptr;
if ((srcImage.width() > 0) && (srcImage.height() > 0)) {
QImage image = processSourceImage(srcImage, true);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
#if CPU_MIPMAPS && COMPRESS_CUBEMAP_TEXTURES
gpu::Element formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA;
gpu::Element formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA;
#else
gpu::Element formatMip = gpu::Element::COLOR_SRGBA_32;
gpu::Element formatGPU = gpu::Element::COLOR_SRGBA_32;
#endif
// Find the layout of the cubemap in the 2D image
// Use the original image size since processSourceImage may have altered the size / aspect ratio
int foundLayout = CubeLayout::findLayout(srcImage.width(), srcImage.height());
std::vector<QImage> faces;
// If found, go extract the faces as separate images
if (foundLayout >= 0) {
auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout];
if (layout._type == CubeLayout::FLAT) {
int faceWidth = image.width() / layout._widthRatio;
faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror));
} else if (layout._type == CubeLayout::EQUIRECTANGULAR) {
// THe face width is estimated from the input image
const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4;
const int EQUIRECT_MAX_FACE_WIDTH = 2048;
int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH);
for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) {
QImage faceImage = CubeLayout::extractEquirectangularFace(image, (gpu::Texture::CubeFace) face, faceWidth);
faces.push_back(faceImage);
}
}
} else {
qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str());
return nullptr;
}
// If the 6 faces have been created go on and define the true Texture
if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) {
theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
for (uint8 face = 0; face < faces.size(); ++face) {
generateMips(theTexture.get(), faces[face], face);
}
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(resource_parse, "generateIrradiance");
auto irradianceTexture = gpu::Texture::createCube(gpu::Element::COLOR_SRGBA_32, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
irradianceTexture->setSource(srcImageName);
irradianceTexture->setStoredMipFormat(gpu::Element::COLOR_SBGRA_32);
for (uint8 face = 0; face < faces.size(); ++face) {
irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits());
}
irradianceTexture->generateIrradiance();
auto irradiance = irradianceTexture->getIrradiance();
theTexture->overrideIrradiance(irradiance);
}
}
}
return theTexture;
}
} // namespace image

View file

@ -0,0 +1,70 @@
//
// Image.h
// image/src/image
//
// Created by Clement Brisset on 4/5/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_image_Image_h
#define hifi_image_Image_h
#include <QVariant>
#include <gpu/Texture.h>
class QByteArray;
class QImage;
namespace image {
namespace TextureUsage {
enum Type {
DEFAULT_TEXTURE,
STRICT_TEXTURE,
ALBEDO_TEXTURE,
NORMAL_TEXTURE,
BUMP_TEXTURE,
SPECULAR_TEXTURE,
METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey
ROUGHNESS_TEXTURE,
GLOSS_TEXTURE,
EMISSIVE_TEXTURE,
CUBE_TEXTURE,
OCCLUSION_TEXTURE,
SCATTERING_TEXTURE = OCCLUSION_TEXTURE,
LIGHTMAP_TEXTURE
};
using TextureLoader = std::function<gpu::TexturePointer(const QImage&, const std::string&)>;
TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap());
gpu::TexturePointer create2DTextureFromImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createCubeTextureFromImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName);
gpu::TexturePointer process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isStrict);
gpu::TexturePointer process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, bool isBumpMap);
gpu::TexturePointer process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, bool isInvertedPixels);
gpu::TexturePointer processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance);
} // namespace TextureUsage
gpu::TexturePointer processImage(const QByteArray& content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType);
} // namespace image
#endif // hifi_image_Image_h

View file

@ -0,0 +1,14 @@
//
// ImageLogging.cpp
// image/src/image
//
// Created by Clement Brisset on 4/5/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ImageLogging.h"
Q_LOGGING_CATEGORY(imagelogging, "hifi.image")

View file

@ -0,0 +1,14 @@
//
// ImageLogging.h
// image/src/image
//
// Created by Clement Brisset on 4/5/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(imagelogging)

View file

@ -101,8 +101,6 @@ namespace ktx {
UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B,
UNSIGNED_INT_5_9_9_9_REV = 0x8C3E,
FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD,
NUM_GLTYPES = 25,
};
enum class GLFormat : uint32_t {
@ -130,8 +128,6 @@ namespace ktx {
RGBA_INTEGER = 0x8D99,
BGR_INTEGER = 0x8D9A,
BGRA_INTEGER = 0x8D9B,
NUM_GLFORMATS = 20,
};
enum class GLInternalFormat_Uncompressed : uint32_t {
@ -232,8 +228,6 @@ namespace ktx {
STENCIL_INDEX4 = 0x8D47,
STENCIL_INDEX8 = 0x8D48,
STENCIL_INDEX16 = 0x8D49,
NUM_UNCOMPRESSED_GLINTERNALFORMATS = 74,
};
enum class GLInternalFormat_Compressed : uint32_t {
@ -246,6 +240,11 @@ namespace ktx {
COMPRESSED_SRGB = 0x8C48,
COMPRESSED_SRGB_ALPHA = 0x8C49,
COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C,
COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D,
COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E,
COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F,
COMPRESSED_RED_RGTC1 = 0x8DBB,
COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC,
COMPRESSED_RG_RGTC2 = 0x8DBD,
@ -267,8 +266,6 @@ namespace ktx {
COMPRESSED_SIGNED_R11_EAC = 0x9271,
COMPRESSED_RG11_EAC = 0x9272,
COMPRESSED_SIGNED_RG11_EAC = 0x9273,
NUM_COMPRESSED_GLINTERNALFORMATS = 24,
};
enum class GLBaseInternalFormat : uint32_t {
@ -280,8 +277,6 @@ namespace ktx {
RGB = 0x1907,
RGBA = 0x1908,
STENCIL_INDEX = 0x1901,
NUM_GLBASEINTERNALFORMATS = 7,
};
enum CubeMapFace {

View file

@ -1,4 +1,4 @@
set(TARGET_NAME model-networking)
setup_hifi_library()
link_hifi_libraries(shared networking model fbx ktx)
link_hifi_libraries(shared networking model fbx ktx image)

View file

@ -489,7 +489,7 @@ QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const FBXTexture& textu
}
model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture,
TextureType type, MapChannel channel) {
image::TextureUsage::Type type, MapChannel channel) {
const auto url = getTextureUrl(baseUrl, fbxTexture);
const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type, fbxTexture.content, fbxTexture.maxNumPixels);
_textures[channel] = Texture { fbxTexture.name, texture };
@ -503,7 +503,7 @@ model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, c
return map;
}
model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, TextureType type, MapChannel channel) {
model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, image::TextureUsage::Type type, MapChannel channel) {
const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type);
_textures[channel].texture = texture;
@ -518,7 +518,7 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur
{
_textures = Textures(MapChannel::NUM_MAP_CHANNELS);
if (!material.albedoTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, NetworkTexture::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP);
auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP);
_albedoTransform = material.albedoTexture.transform;
map->setTextureTransform(_albedoTransform);
@ -535,45 +535,45 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur
if (!material.normalTexture.filename.isEmpty()) {
auto type = (material.normalTexture.isBumpmap ? NetworkTexture::BUMP_TEXTURE : NetworkTexture::NORMAL_TEXTURE);
auto type = (material.normalTexture.isBumpmap ? image::TextureUsage::BUMP_TEXTURE : image::TextureUsage::NORMAL_TEXTURE);
auto map = fetchTextureMap(textureBaseUrl, material.normalTexture, type, MapChannel::NORMAL_MAP);
setTextureMap(MapChannel::NORMAL_MAP, map);
}
if (!material.roughnessTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.roughnessTexture, NetworkTexture::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
auto map = fetchTextureMap(textureBaseUrl, material.roughnessTexture, image::TextureUsage::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
} else if (!material.glossTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.glossTexture, NetworkTexture::GLOSS_TEXTURE, MapChannel::ROUGHNESS_MAP);
auto map = fetchTextureMap(textureBaseUrl, material.glossTexture, image::TextureUsage::GLOSS_TEXTURE, MapChannel::ROUGHNESS_MAP);
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
}
if (!material.metallicTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.metallicTexture, NetworkTexture::METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
auto map = fetchTextureMap(textureBaseUrl, material.metallicTexture, image::TextureUsage::METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
setTextureMap(MapChannel::METALLIC_MAP, map);
} else if (!material.specularTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.specularTexture, NetworkTexture::SPECULAR_TEXTURE, MapChannel::METALLIC_MAP);
auto map = fetchTextureMap(textureBaseUrl, material.specularTexture, image::TextureUsage::SPECULAR_TEXTURE, MapChannel::METALLIC_MAP);
setTextureMap(MapChannel::METALLIC_MAP, map);
}
if (!material.occlusionTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, NetworkTexture::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
map->setTextureTransform(material.occlusionTexture.transform);
setTextureMap(MapChannel::OCCLUSION_MAP, map);
}
if (!material.emissiveTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.emissiveTexture, NetworkTexture::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
auto map = fetchTextureMap(textureBaseUrl, material.emissiveTexture, image::TextureUsage::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
setTextureMap(MapChannel::EMISSIVE_MAP, map);
}
if (!material.scatteringTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.scatteringTexture, NetworkTexture::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP);
auto map = fetchTextureMap(textureBaseUrl, material.scatteringTexture, image::TextureUsage::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP);
setTextureMap(MapChannel::SCATTERING_MAP, map);
}
if (!material.lightmapTexture.filename.isEmpty()) {
auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, NetworkTexture::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
_lightmapTransform = material.lightmapTexture.transform;
_lightmapParams = material.lightmapParams;
map->setTextureTransform(_lightmapTransform);
@ -596,7 +596,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) {
if (!albedoName.isEmpty()) {
auto url = textureMap.contains(albedoName) ? textureMap[albedoName].toUrl() : QUrl();
auto map = fetchTextureMap(url, NetworkTexture::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP);
auto map = fetchTextureMap(url, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP);
map->setTextureTransform(_albedoTransform);
// when reassigning the albedo texture we also check for the alpha channel used as opacity
map->setUseAlphaChannel(true);
@ -605,45 +605,45 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) {
if (!normalName.isEmpty()) {
auto url = textureMap.contains(normalName) ? textureMap[normalName].toUrl() : QUrl();
auto map = fetchTextureMap(url, NetworkTexture::NORMAL_TEXTURE, MapChannel::NORMAL_MAP);
auto map = fetchTextureMap(url, image::TextureUsage::NORMAL_TEXTURE, MapChannel::NORMAL_MAP);
setTextureMap(MapChannel::NORMAL_MAP, map);
}
if (!roughnessName.isEmpty()) {
auto url = textureMap.contains(roughnessName) ? textureMap[roughnessName].toUrl() : QUrl();
// FIXME: If passing a gloss map instead of a roughmap how do we know?
auto map = fetchTextureMap(url, NetworkTexture::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
auto map = fetchTextureMap(url, image::TextureUsage::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
}
if (!metallicName.isEmpty()) {
auto url = textureMap.contains(metallicName) ? textureMap[metallicName].toUrl() : QUrl();
// FIXME: If passing a specular map instead of a metallic how do we know?
auto map = fetchTextureMap(url, NetworkTexture::METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
auto map = fetchTextureMap(url, image::TextureUsage::METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
setTextureMap(MapChannel::METALLIC_MAP, map);
}
if (!occlusionName.isEmpty()) {
auto url = textureMap.contains(occlusionName) ? textureMap[occlusionName].toUrl() : QUrl();
auto map = fetchTextureMap(url, NetworkTexture::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
auto map = fetchTextureMap(url, image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
setTextureMap(MapChannel::OCCLUSION_MAP, map);
}
if (!emissiveName.isEmpty()) {
auto url = textureMap.contains(emissiveName) ? textureMap[emissiveName].toUrl() : QUrl();
auto map = fetchTextureMap(url, NetworkTexture::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
auto map = fetchTextureMap(url, image::TextureUsage::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
setTextureMap(MapChannel::EMISSIVE_MAP, map);
}
if (!scatteringName.isEmpty()) {
auto url = textureMap.contains(scatteringName) ? textureMap[scatteringName].toUrl() : QUrl();
auto map = fetchTextureMap(url, NetworkTexture::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP);
auto map = fetchTextureMap(url, image::TextureUsage::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP);
setTextureMap(MapChannel::SCATTERING_MAP, map);
}
if (!lightmapName.isEmpty()) {
auto url = textureMap.contains(lightmapName) ? textureMap[lightmapName].toUrl() : QUrl();
auto map = fetchTextureMap(url, NetworkTexture::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
auto map = fetchTextureMap(url, image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
map->setTextureTransform(_lightmapTransform);
map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y);
setTextureMap(MapChannel::LIGHTMAP_MAP, map);

View file

@ -180,13 +180,11 @@ protected:
const bool& isOriginal() const { return _isOriginal; }
private:
using TextureType = NetworkTexture::Type;
// Helpers for the ctors
QUrl getTextureUrl(const QUrl& baseUrl, const FBXTexture& fbxTexture);
model::TextureMapPointer fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture,
TextureType type, MapChannel channel);
model::TextureMapPointer fetchTextureMap(const QUrl& url, TextureType type, MapChannel channel);
image::TextureUsage::Type type, MapChannel channel);
model::TextureMapPointer fetchTextureMap(const QUrl& url, image::TextureUsage::Type type, MapChannel channel);
Transform _albedoTransform;
Transform _lightmapTransform;

View file

@ -13,11 +13,12 @@
#include <mutex>
#include <QNetworkReply>
#include <QPainter>
#include <QCryptographicHash>
#include <QImageReader>
#include <QRunnable>
#include <QThreadPool>
#include <QImageReader>
#include <QNetworkReply>
#include <QPainter>
#if DEBUG_DUMP_TEXTURE_LOADS
#include <QtCore/QFile>
@ -31,10 +32,13 @@
#include <ktx/KTX.h>
#include <image/Image.h>
#include <NumericalConstants.h>
#include <shared/NsightHelpers.h>
#include <Finally.h>
#include <Profile.h>
#include "ModelNetworkingLogging.h"
#include <Trace.h>
@ -51,16 +55,6 @@ TextureCache::TextureCache() :
_ktxCache(KTX_DIRNAME, KTX_EXT) {
setUnusedResourceCacheSize(0);
setObjectName("TextureCache");
// Expose enum Type to JS/QML via properties
// Despite being one-off, this should be fine, because TextureCache is a SINGLETON_DEPENDENCY
QObject* type = new QObject(this);
type->setObjectName("TextureType");
setProperty("Type", QVariant::fromValue(type));
auto metaEnum = QMetaEnum::fromType<Type>();
for (int i = 0; i < metaEnum.keyCount(); ++i) {
type->setProperty(metaEnum.key(i), metaEnum.value(i));
}
}
TextureCache::~TextureCache() {
@ -117,7 +111,7 @@ const gpu::TexturePointer& TextureCache::getPermutationNormalTexture() {
data[i + 2] = ((randvec.z + 1.0f) / 2.0f) * 255.0f;
}
_permutationNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB), 256, 2));
_permutationNormalTexture = gpu::Texture::create2D(gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB), 256, 2);
_permutationNormalTexture->setStoredMipFormat(_permutationNormalTexture->getTexelFormat());
_permutationNormalTexture->assignStoredMip(0, sizeof(data), data);
}
@ -131,7 +125,7 @@ const unsigned char OPAQUE_BLACK[] = { 0x00, 0x00, 0x00, 0xFF };
const gpu::TexturePointer& TextureCache::getWhiteTexture() {
if (!_whiteTexture) {
_whiteTexture = gpu::TexturePointer(gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1));
_whiteTexture = gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1);
_whiteTexture->setSource("TextureCache::_whiteTexture");
_whiteTexture->setStoredMipFormat(_whiteTexture->getTexelFormat());
_whiteTexture->assignStoredMip(0, sizeof(OPAQUE_WHITE), OPAQUE_WHITE);
@ -141,7 +135,7 @@ const gpu::TexturePointer& TextureCache::getWhiteTexture() {
const gpu::TexturePointer& TextureCache::getGrayTexture() {
if (!_grayTexture) {
_grayTexture = gpu::TexturePointer(gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1));
_grayTexture = gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1);
_grayTexture->setSource("TextureCache::_grayTexture");
_grayTexture->setStoredMipFormat(_grayTexture->getTexelFormat());
_grayTexture->assignStoredMip(0, sizeof(OPAQUE_GRAY), OPAQUE_GRAY);
@ -151,7 +145,7 @@ const gpu::TexturePointer& TextureCache::getGrayTexture() {
const gpu::TexturePointer& TextureCache::getBlueTexture() {
if (!_blueTexture) {
_blueTexture = gpu::TexturePointer(gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1));
_blueTexture = gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1);
_blueTexture->setSource("TextureCache::_blueTexture");
_blueTexture->setStoredMipFormat(_blueTexture->getTexelFormat());
_blueTexture->assignStoredMip(0, sizeof(OPAQUE_BLUE), OPAQUE_BLUE);
@ -161,7 +155,7 @@ const gpu::TexturePointer& TextureCache::getBlueTexture() {
const gpu::TexturePointer& TextureCache::getBlackTexture() {
if (!_blackTexture) {
_blackTexture = gpu::TexturePointer(gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1));
_blackTexture = gpu::Texture::createStrict(gpu::Element::COLOR_RGBA_32, 1, 1);
_blackTexture->setSource("TextureCache::_blackTexture");
_blackTexture->setStoredMipFormat(_blackTexture->getTexelFormat());
_blackTexture->assignStoredMip(0, sizeof(OPAQUE_BLACK), OPAQUE_BLACK);
@ -172,18 +166,18 @@ const gpu::TexturePointer& TextureCache::getBlackTexture() {
/// Extra data for creating textures.
class TextureExtra {
public:
NetworkTexture::Type type;
image::TextureUsage::Type type;
const QByteArray& content;
int maxNumPixels;
};
ScriptableResource* TextureCache::prefetch(const QUrl& url, int type, int maxNumPixels) {
auto byteArray = QByteArray();
TextureExtra extra = { (Type)type, byteArray, maxNumPixels };
TextureExtra extra = { (image::TextureUsage::Type)type, byteArray, maxNumPixels };
return ResourceCache::prefetch(url, &extra);
}
NetworkTexturePointer TextureCache::getTexture(const QUrl& url, Type type, const QByteArray& content, int maxNumPixels) {
NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) {
TextureExtra extra = { type, content, maxNumPixels };
return ResourceCache::getResource(url, QUrl(), &extra).staticCast<NetworkTexture>();
}
@ -216,8 +210,7 @@ gpu::TexturePointer TextureCache::cacheTextureByHash(const std::string& hash, co
return result;
}
gpu::TexturePointer getFallbackTextureForType(NetworkTexture::Type type) {
gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) {
gpu::TexturePointer result;
auto textureCache = DependencyManager::get<TextureCache>();
// Since this can be called on a background thread, there's a chance that the cache
@ -226,116 +219,51 @@ gpu::TexturePointer getFallbackTextureForType(NetworkTexture::Type type) {
return result;
}
switch (type) {
case NetworkTexture::DEFAULT_TEXTURE:
case NetworkTexture::ALBEDO_TEXTURE:
case NetworkTexture::ROUGHNESS_TEXTURE:
case NetworkTexture::OCCLUSION_TEXTURE:
case image::TextureUsage::DEFAULT_TEXTURE:
case image::TextureUsage::ALBEDO_TEXTURE:
case image::TextureUsage::ROUGHNESS_TEXTURE:
case image::TextureUsage::OCCLUSION_TEXTURE:
result = textureCache->getWhiteTexture();
break;
case NetworkTexture::NORMAL_TEXTURE:
case image::TextureUsage::NORMAL_TEXTURE:
result = textureCache->getBlueTexture();
break;
case NetworkTexture::EMISSIVE_TEXTURE:
case NetworkTexture::LIGHTMAP_TEXTURE:
case image::TextureUsage::EMISSIVE_TEXTURE:
case image::TextureUsage::LIGHTMAP_TEXTURE:
result = textureCache->getBlackTexture();
break;
case NetworkTexture::BUMP_TEXTURE:
case NetworkTexture::SPECULAR_TEXTURE:
case NetworkTexture::GLOSS_TEXTURE:
case NetworkTexture::CUBE_TEXTURE:
case NetworkTexture::CUSTOM_TEXTURE:
case NetworkTexture::STRICT_TEXTURE:
case image::TextureUsage::BUMP_TEXTURE:
case image::TextureUsage::SPECULAR_TEXTURE:
case image::TextureUsage::GLOSS_TEXTURE:
case image::TextureUsage::CUBE_TEXTURE:
case image::TextureUsage::STRICT_TEXTURE:
default:
break;
}
return result;
}
NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type type,
const QVariantMap& options = QVariantMap()) {
using Type = NetworkTexture;
switch (type) {
case Type::ALBEDO_TEXTURE: {
return model::TextureUsage::createAlbedoTextureFromImage;
break;
}
case Type::EMISSIVE_TEXTURE: {
return model::TextureUsage::createEmissiveTextureFromImage;
break;
}
case Type::LIGHTMAP_TEXTURE: {
return model::TextureUsage::createLightmapTextureFromImage;
break;
}
case Type::CUBE_TEXTURE: {
if (options.value("generateIrradiance", true).toBool()) {
return model::TextureUsage::createCubeTextureFromImage;
} else {
return model::TextureUsage::createCubeTextureFromImageWithoutIrradiance;
}
break;
}
case Type::BUMP_TEXTURE: {
return model::TextureUsage::createNormalTextureFromBumpImage;
break;
}
case Type::NORMAL_TEXTURE: {
return model::TextureUsage::createNormalTextureFromNormalImage;
break;
}
case Type::ROUGHNESS_TEXTURE: {
return model::TextureUsage::createRoughnessTextureFromImage;
break;
}
case Type::GLOSS_TEXTURE: {
return model::TextureUsage::createRoughnessTextureFromGlossImage;
break;
}
case Type::SPECULAR_TEXTURE: {
return model::TextureUsage::createMetallicTextureFromImage;
break;
}
case Type::STRICT_TEXTURE: {
return model::TextureUsage::createStrict2DTextureFromImage;
break;
}
case Type::CUSTOM_TEXTURE: {
Q_ASSERT(false);
return NetworkTexture::TextureLoaderFunc();
break;
}
case Type::DEFAULT_TEXTURE:
default: {
return model::TextureUsage::create2DTextureFromImage;
break;
}
}
}
/// Returns a texture version of an image file
gpu::TexturePointer TextureCache::getImageTexture(const QString& path, Type type, QVariantMap options) {
gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) {
QImage image = QImage(path);
auto loader = getTextureLoaderForType(type, options);
auto loader = image::TextureUsage::getTextureLoaderForType(type, options);
return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString()));
}
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
const void* extra) {
const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
auto type = textureExtra ? textureExtra->type : Type::DEFAULT_TEXTURE;
auto type = textureExtra ? textureExtra->type : image::TextureUsage::DEFAULT_TEXTURE;
auto content = textureExtra ? textureExtra->content : QByteArray();
auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS;
NetworkTexture* texture = new NetworkTexture(url, type, content, maxNumPixels);
return QSharedPointer<Resource>(texture, &Resource::deleter);
}
NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& content, int maxNumPixels) :
NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) :
Resource(url),
_type(type),
_maxNumPixels(maxNumPixels)
@ -353,13 +281,6 @@ NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& con
}
}
NetworkTexture::TextureLoaderFunc NetworkTexture::getTextureLoader() const {
if (_type == CUSTOM_TEXTURE) {
return _textureLoader;
}
return getTextureLoaderForType(_type);
}
void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth,
int originalHeight) {
_originalWidth = originalWidth;
@ -384,34 +305,22 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth,
}
gpu::TexturePointer NetworkTexture::getFallbackTexture() const {
if (_type == CUSTOM_TEXTURE) {
return gpu::TexturePointer();
}
return getFallbackTextureForType(_type);
}
class Reader : public QRunnable {
public:
Reader(const QWeakPointer<Resource>& resource, const QUrl& url);
void run() override final;
virtual void read() = 0;
protected:
QWeakPointer<Resource> _resource;
QUrl _url;
};
class ImageReader : public Reader {
class ImageReader : public QRunnable {
public:
ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url,
const QByteArray& data, const std::string& hash, int maxNumPixels);
void read() override final;
const QByteArray& data, int maxNumPixels);
void run() override final;
void read();
private:
static void listSupportedImageFormats();
QWeakPointer<Resource> _resource;
QUrl _url;
QByteArray _content;
std::string _hash;
int _maxNumPixels;
};
@ -420,71 +329,16 @@ void NetworkTexture::downloadFinished(const QByteArray& data) {
}
void NetworkTexture::loadContent(const QByteArray& content) {
// Hash the source image to for KTX caching
std::string hash;
{
QCryptographicHash hasher(QCryptographicHash::Md5);
hasher.addData(content);
hash = hasher.result().toHex().toStdString();
}
auto textureCache = static_cast<TextureCache*>(_cache.data());
if (textureCache != nullptr) {
// If we already have a live texture with the same hash, use it
auto texture = textureCache->getTextureByHash(hash);
// If there is no live texture, check if there's an existing KTX file
if (!texture) {
KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
if (ktxFile) {
texture.reset(gpu::Texture::unserialize(ktxFile->getFilepath()));
if (texture) {
texture = textureCache->cacheTextureByHash(hash, texture);
}
}
}
// If we found the texture either because it's in use or via KTX deserialization,
// set the image and return immediately.
if (texture) {
setImage(texture, texture->getWidth(), texture->getHeight());
return;
}
}
// We failed to find an existing live or KTX texture, so trigger an image reader
QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, hash, _maxNumPixels));
QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels));
}
Reader::Reader(const QWeakPointer<Resource>& resource, const QUrl& url) :
_resource(resource), _url(url) {
ImageReader::ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url, const QByteArray& data, int maxNumPixels) :
_resource(resource),
_url(url),
_content(data),
_maxNumPixels(maxNumPixels)
{
DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing");
}
void Reader::run() {
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } });
DependencyManager::get<StatTracker>()->decrementStat("PendingProcessing");
CounterStat counter("Processing");
auto originalPriority = QThread::currentThread()->priority();
if (originalPriority == QThread::InheritPriority) {
originalPriority = QThread::NormalPriority;
}
QThread::currentThread()->setPriority(QThread::LowPriority);
Finally restorePriority([originalPriority]{ QThread::currentThread()->setPriority(originalPriority); });
if (!_resource.data()) {
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
return;
}
read();
}
ImageReader::ImageReader(const QWeakPointer<Resource>& resource, const QUrl& url,
const QByteArray& data, const std::string& hash, int maxNumPixels) :
Reader(resource, url), _content(data), _hash(hash), _maxNumPixels(maxNumPixels) {
listSupportedImageFormats();
#if DEBUG_DUMP_TEXTURE_LOADS
@ -515,89 +369,110 @@ void ImageReader::listSupportedImageFormats() {
});
}
void ImageReader::read() {
// Help the QImage loader by extracting the image file format from the url filename ext.
// Some tga are not created properly without it.
auto filename = _url.fileName().toStdString();
auto filenameExtension = filename.substr(filename.find_last_of('.') + 1);
QImage image = QImage::fromData(_content, filenameExtension.c_str());
int imageWidth = image.width();
int imageHeight = image.height();
void ImageReader::run() {
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } });
DependencyManager::get<StatTracker>()->decrementStat("PendingProcessing");
CounterStat counter("Processing");
// Validate that the image loaded
if (imageWidth == 0 || imageHeight == 0 || image.format() == QImage::Format_Invalid) {
QString reason(filenameExtension.empty() ? "" : "(no file extension)");
qCWarning(modelnetworking) << "Failed to load" << _url << reason;
auto originalPriority = QThread::currentThread()->priority();
if (originalPriority == QThread::InheritPriority) {
originalPriority = QThread::NormalPriority;
}
QThread::currentThread()->setPriority(QThread::LowPriority);
Finally restorePriority([originalPriority] { QThread::currentThread()->setPriority(originalPriority); });
read();
}
void ImageReader::read() {
auto resource = _resource.lock(); // to ensure the resource is still needed
if (!resource) {
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
return;
}
auto networkTexture = resource.staticCast<NetworkTexture>();
// Validate the image is less than _maxNumPixels, and downscale if necessary
if (imageWidth * imageHeight > _maxNumPixels) {
float scaleFactor = sqrtf(_maxNumPixels / (float)(imageWidth * imageHeight));
int originalWidth = imageWidth;
int originalHeight = imageHeight;
imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f);
imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f);
QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
image.swap(newImage);
qCDebug(modelnetworking).nospace() << "Downscaled " << _url << " (" <<
QSize(originalWidth, originalHeight) << " to " <<
QSize(imageWidth, imageHeight) << ")";
// Hash the source image to for KTX caching
std::string hash;
{
QCryptographicHash hasher(QCryptographicHash::Md5);
hasher.addData(_content);
hash = hasher.result().toHex().toStdString();
}
gpu::TexturePointer texture = nullptr;
// Maybe load from cache
auto textureCache = DependencyManager::get<TextureCache>();
if (textureCache) {
// If we already have a live texture with the same hash, use it
auto texture = textureCache->getTextureByHash(hash);
// If there is no live texture, check if there's an existing KTX file
if (!texture) {
KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
if (ktxFile) {
texture = gpu::Texture::unserialize(ktxFile->getFilepath());
if (texture) {
texture = textureCache->cacheTextureByHash(hash, texture);
}
}
}
// If we found the texture either because it's in use or via KTX deserialization,
// set the image and return immediately.
if (texture) {
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, texture->getWidth()),
Q_ARG(int, texture->getHeight()));
return;
}
}
// Proccess new texture
gpu::TexturePointer texture;
{
auto resource = _resource.lock(); // to ensure the resource is still needed
if (!resource) {
qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope";
PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0);
texture = image::processImage(_content, _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType());
if (!texture) {
qCWarning(modelnetworking) << "Could not process:" << _url;
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
auto url = _url.toString().toStdString();
texture->setSourceHash(hash);
texture->setFallbackTexture(networkTexture->getFallbackTexture());
}
PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0);
// Load the image into a gpu::Texture
auto networkTexture = resource.staticCast<NetworkTexture>();
texture.reset(networkTexture->getTextureLoader()(image, url));
texture->setSource(url);
if (texture) {
texture->setFallbackTexture(networkTexture->getFallbackTexture());
}
auto textureCache = DependencyManager::get<TextureCache>();
// Save the image into a KTXFile
// Save the image into a KTXFile
if (texture && textureCache) {
auto memKtx = gpu::Texture::serialize(*texture);
if (!memKtx) {
qCWarning(modelnetworking) << "Unable to serialize texture to KTX " << _url;
}
if (memKtx && textureCache) {
if (memKtx) {
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
size_t length = memKtx->_storage->size();
KTXFilePointer file;
auto& ktxCache = textureCache->_ktxCache;
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(_hash, length)))) {
networkTexture->_file = ktxCache.writeFile(data, KTXCache::Metadata(hash, length));
if (!networkTexture->_file) {
qCWarning(modelnetworking) << _url << "file cache failed";
} else {
resource.staticCast<NetworkTexture>()->_file = file;
texture->setKtxBacking(file->getFilepath());
texture->setKtxBacking(networkTexture->_file->getFilepath());
}
} else {
qCWarning(modelnetworking) << "Unable to serialize texture to KTX " << _url;
}
// We replace the texture with the one stored in the cache. This deals with the possible race condition of two different
// images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will
// be the winner
if (textureCache) {
texture = textureCache->cacheTextureByHash(_hash, texture);
}
texture = textureCache->cacheTextureByHash(hash, texture);
}
auto resource = _resource.lock(); // to ensure the resource is still needed
if (resource) {
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, imageWidth), Q_ARG(int, imageHeight));
} else {
qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope";
}
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, texture->getWidth()),
Q_ARG(int, texture->getHeight()));
}

View file

@ -22,6 +22,7 @@
#include <DependencyManager.h>
#include <ResourceCache.h>
#include <model/TextureMap.h>
#include <image/Image.h>
#include "KTXCache.h"
@ -43,29 +44,7 @@ class NetworkTexture : public Resource, public Texture {
Q_OBJECT
public:
enum Type {
DEFAULT_TEXTURE,
STRICT_TEXTURE,
ALBEDO_TEXTURE,
NORMAL_TEXTURE,
BUMP_TEXTURE,
SPECULAR_TEXTURE,
METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey
ROUGHNESS_TEXTURE,
GLOSS_TEXTURE,
EMISSIVE_TEXTURE,
CUBE_TEXTURE,
OCCLUSION_TEXTURE,
SCATTERING_TEXTURE = OCCLUSION_TEXTURE,
LIGHTMAP_TEXTURE,
CUSTOM_TEXTURE
};
Q_ENUM(Type)
typedef gpu::Texture* TextureLoader(const QImage& image, const std::string& srcImageName);
using TextureLoaderFunc = std::function<TextureLoader>;
NetworkTexture(const QUrl& url, Type type, const QByteArray& content, int maxNumPixels);
NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels);
QString getType() const override { return "NetworkTexture"; }
@ -73,9 +52,8 @@ public:
int getOriginalHeight() const { return _originalHeight; }
int getWidth() const { return _width; }
int getHeight() const { return _height; }
Type getTextureType() const { return _type; }
image::TextureUsage::Type getTextureType() const { return _type; }
TextureLoaderFunc getTextureLoader() const;
gpu::TexturePointer getFallbackTexture() const;
signals:
@ -93,8 +71,7 @@ private:
friend class KTXReader;
friend class ImageReader;
Type _type;
TextureLoaderFunc _textureLoader { [](const QImage&, const std::string&){ return nullptr; } };
image::TextureUsage::Type _type;
KTXFilePointer _file;
int _originalWidth { 0 };
int _originalHeight { 0 };
@ -110,8 +87,6 @@ class TextureCache : public ResourceCache, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
using Type = NetworkTexture::Type;
public:
/// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture
/// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and
@ -131,10 +106,10 @@ public:
const gpu::TexturePointer& getBlackTexture();
/// Returns a texture version of an image file
static gpu::TexturePointer getImageTexture(const QString& path, Type type = Type::DEFAULT_TEXTURE, QVariantMap options = QVariantMap());
static gpu::TexturePointer getImageTexture(const QString& path, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE, QVariantMap options = QVariantMap());
/// Loads a texture from the specified URL.
NetworkTexturePointer getTexture(const QUrl& url, Type type = Type::DEFAULT_TEXTURE,
NetworkTexturePointer getTexture(const QUrl& url, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE,
const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);

View file

@ -1,5 +1,4 @@
set(TARGET_NAME model)
AUTOSCRIBE_SHADER_LIB(gpu model)
setup_hifi_library()
link_hifi_libraries(shared ktx gpu)
link_hifi_libraries(shared ktx gpu image)

View file

@ -10,81 +10,9 @@
//
#include "TextureMap.h"
#include <ktx/KTX.h>
#include <QImage>
#include <QPainter>
#include <QDebug>
#include <QStandardPaths>
#include <QFileInfo>
#include <QDir>
#include <QCryptographicHash>
#include <Profile.h>
#include "ModelLogging.h"
using namespace model;
using namespace gpu;
// FIXME: Declare this to enable compression
//#define COMPRESS_TEXTURES
static const uvec2 SPARSE_PAGE_SIZE(128);
static const uvec2 MAX_TEXTURE_SIZE(4096);
bool DEV_DECIMATE_TEXTURES = false;
bool needsSparseRectification(const uvec2& size) {
// Don't attempt to rectify small textures (textures less than the sparse page size in any dimension)
if (glm::any(glm::lessThan(size, SPARSE_PAGE_SIZE))) {
return false;
}
// Don't rectify textures that are already an exact multiple of sparse page size
if (uvec2(0) == (size % SPARSE_PAGE_SIZE)) {
return false;
}
// Texture is not sparse compatible, but is bigger than the sparse page size in both dimensions, rectify!
return true;
}
uvec2 rectifyToSparseSize(const uvec2& size) {
uvec2 pages = ((size / SPARSE_PAGE_SIZE) + glm::clamp(size % SPARSE_PAGE_SIZE, uvec2(0), uvec2(1)));
uvec2 result = pages * SPARSE_PAGE_SIZE;
return result;
}
std::atomic<size_t> DECIMATED_TEXTURE_COUNT { 0 };
std::atomic<size_t> RECTIFIED_TEXTURE_COUNT { 0 };
QImage processSourceImage(const QImage& srcImage, bool cubemap) {
PROFILE_RANGE(resource_parse, "processSourceImage");
const uvec2 srcImageSize = toGlm(srcImage.size());
uvec2 targetSize = srcImageSize;
while (glm::any(glm::greaterThan(targetSize, MAX_TEXTURE_SIZE))) {
targetSize /= 2;
}
if (targetSize != srcImageSize) {
++DECIMATED_TEXTURE_COUNT;
}
if (!cubemap && needsSparseRectification(targetSize)) {
++RECTIFIED_TEXTURE_COUNT;
targetSize = rectifyToSparseSize(targetSize);
}
if (DEV_DECIMATE_TEXTURES && glm::all(glm::greaterThanEqual(targetSize / SPARSE_PAGE_SIZE, uvec2(2)))) {
targetSize /= 2;
}
if (targetSize != srcImageSize) {
PROFILE_RANGE(resource_parse, "processSourceImage Rectify");
qCDebug(modelLog) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y;
return srcImage.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
return srcImage;
}
void TextureMap::setTextureSource(TextureSourcePointer& textureSource) {
_textureSource = textureSource;
}
@ -113,758 +41,3 @@ void TextureMap::setLightmapOffsetScale(float offset, float scale) {
_lightmapOffsetScale.x = offset;
_lightmapOffsetScale.y = scale;
}
const QImage TextureUsage::process2DImageColor(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask) {
PROFILE_RANGE(resource_parse, "process2DImageColor");
QImage image = processSourceImage(srcImage, false);
validAlpha = false;
alphaAsMask = true;
const uint8 OPAQUE_ALPHA = 255;
const uint8 TRANSPARENT_ALPHA = 0;
if (image.hasAlphaChannel()) {
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
// Figure out if we can use a mask for alpha or not
int numOpaques = 0;
int numTranslucents = 0;
const int NUM_PIXELS = image.width() * image.height();
const int MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK = (int)(0.05f * (float)(NUM_PIXELS));
const QRgb* data = reinterpret_cast<const QRgb*>(image.constBits());
for (int i = 0; i < NUM_PIXELS; ++i) {
auto alpha = qAlpha(data[i]);
if (alpha == OPAQUE_ALPHA) {
numOpaques++;
} else if (alpha != TRANSPARENT_ALPHA) {
if (++numTranslucents > MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK) {
alphaAsMask = false;
break;
}
}
}
validAlpha = (numOpaques != NUM_PIXELS);
}
// Force all the color images to be rgba32bits
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
return image;
}
void TextureUsage::defineColorTexelFormats(gpu::Element& formatGPU, gpu::Element& formatMip,
const QImage& image, bool isLinear, bool doCompress) {
#ifdef COMPRESS_TEXTURES
#else
doCompress = false;
#endif
if (image.hasAlphaChannel()) {
gpu::Semantic gpuSemantic;
gpu::Semantic mipSemantic;
if (isLinear) {
mipSemantic = gpu::BGRA;
if (doCompress) {
gpuSemantic = gpu::COMPRESSED_RGBA;
} else {
gpuSemantic = gpu::RGBA;
}
} else {
mipSemantic = gpu::SBGRA;
if (doCompress) {
gpuSemantic = gpu::COMPRESSED_SRGBA;
} else {
gpuSemantic = gpu::SRGBA;
}
}
formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpuSemantic);
formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, mipSemantic);
} else {
gpu::Semantic gpuSemantic;
gpu::Semantic mipSemantic;
if (isLinear) {
mipSemantic = gpu::RGB;
if (doCompress) {
gpuSemantic = gpu::COMPRESSED_RGB;
} else {
gpuSemantic = gpu::RGB;
}
} else {
mipSemantic = gpu::SRGB;
if (doCompress) {
gpuSemantic = gpu::COMPRESSED_SRGB;
} else {
gpuSemantic = gpu::SRGB;
}
}
formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpuSemantic);
formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, mipSemantic);
}
}
#define CPU_MIPMAPS 1
void generateMips(gpu::Texture* texture, QImage& image, bool fastResize) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateMips");
auto numMips = texture->getNumMips();
for (uint16 level = 1; level < numMips; ++level) {
QSize mipSize(texture->evalMipWidth(level), texture->evalMipHeight(level));
if (fastResize) {
image = image.scaled(mipSize);
texture->assignStoredMip(level, image.byteCount(), image.constBits());
} else {
QImage mipImage = image.scaled(mipSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
texture->assignStoredMip(level, mipImage.byteCount(), mipImage.constBits());
}
}
#else
texture->autoGenerateMips(-1);
#endif
}
void generateFaceMips(gpu::Texture* texture, QImage& image, uint8 face) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateFaceMips");
auto numMips = texture->getNumMips();
for (uint16 level = 1; level < numMips; ++level) {
QSize mipSize(texture->evalMipWidth(level), texture->evalMipHeight(level));
QImage mipImage = image.scaled(mipSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
texture->assignStoredMipFace(level, face, mipImage.byteCount(), mipImage.constBits());
}
#else
texture->autoGenerateMips(-1);
#endif
}
gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool isStrict) {
PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage");
bool validAlpha = false;
bool alphaAsMask = true;
QImage image = process2DImageColor(srcImage, validAlpha, alphaAsMask);
gpu::Texture* theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
gpu::Element formatGPU;
gpu::Element formatMip;
defineColorTexelFormats(formatGPU, formatMip, image, isLinear, doCompress);
if (isStrict) {
theTexture = (gpu::Texture::createStrict(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
} else {
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
}
theTexture->setSource(srcImageName);
auto usage = gpu::Texture::Usage::Builder().withColor();
if (validAlpha) {
usage.withAlpha();
if (alphaAsMask) {
usage.withAlphaMask();
}
}
theTexture->setUsage(usage.build());
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
if (generateMips) {
::generateMips(theTexture, image, false);
}
theTexture->setSource(srcImageName);
}
return theTexture;
}
gpu::Texture* TextureUsage::createStrict2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, false, true, true);
}
gpu::Texture* TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, false, true);
}
gpu::Texture* TextureUsage::createAlbedoTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, true, true);
}
gpu::Texture* TextureUsage::createEmissiveTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, true, true);
}
gpu::Texture* TextureUsage::createLightmapTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, true, true);
}
gpu::Texture* TextureUsage::createNormalTextureFromNormalImage(const QImage& srcImage, const std::string& srcImageName) {
PROFILE_RANGE(resource_parse, "createNormalTextureFromNormalImage");
QImage image = processSourceImage(srcImage, false);
// Make sure the normal map source image is ARGB32
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
gpu::Texture* theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
gpu::Element formatMip = gpu::Element::COLOR_BGRA_32;
gpu::Element formatGPU = gpu::Element::COLOR_RGBA_32;
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
generateMips(theTexture, image, true);
theTexture->setSource(srcImageName);
}
return theTexture;
}
int clampPixelCoordinate(int coordinate, int maxCoordinate) {
return coordinate - ((int)(coordinate < 0) * coordinate) + ((int)(coordinate > maxCoordinate) * (maxCoordinate - coordinate));
}
const int RGBA_MAX = 255;
// transform -1 - 1 to 0 - 255 (from sobel value to rgb)
double mapComponent(double sobelValue) {
const double factor = RGBA_MAX / 2.0;
return (sobelValue + 1.0) * factor;
}
gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcImage, const std::string& srcImageName) {
PROFILE_RANGE(resource_parse, "createNormalTextureFromBumpImage");
QImage image = processSourceImage(srcImage, false);
if (image.format() != QImage::Format_Grayscale8) {
image = image.convertToFormat(QImage::Format_Grayscale8);
}
// PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps
// The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image
const double pStrength = 2.0;
int width = image.width();
int height = image.height();
QImage result(width, height, QImage::Format_ARGB32);
for (int i = 0; i < width; i++) {
const int iNextClamped = clampPixelCoordinate(i + 1, width - 1);
const int iPrevClamped = clampPixelCoordinate(i - 1, width - 1);
for (int j = 0; j < height; j++) {
const int jNextClamped = clampPixelCoordinate(j + 1, height - 1);
const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1);
// surrounding pixels
const QRgb topLeft = image.pixel(iPrevClamped, jPrevClamped);
const QRgb top = image.pixel(iPrevClamped, j);
const QRgb topRight = image.pixel(iPrevClamped, jNextClamped);
const QRgb right = image.pixel(i, jNextClamped);
const QRgb bottomRight = image.pixel(iNextClamped, jNextClamped);
const QRgb bottom = image.pixel(iNextClamped, j);
const QRgb bottomLeft = image.pixel(iNextClamped, jPrevClamped);
const QRgb left = image.pixel(i, jPrevClamped);
// take their gray intensities
// since it's a grayscale image, the value of each component RGB is the same
const double tl = qRed(topLeft);
const double t = qRed(top);
const double tr = qRed(topRight);
const double r = qRed(right);
const double br = qRed(bottomRight);
const double b = qRed(bottom);
const double bl = qRed(bottomLeft);
const double l = qRed(left);
// apply the sobel filter
const double dX = (tr + pStrength * r + br) - (tl + pStrength * l + bl);
const double dY = (bl + pStrength * b + br) - (tl + pStrength * t + tr);
const double dZ = RGBA_MAX / pStrength;
glm::vec3 v(dX, dY, dZ);
glm::normalize(v);
// convert to rgb from the value obtained computing the filter
QRgb qRgbValue = qRgba(mapComponent(v.z), mapComponent(v.y), mapComponent(v.x), 1.0);
result.setPixel(i, j, qRgbValue);
}
}
gpu::Texture* theTexture = nullptr;
if ((result.width() > 0) && (result.height() > 0)) {
gpu::Element formatMip = gpu::Element::COLOR_BGRA_32;
gpu::Element formatGPU = gpu::Element::COLOR_RGBA_32;
theTexture = (gpu::Texture::create2D(formatGPU, result.width(), result.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, result.byteCount(), result.constBits());
generateMips(theTexture, result, true);
theTexture->setSource(srcImageName);
}
return theTexture;
}
gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
PROFILE_RANGE(resource_parse, "createRoughnessTextureFromImage");
QImage image = processSourceImage(srcImage, false);
if (!image.hasAlphaChannel()) {
if (image.format() != QImage::Format_RGB888) {
image = image.convertToFormat(QImage::Format_RGB888);
}
} else {
if (image.format() != QImage::Format_RGBA8888) {
image = image.convertToFormat(QImage::Format_RGBA8888);
}
}
image = image.convertToFormat(QImage::Format_Grayscale8);
gpu::Texture* theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
#ifdef COMPRESS_TEXTURES
gpu::Element formatGPU = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::COMPRESSED_R);
#else
gpu::Element formatGPU = gpu::Element::COLOR_R_8;
#endif
gpu::Element formatMip = gpu::Element::COLOR_R_8;
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
generateMips(theTexture, image, true);
theTexture->setSource(srcImageName);
}
return theTexture;
}
gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& srcImage, const std::string& srcImageName) {
PROFILE_RANGE(resource_parse, "createRoughnessTextureFromGlossImage");
QImage image = processSourceImage(srcImage, false);
if (!image.hasAlphaChannel()) {
if (image.format() != QImage::Format_RGB888) {
image = image.convertToFormat(QImage::Format_RGB888);
}
} else {
if (image.format() != QImage::Format_RGBA8888) {
image = image.convertToFormat(QImage::Format_RGBA8888);
}
}
// Gloss turned into Rough
image.invertPixels(QImage::InvertRgba);
image = image.convertToFormat(QImage::Format_Grayscale8);
gpu::Texture* theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
#ifdef COMPRESS_TEXTURES
gpu::Element formatGPU = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::COMPRESSED_R);
#else
gpu::Element formatGPU = gpu::Element::COLOR_R_8;
#endif
gpu::Element formatMip = gpu::Element::COLOR_R_8;
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
generateMips(theTexture, image, true);
theTexture->setSource(srcImageName);
}
return theTexture;
}
gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
PROFILE_RANGE(resource_parse, "createMetallicTextureFromImage");
QImage image = processSourceImage(srcImage, false);
if (!image.hasAlphaChannel()) {
if (image.format() != QImage::Format_RGB888) {
image = image.convertToFormat(QImage::Format_RGB888);
}
} else {
if (image.format() != QImage::Format_RGBA8888) {
image = image.convertToFormat(QImage::Format_RGBA8888);
}
}
image = image.convertToFormat(QImage::Format_Grayscale8);
gpu::Texture* theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
#ifdef COMPRESS_TEXTURES
gpu::Element formatGPU = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::COMPRESSED_R);
#else
gpu::Element formatGPU = gpu::Element::COLOR_R_8;
#endif
gpu::Element formatMip = gpu::Element::COLOR_R_8;
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
generateMips(theTexture, image, true);
theTexture->setSource(srcImageName);
}
return theTexture;
}
class CubeLayout {
public:
enum SourceProjection {
FLAT = 0,
EQUIRECTANGULAR,
};
int _type = FLAT;
int _widthRatio = 1;
int _heightRatio = 1;
class Face {
public:
int _x = 0;
int _y = 0;
bool _horizontalMirror = false;
bool _verticalMirror = false;
Face() {}
Face(int x, int y, bool horizontalMirror, bool verticalMirror) : _x(x), _y(y), _horizontalMirror(horizontalMirror), _verticalMirror(verticalMirror) {}
};
Face _faceXPos;
Face _faceXNeg;
Face _faceYPos;
Face _faceYNeg;
Face _faceZPos;
Face _faceZNeg;
CubeLayout(int wr, int hr, Face fXP, Face fXN, Face fYP, Face fYN, Face fZP, Face fZN) :
_type(FLAT),
_widthRatio(wr),
_heightRatio(hr),
_faceXPos(fXP),
_faceXNeg(fXN),
_faceYPos(fYP),
_faceYNeg(fYN),
_faceZPos(fZP),
_faceZNeg(fZN) {}
CubeLayout(int wr, int hr) :
_type(EQUIRECTANGULAR),
_widthRatio(wr),
_heightRatio(hr) {}
static const CubeLayout CUBEMAP_LAYOUTS[];
static const int NUM_CUBEMAP_LAYOUTS;
static int findLayout(int width, int height) {
// Find the layout of the cubemap in the 2D image
int foundLayout = -1;
for (int i = 0; i < NUM_CUBEMAP_LAYOUTS; i++) {
if ((height * CUBEMAP_LAYOUTS[i]._widthRatio) == (width * CUBEMAP_LAYOUTS[i]._heightRatio)) {
foundLayout = i;
break;
}
}
return foundLayout;
}
static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) {
QImage image(faceWidth, faceWidth, source.format());
glm::vec2 dstInvSize(1.0f / (float)image.width(), 1.0f / (float)image.height());
struct CubeToXYZ {
gpu::Texture::CubeFace _face;
CubeToXYZ(gpu::Texture::CubeFace face) : _face(face) {}
glm::vec3 xyzFrom(const glm::vec2& uv) {
auto faceDir = glm::normalize(glm::vec3(-1.0f + 2.0f * uv.x, -1.0f + 2.0f * uv.y, 1.0f));
switch (_face) {
case gpu::Texture::CubeFace::CUBE_FACE_BACK_POS_Z:
return glm::vec3(-faceDir.x, faceDir.y, faceDir.z);
case gpu::Texture::CubeFace::CUBE_FACE_FRONT_NEG_Z:
return glm::vec3(faceDir.x, faceDir.y, -faceDir.z);
case gpu::Texture::CubeFace::CUBE_FACE_LEFT_NEG_X:
return glm::vec3(faceDir.z, faceDir.y, faceDir.x);
case gpu::Texture::CubeFace::CUBE_FACE_RIGHT_POS_X:
return glm::vec3(-faceDir.z, faceDir.y, -faceDir.x);
case gpu::Texture::CubeFace::CUBE_FACE_BOTTOM_NEG_Y:
return glm::vec3(-faceDir.x, -faceDir.z, faceDir.y);
case gpu::Texture::CubeFace::CUBE_FACE_TOP_POS_Y:
default:
return glm::vec3(-faceDir.x, faceDir.z, -faceDir.y);
}
}
};
CubeToXYZ cubeToXYZ(face);
struct RectToXYZ {
RectToXYZ() {}
glm::vec2 uvFrom(const glm::vec3& xyz) {
auto flatDir = glm::normalize(glm::vec2(xyz.x, xyz.z));
auto uvRad = glm::vec2(atan2(flatDir.x, flatDir.y), asin(xyz.y));
const float LON_TO_RECT_U = 1.0f / (glm::pi<float>());
const float LAT_TO_RECT_V = 2.0f / glm::pi<float>();
return glm::vec2(0.5f * uvRad.x * LON_TO_RECT_U + 0.5f, 0.5f * uvRad.y * LAT_TO_RECT_V + 0.5f);
}
};
RectToXYZ rectToXYZ;
int srcFaceHeight = source.height();
int srcFaceWidth = source.width();
glm::vec2 dstCoord;
glm::ivec2 srcPixel;
for (int y = 0; y < faceWidth; ++y) {
dstCoord.y = 1.0f - (y + 0.5f) * dstInvSize.y; // Fill cube face images from top to bottom
for (int x = 0; x < faceWidth; ++x) {
dstCoord.x = (x + 0.5f) * dstInvSize.x;
auto xyzDir = cubeToXYZ.xyzFrom(dstCoord);
auto srcCoord = rectToXYZ.uvFrom(xyzDir);
srcPixel.x = floor(srcCoord.x * srcFaceWidth);
// Flip the vertical axis to QImage going top to bottom
srcPixel.y = floor((1.0f - srcCoord.y) * srcFaceHeight);
if (((uint32) srcPixel.x < (uint32) source.width()) && ((uint32) srcPixel.y < (uint32) source.height())) {
image.setPixel(x, y, source.pixel(QPoint(srcPixel.x, srcPixel.y)));
// Keep for debug, this is showing the dir as a color
// glm::u8vec4 rgba((xyzDir.x + 1.0)*0.5 * 256, (xyzDir.y + 1.0)*0.5 * 256, (xyzDir.z + 1.0)*0.5 * 256, 256);
// unsigned int val = 0xff000000 | (rgba.r) | (rgba.g << 8) | (rgba.b << 16);
// image.setPixel(x, y, val);
}
}
}
return image;
}
};
const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = {
// Here is the expected layout for the faces in an image with the 2/1 aspect ratio:
// THis is detected as an Equirectangular projection
// WIDTH
// <--------------------------->
// ^ +------+------+------+------+
// H | | | | |
// E | | | | |
// I | | | | |
// G +------+------+------+------+
// H | | | | |
// T | | | | |
// | | | | | |
// v +------+------+------+------+
//
// FaceWidth = width = height / 6
{ 2, 1 },
// Here is the expected layout for the faces in an image with the 1/6 aspect ratio:
//
// WIDTH
// <------>
// ^ +------+
// | | |
// | | +X |
// | | |
// H +------+
// E | |
// I | -X |
// G | |
// H +------+
// T | |
// | | +Y |
// | | |
// | +------+
// | | |
// | | -Y |
// | | |
// H +------+
// E | |
// I | +Z |
// G | |
// H +------+
// T | |
// | | -Z |
// | | |
// V +------+
//
// FaceWidth = width = height / 6
{ 1, 6,
{ 0, 0, true, false },
{ 0, 1, true, false },
{ 0, 2, false, true },
{ 0, 3, false, true },
{ 0, 4, true, false },
{ 0, 5, true, false }
},
// Here is the expected layout for the faces in an image with the 3/4 aspect ratio:
//
// <-----------WIDTH----------->
// ^ +------+------+------+------+
// | | | | | |
// | | | +Y | | |
// | | | | | |
// H +------+------+------+------+
// E | | | | |
// I | -X | -Z | +X | +Z |
// G | | | | |
// H +------+------+------+------+
// T | | | | |
// | | | -Y | | |
// | | | | | |
// V +------+------+------+------+
//
// FaceWidth = width / 4 = height / 3
{ 4, 3,
{ 2, 1, true, false },
{ 0, 1, true, false },
{ 1, 0, false, true },
{ 1, 2, false, true },
{ 3, 1, true, false },
{ 1, 1, true, false }
},
// Here is the expected layout for the faces in an image with the 4/3 aspect ratio:
//
// <-------WIDTH-------->
// ^ +------+------+------+
// | | | | |
// | | | +Y | |
// | | | | |
// H +------+------+------+
// E | | | |
// I | -X | -Z | +X |
// G | | | |
// H +------+------+------+
// T | | | |
// | | | -Y | |
// | | | | |
// | +------+------+------+
// | | | | |
// | | | +Z! | | <+Z is upside down!
// | | | | |
// V +------+------+------+
//
// FaceWidth = width / 3 = height / 4
{ 3, 4,
{ 2, 1, true, false },
{ 0, 1, true, false },
{ 1, 0, false, true },
{ 1, 2, false, true },
{ 1, 3, false, true },
{ 1, 1, true, false }
}
};
const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout);
gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool generateIrradiance) {
PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage");
gpu::Texture* theTexture = nullptr;
if ((srcImage.width() > 0) && (srcImage.height() > 0)) {
QImage image = processSourceImage(srcImage, true);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
gpu::Element formatGPU;
gpu::Element formatMip;
defineColorTexelFormats(formatGPU, formatMip, image, isLinear, doCompress);
// Find the layout of the cubemap in the 2D image
// Use the original image size since processSourceImage may have altered the size / aspect ratio
int foundLayout = CubeLayout::findLayout(srcImage.width(), srcImage.height());
std::vector<QImage> faces;
// If found, go extract the faces as separate images
if (foundLayout >= 0) {
auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout];
if (layout._type == CubeLayout::FLAT) {
int faceWidth = image.width() / layout._widthRatio;
faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror));
} else if (layout._type == CubeLayout::EQUIRECTANGULAR) {
// THe face width is estimated from the input image
const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4;
const int EQUIRECT_MAX_FACE_WIDTH = 2048;
int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH);
for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) {
QImage faceImage = CubeLayout::extractEquirectangularFace(image, (gpu::Texture::CubeFace) face, faceWidth);
faces.push_back(faceImage);
}
}
} else {
qCDebug(modelLog) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str());
return nullptr;
}
// If the 6 faces have been created go on and define the true Texture
if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) {
theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
int f = 0;
for (auto& face : faces) {
theTexture->assignStoredMipFace(0, f, face.byteCount(), face.constBits());
if (generateMips) {
generateFaceMips(theTexture, face, f);
}
f++;
}
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(resource_parse, "generateIrradiance");
theTexture->generateIrradiance();
}
theTexture->setSource(srcImageName);
}
}
return theTexture;
}
gpu::Texture* TextureUsage::createCubeTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
return processCubeTextureColorFromImage(srcImage, srcImageName, false, true, true, true);
}
gpu::Texture* TextureUsage::createCubeTextureFromImageWithoutIrradiance(const QImage& srcImage, const std::string& srcImageName) {
return processCubeTextureColorFromImage(srcImage, srcImageName, false, true, true, false);
}

View file

@ -13,48 +13,10 @@
#include "gpu/Texture.h"
#include "Material.h"
#include "Transform.h"
#include <qurl.h>
class QImage;
namespace model {
typedef glm::vec3 Color;
class TextureUsage {
public:
gpu::Texture::Type _type{ gpu::Texture::TEX_2D };
Material::MapFlags _materialUsage{ MaterialKey::ALBEDO_MAP };
int _environmentUsage = 0;
static gpu::Texture* create2DTextureFromImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createCubeTextureFromImage(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName);
static gpu::Texture* createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName);
static const QImage process2DImageColor(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask);
static void defineColorTexelFormats(gpu::Element& formatGPU, gpu::Element& formatMip,
const QImage& srcImage, bool isLinear, bool doCompress);
static gpu::Texture* process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool isStrict = false);
static gpu::Texture* processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool generateIrradiance);
};
class TextureMap {
public:
TextureMap() {}

View file

@ -644,8 +644,6 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
}
}
std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(PingType_t pingType) {
int packetSize = sizeof(PingType_t) + sizeof(quint64);

View file

@ -257,14 +257,14 @@ void NodeList::reset() {
_avatarGainMap.clear();
_avatarGainMapLock.unlock();
// refresh the owner UUID to the NULL UUID
setSessionUUID(QUuid());
if (sender() != &_domainHandler) {
// clear the domain connection information, unless they're the ones that asked us to reset
_domainHandler.softReset();
}
// refresh the owner UUID to the NULL UUID
setSessionUUID(QUuid());
// if we setup the DTLS socket, also disconnect from the DTLS socket readyRead() so it can handle handshaking
if (_dtlsSocket) {
disconnect(_dtlsSocket, 0, this, 0);

View file

@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
return VERSION_ENTITIES_ZONE_FILTERS;
return VERSION_ENTITIES_HINGE_CONSTRAINT;
case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::JSONFilterWithFamilyTree);
case PacketType::AvatarIdentity:

View file

@ -206,6 +206,7 @@ const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65;
const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66;
const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67;
const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68;
const PacketVersion VERSION_ENTITIES_HINGE_CONSTRAINT = 69;
enum class EntityQueryPacketVersion: PacketVersion {
JSONFilter = 18,

View file

@ -94,7 +94,7 @@ void EntityMotionState::updateServerPhysicsVariables() {
_serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration();
_serverActionData = _entity->getActionData();
_serverActionData = _entity->getDynamicData();
}
void EntityMotionState::handleDeactivation() {
@ -309,7 +309,7 @@ bool EntityMotionState::isCandidateForOwnership() const {
assert(entityTreeIsLocked());
return _outgoingPriority != 0
|| Physics::getSessionUUID() == _entity->getSimulatorID()
|| _entity->actionDataNeedsTransmit();
|| _entity->dynamicDataNeedsTransmit();
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
@ -335,7 +335,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
_serverAcceleration = Vectors::ZERO;
_serverAngularVelocity = worldVelocityToLocal.transform(bulletToGLM(_body->getAngularVelocity()));
_lastStep = simulationStep;
_serverActionData = _entity->getActionData();
_serverActionData = _entity->getDynamicData();
_numInactiveUpdates = 1;
return false;
}
@ -387,7 +387,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
}
}
if (_entity->actionDataNeedsTransmit()) {
if (_entity->dynamicDataNeedsTransmit()) {
_outgoingPriority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
return true;
}
@ -474,7 +474,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
return false;
}
if (_entity->actionDataNeedsTransmit()) {
if (_entity->dynamicDataNeedsTransmit()) {
return true;
}
@ -551,7 +551,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
_serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration();
_serverActionData = _entity->getActionData();
_serverActionData = _entity->getDynamicData();
EntityItemProperties properties;
@ -562,8 +562,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
properties.setVelocity(_serverVelocity);
properties.setAcceleration(_serverAcceleration);
properties.setAngularVelocity(_serverAngularVelocity);
if (_entity->actionDataNeedsTransmit()) {
_entity->setActionDataNeedsTransmit(false);
if (_entity->dynamicDataNeedsTransmit()) {
_entity->setDynamicDataNeedsTransmit(false);
properties.setActionData(_serverActionData);
}

View file

@ -16,13 +16,10 @@
#include "PhysicsLogging.h"
ObjectAction::ObjectAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) :
ObjectAction::ObjectAction(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) :
btActionInterface(),
EntityActionInterface(type, id),
_ownerEntity(ownerEntity) {
}
ObjectAction::~ObjectAction() {
ObjectDynamic(type, id, ownerEntity)
{
}
void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) {
@ -35,7 +32,7 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta
});
if (!ownerEntity) {
qCDebug(physics) << "warning -- action with no entity removing self from btCollisionWorld.";
qCDebug(physics) << "warning -- action [" << _tag << "] with no entity removing self from btCollisionWorld.";
btDynamicsWorld* dynamicsWorld = static_cast<btDynamicsWorld*>(collisionWorld);
if (dynamicsWorld) {
dynamicsWorld->removeAction(this);
@ -64,240 +61,5 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta
updateActionWorker(deltaTimeStep);
}
qint64 ObjectAction::getEntityServerClockSkew() const {
auto nodeList = DependencyManager::get<NodeList>();
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return 0;
}
const QUuid& entityServerNodeID = ownerEntity->getSourceUUID();
auto entityServerNode = nodeList->nodeWithUUID(entityServerNodeID);
if (entityServerNode) {
return entityServerNode->getClockSkewUsec();
}
return 0;
}
bool ObjectAction::updateArguments(QVariantMap arguments) {
bool somethingChanged = false;
withWriteLock([&]{
quint64 previousExpires = _expires;
QString previousTag = _tag;
bool ttlSet = true;
float ttl = EntityActionInterface::extractFloatArgument("action", arguments, "ttl", ttlSet, false);
if (ttlSet) {
quint64 now = usecTimestampNow();
_expires = now + (quint64)(ttl * USECS_PER_SECOND);
} else {
_expires = 0;
}
bool tagSet = true;
QString tag = EntityActionInterface::extractStringArgument("action", arguments, "tag", tagSet, false);
if (tagSet) {
_tag = tag;
} else {
tag = "";
}
if (previousExpires != _expires || previousTag != _tag) {
somethingChanged = true;
}
});
return somethingChanged;
}
QVariantMap ObjectAction::getArguments() {
QVariantMap arguments;
withReadLock([&]{
if (_expires == 0) {
arguments["ttl"] = 0.0f;
} else {
quint64 now = usecTimestampNow();
arguments["ttl"] = (float)(_expires - now) / (float)USECS_PER_SECOND;
}
arguments["tag"] = _tag;
EntityItemPointer entity = _ownerEntity.lock();
if (entity) {
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(entity->getPhysicsInfo());
if (motionState) {
arguments["::active"] = motionState->isActive();
arguments["::motion-type"] = motionTypeToString(motionState->getMotionType());
} else {
arguments["::no-motion-state"] = true;
}
}
arguments["isMine"] = isMine();
});
return arguments;
}
void ObjectAction::debugDraw(btIDebugDraw* debugDrawer) {
}
void ObjectAction::removeFromSimulation(EntitySimulationPointer simulation) const {
QUuid myID;
withReadLock([&]{
myID = _id;
});
simulation->removeAction(myID);
}
btRigidBody* ObjectAction::getRigidBody() {
ObjectMotionState* motionState = nullptr;
withReadLock([&]{
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
}
void* physicsInfo = ownerEntity->getPhysicsInfo();
if (!physicsInfo) {
return;
}
motionState = static_cast<ObjectMotionState*>(physicsInfo);
});
if (motionState) {
return motionState->getRigidBody();
}
return nullptr;
}
glm::vec3 ObjectAction::getPosition() {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return glm::vec3(0.0f);
}
return bulletToGLM(rigidBody->getCenterOfMassPosition());
}
void ObjectAction::setPosition(glm::vec3 position) {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return;
}
// XXX
// void setWorldTransform (const btTransform &worldTrans)
assert(false);
rigidBody->activate();
}
glm::quat ObjectAction::getRotation() {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return glm::quat(0.0f, 0.0f, 0.0f, 1.0f);
}
return bulletToGLM(rigidBody->getOrientation());
}
void ObjectAction::setRotation(glm::quat rotation) {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return;
}
// XXX
// void setWorldTransform (const btTransform &worldTrans)
assert(false);
rigidBody->activate();
}
glm::vec3 ObjectAction::getLinearVelocity() {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return glm::vec3(0.0f);
}
return bulletToGLM(rigidBody->getLinearVelocity());
}
void ObjectAction::setLinearVelocity(glm::vec3 linearVelocity) {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return;
}
rigidBody->setLinearVelocity(glmToBullet(glm::vec3(0.0f)));
rigidBody->activate();
}
glm::vec3 ObjectAction::getAngularVelocity() {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return glm::vec3(0.0f);
}
return bulletToGLM(rigidBody->getAngularVelocity());
}
void ObjectAction::setAngularVelocity(glm::vec3 angularVelocity) {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return;
}
rigidBody->setAngularVelocity(glmToBullet(angularVelocity));
rigidBody->activate();
}
void ObjectAction::activateBody(bool forceActivation) {
auto rigidBody = getRigidBody();
if (rigidBody) {
rigidBody->activate(forceActivation);
} else {
qCDebug(physics) << "ObjectAction::activateBody -- no rigid body" << (void*)rigidBody;
}
}
void ObjectAction::forceBodyNonStatic() {
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
}
void* physicsInfo = ownerEntity->getPhysicsInfo();
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
if (motionState && motionState->getMotionType() == MOTION_TYPE_STATIC) {
ownerEntity->flagForMotionStateChange();
}
}
bool ObjectAction::lifetimeIsOver() {
if (_expires == 0) {
return false;
}
quint64 now = usecTimestampNow();
if (now >= _expires) {
return true;
}
return false;
}
quint64 ObjectAction::localTimeToServerTime(quint64 timeValue) const {
// 0 indicates no expiration
if (timeValue == 0) {
return 0;
}
qint64 serverClockSkew = getEntityServerClockSkew();
if (serverClockSkew < 0 && timeValue <= (quint64)(-serverClockSkew)) {
return 1; // non-zero but long-expired value to avoid negative roll-over
}
return timeValue + serverClockSkew;
}
quint64 ObjectAction::serverTimeToLocalTime(quint64 timeValue) const {
// 0 indicates no expiration
if (timeValue == 0) {
return 0;
}
qint64 serverClockSkew = getEntityServerClockSkew();
if (serverClockSkew > 0 && timeValue <= (quint64)serverClockSkew) {
return 1; // non-zero but long-expired value to avoid negative roll-over
}
return timeValue - serverClockSkew;
}

View file

@ -14,27 +14,15 @@
#define hifi_ObjectAction_h
#include <QUuid>
#include <btBulletDynamicsCommon.h>
#include "ObjectDynamic.h"
#include <shared/ReadWriteLockable.h>
#include "ObjectMotionState.h"
#include "BulletUtil.h"
#include "EntityActionInterface.h"
class ObjectAction : public btActionInterface, public EntityActionInterface, public ReadWriteLockable {
class ObjectAction : public btActionInterface, public ObjectDynamic {
public:
ObjectAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectAction();
ObjectAction(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectAction() {}
virtual void removeFromSimulation(EntitySimulationPointer simulation) const override;
virtual EntityItemWeakPointer getOwnerEntity() const override { return _ownerEntity; }
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) override { _ownerEntity = ownerEntity; }
virtual bool updateArguments(QVariantMap arguments) override;
virtual QVariantMap getArguments() override;
virtual bool isAction() const override { return true; }
// this is called from updateAction and should be overridden by subclasses
virtual void updateActionWorker(float deltaTimeStep) = 0;
@ -42,35 +30,6 @@ public:
// these are from btActionInterface
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) override;
virtual void debugDraw(btIDebugDraw* debugDrawer) override;
virtual QByteArray serialize() const override = 0;
virtual void deserialize(QByteArray serializedArguments) override = 0;
virtual bool lifetimeIsOver() override;
virtual quint64 getExpires() override { return _expires; }
protected:
quint64 localTimeToServerTime(quint64 timeValue) const;
quint64 serverTimeToLocalTime(quint64 timeValue) const;
virtual btRigidBody* getRigidBody();
virtual glm::vec3 getPosition() override;
virtual void setPosition(glm::vec3 position) override;
virtual glm::quat getRotation() override;
virtual void setRotation(glm::quat rotation) override;
virtual glm::vec3 getLinearVelocity() override;
virtual void setLinearVelocity(glm::vec3 linearVelocity) override;
virtual glm::vec3 getAngularVelocity() override;
virtual void setAngularVelocity(glm::vec3 angularVelocity) override;
virtual void activateBody(bool forceActivation = false);
virtual void forceBodyNonStatic();
EntityItemWeakPointer _ownerEntity;
QString _tag;
quint64 _expires { 0 }; // in seconds since epoch
private:
qint64 getEntityServerClockSkew() const;
};
#endif // hifi_ObjectAction_h

View file

@ -19,7 +19,7 @@
const uint16_t ObjectActionOffset::offsetVersion = 1;
ObjectActionOffset::ObjectActionOffset(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectAction(ACTION_TYPE_OFFSET, id, ownerEntity),
ObjectAction(DYNAMIC_TYPE_OFFSET, id, ownerEntity),
_pointToOffsetFrom(0.0f),
_linearDistance(0.0f),
_linearTimeScale(FLT_MAX),
@ -88,26 +88,26 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
float linearDistance;
bool needUpdate = false;
bool somethingChanged = ObjectAction::updateArguments(arguments);
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
withReadLock([&]{
bool ok = true;
pointToOffsetFrom =
EntityActionInterface::extractVec3Argument("offset action", arguments, "pointToOffsetFrom", ok, true);
EntityDynamicInterface::extractVec3Argument("offset action", arguments, "pointToOffsetFrom", ok, true);
if (!ok) {
pointToOffsetFrom = _pointToOffsetFrom;
}
ok = true;
linearTimeScale =
EntityActionInterface::extractFloatArgument("offset action", arguments, "linearTimeScale", ok, false);
EntityDynamicInterface::extractFloatArgument("offset action", arguments, "linearTimeScale", ok, false);
if (!ok) {
linearTimeScale = _linearTimeScale;
}
ok = true;
linearDistance =
EntityActionInterface::extractFloatArgument("offset action", arguments, "linearDistance", ok, false);
EntityDynamicInterface::extractFloatArgument("offset action", arguments, "linearDistance", ok, false);
if (!ok) {
linearDistance = _linearDistance;
}
@ -132,8 +132,8 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setActionDataDirty(true);
ownerEntity->setActionDataNeedsTransmit(true);
ownerEntity->setDynamicDataDirty(true);
ownerEntity->setDynamicDataNeedsTransmit(true);
}
});
activateBody();
@ -143,7 +143,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
}
QVariantMap ObjectActionOffset::getArguments() {
QVariantMap arguments = ObjectAction::getArguments();
QVariantMap arguments = ObjectDynamic::getArguments();
withReadLock([&] {
arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom);
arguments["linearTimeScale"] = _linearTimeScale;
@ -155,7 +155,7 @@ QVariantMap ObjectActionOffset::getArguments() {
QByteArray ObjectActionOffset::serialize() const {
QByteArray ba;
QDataStream dataStream(&ba, QIODevice::WriteOnly);
dataStream << ACTION_TYPE_OFFSET;
dataStream << DYNAMIC_TYPE_OFFSET;
dataStream << getID();
dataStream << ObjectActionOffset::offsetVersion;
@ -174,7 +174,7 @@ QByteArray ObjectActionOffset::serialize() const {
void ObjectActionOffset::deserialize(QByteArray serializedArguments) {
QDataStream dataStream(serializedArguments);
EntityActionType type;
EntityDynamicType type;
dataStream >> type;
assert(type == getType());

View file

@ -21,7 +21,7 @@ const uint16_t ObjectActionSpring::springVersion = 1;
ObjectActionSpring::ObjectActionSpring(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectAction(ACTION_TYPE_SPRING, id, ownerEntity),
ObjectAction(DYNAMIC_TYPE_SPRING, id, ownerEntity),
_positionalTarget(glm::vec3(0.0f)),
_desiredPositionalTarget(glm::vec3(0.0f)),
_linearTimeScale(FLT_MAX),
@ -64,11 +64,12 @@ bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) {
bool valid = false;
int springCount = 0;
QList<EntityActionPointer> springDerivedActions;
springDerivedActions.append(ownerEntity->getActionsOfType(ACTION_TYPE_SPRING));
springDerivedActions.append(ownerEntity->getActionsOfType(ACTION_TYPE_HOLD));
QList<EntityDynamicPointer> springDerivedActions;
springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_SPRING));
springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_FAR_GRAB));
springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_HOLD));
foreach (EntityActionPointer action, springDerivedActions) {
foreach (EntityDynamicPointer action, springDerivedActions) {
std::shared_ptr<ObjectActionSpring> springAction = std::static_pointer_cast<ObjectActionSpring>(action);
glm::quat rotationForAction;
glm::vec3 positionForAction;
@ -190,29 +191,29 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
float angularTimeScale;
bool needUpdate = false;
bool somethingChanged = ObjectAction::updateArguments(arguments);
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
withReadLock([&]{
// targets are required, spring-constants are optional
bool ok = true;
positionalTarget = EntityActionInterface::extractVec3Argument("spring action", arguments, "targetPosition", ok, false);
positionalTarget = EntityDynamicInterface::extractVec3Argument("spring action", arguments, "targetPosition", ok, false);
if (!ok) {
positionalTarget = _desiredPositionalTarget;
}
ok = true;
linearTimeScale = EntityActionInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", ok, false);
linearTimeScale = EntityDynamicInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", ok, false);
if (!ok || linearTimeScale <= 0.0f) {
linearTimeScale = _linearTimeScale;
}
ok = true;
rotationalTarget = EntityActionInterface::extractQuatArgument("spring action", arguments, "targetRotation", ok, false);
rotationalTarget = EntityDynamicInterface::extractQuatArgument("spring action", arguments, "targetRotation", ok, false);
if (!ok) {
rotationalTarget = _desiredRotationalTarget;
}
ok = true;
angularTimeScale =
EntityActionInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", ok, false);
EntityDynamicInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", ok, false);
if (!ok) {
angularTimeScale = _angularTimeScale;
}
@ -237,8 +238,8 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setActionDataDirty(true);
ownerEntity->setActionDataNeedsTransmit(true);
ownerEntity->setDynamicDataDirty(true);
ownerEntity->setDynamicDataNeedsTransmit(true);
}
});
activateBody();
@ -248,7 +249,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
}
QVariantMap ObjectActionSpring::getArguments() {
QVariantMap arguments = ObjectAction::getArguments();
QVariantMap arguments = ObjectDynamic::getArguments();
withReadLock([&] {
arguments["linearTimeScale"] = _linearTimeScale;
arguments["targetPosition"] = glmToQMap(_desiredPositionalTarget);
@ -259,14 +260,7 @@ QVariantMap ObjectActionSpring::getArguments() {
return arguments;
}
QByteArray ObjectActionSpring::serialize() const {
QByteArray serializedActionArguments;
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
dataStream << ACTION_TYPE_SPRING;
dataStream << getID();
dataStream << ObjectActionSpring::springVersion;
void ObjectActionSpring::serializeParameters(QDataStream& dataStream) const {
withReadLock([&] {
dataStream << _desiredPositionalTarget;
dataStream << _linearTimeScale;
@ -277,28 +271,22 @@ QByteArray ObjectActionSpring::serialize() const {
dataStream << localTimeToServerTime(_expires);
dataStream << _tag;
});
}
QByteArray ObjectActionSpring::serialize() const {
QByteArray serializedActionArguments;
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
dataStream << DYNAMIC_TYPE_SPRING;
dataStream << getID();
dataStream << ObjectActionSpring::springVersion;
serializeParameters(dataStream);
return serializedActionArguments;
}
void ObjectActionSpring::deserialize(QByteArray serializedArguments) {
QDataStream dataStream(serializedArguments);
EntityActionType type;
dataStream >> type;
assert(type == getType());
QUuid id;
dataStream >> id;
assert(id == getID());
uint16_t serializationVersion;
dataStream >> serializationVersion;
if (serializationVersion != ObjectActionSpring::springVersion) {
assert(false);
return;
}
void ObjectActionSpring::deserializeParameters(QByteArray serializedArguments, QDataStream& dataStream) {
withWriteLock([&] {
dataStream >> _desiredPositionalTarget;
dataStream >> _linearTimeScale;
@ -317,3 +305,24 @@ void ObjectActionSpring::deserialize(QByteArray serializedArguments) {
_active = true;
});
}
void ObjectActionSpring::deserialize(QByteArray serializedArguments) {
QDataStream dataStream(serializedArguments);
EntityDynamicType type;
dataStream >> type;
assert(type == getType());
QUuid id;
dataStream >> id;
assert(id == getID());
uint16_t serializationVersion;
dataStream >> serializationVersion;
if (serializationVersion != ObjectActionSpring::springVersion) {
assert(false);
return;
}
deserializeParameters(serializedArguments, dataStream);
}

View file

@ -47,6 +47,9 @@ protected:
glm::vec3 _angularVelocityTarget;
virtual bool prepareForSpringUpdate(btScalar deltaTimeStep);
void serializeParameters(QDataStream& dataStream) const;
void deserializeParameters(QByteArray serializedArguments, QDataStream& dataStream);
};
#endif // hifi_ObjectActionSpring_h

View file

@ -19,7 +19,7 @@ const uint16_t ObjectActionTravelOriented::actionVersion = 1;
ObjectActionTravelOriented::ObjectActionTravelOriented(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectAction(ACTION_TYPE_TRAVEL_ORIENTED, id, ownerEntity) {
ObjectAction(DYNAMIC_TYPE_TRAVEL_ORIENTED, id, ownerEntity) {
#if WANT_DEBUG
qCDebug(physics) << "ObjectActionTravelOriented::ObjectActionTravelOriented";
#endif
@ -106,16 +106,16 @@ bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) {
float angularTimeScale;
bool needUpdate = false;
bool somethingChanged = ObjectAction::updateArguments(arguments);
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
withReadLock([&]{
bool ok = true;
forward = EntityActionInterface::extractVec3Argument("travel oriented action", arguments, "forward", ok, true);
forward = EntityDynamicInterface::extractVec3Argument("travel oriented action", arguments, "forward", ok, true);
if (!ok) {
forward = _forward;
}
ok = true;
angularTimeScale =
EntityActionInterface::extractFloatArgument("travel oriented action", arguments, "angularTimeScale", ok, false);
EntityDynamicInterface::extractFloatArgument("travel oriented action", arguments, "angularTimeScale", ok, false);
if (!ok) {
angularTimeScale = _angularTimeScale;
}
@ -136,8 +136,8 @@ bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) {
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setActionDataDirty(true);
ownerEntity->setActionDataNeedsTransmit(true);
ownerEntity->setDynamicDataDirty(true);
ownerEntity->setDynamicDataNeedsTransmit(true);
}
});
activateBody();
@ -147,7 +147,7 @@ bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) {
}
QVariantMap ObjectActionTravelOriented::getArguments() {
QVariantMap arguments = ObjectAction::getArguments();
QVariantMap arguments = ObjectDynamic::getArguments();
withReadLock([&] {
arguments["forward"] = glmToQMap(_forward);
arguments["angularTimeScale"] = _angularTimeScale;
@ -159,7 +159,7 @@ QByteArray ObjectActionTravelOriented::serialize() const {
QByteArray serializedActionArguments;
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
dataStream << ACTION_TYPE_TRAVEL_ORIENTED;
dataStream << DYNAMIC_TYPE_TRAVEL_ORIENTED;
dataStream << getID();
dataStream << ObjectActionTravelOriented::actionVersion;
@ -177,7 +177,7 @@ QByteArray ObjectActionTravelOriented::serialize() const {
void ObjectActionTravelOriented::deserialize(QByteArray serializedArguments) {
QDataStream dataStream(serializedArguments);
EntityActionType type;
EntityDynamicType type;
dataStream >> type;
assert(type == getType());

View file

@ -0,0 +1,25 @@
//
// ObjectConstraint.cpp
// libraries/physcis/src
//
// Created by Seth Alves 2015-6-2
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntitySimulation.h"
#include "ObjectConstraint.h"
#include "PhysicsLogging.h"
ObjectConstraint::ObjectConstraint(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) :
ObjectDynamic(type, id, ownerEntity)
{
}
void ObjectConstraint::invalidate() {
_constraint = nullptr;
}

View file

@ -0,0 +1,35 @@
//
// ObjectConstraint.h
// libraries/physcis/src
//
// Created by Seth Alves 2017-4-11
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// http://bulletphysics.org/Bullet/BulletFull/classbtConstraintInterface.html
#ifndef hifi_ObjectConstraint_h
#define hifi_ObjectConstraint_h
#include <QUuid>
#include <btBulletDynamicsCommon.h>
#include "ObjectDynamic.h"
class ObjectConstraint : public ObjectDynamic
{
public:
ObjectConstraint(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectConstraint() {}
virtual btTypedConstraint* getConstraint() = 0;
virtual bool isConstraint() const override { return true; }
virtual void invalidate() override;
protected:
btTypedConstraint* _constraint { nullptr };
};
#endif // hifi_ObjectConstraint_h

View file

@ -0,0 +1,363 @@
//
// ObjectConstraintHinge.cpp
// libraries/physics/src
//
// Created by Seth Alves 2017-4-11
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "QVariantGLM.h"
#include "EntityTree.h"
#include "ObjectConstraintHinge.h"
#include "PhysicsLogging.h"
const uint16_t ObjectConstraintHinge::constraintVersion = 1;
ObjectConstraintHinge::ObjectConstraintHinge(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectConstraint(DYNAMIC_TYPE_HINGE, id, ownerEntity),
_pivotInA(glm::vec3(0.0f)),
_axisInA(glm::vec3(0.0f))
{
#if WANT_DEBUG
qCDebug(physics) << "ObjectConstraintHinge::ObjectConstraintHinge";
#endif
}
ObjectConstraintHinge::~ObjectConstraintHinge() {
#if WANT_DEBUG
qCDebug(physics) << "ObjectConstraintHinge::~ObjectConstraintHinge";
#endif
}
QList<btRigidBody*> ObjectConstraintHinge::getRigidBodies() {
QList<btRigidBody*> result;
result += getRigidBody();
QUuid otherEntityID;
withReadLock([&]{
otherEntityID = _otherEntityID;
});
if (!otherEntityID.isNull()) {
result += getOtherRigidBody(otherEntityID);
}
return result;
}
void ObjectConstraintHinge::updateHinge() {
btHingeConstraint* constraint { nullptr };
float low;
float high;
float softness;
float biasFactor;
float relaxationFactor;
float motorVelocity;
withReadLock([&]{
constraint = static_cast<btHingeConstraint*>(_constraint);
low = _low;
high = _high;
softness = _softness;
biasFactor = _biasFactor;
relaxationFactor = _relaxationFactor;
motorVelocity = _motorVelocity;
});
if (!constraint) {
return;
}
constraint->setLimit(low, high, softness, biasFactor, relaxationFactor);
if (motorVelocity != 0.0f) {
constraint->setMotorTargetVelocity(motorVelocity);
constraint->enableMotor(true);
} else {
constraint->enableMotor(false);
}
}
btTypedConstraint* ObjectConstraintHinge::getConstraint() {
btHingeConstraint* constraint { nullptr };
QUuid otherEntityID;
glm::vec3 pivotInA;
glm::vec3 axisInA;
glm::vec3 pivotInB;
glm::vec3 axisInB;
withReadLock([&]{
constraint = static_cast<btHingeConstraint*>(_constraint);
pivotInA = _pivotInA;
axisInA = _axisInA;
otherEntityID = _otherEntityID;
pivotInB = _pivotInB;
axisInB = _axisInB;
});
if (constraint) {
return constraint;
}
btRigidBody* rigidBodyA = getRigidBody();
if (!rigidBodyA) {
qCDebug(physics) << "ObjectConstraintHinge::getConstraint -- no rigidBodyA";
return nullptr;
}
if (!otherEntityID.isNull()) {
// This hinge is between two entities... find the other rigid body.
btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
if (!rigidBodyB) {
return nullptr;
}
constraint = new btHingeConstraint(*rigidBodyA, *rigidBodyB,
glmToBullet(pivotInA), glmToBullet(pivotInB),
glmToBullet(axisInA), glmToBullet(axisInB),
true); // useReferenceFrameA
} else {
// This hinge is between an entity and the world-frame.
constraint = new btHingeConstraint(*rigidBodyA,
glmToBullet(pivotInA), glmToBullet(axisInA),
true); // useReferenceFrameA
}
withWriteLock([&]{
_constraint = constraint;
});
// if we don't wake up rigidBodyA, we may not send the dynamicData property over the network
forceBodyNonStatic();
activateBody();
updateHinge();
return constraint;
}
bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) {
glm::vec3 pivotInA;
glm::vec3 axisInA;
QUuid otherEntityID;
glm::vec3 pivotInB;
glm::vec3 axisInB;
float low;
float high;
float softness;
float biasFactor;
float relaxationFactor;
float motorVelocity;
bool needUpdate = false;
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
withReadLock([&]{
bool ok = true;
pivotInA = EntityDynamicInterface::extractVec3Argument("hinge constraint", arguments, "pivot", ok, false);
if (!ok) {
pivotInA = _pivotInA;
}
ok = true;
axisInA = EntityDynamicInterface::extractVec3Argument("hinge constraint", arguments, "axis", ok, false);
if (!ok) {
axisInA = _axisInA;
}
ok = true;
otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("hinge constraint",
arguments, "otherEntityID", ok, false));
if (!ok) {
otherEntityID = _otherEntityID;
}
ok = true;
pivotInB = EntityDynamicInterface::extractVec3Argument("hinge constraint", arguments, "otherPivot", ok, false);
if (!ok) {
pivotInB = _pivotInB;
}
ok = true;
axisInB = EntityDynamicInterface::extractVec3Argument("hinge constraint", arguments, "otherAxis", ok, false);
if (!ok) {
axisInB = _axisInB;
}
ok = true;
low = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, "low", ok, false);
if (!ok) {
low = _low;
}
ok = true;
high = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, "high", ok, false);
if (!ok) {
high = _high;
}
ok = true;
softness = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, "softness", ok, false);
if (!ok) {
softness = _softness;
}
ok = true;
biasFactor = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, "biasFactor", ok, false);
if (!ok) {
biasFactor = _biasFactor;
}
ok = true;
relaxationFactor = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments,
"relaxationFactor", ok, false);
if (!ok) {
relaxationFactor = _relaxationFactor;
}
ok = true;
motorVelocity = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments,
"motorVelocity", ok, false);
if (!ok) {
motorVelocity = _motorVelocity;
}
if (somethingChanged ||
pivotInA != _pivotInA ||
axisInA != _axisInA ||
otherEntityID != _otherEntityID ||
pivotInB != _pivotInB ||
axisInB != _axisInB ||
low != _low ||
high != _high ||
softness != _softness ||
biasFactor != _biasFactor ||
relaxationFactor != _relaxationFactor ||
motorVelocity != _motorVelocity) {
// something changed
needUpdate = true;
}
});
if (needUpdate) {
withWriteLock([&] {
_pivotInA = pivotInA;
_axisInA = axisInA;
_otherEntityID = otherEntityID;
_pivotInB = pivotInB;
_axisInB = axisInB;
_low = low;
_high = high;
_softness = softness;
_biasFactor = biasFactor;
_relaxationFactor = relaxationFactor;
_motorVelocity = motorVelocity;
_active = true;
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setDynamicDataDirty(true);
ownerEntity->setDynamicDataNeedsTransmit(true);
}
});
updateHinge();
}
return true;
}
QVariantMap ObjectConstraintHinge::getArguments() {
QVariantMap arguments = ObjectDynamic::getArguments();
withReadLock([&] {
if (_constraint) {
arguments["pivot"] = glmToQMap(_pivotInA);
arguments["axis"] = glmToQMap(_axisInA);
arguments["otherEntityID"] = _otherEntityID;
arguments["otherPivot"] = glmToQMap(_pivotInB);
arguments["otherAxis"] = glmToQMap(_axisInB);
arguments["low"] = _low;
arguments["high"] = _high;
arguments["softness"] = _softness;
arguments["biasFactor"] = _biasFactor;
arguments["relaxationFactor"] = _relaxationFactor;
arguments["motorVelocity"] = _motorVelocity;
arguments["angle"] = static_cast<btHingeConstraint*>(_constraint)->getHingeAngle(); // [-PI,PI]
}
});
return arguments;
}
QByteArray ObjectConstraintHinge::serialize() const {
QByteArray serializedConstraintArguments;
QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly);
dataStream << DYNAMIC_TYPE_HINGE;
dataStream << getID();
dataStream << ObjectConstraintHinge::constraintVersion;
withReadLock([&] {
dataStream << _pivotInA;
dataStream << _axisInA;
dataStream << _otherEntityID;
dataStream << _pivotInB;
dataStream << _axisInB;
dataStream << _low;
dataStream << _high;
dataStream << _softness;
dataStream << _biasFactor;
dataStream << _relaxationFactor;
dataStream << localTimeToServerTime(_expires);
dataStream << _tag;
dataStream << _motorVelocity;
});
return serializedConstraintArguments;
}
void ObjectConstraintHinge::deserialize(QByteArray serializedArguments) {
QDataStream dataStream(serializedArguments);
EntityDynamicType type;
dataStream >> type;
assert(type == getType());
QUuid id;
dataStream >> id;
assert(id == getID());
uint16_t serializationVersion;
dataStream >> serializationVersion;
if (serializationVersion != ObjectConstraintHinge::constraintVersion) {
assert(false);
return;
}
withWriteLock([&] {
dataStream >> _pivotInA;
dataStream >> _axisInA;
dataStream >> _otherEntityID;
dataStream >> _pivotInB;
dataStream >> _axisInB;
dataStream >> _low;
dataStream >> _high;
dataStream >> _softness;
dataStream >> _biasFactor;
dataStream >> _relaxationFactor;
quint64 serverExpires;
dataStream >> serverExpires;
_expires = serverTimeToLocalTime(serverExpires);
dataStream >> _tag;
dataStream >> _motorVelocity;
_active = true;
});
}

View file

@ -0,0 +1,53 @@
//
// ObjectConstraintHinge.h
// libraries/physics/src
//
// Created by Seth Alves 2017-4-11
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ObjectConstraintHinge_h
#define hifi_ObjectConstraintHinge_h
#include "ObjectConstraint.h"
// http://bulletphysics.org/Bullet/BulletFull/classbtHingeConstraint.html
class ObjectConstraintHinge : public ObjectConstraint {
public:
ObjectConstraintHinge(const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectConstraintHinge();
virtual bool updateArguments(QVariantMap arguments) override;
virtual QVariantMap getArguments() override;
virtual QByteArray serialize() const override;
virtual void deserialize(QByteArray serializedArguments) override;
virtual QList<btRigidBody*> getRigidBodies() override;
virtual btTypedConstraint* getConstraint() override;
protected:
static const uint16_t constraintVersion;
void updateHinge();
glm::vec3 _pivotInA;
glm::vec3 _axisInA;
EntityItemID _otherEntityID;
glm::vec3 _pivotInB;
glm::vec3 _axisInB;
float _low { -2.0f * PI };
float _high { 2.0f * PI };
float _softness { 0.9f };
float _biasFactor { 0.3f };
float _relaxationFactor { 1.0f };
float _motorVelocity { 0.0f };
};
#endif // hifi_ObjectConstraintHinge_h

View file

@ -0,0 +1,276 @@
//
// ObjectDynamic.cpp
// libraries/physcis/src
//
// Created by Seth Alves 2015-6-2
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntitySimulation.h"
#include "ObjectDynamic.h"
#include "PhysicsLogging.h"
ObjectDynamic::ObjectDynamic(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) :
EntityDynamicInterface(type, id),
_ownerEntity(ownerEntity) {
}
ObjectDynamic::~ObjectDynamic() {
}
qint64 ObjectDynamic::getEntityServerClockSkew() const {
auto nodeList = DependencyManager::get<NodeList>();
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return 0;
}
const QUuid& entityServerNodeID = ownerEntity->getSourceUUID();
auto entityServerNode = nodeList->nodeWithUUID(entityServerNodeID);
if (entityServerNode) {
return entityServerNode->getClockSkewUsec();
}
return 0;
}
bool ObjectDynamic::updateArguments(QVariantMap arguments) {
bool somethingChanged = false;
withWriteLock([&]{
quint64 previousExpires = _expires;
QString previousTag = _tag;
bool ttlSet = true;
float ttl = EntityDynamicInterface::extractFloatArgument("dynamic", arguments, "ttl", ttlSet, false);
if (ttlSet) {
quint64 now = usecTimestampNow();
_expires = now + (quint64)(ttl * USECS_PER_SECOND);
} else {
_expires = 0;
}
bool tagSet = true;
QString tag = EntityDynamicInterface::extractStringArgument("dynamic", arguments, "tag", tagSet, false);
if (tagSet) {
_tag = tag;
} else {
tag = "";
}
if (previousExpires != _expires || previousTag != _tag) {
somethingChanged = true;
}
});
return somethingChanged;
}
QVariantMap ObjectDynamic::getArguments() {
QVariantMap arguments;
withReadLock([&]{
if (_expires == 0) {
arguments["ttl"] = 0.0f;
} else {
quint64 now = usecTimestampNow();
arguments["ttl"] = (float)(_expires - now) / (float)USECS_PER_SECOND;
}
arguments["tag"] = _tag;
EntityItemPointer entity = _ownerEntity.lock();
if (entity) {
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(entity->getPhysicsInfo());
if (motionState) {
arguments["::active"] = motionState->isActive();
arguments["::motion-type"] = motionTypeToString(motionState->getMotionType());
} else {
arguments["::no-motion-state"] = true;
}
}
arguments["isMine"] = isMine();
});
return arguments;
}
void ObjectDynamic::removeFromSimulation(EntitySimulationPointer simulation) const {
QUuid myID;
withReadLock([&]{
myID = _id;
});
simulation->removeDynamic(myID);
}
EntityItemPointer ObjectDynamic::getEntityByID(EntityItemID entityID) const {
EntityItemPointer ownerEntity;
withReadLock([&]{
ownerEntity = _ownerEntity.lock();
});
EntityTreeElementPointer element = ownerEntity ? ownerEntity->getElement() : nullptr;
EntityTreePointer tree = element ? element->getTree() : nullptr;
if (!tree) {
return nullptr;
}
return tree->findEntityByID(entityID);
}
btRigidBody* ObjectDynamic::getRigidBody() {
ObjectMotionState* motionState = nullptr;
withReadLock([&]{
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
}
void* physicsInfo = ownerEntity->getPhysicsInfo();
if (!physicsInfo) {
return;
}
motionState = static_cast<ObjectMotionState*>(physicsInfo);
});
if (motionState) {
return motionState->getRigidBody();
}
return nullptr;
}
glm::vec3 ObjectDynamic::getPosition() {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return glm::vec3(0.0f);
}
return bulletToGLM(rigidBody->getCenterOfMassPosition());
}
glm::quat ObjectDynamic::getRotation() {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return glm::quat(0.0f, 0.0f, 0.0f, 1.0f);
}
return bulletToGLM(rigidBody->getOrientation());
}
glm::vec3 ObjectDynamic::getLinearVelocity() {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return glm::vec3(0.0f);
}
return bulletToGLM(rigidBody->getLinearVelocity());
}
void ObjectDynamic::setLinearVelocity(glm::vec3 linearVelocity) {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return;
}
rigidBody->setLinearVelocity(glmToBullet(glm::vec3(0.0f)));
rigidBody->activate();
}
glm::vec3 ObjectDynamic::getAngularVelocity() {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return glm::vec3(0.0f);
}
return bulletToGLM(rigidBody->getAngularVelocity());
}
void ObjectDynamic::setAngularVelocity(glm::vec3 angularVelocity) {
auto rigidBody = getRigidBody();
if (!rigidBody) {
return;
}
rigidBody->setAngularVelocity(glmToBullet(angularVelocity));
rigidBody->activate();
}
void ObjectDynamic::activateBody(bool forceActivation) {
auto rigidBody = getRigidBody();
if (rigidBody) {
rigidBody->activate(forceActivation);
} else {
qCDebug(physics) << "ObjectDynamic::activateBody -- no rigid body" << (void*)rigidBody;
}
}
void ObjectDynamic::forceBodyNonStatic() {
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
}
void* physicsInfo = ownerEntity->getPhysicsInfo();
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
if (motionState && motionState->getMotionType() == MOTION_TYPE_STATIC) {
ownerEntity->flagForMotionStateChange();
}
}
bool ObjectDynamic::lifetimeIsOver() {
if (_expires == 0) {
return false;
}
quint64 now = usecTimestampNow();
if (now >= _expires) {
return true;
}
return false;
}
quint64 ObjectDynamic::localTimeToServerTime(quint64 timeValue) const {
// 0 indicates no expiration
if (timeValue == 0) {
return 0;
}
qint64 serverClockSkew = getEntityServerClockSkew();
if (serverClockSkew < 0 && timeValue <= (quint64)(-serverClockSkew)) {
return 1; // non-zero but long-expired value to avoid negative roll-over
}
return timeValue + serverClockSkew;
}
quint64 ObjectDynamic::serverTimeToLocalTime(quint64 timeValue) const {
// 0 indicates no expiration
if (timeValue == 0) {
return 0;
}
qint64 serverClockSkew = getEntityServerClockSkew();
if (serverClockSkew > 0 && timeValue <= (quint64)serverClockSkew) {
return 1; // non-zero but long-expired value to avoid negative roll-over
}
return timeValue - serverClockSkew;
}
btRigidBody* ObjectDynamic::getOtherRigidBody(EntityItemID otherEntityID) {
EntityItemPointer otherEntity = getEntityByID(otherEntityID);
if (!otherEntity) {
return nullptr;
}
void* otherPhysicsInfo = otherEntity->getPhysicsInfo();
if (!otherPhysicsInfo) {
return nullptr;
}
ObjectMotionState* otherMotionState = static_cast<ObjectMotionState*>(otherPhysicsInfo);
if (!otherMotionState) {
return nullptr;
}
return otherMotionState->getRigidBody();
}
QList<btRigidBody*> ObjectDynamic::getRigidBodies() {
QList<btRigidBody*> result;
result += getRigidBody();
return result;
}

View file

@ -0,0 +1,76 @@
//
// ObjectDynamic.h
// libraries/physcis/src
//
// Created by Seth Alves 2015-6-2
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// http://bulletphysics.org/Bullet/BulletFull/classbtDynamicInterface.html
#ifndef hifi_ObjectDynamic_h
#define hifi_ObjectDynamic_h
#include <QUuid>
#include <btBulletDynamicsCommon.h>
#include <shared/ReadWriteLockable.h>
#include "ObjectMotionState.h"
#include "BulletUtil.h"
#include "EntityDynamicInterface.h"
class ObjectDynamic : public EntityDynamicInterface, public ReadWriteLockable {
public:
ObjectDynamic(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectDynamic();
virtual void removeFromSimulation(EntitySimulationPointer simulation) const override;
virtual EntityItemWeakPointer getOwnerEntity() const override { return _ownerEntity; }
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) override { _ownerEntity = ownerEntity; }
virtual void invalidate() {};
virtual bool updateArguments(QVariantMap arguments) override;
virtual QVariantMap getArguments() override;
virtual QByteArray serialize() const override = 0;
virtual void deserialize(QByteArray serializedArguments) override = 0;
virtual bool lifetimeIsOver() override;
virtual quint64 getExpires() override { return _expires; }
virtual QList<btRigidBody*> getRigidBodies();
protected:
quint64 localTimeToServerTime(quint64 timeValue) const;
quint64 serverTimeToLocalTime(quint64 timeValue) const;
btRigidBody* getOtherRigidBody(EntityItemID otherEntityID);
EntityItemPointer getEntityByID(EntityItemID entityID) const;
virtual btRigidBody* getRigidBody();
virtual glm::vec3 getPosition() override;
virtual glm::quat getRotation() override;
virtual glm::vec3 getLinearVelocity() override;
virtual void setLinearVelocity(glm::vec3 linearVelocity) override;
virtual glm::vec3 getAngularVelocity() override;
virtual void setAngularVelocity(glm::vec3 angularVelocity) override;
virtual void activateBody(bool forceActivation = false);
virtual void forceBodyNonStatic();
EntityItemWeakPointer _ownerEntity;
QString _tag;
quint64 _expires { 0 }; // in seconds since epoch
private:
qint64 getEntityServerClockSkew() const;
};
typedef std::shared_ptr<ObjectDynamic> ObjectDynamicPointer;
#endif // hifi_ObjectDynamic_h

View file

@ -330,33 +330,41 @@ void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& coll
}
void PhysicalEntitySimulation::addAction(EntityActionPointer action) {
void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) {
if (_physicsEngine) {
// FIXME put fine grain locking into _physicsEngine
{
QMutexLocker lock(&_mutex);
const QUuid& actionID = action->getID();
if (_physicsEngine->getActionByID(actionID)) {
qCDebug(physics) << "warning -- PhysicalEntitySimulation::addAction -- adding an "
"action that was already in _physicsEngine";
const QUuid& dynamicID = dynamic->getID();
if (_physicsEngine->getDynamicByID(dynamicID)) {
qCDebug(physics) << "warning -- PhysicalEntitySimulation::addDynamic -- adding an "
"dynamic that was already in _physicsEngine";
}
}
EntitySimulation::addAction(action);
EntitySimulation::addDynamic(dynamic);
}
}
void PhysicalEntitySimulation::applyActionChanges() {
void PhysicalEntitySimulation::applyDynamicChanges() {
QList<EntityDynamicPointer> dynamicsFailedToAdd;
if (_physicsEngine) {
// FIXME put fine grain locking into _physicsEngine
QMutexLocker lock(&_mutex);
foreach(QUuid actionToRemove, _actionsToRemove) {
_physicsEngine->removeAction(actionToRemove);
foreach(QUuid dynamicToRemove, _dynamicsToRemove) {
_physicsEngine->removeDynamic(dynamicToRemove);
}
foreach (EntityActionPointer actionToAdd, _actionsToAdd) {
if (!_actionsToRemove.contains(actionToAdd->getID())) {
_physicsEngine->addAction(actionToAdd);
foreach (EntityDynamicPointer dynamicToAdd, _dynamicsToAdd) {
if (!_dynamicsToRemove.contains(dynamicToAdd->getID())) {
if (!_physicsEngine->addDynamic(dynamicToAdd)) {
dynamicsFailedToAdd += dynamicToAdd;
}
}
}
}
EntitySimulation::applyActionChanges();
// applyDynamicChanges will clear _dynamicsToRemove and _dynamicsToAdd
EntitySimulation::applyDynamicChanges();
// put back the ones that couldn't yet be added
foreach (EntityDynamicPointer dynamicFailedToAdd, dynamicsFailedToAdd) {
addDynamic(dynamicFailedToAdd);
}
}

View file

@ -34,8 +34,8 @@ public:
void init(EntityTreePointer tree, PhysicsEnginePointer engine, EntityEditPacketSender* packetSender);
virtual void addAction(EntityActionPointer action) override;
virtual void applyActionChanges() override;
virtual void addDynamic(EntityDynamicPointer dynamic) override;
virtual void applyDynamicChanges() override;
virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) override;

View file

@ -142,6 +142,26 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
motionState->clearIncomingDirtyFlags();
}
QList<EntityDynamicPointer> PhysicsEngine::removeDynamicsForBody(btRigidBody* body) {
// remove dynamics that are attached to this body
QList<EntityDynamicPointer> removedDynamics;
QMutableSetIterator<QUuid> i(_objectDynamicsByBody[body]);
while (i.hasNext()) {
QUuid dynamicID = i.next();
if (dynamicID.isNull()) {
continue;
}
EntityDynamicPointer dynamic = _objectDynamics[dynamicID];
if (!dynamic) {
continue;
}
removeDynamic(dynamicID);
removedDynamics += dynamic;
}
return removedDynamics;
}
void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) {
// bump and prune contacts for all objects in the list
for (auto object : objects) {
@ -175,6 +195,7 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) {
for (auto object : objects) {
btRigidBody* body = object->getRigidBody();
if (body) {
removeDynamicsForBody(body);
_dynamicsWorld->removeRigidBody(body);
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
@ -191,6 +212,7 @@ void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) {
for (auto object : objects) {
btRigidBody* body = object->getRigidBody();
if (body) {
removeDynamicsForBody(body);
_dynamicsWorld->removeRigidBody(body);
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
@ -240,7 +262,6 @@ void PhysicsEngine::reinsertObject(ObjectMotionState* object) {
btRigidBody* body = object->getRigidBody();
if (body) {
_dynamicsWorld->removeRigidBody(body);
// add it back
addObjectToDynamicsWorld(object);
}
@ -548,44 +569,84 @@ void PhysicsEngine::setCharacterController(CharacterController* character) {
}
}
EntityActionPointer PhysicsEngine::getActionByID(const QUuid& actionID) const {
if (_objectActions.contains(actionID)) {
return _objectActions[actionID];
EntityDynamicPointer PhysicsEngine::getDynamicByID(const QUuid& dynamicID) const {
if (_objectDynamics.contains(dynamicID)) {
return _objectDynamics[dynamicID];
}
return nullptr;
}
void PhysicsEngine::addAction(EntityActionPointer action) {
assert(action);
const QUuid& actionID = action->getID();
if (_objectActions.contains(actionID)) {
if (_objectActions[actionID] == action) {
bool PhysicsEngine::addDynamic(EntityDynamicPointer dynamic) {
assert(dynamic);
if (!dynamic->isReadyForAdd()) {
return false;
}
const QUuid& dynamicID = dynamic->getID();
if (_objectDynamics.contains(dynamicID)) {
if (_objectDynamics[dynamicID] == dynamic) {
return true;
}
removeDynamic(dynamic->getID());
}
bool success { false };
if (dynamic->isAction()) {
ObjectAction* objectAction = static_cast<ObjectAction*>(dynamic.get());
_dynamicsWorld->addAction(objectAction);
success = true;
} else if (dynamic->isConstraint()) {
ObjectConstraint* objectConstraint = static_cast<ObjectConstraint*>(dynamic.get());
btTypedConstraint* constraint = objectConstraint->getConstraint();
if (constraint) {
_dynamicsWorld->addConstraint(constraint);
success = true;
} // else perhaps not all the rigid bodies are available, yet
}
if (success) {
_objectDynamics[dynamicID] = dynamic;
foreach(btRigidBody* rigidBody, std::static_pointer_cast<ObjectDynamic>(dynamic)->getRigidBodies()) {
_objectDynamicsByBody[rigidBody] += dynamic->getID();
}
}
return success;
}
void PhysicsEngine::removeDynamic(const QUuid dynamicID) {
if (_objectDynamics.contains(dynamicID)) {
ObjectDynamicPointer dynamic = std::static_pointer_cast<ObjectDynamic>(_objectDynamics[dynamicID]);
if (!dynamic) {
return;
}
removeAction(action->getID());
}
_objectActions[actionID] = action;
// bullet needs a pointer to the action, but it doesn't use shared pointers.
// is there a way to bump the reference count?
ObjectAction* objectAction = static_cast<ObjectAction*>(action.get());
_dynamicsWorld->addAction(objectAction);
}
void PhysicsEngine::removeAction(const QUuid actionID) {
if (_objectActions.contains(actionID)) {
EntityActionPointer action = _objectActions[actionID];
ObjectAction* objectAction = static_cast<ObjectAction*>(action.get());
_dynamicsWorld->removeAction(objectAction);
_objectActions.remove(actionID);
QList<btRigidBody*> rigidBodies = dynamic->getRigidBodies();
if (dynamic->isAction()) {
ObjectAction* objectAction = static_cast<ObjectAction*>(dynamic.get());
_dynamicsWorld->removeAction(objectAction);
} else {
ObjectConstraint* objectConstraint = static_cast<ObjectConstraint*>(dynamic.get());
btTypedConstraint* constraint = objectConstraint->getConstraint();
if (constraint) {
_dynamicsWorld->removeConstraint(constraint);
} else {
qCDebug(physics) << "PhysicsEngine::removeDynamic of constraint failed";
}
}
_objectDynamics.remove(dynamicID);
foreach(btRigidBody* rigidBody, rigidBodies) {
_objectDynamicsByBody[rigidBody].remove(dynamic->getID());
}
dynamic->invalidate();
}
}
void PhysicsEngine::forEachAction(std::function<void(EntityActionPointer)> actor) {
QHashIterator<QUuid, EntityActionPointer> iter(_objectActions);
void PhysicsEngine::forEachDynamic(std::function<void(EntityDynamicPointer)> actor) {
QMutableHashIterator<QUuid, EntityDynamicPointer> iter(_objectDynamics);
while (iter.hasNext()) {
iter.next();
actor(iter.value());
if (iter.value()) {
actor(iter.value());
}
}
}

View file

@ -24,6 +24,7 @@
#include "ObjectMotionState.h"
#include "ThreadSafeDynamicsWorld.h"
#include "ObjectAction.h"
#include "ObjectConstraint.h"
const float HALF_SIMULATION_EXTENT = 512.0f; // meters
@ -84,12 +85,13 @@ public:
void dumpNextStats() { _dumpNextStats = true; }
EntityActionPointer getActionByID(const QUuid& actionID) const;
void addAction(EntityActionPointer action);
void removeAction(const QUuid actionID);
void forEachAction(std::function<void(EntityActionPointer)> actor);
EntityDynamicPointer getDynamicByID(const QUuid& dynamicID) const;
bool addDynamic(EntityDynamicPointer dynamic);
void removeDynamic(const QUuid dynamicID);
void forEachDynamic(std::function<void(EntityDynamicPointer)> actor);
private:
QList<EntityDynamicPointer> removeDynamicsForBody(btRigidBody* body);
void addObjectToDynamicsWorld(ObjectMotionState* motionState);
void recursivelyHarvestPerformanceStats(CProfileIterator* profileIterator, QString contextName);
@ -110,7 +112,8 @@ private:
ContactMap _contactMap;
CollisionEvents _collisionEvents;
QHash<QUuid, EntityActionPointer> _objectActions;
QHash<QUuid, EntityDynamicPointer> _objectDynamics;
QHash<btRigidBody*, QSet<QUuid>> _objectDynamicsByBody;
std::vector<btRigidBody*> _activeStaticBodies;
glm::vec3 _originOffset;
@ -122,6 +125,7 @@ private:
bool _dumpNextStats = false;
bool _hasOutgoingChanges = false;
};
typedef std::shared_ptr<PhysicsEngine> PhysicsEnginePointer;

View file

@ -1,5 +1,5 @@
set(TARGET_NAME procedural)
AUTOSCRIBE_SHADER_LIB(gpu model)
setup_hifi_library()
link_hifi_libraries(shared gpu gpu-gl networking model model-networking)
link_hifi_libraries(shared gpu gpu-gl networking model model-networking image)

View file

@ -3,7 +3,7 @@ AUTOSCRIBE_SHADER_LIB(gpu model render)
# pull in the resources.qrc file
qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc")
setup_hifi_library(Widgets OpenGL Network Qml Quick Script)
link_hifi_libraries(shared ktx gpu model model-networking render animation fbx entities)
link_hifi_libraries(shared ktx gpu model model-networking render animation fbx entities image)
if (NOT ANDROID)
target_nsight()

View file

@ -74,11 +74,11 @@ void AmbientOcclusionFramebuffer::allocate() {
auto width = _frameSize.x;
auto height = _frameSize.y;
_occlusionTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_occlusionTexture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT));
_occlusionFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("occlusion"));
_occlusionFramebuffer->setRenderBuffer(0, _occlusionTexture);
_occlusionBlurredTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_occlusionBlurredTexture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT));
_occlusionBlurredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("occlusionBlurred"));
_occlusionBlurredFramebuffer->setRenderBuffer(0, _occlusionBlurredTexture);
}

View file

@ -52,7 +52,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() {
_antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing"));
auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get<FramebufferCache>()->getLightingTexture()->getTexelFormat();
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
_antialiasingTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler);
_antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture);
}

View file

@ -53,9 +53,9 @@ void DeferredFramebuffer::allocate() {
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
_deferredColorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_deferredNormalTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(linearFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_deferredSpecularTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(linearFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_deferredColorTexture = gpu::Texture::createRenderBuffer(colorFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler);
_deferredNormalTexture = gpu::Texture::createRenderBuffer(linearFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler);
_deferredSpecularTexture = gpu::Texture::createRenderBuffer(linearFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler);
_deferredFramebuffer->setRenderBuffer(0, _deferredColorTexture);
_deferredFramebuffer->setRenderBuffer(1, _deferredNormalTexture);
@ -65,7 +65,7 @@ void DeferredFramebuffer::allocate() {
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
if (!_primaryDepthTexture) {
_primaryDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler);
}
_deferredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat);
@ -75,7 +75,7 @@ void DeferredFramebuffer::allocate() {
auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR);
_lightingTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_lightingTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, gpu::Texture::SINGLE_MIP, defaultSampler);
_lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("lighting"));
_lightingFramebuffer->setRenderBuffer(0, _lightingTexture);
_lightingFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat);

View file

@ -496,14 +496,14 @@ void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, g
auto colorFormat = gpu::Element::COLOR_SRGBA_32;
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
auto primaryColorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler));
auto primaryColorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
_primaryFramebuffer->setRenderBuffer(0, primaryColorTexture);
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
auto primaryDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler));
auto primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
_primaryFramebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat);
}

View file

@ -199,7 +199,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
{
// Grab a texture map representing the different status icons and assign that to the drawStatsuJob
auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg";
auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath, NetworkTexture::STRICT_TEXTURE);
auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath, image::TextureUsage::STRICT_TEXTURE);
task.addJob<DrawStatus>("DrawStatus", opaques, DrawStatus(statusIconMap));
}
}

View file

@ -75,11 +75,11 @@ void PrepareFramebuffer::run(const RenderContextPointer& renderContext,
auto colorFormat = gpu::Element::COLOR_SRGBA_32;
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
auto colorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler));
auto colorTexture = gpu::Texture::create2D(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
_framebuffer->setRenderBuffer(0, colorTexture);
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
auto depthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler));
auto depthTexture = gpu::Texture::create2D(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
_framebuffer->setDepthStencilBuffer(depthTexture, depthFormat);
}

View file

@ -414,7 +414,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringProfile(Rend
const int PROFILE_RESOLUTION = 512;
// const auto pixelFormat = gpu::Element::COLOR_SRGBA_32;
const auto pixelFormat = gpu::Element::COLOR_R11G11B10;
auto profileMap = gpu::TexturePointer(gpu::Texture::createRenderBuffer(pixelFormat, PROFILE_RESOLUTION, 1, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)));
auto profileMap = gpu::Texture::createRenderBuffer(pixelFormat, PROFILE_RESOLUTION, 1, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
profileMap->setSource("Generated Scattering Profile");
diffuseProfileGPU(profileMap, args);
return profileMap;
@ -425,7 +425,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generatePreIntegratedScatterin
const int TABLE_RESOLUTION = 512;
// const auto pixelFormat = gpu::Element::COLOR_SRGBA_32;
const auto pixelFormat = gpu::Element::COLOR_R11G11B10;
auto scatteringLUT = gpu::TexturePointer(gpu::Texture::createRenderBuffer(pixelFormat, TABLE_RESOLUTION, TABLE_RESOLUTION, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)));
auto scatteringLUT = gpu::Texture::createRenderBuffer(pixelFormat, TABLE_RESOLUTION, TABLE_RESOLUTION, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
//diffuseScatter(scatteringLUT);
scatteringLUT->setSource("Generated pre-integrated scattering");
diffuseScatterGPU(profile, scatteringLUT, args);
@ -434,7 +434,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generatePreIntegratedScatterin
gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringSpecularBeckmann(RenderArgs* args) {
const int SPECULAR_RESOLUTION = 256;
auto beckmannMap = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, SPECULAR_RESOLUTION, SPECULAR_RESOLUTION, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)));
auto beckmannMap = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, SPECULAR_RESOLUTION, SPECULAR_RESOLUTION, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
beckmannMap->setSource("Generated beckmannMap");
computeSpecularBeckmannGPU(beckmannMap, args);
return beckmannMap;

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