Merge branch 'master' of https://github.com/highfidelity/hifi into ambient-bis

This commit is contained in:
samcake 2017-04-11 10:00:34 -07:00
commit 79bcf95792
59 changed files with 586 additions and 488 deletions

View file

@ -443,13 +443,8 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
(viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
viewFrustumChanged,
boundaryLevelAdjust, octreeSizeScale,
nodeData->getLastTimeBagEmpty(),
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
&nodeData->extraEncodeData,
nodeData->getUsesFrustum(),
nodeData);
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
isFullScene, _myServer->getJurisdiction(), nodeData);
nodeData->copyCurrentViewFrustum(params.viewFrustum);
if (viewFrustumChanged) {
nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);

View file

@ -6,56 +6,60 @@
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
#
macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN)
set(${TARGET_NAME}_SHARED 1)
setup_hifi_library(${ARGV})
if (NOT DEFINED SERVER_ONLY)
add_dependencies(interface ${TARGET_NAME})
endif()
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins")
set(${TARGET_NAME}_SHARED 1)
setup_hifi_library(${ARGV})
if (APPLE)
set(CLIENT_PLUGIN_PATH "${INTERFACE_BUNDLE_NAME}.app/Contents/PlugIns")
set(SERVER_PLUGIN_PATH "plugins")
else()
set(CLIENT_PLUGIN_PATH "plugins")
set(SERVER_PLUGIN_PATH "plugins")
endif()
if (NOT DEFINED SERVER_ONLY)
add_dependencies(interface ${TARGET_NAME})
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles")
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${CLIENT_PLUGIN_PATH}/")
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/${SERVER_PLUGIN_PATH}/")
elseif (APPLE)
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$<CONFIGURATION>/${CLIENT_PLUGIN_PATH}/")
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$<CONFIGURATION>/${SERVER_PLUGIN_PATH}/")
else()
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$<CONFIGURATION>/${CLIENT_PLUGIN_PATH}/")
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$<CONFIGURATION>/${SERVER_PLUGIN_PATH}/")
endif()
add_dependencies(assignment-client ${TARGET_NAME})
# create the destination for the client plugin binaries
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory
${CLIENT_PLUGIN_FULL_PATH}
)
# copy the client plugin binaries
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${TARGET_NAME}>"
${CLIENT_PLUGIN_FULL_PATH}
)
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins")
# create the destination for the server plugin binaries
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory
${SERVER_PLUGIN_FULL_PATH}
)
# copy the server plugin binaries
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${TARGET_NAME}>"
${SERVER_PLUGIN_FULL_PATH}
)
if (APPLE)
set(CLIENT_PLUGIN_PATH "${INTERFACE_BUNDLE_NAME}.app/Contents/PlugIns")
set(SERVER_PLUGIN_PATH "plugins")
else()
set(CLIENT_PLUGIN_PATH "plugins")
set(SERVER_PLUGIN_PATH "plugins")
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles")
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${CLIENT_PLUGIN_PATH}/")
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/${SERVER_PLUGIN_PATH}/")
elseif (APPLE)
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$<CONFIGURATION>/${CLIENT_PLUGIN_PATH}/")
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$<CONFIGURATION>/${SERVER_PLUGIN_PATH}/")
else()
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$<CONFIGURATION>/${CLIENT_PLUGIN_PATH}/")
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$<CONFIGURATION>/${SERVER_PLUGIN_PATH}/")
endif()
# create the destination for the client plugin binaries
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory
${CLIENT_PLUGIN_FULL_PATH}
)
# copy the client plugin binaries
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${TARGET_NAME}>"
${CLIENT_PLUGIN_FULL_PATH}
)
# create the destination for the server plugin binaries
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory
${SERVER_PLUGIN_FULL_PATH}
)
# copy the server plugin binaries
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${TARGET_NAME}>"
${SERVER_PLUGIN_FULL_PATH}
)
endmacro()

View file

@ -192,7 +192,7 @@ link_hifi_libraries(
shared octree ktx gpu gl gpu-gl procedural model render
recording fbx networking model-networking entities avatars
audio audio-client animation script-engine physics
render-utils entities-renderer ui auto-updater
render-utils entities-renderer avatars-renderer ui auto-updater
controllers plugins
ui-plugins display-plugins input-plugins
${NON_ANDROID_LIBRARIES}

View file

@ -66,7 +66,11 @@ TabletModalWindow {
HifiConstants { id: hifi }
onCanceled: {
loginDialogRoot.Stack.view.pop()
if (loginDialogRoot.Stack.view !== null) {
loginDialogRoot.Stack.view.pop()
} else {
Tablet.getTablet("com.highfidelity.interface.tablet.system").gotoHomeScreen();
}
}
LoginDialog {

View file

@ -81,6 +81,25 @@ Item {
anchors.fill: parent;
visible: userImage.status != Image.Ready;
}
StateImage {
id: infoHoverImage;
visible: false;
imageURL: "../../images/info-icon-2-state.svg";
size: 32;
buttonState: 1;
anchors.centerIn: parent;
}
MouseArea {
anchors.fill: parent
enabled: (selected || isMyCard) && activeTab == "nearbyTab";
hoverEnabled: enabled
onClicked: {
userInfoViewer.url = defaultBaseUrl + "/users/" + userName;
userInfoViewer.visible = true;
}
onEntered: infoHoverImage.visible = true;
onExited: infoHoverImage.visible = false;
}
}
// Colored border around avatarImage
@ -302,7 +321,7 @@ Item {
height: usernameTextPixelSize + 4
// Anchors
anchors.top: isMyCard ? myDisplayName.bottom : pal.activeTab == "nearbyTab" ? displayNameContainer.bottom : undefined //(parent.height - displayNameTextPixelSize/2));
anchors.verticalCenter: pal.activeTab == "connectionsTab" ? avatarImage.verticalCenter : undefined
anchors.verticalCenter: pal.activeTab == "connectionsTab" && !isMyCard ? avatarImage.verticalCenter : undefined
anchors.left: avatarImage.right;
anchors.leftMargin: avatarImage.visible ? 5 : 0;
anchors.rightMargin: 5;

View file

@ -910,17 +910,21 @@ Rectangle {
color: hifi.colors.darkGray
wrapMode: Text.WordWrap
textFormat: Text.StyledText;
property string hmdMountedInstructions:
"1. Put your hand out onto their hand and squeeze your controller's grip button on its side.<br>" +
"2. Once the other person puts their hand onto yours, you'll see your connection form.<br>" +
"3. After about 3 seconds, you're connected!"
property string hmdNotMountedInstructions:
"1. Press and hold the 'x' key to extend your arm.<br>" +
"2. Once the other person puts their hand onto yours, you'll see your connection form.<br>" +
"3. After about 3 seconds, you're connected!";
property string notLoggedInInstructions: "<b><font color='red'>You must be logged into your High Fidelity account to make connections.</b></font><br>"
property string instructions:
"<b>When you meet someone you want to remember later, you can <font color='purple'>connect</font> with a handshake:</b><br><br>"
// Text
text: HMD.isMounted ?
"<b>When you meet someone you want to remember later, you can <font color='purple'>connect</font> with a handshake:</b><br><br>" +
"1. Put your hand out onto their hand and squeeze your controller's grip button on its side.<br>" +
"2. Once the other person puts their hand onto yours, you'll see your connection form.<br>" +
"3. After about 3 seconds, you're connected!"
:
"<b>When you meet someone you want to remember later, you can <font color='purple'>connect</font> with a handshake:</b><br><br>" +
"1. Press and hold the 'x' key to extend your arm.<br>" +
"2. Once the other person puts their hand onto yours, you'll see your connection form.<br>" +
"3. After about 3 seconds, you're connected!";
text:
Account.isLoggedIn() ? ( HMD.mounted ? instructions + hmdMountedInstructions : instructions + hmdNotMountedInstructions)
: ( HMD.mounted ? notLoggedInInstructions + instructions + hmdMountedInstructions : notLoggedInInstructions + instructions + hmdNotMountedInstructions)
}
}

View file

@ -16,6 +16,7 @@ Rectangle {
property alias text: label.text
property alias pixelSize: label.font.pixelSize;
property bool selected: false
property bool hovered: false
property int spacing: 2
property var action: function () {}
property string highlightColor: hifi.colors.blueHighlight;
@ -37,14 +38,14 @@ Rectangle {
Rectangle {
id: indicator
width: parent.width
height: 3
height: selected ? 3 : 1
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
color: hifi.colors.blueHighlight
visible: parent.selected
visible: parent.selected || hovered
}
MouseArea {
@ -53,6 +54,8 @@ Rectangle {
acceptedButtons: Qt.LeftButton;
onClicked: action(parent);
hoverEnabled: true;
onEntered: hovered = true
onExited: hovered = false
}
}

View file

@ -255,7 +255,7 @@ StackView {
TabletTextButton {
id: allTab;
text: "ALL";
property string includeActions: 'snapshot, concurrency';
property string includeActions: 'snapshot,concurrency';
selected: allTab === selectedTab;
action: tabSelect;
}

View file

@ -1802,10 +1802,11 @@ Application::~Application() {
_physicsEngine->setCharacterController(nullptr);
// remove avatars from physics engine
DependencyManager::get<AvatarManager>()->clearAllAvatars();
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
VectorOfMotionStates motionStates;
DependencyManager::get<AvatarManager>()->getObjectsToRemoveFromPhysics(motionStates);
_physicsEngine->removeObjects(motionStates);
DependencyManager::get<AvatarManager>()->deleteAllAvatars();
DependencyManager::destroy<AvatarManager>();
DependencyManager::destroy<AnimationCache>();

View file

@ -193,6 +193,9 @@ void Avatar::animateScaleChanges(float deltaTime) {
}
setScale(glm::vec3(animatedScale)); // avatar scale is uniform
// flag the joints as having changed for force update to RenderItem
_hasNewJointData = true;
// TODO: rebuilding the shape constantly is somehwat expensive.
// We should only rebuild after significant change.
rebuildCollisionShape();
@ -200,8 +203,12 @@ void Avatar::animateScaleChanges(float deltaTime) {
}
void Avatar::setTargetScale(float targetScale) {
AvatarData::setTargetScale(targetScale);
_isAnimatingScale = true;
float newValue = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
if (_targetScale != newValue) {
_targetScale = newValue;
_scaleChanged = usecTimestampNow();
_isAnimatingScale = true;
}
}
void Avatar::updateAvatarEntities() {
@ -476,7 +483,7 @@ static TextRenderer3D* textRenderer(TextRendererType type) {
return displayNameRenderer;
}
bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene, render::Transaction& transaction) {
void Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene, render::Transaction& transaction) {
auto avatarPayload = new render::Payload<AvatarData>(self);
auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload);
_renderItemID = scene->allocateID();
@ -486,9 +493,6 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene>
for (auto& attachmentModel : _attachmentModels) {
attachmentModel->addToScene(scene, transaction);
}
_inScene = true;
return true;
}
void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene, render::Transaction& transaction) {
@ -498,7 +502,6 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::S
for (auto& attachmentModel : _attachmentModels) {
attachmentModel->removeFromScene(scene, transaction);
}
_inScene = false;
}
void Avatar::updateRenderItem(render::Transaction& transaction) {
@ -933,6 +936,21 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
return translation;
}
glm::quat Avatar::getAbsoluteDefaultJointRotationInObjectFrame(int index) const {
glm::quat rotation;
auto rig = _skeletonModel->getRig();
glm::quat rot = rig->getAnimSkeleton()->getAbsoluteDefaultPose(index).rot();
return Quaternions::Y_180 * rot;
}
glm::vec3 Avatar::getAbsoluteDefaultJointTranslationInObjectFrame(int index) const {
glm::vec3 translation;
auto rig = _skeletonModel->getRig();
glm::vec3 trans = rig->getAnimSkeleton()->getAbsoluteDefaultPose(index).trans();
glm::mat4 y180Mat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3());
return transformPoint(y180Mat * rig->getGeometryToRigTransform(), trans);
}
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
if (index < 0) {
index += numeric_limits<unsigned short>::max() + 1; // 65536
@ -1435,7 +1453,7 @@ void Avatar::addToScene(AvatarSharedPointer myHandle) {
}
}
void Avatar::ensureInScene(AvatarSharedPointer self) {
if (!_inScene) {
if (!render::Item::isValidID(_renderItemID)) {
addToScene(self);
}
}

View file

@ -81,7 +81,7 @@ public:
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition);
bool addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene,
void addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene,
render::Transaction& transaction);
void removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene,
@ -121,6 +121,26 @@ public:
Q_INVOKABLE virtual glm::quat getDefaultJointRotation(int index) const;
Q_INVOKABLE virtual glm::vec3 getDefaultJointTranslation(int index) const;
/**jsdoc
* Provides read only access to the default joint rotations in avatar coordinates.
* The default pose of the avatar is defined by the position and orientation of all bones
* in the avatar's model file. Typically this is a t-pose.
* @function Avatar.getAbsoluteDefaultJointRotationInObjectFrame
* @param index {number} index number
* @returns {Quat} The rotation of this joint in avatar coordinates.
*/
Q_INVOKABLE virtual glm::quat getAbsoluteDefaultJointRotationInObjectFrame(int index) const;
/**jsdoc
* Provides read only access to the default joint translations in avatar coordinates.
* The default pose of the avatar is defined by the position and orientation of all bones
* in the avatar's model file. Typically this is a t-pose.
* @function Avatar.getAbsoluteDefaultJointTranslationInObjectFrame
* @param index {number} index number
* @returns {Vec3} The position of this joint in avatar coordinates.
*/
Q_INVOKABLE virtual glm::vec3 getAbsoluteDefaultJointTranslationInObjectFrame(int index) const;
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; }
@ -285,6 +305,7 @@ protected:
void addToScene(AvatarSharedPointer self);
void ensureInScene(AvatarSharedPointer self);
bool isInScene() const { return render::Item::isValidID(_renderItemID); }
// Some rate tracking support
RateCounter<> _simulationRate;
@ -310,7 +331,6 @@ private:
int _nameRectGeometryID { 0 };
bool _initialized;
bool _isLookAtTarget { false };
bool _inScene { false };
bool _isAnimatingScale { false };
float getBoundingRadius() const;

View file

@ -68,7 +68,7 @@ void AvatarManager::registerMetaTypes(QScriptEngine* engine) {
}
AvatarManager::AvatarManager(QObject* parent) :
_avatarFades(),
_avatarsToFade(),
_myAvatar(std::make_shared<MyAvatar>(std::make_shared<Rig>()))
{
// register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar
@ -100,15 +100,16 @@ void AvatarManager::init() {
_avatarHash.insert(MY_AVATAR_KEY, _myAvatar);
}
_shouldRender = DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars();
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged,
this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection);
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()) {
if (_shouldRender) {
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
_myAvatar->addToScene(_myAvatar, scene, transaction);
scene->enqueueTransaction(transaction);
}
scene->enqueueTransaction(transaction);
}
void AvatarManager::updateMyAvatar(float deltaTime) {
@ -151,7 +152,7 @@ float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QStri
void AvatarManager::updateOtherAvatars(float deltaTime) {
// lock the hash for read to check the size
QReadLocker lock(&_hashLock);
if (_avatarHash.size() < 2 && _avatarFades.isEmpty()) {
if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) {
return;
}
lock.unlock();
@ -181,30 +182,24 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
// DO NOT update or fade out uninitialized Avatars
return true; // ignore it
}
if (avatar->shouldDie()) {
removeAvatar(avatar->getID());
return true; // ignore it
}
if (avatar->isDead()) {
return true; // ignore it
}
return false;
});
render::Transaction transaction;
uint64_t startTime = usecTimestampNow();
const uint64_t UPDATE_BUDGET = 2000; // usec
uint64_t updateExpiry = startTime + UPDATE_BUDGET;
int numAvatarsUpdated = 0;
int numAVatarsNotUpdated = 0;
render::Transaction transaction;
while (!sortedAvatars.empty()) {
const AvatarPriority& sortData = sortedAvatars.top();
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
// for ALL avatars...
avatar->ensureInScene(avatar);
if (_shouldRender) {
avatar->ensureInScene(avatar);
}
if (!avatar->getMotionState()) {
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
@ -218,6 +213,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
}
}
avatar->animateScaleChanges(deltaTime);
if (avatar->shouldDie()) {
avatar->die();
removeAvatar(avatar->getID());
}
const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY;
uint64_t now = usecTimestampNow();
@ -259,10 +258,24 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
sortedAvatars.pop();
}
if (_shouldRender) {
if (!_avatarsToFade.empty()) {
QReadLocker lock(&_hashLock);
QVector<AvatarSharedPointer>::iterator itr = _avatarsToFade.begin();
while (itr != _avatarsToFade.end() && usecTimestampNow() > updateExpiry) {
auto avatar = std::static_pointer_cast<Avatar>(*itr);
avatar->animateScaleChanges(deltaTime);
avatar->simulate(deltaTime, true);
avatar->updateRenderItem(transaction);
++itr;
}
}
qApp->getMain3DScene()->enqueueTransaction(transaction);
}
_avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;
_numAvatarsUpdated = numAvatarsUpdated;
_numAvatarsNotUpdated = numAVatarsNotUpdated;
qApp->getMain3DScene()->enqueueTransaction(transaction);
simulateAvatarFades(deltaTime);
}
@ -277,54 +290,76 @@ void AvatarManager::postUpdate(float deltaTime) {
}
void AvatarManager::simulateAvatarFades(float deltaTime) {
QVector<AvatarSharedPointer>::iterator fadingIterator = _avatarFades.begin();
if (_avatarsToFade.empty()) {
return;
}
const float SHRINK_RATE = 0.15f;
const float MIN_FADE_SCALE = MIN_AVATAR_SCALE;
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
while (fadingIterator != _avatarFades.end()) {
auto avatar = std::static_pointer_cast<Avatar>(*fadingIterator);
QReadLocker locker(&_hashLock);
QVector<AvatarSharedPointer>::iterator itr = _avatarsToFade.begin();
while (itr != _avatarsToFade.end()) {
auto avatar = std::static_pointer_cast<Avatar>(*itr);
avatar->setTargetScale(avatar->getUniformScale() * SHRINK_RATE);
avatar->animateScaleChanges(deltaTime);
if (avatar->getTargetScale() <= MIN_FADE_SCALE) {
avatar->removeFromScene(*fadingIterator, scene, transaction);
// only remove from _avatarFades if we're sure its motionState has been removed from PhysicsEngine
// fading to zero is such a rare event we push unique transaction for each one
if (avatar->isInScene()) {
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->removeFromScene(*itr, scene, transaction);
if (scene) {
scene->enqueueTransaction(transaction);
}
}
// only remove from _avatarsToFade if we're sure its motionState has been removed from PhysicsEngine
if (_motionStatesToRemoveFromPhysics.empty()) {
fadingIterator = _avatarFades.erase(fadingIterator);
itr = _avatarsToFade.erase(itr);
} else {
++fadingIterator;
++itr;
}
} else {
const bool inView = true; // HACK
avatar->simulate(deltaTime, inView);
++fadingIterator;
++itr;
}
}
scene->enqueueTransaction(transaction);
}
AvatarSharedPointer AvatarManager::newSharedAvatar() {
return std::make_shared<Avatar>(std::make_shared<Rig>());
}
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
auto newAvatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer);
auto rawRenderableAvatar = std::static_pointer_cast<Avatar>(newAvatar);
rawRenderableAvatar->addToScene(rawRenderableAvatar);
return newAvatar;
}
// virtual
void AvatarManager::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
QWriteLocker locker(&_hashLock);
auto removedAvatar = _avatarHash.take(sessionUUID);
if (removedAvatar) {
handleRemovedAvatar(removedAvatar, removalReason);
void AvatarManager::processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
PerformanceTimer perfTimer("receiveAvatar");
// enumerate over all of the avatars in this packet
// only add them if mixerWeakPointer points to something (meaning that mixer is still around)
while (message->getBytesLeftToRead()) {
AvatarSharedPointer avatarData = parseAvatarData(message, sendingNode);
if (avatarData) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
if (avatar->isInScene()) {
if (!_shouldRender) {
// rare transition so we process the transaction immediately
render::ScenePointer scene = qApp->getMain3DScene();
if (scene) {
render::Transaction transaction;
avatar->removeFromScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
}
}
} else if (_shouldRender) {
// very rare transition so we process the transaction immediately
render::ScenePointer scene = qApp->getMain3DScene();
if (scene) {
render::Transaction transaction;
avatar->addToScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
}
}
}
}
}
@ -353,35 +388,46 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
DependencyManager::get<NodeList>()->removeFromIgnoreMuteSets(avatar->getSessionUUID());
DependencyManager::get<UsersScriptingInterface>()->avatarDisconnected(avatar->getSessionUUID());
}
_avatarFades.push_back(removedAvatar);
_avatarsToFade.push_back(removedAvatar);
}
void AvatarManager::clearOtherAvatars() {
// clear any avatars that came from an avatar-mixer
QWriteLocker locker(&_hashLock);
// Remove other avatars from the world but don't actually remove them from _avatarHash
// each will either be removed on timeout or will re-added to the world on receipt of update.
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
QReadLocker locker(&_hashLock);
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
if (avatar == _myAvatar || !avatar->isInitialized()) {
// don't remove myAvatar or uninitialized avatars from the list
++avatarIterator;
} else {
auto removedAvatar = avatarIterator.value();
avatarIterator = _avatarHash.erase(avatarIterator);
handleRemovedAvatar(removedAvatar);
if (avatar != _myAvatar) {
if (avatar->isInScene()) {
avatar->removeFromScene(avatar, scene, transaction);
}
AvatarMotionState* motionState = avatar->getMotionState();
if (motionState) {
_motionStatesThatMightUpdate.remove(motionState);
_motionStatesToAddToPhysics.remove(motionState);
_motionStatesToRemoveFromPhysics.push_back(motionState);
}
}
++avatarIterator;
}
if (scene) {
scene->enqueueTransaction(transaction);
}
_myAvatar->clearLookAtTargetAvatar();
}
void AvatarManager::clearAllAvatars() {
clearOtherAvatars();
QWriteLocker locker(&_hashLock);
handleRemovedAvatar(_myAvatar);
void AvatarManager::deleteAllAvatars() {
QReadLocker locker(&_hashLock);
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
avatarIterator = _avatarHash.erase(avatarIterator);
avatar->die();
}
}
void AvatarManager::setLocalLights(const QVector<AvatarManager::LocalLight>& localLights) {
@ -475,26 +521,25 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
}
void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) {
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()) {
_shouldRender = shouldRenderAvatars;
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
if (_shouldRender) {
for (auto avatarData : _avatarHash) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->addToScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
}
} else {
for (auto avatarData : _avatarHash) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->removeFromScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
}
}
if (scene) {
scene->enqueueTransaction(transaction);
}
}
AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) const {
if (sessionID == AVATAR_SELF_ID || sessionID == _myAvatar->getSessionUUID()) {
return _myAvatar;

View file

@ -53,7 +53,7 @@ public:
void postUpdate(float deltaTime);
void clearOtherAvatars();
void clearAllAvatars();
void deleteAllAvatars();
bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; }
@ -91,8 +91,8 @@ public slots:
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
void updateAvatarRenderStatus(bool shouldRenderAvatars);
private slots:
virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
protected slots:
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) override;
private:
explicit AvatarManager(QObject* parent = 0);
@ -100,12 +100,15 @@ private:
void simulateAvatarFades(float deltaTime);
// virtual overrides
virtual AvatarSharedPointer newSharedAvatar() override;
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) override;
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
AvatarSharedPointer newSharedAvatar() override;
void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
QVector<AvatarSharedPointer> _avatarsToFade;
SetOfAvatarMotionStates _motionStatesThatMightUpdate;
VectorOfMotionStates _motionStatesToRemoveFromPhysics;
SetOfMotionStates _motionStatesToAddToPhysics;
QVector<AvatarSharedPointer> _avatarFades;
std::shared_ptr<MyAvatar> _myAvatar;
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
@ -115,14 +118,11 @@ private:
std::list<QPointer<AudioInjector>> _collisionInjectors;
SetOfAvatarMotionStates _motionStatesThatMightUpdate;
SetOfMotionStates _motionStatesToAddToPhysics;
VectorOfMotionStates _motionStatesToRemoveFromPhysics;
RateCounter<> _myAvatarSendRate;
int _numAvatarsUpdated { 0 };
int _numAvatarsNotUpdated { 0 };
float _avatarSimulationTime { 0.0f };
bool _shouldRender { true };
};
Q_DECLARE_METATYPE(AvatarManager::LocalLight)

View file

@ -54,6 +54,7 @@
#include "DebugDraw.h"
#include "EntityEditPacketSender.h"
#include "MovingEntitiesOperator.h"
#include "SceneScriptingInterface.h"
using namespace std;
@ -1634,7 +1635,7 @@ void MyAvatar::postUpdate(float deltaTime) {
Avatar::postUpdate(deltaTime);
render::ScenePointer scene = qApp->getMain3DScene();
if (_skeletonModel->initWhenReady(scene)) {
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars() && _skeletonModel->initWhenReady(scene)) {
initHeadBones();
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();

View file

@ -109,7 +109,7 @@ void AnimVariantMap::animVariantMapFromScriptValue(const QScriptValue& source) {
if (z.isNumber()) {
QScriptValue w = value.property("w");
if (w.isNumber()) {
set(property.name(), glm::quat(x.toNumber(), y.toNumber(), z.toNumber(), w.toNumber()));
set(property.name(), glm::quat(w.toNumber(), x.toNumber(), y.toNumber(), z.toNumber()));
} else {
set(property.name(), glm::vec3(x.toNumber(), y.toNumber(), z.toNumber()));
}

View file

@ -74,7 +74,7 @@ void AnimationReader::run() {
// Parse the FBX directly from the QNetworkReply
FBXGeometry::Pointer fbxgeo;
if (_url.path().toLower().endsWith(".fbx")) {
fbxgeo.reset(readFBX(_data, QVariantHash(), _url));
fbxgeo.reset(readFBX(_data, QVariantHash(), _url.path()));
} else {
QString errorStr("usupported format");
emit onError(299, errorStr);

View file

@ -0,0 +1,6 @@
set(TARGET_NAME avatars-renderer)
AUTOSCRIBE_SHADER_LIB(gpu model render render-utils)
setup_hifi_library(Widgets Network Script)
link_hifi_libraries(shared gpu model animation physics model-networking script-engine render render-utils)
target_bullet()

View file

@ -0,0 +1,11 @@
//
// Created by Bradley Austin Davis on 2016/12/06
// Copyright 2013-2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AvatarsRendererLogging.h"
Q_LOGGING_CATEGORY(avatars_renderer, "hifi.avatars.rendering")

View file

@ -0,0 +1,16 @@
//
// Created by Bradley Austin Davis on 2016/12/06
// Copyright 2013-2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AvatarsRendererLogging_h
#define hifi_AvatarsRendererLogging_h
#include <QtCore/QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(avatars_renderer)
#endif // hifi_AvatarsRendererLogging_h

View file

@ -123,6 +123,7 @@ void AvatarData::setTargetScale(float targetScale) {
if (_targetScale != newValue) {
_targetScale = newValue;
_scaleChanged = usecTimestampNow();
_avatarScaleChanged = _scaleChanged;
}
}

View file

@ -80,13 +80,10 @@ AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWe
AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
QWriteLocker locker(&_hashLock);
auto avatar = _avatarHash.value(sessionUUID);
if (!avatar) {
avatar = addAvatar(sessionUUID, mixerWeakPointer);
}
return avatar;
}
@ -103,27 +100,33 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer<ReceivedMessage> mess
// enumerate over all of the avatars in this packet
// only add them if mixerWeakPointer points to something (meaning that mixer is still around)
while (message->getBytesLeftToRead()) {
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
parseAvatarData(message, sendingNode);
}
}
int positionBeforeRead = message->getPosition();
AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead());
int positionBeforeRead = message->getPosition();
// make sure this isn't our own avatar data or for a previously ignored node
auto nodeList = DependencyManager::get<NodeList>();
QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead());
if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) {
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
// make sure this isn't our own avatar data or for a previously ignored node
auto nodeList = DependencyManager::get<NodeList>();
// have the matching (or new) avatar parse the data from the packet
int bytesRead = avatar->parseDataFromBuffer(byteArray);
message->seek(positionBeforeRead + bytesRead);
} else {
// create a dummy AvatarData class to throw this data on the ground
AvatarData dummyData;
int bytesRead = dummyData.parseDataFromBuffer(byteArray);
message->seek(positionBeforeRead + bytesRead);
}
if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) {
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
// have the matching (or new) avatar parse the data from the packet
int bytesRead = avatar->parseDataFromBuffer(byteArray);
message->seek(positionBeforeRead + bytesRead);
return avatar;
} else {
// create a dummy AvatarData class to throw this data on the ground
AvatarData dummyData;
int bytesRead = dummyData.parseDataFromBuffer(byteArray);
message->seek(positionBeforeRead + bytesRead);
return std::make_shared<AvatarData>();
}
}

View file

@ -49,11 +49,11 @@ signals:
public slots:
bool isAvatarInRange(const glm::vec3 & position, const float range);
private slots:
protected slots:
void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID);
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
virtual void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void processExitingSpaceBubble(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
@ -61,12 +61,13 @@ private slots:
protected:
AvatarHashMap();
virtual AvatarSharedPointer parseAvatarData(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
virtual AvatarSharedPointer newSharedAvatar();
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock
virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason);
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
AvatarHash _avatarHash;

View file

@ -14,6 +14,7 @@
#include <QJsonDocument>
#include <QtCore/QThread>
#include <QUrlQuery>
#include <glm/gtx/transform.hpp>
#include <AbstractViewStateInterface.h>
@ -607,11 +608,19 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
face, surfaceNormal, extraInfo, precisionPicking);
}
void RenderableModelEntityItem::getCollisionGeometryResource() {
QUrl hullURL(getCompoundShapeURL());
QUrlQuery queryArgs(hullURL);
queryArgs.addQueryItem("collision-hull", "");
hullURL.setQuery(queryArgs);
_compoundShapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
}
void RenderableModelEntityItem::setShapeType(ShapeType type) {
ModelEntityItem::setShapeType(type);
if (getShapeType() == SHAPE_TYPE_COMPOUND) {
if (!_compoundShapeResource && !getCompoundShapeURL().isEmpty()) {
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(getCompoundShapeURL());
getCollisionGeometryResource();
}
} else if (_compoundShapeResource && !getCompoundShapeURL().isEmpty()) {
// the compoundURL has been set but the shapeType does not agree
@ -620,6 +629,10 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) {
}
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
// because the caching system only allows one Geometry per url, and because this url might also be used
// as a visual model, we need to change this url in some way. We add a "collision-hull" query-arg so it
// will end up in a different hash-key in ResourceCache. TODO: It would be better to use the same URL and
// parse it twice.
auto currentCompoundShapeURL = getCompoundShapeURL();
ModelEntityItem::setCompoundShapeURL(url);
@ -629,7 +642,7 @@ void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
}
if (getShapeType() == SHAPE_TYPE_COMPOUND) {
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(url);
getCollisionGeometryResource();
}
}
}
@ -661,7 +674,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
}
return true;
} else if (!getCompoundShapeURL().isEmpty()) {
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(getCompoundShapeURL());
getCollisionGeometryResource();
}
}

View file

@ -108,6 +108,7 @@ private:
QVariantMap parseTexturesToMap(QString textures);
void remapTextures();
void getCollisionGeometryResource();
GeometryResource::Pointer _compoundShapeResource;
ModelPointer _model = nullptr;
bool _needsInitialSimulation = true;

View file

@ -126,7 +126,7 @@ public:
void markAsChangedOnServer();
quint64 getLastChangedOnServer() const;
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -51,7 +51,10 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons
qCDebug(entities) << "EntityTreeElement::debugExtraEncodeData()... ";
qCDebug(entities) << " element:" << _cube;
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
assert(entityNodeData);
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
@ -65,7 +68,11 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons
void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
assert(entityNodeData);
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
// Check to see if this element yet has encode data... if it doesn't create it
if (!extraEncodeData->contains(this)) {
@ -93,7 +100,11 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
}
bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
assert(entityNodeData);
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
@ -122,7 +133,10 @@ bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamPa
}
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
assert(entityNodeData);
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
@ -137,8 +151,12 @@ bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const
}
void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
assert(entityNodeData);
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
@ -161,7 +179,10 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube;
}
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
assert(entityNodeData);
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
assert(extraEncodeData->contains(this));
@ -236,8 +257,12 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best...
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
Q_ASSERT_X(entityNodeData, "EntityTreeElement::appendElementData", "expected params.nodeData not to be null");
// first, check the params.extraEncodeData to see if there's any partial re-encode data for this element
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData = NULL;
bool hadElementExtraData = false;
if (extraEncodeData && extraEncodeData->contains(this)) {
@ -285,20 +310,17 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// need to handle the case where our sibling elements need encoding but we don't.
if (!entityTreeElementExtraEncodeData->elementCompleted) {
QJsonObject jsonFilters;
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
if (entityNodeData) {
// we have an EntityNodeData instance
// so we should assume that means we might have JSON filters to check
jsonFilters = entityNodeData->getJSONParameters();
}
// we have an EntityNodeData instance
// so we should assume that means we might have JSON filters to check
auto jsonFilters = entityNodeData->getJSONParameters();
for (uint16_t i = 0; i < _entityItems.size(); i++) {
EntityItemPointer entity = _entityItems[i];
bool includeThisEntity = true;
if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastQuerySent) {
if (!params.forceSendScene && entity->getLastChangedOnServer() < entityNodeData->getLastTimeBagEmpty()) {
includeThisEntity = false;
}
@ -330,7 +352,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// we only check the bounds against our frustum and LOD if the query has asked us to check against the frustum
// which can sometimes not be the case when JSON filters are sent
if (params.usesFrustum && (includeThisEntity || params.recurseEverything)) {
if (entityNodeData->getUsesFrustum() && (includeThisEntity || params.recurseEverything)) {
// we want to use the maximum possible box for this, so that we don't have to worry about the nuance of
// simulation changing what's visible. consider the case where the entity contains an angular velocity

View file

@ -195,7 +195,7 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_IS_SPOTLIGHT;

View file

@ -131,7 +131,7 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;

View file

@ -26,7 +26,7 @@ class LineEntityItem : public EntityItem {
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -160,7 +160,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
return bytesRead;
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);

View file

@ -29,7 +29,7 @@ public:
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -469,7 +469,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);

View file

@ -178,7 +178,7 @@ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;

View file

@ -26,7 +26,7 @@ class PolyLineEntityItem : public EntityItem {
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -179,7 +179,7 @@ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* dat
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_VOXEL_VOLUME_SIZE;

View file

@ -26,7 +26,7 @@ class PolyVoxEntityItem : public EntityItem {
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -137,7 +137,7 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_SHAPE;

View file

@ -99,7 +99,7 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_TEXT;

View file

@ -30,7 +30,7 @@ public:
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -84,7 +84,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_SOURCE_URL;

View file

@ -29,7 +29,7 @@ public:
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -137,7 +137,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);

View file

@ -30,7 +30,7 @@ public:
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastQuerySent time
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -376,10 +376,10 @@ public:
};
bool checkMaterialsHaveTextures(const QHash<QString, FBXMaterial>& materials,
const QHash<QString, QByteArray>& textureFilepaths, const QMultiMap<QString, QString>& _connectionChildMap) {
const QHash<QString, QByteArray>& textureFilenames, const QMultiMap<QString, QString>& _connectionChildMap) {
foreach (const QString& materialID, materials.keys()) {
foreach (const QString& childID, _connectionChildMap.values(materialID)) {
if (textureFilepaths.contains(childID)) {
if (textureFilenames.contains(childID)) {
return true;
}
}
@ -443,48 +443,21 @@ FBXLight extractLight(const FBXNode& object) {
return light;
}
QByteArray fixedTextureFilepath(QByteArray fbxRelativeFilepath, QUrl url) {
// first setup a QFileInfo for the passed relative filepath, with backslashes replaced by forward slashes
auto fileInfo = QFileInfo { fbxRelativeFilepath.replace("\\", "/") };
QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
QString path = QFileInfo(url).path();
QByteArray filename = filepath;
QFileInfo checkFile(path + "/" + filepath);
#ifndef Q_OS_WIN
// it turns out that absolute windows paths starting with drive letters look like relative paths to QFileInfo on UNIX
// so we add a check for that here to work around it
bool isRelative = fbxRelativeFilepath[1] != ':' && fileInfo.isRelative();
#else
bool isRelative = fileInfo.isRelative();
#endif
if (isRelative) {
// the RelativeFilename pulled from the FBX is already correctly relative
// so simply return this as the filepath to use
return fbxRelativeFilepath;
} else {
// the RelativeFilename pulled from the FBX is an absolute path
// use the URL to figure out where the FBX is being loaded from
auto filename = fileInfo.fileName();
if (url.isLocalFile()) {
// the FBX is being loaded from the local filesystem
if (fileInfo.exists() && fileInfo.isFile()) {
// found a file at the absolute path in the FBX, return that path
return fbxRelativeFilepath;
} else {
// didn't find a file at the absolute path, assume it is right beside the FBX
// return just the filename as the relative path
return filename.toUtf8();
}
} else {
// this is a remote file, meaning we can't really do anything with the absolute path to the texture
// so assume it will be right beside the fbx
return filename.toUtf8();
}
// check if the file exists at the RelativeFilename
if (!(checkFile.exists() && checkFile.isFile())) {
// if not, assume it is in the fbx directory
filename = filename.mid(filename.lastIndexOf('/') + 1);
}
return filename;
}
FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QUrl& url) {
FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) {
const FBXNode& node = _fbxNode;
QMap<QString, ExtractedMesh> meshes;
QHash<QString, QString> modelIDsToNames;
@ -860,9 +833,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU
const int MODEL_UV_SCALING_MIN_SIZE = 2;
const int CROPPING_MIN_SIZE = 4;
if (subobject.name == "RelativeFilename" && subobject.properties.length() >= RELATIVE_FILENAME_MIN_SIZE) {
auto filepath = fixedTextureFilepath(subobject.properties.at(0).toByteArray(), url);
QByteArray filename = subobject.properties.at(0).toByteArray();
QByteArray filepath = filename.replace('\\', '/');
filename = fileOnUrl(filepath, url);
_textureFilepaths.insert(getID(object.properties), filepath);
_textureFilenames.insert(getID(object.properties), filename);
} else if (subobject.name == "TextureName" && subobject.properties.length() >= TEXTURE_NAME_MIN_SIZE) {
// trim the name from the timestamp
QString name = QString(subobject.properties.at(0).toByteArray());
@ -955,7 +930,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU
QByteArray content;
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "RelativeFilename") {
filepath = subobject.properties.at(0).toByteArray();
filepath= subobject.properties.at(0).toByteArray();
filepath = filepath.replace('\\', '/');
} else if (subobject.name == "Content" && !subobject.properties.isEmpty()) {
@ -1527,7 +1502,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU
geometry.materials = _fbxMaterials;
// see if any materials have texture children
bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, _textureFilepaths, _connectionChildMap);
bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, _textureFilenames, _connectionChildMap);
for (QMap<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
ExtractedMesh& extracted = it.value();
@ -1572,7 +1547,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU
materialIndex++;
} else if (_textureFilepaths.contains(childID)) {
} else if (_textureFilenames.contains(childID)) {
FBXTexture texture = getTexture(childID);
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
int partTexture = extracted.partMaterialTextures.at(j).second;
@ -1843,13 +1818,13 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QU
return geometryPtr;
}
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) {
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);
return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel);
}
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) {
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
FBXReader reader;
reader._fbxNode = FBXReader::parseFBX(device);
reader._loadLightmaps = loadLightmaps;

View file

@ -268,7 +268,7 @@ class FBXGeometry {
public:
using Pointer = std::shared_ptr<FBXGeometry>;
QUrl originalURL;
QString originalURL;
QString author;
QString applicationName; ///< the name of the application that generated the model
@ -330,11 +330,11 @@ Q_DECLARE_METATYPE(FBXGeometry::Pointer)
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QUrl& url = QUrl(), bool loadLightmaps = true, float lightmapLevel = 1.0f);
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QUrl& url = QUrl(), bool loadLightmaps = true, float lightmapLevel = 1.0f);
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
class TextureParam {
public:
@ -402,17 +402,19 @@ public:
FBXNode _fbxNode;
static FBXNode parseFBX(QIODevice* device);
FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QUrl& url);
FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QString& url);
ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex);
QHash<QString, ExtractedMesh> meshes;
static void buildModelMesh(FBXMesh& extractedMesh, const QUrl& url);
static void buildModelMesh(FBXMesh& extractedMesh, const QString& url);
FBXTexture getTexture(const QString& textureID);
QHash<QString, QString> _textureNames;
// Hashes the original RelativeFilename of textures
QHash<QString, QByteArray> _textureFilepaths;
// Hashes the place to look for textures, in case they are not inlined
QHash<QString, QByteArray> _textureFilenames;
// Hashes texture content by filepath, in case they are inlined
QHash<QByteArray, QByteArray> _textureContent;
QHash<QString, TextureParam> _textureParams;

View file

@ -85,7 +85,12 @@ FBXTexture FBXReader::getTexture(const QString& textureID) {
FBXTexture texture;
const QByteArray& filepath = _textureFilepaths.value(textureID);
texture.content = _textureContent.value(filepath);
texture.filename = filepath;
if (texture.content.isEmpty()) { // the content is not inlined
texture.filename = _textureFilenames.value(textureID);
} else { // use supplied filepath for inlined content
texture.filename = filepath;
}
texture.name = _textureNames.value(textureID);
texture.transform.setIdentity();
@ -150,7 +155,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) {
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach(const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) {
if (_textureFilepaths.contains(childTextureID)) {
if (_textureFilenames.contains(childTextureID)) {
diffuseTexture = getTexture(diffuseTextureID);
}
}

View file

@ -388,7 +388,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn
return data.extracted;
}
void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QUrl& url) {
void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) {
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*");
unsigned int totalSourceIndices = 0;

View file

@ -302,7 +302,8 @@ QNetworkReply* OBJReader::request(QUrl& url, bool isTest) {
}
bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess) {
bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry,
float& scaleGuess, bool combineParts) {
FaceGroup faces;
FBXMesh& mesh = geometry.meshes[0];
mesh.parts.append(FBXMeshPart());
@ -348,6 +349,9 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi
}
QByteArray groupName = tokenizer.getDatum();
currentGroup = groupName;
if (!combineParts) {
currentMaterialName = QString("part-") + QString::number(_partCounter++);
}
} else if (token == "mtllib" && !_url.isEmpty()) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
break;
@ -361,7 +365,9 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi
}
QString nextName = tokenizer.getDatum();
if (nextName != currentMaterialName) {
currentMaterialName = nextName;
if (combineParts) {
currentMaterialName = nextName;
}
#ifdef WANT_DEBUG
qCDebug(modelformat) << "OBJ Reader new current material:" << currentMaterialName;
#endif
@ -419,7 +425,7 @@ done:
}
FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, const QUrl& url) {
FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url) {
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);
QBuffer buffer { &model };
buffer.open(QIODevice::ReadOnly);
@ -438,7 +444,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping,
try {
// call parseOBJGroup as long as it's returning true. Each successful call will
// add a new meshPart to the geometry's single mesh.
while (parseOBJGroup(tokenizer, mapping, geometry, scaleGuess)) {}
while (parseOBJGroup(tokenizer, mapping, geometry, scaleGuess, combineParts)) {}
FBXMesh& mesh = geometry.meshes[0];
mesh.meshIndex = 0;

View file

@ -22,7 +22,7 @@ public:
glm::vec3 getVec3();
glm::vec2 getVec2();
float getFloat() { return std::stof((nextToken() != OBJTokenizer::DATUM_TOKEN) ? nullptr : getDatum().data()); }
private:
QIODevice* _device;
QByteArray _datum;
@ -73,15 +73,18 @@ public:
QHash<QString, OBJMaterial> materials;
QNetworkReply* request(QUrl& url, bool isTest);
FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, const QUrl& url = QUrl());
FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl());
private:
QUrl _url;
QHash<QByteArray, bool> librariesSeen;
bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess);
bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry,
float& scaleGuess, bool combineParts);
void parseMaterialLibrary(QIODevice* device);
bool isValidTexture(const QByteArray &filename); // true if the file exists. TODO?: check content-type header and that it is a supported format.
int _partCounter { 0 };
};
// What are these utilities doing here? One is used by fbx loading code in VHACD Utils, and the other a general debugging utility.

View file

@ -32,6 +32,7 @@ class GeometryExtra {
public:
const QVariantHash& mapping;
const QUrl& textureBaseUrl;
bool combineParts;
};
QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) {
@ -89,7 +90,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
}
auto modelCache = DependencyManager::get<ModelCache>();
GeometryExtra extra{ mapping, _textureBaseUrl };
GeometryExtra extra{ mapping, _textureBaseUrl, false };
// Get the raw GeometryResource
_geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast<GeometryResource>();
@ -129,8 +130,8 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
class GeometryReader : public QRunnable {
public:
GeometryReader(QWeakPointer<Resource>& resource, const QUrl& url, const QVariantHash& mapping,
const QByteArray& data) :
_resource(resource), _url(url), _mapping(mapping), _data(data) {
const QByteArray& data, bool combineParts) :
_resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts) {
DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing");
}
@ -142,6 +143,7 @@ private:
QUrl _url;
QVariantHash _mapping;
QByteArray _data;
bool _combineParts;
};
void GeometryReader::run() {
@ -173,12 +175,12 @@ void GeometryReader::run() {
FBXGeometry::Pointer fbxGeometry;
if (_url.path().toLower().endsWith(".fbx")) {
fbxGeometry.reset(readFBX(_data, _mapping, _url));
fbxGeometry.reset(readFBX(_data, _mapping, _url.path()));
if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) {
throw QString("empty geometry, possibly due to an unsupported FBX version");
}
} else if (_url.path().toLower().endsWith(".obj")) {
fbxGeometry.reset(OBJReader().readOBJ(_data, _mapping, _url));
fbxGeometry.reset(OBJReader().readOBJ(_data, _mapping, _combineParts, _url));
} else {
throw QString("unsupported format");
}
@ -209,8 +211,8 @@ void GeometryReader::run() {
class GeometryDefinitionResource : public GeometryResource {
Q_OBJECT
public:
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _mapping(mapping) {}
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) :
GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _mapping(mapping), _combineParts(combineParts) {}
QString getType() const override { return "GeometryDefinition"; }
@ -221,10 +223,11 @@ protected:
private:
QVariantHash _mapping;
bool _combineParts;
};
void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
QThreadPool::globalInstance()->start(new GeometryReader(_self, _url, _mapping, data));
QThreadPool::globalInstance()->start(new GeometryReader(_self, _url, _mapping, data, _combineParts));
}
void GeometryDefinitionResource::setGeometryDefinition(FBXGeometry::Pointer fbxGeometry) {
@ -265,7 +268,7 @@ ModelCache::ModelCache() {
}
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
const void* extra) {
const void* extra) {
Resource* resource = nullptr;
if (url.path().toLower().endsWith(".fst")) {
resource = new GeometryMappingResource(url);
@ -273,14 +276,30 @@ QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QShar
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(extra);
auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash();
auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl();
resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl);
bool combineParts = geometryExtra ? geometryExtra->combineParts : true;
resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl, combineParts);
}
return QSharedPointer<Resource>(resource, &Resource::deleter);
}
GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) {
GeometryExtra geometryExtra = { mapping, textureBaseUrl };
GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url,
const QVariantHash& mapping, const QUrl& textureBaseUrl) {
bool combineParts = true;
GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts };
GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast<GeometryResource>();
if (resource) {
if (resource->isLoaded() && resource->shouldSetTextures()) {
resource->setTextures();
}
}
return resource;
}
GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& url,
const QVariantHash& mapping, const QUrl& textureBaseUrl) {
bool combineParts = false;
GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts };
GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast<GeometryResource>();
if (resource) {
if (resource->isLoaded() && resource->shouldSetTextures()) {

View file

@ -136,13 +136,18 @@ class ModelCache : public ResourceCache, public Dependency {
public:
GeometryResource::Pointer getGeometryResource(const QUrl& url,
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl());
const QVariantHash& mapping = QVariantHash(),
const QUrl& textureBaseUrl = QUrl());
GeometryResource::Pointer getCollisionGeometryResource(const QUrl& url,
const QVariantHash& mapping = QVariantHash(),
const QUrl& textureBaseUrl = QUrl());
protected:
friend class GeometryMappingResource;
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
const void* extra) override;
const void* extra) override;
private:
ModelCache();

View file

@ -191,7 +191,7 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) {
result->setObjectName(url.toString());
result->_resource = resource;
if (resource->isLoaded()) {
if (resource->isLoaded() || resource->_failedToLoad) {
result->finished(!resource->_failedToLoad);
} else {
result->_progressConnection = connect(

View file

@ -295,8 +295,8 @@ protected:
/// Creates a new resource.
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
const void* extra) = 0;
const void* extra) = 0;
void addUnusedResource(const QSharedPointer<Resource>& resource);
void removeUnusedResource(const QSharedPointer<Resource>& resource);

View file

@ -43,14 +43,15 @@
#include <PathUtils.h>
#include <ViewFrustum.h>
#include "Octree.h"
#include "OctreeConstants.h"
#include "OctreeElementBag.h"
#include "Octree.h"
#include "OctreeUtils.h"
#include "OctreeLogging.h"
#include "OctreeQueryNode.h"
#include "OctreeUtils.h"
QVector<QString> PERSIST_EXTENSIONS = {"svo", "json", "json.gz"};
QVector<QString> PERSIST_EXTENSIONS = {"json", "json.gz"};
Octree::Octree(bool shouldReaverage) :
_rootElement(NULL),
@ -898,8 +899,16 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
return bytesWritten;
}
// you can't call this without a valid nodeData
auto octreeQueryNode = static_cast<OctreeQueryNode*>(params.nodeData);
if (!octreeQueryNode) {
qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL");
params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA;
return bytesWritten;
}
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
if (params.usesFrustum && !params.recurseEverything && !element->isInView(params.viewFrustum)) {
if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything && !element->isInView(params.viewFrustum)) {
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
return bytesWritten;
}
@ -935,9 +944,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
// record some stats, this is the one element that we won't record below in the recursion function, so we need to
// track it here
if (params.stats) {
params.stats->traversed(element);
}
octreeQueryNode->stats.traversed(element);
ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
@ -993,6 +1000,15 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
return bytesAtThisLevel;
}
// you can't call this without a valid nodeData
auto octreeQueryNode = static_cast<OctreeQueryNode*>(params.nodeData);
if (!octreeQueryNode) {
qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL");
params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA;
return bytesAtThisLevel;
}
// Keep track of how deep we've encoded.
currentEncodeLevel++;
@ -1015,15 +1031,13 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
}
ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
if (params.usesFrustum && !params.recurseEverything) {
if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything) {
float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust,
params.octreeElementSizeScale);
// If we're too far away for our render level, then just return
if (element->distanceToCamera(params.viewFrustum) >= boundaryDistance) {
if (params.stats) {
params.stats->skippedDistance(element);
}
octreeQueryNode->stats.skippedDistance(element);
params.stopReason = EncodeBitstreamParams::LOD_SKIP;
return bytesAtThisLevel;
}
@ -1039,9 +1053,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if
// we're out of view
if (nodeLocationThisView == ViewFrustum::OUTSIDE) {
if (params.stats) {
params.stats->skippedOutOfView(element);
}
octreeQueryNode->stats.skippedOutOfView(element);
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
return bytesAtThisLevel;
}
@ -1066,7 +1078,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// as "was in view"...
if (wasInView) {
float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust,
params.octreeElementSizeScale);
params.octreeElementSizeScale);
if (element->distanceToCamera(params.lastViewFrustum) >= boundaryDistance) {
// This would have been invisible... but now should be visible (we wouldn't be here otherwise)...
wasInView = false;
@ -1077,22 +1089,20 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// If we were previously in the view, then we normally will return out of here and stop recursing. But
// if we're in deltaView mode, and this element has changed since it was last sent, then we do
// need to send it.
if (wasInView && !(params.deltaView && element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE))) {
if (params.stats) {
params.stats->skippedWasInView(element);
}
if (wasInView && !(params.deltaView && element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))) {
octreeQueryNode->stats.skippedWasInView(element);
params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW;
return bytesAtThisLevel;
}
}
// If we're not in delta sending mode, and we weren't asked to do a force send, and the octree element hasn't changed,
// If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed,
// then we can also bail early and save bits
if (!params.forceSendScene && !params.deltaView &&
!element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE)) {
if (params.stats) {
params.stats->skippedNoChange(element);
}
!element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE)) {
octreeQueryNode->stats.skippedNoChange(element);
params.stopReason = EncodeBitstreamParams::NO_CHANGE;
return bytesAtThisLevel;
}
@ -1164,8 +1174,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// track stats
// must check childElement here, because it could be we got here with no childElement
if (params.stats && childElement) {
params.stats->traversed(childElement);
if (childElement) {
octreeQueryNode->stats.traversed(childElement);
}
}
@ -1176,7 +1186,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
int originalIndex = indexOfChildren[i];
bool childIsInView = (childElement &&
(params.recurseEverything || !params.usesFrustum ||
(params.recurseEverything || !octreeQueryNode->getUsesFrustum() ||
(nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are
(nodeLocationThisView == ViewFrustum::INTERSECT &&
childElement->isInView(params.viewFrustum)) // the parent intersects and the child is in view
@ -1184,20 +1194,18 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
if (!childIsInView) {
// must check childElement here, because it could be we got here because there was no childElement
if (params.stats && childElement) {
params.stats->skippedOutOfView(childElement);
if (childElement) {
octreeQueryNode->stats.skippedOutOfView(childElement);
}
} else {
// Before we consider this further, let's see if it's in our LOD scope...
float boundaryDistance = params.recurseEverything || !params.usesFrustum ? 1 :
float boundaryDistance = params.recurseEverything || !octreeQueryNode->getUsesFrustum() ? 1 :
boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust,
params.octreeElementSizeScale);
if (!(distancesToChildren[i] < boundaryDistance)) {
// don't need to check childElement here, because we can't get here with no childElement
if (params.stats) {
params.stats->skippedDistance(childElement);
}
octreeQueryNode->stats.skippedDistance(childElement);
} else {
inViewCount++;
@ -1211,20 +1219,18 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
bool childIsOccluded = false; // assume it's not occluded
bool shouldRender = params.recurseEverything || !params.usesFrustum ||
bool shouldRender = params.recurseEverything || !octreeQueryNode->getUsesFrustum() ||
childElement->calculateShouldRender(params.viewFrustum,
params.octreeElementSizeScale, params.boundaryLevelAdjust);
// track some stats
if (params.stats) {
// don't need to check childElement here, because we can't get here with no childElement
if (!shouldRender && childElement->isLeaf()) {
params.stats->skippedDistance(childElement);
}
// don't need to check childElement here, because we can't get here with no childElement
if (childIsOccluded) {
params.stats->skippedOccluded(childElement);
}
// don't need to check childElement here, because we can't get here with no childElement
if (!shouldRender && childElement->isLeaf()) {
octreeQueryNode->stats.skippedDistance(childElement);
}
// don't need to check childElement here, because we can't get here with no childElement
if (childIsOccluded) {
octreeQueryNode->stats.skippedOccluded(childElement);
}
// track children with actual color, only if the child wasn't previously in view!
@ -1247,19 +1253,17 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// need to send it.
if (!childWasInView ||
(params.deltaView &&
childElement->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE))){
childElement->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))){
childrenDataBits += (1 << (7 - originalIndex));
inViewWithColorCount++;
} else {
// otherwise just track stats of the items we discarded
// don't need to check childElement here, because we can't get here with no childElement
if (params.stats) {
if (childWasInView) {
params.stats->skippedWasInView(childElement);
} else {
params.stats->skippedNoChange(childElement);
}
if (childWasInView) {
octreeQueryNode->stats.skippedWasInView(childElement);
} else {
octreeQueryNode->stats.skippedNoChange(childElement);
}
}
}
@ -1277,9 +1281,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
assert(continueThisLevel); // since we used reserved bits, this really shouldn't fail
bytesAtThisLevel += sizeof(childrenDataBits); // keep track of byte count
if (params.stats) {
params.stats->colorBitsWritten(); // really data bits not just color bits
}
octreeQueryNode->stats.colorBitsWritten(); // really data bits not just color bits
// NOW might be a good time to give our tree subclass and this element a chance to set up and check any extra encode data
element->initializeExtraEncodeData(params);
@ -1349,8 +1352,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
// don't need to check childElement here, because we can't get here with no childElement
if (params.stats && (childAppendState != OctreeElement::NONE)) {
params.stats->colorSent(childElement);
if (childAppendState != OctreeElement::NONE) {
octreeQueryNode->stats.colorSent(childElement);
}
}
}
@ -1377,9 +1380,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits);
if (continueThisLevel) {
bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count
if (params.stats) {
params.stats->existsBitsWritten();
}
octreeQueryNode->stats.existsBitsWritten();
} else {
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInTreeBits";
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
@ -1392,9 +1394,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits);
if (continueThisLevel) {
bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count
if (params.stats) {
params.stats->existsInPacketBitsWritten();
}
octreeQueryNode->stats.existsInPacketBitsWritten();
} else {
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInPacketBits";
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
@ -1451,7 +1452,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep
// recursing, by returning TRUE in recurseChildrenWithData().
if (params.recurseEverything || !params.usesFrustum
if (params.recurseEverything || !octreeQueryNode->getUsesFrustum()
|| recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) {
// Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this
@ -1502,8 +1503,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
}
// If this is the last of the child exists bits, then we're actually be rolling out the entire tree
if (params.stats && childrenExistInPacketBits == 0) {
params.stats->childBitsRemoved(params.includeExistsBits);
if (childrenExistInPacketBits == 0) {
octreeQueryNode->stats.childBitsRemoved(params.includeExistsBits);
}
if (!continueThisLevel) {
@ -1558,9 +1559,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
if (continueThisLevel) {
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
if (params.stats) {
params.stats->colorSent(element);
}
octreeQueryNode->stats.colorSent(element);
}
if (!continueThisLevel) {
@ -1595,9 +1594,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
bag.insert(element);
// don't need to check element here, because we can't get here with no element
if (params.stats) {
params.stats->didntFit(element);
}
octreeQueryNode->stats.didntFit(element);
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
bytesAtThisLevel = 0; // didn't fit
@ -1876,9 +1873,7 @@ bool Octree::writeToFile(const char* fileName, OctreeElementPointer element, QSt
const char* cFileName = byteArray.constData();
bool success = false;
if (persistAsFileType == "svo") {
success = writeToSVOFile(fileName, element);
} else if (persistAsFileType == "json") {
if (persistAsFileType == "json") {
success = writeToJSONFile(cFileName, element);
} else if (persistAsFileType == "json.gz") {
success = writeToJSONFile(cFileName, element, true);
@ -1936,95 +1931,6 @@ bool Octree::writeToJSONFile(const char* fileName, OctreeElementPointer element,
return success;
}
bool Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) {
qWarning() << "SVO file format deprecated. Support for reading SVO files is no longer support and will be removed soon.";
bool success = false;
std::ofstream file(fileName, std::ios::out|std::ios::binary);
if(file.is_open()) {
qCDebug(octree, "Saving binary SVO to file %s...", fileName);
PacketType expectedPacketType = expectedDataPacketType();
int expectedIntType = (int) expectedPacketType;
PacketVersion expectedVersion = versionForPacketType(expectedPacketType);
bool hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion);
// before reading the file, check to see if this version of the Octree supports file versions
if (getWantSVOfileVersions()) {
// if so, read the first byte of the file and see if it matches the expected version code
file.write(reinterpret_cast<char*>(&expectedIntType), sizeof(expectedIntType));
file.write(&expectedVersion, sizeof(expectedVersion));
qCDebug(octree) << "SVO file type: " << expectedPacketType << " version: " << (int)expectedVersion;
hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion);
}
if (hasBufferBreaks) {
qCDebug(octree) << " this version includes buffer breaks";
} else {
qCDebug(octree) << " this version does not include buffer breaks";
}
OctreeElementBag elementBag;
OctreeElementExtraEncodeData extraEncodeData;
// If we were given a specific element, start from there, otherwise start from root
if (element) {
elementBag.insert(element);
} else {
elementBag.insert(_rootElement);
}
OctreePacketData packetData;
int bytesWritten = 0;
bool lastPacketWritten = false;
while (OctreeElementPointer subTree = elementBag.extract()) {
EncodeBitstreamParams params(INT_MAX, NO_EXISTS_BITS);
params.recurseEverything = true;
withReadLock([&] {
params.extraEncodeData = &extraEncodeData;
bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params);
});
// if the subTree couldn't fit, and so we should reset the packet and reinsert the element in our bag and try again
if (bytesWritten == 0 && (params.stopReason == EncodeBitstreamParams::DIDNT_FIT)) {
if (packetData.hasContent()) {
// if this type of SVO file should have buffer breaks, then we will write a buffer size before each
// buffer to allow the reader to read this file in chunks.
if (hasBufferBreaks) {
quint16 bufferSize = packetData.getFinalizedSize();
file.write((const char*)&bufferSize, sizeof(bufferSize));
}
file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize());
lastPacketWritten = true;
}
packetData.reset(); // is there a better way to do this? could we fit more?
elementBag.insert(subTree);
} else {
lastPacketWritten = false;
}
}
if (!lastPacketWritten) {
// if this type of SVO file should have buffer breaks, then we will write a buffer size before each
// buffer to allow the reader to read this file in chunks.
if (hasBufferBreaks) {
quint16 bufferSize = packetData.getFinalizedSize();
file.write((const char*)&bufferSize, sizeof(bufferSize));
}
file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize());
}
releaseSceneEncodeData(&extraEncodeData);
success = true;
}
file.close();
return success;
}
unsigned long Octree::getOctreeElementsCount() {
unsigned long nodeCount = 0;
recurseTreeWithOperation(countOctreeElementsOperation, &nodeCount);

View file

@ -59,9 +59,7 @@ const bool DONT_COLLAPSE = false;
const int DONT_CHOP = 0;
const int NO_BOUNDARY_ADJUST = 0;
const int LOW_RES_MOVING_ADJUST = 1;
const quint64 IGNORE_LAST_SENT = 0;
#define IGNORE_SCENE_STATS NULL
#define IGNORE_COVERAGE_MAP NULL
#define IGNORE_JURISDICTION_MAP NULL
@ -69,7 +67,6 @@ class EncodeBitstreamParams {
public:
ViewFrustum viewFrustum;
ViewFrustum lastViewFrustum;
quint64 lastQuerySent;
int maxEncodeLevel;
int maxLevelReached;
bool includeExistsBits;
@ -79,10 +76,7 @@ public:
int boundaryLevelAdjust;
float octreeElementSizeScale;
bool forceSendScene;
OctreeSceneStats* stats;
JurisdictionMap* jurisdictionMap;
OctreeElementExtraEncodeData* extraEncodeData;
bool usesFrustum;
NodeData* nodeData;
// output hints from the encode process
@ -90,6 +84,7 @@ public:
UNKNOWN,
DIDNT_FIT,
NULL_NODE,
NULL_NODE_DATA,
TOO_DEEP,
OUT_OF_JURISDICTION,
LOD_SKIP,
@ -107,14 +102,9 @@ public:
bool useDeltaView = false,
int boundaryLevelAdjust = NO_BOUNDARY_ADJUST,
float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
quint64 lastQuerySent = IGNORE_LAST_SENT,
bool forceSendScene = true,
OctreeSceneStats* stats = IGNORE_SCENE_STATS,
JurisdictionMap* jurisdictionMap = IGNORE_JURISDICTION_MAP,
OctreeElementExtraEncodeData* extraEncodeData = nullptr,
bool usesFrustum = true,
NodeData* nodeData = nullptr) :
lastQuerySent(lastQuerySent),
maxEncodeLevel(maxEncodeLevel),
maxLevelReached(0),
includeExistsBits(includeExistsBits),
@ -123,10 +113,7 @@ public:
boundaryLevelAdjust(boundaryLevelAdjust),
octreeElementSizeScale(octreeElementSizeScale),
forceSendScene(forceSendScene),
stats(stats),
jurisdictionMap(jurisdictionMap),
extraEncodeData(extraEncodeData),
usesFrustum(usesFrustum),
nodeData(nodeData),
stopReason(UNKNOWN)
{
@ -306,9 +293,8 @@ public:
void loadOctreeFile(const char* fileName);
// Octree exporters
bool writeToFile(const char* filename, OctreeElementPointer element = NULL, QString persistAsFileType = "svo");
bool writeToFile(const char* filename, OctreeElementPointer element = NULL, QString persistAsFileType = "json.gz");
bool writeToJSONFile(const char* filename, OctreeElementPointer element = NULL, bool doGzip = false);
bool writeToSVOFile(const char* filename, OctreeElementPointer element = NULL);
virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues,
bool skipThoseWithBadParents) = 0;

View file

@ -35,7 +35,7 @@ public:
OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory,
int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false,
const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false, QString persistAsFileType="svo");
const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false, QString persistAsFileType="json.gz");
bool isInitialLoadComplete() const { return _initialLoadComplete; }
quint64 getLoadElapsedTime() const { return _loadTimeUSecs; }

View file

@ -511,7 +511,6 @@ WebTablet.prototype.mousePressEvent = function (event) {
tablet.gotoHomeScreen();
this.setHomeButtonTexture();
}
Messages.sendLocalMessage("home", this.homeButtonID);
}
} else if (!HMD.active && (!overlayPickResults.intersects || overlayPickResults.overlayID !== this.webOverlayID)) {
this.dragging = true;

View file

@ -43,7 +43,8 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) {
QByteArray fbxContents = fbx.readAll();
FBXGeometry* geom;
if (filename.toLower().endsWith(".obj")) {
geom = OBJReader().readOBJ(fbxContents, QVariantHash());
bool combineParts = false;
geom = OBJReader().readOBJ(fbxContents, QVariantHash(), combineParts);
} else if (filename.toLower().endsWith(".fbx")) {
geom = readFBX(fbxContents, QVariantHash(), filename);
} else {

View file

@ -10,6 +10,7 @@
//
#include <QCommandLineParser>
#include <Trace.h>
#include <VHACD.h>
#include "VHACDUtilApp.h"
#include "VHACDUtil.h"
@ -98,6 +99,8 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) :
{
vhacd::VHACDUtil vUtil;
DependencyManager::set<tracing::Tracer>();
// parse command-line
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity Object Decomposer");