mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 16:30:10 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into overlaysupport
This commit is contained in:
commit
dd0909659a
29 changed files with 696 additions and 322 deletions
47
cmake/modules/MacOSXBundleInfo.plist.in
Normal file
47
cmake/modules/MacOSXBundleInfo.plist.in
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLongVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>LSRequiresCarbon</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME} URL</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>hifi</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -90,7 +90,13 @@ qt5_wrap_ui(QT_UI_HEADERS ${QT_UI_FILES})
|
|||
set(INTERFACE_SRCS ${INTERFACE_SRCS} ${QT_UI_HEADERS})
|
||||
|
||||
if (APPLE)
|
||||
|
||||
# configure CMake to use a custom Info.plist
|
||||
SET_TARGET_PROPERTIES( ${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in )
|
||||
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME Interface)
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER io.highfidelity.Interface)
|
||||
|
||||
# set how the icon shows up in the Info.plist file
|
||||
SET(MACOSX_BUNDLE_ICON_FILE interface.icns)
|
||||
|
||||
|
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 330 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
@ -1,3 +1,5 @@
|
|||
filename=defaultAvatar/body.fbx
|
||||
texdir=defaultAvatar
|
||||
scale=130
|
||||
joint = jointRoot = jointRoot
|
||||
joint = jointLean = jointSpine
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# faceshift target mapping file
|
||||
name= defaultAvatar_head
|
||||
filename=../../../Avatars/Jelly/jellyrob_blue.fbx
|
||||
texdir=../../../Avatars/Jelly
|
||||
filename=defaultAvatar/head.fbx
|
||||
texdir=defaultAvatar
|
||||
scale=80
|
||||
rx=0
|
||||
ry=0
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
uniform sampler2D permutationNormalTexture;
|
||||
|
||||
// the noise frequency
|
||||
const float frequency = 1024.0;
|
||||
const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128
|
||||
|
||||
// the noise amplitude
|
||||
const float amplitude = 0.1;
|
||||
|
|
|
@ -101,6 +101,8 @@ const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::D
|
|||
|
||||
const int STATS_PELS_PER_LINE = 20;
|
||||
|
||||
const QString CUSTOM_URL_SCHEME = "hifi:";
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||
if (message.size() > 0) {
|
||||
QString messageWithNewLine = message + "\n";
|
||||
|
@ -682,6 +684,38 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
|
|||
}
|
||||
}
|
||||
|
||||
bool Application::event(QEvent* event) {
|
||||
|
||||
// handle custom URL
|
||||
if (event->type() == QEvent::FileOpen) {
|
||||
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
|
||||
if (!fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME)) {
|
||||
QString destination = fileEvent->url().toLocalFile().remove(CUSTOM_URL_SCHEME);
|
||||
QStringList urlParts = destination.split('/', QString::SkipEmptyParts);
|
||||
|
||||
if (urlParts.count() > 1) {
|
||||
// if url has 2 or more parts, the first one is domain name
|
||||
Menu::getInstance()->goToDomain(urlParts[0]);
|
||||
|
||||
// location coordinates
|
||||
Menu::getInstance()->goToDestination(urlParts[1]);
|
||||
if (urlParts.count() > 2) {
|
||||
|
||||
// location orientation
|
||||
Menu::getInstance()->goToOrientation(urlParts[2]);
|
||||
}
|
||||
} else if (urlParts.count() == 1) {
|
||||
|
||||
// location coordinates
|
||||
Menu::getInstance()->goToDestination(urlParts[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return QApplication::event(event);
|
||||
}
|
||||
|
||||
void Application::keyPressEvent(QKeyEvent* event) {
|
||||
|
||||
_controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
|
||||
|
@ -2711,7 +2745,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... voxels...");
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRenderVoxels)) {
|
||||
_voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures));
|
||||
_voxels.render();
|
||||
|
||||
// double check that our LOD doesn't need to be auto-adjusted
|
||||
// only adjust if our option is set
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AutoAdjustLOD)) {
|
||||
Menu::getInstance()->autoAdjustLOD(_fps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2802,7 +2842,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
_mouseVoxel.s,
|
||||
_mouseVoxel.s);
|
||||
|
||||
_sharedVoxelSystem.render(true);
|
||||
_sharedVoxelSystem.render();
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,9 @@ public:
|
|||
void touchUpdateEvent(QTouchEvent* event);
|
||||
|
||||
void wheelEvent(QWheelEvent* event);
|
||||
|
||||
|
||||
bool event(QEvent* event);
|
||||
|
||||
void makeVoxel(glm::vec3 position,
|
||||
float scale,
|
||||
unsigned char red,
|
||||
|
|
|
@ -70,7 +70,8 @@ Menu::Menu() :
|
|||
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
|
||||
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
|
||||
_boundaryLevelAdjust(0),
|
||||
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS)
|
||||
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS),
|
||||
_lastAdjust(usecTimestampNow())
|
||||
{
|
||||
Application *appInstance = Application::getInstance();
|
||||
|
||||
|
@ -318,6 +319,7 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
|
||||
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
|
||||
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
|
||||
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AutoAdjustLOD);
|
||||
addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
|
||||
|
||||
QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options");
|
||||
|
@ -884,6 +886,17 @@ void Menu::editPreferences() {
|
|||
sendFakeEnterEvent();
|
||||
}
|
||||
|
||||
void Menu::goToDomain(const QString newDomain) {
|
||||
if (NodeList::getInstance()->getDomainHostname() != newDomain) {
|
||||
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
Application::getInstance()->getAvatar()->sendKillAvatar();
|
||||
|
||||
// give our nodeList the new domain-server hostname
|
||||
NodeList::getInstance()->setDomainHostname(newDomain);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::goToDomain() {
|
||||
|
||||
QString currentDomainHostname = NodeList::getInstance()->getDomainHostname();
|
||||
|
@ -908,17 +921,77 @@ void Menu::goToDomain() {
|
|||
// the user input a new hostname, use that
|
||||
newHostname = domainDialog.textValue();
|
||||
}
|
||||
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
Application::getInstance()->getAvatar()->sendKillAvatar();
|
||||
|
||||
// give our nodeList the new domain-server hostname
|
||||
NodeList::getInstance()->setDomainHostname(domainDialog.textValue());
|
||||
|
||||
goToDomain(newHostname);
|
||||
}
|
||||
|
||||
sendFakeEnterEvent();
|
||||
}
|
||||
|
||||
void Menu::goToOrientation(QString orientation) {
|
||||
|
||||
if (orientation.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList orientationItems = orientation.split(QRegExp("_|,"), QString::SkipEmptyParts);
|
||||
|
||||
const int NUMBER_OF_ORIENTATION_ITEMS = 4;
|
||||
const int W_ITEM = 0;
|
||||
const int X_ITEM = 1;
|
||||
const int Y_ITEM = 2;
|
||||
const int Z_ITEM = 3;
|
||||
|
||||
if (orientationItems.size() == NUMBER_OF_ORIENTATION_ITEMS) {
|
||||
|
||||
double w = replaceLastOccurrence('-', '.', orientationItems[W_ITEM].trimmed()).toDouble();
|
||||
double x = replaceLastOccurrence('-', '.', orientationItems[X_ITEM].trimmed()).toDouble();
|
||||
double y = replaceLastOccurrence('-', '.', orientationItems[Y_ITEM].trimmed()).toDouble();
|
||||
double z = replaceLastOccurrence('-', '.', orientationItems[Z_ITEM].trimmed()).toDouble();
|
||||
|
||||
glm::quat newAvatarOrientation(w, x, y, z);
|
||||
|
||||
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||
glm::quat avatarOrientation = myAvatar->getOrientation();
|
||||
if (newAvatarOrientation != avatarOrientation) {
|
||||
myAvatar->setOrientation(newAvatarOrientation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::goToDestination(QString destination) {
|
||||
|
||||
QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
|
||||
|
||||
const int NUMBER_OF_COORDINATE_ITEMS = 3;
|
||||
const int X_ITEM = 0;
|
||||
const int Y_ITEM = 1;
|
||||
const int Z_ITEM = 2;
|
||||
if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
|
||||
|
||||
double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble();
|
||||
double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble();
|
||||
double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble();
|
||||
|
||||
glm::vec3 newAvatarPos(x, y, z);
|
||||
|
||||
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||
glm::vec3 avatarPos = myAvatar->getPosition();
|
||||
if (newAvatarPos != avatarPos) {
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
MyAvatar::sendKillAvatar();
|
||||
|
||||
qDebug("Going To Location: %f, %f, %f...", x, y, z);
|
||||
myAvatar->setPosition(newAvatarPos);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// no coordinates were parsed
|
||||
return false;
|
||||
}
|
||||
|
||||
void Menu::goTo() {
|
||||
|
||||
QInputDialog gotoDialog(Application::getInstance()->getWindow());
|
||||
|
@ -934,31 +1007,8 @@ void Menu::goTo() {
|
|||
|
||||
destination = gotoDialog.textValue();
|
||||
|
||||
QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
|
||||
|
||||
const int NUMBER_OF_COORDINATE_ITEMS = 3;
|
||||
const int X_ITEM = 0;
|
||||
const int Y_ITEM = 1;
|
||||
const int Z_ITEM = 2;
|
||||
if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
|
||||
|
||||
double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble();
|
||||
double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble();
|
||||
double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble();
|
||||
|
||||
glm::vec3 newAvatarPos(x, y, z);
|
||||
|
||||
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||
glm::vec3 avatarPos = myAvatar->getPosition();
|
||||
if (newAvatarPos != avatarPos) {
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
MyAvatar::sendKillAvatar();
|
||||
|
||||
qDebug("Going To Location: %f, %f, %f...", x, y, z);
|
||||
myAvatar->setPosition(newAvatarPos);
|
||||
}
|
||||
|
||||
} else {
|
||||
// go to coordinate destination or to Username
|
||||
if (!goToDestination(destination)) {
|
||||
// there's a username entered by the user, make a request to the data-server
|
||||
DataServerClient::getValuesForKeysAndUserString(
|
||||
QStringList()
|
||||
|
@ -989,29 +1039,7 @@ void Menu::goToLocation() {
|
|||
|
||||
int dialogReturn = coordinateDialog.exec();
|
||||
if (dialogReturn == QDialog::Accepted && !coordinateDialog.textValue().isEmpty()) {
|
||||
QByteArray newCoordinates;
|
||||
|
||||
QString delimiterPattern(",");
|
||||
QStringList coordinateItems = coordinateDialog.textValue().split(delimiterPattern);
|
||||
|
||||
const int NUMBER_OF_COORDINATE_ITEMS = 3;
|
||||
const int X_ITEM = 0;
|
||||
const int Y_ITEM = 1;
|
||||
const int Z_ITEM = 2;
|
||||
if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
|
||||
double x = coordinateItems[X_ITEM].toDouble();
|
||||
double y = coordinateItems[Y_ITEM].toDouble();
|
||||
double z = coordinateItems[Z_ITEM].toDouble();
|
||||
glm::vec3 newAvatarPos(x, y, z);
|
||||
|
||||
if (newAvatarPos != avatarPos) {
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
MyAvatar::sendKillAvatar();
|
||||
|
||||
qDebug("Going To Location: %f, %f, %f...", x, y, z);
|
||||
myAvatar->setPosition(newAvatarPos);
|
||||
}
|
||||
}
|
||||
goToDestination(coordinateDialog.textValue());
|
||||
}
|
||||
|
||||
sendFakeEnterEvent();
|
||||
|
@ -1091,14 +1119,38 @@ void Menu::voxelStatsDetailsClosed() {
|
|||
}
|
||||
}
|
||||
|
||||
void Menu::autoAdjustLOD(float currentFPS) {
|
||||
bool changed = false;
|
||||
quint64 now = usecTimestampNow();
|
||||
quint64 elapsed = now - _lastAdjust;
|
||||
|
||||
if (elapsed > ADJUST_LOD_DOWN_DELAY && currentFPS < ADJUST_LOD_DOWN_FPS && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
|
||||
_voxelSizeScale *= ADJUST_LOD_DOWN_BY;
|
||||
changed = true;
|
||||
_lastAdjust = now;
|
||||
qDebug() << "adjusting LOD down... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
|
||||
}
|
||||
|
||||
if (elapsed > ADJUST_LOD_UP_DELAY && currentFPS > ADJUST_LOD_UP_FPS && _voxelSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
|
||||
_voxelSizeScale *= ADJUST_LOD_UP_BY;
|
||||
changed = true;
|
||||
_lastAdjust = now;
|
||||
qDebug() << "adjusting LOD up... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (_lodToolsDialog) {
|
||||
_lodToolsDialog->reloadSliders();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::setVoxelSizeScale(float sizeScale) {
|
||||
_voxelSizeScale = sizeScale;
|
||||
Application::getInstance()->getVoxels()->redrawInViewVoxels();
|
||||
}
|
||||
|
||||
void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
|
||||
_boundaryLevelAdjust = boundaryLevelAdjust;
|
||||
Application::getInstance()->getVoxels()->redrawInViewVoxels();
|
||||
}
|
||||
|
||||
void Menu::lodTools() {
|
||||
|
|
|
@ -16,6 +16,18 @@
|
|||
|
||||
#include <AbstractMenuInterface.h>
|
||||
|
||||
const float ADJUST_LOD_DOWN_FPS = 40.0;
|
||||
const float ADJUST_LOD_UP_FPS = 55.0;
|
||||
|
||||
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 5;
|
||||
const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
|
||||
|
||||
const float ADJUST_LOD_DOWN_BY = 0.9f;
|
||||
const float ADJUST_LOD_UP_BY = 1.1f;
|
||||
|
||||
const float ADJUST_LOD_MIN_SIZE_SCALE = TREE_SCALE * 1.0f;
|
||||
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
|
||||
|
||||
enum FrustumDrawMode {
|
||||
FRUSTUM_DRAW_MODE_ALL,
|
||||
FRUSTUM_DRAW_MODE_VECTORS,
|
||||
|
@ -68,6 +80,7 @@ public:
|
|||
void handleViewFrustumOffsetKeyModifier(int key);
|
||||
|
||||
// User Tweakable LOD Items
|
||||
void autoAdjustLOD(float currentFPS);
|
||||
void setVoxelSizeScale(float sizeScale);
|
||||
float getVoxelSizeScale() const { return _voxelSizeScale; }
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
|
||||
|
@ -84,6 +97,9 @@ public:
|
|||
const char* member = NULL,
|
||||
QAction::MenuRole role = QAction::NoRole);
|
||||
virtual void removeAction(QMenu* menu, const QString& actionName);
|
||||
bool goToDestination(QString destination);
|
||||
void goToOrientation(QString orientation);
|
||||
void goToDomain(const QString newDomain);
|
||||
|
||||
public slots:
|
||||
void bandwidthDetails();
|
||||
|
@ -154,6 +170,7 @@ private:
|
|||
int _maxVoxelPacketsPerSecond;
|
||||
QMenu* _activeScriptsMenu;
|
||||
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
|
||||
quint64 _lastAdjust;
|
||||
};
|
||||
|
||||
namespace MenuOption {
|
||||
|
@ -161,6 +178,7 @@ namespace MenuOption {
|
|||
const QString AmbientOcclusion = "Ambient Occlusion";
|
||||
const QString Avatars = "Avatars";
|
||||
const QString Atmosphere = "Atmosphere";
|
||||
const QString AutoAdjustLOD = "Automatically Adjust LOD";
|
||||
const QString AutomaticallyAuditTree = "Automatically Audit Tree Stats";
|
||||
const QString Bandwidth = "Bandwidth Display";
|
||||
const QString BandwidthDetails = "Bandwidth Details";
|
||||
|
|
|
@ -57,9 +57,12 @@ GLubyte identityIndicesBack[] = { 4, 5, 6, 4, 6, 7 };
|
|||
|
||||
VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
|
||||
: NodeData(),
|
||||
_treeScale(treeScale),
|
||||
_maxVoxels(maxVoxels),
|
||||
_initialized(false) {
|
||||
_treeScale(treeScale),
|
||||
_maxVoxels(maxVoxels),
|
||||
_initialized(false),
|
||||
_writeArraysLock(QReadWriteLock::Recursive),
|
||||
_readArraysLock(QReadWriteLock::Recursive)
|
||||
{
|
||||
|
||||
_voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
|
||||
_writeRenderFullVBO = true;
|
||||
|
@ -99,6 +102,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
|
|||
|
||||
_culledOnce = false;
|
||||
_inhideOutOfView = false;
|
||||
|
||||
_lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
|
||||
_lastKnownBoundaryLevelAdjust = 0;
|
||||
}
|
||||
|
||||
void VoxelSystem::elementDeleted(OctreeElement* element) {
|
||||
|
@ -121,6 +127,7 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) {
|
|||
|
||||
void VoxelSystem::elementUpdated(OctreeElement* element) {
|
||||
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
||||
|
||||
// If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail..
|
||||
if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) {
|
||||
return;
|
||||
|
@ -249,6 +256,9 @@ VoxelSystem::~VoxelSystem() {
|
|||
delete _tree;
|
||||
}
|
||||
|
||||
|
||||
// This is called by the main application thread on both the initialization of the application and when
|
||||
// the preferences dialog box is called/saved
|
||||
void VoxelSystem::setMaxVoxels(int maxVoxels) {
|
||||
if (maxVoxels == _maxVoxels) {
|
||||
return;
|
||||
|
@ -267,6 +277,8 @@ void VoxelSystem::setMaxVoxels(int maxVoxels) {
|
|||
}
|
||||
}
|
||||
|
||||
// This is called by the main application thread on both the initialization of the application and when
|
||||
// the use voxel shader menu item is chosen
|
||||
void VoxelSystem::setUseVoxelShader(bool useVoxelShader) {
|
||||
if (_useVoxelShader == useVoxelShader) {
|
||||
return;
|
||||
|
@ -330,7 +342,7 @@ void VoxelSystem::setVoxelsAsPoints(bool voxelsAsPoints) {
|
|||
|
||||
void VoxelSystem::cleanupVoxelMemory() {
|
||||
if (_initialized) {
|
||||
_bufferWriteLock.lock();
|
||||
_readArraysLock.lockForWrite();
|
||||
_initialized = false; // no longer initialized
|
||||
if (_useVoxelShader) {
|
||||
// these are used when in VoxelShader mode.
|
||||
|
@ -368,7 +380,7 @@ void VoxelSystem::cleanupVoxelMemory() {
|
|||
delete[] _writeVoxelDirtyArray;
|
||||
delete[] _readVoxelDirtyArray;
|
||||
_writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
|
||||
_bufferWriteLock.unlock();
|
||||
_readArraysLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -401,7 +413,8 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice
|
|||
}
|
||||
|
||||
void VoxelSystem::initVoxelMemory() {
|
||||
_bufferWriteLock.lock();
|
||||
_readArraysLock.lockForWrite();
|
||||
_writeArraysLock.lockForWrite();
|
||||
|
||||
_memoryUsageRAM = 0;
|
||||
_memoryUsageVBO = 0; // our VBO allocations as we know them
|
||||
|
@ -516,7 +529,8 @@ void VoxelSystem::initVoxelMemory() {
|
|||
|
||||
_initialized = true;
|
||||
|
||||
_bufferWriteLock.unlock();
|
||||
_writeArraysLock.unlock();
|
||||
_readArraysLock.unlock();
|
||||
}
|
||||
|
||||
void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const {
|
||||
|
@ -646,7 +660,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
|||
}
|
||||
|
||||
_inSetupNewVoxelsForDrawing = true;
|
||||
|
||||
|
||||
bool didWriteFullVBO = _writeRenderFullVBO;
|
||||
if (_tree->isDirty()) {
|
||||
static char buffer[64] = { 0 };
|
||||
|
@ -673,7 +687,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
|||
}
|
||||
|
||||
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
|
||||
_bufferWriteLock.lock();
|
||||
_readArraysLock.lockForWrite();
|
||||
|
||||
if (_voxelsUpdated) {
|
||||
_voxelsDirty=true;
|
||||
|
@ -682,7 +696,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
|||
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
|
||||
copyWrittenDataToReadArrays(didWriteFullVBO);
|
||||
|
||||
_bufferWriteLock.unlock();
|
||||
_readArraysLock.unlock();
|
||||
|
||||
quint64 end = usecTimestampNow();
|
||||
int elapsedmsec = (end - start) / 1000;
|
||||
|
@ -713,8 +727,8 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
|
|||
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
|
||||
{
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" );
|
||||
_bufferWriteLock.lock();
|
||||
"setupNewVoxelsForDrawingSingleNode()... _readArraysLock.lockForWrite();" );
|
||||
_readArraysLock.lockForWrite();
|
||||
}
|
||||
|
||||
_voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
|
||||
|
@ -725,7 +739,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
|
|||
// after...
|
||||
_voxelsUpdated = 0;
|
||||
|
||||
_bufferWriteLock.unlock();
|
||||
_readArraysLock.unlock();
|
||||
|
||||
quint64 end = usecTimestampNow();
|
||||
int elapsedmsec = (end - start) / 1000;
|
||||
|
@ -733,8 +747,73 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
|
|||
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
|
||||
}
|
||||
|
||||
void VoxelSystem::checkForCulling() {
|
||||
|
||||
|
||||
class recreateVoxelGeometryInViewArgs {
|
||||
public:
|
||||
VoxelSystem* thisVoxelSystem;
|
||||
ViewFrustum thisViewFrustum;
|
||||
unsigned long nodesScanned;
|
||||
float voxelSizeScale;
|
||||
int boundaryLevelAdjust;
|
||||
|
||||
recreateVoxelGeometryInViewArgs(VoxelSystem* voxelSystem) :
|
||||
thisVoxelSystem(voxelSystem),
|
||||
thisViewFrustum(*voxelSystem->getViewFrustum()),
|
||||
nodesScanned(0),
|
||||
voxelSizeScale(Menu::getInstance()->getVoxelSizeScale()),
|
||||
boundaryLevelAdjust(Menu::getInstance()->getBoundaryLevelAdjust())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// The goal of this operation is to remove any old references to old geometry, and if the voxel
|
||||
// should be visible, create new geometry for it.
|
||||
bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData) {
|
||||
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
||||
recreateVoxelGeometryInViewArgs* args = (recreateVoxelGeometryInViewArgs*)extraData;
|
||||
|
||||
args->nodesScanned++;
|
||||
|
||||
// reset the old geometry...
|
||||
// note: this doesn't "mark the voxel as changed", so it only releases the old buffer index thereby forgetting the
|
||||
// old geometry
|
||||
voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
|
||||
|
||||
bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, args->voxelSizeScale, args->boundaryLevelAdjust);
|
||||
bool inView = voxel->isInView(args->thisViewFrustum);
|
||||
voxel->setShouldRender(inView && shouldRender);
|
||||
if (shouldRender && inView) {
|
||||
// recreate the geometry
|
||||
args->thisVoxelSystem->updateNodeInArrays(voxel, false, true); // DONT_REUSE_INDEX, FORCE_REDRAW
|
||||
}
|
||||
|
||||
return true; // keep recursing!
|
||||
}
|
||||
|
||||
|
||||
// TODO: does cleanupRemovedVoxels() ever get called?
|
||||
// TODO: other than cleanupRemovedVoxels() is there anyplace we attempt to detect too many abandoned slots???
|
||||
void VoxelSystem::recreateVoxelGeometryInView() {
|
||||
|
||||
qDebug() << "recreateVoxelGeometryInView()...";
|
||||
|
||||
recreateVoxelGeometryInViewArgs args(this);
|
||||
_writeArraysLock.lockForWrite(); // don't let anyone read or write our write arrays until we're done
|
||||
_tree->lockForRead(); // don't let anyone change our tree structure until we're run
|
||||
|
||||
// reset our write arrays bookkeeping to think we've got no voxels in it
|
||||
clearFreeBufferIndexes();
|
||||
|
||||
// do we need to reset out _writeVoxelDirtyArray arrays??
|
||||
memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool));
|
||||
|
||||
_tree->recurseTreeWithOperation(recreateVoxelGeometryInViewOperation,(void*)&args);
|
||||
_tree->unlock();
|
||||
_writeArraysLock.unlock();
|
||||
}
|
||||
|
||||
void VoxelSystem::checkForCulling() {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
|
||||
quint64 start = usecTimestampNow();
|
||||
|
||||
|
@ -762,7 +841,20 @@ void VoxelSystem::checkForCulling() {
|
|||
_hasRecentlyChanged = false;
|
||||
}
|
||||
|
||||
hideOutOfView(forceFullFrustum);
|
||||
// This would be a good place to do a special processing pass, for example, switching the LOD of the scene
|
||||
bool fullRedraw = (_lastKnownVoxelSizeScale != Menu::getInstance()->getVoxelSizeScale() ||
|
||||
_lastKnownBoundaryLevelAdjust != Menu::getInstance()->getBoundaryLevelAdjust());
|
||||
|
||||
// track that these values
|
||||
_lastKnownVoxelSizeScale = Menu::getInstance()->getVoxelSizeScale();
|
||||
_lastKnownBoundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust();
|
||||
|
||||
if (fullRedraw) {
|
||||
// this will remove all old geometry and recreate the correct geometry for all in view voxels
|
||||
recreateVoxelGeometryInView();
|
||||
} else {
|
||||
hideOutOfView(forceFullFrustum);
|
||||
}
|
||||
|
||||
if (forceFullFrustum) {
|
||||
quint64 endViewCulling = usecTimestampNow();
|
||||
|
@ -880,12 +972,26 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) {
|
|||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"copyWrittenDataToReadArrays()");
|
||||
|
||||
if (_voxelsDirty && _voxelsUpdated) {
|
||||
if (fullVBOs) {
|
||||
copyWrittenDataToReadArraysFullVBOs();
|
||||
// attempt to get the writeArraysLock for reading and the readArraysLock for writing
|
||||
// so we can copy from the write to the read... if we fail, that's ok, we'll get it the next
|
||||
// time around, the only side effect is the VBOs won't be updated this frame
|
||||
const int WAIT_FOR_LOCK_IN_MS = 5;
|
||||
if (_readArraysLock.tryLockForWrite(WAIT_FOR_LOCK_IN_MS)) {
|
||||
if (_writeArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
|
||||
if (_voxelsDirty && _voxelsUpdated) {
|
||||
if (fullVBOs) {
|
||||
copyWrittenDataToReadArraysFullVBOs();
|
||||
} else {
|
||||
copyWrittenDataToReadArraysPartialVBOs();
|
||||
}
|
||||
}
|
||||
_writeArraysLock.unlock();
|
||||
} else {
|
||||
copyWrittenDataToReadArraysPartialVBOs();
|
||||
qDebug() << "couldn't get _writeArraysLock.LockForRead()...";
|
||||
}
|
||||
_readArraysLock.unlock();
|
||||
} else {
|
||||
qDebug() << "couldn't get _readArraysLock.LockForWrite()...";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1141,17 +1247,27 @@ void VoxelSystem::updateVBOs() {
|
|||
// would like to include _callsToTreesToArrays
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
|
||||
if (_voxelsDirty) {
|
||||
if (_readRenderFullVBO) {
|
||||
updateFullVBOs();
|
||||
|
||||
// attempt to lock the read arrays, to for copying from them to the actual GPU VBOs.
|
||||
// if we fail to get the lock, that's ok, our VBOs will update on the next frame...
|
||||
const int WAIT_FOR_LOCK_IN_MS = 5;
|
||||
if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
|
||||
if (_readRenderFullVBO) {
|
||||
updateFullVBOs();
|
||||
} else {
|
||||
updatePartialVBOs();
|
||||
}
|
||||
_voxelsDirty = false;
|
||||
_readRenderFullVBO = false;
|
||||
_readArraysLock.unlock();
|
||||
} else {
|
||||
updatePartialVBOs();
|
||||
qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()";
|
||||
}
|
||||
_voxelsDirty = false;
|
||||
_readRenderFullVBO = false;
|
||||
}
|
||||
_callsToTreesToArrays = 0; // clear it
|
||||
}
|
||||
|
||||
// this should only be called on the main application thread during render
|
||||
void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) {
|
||||
bool showWarning = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarning, "updateVBOSegment()");
|
||||
|
@ -1197,7 +1313,8 @@ void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex seg
|
|||
}
|
||||
}
|
||||
|
||||
void VoxelSystem::render(bool texture) {
|
||||
void VoxelSystem::render() {
|
||||
bool texture = Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures);
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "render()");
|
||||
|
||||
|
@ -1404,11 +1521,7 @@ void VoxelSystem::killLocalVoxels() {
|
|||
setupNewVoxelsForDrawing();
|
||||
}
|
||||
|
||||
void VoxelSystem::redrawInViewVoxels() {
|
||||
hideOutOfView(true);
|
||||
}
|
||||
|
||||
|
||||
// only called on main thread
|
||||
bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) {
|
||||
_nodeCount++;
|
||||
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
||||
|
@ -1416,12 +1529,15 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void
|
|||
return true;
|
||||
}
|
||||
|
||||
// only called on main thread, and also always followed by a call to cleanupVoxelMemory()
|
||||
// you shouldn't be calling this on any other thread or without also cleaning up voxel memory
|
||||
void VoxelSystem::clearAllNodesBufferIndex() {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"VoxelSystem::clearAllNodesBufferIndex()");
|
||||
_nodeCount = 0;
|
||||
_tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read
|
||||
_tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation);
|
||||
clearFreeBufferIndexes(); // this should be called too
|
||||
_tree->unlock();
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
|
||||
qDebug("clearing buffer index of %d nodes", _nodeCount);
|
||||
|
|
|
@ -53,7 +53,7 @@ public:
|
|||
|
||||
virtual void init();
|
||||
void simulate(float deltaTime) { }
|
||||
void render(bool texture);
|
||||
void render();
|
||||
|
||||
void changeTree(VoxelTree* newTree);
|
||||
VoxelTree* getTree() const { return _tree; }
|
||||
|
@ -79,7 +79,6 @@ public:
|
|||
unsigned long getVoxelMemoryUsageGPU();
|
||||
|
||||
void killLocalVoxels();
|
||||
void redrawInViewVoxels();
|
||||
|
||||
virtual void removeOutOfView();
|
||||
virtual void hideOutOfView(bool forceFullFrustum = false);
|
||||
|
@ -151,6 +150,7 @@ protected:
|
|||
static const bool DONT_BAIL_EARLY; // by default we will bail early, if you want to force not bailing, then use this
|
||||
void setupNewVoxelsForDrawingSingleNode(bool allowBailEarly = true);
|
||||
void checkForCulling();
|
||||
void recreateVoxelGeometryInView();
|
||||
|
||||
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
|
||||
|
||||
|
@ -194,6 +194,7 @@ private:
|
|||
static bool showAllSubTreeOperation(OctreeElement* element, void* extraData);
|
||||
static bool showAllLocalVoxelsOperation(OctreeElement* element, void* extraData);
|
||||
static bool getVoxelEnclosingOperation(OctreeElement* element, void* extraData);
|
||||
static bool recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData);
|
||||
|
||||
int updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw);
|
||||
int forceRemoveNodeFromArrays(VoxelTreeElement* node);
|
||||
|
@ -211,6 +212,11 @@ private:
|
|||
|
||||
GLfloat* _readVerticesArray;
|
||||
GLubyte* _readColorsArray;
|
||||
|
||||
QReadWriteLock _writeArraysLock;
|
||||
QReadWriteLock _readArraysLock;
|
||||
|
||||
|
||||
GLfloat* _writeVerticesArray;
|
||||
GLubyte* _writeColorsArray;
|
||||
bool* _writeVoxelDirtyArray;
|
||||
|
@ -253,9 +259,6 @@ private:
|
|||
GLuint _vboIndicesFront;
|
||||
GLuint _vboIndicesBack;
|
||||
|
||||
QMutex _bufferWriteLock;
|
||||
QMutex _treeLock;
|
||||
|
||||
ViewFrustum _lastKnownViewFrustum;
|
||||
ViewFrustum _lastStableViewFrustum;
|
||||
ViewFrustum* _viewFrustum;
|
||||
|
@ -299,6 +302,9 @@ private:
|
|||
bool _useFastVoxelPipeline;
|
||||
|
||||
bool _inhideOutOfView;
|
||||
|
||||
float _lastKnownVoxelSizeScale;
|
||||
int _lastKnownBoundaryLevelAdjust;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -349,13 +349,13 @@ bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, floa
|
|||
|
||||
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
|
||||
AvatarData::setFaceModelURL(faceModelURL);
|
||||
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fbx");
|
||||
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
|
||||
_head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
|
||||
}
|
||||
|
||||
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
|
||||
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
||||
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fbx");
|
||||
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst");
|
||||
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ FaceModel::FaceModel(Head* owningHead) :
|
|||
|
||||
void FaceModel::simulate(float deltaTime) {
|
||||
if (!isActive()) {
|
||||
Model::simulate(deltaTime);
|
||||
return;
|
||||
}
|
||||
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
|
||||
|
|
|
@ -21,9 +21,9 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
|
|||
|
||||
void SkeletonModel::simulate(float deltaTime) {
|
||||
if (!isActive()) {
|
||||
Model::simulate(deltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
setTranslation(_owningAvatar->getPosition());
|
||||
setRotation(_owningAvatar->getOrientation() * glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f));
|
||||
const float MODEL_SCALE = 0.0006f;
|
||||
|
|
|
@ -1577,14 +1577,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
return geometry;
|
||||
}
|
||||
|
||||
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) {
|
||||
QBuffer modelBuffer(const_cast<QByteArray*>(&model));
|
||||
modelBuffer.open(QIODevice::ReadOnly);
|
||||
QVariantHash readMapping(const QByteArray& data) {
|
||||
QBuffer buffer(const_cast<QByteArray*>(&data));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
return parseMapping(&buffer);
|
||||
}
|
||||
|
||||
QBuffer mappingBuffer(const_cast<QByteArray*>(&mapping));
|
||||
mappingBuffer.open(QIODevice::ReadOnly);
|
||||
|
||||
return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer));
|
||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
|
||||
QBuffer buffer(const_cast<QByteArray*>(&model));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
return extractFBXGeometry(parseFBX(&buffer), mapping);
|
||||
}
|
||||
|
||||
bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) {
|
||||
|
|
|
@ -164,9 +164,12 @@ public:
|
|||
QVector<FBXAttachment> attachments;
|
||||
};
|
||||
|
||||
/// Reads an FST mapping from the supplied data.
|
||||
QVariantHash readMapping(const QByteArray& data);
|
||||
|
||||
/// Reads FBX geometry from the supplied model and mapping data.
|
||||
/// \exception QString if an error occurs in parsing
|
||||
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping);
|
||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);
|
||||
|
||||
/// Reads SVO geometry from the supplied model data.
|
||||
FBXGeometry readSVO(const QByteArray& model);
|
||||
|
|
|
@ -7,11 +7,7 @@
|
|||
|
||||
#include <cmath>
|
||||
|
||||
// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
|
||||
#include "InterfaceConfig.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QTimer>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -298,46 +294,86 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
|
|||
if (geometry.isNull()) {
|
||||
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
|
||||
getGeometry(fallback) : QSharedPointer<NetworkGeometry>()));
|
||||
geometry->setLODParent(geometry);
|
||||
_networkGeometry.insert(url, geometry);
|
||||
}
|
||||
return geometry;
|
||||
}
|
||||
|
||||
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback) :
|
||||
_modelRequest(url),
|
||||
_modelReply(NULL),
|
||||
_mappingReply(NULL),
|
||||
const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
|
||||
|
||||
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback,
|
||||
const QVariantHash& mapping, const QUrl& textureBase) :
|
||||
_request(url),
|
||||
_reply(NULL),
|
||||
_mapping(mapping),
|
||||
_textureBase(textureBase.isValid() ? textureBase : url),
|
||||
_fallback(fallback),
|
||||
_attempts(0)
|
||||
{
|
||||
_startedLoading(false),
|
||||
_failedToLoad(false),
|
||||
_attempts(0) {
|
||||
|
||||
if (!url.isValid()) {
|
||||
return;
|
||||
}
|
||||
_modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||
makeModelRequest();
|
||||
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||
|
||||
QUrl mappingURL = url;
|
||||
QString path = url.path();
|
||||
mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst");
|
||||
QNetworkRequest mappingRequest(mappingURL);
|
||||
mappingRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||
_mappingReply = Application::getInstance()->getNetworkAccessManager()->get(mappingRequest);
|
||||
|
||||
connect(_mappingReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
|
||||
connect(_mappingReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleMappingReplyError()));
|
||||
// if we already have a mapping (because we're an LOD), hold off on loading until we're requested
|
||||
if (mapping.isEmpty()) {
|
||||
makeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
NetworkGeometry::~NetworkGeometry() {
|
||||
if (_modelReply != NULL) {
|
||||
delete _modelReply;
|
||||
if (_reply != NULL) {
|
||||
delete _reply;
|
||||
}
|
||||
if (_mappingReply != NULL) {
|
||||
delete _mappingReply;
|
||||
}
|
||||
|
||||
QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis) const {
|
||||
if (_lodParent.data() != this) {
|
||||
return _lodParent.data()->getLODOrFallback(distance, hysteresis);
|
||||
}
|
||||
foreach (const NetworkMesh& mesh, _meshes) {
|
||||
glDeleteBuffers(1, &mesh.indexBufferID);
|
||||
glDeleteBuffers(1, &mesh.vertexBufferID);
|
||||
}
|
||||
if (_failedToLoad && _fallback) {
|
||||
return _fallback;
|
||||
}
|
||||
QSharedPointer<NetworkGeometry> lod = _lodParent;
|
||||
float lodDistance = 0.0f;
|
||||
QMap<float, QSharedPointer<NetworkGeometry> >::const_iterator it = _lods.upperBound(distance);
|
||||
if (it != _lods.constBegin()) {
|
||||
it = it - 1;
|
||||
lod = it.value();
|
||||
lodDistance = it.key();
|
||||
}
|
||||
if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) {
|
||||
// if we previously selected a different distance, make sure we've moved far enough to justify switching
|
||||
const float HYSTERESIS_PROPORTION = 0.1f;
|
||||
if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) {
|
||||
return getLODOrFallback(hysteresis, hysteresis);
|
||||
}
|
||||
}
|
||||
if (lod->isLoaded()) {
|
||||
hysteresis = lodDistance;
|
||||
return lod;
|
||||
}
|
||||
// if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one
|
||||
if (!lod->_startedLoading) {
|
||||
lod->makeRequest();
|
||||
}
|
||||
float closestDistance = FLT_MAX;
|
||||
if (isLoaded()) {
|
||||
lod = _lodParent;
|
||||
closestDistance = distance;
|
||||
}
|
||||
for (it = _lods.constBegin(); it != _lods.constEnd(); it++) {
|
||||
float distanceToLOD = glm::abs(distance - it.key());
|
||||
if (it.value()->isLoaded() && distanceToLOD < closestDistance) {
|
||||
lod = it.value();
|
||||
closestDistance = distanceToLOD;
|
||||
}
|
||||
}
|
||||
hysteresis = NO_HYSTERESIS;
|
||||
return lod;
|
||||
}
|
||||
|
||||
glm::vec4 NetworkGeometry::computeAverageColor() const {
|
||||
|
@ -364,20 +400,167 @@ glm::vec4 NetworkGeometry::computeAverageColor() const {
|
|||
return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
|
||||
}
|
||||
|
||||
void NetworkGeometry::makeModelRequest() {
|
||||
_modelReply = Application::getInstance()->getNetworkAccessManager()->get(_modelRequest);
|
||||
void NetworkGeometry::makeRequest() {
|
||||
_startedLoading = true;
|
||||
_reply = Application::getInstance()->getNetworkAccessManager()->get(_request);
|
||||
|
||||
connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
|
||||
connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError()));
|
||||
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
|
||||
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
|
||||
}
|
||||
|
||||
void NetworkGeometry::handleModelReplyError() {
|
||||
QDebug debug = qDebug() << _modelReply->errorString();
|
||||
void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
||||
if (!_reply->isFinished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkReply::NetworkError error = _modelReply->error();
|
||||
_modelReply->disconnect(this);
|
||||
_modelReply->deleteLater();
|
||||
_modelReply = NULL;
|
||||
QUrl url = _reply->url();
|
||||
QByteArray data = _reply->readAll();
|
||||
_reply->disconnect(this);
|
||||
_reply->deleteLater();
|
||||
_reply = NULL;
|
||||
|
||||
if (url.path().toLower().endsWith(".fst")) {
|
||||
// it's a mapping file; parse it and get the mesh filename
|
||||
_mapping = readMapping(data);
|
||||
QString filename = _mapping.value("filename").toString();
|
||||
if (filename.isNull()) {
|
||||
qDebug() << "Mapping file " << url << " has no filename.";
|
||||
_failedToLoad = true;
|
||||
|
||||
} else {
|
||||
QString texdir = _mapping.value("texdir").toString();
|
||||
if (!texdir.isNull()) {
|
||||
if (!texdir.endsWith('/')) {
|
||||
texdir += '/';
|
||||
}
|
||||
_textureBase = url.resolved(texdir);
|
||||
}
|
||||
QVariantHash lods = _mapping.value("lod").toHash();
|
||||
for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) {
|
||||
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url.resolved(it.key()),
|
||||
QSharedPointer<NetworkGeometry>(), _mapping, _textureBase));
|
||||
geometry->setLODParent(_lodParent);
|
||||
_lods.insert(it.value().toFloat(), geometry);
|
||||
}
|
||||
_request.setUrl(url.resolved(filename));
|
||||
|
||||
// make the request immediately only if we have no LODs to switch between
|
||||
_startedLoading = false;
|
||||
if (_lods.isEmpty()) {
|
||||
makeRequest();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_geometry = url.path().toLower().endsWith(".svo") ? readSVO(data) : readFBX(data, _mapping);
|
||||
|
||||
} catch (const QString& error) {
|
||||
qDebug() << "Error reading " << url << ": " << error;
|
||||
_failedToLoad = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const FBXMesh& mesh, _geometry.meshes) {
|
||||
NetworkMesh networkMesh = { QOpenGLBuffer(QOpenGLBuffer::IndexBuffer), QOpenGLBuffer(QOpenGLBuffer::VertexBuffer) };
|
||||
|
||||
int totalIndices = 0;
|
||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||
NetworkMeshPart networkPart;
|
||||
if (!part.diffuseFilename.isEmpty()) {
|
||||
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
|
||||
_textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
|
||||
}
|
||||
if (!part.normalFilename.isEmpty()) {
|
||||
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
|
||||
_textureBase.resolved(QUrl(part.normalFilename)), true);
|
||||
}
|
||||
networkMesh.parts.append(networkPart);
|
||||
|
||||
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
|
||||
}
|
||||
|
||||
networkMesh.indexBuffer.create();
|
||||
networkMesh.indexBuffer.bind();
|
||||
networkMesh.indexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||
networkMesh.indexBuffer.allocate(totalIndices * sizeof(int));
|
||||
int offset = 0;
|
||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
|
||||
part.quadIndices.constData());
|
||||
offset += part.quadIndices.size() * sizeof(int);
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
|
||||
part.triangleIndices.constData());
|
||||
offset += part.triangleIndices.size() * sizeof(int);
|
||||
}
|
||||
networkMesh.indexBuffer.release();
|
||||
|
||||
networkMesh.vertexBuffer.create();
|
||||
networkMesh.vertexBuffer.bind();
|
||||
networkMesh.vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||
|
||||
// if we don't need to do any blending or springing, then the positions/normals can be static
|
||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
||||
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
||||
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
||||
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||
|
||||
networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||
networkMesh.vertexBuffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
|
||||
networkMesh.vertexBuffer.write(normalsOffset, mesh.normals.constData(), mesh.normals.size() * sizeof(glm::vec3));
|
||||
networkMesh.vertexBuffer.write(tangentsOffset, mesh.tangents.constData(),
|
||||
mesh.tangents.size() * sizeof(glm::vec3));
|
||||
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
||||
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
||||
mesh.texCoords.size() * sizeof(glm::vec2));
|
||||
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
|
||||
mesh.clusterIndices.size() * sizeof(glm::vec4));
|
||||
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
||||
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||
|
||||
// if there's no springiness, then the cluster indices/weights can be static
|
||||
} else if (mesh.springiness == 0.0f) {
|
||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||
networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||
networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
|
||||
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
||||
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
||||
mesh.texCoords.size() * sizeof(glm::vec2));
|
||||
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
|
||||
mesh.clusterIndices.size() * sizeof(glm::vec4));
|
||||
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
||||
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||
|
||||
} else {
|
||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||
networkMesh.vertexBuffer.allocate(texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2));
|
||||
networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
|
||||
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
||||
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
||||
mesh.texCoords.size() * sizeof(glm::vec2));
|
||||
}
|
||||
|
||||
networkMesh.vertexBuffer.release();
|
||||
|
||||
_meshes.append(networkMesh);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkGeometry::handleReplyError() {
|
||||
QDebug debug = qDebug() << _reply->errorString();
|
||||
|
||||
QNetworkReply::NetworkError error = _reply->error();
|
||||
_reply->disconnect(this);
|
||||
_reply->deleteLater();
|
||||
_reply = NULL;
|
||||
|
||||
// retry for certain types of failures
|
||||
switch (error) {
|
||||
|
@ -394,164 +577,19 @@ void NetworkGeometry::handleModelReplyError() {
|
|||
const int MAX_ATTEMPTS = 8;
|
||||
const int BASE_DELAY_MS = 1000;
|
||||
if (++_attempts < MAX_ATTEMPTS) {
|
||||
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest()));
|
||||
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest()));
|
||||
debug << " -- retrying...";
|
||||
return;
|
||||
}
|
||||
// fall through to final failure
|
||||
}
|
||||
default:
|
||||
maybeLoadFallback();
|
||||
_failedToLoad = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NetworkGeometry::handleMappingReplyError() {
|
||||
_mappingReply->disconnect(this);
|
||||
_mappingReply->deleteLater();
|
||||
_mappingReply = NULL;
|
||||
|
||||
maybeReadModelWithMapping();
|
||||
}
|
||||
|
||||
void NetworkGeometry::maybeReadModelWithMapping() {
|
||||
if (_modelReply == NULL || !_modelReply->isFinished() || (_mappingReply != NULL && !_mappingReply->isFinished())) {
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl url = _modelReply->url();
|
||||
QByteArray model = _modelReply->readAll();
|
||||
_modelReply->disconnect(this);
|
||||
_modelReply->deleteLater();
|
||||
_modelReply = NULL;
|
||||
|
||||
QByteArray mapping;
|
||||
if (_mappingReply != NULL) {
|
||||
mapping = _mappingReply->readAll();
|
||||
_mappingReply->disconnect(this);
|
||||
_mappingReply->deleteLater();
|
||||
_mappingReply = NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
_geometry = url.path().toLower().endsWith(".svo") ? readSVO(model) : readFBX(model, mapping);
|
||||
|
||||
} catch (const QString& error) {
|
||||
qDebug() << "Error reading " << url << ": " << error;
|
||||
maybeLoadFallback();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const FBXMesh& mesh, _geometry.meshes) {
|
||||
NetworkMesh networkMesh;
|
||||
|
||||
int totalIndices = 0;
|
||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||
NetworkMeshPart networkPart;
|
||||
QString basePath = url.path();
|
||||
basePath = basePath.left(basePath.lastIndexOf('/') + 1);
|
||||
if (!part.diffuseFilename.isEmpty()) {
|
||||
url.setPath(basePath + part.diffuseFilename);
|
||||
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, false, mesh.isEye);
|
||||
}
|
||||
if (!part.normalFilename.isEmpty()) {
|
||||
url.setPath(basePath + part.normalFilename);
|
||||
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url, true);
|
||||
}
|
||||
networkMesh.parts.append(networkPart);
|
||||
|
||||
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
|
||||
}
|
||||
|
||||
glGenBuffers(1, &networkMesh.indexBufferID);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndices * sizeof(int), NULL, GL_STATIC_DRAW);
|
||||
int offset = 0;
|
||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
|
||||
part.quadIndices.constData());
|
||||
offset += part.quadIndices.size() * sizeof(int);
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
|
||||
part.triangleIndices.constData());
|
||||
offset += part.triangleIndices.size() * sizeof(int);
|
||||
}
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
glGenBuffers(1, &networkMesh.vertexBufferID);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
||||
|
||||
// if we don't need to do any blending or springing, then the positions/normals can be static
|
||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
||||
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
||||
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
||||
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||
glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
|
||||
NULL, GL_STATIC_DRAW);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, normalsOffset, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, tangentsOffset, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
|
||||
mesh.texCoords.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
|
||||
mesh.clusterIndices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
|
||||
mesh.clusterWeights.constData());
|
||||
|
||||
// if there's no springiness, then the cluster indices/weights can be static
|
||||
} else if (mesh.springiness == 0.0f) {
|
||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||
glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
|
||||
NULL, GL_STATIC_DRAW);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
|
||||
mesh.clusterIndices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
|
||||
mesh.clusterWeights.constData());
|
||||
|
||||
} else {
|
||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||
glBufferData(GL_ARRAY_BUFFER, texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
|
||||
mesh.texCoords.constData());
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
_meshes.append(networkMesh);
|
||||
}
|
||||
|
||||
emit loaded();
|
||||
}
|
||||
|
||||
void NetworkGeometry::loadFallback() {
|
||||
_geometry = _fallback->_geometry;
|
||||
_meshes = _fallback->_meshes;
|
||||
emit loaded();
|
||||
}
|
||||
|
||||
void NetworkGeometry::maybeLoadFallback() {
|
||||
if (_fallback) {
|
||||
if (_fallback->isLoaded()) {
|
||||
loadFallback();
|
||||
} else {
|
||||
connect(_fallback.data(), SIGNAL(loaded()), SLOT(loadFallback()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkMeshPart::isTranslucent() const {
|
||||
return diffuseTexture && diffuseTexture->isTranslucent();
|
||||
}
|
||||
|
|
|
@ -9,17 +9,20 @@
|
|||
#ifndef __interface__GeometryCache__
|
||||
#define __interface__GeometryCache__
|
||||
|
||||
// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
|
||||
#include "InterfaceConfig.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QNetworkRequest>
|
||||
#include <QObject>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QSharedPointer>
|
||||
#include <QWeakPointer>
|
||||
|
||||
#include "FBXReader.h"
|
||||
#include "InterfaceConfig.h"
|
||||
|
||||
class QNetworkReply;
|
||||
class QOpenGLBuffer;
|
||||
|
||||
class NetworkGeometry;
|
||||
class NetworkMesh;
|
||||
|
@ -59,41 +62,52 @@ class NetworkGeometry : public QObject {
|
|||
|
||||
public:
|
||||
|
||||
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback);
|
||||
/// A hysteresis value indicating that we have no state memory.
|
||||
static const float NO_HYSTERESIS;
|
||||
|
||||
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback,
|
||||
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl());
|
||||
~NetworkGeometry();
|
||||
|
||||
/// Checks whether the geometry is fulled loaded.
|
||||
bool isLoaded() const { return !_geometry.joints.isEmpty(); }
|
||||
|
||||
/// Returns a pointer to the geometry appropriate for the specified distance.
|
||||
/// \param hysteresis a hysteresis parameter that prevents rapid model switching
|
||||
QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis) const;
|
||||
|
||||
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
||||
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
||||
|
||||
/// Returns the average color of all meshes in the geometry.
|
||||
glm::vec4 computeAverageColor() const;
|
||||
|
||||
signals:
|
||||
|
||||
void loaded();
|
||||
|
||||
private slots:
|
||||
|
||||
void makeModelRequest();
|
||||
void handleModelReplyError();
|
||||
void handleMappingReplyError();
|
||||
void maybeReadModelWithMapping();
|
||||
void loadFallback();
|
||||
void makeRequest();
|
||||
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void handleReplyError();
|
||||
|
||||
private:
|
||||
|
||||
void maybeLoadFallback();
|
||||
friend class GeometryCache;
|
||||
|
||||
QNetworkRequest _modelRequest;
|
||||
QNetworkReply* _modelReply;
|
||||
QNetworkReply* _mappingReply;
|
||||
void setLODParent(const QWeakPointer<NetworkGeometry>& lodParent) { _lodParent = lodParent; }
|
||||
|
||||
QNetworkRequest _request;
|
||||
QNetworkReply* _reply;
|
||||
QVariantHash _mapping;
|
||||
QUrl _textureBase;
|
||||
QSharedPointer<NetworkGeometry> _fallback;
|
||||
bool _startedLoading;
|
||||
bool _failedToLoad;
|
||||
|
||||
int _attempts;
|
||||
QMap<float, QSharedPointer<NetworkGeometry> > _lods;
|
||||
FBXGeometry _geometry;
|
||||
QVector<NetworkMesh> _meshes;
|
||||
|
||||
QWeakPointer<NetworkGeometry> _lodParent;
|
||||
};
|
||||
|
||||
/// The state associated with a single mesh part.
|
||||
|
@ -110,8 +124,8 @@ public:
|
|||
class NetworkMesh {
|
||||
public:
|
||||
|
||||
GLuint indexBufferID;
|
||||
GLuint vertexBufferID;
|
||||
QOpenGLBuffer indexBuffer;
|
||||
QOpenGLBuffer vertexBuffer;
|
||||
|
||||
QVector<NetworkMeshPart> parts;
|
||||
|
||||
|
|
|
@ -90,6 +90,16 @@ void Model::reset() {
|
|||
}
|
||||
|
||||
void Model::simulate(float deltaTime) {
|
||||
// update our LOD
|
||||
if (_geometry) {
|
||||
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(glm::distance(_translation,
|
||||
Application::getInstance()->getCamera()->getPosition()), _lodHysteresis);
|
||||
if (_geometry != geometry) {
|
||||
deleteGeometry();
|
||||
_dilatedTextures.clear();
|
||||
_geometry = geometry;
|
||||
}
|
||||
}
|
||||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
|
@ -409,8 +419,9 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) {
|
|||
// delete our local geometry and custom textures
|
||||
deleteGeometry();
|
||||
_dilatedTextures.clear();
|
||||
_lodHysteresis = NetworkGeometry::NO_HYSTERESIS;
|
||||
|
||||
_geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
|
||||
_baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
|
||||
}
|
||||
|
||||
glm::vec4 Model::computeAverageColor() const {
|
||||
|
@ -791,13 +802,17 @@ void Model::renderMeshes(float alpha, bool translucent) {
|
|||
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
|
||||
continue;
|
||||
}
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
||||
|
||||
const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
|
||||
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
int vertexCount = mesh.vertices.size();
|
||||
if (vertexCount == 0) {
|
||||
// sanity check
|
||||
continue;
|
||||
}
|
||||
|
||||
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
||||
|
||||
ProgramObject* program = &_program;
|
||||
ProgramObject* skinProgram = &_skinProgram;
|
||||
SkinLocations* skinLocations = &_skinLocations;
|
||||
|
@ -904,11 +919,11 @@ void Model::renderMeshes(float alpha, bool translucent) {
|
|||
qint64 offset = 0;
|
||||
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
||||
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
||||
const FBXMeshPart& part = mesh.parts.at(j);
|
||||
if (networkPart.isTranslucent() != translucent) {
|
||||
offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int);
|
||||
continue;
|
||||
}
|
||||
const FBXMeshPart& part = mesh.parts.at(j);
|
||||
|
||||
// apply material properties
|
||||
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
|
||||
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
|
||||
|
|
|
@ -236,6 +236,9 @@ private:
|
|||
void deleteGeometry();
|
||||
void renderMeshes(float alpha, bool translucent);
|
||||
|
||||
QSharedPointer<NetworkGeometry> _baseGeometry;
|
||||
float _lodHysteresis;
|
||||
|
||||
float _pupilDilation;
|
||||
std::vector<float> _blendshapeCoefficients;
|
||||
|
||||
|
|
|
@ -121,6 +121,12 @@ LodToolsDialog::~LodToolsDialog() {
|
|||
delete _boundaryLevelAdjust;
|
||||
}
|
||||
|
||||
void LodToolsDialog::reloadSliders() {
|
||||
_lodSize->setValue(Menu::getInstance()->getVoxelSizeScale() / TREE_SCALE);
|
||||
_boundaryLevelAdjust->setValue(Menu::getInstance()->getBoundaryLevelAdjust());
|
||||
_feedback->setText(getFeedbackText());
|
||||
}
|
||||
|
||||
void LodToolsDialog::sizeScaleValueChanged(int value) {
|
||||
float realValue = value * TREE_SCALE;
|
||||
Menu::getInstance()->setVoxelSizeScale(realValue);
|
||||
|
|
|
@ -28,6 +28,7 @@ public slots:
|
|||
void sizeScaleValueChanged(int value);
|
||||
void boundaryLevelValueChanged(int value);
|
||||
void resetClicked(bool checked);
|
||||
void reloadSliders();
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -52,8 +52,8 @@ static const float MIN_AVATAR_SCALE = .005f;
|
|||
|
||||
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
|
||||
|
||||
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fbx");
|
||||
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fbx");
|
||||
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst");
|
||||
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst");
|
||||
|
||||
enum KeyState {
|
||||
NO_KEY_DOWN = 0,
|
||||
|
|
|
@ -45,6 +45,8 @@ public:
|
|||
_alwaysDisplay(alwaysDisplay),
|
||||
_runningTotal(runningTotal),
|
||||
_totalCalls(totalCalls) { }
|
||||
|
||||
quint64 elapsed() const { return (usecTimestampNow() - _start); };
|
||||
|
||||
~PerformanceWarning();
|
||||
|
||||
|
|
Loading…
Reference in a new issue