mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 03:24:00 +02:00
Merge branch 'master' into 20855
This commit is contained in:
commit
b9054897f7
38 changed files with 573 additions and 213 deletions
26
examples/entityScripts/changeColorOnEnterLeave.js
Normal file
26
examples/entityScripts/changeColorOnEnterLeave.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// changeColorOnEnterLeave.js
|
||||
// examples/entityScripts
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 3/31/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function(){
|
||||
function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
this.enterEntity = function(myID) {
|
||||
print("enterEntity() myID:" + myID);
|
||||
Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} });
|
||||
};
|
||||
|
||||
this.leaveEntity = function(myID) {
|
||||
print("leaveEntity() myID:" + myID);
|
||||
Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} });
|
||||
};
|
||||
})
|
BIN
examples/tests/cube_texture.png
Normal file
BIN
examples/tests/cube_texture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 MiB |
41
examples/tests/textureStress.fs
Normal file
41
examples/tests/textureStress.fs
Normal file
|
@ -0,0 +1,41 @@
|
|||
float aspect(vec2 v) {
|
||||
return v.x / v.y;
|
||||
}
|
||||
|
||||
vec3 aspectCorrectedTexture() {
|
||||
vec2 uv = _position.xy;
|
||||
uv += 0.5;
|
||||
uv.y = 1.0 - uv.y;
|
||||
|
||||
float targetAspect = iWorldScale.x / iWorldScale.y;
|
||||
float sourceAspect = aspect(iChannelResolution[0].xy);
|
||||
float aspectCorrection = sourceAspect / targetAspect;
|
||||
if (aspectCorrection > 1.0) {
|
||||
float offset = aspectCorrection - 1.0;
|
||||
float halfOffset = offset / 2.0;
|
||||
uv.y -= halfOffset;
|
||||
uv.y *= aspectCorrection;
|
||||
} else {
|
||||
float offset = 1.0 - aspectCorrection;
|
||||
float halfOffset = offset / 2.0;
|
||||
uv.x -= halfOffset;
|
||||
uv.x /= aspectCorrection;
|
||||
}
|
||||
|
||||
if (any(lessThan(uv, vec2(0.0)))) {
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
if (any(greaterThan(uv, vec2(1.0)))) {
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
vec4 color = texture(iChannel0, uv);
|
||||
return color.rgb * max(0.5, sourceAspect) * max(0.9, fract(iWorldPosition.x));
|
||||
}
|
||||
|
||||
float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) {
|
||||
specular = aspectCorrectedTexture();
|
||||
return 1.0;
|
||||
}
|
||||
|
67
examples/tests/textureStress.js
Normal file
67
examples/tests/textureStress.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
Script.include("https://s3.amazonaws.com/DreamingContent/scripts/Austin.js");
|
||||
|
||||
var ENTITY_SPAWN_LIMIT = 500;
|
||||
var ENTITY_LIFETIME = 600;
|
||||
var RADIUS = 1.0; // Spawn within this radius (square)
|
||||
var TEST_ENTITY_NAME = "EntitySpawnTest";
|
||||
|
||||
var entities = [];
|
||||
var textureIndex = 0;
|
||||
var texture = Script.resolvePath('cube_texture.png');
|
||||
var shader = Script.resolvePath('textureStress.fs');
|
||||
var qml = Script.resolvePath('textureStress.qml');
|
||||
qmlWindow = new OverlayWindow({
|
||||
title: 'Test Qml',
|
||||
source: qml,
|
||||
height: 240,
|
||||
width: 320,
|
||||
toolWindow: false,
|
||||
visible: true
|
||||
});
|
||||
|
||||
function deleteItems(count) {
|
||||
if (!count) {
|
||||
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||
ids.forEach(function(id) {
|
||||
var properties = Entities.getEntityProperties(id, ["name"]);
|
||||
if (properties.name === TEST_ENTITY_NAME) {
|
||||
Entities.deleteEntity(id);
|
||||
}
|
||||
}, this);
|
||||
entities = [];
|
||||
return;
|
||||
} else {
|
||||
// FIXME... implement
|
||||
}
|
||||
}
|
||||
|
||||
function createItems(count) {
|
||||
for (var i = 0; i < count; ++i) {
|
||||
var newEntity = Entities.addEntity({
|
||||
type: "Box",
|
||||
name: TEST_ENTITY_NAME,
|
||||
position: AUSTIN.avatarRelativePosition(AUSTIN.randomPositionXZ({ x: 0, y: 0, z: -2 }, RADIUS)),
|
||||
color: { r: 255, g: 255, b: 255 },
|
||||
dimensions: AUSTIN.randomDimensions(),
|
||||
lifetime: ENTITY_LIFETIME,
|
||||
userData: JSON.stringify({
|
||||
ProceduralEntity: {
|
||||
version: 2,
|
||||
shaderUrl: shader,
|
||||
channels: [ texture + "?" + textureIndex++ ]
|
||||
}
|
||||
})
|
||||
});
|
||||
entities.push(newEntity);
|
||||
}
|
||||
}
|
||||
|
||||
qmlWindow.fromQml.connect(function(message){
|
||||
print(message);
|
||||
if (message[0] === "create") {
|
||||
var count = message[1] || 1;
|
||||
createItems(message[1] || 1);
|
||||
} else if (message[0] === "delete") {
|
||||
deleteItems(message[1]);
|
||||
}
|
||||
});
|
69
examples/tests/textureStress.qml
Normal file
69
examples/tests/textureStress.qml
Normal file
|
@ -0,0 +1,69 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
width: parent ? parent.width : 100
|
||||
height: parent ? parent.height : 100
|
||||
|
||||
signal sendToScript(var message);
|
||||
|
||||
Text {
|
||||
id: label
|
||||
text: "GPU Texture Usage: "
|
||||
}
|
||||
Text {
|
||||
id: usage
|
||||
anchors.left: label.right
|
||||
anchors.leftMargin: 8
|
||||
text: "N/A"
|
||||
Timer {
|
||||
repeat: true
|
||||
running: true
|
||||
interval: 500
|
||||
onTriggered: {
|
||||
usage.text = Render.getConfig("Stats")["textureGPUMemoryUsage"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
|
||||
|
||||
anchors { left: parent.left; right: parent.right; top: label.bottom; topMargin: 8; bottom: parent.bottom }
|
||||
spacing: 8
|
||||
|
||||
Button {
|
||||
text: "Add 1"
|
||||
onClicked: root.sendToScript(["create", 1]);
|
||||
}
|
||||
Button {
|
||||
text: "Add 10"
|
||||
onClicked: root.sendToScript(["create", 10]);
|
||||
}
|
||||
Button {
|
||||
text: "Add 100"
|
||||
onClicked: root.sendToScript(["create", 100]);
|
||||
}
|
||||
/*
|
||||
Button {
|
||||
text: "Delete 1"
|
||||
onClicked: root.sendToScript(["delete", 1]);
|
||||
}
|
||||
Button {
|
||||
text: "Delete 10"
|
||||
onClicked: root.sendToScript(["delete", 10]);
|
||||
}
|
||||
Button {
|
||||
text: "Delete 100"
|
||||
onClicked: root.sendToScript(["delete", 100]);
|
||||
}
|
||||
*/
|
||||
Button {
|
||||
text: "Delete All"
|
||||
onClicked: root.sendToScript(["delete", 0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3357,9 +3357,10 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
{
|
||||
PROFILE_RANGE_EX("HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
|
||||
PerformanceTimer perfTimer("havestChanges");
|
||||
PerformanceTimer perfTimer("harvestChanges");
|
||||
if (_physicsEngine->hasOutgoingChanges()) {
|
||||
getEntities()->getTree()->withWriteLock([&] {
|
||||
PerformanceTimer perfTimer("handleOutgoingChanges");
|
||||
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges();
|
||||
_entitySimulation.handleOutgoingChanges(outgoingChanges, Physics::getSessionUUID());
|
||||
avatarManager->handleOutgoingChanges(outgoingChanges);
|
||||
|
@ -3375,6 +3376,7 @@ void Application::update(float deltaTime) {
|
|||
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
|
||||
// deadlock.)
|
||||
_entitySimulation.handleCollisionEvents(collisionEvents);
|
||||
|
||||
// NOTE: the getEntities()->update() call below will wait for lock
|
||||
// and will simulate entity motion (the EntityTree has been given an EntitySimulation).
|
||||
getEntities()->update(); // update the models...
|
||||
|
|
|
@ -859,7 +859,11 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
|
|||
|
||||
void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
||||
_skeletonModel->setURL(_skeletonModelURL);
|
||||
if (QThread::currentThread() == thread()) {
|
||||
_skeletonModel->setURL(_skeletonModelURL);
|
||||
} else {
|
||||
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", Qt::QueuedConnection, Q_ARG(QUrl, _skeletonModelURL));
|
||||
}
|
||||
}
|
||||
|
||||
// create new model, can return an instance of a SoftAttachmentModel rather then Model
|
||||
|
|
|
@ -97,3 +97,11 @@ bool HMDScriptingInterface::isMounted() const{
|
|||
auto displayPlugin = qApp->getActiveDisplayPlugin();
|
||||
return (displayPlugin->isHmd() && displayPlugin->isDisplayVisible());
|
||||
}
|
||||
|
||||
QString HMDScriptingInterface::preferredAudioInput() const {
|
||||
return qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice();
|
||||
}
|
||||
|
||||
QString HMDScriptingInterface::preferredAudioOutput() const {
|
||||
return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ public:
|
|||
|
||||
Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
|
||||
Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
|
||||
Q_INVOKABLE QString preferredAudioInput() const;
|
||||
Q_INVOKABLE QString preferredAudioOutput() const;
|
||||
|
||||
public:
|
||||
HMDScriptingInterface();
|
||||
|
|
|
@ -36,7 +36,7 @@ AnimationPointer AnimationCache::getAnimation(const QUrl& url) {
|
|||
|
||||
QSharedPointer<Resource> AnimationCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||
bool delayLoad, const void* extra) {
|
||||
return QSharedPointer<Resource>(new Animation(url), &Resource::allReferencesCleared);
|
||||
return QSharedPointer<Resource>(new Animation(url), &Resource::deleter);
|
||||
}
|
||||
|
||||
Animation::Animation(const QUrl& url) : Resource(url) {}
|
||||
|
|
|
@ -175,6 +175,50 @@ int numDestinationSamplesRequired(const QAudioFormat& sourceFormat, const QAudio
|
|||
return (numSourceSamples * ratio) + 0.5f;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString friendlyNameForAudioDevice(IMMDevice* pEndpoint) {
|
||||
QString deviceName;
|
||||
IPropertyStore* pPropertyStore;
|
||||
pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore);
|
||||
pEndpoint->Release();
|
||||
pEndpoint = NULL;
|
||||
PROPVARIANT pv;
|
||||
PropVariantInit(&pv);
|
||||
HRESULT hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
|
||||
pPropertyStore->Release();
|
||||
pPropertyStore = NULL;
|
||||
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal);
|
||||
if (!IsWindows8OrGreater()) {
|
||||
// Windows 7 provides only the 31 first characters of the device name.
|
||||
const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31;
|
||||
deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN);
|
||||
}
|
||||
PropVariantClear(&pv);
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
QString AudioClient::friendlyNameForAudioDevice(wchar_t* guid) {
|
||||
QString deviceName;
|
||||
HRESULT hr = S_OK;
|
||||
CoInitialize(NULL);
|
||||
IMMDeviceEnumerator* pMMDeviceEnumerator = NULL;
|
||||
CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pMMDeviceEnumerator);
|
||||
IMMDevice* pEndpoint;
|
||||
hr = pMMDeviceEnumerator->GetDevice(guid, &pEndpoint);
|
||||
if (hr == E_NOTFOUND) {
|
||||
printf("Audio Error: device not found\n");
|
||||
deviceName = QString("NONE");
|
||||
} else {
|
||||
deviceName = ::friendlyNameForAudioDevice(pEndpoint);
|
||||
}
|
||||
pMMDeviceEnumerator->Release();
|
||||
pMMDeviceEnumerator = NULL;
|
||||
CoUninitialize();
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
|
||||
#ifdef __APPLE__
|
||||
if (QAudioDeviceInfo::availableDevices(mode).size() > 1) {
|
||||
|
@ -248,23 +292,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
|
|||
printf("Audio Error: device not found\n");
|
||||
deviceName = QString("NONE");
|
||||
} else {
|
||||
IPropertyStore* pPropertyStore;
|
||||
pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore);
|
||||
pEndpoint->Release();
|
||||
pEndpoint = NULL;
|
||||
PROPVARIANT pv;
|
||||
PropVariantInit(&pv);
|
||||
hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
|
||||
pPropertyStore->Release();
|
||||
pPropertyStore = NULL;
|
||||
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal);
|
||||
if (!IsWindows8OrGreater()) {
|
||||
// Windows 7 provides only the 31 first characters of the device name.
|
||||
const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31;
|
||||
deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN);
|
||||
}
|
||||
qCDebug(audioclient) << (mode == QAudio::AudioOutput ? "output" : "input") << " device:" << deviceName;
|
||||
PropVariantClear(&pv);
|
||||
deviceName = friendlyNameForAudioDevice(pEndpoint);
|
||||
}
|
||||
pMMDeviceEnumerator->Release();
|
||||
pMMDeviceEnumerator = NULL;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QtCore/qsystemdetection.h>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QObject>
|
||||
|
@ -126,6 +127,10 @@ public:
|
|||
|
||||
static const float CALLBACK_ACCELERATOR_RATIO;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
static QString friendlyNameForAudioDevice(wchar_t* guid);
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
void stop();
|
||||
|
|
|
@ -36,5 +36,5 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) {
|
|||
QSharedPointer<Resource> SoundCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||
bool delayLoad, const void* extra) {
|
||||
qCDebug(audio) << "Requesting sound at" << url.toString();
|
||||
return QSharedPointer<Resource>(new Sound(url), &Resource::allReferencesCleared);
|
||||
return QSharedPointer<Resource>(new Sound(url), &Resource::deleter);
|
||||
}
|
||||
|
|
|
@ -130,6 +130,7 @@ void EntityTreeRenderer::setTree(OctreePointer newTree) {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::update() {
|
||||
PerformanceTimer perfTimer("ETRupdate");
|
||||
if (_tree && !_shuttingDown) {
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
tree->update();
|
||||
|
@ -159,12 +160,14 @@ void EntityTreeRenderer::update() {
|
|||
|
||||
bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector<EntityItemID>* entitiesContainingAvatar) {
|
||||
bool didUpdate = false;
|
||||
float radius = 1.0f; // for now, assume 1 meter radius
|
||||
float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later
|
||||
QVector<EntityItemPointer> foundEntities;
|
||||
|
||||
// find the entities near us
|
||||
// don't let someone else change our tree while we search
|
||||
_tree->withReadLock([&] {
|
||||
|
||||
// FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster
|
||||
std::static_pointer_cast<EntityTree>(_tree)->findEntities(avatarPosition, radius, foundEntities);
|
||||
|
||||
// Whenever you're in an intersection between zones, we will always choose the smallest zone.
|
||||
|
@ -173,36 +176,37 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3&
|
|||
_bestZoneVolume = std::numeric_limits<float>::max();
|
||||
|
||||
// create a list of entities that actually contain the avatar's position
|
||||
foreach(EntityItemPointer entity, foundEntities) {
|
||||
if (entity->contains(avatarPosition)) {
|
||||
if (entitiesContainingAvatar) {
|
||||
*entitiesContainingAvatar << entity->getEntityItemID();
|
||||
}
|
||||
for (auto& entity : foundEntities) {
|
||||
auto isZone = entity->getType() == EntityTypes::Zone;
|
||||
auto hasScript = !entity->getScript().isEmpty();
|
||||
|
||||
// if this entity is a zone, use this time to determine the bestZone
|
||||
if (entity->getType() == EntityTypes::Zone) {
|
||||
if (!entity->getVisible()) {
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(entitiesrenderer) << "not visible";
|
||||
#endif
|
||||
} else {
|
||||
// only consider entities that are zones or have scripts, all other entities can
|
||||
// be ignored because they can have events fired on them.
|
||||
// FIXME - this could be optimized further by determining if the script is loaded
|
||||
// and if it has either an enterEntity or leaveEntity method
|
||||
if (isZone || hasScript) {
|
||||
// now check to see if the point contains our entity, this can be expensive if
|
||||
// the entity has a collision hull
|
||||
if (entity->contains(avatarPosition)) {
|
||||
if (entitiesContainingAvatar) {
|
||||
*entitiesContainingAvatar << entity->getEntityItemID();
|
||||
}
|
||||
|
||||
// if this entity is a zone and visible, determine if it is the bestZone
|
||||
if (isZone && entity->getVisible()) {
|
||||
float entityVolumeEstimate = entity->getVolumeEstimate();
|
||||
if (entityVolumeEstimate < _bestZoneVolume) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
|
||||
}
|
||||
else if (entityVolumeEstimate == _bestZoneVolume) {
|
||||
} else if (entityVolumeEstimate == _bestZoneVolume) {
|
||||
// in the case of the volume being equal, we will use the
|
||||
// EntityItemID to deterministically pick one entity over the other
|
||||
if (!_bestZone) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
|
||||
}
|
||||
else {
|
||||
// in the case of the volume being equal, we will use the
|
||||
// EntityItemID to deterministically pick one entity over the other
|
||||
if (entity->getEntityItemID() < _bestZone->getEntityItemID()) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
|
||||
}
|
||||
} else if (entity->getEntityItemID() < _bestZone->getEntityItemID()) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,13 +221,24 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3&
|
|||
});
|
||||
return didUpdate;
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||
PerformanceTimer perfTimer("checkEnterLeaveEntities");
|
||||
auto now = usecTimestampNow();
|
||||
bool didUpdate = false;
|
||||
|
||||
if (_tree && !_shuttingDown) {
|
||||
glm::vec3 avatarPosition = _viewState->getAvatarPosition();
|
||||
|
||||
if (avatarPosition != _lastAvatarPosition) {
|
||||
// we want to check our enter/leave state if we've moved a significant amount, or
|
||||
// if some amount of time has elapsed since we last checked. We check the time
|
||||
// elapsed because zones or entities might have been created "around us" while we've
|
||||
// been stationary
|
||||
auto movedEnough = glm::distance(avatarPosition, _lastAvatarPosition) > ZONE_CHECK_DISTANCE;
|
||||
auto enoughTimeElapsed = (now - _lastZoneCheck) > ZONE_CHECK_INTERVAL;
|
||||
|
||||
if (movedEnough || enoughTimeElapsed) {
|
||||
_lastZoneCheck = now;
|
||||
QVector<EntityItemID> entitiesContainingAvatar;
|
||||
didUpdate = findBestZoneAndMaybeContainingEntities(avatarPosition, &entitiesContainingAvatar);
|
||||
|
||||
|
@ -248,8 +263,6 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
}
|
||||
_currentEntitiesInside = entitiesContainingAvatar;
|
||||
_lastAvatarPosition = avatarPosition;
|
||||
} else {
|
||||
didUpdate = findBestZoneAndMaybeContainingEntities(avatarPosition, nullptr);
|
||||
}
|
||||
}
|
||||
return didUpdate;
|
||||
|
|
|
@ -178,6 +178,10 @@ private:
|
|||
std::shared_ptr<ZoneEntityItem> _bestZone;
|
||||
float _bestZoneVolume;
|
||||
|
||||
quint64 _lastZoneCheck { 0 };
|
||||
const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
|
||||
const float ZONE_CHECK_DISTANCE = 0.001f;
|
||||
|
||||
glm::vec3 _previousKeyLightColor;
|
||||
float _previousKeyLightIntensity;
|
||||
float _previousKeyLightAmbientIntensity;
|
||||
|
|
|
@ -116,11 +116,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
|
|||
|
||||
QJsonParseError error;
|
||||
QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error);
|
||||
// If textures are invalid, revert to original textures
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << textures;
|
||||
return _originalTextures;
|
||||
}
|
||||
|
||||
QVariantMap texturesMap = texturesJson.toVariant().toMap();
|
||||
// If textures are unset, revert to original textures
|
||||
if (texturesMap.isEmpty()) {
|
||||
return _originalTextures;
|
||||
}
|
||||
|
||||
return texturesJson.toVariant().toMap();
|
||||
}
|
||||
|
||||
|
@ -133,10 +140,8 @@ void RenderableModelEntityItem::remapTextures() {
|
|||
return; // nothing to do if the model has not yet loaded
|
||||
}
|
||||
|
||||
auto& geometry = _model->getGeometry()->getGeometry();
|
||||
|
||||
if (!_originalTexturesRead) {
|
||||
_originalTextures = geometry->getTextures();
|
||||
_originalTextures = _model->getTextures();
|
||||
_originalTexturesRead = true;
|
||||
|
||||
// Default to _originalTextures to avoid remapping immediately and lagging on load
|
||||
|
@ -152,7 +157,7 @@ void RenderableModelEntityItem::remapTextures() {
|
|||
auto newTextures = parseTexturesToMap(textures);
|
||||
|
||||
if (newTextures != _currentTextures) {
|
||||
geometry->setTextures(newTextures);
|
||||
_model->setTextures(newTextures);
|
||||
_currentTextures = newTextures;
|
||||
}
|
||||
}
|
||||
|
@ -366,41 +371,16 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
|||
assert(getType() == EntityTypes::Model);
|
||||
|
||||
if (hasModel()) {
|
||||
if (_model) {
|
||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||
|
||||
// check to see if when we added our models to the scene they were ready, if they were not ready, then
|
||||
// fix them up in the scene
|
||||
bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0;
|
||||
if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) {
|
||||
_showCollisionHull = shouldShowCollisionHull;
|
||||
render::PendingChanges pendingChanges;
|
||||
|
||||
_model->removeFromScene(scene, pendingChanges);
|
||||
|
||||
render::Item::Status::Getters statusGetters;
|
||||
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
|
||||
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull);
|
||||
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
}
|
||||
|
||||
// FIXME: this seems like it could be optimized if we tracked our last known visible state in
|
||||
// the renderable item. As it stands now the model checks it's visible/invisible state
|
||||
// so most of the time we don't do anything in this function.
|
||||
_model->setVisibleInScene(getVisible(), scene);
|
||||
}
|
||||
|
||||
|
||||
remapTextures();
|
||||
// Prepare the current frame
|
||||
{
|
||||
// float alpha = getLocalRenderAlpha();
|
||||
|
||||
if (!_model || _needsModelReload) {
|
||||
// TODO: this getModel() appears to be about 3% of model render time. We should optimize
|
||||
PerformanceTimer perfTimer("getModel");
|
||||
EntityTreeRenderer* renderer = static_cast<EntityTreeRenderer*>(args->_renderer);
|
||||
getModel(renderer);
|
||||
|
||||
// Remap textures immediately after loading to avoid flicker
|
||||
remapTextures();
|
||||
}
|
||||
|
||||
if (_model) {
|
||||
|
@ -431,15 +411,40 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
|||
}
|
||||
});
|
||||
updateModelBounds();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the URL has changed
|
||||
// Do this last as the getModel is queued for the next frame,
|
||||
// and we need to keep state directing the model to reinitialize
|
||||
auto& currentURL = getParsedModelURL();
|
||||
if (currentURL != _model->getURL()) {
|
||||
// Defer setting the url to the render thread
|
||||
getModel(_myRenderer);
|
||||
}
|
||||
// Enqueue updates for the next frame
|
||||
if (_model) {
|
||||
|
||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||
|
||||
// FIXME: this seems like it could be optimized if we tracked our last known visible state in
|
||||
// the renderable item. As it stands now the model checks it's visible/invisible state
|
||||
// so most of the time we don't do anything in this function.
|
||||
_model->setVisibleInScene(getVisible(), scene);
|
||||
|
||||
// Remap textures for the next frame to avoid flicker
|
||||
remapTextures();
|
||||
|
||||
// check to see if when we added our models to the scene they were ready, if they were not ready, then
|
||||
// fix them up in the scene
|
||||
bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0;
|
||||
if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) {
|
||||
_showCollisionHull = shouldShowCollisionHull;
|
||||
render::PendingChanges pendingChanges;
|
||||
|
||||
render::Item::Status::Getters statusGetters;
|
||||
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
|
||||
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull);
|
||||
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
}
|
||||
|
||||
auto& currentURL = getParsedModelURL();
|
||||
if (currentURL != _model->getURL()) {
|
||||
// Defer setting the url to the render thread
|
||||
getModel(_myRenderer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -457,6 +457,14 @@ uint32 Texture::getStoredMipSize(uint16 level) const {
|
|||
return 0;
|
||||
}
|
||||
|
||||
gpu::Resource::Size Texture::getStoredSize() const {
|
||||
auto size = 0;
|
||||
for (int level = 0; level < evalNumMips(); ++level) {
|
||||
size += getStoredMipSize(level);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
uint16 Texture::evalNumSamplesUsed(uint16 numSamplesTried) {
|
||||
uint16 sample = numSamplesTried;
|
||||
if (numSamplesTried <= 1)
|
||||
|
|
|
@ -288,9 +288,12 @@ public:
|
|||
Stamp getStamp() const { return _stamp; }
|
||||
Stamp getDataStamp() const { return _storage->getStamp(); }
|
||||
|
||||
// The size in bytes of data stored in the texture
|
||||
// The theoretical size in bytes of data stored in the texture
|
||||
Size getSize() const { return _size; }
|
||||
|
||||
// The actual size in bytes of data stored in the texture
|
||||
Size getStoredSize() const;
|
||||
|
||||
// Resize, unless auto mips mode would destroy all the sub mips
|
||||
Size resize1D(uint16 width, uint16 numSamples);
|
||||
Size resize2D(uint16 width, uint16 height, uint16 numSamples);
|
||||
|
|
|
@ -66,16 +66,15 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|||
GeometryExtra extra{ mapping, textureBaseUrl };
|
||||
|
||||
// Get the raw GeometryResource, not the wrapped NetworkGeometry
|
||||
_geometryResource = modelCache->getResource(url, QUrl(), true, &extra).staticCast<GeometryResource>();
|
||||
_geometryResource = modelCache->getResource(url, QUrl(), false, &extra).staticCast<GeometryResource>();
|
||||
// Avoid caching nested resources - their references will be held by the parent
|
||||
_geometryResource->_isCacheable = false;
|
||||
|
||||
if (_geometryResource->isLoaded()) {
|
||||
onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty());
|
||||
} else {
|
||||
connect(_geometryResource.data(), &Resource::finished, this, &GeometryMappingResource::onGeometryMappingLoaded);
|
||||
}
|
||||
|
||||
// Avoid caching nested resources - their references will be held by the parent
|
||||
_geometryResource->_isCacheable = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,6 +85,10 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
|
|||
_meshes = _geometryResource->_meshes;
|
||||
_materials = _geometryResource->_materials;
|
||||
}
|
||||
|
||||
// Avoid holding onto extra references
|
||||
_geometryResource.reset();
|
||||
|
||||
finishedLoading(success);
|
||||
}
|
||||
|
||||
|
@ -157,7 +160,7 @@ class GeometryDefinitionResource : public GeometryResource {
|
|||
Q_OBJECT
|
||||
public:
|
||||
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
|
||||
GeometryResource(url), _mapping(mapping), _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) {}
|
||||
GeometryResource(url, textureBaseUrl.isValid() ? textureBaseUrl : url), _mapping(mapping) {}
|
||||
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
|
||||
|
@ -166,7 +169,6 @@ protected:
|
|||
|
||||
private:
|
||||
QVariantHash _mapping;
|
||||
QUrl _textureBaseUrl;
|
||||
};
|
||||
|
||||
void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
|
||||
|
@ -220,13 +222,20 @@ QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QShar
|
|||
resource = new GeometryDefinitionResource(url, geometryExtra->mapping, geometryExtra->textureBaseUrl);
|
||||
}
|
||||
|
||||
return QSharedPointer<Resource>(resource, &Resource::allReferencesCleared);
|
||||
return QSharedPointer<Resource>(resource, &Resource::deleter);
|
||||
}
|
||||
|
||||
std::shared_ptr<NetworkGeometry> ModelCache::getGeometry(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) {
|
||||
GeometryExtra geometryExtra = { mapping, textureBaseUrl };
|
||||
GeometryResource::Pointer resource = getResource(url, QUrl(), true, &geometryExtra).staticCast<GeometryResource>();
|
||||
return std::make_shared<NetworkGeometry>(resource);
|
||||
if (resource) {
|
||||
if (resource->isLoaded() && !resource->hasTextures()) {
|
||||
resource->setTextures();
|
||||
}
|
||||
return std::make_shared<NetworkGeometry>(resource);
|
||||
} else {
|
||||
return NetworkGeometry::Pointer();
|
||||
}
|
||||
}
|
||||
|
||||
const QVariantMap Geometry::getTextures() const {
|
||||
|
@ -270,6 +279,9 @@ void Geometry::setTextures(const QVariantMap& textureMap) {
|
|||
|
||||
material->setTextures(textureMap);
|
||||
_areTexturesLoaded = false;
|
||||
|
||||
// If we only use cached textures, they should all be loaded
|
||||
areTexturesLoaded();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -279,8 +291,6 @@ void Geometry::setTextures(const QVariantMap& textureMap) {
|
|||
|
||||
bool Geometry::areTexturesLoaded() const {
|
||||
if (!_areTexturesLoaded) {
|
||||
_hasTransparentTextures = false;
|
||||
|
||||
for (auto& material : _materials) {
|
||||
// Check if material textures are loaded
|
||||
if (std::any_of(material->_textures.cbegin(), material->_textures.cend(),
|
||||
|
@ -293,8 +303,6 @@ bool Geometry::areTexturesLoaded() const {
|
|||
const auto albedoTexture = material->_textures[NetworkMaterial::MapChannel::ALBEDO_MAP];
|
||||
if (albedoTexture.texture && albedoTexture.texture->getGPUTexture()) {
|
||||
material->resetOpacityMap();
|
||||
|
||||
_hasTransparentTextures |= material->getKey().isTranslucent();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,6 +321,21 @@ const std::shared_ptr<const NetworkMaterial> Geometry::getShapeMaterial(int shap
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void GeometryResource::deleter() {
|
||||
resetTextures();
|
||||
Resource::deleter();
|
||||
}
|
||||
|
||||
void GeometryResource::setTextures() {
|
||||
for (const FBXMaterial& material : _geometry->materials) {
|
||||
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
|
||||
}
|
||||
}
|
||||
|
||||
void GeometryResource::resetTextures() {
|
||||
_materials.clear();
|
||||
}
|
||||
|
||||
NetworkGeometry::NetworkGeometry(const GeometryResource::Pointer& networkGeometry) : _resource(networkGeometry) {
|
||||
connect(_resource.data(), &Resource::finished, this, &NetworkGeometry::resourceFinished);
|
||||
connect(_resource.data(), &Resource::onRefresh, this, &NetworkGeometry::resourceRefreshed);
|
||||
|
|
|
@ -74,9 +74,6 @@ public:
|
|||
void setTextures(const QVariantMap& textureMap);
|
||||
|
||||
virtual bool areTexturesLoaded() const;
|
||||
// Returns true if any albedo texture has a non-masking alpha channel.
|
||||
// This can only be known after areTexturesLoaded().
|
||||
bool hasTransparentTextures() const { return _hasTransparentTextures; }
|
||||
|
||||
protected:
|
||||
friend class GeometryMappingResource;
|
||||
|
@ -91,7 +88,6 @@ protected:
|
|||
|
||||
private:
|
||||
mutable bool _areTexturesLoaded { false };
|
||||
mutable bool _hasTransparentTextures { false };
|
||||
};
|
||||
|
||||
/// A geometry loaded from the network.
|
||||
|
@ -99,15 +95,24 @@ class GeometryResource : public Resource, public Geometry {
|
|||
public:
|
||||
using Pointer = QSharedPointer<GeometryResource>;
|
||||
|
||||
GeometryResource(const QUrl& url) : Resource(url) {}
|
||||
GeometryResource(const QUrl& url, const QUrl& textureBaseUrl = QUrl()) : Resource(url) {}
|
||||
|
||||
virtual bool areTexturesLoaded() const { return isLoaded() && Geometry::areTexturesLoaded(); }
|
||||
|
||||
virtual void deleter() override;
|
||||
|
||||
protected:
|
||||
friend class ModelCache;
|
||||
friend class GeometryMappingResource;
|
||||
|
||||
virtual bool isCacheable() const override { return _loaded && _isCacheable; }
|
||||
// Geometries may not hold onto textures while cached - that is for the texture cache
|
||||
bool hasTextures() const { return !_materials.empty(); }
|
||||
void setTextures();
|
||||
void resetTextures();
|
||||
|
||||
QUrl _textureBaseUrl;
|
||||
|
||||
virtual bool isCacheable() const override { return _loaded && _isCacheable; }
|
||||
bool _isCacheable { true };
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,6 @@ NetworkShaderPointer ShaderCache::getShader(const QUrl& url) {
|
|||
}
|
||||
|
||||
QSharedPointer<Resource> ShaderCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||
return QSharedPointer<Resource>(new NetworkShader(url, delayLoad), &Resource::allReferencesCleared);
|
||||
return QSharedPointer<Resource>(new NetworkShader(url, delayLoad), &Resource::deleter);
|
||||
}
|
||||
|
||||
|
|
|
@ -166,12 +166,11 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path) {
|
|||
return texture;
|
||||
}
|
||||
|
||||
|
||||
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url,
|
||||
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||
const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
|
||||
return QSharedPointer<Resource>(new NetworkTexture(url, textureExtra->type, textureExtra->content),
|
||||
&Resource::allReferencesCleared);
|
||||
&Resource::deleter);
|
||||
}
|
||||
|
||||
NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) :
|
||||
|
@ -339,10 +338,13 @@ void NetworkTexture::setImage(void* voidTexture, int originalWidth,
|
|||
if (gpuTexture) {
|
||||
_width = gpuTexture->getWidth();
|
||||
_height = gpuTexture->getHeight();
|
||||
setBytes(gpuTexture->getStoredSize());
|
||||
} else {
|
||||
// FIXME: If !gpuTexture, we failed to load!
|
||||
_width = _height = 0;
|
||||
qWarning() << "Texture did not load";
|
||||
}
|
||||
|
||||
|
||||
finishedLoading(true);
|
||||
|
||||
emit networkTextureCreated(qWeakPointerCast<NetworkTexture, Resource> (_self));
|
||||
|
|
|
@ -117,22 +117,22 @@ void ResourceCache::setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize) {
|
|||
}
|
||||
|
||||
void ResourceCache::addUnusedResource(const QSharedPointer<Resource>& resource) {
|
||||
if (resource->getBytesTotal() > _unusedResourcesMaxSize) {
|
||||
// If it doesn't fit anyway, let's leave whatever is already in the cache.
|
||||
// If it doesn't fit or its size is unknown, leave the cache alone.
|
||||
if (resource->getBytes() == 0 || resource->getBytes() > _unusedResourcesMaxSize) {
|
||||
resource->setCache(nullptr);
|
||||
return;
|
||||
}
|
||||
reserveUnusedResource(resource->getBytesTotal());
|
||||
reserveUnusedResource(resource->getBytes());
|
||||
|
||||
resource->setLRUKey(++_lastLRUKey);
|
||||
_unusedResources.insert(resource->getLRUKey(), resource);
|
||||
_unusedResourcesSize += resource->getBytesTotal();
|
||||
_unusedResourcesSize += resource->getBytes();
|
||||
}
|
||||
|
||||
void ResourceCache::removeUnusedResource(const QSharedPointer<Resource>& resource) {
|
||||
if (_unusedResources.contains(resource->getLRUKey())) {
|
||||
_unusedResources.remove(resource->getLRUKey());
|
||||
_unusedResourcesSize -= resource->getBytesTotal();
|
||||
_unusedResourcesSize -= resource->getBytes();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) {
|
|||
// unload the oldest resource
|
||||
QMap<int, QSharedPointer<Resource> >::iterator it = _unusedResources.begin();
|
||||
|
||||
_unusedResourcesSize -= it.value()->getBytesTotal();
|
||||
_unusedResourcesSize -= it.value()->getBytes();
|
||||
it.value()->setCache(nullptr);
|
||||
_unusedResources.erase(it);
|
||||
}
|
||||
|
@ -399,7 +399,7 @@ void Resource::makeRequest() {
|
|||
connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress);
|
||||
connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished);
|
||||
|
||||
_bytesReceived = _bytesTotal = 0;
|
||||
_bytesReceived = _bytesTotal = _bytes = 0;
|
||||
|
||||
_request->send();
|
||||
}
|
||||
|
@ -412,6 +412,8 @@ void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTota
|
|||
void Resource::handleReplyFinished() {
|
||||
Q_ASSERT_X(_request, "Resource::handleReplyFinished", "Request should not be null while in handleReplyFinished");
|
||||
|
||||
_bytes = _bytesTotal;
|
||||
|
||||
if (!_request || _request != sender()) {
|
||||
// This can happen in the edge case that a request is timed out, but a `finished` signal is emitted before it is deleted.
|
||||
qWarning(networking) << "Received signal Resource::handleReplyFinished from ResourceRequest that is not the current"
|
||||
|
|
|
@ -181,6 +181,9 @@ public:
|
|||
/// For loading resources, returns the number of total bytes (<= zero if unknown).
|
||||
qint64 getBytesTotal() const { return _bytesTotal; }
|
||||
|
||||
/// For loaded resources, returns the number of actual bytes (defaults to total bytes if not explicitly set).
|
||||
qint64 getBytes() const { return _bytes; }
|
||||
|
||||
/// For loading resources, returns the load progress.
|
||||
float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; }
|
||||
|
||||
|
@ -191,7 +194,7 @@ public:
|
|||
|
||||
void setCache(ResourceCache* cache) { _cache = cache; }
|
||||
|
||||
Q_INVOKABLE void allReferencesCleared();
|
||||
virtual void deleter() { allReferencesCleared(); }
|
||||
|
||||
const QUrl& getURL() const { return _url; }
|
||||
|
||||
|
@ -222,10 +225,15 @@ protected:
|
|||
/// This should be overridden by subclasses that need to process the data once it is downloaded.
|
||||
virtual void downloadFinished(const QByteArray& data) { finishedLoading(true); }
|
||||
|
||||
/// Called when the download is finished and processed, sets the number of actual bytes.
|
||||
void setBytes(qint64 bytes) { _bytes = bytes; }
|
||||
|
||||
/// Called when the download is finished and processed.
|
||||
/// This should be called by subclasses that override downloadFinished to mark the end of processing.
|
||||
Q_INVOKABLE void finishedLoading(bool success);
|
||||
|
||||
Q_INVOKABLE void allReferencesCleared();
|
||||
|
||||
QUrl _url;
|
||||
QUrl _activeUrl;
|
||||
bool _startedLoading = false;
|
||||
|
@ -253,6 +261,7 @@ private:
|
|||
QTimer* _replyTimer = nullptr;
|
||||
qint64 _bytesReceived = 0;
|
||||
qint64 _bytesTotal = 0;
|
||||
qint64 _bytes = 0;
|
||||
int _attempts = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -74,6 +74,9 @@ public:
|
|||
/// whether the HMD is being worn
|
||||
virtual bool isDisplayVisible() const { return false; }
|
||||
|
||||
virtual QString getPreferredAudioInDevice() const { return QString(); }
|
||||
virtual QString getPreferredAudioOutDevice() const { return QString(); }
|
||||
|
||||
// Rendering support
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,6 +36,6 @@ NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) {
|
|||
}
|
||||
|
||||
QSharedPointer<Resource> ClipCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||
return QSharedPointer<Resource>(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared);
|
||||
return QSharedPointer<Resource>(new NetworkClipLoader(url, delayLoad), &Resource::deleter);
|
||||
}
|
||||
|
||||
|
|
|
@ -76,14 +76,9 @@ AbstractViewStateInterface* Model::_viewState = NULL;
|
|||
|
||||
bool Model::needsFixupInScene() const {
|
||||
if (readyToAddToScene()) {
|
||||
// Once textures are loaded, fixup if they are now transparent
|
||||
if (_needsUpdateTransparentTextures && _geometry->getGeometry()->areTexturesLoaded()) {
|
||||
_needsUpdateTransparentTextures = false;
|
||||
bool hasTransparentTextures = _geometry->getGeometry()->hasTransparentTextures();
|
||||
if (_hasTransparentTextures != hasTransparentTextures) {
|
||||
_hasTransparentTextures = hasTransparentTextures;
|
||||
return true;
|
||||
}
|
||||
if (_needsUpdateTextures && _geometry->getGeometry()->areTexturesLoaded()) {
|
||||
_needsUpdateTextures = false;
|
||||
return true;
|
||||
}
|
||||
if (!_readyWhenAdded) {
|
||||
return true;
|
||||
|
@ -167,13 +162,15 @@ void Model::enqueueLocationChange() {
|
|||
render::PendingChanges pendingChanges;
|
||||
foreach (auto itemID, self->_modelMeshRenderItems.keys()) {
|
||||
pendingChanges.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, modelMeshOffset](ModelMeshPartPayload& data) {
|
||||
// Ensure the model geometry was not reset between frames
|
||||
if (data._model->isLoaded()) {
|
||||
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
|
||||
data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation());
|
||||
|
||||
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
|
||||
data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation());
|
||||
|
||||
// update the model transform and bounding box for this render item.
|
||||
const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);
|
||||
data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices);
|
||||
// update the model transform and bounding box for this render item.
|
||||
const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);
|
||||
data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -544,43 +541,6 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scen
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges, bool showCollisionHull) {
|
||||
|
||||
if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) {
|
||||
_showCollisionHull = showCollisionHull;
|
||||
segregateMeshGroups();
|
||||
}
|
||||
|
||||
bool somethingAdded = false;
|
||||
|
||||
foreach (auto renderItem, _modelMeshRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
|
||||
data.notifyLocationChanged();
|
||||
});
|
||||
_modelMeshRenderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
}
|
||||
|
||||
foreach (auto renderItem, _collisionRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
|
||||
data.notifyLocationChanged();
|
||||
});
|
||||
_collisionRenderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
}
|
||||
|
||||
_readyWhenAdded = readyToAddToScene();
|
||||
|
||||
return somethingAdded;
|
||||
}
|
||||
|
||||
bool Model::addToScene(std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges,
|
||||
render::Item::Status::Getters& statusGetters,
|
||||
|
@ -592,28 +552,48 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
|
|||
|
||||
bool somethingAdded = false;
|
||||
|
||||
foreach (auto renderItem, _modelMeshRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
|
||||
data.notifyLocationChanged();
|
||||
});
|
||||
_modelMeshRenderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
if (_modelMeshRenderItems.size()) {
|
||||
for (auto item : _modelMeshRenderItems.keys()) {
|
||||
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
|
||||
data.notifyLocationChanged();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (auto renderItem : _modelMeshRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||
if (statusGetters.size()) {
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
}
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
|
||||
data.notifyLocationChanged();
|
||||
});
|
||||
_modelMeshRenderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (auto renderItem, _collisionRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
|
||||
data.notifyLocationChanged();
|
||||
});
|
||||
_collisionRenderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
if (_collisionRenderItems.size()) {
|
||||
for (auto item : _collisionRenderItems.keys()) {
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
|
||||
data.notifyLocationChanged();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (auto renderItem : _collisionRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||
if (statusGetters.size()) {
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
}
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
|
||||
data.notifyLocationChanged();
|
||||
});
|
||||
_collisionRenderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
_readyWhenAdded = readyToAddToScene();
|
||||
|
@ -789,6 +769,13 @@ int Model::getLastFreeJointIndex(int jointIndex) const {
|
|||
return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1;
|
||||
}
|
||||
|
||||
void Model::setTextures(const QVariantMap& textures) {
|
||||
if (isLoaded()) {
|
||||
_needsUpdateTextures = true;
|
||||
_geometry->getGeometry()->setTextures(textures);
|
||||
}
|
||||
}
|
||||
|
||||
void Model::setURL(const QUrl& url) {
|
||||
// don't recreate the geometry if it's the same URL
|
||||
if (_url == url && _geometry && _geometry->getURL() == url) {
|
||||
|
@ -805,8 +792,7 @@ void Model::setURL(const QUrl& url) {
|
|||
}
|
||||
|
||||
_needsReload = true;
|
||||
_needsUpdateTransparentTextures = true;
|
||||
_hasTransparentTextures = false;
|
||||
_needsUpdateTextures = true;
|
||||
_meshGroupsKnown = false;
|
||||
invalidCalculatedMeshBoxes();
|
||||
deleteGeometry();
|
||||
|
@ -1061,7 +1047,7 @@ void Model::simulateInternal(float deltaTime) {
|
|||
void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) {
|
||||
PerformanceTimer perfTimer("Model::updateClusterMatrices");
|
||||
|
||||
if (!_needsUpdateClusterMatrices) {
|
||||
if (!_needsUpdateClusterMatrices || !isLoaded()) {
|
||||
return;
|
||||
}
|
||||
_needsUpdateClusterMatrices = false;
|
||||
|
|
|
@ -87,7 +87,10 @@ public:
|
|||
bool initWhenReady(render::ScenePointer scene);
|
||||
bool addToScene(std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges,
|
||||
bool showCollisionHull = false);
|
||||
bool showCollisionHull = false) {
|
||||
auto getters = render::Item::Status::Getters(0);
|
||||
return addToScene(scene, pendingChanges, getters, showCollisionHull);
|
||||
}
|
||||
bool addToScene(std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges,
|
||||
render::Item::Status::Getters& statusGetters,
|
||||
|
@ -129,6 +132,9 @@ public:
|
|||
/// Returns a reference to the shared collision geometry.
|
||||
const NetworkGeometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; }
|
||||
|
||||
const QVariantMap getTextures() const { assert(isLoaded()); return _geometry->getGeometry()->getTextures(); }
|
||||
void setTextures(const QVariantMap& textures);
|
||||
|
||||
/// Provided as a convenience, will crash if !isLoaded()
|
||||
// And so that getGeometry() isn't chained everywhere
|
||||
const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return getGeometry()->getGeometry()->getGeometry(); }
|
||||
|
@ -385,9 +391,8 @@ protected:
|
|||
bool _readyWhenAdded { false };
|
||||
bool _needsReload { true };
|
||||
bool _needsUpdateClusterMatrices { true };
|
||||
mutable bool _needsUpdateTransparentTextures { true };
|
||||
mutable bool _hasTransparentTextures { false };
|
||||
bool _showCollisionHull { false };
|
||||
mutable bool _needsUpdateTextures { true };
|
||||
|
||||
friend class ModelMeshPartPayload;
|
||||
RigPointer _rig;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <NetworkAccessManager.h>
|
||||
#include <SharedUtil.h>
|
||||
#include "ResourceManager.h"
|
||||
#include "ScriptEngines.h"
|
||||
|
||||
BatchLoader::BatchLoader(const QList<QUrl>& urls)
|
||||
: QObject(),
|
||||
|
@ -34,8 +35,9 @@ void BatchLoader::start() {
|
|||
}
|
||||
|
||||
_started = true;
|
||||
|
||||
for (const auto& url : _urls) {
|
||||
|
||||
for (const auto& rawURL : _urls) {
|
||||
QUrl url = expandScriptUrl(normalizeScriptURL(rawURL));
|
||||
auto request = ResourceManager::createResourceRequest(this, url);
|
||||
if (!request) {
|
||||
_data.insert(url, QString());
|
||||
|
|
|
@ -220,11 +220,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
|||
return;
|
||||
}
|
||||
|
||||
_fileNameString = scriptURL.toString();
|
||||
QUrl url = expandScriptUrl(normalizeScriptURL(scriptURL));
|
||||
_fileNameString = url.toString();
|
||||
_isReloading = reload;
|
||||
|
||||
QUrl url(scriptURL);
|
||||
|
||||
bool isPending;
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
scriptCache->getScript(url, this, isPending, reload);
|
||||
|
@ -848,7 +847,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const {
|
|||
QUrl url(include);
|
||||
// first lets check to see if it's already a full URL
|
||||
if (!url.scheme().isEmpty()) {
|
||||
return url;
|
||||
return expandScriptUrl(normalizeScriptURL(url));
|
||||
}
|
||||
|
||||
// we apparently weren't a fully qualified url, so, let's assume we're relative
|
||||
|
@ -865,7 +864,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const {
|
|||
}
|
||||
|
||||
// at this point we should have a legitimate fully qualified URL for our parent
|
||||
url = parentURL.resolved(url);
|
||||
url = expandScriptUrl(normalizeScriptURL(parentURL.resolved(url)));
|
||||
return url;
|
||||
}
|
||||
|
||||
|
|
|
@ -429,7 +429,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
|
|||
connect(scriptEngine, &ScriptEngine::errorLoadingScript, this, &ScriptEngines::onScriptEngineError);
|
||||
|
||||
// get the script engine object to load the script at the designated script URL
|
||||
scriptEngine->loadURL(QUrl(expandScriptUrl(scriptUrl.toString())), reload);
|
||||
scriptEngine->loadURL(scriptUrl, reload);
|
||||
}
|
||||
|
||||
return scriptEngine;
|
||||
|
|
|
@ -309,6 +309,9 @@ void ScriptsModel::rebuildTree() {
|
|||
QString hash;
|
||||
QStringList pathList = script->getLocalPath().split(tr("/"));
|
||||
pathList.removeLast();
|
||||
if (pathList.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QStringList::const_iterator pathIterator;
|
||||
for (pathIterator = pathList.constBegin(); pathIterator != pathList.constEnd(); ++pathIterator) {
|
||||
hash.append(*pathIterator + "/");
|
||||
|
|
|
@ -122,13 +122,18 @@ void QmlWindowClass::initQml(QVariantMap properties) {
|
|||
object->setProperty(SOURCE_PROPERTY, _source);
|
||||
|
||||
// Forward messages received from QML on to the script
|
||||
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection);
|
||||
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
Q_ASSERT(_qmlWindow);
|
||||
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));
|
||||
}
|
||||
|
||||
void QmlWindowClass::qmlToScript(const QVariant& message) {
|
||||
QJSValue js = qvariant_cast<QJSValue>(message);
|
||||
emit fromQml(js.toVariant());
|
||||
}
|
||||
|
||||
void QmlWindowClass::sendToQml(const QVariant& message) {
|
||||
// Forward messages received from the script on to QML
|
||||
QMetaObject::invokeMethod(asQuickItem(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
|
|
|
@ -63,6 +63,7 @@ signals:
|
|||
|
||||
protected slots:
|
||||
void hasClosed();
|
||||
void qmlToScript(const QVariant& message);
|
||||
|
||||
protected:
|
||||
static QVariantMap parseArguments(QScriptContext* context);
|
||||
|
|
|
@ -12,8 +12,8 @@ if (WIN32)
|
|||
add_definitions(-DGLEW_STATIC)
|
||||
|
||||
set(TARGET_NAME oculus)
|
||||
setup_hifi_plugin()
|
||||
link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins)
|
||||
setup_hifi_plugin(Multimedia)
|
||||
link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins audio-client networking)
|
||||
|
||||
include_hifi_library_headers(octree)
|
||||
|
||||
|
@ -21,5 +21,6 @@ if (WIN32)
|
|||
find_package(LibOVR REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES})
|
||||
target_link_libraries(${TARGET_NAME} Winmm.lib)
|
||||
|
||||
endif()
|
|
@ -21,6 +21,7 @@ public:
|
|||
// Stereo specific methods
|
||||
virtual void resetSensors() override final;
|
||||
virtual void beginFrameRender(uint32_t frameIndex) override;
|
||||
float getTargetFrameRate() override { return _hmdDesc.DisplayRefreshRate; }
|
||||
|
||||
|
||||
protected:
|
||||
|
|
|
@ -6,7 +6,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "OculusDisplayPlugin.h"
|
||||
|
||||
// Odd ordering of header is required to avoid 'macro redinition warnings'
|
||||
#include <AudioClient.h>
|
||||
|
||||
#include <OVR_CAPI_Audio.h>
|
||||
|
||||
#include <shared/NsightHelpers.h>
|
||||
|
||||
#include "OculusHelpers.h"
|
||||
|
||||
const QString OculusDisplayPlugin::NAME("Oculus Rift");
|
||||
|
@ -86,3 +93,26 @@ void OculusDisplayPlugin::hmdPresent() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OculusDisplayPlugin::isHmdMounted() const {
|
||||
ovrSessionStatus status;
|
||||
return (OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)) &&
|
||||
(ovrFalse != status.HmdMounted));
|
||||
}
|
||||
|
||||
QString OculusDisplayPlugin::getPreferredAudioInDevice() const {
|
||||
WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE];
|
||||
if (!OVR_SUCCESS(ovr_GetAudioDeviceInGuidStr(buffer))) {
|
||||
return QString();
|
||||
}
|
||||
return AudioClient::friendlyNameForAudioDevice(buffer);
|
||||
}
|
||||
|
||||
QString OculusDisplayPlugin::getPreferredAudioOutDevice() const {
|
||||
WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE];
|
||||
if (!OVR_SUCCESS(ovr_GetAudioDeviceOutGuidStr(buffer))) {
|
||||
return QString();
|
||||
}
|
||||
return AudioClient::friendlyNameForAudioDevice(buffer);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,20 +12,18 @@
|
|||
struct SwapFramebufferWrapper;
|
||||
using SwapFboPtr = QSharedPointer<SwapFramebufferWrapper>;
|
||||
|
||||
const float TARGET_RATE_Oculus = 75.0f;
|
||||
|
||||
class OculusDisplayPlugin : public OculusBaseDisplayPlugin {
|
||||
using Parent = OculusBaseDisplayPlugin;
|
||||
public:
|
||||
const QString& getName() const override { return NAME; }
|
||||
|
||||
float getTargetFrameRate() override { return TARGET_RATE_Oculus; }
|
||||
QString getPreferredAudioInDevice() const override;
|
||||
QString getPreferredAudioOutDevice() const override;
|
||||
|
||||
protected:
|
||||
bool internalActivate() override;
|
||||
void hmdPresent() override;
|
||||
// FIXME update with Oculus API call once it's available in the SDK
|
||||
bool isHmdMounted() const override { return true; }
|
||||
bool isHmdMounted() const override;
|
||||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
void cycleDebugOutput() override;
|
||||
|
|
Loading…
Reference in a new issue