+
+class ClipboardScriptingInterface : public QObject {
+ Q_OBJECT
+public:
+ ClipboardScriptingInterface();
+
+public slots:
+ void cutVoxel(const VoxelDetail& sourceVoxel);
+ void cutVoxel(float x, float y, float z, float s);
+
+ void copyVoxel(const VoxelDetail& sourceVoxel);
+ void copyVoxel(float x, float y, float z, float s);
+
+ void pasteVoxel(const VoxelDetail& destinationVoxel);
+ void pasteVoxel(float x, float y, float z, float s);
+
+ void deleteVoxel(const VoxelDetail& sourceVoxel);
+ void deleteVoxel(float x, float y, float z, float s);
+
+ void exportVoxel(const VoxelDetail& sourceVoxel);
+ void exportVoxel(float x, float y, float z, float s);
+
+ void importVoxels();
+
+ void nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec);
+ void nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec);
+};
+
+#endif // __interface__Clipboard__
diff --git a/interface/src/ControllerScriptingInterface.cpp b/interface/src/ControllerScriptingInterface.cpp
index b3d6170bff..b60615f124 100644
--- a/interface/src/ControllerScriptingInterface.cpp
+++ b/interface/src/ControllerScriptingInterface.cpp
@@ -250,3 +250,7 @@ void ControllerScriptingInterface::releaseJoystick(int joystickIndex) {
}
}
+glm::vec2 ControllerScriptingInterface::getViewportDimensions() const {
+ QGLWidget* widget = Application::getInstance()->getGLWidget();
+ return glm::vec2(widget->width(), widget->height());
+}
diff --git a/interface/src/ControllerScriptingInterface.h b/interface/src/ControllerScriptingInterface.h
index f0a50559f9..6fe5a60fa4 100644
--- a/interface/src/ControllerScriptingInterface.h
+++ b/interface/src/ControllerScriptingInterface.h
@@ -74,6 +74,8 @@ public slots:
virtual void captureJoystick(int joystickIndex);
virtual void releaseJoystick(int joystickIndex);
+ virtual glm::vec2 getViewportDimensions() const;
+
private:
const PalmData* getPrimaryPalm() const;
const PalmData* getPalm(int palmIndex) const;
diff --git a/interface/src/ImportDialog.cpp b/interface/src/ImportDialog.cpp
index ac7853629c..2e0173fb9f 100644
--- a/interface/src/ImportDialog.cpp
+++ b/interface/src/ImportDialog.cpp
@@ -16,14 +16,12 @@
const QString WINDOW_NAME = QObject::tr("Import Voxels");
const QString IMPORT_BUTTON_NAME = QObject::tr("Import");
+const QString LOADING_BUTTON_NAME = QObject::tr("Loading ...");
+const QString PLACE_BUTTON_NAME = QObject::tr("Place voxels");
const QString IMPORT_INFO = QObject::tr("Import %1 as voxels");
const QString CANCEL_BUTTON_NAME = QObject::tr("Cancel");
-const QString INFO_LABEL_TEXT = QObject::tr(""
- "This will load the selected file into Hifi and allow you
"
- "to place it with %1-V; you must be in select or
"
- "add mode (S or V keys will toggle mode) to place.
");
-const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
+const QString DOWNLOAD_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
const int SHORT_FILE_EXTENSION = 4;
const int SECOND_INDEX_LETTER = 1;
@@ -66,7 +64,7 @@ QIcon HiFiIconProvider::icon(const QFileInfo &info) const {
if (info.isDir()) {
if (info.absoluteFilePath() == QDir::homePath()) {
return QIcon("resources/icons/home.svg");
- } else if (info.absoluteFilePath() == DESKTOP_LOCATION) {
+ } else if (info.absoluteFilePath() == QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)) {
return QIcon("resources/icons/desktop.svg");
} else if (info.absoluteFilePath() == QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)) {
return QIcon("resources/icons/documents.svg");
@@ -95,108 +93,114 @@ QString HiFiIconProvider::type(const QFileInfo &info) const {
}
ImportDialog::ImportDialog(QWidget* parent) :
- QFileDialog(parent, WINDOW_NAME, DESKTOP_LOCATION, NULL),
+ QFileDialog(parent, WINDOW_NAME, DOWNLOAD_LOCATION, NULL),
+ _progressBar(this),
_importButton(IMPORT_BUTTON_NAME, this),
_cancelButton(CANCEL_BUTTON_NAME, this),
- fileAccepted(false) {
+ _mode(importMode) {
setOption(QFileDialog::DontUseNativeDialog, true);
setFileMode(QFileDialog::ExistingFile);
setViewMode(QFileDialog::Detail);
-#ifdef Q_OS_MAC
- QString cmdString = ("Command");
-#else
- QString cmdString = ("Control");
-#endif
- QLabel* infoLabel = new QLabel(QString(INFO_LABEL_TEXT).arg(cmdString));
- infoLabel->setObjectName("infoLabel");
-
- QGridLayout* gridLayout = (QGridLayout*) layout();
- gridLayout->addWidget(infoLabel, 2, 0, 2, 1);
- gridLayout->addWidget(&_cancelButton, 2, 1, 2, 1);
- gridLayout->addWidget(&_importButton, 2, 2, 2, 1);
-
setImportTypes();
setLayout();
- connect(&_importButton, SIGNAL(pressed()), SLOT(import()));
- connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
-
- connect(&_cancelButton, SIGNAL(pressed()), SLOT(close()));
- connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
-}
-
-ImportDialog::~ImportDialog() {
- deleteLater();
-}
-
-void ImportDialog::import() {
- fileAccepted = true;
- emit accepted();
-}
-
-void ImportDialog::accept() {
- // do nothing if import is not enable
- if (!_importButton.isEnabled()) {
- return;
- }
+ _progressBar.setRange(0, 100);
- if (!fileAccepted) {
- fileAccepted = true;
- emit accepted();
- } else {
- QFileDialog::accept();
- }
-}
-
-void ImportDialog::reject() {
- QFileDialog::reject();
-}
-
-int ImportDialog::exec() {
- // deselect selected file
- selectFile(" ");
- return QFileDialog::exec();
+ connect(&_importButton, SIGNAL(pressed()), SLOT(accept()));
+ connect(&_cancelButton, SIGNAL(pressed()), SIGNAL(canceled()));
+ connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
}
void ImportDialog::reset() {
- _importButton.setEnabled(false);
+ setMode(importMode);
+ _progressBar.setValue(0);
}
-void ImportDialog::saveCurrentFile(QString filename) {
- if (!filename.isEmpty() && QFileInfo(filename).isFile()) {
- _currentFile = filename;
- _importButton.setEnabled(true);
- } else {
- _currentFile.clear();
- _importButton.setEnabled(false);
+void ImportDialog::setMode(dialogMode mode) {
+ _mode = mode;
+
+ switch (_mode) {
+ case loadingMode:
+ _importButton.setEnabled(false);
+ _importButton.setText(LOADING_BUTTON_NAME);
+ findChild("sidebar")->setEnabled(false);
+ findChild("treeView")->setEnabled(false);
+ findChild("backButton")->setEnabled(false);
+ findChild("forwardButton")->setEnabled(false);
+ findChild("toParentButton")->setEnabled(false);
+ break;
+ case placeMode:
+ _progressBar.setValue(100);
+ _importButton.setEnabled(true);
+ _importButton.setText(PLACE_BUTTON_NAME);
+ findChild("sidebar")->setEnabled(false);
+ findChild("treeView")->setEnabled(false);
+ findChild("backButton")->setEnabled(false);
+ findChild("forwardButton")->setEnabled(false);
+ findChild("toParentButton")->setEnabled(false);
+ break;
+ case importMode:
+ default:
+ _progressBar.setValue(0);
+ _importButton.setEnabled(true);
+ _importButton.setText(IMPORT_BUTTON_NAME);
+ findChild("sidebar")->setEnabled(true);
+ findChild("treeView")->setEnabled(true);
+ findChild("backButton")->setEnabled(true);
+ findChild("forwardButton")->setEnabled(true);
+ findChild("toParentButton")->setEnabled(true);
+ break;
}
}
-void ImportDialog::setLayout() {
+void ImportDialog::setProgressBarValue(int value) {
+ _progressBar.setValue(value);
+}
+void ImportDialog::accept() {
+ emit accepted();
+}
+
+void ImportDialog::saveCurrentFile(QString filename) {
+ _currentFile = QFileInfo(filename).isFile() ? filename : "";
+}
+
+void ImportDialog::setLayout() {
+ QGridLayout* gridLayout = (QGridLayout*) layout();
+ gridLayout->addWidget(&_progressBar, 2, 0, 2, 1);
+ gridLayout->addWidget(&_cancelButton, 2, 1, 2, 1);
+ gridLayout->addWidget(&_importButton, 2, 2, 2, 1);
+
// set ObjectName used in qss for styling
+ _progressBar.setObjectName("progressBar");
_importButton.setObjectName("importButton");
_cancelButton.setObjectName("cancelButton");
// set fixed size
_importButton.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
_cancelButton.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ _cancelButton.setFlat(true);
+ int progressBarHeight = 7;
+ _progressBar.setFixedHeight(progressBarHeight);
+ _progressBar.setTextVisible(false);
+
+ QSize BUTTON_SIZE = QSize(43, 33);
+ QPushButton* button = (QPushButton*) findChild("backButton");
+ button->setIcon(QIcon());
+ button->setFixedSize(BUTTON_SIZE);
+ button = (QPushButton*) findChild("forwardButton");
+ button->setIcon(QIcon());
+ button->setFixedSize(BUTTON_SIZE);
+ button = (QPushButton*) findChild("toParentButton");
+ button->setIcon(QIcon());
+ button->setFixedSize(BUTTON_SIZE);
// hide unused embedded widgets in QFileDialog
QWidget* widget = findChild("lookInCombo");
widget->hide();
-
- widget = findChild("backButton");
- widget->hide();
-
- widget = findChild("forwardButton");
- widget->hide();
-
- widget = findChild("toParentButton");
- widget->hide();
-
+
widget = findChild("newFolderButton");
widget->hide();
@@ -230,7 +234,7 @@ void ImportDialog::setLayout() {
widget = findChild("treeView");
widget->setAttribute(Qt::WA_MacShowFocusRect, false);
-
+
switchToResourcesParentIfRequired();
QFile styleSheet("resources/styles/import_dialog.qss");
if (styleSheet.open(QIODevice::ReadOnly)) {
@@ -281,11 +285,6 @@ void ImportDialog::setImportTypes() {
setIconProvider(new HiFiIconProvider(iconsMap));
setNameFilter(importFormatsFilterList);
-#ifdef Q_OS_MAC
- QString cmdString = ("Command");
-#else
- QString cmdString = ("Control");
-#endif
setLabelText(QFileDialog::LookIn, QString(IMPORT_INFO).arg(importFormatsInfo));
}
}
diff --git a/interface/src/ImportDialog.h b/interface/src/ImportDialog.h
index 5cfc49e51e..910cd8f789 100644
--- a/interface/src/ImportDialog.h
+++ b/interface/src/ImportDialog.h
@@ -11,6 +11,7 @@
#include
#include
+#include
#include
#include
#include
@@ -26,37 +27,42 @@ public:
QHash iconsMap;
};
+enum dialogMode {
+ importMode,
+ loadingMode,
+ placeMode
+};
+
class ImportDialog : public QFileDialog {
Q_OBJECT
public:
ImportDialog(QWidget* parent = NULL);
- ~ImportDialog();
-
void reset();
-
+
QString getCurrentFile() const { return _currentFile; }
-
+ dialogMode getMode() const { return _mode; }
+ void setMode(dialogMode mode);
+
signals:
- void accepted();
-
+ void canceled();
+
public slots:
- int exec();
- void import();
- void accept();
- void reject();
+ void setProgressBarValue(int value);
private slots:
- void saveCurrentFile(QString);
+ void accept();
+ void saveCurrentFile(QString filename);
private:
QString _currentFile;
+ QProgressBar _progressBar;
QPushButton _importButton;
QPushButton _cancelButton;
-
+ dialogMode _mode;
+
void setLayout();
void setImportTypes();
- bool fileAccepted;
};
#endif /* defined(__hifi__ImportDialog__) */
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 83788b265d..100022c2a6 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -327,7 +327,8 @@ Menu::Menu() :
QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options");
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true);
- addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollisionProxies);
+ addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionProxies);
+ addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionProxies);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
@@ -786,7 +787,7 @@ void Menu::editPreferences() {
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
- QString faceURLString = applicationInstance->getAvatar()->getHead().getFaceModel().getURL().toString();
+ QString faceURLString = applicationInstance->getAvatar()->getHead()->getFaceModel().getURL().toString();
QLineEdit* faceURLEdit = new QLineEdit(faceURLString);
faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
faceURLEdit->setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString());
@@ -798,8 +799,13 @@ void Menu::editPreferences() {
skeletonURLEdit->setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString());
form->addRow("Skeleton URL:", skeletonURLEdit);
+ QString displayNameString = applicationInstance->getAvatar()->getDisplayName();
+ QLineEdit* displayNameEdit = new QLineEdit(displayNameString);
+ displayNameEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
+ form->addRow("Display name:", displayNameEdit);
+
QSlider* pupilDilation = new QSlider(Qt::Horizontal);
- pupilDilation->setValue(applicationInstance->getAvatar()->getHead().getPupilDilation() * pupilDilation->maximum());
+ pupilDilation->setValue(applicationInstance->getAvatar()->getHead()->getPupilDilation() * pupilDilation->maximum());
form->addRow("Pupil Dilation:", pupilDilation);
QSlider* faceshiftEyeDeflection = new QSlider(Qt::Horizontal);
@@ -870,12 +876,19 @@ void Menu::editPreferences() {
applicationInstance->getAvatar()->setSkeletonModelURL(skeletonModelURL);
shouldDispatchIdentityPacket = true;
}
+
+ QString displayNameStr(displayNameEdit->text());
+ if (displayNameStr != displayNameString) {
+ applicationInstance->getAvatar()->setDisplayName(displayNameStr);
+ shouldDispatchIdentityPacket = true;
+ }
+
if (shouldDispatchIdentityPacket) {
applicationInstance->getAvatar()->sendIdentityPacket();
}
- applicationInstance->getAvatar()->getHead().setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum());
+ applicationInstance->getAvatar()->getHead()->setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum());
_maxVoxels = maxVoxels->value();
applicationInstance->getVoxels()->setMaxVoxels(_maxVoxels);
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 60a8a0761e..262b618526 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -185,7 +185,6 @@ namespace MenuOption {
const QString Bandwidth = "Bandwidth Display";
const QString BandwidthDetails = "Bandwidth Details";
const QString ChatCircling = "Chat Circling";
- const QString CollisionProxies = "Collision Proxies";
const QString Collisions = "Collisions";
const QString CollideWithAvatars = "Collide With Avatars";
const QString CollideWithParticles = "Collide With Particles";
@@ -268,6 +267,8 @@ namespace MenuOption {
const QString Preferences = "Preferences...";
const QString RandomizeVoxelColors = "Randomize Voxel TRUE Colors";
const QString ReloadAllScripts = "Reload All Scripts";
+ const QString RenderSkeletonCollisionProxies = "Skeleton Collision Proxies";
+ const QString RenderHeadCollisionProxies = "Head Collision Proxies";
const QString ResetAvatarSize = "Reset Avatar Size";
const QString ResetSwatchColors = "Reset Swatch Colors";
const QString RunTimingTests = "Run Timing Tests";
diff --git a/interface/src/VoxelImporter.cpp b/interface/src/VoxelImporter.cpp
index 653d04cee4..3949ee96d2 100644
--- a/interface/src/VoxelImporter.cpp
+++ b/interface/src/VoxelImporter.cpp
@@ -12,6 +12,9 @@
#include
#include
+const QString SETTINGS_GROUP_NAME = "VoxelImport";
+const QString IMPORT_DIALOG_SETTINGS_KEY = "ImportDialogSettings";
+
class ImportTask : public QObject, public QRunnable {
public:
ImportTask(const QString &filename);
@@ -21,18 +24,16 @@ private:
QString _filename;
};
-const QString SETTINGS_GROUP_NAME = "VoxelImport";
-const QString IMPORT_DIALOG_SETTINGS_KEY = "ImportDialogSettings";
-
VoxelImporter::VoxelImporter(QWidget* parent) :
QObject(parent),
_voxelTree(true),
_importDialog(parent),
- _currentTask(NULL),
- _nextTask(NULL)
+ _task(NULL),
+ _didImport(false)
{
- connect(&_importDialog, &QFileDialog::currentChanged, this, &VoxelImporter::preImport);
- connect(&_importDialog, &QFileDialog::accepted, this, &VoxelImporter::import);
+ connect(&_voxelTree, SIGNAL(importProgress(int)), &_importDialog, SLOT(setProgressBarValue(int)));
+ connect(&_importDialog, SIGNAL(canceled()), this, SLOT(cancel()));
+ connect(&_importDialog, SIGNAL(accepted()), this, SLOT(import()));
}
void VoxelImporter::saveSettings(QSettings* settings) {
@@ -41,145 +42,106 @@ void VoxelImporter::saveSettings(QSettings* settings) {
settings->endGroup();
}
-void VoxelImporter::init(QSettings* settings) {
+void VoxelImporter::loadSettings(QSettings* settings) {
settings->beginGroup(SETTINGS_GROUP_NAME);
_importDialog.restoreState(settings->value(IMPORT_DIALOG_SETTINGS_KEY).toByteArray());
settings->endGroup();
}
VoxelImporter::~VoxelImporter() {
- if (_nextTask) {
- delete _nextTask;
- _nextTask = NULL;
- }
-
- if (_currentTask) {
- disconnect(_currentTask, 0, 0, 0);
- _voxelTree.cancelImport();
- _currentTask = NULL;
- }
+ cleanupTask();
}
void VoxelImporter::reset() {
_voxelTree.eraseAllOctreeElements();
_importDialog.reset();
- _filename = "";
- if (_nextTask) {
- delete _nextTask;
- _nextTask = NULL;
- }
-
- if (_currentTask) {
- _voxelTree.cancelImport();
- }
+ cleanupTask();
}
int VoxelImporter::exec() {
reset();
-
- int ret = _importDialog.exec();
-
- if (!ret) {
- reset();
- } else {
- _importDialog.reset();
-
- VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
-
- voxelSystem->copySubTreeIntoNewTree(voxelSystem->getTree()->getRoot(),
- Application::getInstance()->getClipboard(),
- true);
- voxelSystem->changeTree(Application::getInstance()->getClipboard());
- }
-
- return ret;
-}
-
-int VoxelImporter::preImport() {
- QString filename = _importDialog.getCurrentFile();
-
- if (!QFileInfo(filename).isFile()) {
- return 0;
- }
+ _importDialog.exec();
- _filename = filename;
-
- if (_nextTask) {
- delete _nextTask;
- }
-
- _nextTask = new ImportTask(_filename);
- connect(_nextTask, SIGNAL(destroyed()), SLOT(launchTask()));
-
- if (_currentTask != NULL) {
- _voxelTree.cancelImport();
- } else {
- launchTask();
- }
-
- return 1;
-}
-
-int VoxelImporter::import() {
- QString filename = _importDialog.getCurrentFile();
-
- if (!QFileInfo(filename).isFile()) {
- _importDialog.reject();
- return 0;
- }
-
- if (_filename == filename) {
- if (_currentTask) {
- connect(_currentTask, SIGNAL(destroyed()), &_importDialog, SLOT(accept()));
- } else {
- _importDialog.accept();
- }
+ if (!_didImport) {
+ // if the import is rejected, we make sure to cleanup before leaving
+ cleanupTask();
return 1;
- }
-
- _filename = filename;
-
- if (_nextTask) {
- delete _nextTask;
- }
-
- _nextTask = new ImportTask(_filename);
- connect(_nextTask, SIGNAL(destroyed()), SLOT(launchTask()));
- connect(_nextTask, SIGNAL(destroyed()), &_importDialog, SLOT(accept()));
-
- if (_currentTask != NULL) {
- _voxelTree.cancelImport();
} else {
- launchTask();
+ _didImport = false;
+ return 0;
}
-
- return 1;
}
-void VoxelImporter::launchTask() {
- if (_nextTask != NULL) {
- _currentTask = _nextTask;
- _nextTask = NULL;
+void VoxelImporter::import() {
+ switch (_importDialog.getMode()) {
+ case loadingMode:
+ _importDialog.setMode(placeMode);
+ return;
+ case placeMode:
+ // Means the user chose to import
+ _didImport = true;
+ _importDialog.close();
+ return;
+ case importMode:
+ default:
+ QString filename = _importDialog.getCurrentFile();
+ // if it's not a file, we ignore the call
+ if (!QFileInfo(filename).isFile()) {
+ return;
+ }
+
+ // Let's prepare the dialog window for import
+ _importDialog.setMode(loadingMode);
+
+ // If not already done, we switch to the local tree
+ if (Application::getInstance()->getSharedVoxelSystem()->getTree() != &_voxelTree) {
+ Application::getInstance()->getSharedVoxelSystem()->changeTree(&_voxelTree);
+ }
+
+ // Creation and launch of the import task on the thread pool
+ _task = new ImportTask(filename);
+ connect(_task, SIGNAL(destroyed()), SLOT(import()));
+ QThreadPool::globalInstance()->start(_task);
+ break;
+ }
+}
- if (Application::getInstance()->getSharedVoxelSystem()->getTree() != &_voxelTree) {
- Application::getInstance()->getSharedVoxelSystem()->changeTree(&_voxelTree);
- }
+void VoxelImporter::cancel() {
+ switch (_importDialog.getMode()) {
+ case loadingMode:
+ disconnect(_task, 0, 0, 0);
+ cleanupTask();
+ case placeMode:
+ _importDialog.setMode(importMode);
+ break;
+ case importMode:
+ default:
+ _importDialog.close();
+ break;
+ }
+}
- QThreadPool::globalInstance()->start(_currentTask);
- } else {
- _currentTask = NULL;
+void VoxelImporter::cleanupTask() {
+ // If a task is running, we cancel it and put the pointer to null
+ if (_task) {
+ _task = NULL;
+ _voxelTree.cancelImport();
}
}
ImportTask::ImportTask(const QString &filename)
- : _filename(filename) {
+ : _filename(filename)
+{
+ setAutoDelete(true);
}
void ImportTask::run() {
VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
+ // We start by cleaning up the shared voxel system just in case
voxelSystem->killLocalVoxels();
+ // Then we call the righ method for the job
if (_filename.endsWith(".png", Qt::CaseInsensitive)) {
voxelSystem->readFromSquareARGB32Pixels(_filename.toLocal8Bit().data());
} else if (_filename.endsWith(".svo", Qt::CaseInsensitive)) {
@@ -187,8 +149,10 @@ void ImportTask::run() {
} else if (_filename.endsWith(".schematic", Qt::CaseInsensitive)) {
voxelSystem->readFromSchematicFile(_filename.toLocal8Bit().data());
} else {
- qDebug("[ERROR] Invalid file extension.");
+ // We should never get here.
+ qDebug() << "[ERROR] Invalid file extension." << endl;
}
-
+
+ // Here we reaverage the tree so that he is ready for preview
voxelSystem->getTree()->reaverageOctreeElements();
}
diff --git a/interface/src/VoxelImporter.h b/interface/src/VoxelImporter.h
index 43a3835e68..e77abaf18d 100644
--- a/interface/src/VoxelImporter.h
+++ b/interface/src/VoxelImporter.h
@@ -23,28 +23,25 @@ public:
VoxelImporter(QWidget* parent = NULL);
~VoxelImporter();
- void init(QSettings* settings);
void reset();
+ void loadSettings(QSettings* settings);
void saveSettings(QSettings* settings);
VoxelTree* getVoxelTree() { return &_voxelTree; }
public slots:
int exec();
- int preImport();
- int import();
-
-private slots:
- void launchTask();
+ void import();
+ void cancel();
private:
VoxelTree _voxelTree;
ImportDialog _importDialog;
- QString _filename;
-
- ImportTask* _currentTask;
- ImportTask* _nextTask;
+ ImportTask* _task;
+ bool _didImport;
+
+ void cleanupTask();
};
#endif /* defined(__hifi__VoxelImporter__) */
diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp
index ac27ac3209..99f6171b7a 100644
--- a/interface/src/VoxelSystem.cpp
+++ b/interface/src/VoxelSystem.cpp
@@ -2382,7 +2382,7 @@ void VoxelSystem::hideOutOfView(bool forceFullFrustum) {
bool VoxelSystem::hideAllSubTreeOperation(OctreeElement* element, void* extraData) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData;
-
+
// If we've culled at least once, then we will use the status of this voxel in the last culled frustum to determine
// how to proceed. If we've never culled, then we just consider all these voxels to be UNKNOWN so that we will not
// consider that case.
@@ -2418,7 +2418,7 @@ bool VoxelSystem::hideAllSubTreeOperation(OctreeElement* element, void* extraDat
bool VoxelSystem::showAllSubTreeOperation(OctreeElement* element, void* extraData) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData;
-
+
// If we've culled at least once, then we will use the status of this voxel in the last culled frustum to determine
// how to proceed. If we've never culled, then we just consider all these voxels to be UNKNOWN so that we will not
// consider that case.
@@ -2468,7 +2468,7 @@ bool VoxelSystem::showAllSubTreeOperation(OctreeElement* element, void* extraDat
bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData;
-
+
// If we're still recursing the tree using this operator, then we don't know if we're inside or outside...
// so before we move forward we need to determine our frustum location
ViewFrustum::location inFrustum = voxel->inFrustum(args->thisViewFrustum);
@@ -2485,7 +2485,6 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData
// ok, now do some processing for this node...
switch (inFrustum) {
case ViewFrustum::OUTSIDE: {
-
// If this node is outside the current view, then we might want to hide it... unless it was previously OUTSIDE,
// if it was previously outside, then we can safely assume it's already hidden, and we can also safely assume
// that all of it's children are outside both of our views, in which case we can just stop recursing...
@@ -2503,7 +2502,6 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData
} break;
case ViewFrustum::INSIDE: {
-
// If this node is INSIDE the current view, then we might want to show it... unless it was previously INSIDE,
// if it was previously INSIDE, then we can safely assume it's already shown, and we can also safely assume
// that all of it's children are INSIDE both of our views, in which case we can just stop recursing...
@@ -2517,12 +2515,10 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData
// we need to show it. Additionally we know that ALL of it's children are also fully INSIDE so we can recurse
// the children and simply mark them as visible (as appropriate based on LOD)
args->tree->recurseNodeWithOperation(voxel, showAllSubTreeOperation, args);
-
return false;
} break;
case ViewFrustum::INTERSECT: {
args->nodesScanned++;
-
// If this node INTERSECTS the current view, then we might want to show it... unless it was previously INSIDE
// the last known view, in which case it will already be visible, and we know that all it's children are also
// previously INSIDE and visible. So in this case stop recursing
@@ -2536,8 +2532,15 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData
// if the child node INTERSECTs the view, then we want to check to see if it thinks it should render
// if it should render but is missing it's VBO index, then we want to flip it on, and we can stop recursing from
// here because we know will block any children anyway
+
+ float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale();
+ int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust();
+ bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, voxelSizeScale, boundaryLevelAdjust);
+ voxel->setShouldRender(shouldRender);
+
if (voxel->getShouldRender() && !voxel->isKnownBufferIndex()) {
voxel->setDirtyBit(); // will this make it draw?
+ voxel->markWithChangedTime(); // both are needed to force redraw
args->nodesShown++;
return false;
}
@@ -2550,7 +2553,6 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData
} break;
} // switch
-
return true; // keep going!
}
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index efee7fcc8e..dcecd0258d 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -56,11 +56,13 @@ const float HEAD_RATE_MAX = 50.f;
const int NUM_BODY_CONE_SIDES = 9;
const float CHAT_MESSAGE_SCALE = 0.0015f;
const float CHAT_MESSAGE_HEIGHT = 0.1f;
+const float DISPLAYNAME_FADE_TIME = 0.5f;
+const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);
+const float DISPLAYNAME_ALPHA = 0.95f;
+const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f;
Avatar::Avatar() :
AvatarData(),
- _head(this),
- _hand(this),
_skeletonModel(this),
_bodyYawDelta(0.0f),
_mode(AVATAR_MODE_STANDING),
@@ -81,18 +83,16 @@ Avatar::Avatar() :
moveToThread(Application::getInstance()->thread());
// give the pointer to our head to inherited _headData variable from AvatarData
- _headData = &_head;
- _handData = &_hand;
+ _headData = static_cast(new Head(this));
+ _handData = static_cast(new Hand(this));
}
Avatar::~Avatar() {
- _headData = NULL;
- _handData = NULL;
}
void Avatar::init() {
- _head.init();
- _hand.init();
+ getHead()->init();
+ getHand()->init();
_skeletonModel.init();
_initialized = true;
}
@@ -111,20 +111,21 @@ void Avatar::simulate(float deltaTime) {
if (_scale != _targetScale) {
setScale(_targetScale);
}
-
+
// copy velocity so we can use it later for acceleration
glm::vec3 oldVelocity = getVelocity();
- _hand.simulate(deltaTime, false);
+ getHand()->simulate(deltaTime, false);
_skeletonModel.simulate(deltaTime);
- _head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
+ Head* head = getHead();
+ head->setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
glm::vec3 headPosition;
if (!_skeletonModel.getHeadPosition(headPosition)) {
headPosition = _position;
}
- _head.setPosition(headPosition);
- _head.setScale(_scale);
- _head.simulate(deltaTime, false);
+ head->setPosition(headPosition);
+ head->setScale(_scale);
+ getHead()->simulate(deltaTime, false);
// use speed and angular velocity to determine walking vs. standing
if (_speed + fabs(_bodyYawDelta) > 0.2) {
@@ -138,7 +139,23 @@ void Avatar::simulate(float deltaTime) {
// Zero thrust out now that we've added it to velocity in this frame
_thrust = glm::vec3(0, 0, 0);
-
+
+ // update animation for display name fade in/out
+ if ( _displayNameTargetAlpha != _displayNameAlpha) {
+ // the alpha function is
+ // Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt)
+ // Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt)
+ // factor^(dt) = coef
+ float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime);
+ if (_displayNameTargetAlpha < _displayNameAlpha) {
+ // Fading out
+ _displayNameAlpha *= coef;
+ } else {
+ // Fading in
+ _displayNameAlpha = 1 - (1 - _displayNameAlpha) * coef;
+ }
+ _displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01? _displayNameTargetAlpha : _displayNameAlpha;
+ }
}
void Avatar::setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction) {
@@ -146,33 +163,50 @@ void Avatar::setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction) {
_mouseRayDirection = direction;
}
-static TextRenderer* textRenderer() {
- static TextRenderer* renderer = new TextRenderer(SANS_FONT_FAMILY, 24, -1, false, TextRenderer::SHADOW_EFFECT);
- return renderer;
+enum TextRendererType {
+ CHAT,
+ DISPLAYNAME
+};
+
+static TextRenderer* textRenderer(TextRendererType type) {
+ static TextRenderer* chatRenderer = new TextRenderer(SANS_FONT_FAMILY, 24, -1, false, TextRenderer::SHADOW_EFFECT);
+ static TextRenderer* displayNameRenderer = new TextRenderer(SANS_FONT_FAMILY, 12, -1, false, TextRenderer::NO_EFFECT);
+
+ switch(type) {
+ case CHAT:
+ return chatRenderer;
+ case DISPLAYNAME:
+ return displayNameRenderer;
+ }
+
+ return displayNameRenderer;
}
void Avatar::render(bool forceRenderHead) {
-
+ glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
+ float lengthToTarget = glm::length(toTarget);
+
{
// glow when moving in the distance
- glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
- const float GLOW_DISTANCE = 5.0f;
- Glower glower(_moving && glm::length(toTarget) > GLOW_DISTANCE ? 1.0f : 0.0f);
- // render body
- if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
- _skeletonModel.renderCollisionProxies(1.f);
- //_head.getFaceModel().renderCollisionProxies(0.5f);
- }
+ const float GLOW_DISTANCE = 5.0f;
+ Glower glower(_moving && lengthToTarget > GLOW_DISTANCE ? 1.0f : 0.0f);
+ // render body
+ if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
+ _skeletonModel.renderCollisionProxies(0.7f);
+ }
+ if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionProxies)) {
+ getHead()->getFaceModel().renderCollisionProxies(0.7f);
+ }
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
renderBody(forceRenderHead);
}
-
+
// render sphere when far away
const float MAX_ANGLE = 10.f;
- float height = getHeight();
- glm::vec3 delta = height * (_head.getCameraOrientation() * IDENTITY_UP) / 2.f;
+ float height = getSkeletonHeight();
+ glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.f;
float angle = abs(angleBetween(toTarget + delta, toTarget - delta));
if (angle < MAX_ANGLE) {
@@ -180,21 +214,23 @@ void Avatar::render(bool forceRenderHead) {
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z);
glScalef(height / 2.f, height / 2.f, height / 2.f);
- glutSolidSphere(1.2f + _head.getAverageLoudness() * .0005f, 20, 20);
+ glutSolidSphere(1.2f + getHead()->getAverageLoudness() * .0005f, 20, 20);
glPopMatrix();
}
}
+ const float DISPLAYNAME_DISTANCE = 10.0f;
+ setShowDisplayName(lengthToTarget < DISPLAYNAME_DISTANCE);
+ renderDisplayName();
-
if (!_chatMessage.empty()) {
int width = 0;
int lastWidth = 0;
for (string::iterator it = _chatMessage.begin(); it != _chatMessage.end(); it++) {
- width += (lastWidth = textRenderer()->computeWidth(*it));
+ width += (lastWidth = textRenderer(CHAT)->computeWidth(*it));
}
glPushMatrix();
- glm::vec3 chatPosition = getHead().getEyePosition() + getBodyUpDirection() * CHAT_MESSAGE_HEIGHT * _scale;
+ glm::vec3 chatPosition = getHead()->getEyePosition() + getBodyUpDirection() * CHAT_MESSAGE_HEIGHT * _scale;
glTranslatef(chatPosition.x, chatPosition.y, chatPosition.z);
glm::quat chatRotation = Application::getInstance()->getCamera()->getRotation();
glm::vec3 chatAxis = glm::axis(chatRotation);
@@ -209,7 +245,7 @@ void Avatar::render(bool forceRenderHead) {
glDisable(GL_LIGHTING);
glDepthMask(false);
if (_keyState == NO_KEY_DOWN) {
- textRenderer()->draw(-width / 2.0f, 0, _chatMessage.c_str());
+ textRenderer(CHAT)->draw(-width / 2.0f, 0, _chatMessage.c_str());
} else {
// rather than using substr and allocating a new string, just replace the last
@@ -217,10 +253,10 @@ void Avatar::render(bool forceRenderHead) {
int lastIndex = _chatMessage.size() - 1;
char lastChar = _chatMessage[lastIndex];
_chatMessage[lastIndex] = '\0';
- textRenderer()->draw(-width / 2.0f, 0, _chatMessage.c_str());
+ textRenderer(CHAT)->draw(-width / 2.0f, 0, _chatMessage.c_str());
_chatMessage[lastIndex] = lastChar;
glColor3f(0, 1, 0);
- textRenderer()->draw(width / 2.0f - lastWidth, 0, _chatMessage.c_str() + lastIndex);
+ textRenderer(CHAT)->draw(width / 2.0f - lastWidth, 0, _chatMessage.c_str() + lastIndex);
}
glEnable(GL_LIGHTING);
glDepthMask(true);
@@ -251,9 +287,105 @@ void Avatar::renderBody(bool forceRenderHead) {
//printf("Render other at %.3f, %.2f, %.2f\n", pos.x, pos.y, pos.z);
_skeletonModel.render(1.0f);
if (forceRenderHead) {
- _head.render(1.0f);
+ getHead()->render(1.0f);
}
- _hand.render(false);
+ getHand()->render(false);
+}
+
+void Avatar::renderDisplayName() {
+
+ if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) {
+ return;
+ }
+
+ glDisable(GL_LIGHTING);
+
+ glPushMatrix();
+ glm::vec3 textPosition;
+ getSkeletonModel().getNeckPosition(textPosition);
+ textPosition += getBodyUpDirection() * getHeadHeight() * 1.1f;
+
+ glTranslatef(textPosition.x, textPosition.y, textPosition.z);
+
+ // we need "always facing camera": we must remove the camera rotation from the stack
+ glm::quat rotation = Application::getInstance()->getCamera()->getRotation();
+ glm::vec3 axis = glm::axis(rotation);
+ glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
+
+ // We need to compute the scale factor such as the text remains with fixed size respect to window coordinates
+ // We project a unit vector and check the difference in screen coordinates, to check which is the
+ // correction scale needed
+ // save the matrices for later scale correction factor
+ glm::dmat4 modelViewMatrix;
+ glm::dmat4 projectionMatrix;
+ GLint viewportMatrix[4];
+ Application::getInstance()->getModelViewMatrix(&modelViewMatrix);
+ Application::getInstance()->getProjectionMatrix(&projectionMatrix);
+ glGetIntegerv(GL_VIEWPORT, viewportMatrix);
+ GLdouble result0[3], result1[3];
+
+ glm::dvec3 upVector(modelViewMatrix[1]);
+
+ glm::dvec3 testPoint0 = glm::dvec3(textPosition);
+ glm::dvec3 testPoint1 = glm::dvec3(textPosition) + upVector;
+
+ bool success;
+ success = gluProject(testPoint0.x, testPoint0.y, testPoint0.z,
+ (GLdouble*)&modelViewMatrix, (GLdouble*)&projectionMatrix, viewportMatrix,
+ &result0[0], &result0[1], &result0[2]);
+ success = success &&
+ gluProject(testPoint1.x, testPoint1.y, testPoint1.z,
+ (GLdouble*)&modelViewMatrix, (GLdouble*)&projectionMatrix, viewportMatrix,
+ &result1[0], &result1[1], &result1[2]);
+
+ if (success) {
+ double textWindowHeight = abs(result1[1] - result0[1]);
+ float scaleFactor = (textWindowHeight > EPSILON) ? 1.0f / textWindowHeight : 1.0f;
+ glScalef(scaleFactor, scaleFactor, 1.0);
+
+ glScalef(1.0f, -1.0f, 1.0f); // TextRenderer::draw paints the text upside down in y axis
+
+ int text_x = -_displayNameBoundingRect.width() / 2;
+ int text_y = -_displayNameBoundingRect.height() / 2;
+
+ // draw a gray background
+ int left = text_x + _displayNameBoundingRect.x();
+ int right = left + _displayNameBoundingRect.width();
+ int bottom = text_y + _displayNameBoundingRect.y();
+ int top = bottom + _displayNameBoundingRect.height();
+ const int border = 8;
+ bottom -= border;
+ left -= border;
+ top += border;
+ right += border;
+
+ // We are drawing coplanar textures with depth: need the polygon offset
+ glEnable(GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset(1.0f, 1.0f);
+
+ glColor4f(0.2f, 0.2f, 0.2f, _displayNameAlpha * DISPLAYNAME_BACKGROUND_ALPHA / DISPLAYNAME_ALPHA);
+ glBegin(GL_QUADS);
+ glVertex2f(left, bottom);
+ glVertex2f(right, bottom);
+ glVertex2f(right, top);
+ glVertex2f(left, top);
+ glEnd();
+
+
+ glColor4f(0.93f, 0.93f, 0.93f, _displayNameAlpha);
+
+ QByteArray ba = _displayName.toLocal8Bit();
+ const char* text = ba.data();
+
+ glDisable(GL_POLYGON_OFFSET_FILL);
+ textRenderer(DISPLAYNAME)->draw(text_x, text_y, text);
+
+
+ }
+
+ glPopMatrix();
+
+ glEnable(GL_LIGHTING);
}
bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
@@ -262,7 +394,7 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
if (_skeletonModel.findRayIntersection(origin, direction, modelDistance)) {
minDistance = qMin(minDistance, modelDistance);
}
- if (_head.getFaceModel().findRayIntersection(origin, direction, modelDistance)) {
+ if (getHead()->getFaceModel().findRayIntersection(origin, direction, modelDistance)) {
minDistance = qMin(minDistance, modelDistance);
}
if (minDistance < FLT_MAX) {
@@ -277,7 +409,7 @@ bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penet
// Temporarily disabling collisions against the skeleton because the collision proxies up
// near the neck are bad and prevent the hand from hitting the face.
//return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, 1.0f, skeletonSkipIndex);
- return _head.getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
+ return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
}
bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
@@ -356,7 +488,7 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
- _head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
+ getHead()->getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
}
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
@@ -365,6 +497,11 @@ void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
}
+void Avatar::setDisplayName(const QString& displayName) {
+ AvatarData::setDisplayName(displayName);
+ _displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName);
+}
+
int Avatar::parseData(const QByteArray& packet) {
// change in position implies movement
glm::vec3 oldPosition = _position;
@@ -450,11 +587,16 @@ void Avatar::setScale(float scale) {
}
}
-float Avatar::getHeight() const {
+float Avatar::getSkeletonHeight() const {
Extents extents = _skeletonModel.getBindExtents();
return extents.maximum.y - extents.minimum.y;
}
+float Avatar::getHeadHeight() const {
+ Extents extents = getHead()->getFaceModel().getBindExtents();
+ return extents.maximum.y - extents.minimum.y;
+}
+
bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
if (!collision._data || collision._type != MODEL_COLLISION) {
return false;
@@ -467,7 +609,7 @@ bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
return false;
//return _skeletonModel.collisionHitsMoveableJoint(collision);
}
- if (model == &(_head.getFaceModel())) {
+ if (model == &(getHead()->getFaceModel())) {
// ATM we always handle MODEL_COLLISIONS against the face.
return true;
}
@@ -480,8 +622,8 @@ void Avatar::applyCollision(CollisionInfo& collision) {
}
// TODO: make skeleton also respond to collisions
Model* model = static_cast(collision._data);
- if (model == &(_head.getFaceModel())) {
- _head.applyCollision(collision);
+ if (model == &(getHead()->getFaceModel())) {
+ getHead()->applyCollision(collision);
}
}
@@ -490,6 +632,24 @@ float Avatar::getPelvisFloatingHeight() const {
}
float Avatar::getPelvisToHeadLength() const {
- return glm::distance(_position, _head.getPosition());
+ return glm::distance(_position, getHead()->getPosition());
+}
+
+void Avatar::setShowDisplayName(bool showDisplayName) {
+ // For myAvatar, the alpha update is not done (called in simulate for other avatars)
+ if (Application::getInstance()->getAvatar() == this) {
+ if (showDisplayName) {
+ _displayNameAlpha = DISPLAYNAME_ALPHA;
+ } else {
+ _displayNameAlpha = 0.0f;
+ }
+ }
+
+ if (showDisplayName) {
+ _displayNameTargetAlpha = DISPLAYNAME_ALPHA;
+ } else {
+ _displayNameTargetAlpha = 0.0f;
+ }
+
}
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index cc1168ca88..6fa0f203e9 100755
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -74,7 +74,7 @@ public:
void render(bool forceRenderHead);
//setters
- void setDisplayingLookatVectors(bool displayingLookatVectors) { _head.setRenderLookatVectors(displayingLookatVectors); }
+ void setDisplayingLookatVectors(bool displayingLookatVectors) { getHead()->setRenderLookatVectors(displayingLookatVectors); }
void setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction);
//getters
@@ -83,8 +83,9 @@ public:
glm::vec3 getChestPosition() const;
float getScale() const { return _scale; }
const glm::vec3& getVelocity() const { return _velocity; }
- Head& getHead() { return _head; }
- Hand& getHand() { return _hand; }
+ const Head* getHead() const { return static_cast(_headData); }
+ Head* getHead() { return static_cast(_headData); }
+ Hand* getHand() { return static_cast(_handData); }
glm::quat getWorldAlignedOrientation() const;
Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); }
@@ -112,25 +113,28 @@ public:
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
+ virtual void setDisplayName(const QString& displayName);
+
+ void setShowDisplayName(bool showDisplayName);
int parseData(const QByteArray& packet);
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
+
+
/// \return true if we expect the avatar would move as a result of the collision
bool collisionWouldMoveAvatar(CollisionInfo& collision) const;
/// \param collision a data structure for storing info about collisions against Models
void applyCollision(CollisionInfo& collision);
- float getBoundingRadius() const { return 0.5f * getHeight(); }
+ float getBoundingRadius() const { return 0.5f * getSkeletonHeight(); }
public slots:
void updateCollisionFlags();
protected:
- Head _head;
- Hand _hand;
SkeletonModel _skeletonModel;
float _bodyYawDelta;
AvatarMode _mode;
@@ -155,7 +159,8 @@ protected:
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
void setScale(float scale);
- float getHeight() const;
+ float getSkeletonHeight() const;
+ float getHeadHeight() const;
float getPelvisFloatingHeight() const;
float getPelvisToHeadLength() const;
@@ -164,6 +169,8 @@ private:
bool _initialized;
void renderBody(bool forceRenderHead);
+
+ void renderDisplayName();
};
#endif
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 51cea9d085..4cc568cf18 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -5,6 +5,9 @@
// Created by Stephen Birarda on 1/23/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
+#include
+
+#include
#include
#include
@@ -71,6 +74,8 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) {
"Application::renderAvatars()");
bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors);
+
+
if (!selfAvatarOnly) {
foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) {
Avatar* avatar = static_cast(avatarPointer.data());
@@ -184,8 +189,9 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) {
while (!identityStream.atEnd()) {
QUrl faceMeshURL, skeletonURL;
- identityStream >> nodeUUID >> faceMeshURL >> skeletonURL;
-
+ QString displayName;
+ identityStream >> nodeUUID >> faceMeshURL >> skeletonURL >> displayName;
+
// mesh URL for a UUID, find avatar in our list
AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID);
if (matchingAvatar) {
@@ -198,6 +204,10 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) {
if (avatar->getSkeletonModelURL() != skeletonURL) {
avatar->setSkeletonModelURL(skeletonURL);
}
+
+ if (avatar->getDisplayName() != displayName) {
+ avatar->setDisplayName(displayName);
+ }
}
}
}
diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp
index c9d2565cee..2a1593b776 100644
--- a/interface/src/avatar/FaceModel.cpp
+++ b/interface/src/avatar/FaceModel.cpp
@@ -48,9 +48,6 @@ bool FaceModel::render(float alpha) {
if (!Model::render(alpha)) {
return false;
}
- if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
- renderCollisionProxies(alpha);
- }
return true;
}
diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp
index 7c9905557e..efbdfa2438 100644
--- a/interface/src/avatar/Hand.cpp
+++ b/interface/src/avatar/Hand.cpp
@@ -125,6 +125,41 @@ void Hand::simulate(float deltaTime, bool isMine) {
}
}
+void Hand::playSlaps(PalmData& palm, Avatar* avatar)
+{
+ // Check for palm collisions
+ glm::vec3 myPalmPosition = palm.getPosition();
+ float palmCollisionDistance = 0.1f;
+ bool wasColliding = palm.getIsCollidingWithPalm();
+ palm.setIsCollidingWithPalm(false);
+ // If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound
+ for (size_t j = 0; j < avatar->getHand()->getNumPalms(); j++) {
+ PalmData& otherPalm = avatar->getHand()->getPalms()[j];
+ if (!otherPalm.isActive()) {
+ continue;
+ }
+ glm::vec3 otherPalmPosition = otherPalm.getPosition();
+ if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
+ palm.setIsCollidingWithPalm(true);
+ if (!wasColliding) {
+ const float PALM_COLLIDE_VOLUME = 1.f;
+ const float PALM_COLLIDE_FREQUENCY = 1000.f;
+ const float PALM_COLLIDE_DURATION_MAX = 0.75f;
+ const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
+ Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
+ PALM_COLLIDE_FREQUENCY,
+ PALM_COLLIDE_DURATION_MAX,
+ PALM_COLLIDE_DECAY_PER_SAMPLE);
+ // If the other person's palm is in motion, move mine downward to show I was hit
+ const float MIN_VELOCITY_FOR_SLAP = 0.05f;
+ if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
+ // add slapback here
+ }
+ }
+ }
+ }
+}
+
// We create a static CollisionList that is recycled for each collision test.
const float MAX_COLLISIONS_PER_AVATAR = 32;
static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR);
@@ -139,41 +174,12 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
PalmData& palm = getPalms()[i];
if (!palm.isActive()) {
continue;
- }
- glm::vec3 totalPenetration;
- if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
- // Check for palm collisions
- glm::vec3 myPalmPosition = palm.getPosition();
- float palmCollisionDistance = 0.1f;
- bool wasColliding = palm.getIsCollidingWithPalm();
- palm.setIsCollidingWithPalm(false);
- // If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound
- for (size_t j = 0; j < avatar->getHand().getNumPalms(); j++) {
- PalmData& otherPalm = avatar->getHand().getPalms()[j];
- if (!otherPalm.isActive()) {
- continue;
- }
- glm::vec3 otherPalmPosition = otherPalm.getPosition();
- if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
- palm.setIsCollidingWithPalm(true);
- if (!wasColliding) {
- const float PALM_COLLIDE_VOLUME = 1.f;
- const float PALM_COLLIDE_FREQUENCY = 1000.f;
- const float PALM_COLLIDE_DURATION_MAX = 0.75f;
- const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
- Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
- PALM_COLLIDE_FREQUENCY,
- PALM_COLLIDE_DURATION_MAX,
- PALM_COLLIDE_DECAY_PER_SAMPLE);
- // If the other person's palm is in motion, move mine downward to show I was hit
- const float MIN_VELOCITY_FOR_SLAP = 0.05f;
- if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
- // add slapback here
- }
- }
- }
- }
}
+ if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
+ playSlaps(palm, avatar);
+ }
+
+ glm::vec3 totalPenetration;
handCollisions.clear();
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions)) {
for (int j = 0; j < handCollisions.size(); ++j) {
@@ -307,7 +313,8 @@ void Hand::render(bool isMine) {
_buckyBalls.render();
}
- if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
+ if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
+ // draw a green sphere at hand joint location, which is actually near the wrist)
for (size_t i = 0; i < getNumPalms(); i++) {
PalmData& palm = getPalms()[i];
if (!palm.isActive()) {
diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h
index 5a423630b4..9888c9f054 100755
--- a/interface/src/avatar/Hand.h
+++ b/interface/src/avatar/Hand.h
@@ -93,6 +93,8 @@ private:
void calculateGeometry();
void handleVoxelCollision(PalmData* palm, const glm::vec3& fingerTipPosition, VoxelTreeElement* voxel, float deltaTime);
+
+ void playSlaps(PalmData& palm, Avatar* avatar);
};
#endif
diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp
index ddb0660364..5c6100764a 100644
--- a/interface/src/avatar/Head.cpp
+++ b/interface/src/avatar/Head.cpp
@@ -62,24 +62,20 @@ void Head::simulate(float deltaTime, bool isMine) {
// Update audio trailing average for rendering facial animations
Faceshift* faceshift = Application::getInstance()->getFaceshift();
+ Visage* visage = Application::getInstance()->getVisage();
if (isMine) {
- _isFaceshiftConnected = faceshift->isActive();
+ _isFaceshiftConnected = false;
+ if (faceshift->isActive()) {
+ _blendshapeCoefficients = faceshift->getBlendshapeCoefficients();
+ _isFaceshiftConnected = true;
+
+ } else if (visage->isActive()) {
+ _blendshapeCoefficients = visage->getBlendshapeCoefficients();
+ _isFaceshiftConnected = true;
+ }
}
- if (isMine && faceshift->isActive()) {
- const float EYE_OPEN_SCALE = 0.5f;
- _leftEyeBlink = faceshift->getLeftBlink() - EYE_OPEN_SCALE * faceshift->getLeftEyeOpen();
- _rightEyeBlink = faceshift->getRightBlink() - EYE_OPEN_SCALE * faceshift->getRightEyeOpen();
-
- // set these values based on how they'll be used. if we use faceshift in the long term, we'll want a complete
- // mapping between their blendshape coefficients and our avatar features
- const float MOUTH_SIZE_SCALE = 2500.0f;
- _averageLoudness = faceshift->getMouthSize() * faceshift->getMouthSize() * MOUTH_SIZE_SCALE;
- const float BROW_HEIGHT_SCALE = 0.005f;
- _browAudioLift = faceshift->getBrowUpCenter() * BROW_HEIGHT_SCALE;
- _blendshapeCoefficients = faceshift->getBlendshapeCoefficients();
-
- } else if (!_isFaceshiftConnected) {
+ if (!_isFaceshiftConnected) {
// Update eye saccades
const float AVERAGE_MICROSACCADE_INTERVAL = 0.50f;
const float AVERAGE_SACCADE_INTERVAL = 4.0f;
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 17b80089a7..016159f415 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -53,7 +53,6 @@ MyAvatar::MyAvatar() :
_elapsedTimeSinceCollision(0.0f),
_lastCollisionPosition(0, 0, 0),
_speedBrakes(false),
- _isCollisionsOn(true),
_isThrustOn(false),
_thrustMultiplier(1.0f),
_moveTarget(0,0,0),
@@ -73,8 +72,8 @@ void MyAvatar::reset() {
// TODO? resurrect headMouse stuff?
//_headMouseX = _glWidget->width() / 2;
//_headMouseY = _glWidget->height() / 2;
- _head.reset();
- _hand.reset();
+ getHead()->reset();
+ getHand()->reset();
setVelocity(glm::vec3(0,0,0));
setThrust(glm::vec3(0,0,0));
@@ -131,19 +130,20 @@ void MyAvatar::update(float deltaTime) {
//_headMouseY = glm::clamp(_headMouseY, 0, _glWidget->height());
}
+ Head* head = getHead();
if (OculusManager::isConnected()) {
float yaw, pitch, roll;
OculusManager::getEulerAngles(yaw, pitch, roll);
- _head.setYaw(yaw);
- _head.setPitch(pitch);
- _head.setRoll(roll);
+ head->setYaw(yaw);
+ head->setPitch(pitch);
+ head->setRoll(roll);
}
// Get audio loudness data from audio input device
Audio* audio = Application::getInstance()->getAudio();
- _head.setAudioLoudness(audio->getLastInputLoudness());
- _head.setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
+ head->setAudioLoudness(audio->getLastInputLoudness());
+ head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
if (Menu::getInstance()->isOptionChecked(MenuOption::Gravity)) {
setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()));
@@ -198,7 +198,7 @@ void MyAvatar::simulate(float deltaTime) {
if (_collisionFlags != 0) {
Camera* myCamera = Application::getInstance()->getCamera();
- float radius = getHeight() * COLLISION_RADIUS_SCALE;
+ float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE;
if (myCamera->getMode() == CAMERA_MODE_FIRST_PERSON && !OculusManager::isConnected()) {
radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.f));
radius *= COLLISION_RADIUS_SCALAR;
@@ -267,7 +267,7 @@ void MyAvatar::simulate(float deltaTime) {
if (!Application::getInstance()->getFaceshift()->isActive() && OculusManager::isConnected() &&
fabsf(forwardAcceleration) > OCULUS_ACCELERATION_PULL_THRESHOLD &&
- fabs(_head.getYaw()) > OCULUS_YAW_OFFSET_THRESHOLD) {
+ fabs(getHead()->getYaw()) > OCULUS_YAW_OFFSET_THRESHOLD) {
// if we're wearing the oculus
// and this acceleration is above the pull threshold
@@ -277,7 +277,7 @@ void MyAvatar::simulate(float deltaTime) {
_bodyYaw = getAbsoluteHeadYaw();
// set the head yaw to zero for this draw
- _head.setYaw(0);
+ getHead()->setYaw(0);
// correct the oculus yaw offset
OculusManager::updateYawOffset();
@@ -315,17 +315,20 @@ void MyAvatar::simulate(float deltaTime) {
_position += _velocity * deltaTime;
// update avatar skeleton and simulate hand and head
- _hand.collideAgainstOurself();
- _hand.simulate(deltaTime, true);
+ getHand()->collideAgainstOurself();
+ getHand()->simulate(deltaTime, true);
+
_skeletonModel.simulate(deltaTime);
- _head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
+
+ Head* head = getHead();
+ head->setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
glm::vec3 headPosition;
if (!_skeletonModel.getHeadPosition(headPosition)) {
headPosition = _position;
}
- _head.setPosition(headPosition);
- _head.setScale(_scale);
- _head.simulate(deltaTime, true);
+ head->setPosition(headPosition);
+ head->setScale(_scale);
+ head->simulate(deltaTime, true);
// Zero thrust out now that we've added it to velocity in this frame
_thrust = glm::vec3(0, 0, 0);
@@ -337,22 +340,34 @@ const float MAX_PITCH = 90.0f;
// Update avatar head rotation with sensor data
void MyAvatar::updateFromGyros(float deltaTime) {
Faceshift* faceshift = Application::getInstance()->getFaceshift();
+ Visage* visage = Application::getInstance()->getVisage();
glm::vec3 estimatedPosition, estimatedRotation;
+ bool trackerActive = false;
if (faceshift->isActive()) {
estimatedPosition = faceshift->getHeadTranslation();
estimatedRotation = safeEulerAngles(faceshift->getHeadRotation());
+ trackerActive = true;
+
+ } else if (visage->isActive()) {
+ estimatedPosition = visage->getHeadTranslation();
+ estimatedRotation = safeEulerAngles(visage->getHeadRotation());
+ trackerActive = true;
+ }
+
+ Head* head = getHead();
+ if (trackerActive) {
// Rotate the body if the head is turned beyond the screen
if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) {
- const float FACESHIFT_YAW_TURN_SENSITIVITY = 0.5f;
- const float FACESHIFT_MIN_YAW_TURN = 15.f;
- const float FACESHIFT_MAX_YAW_TURN = 50.f;
- if ( (fabs(estimatedRotation.y) > FACESHIFT_MIN_YAW_TURN) &&
- (fabs(estimatedRotation.y) < FACESHIFT_MAX_YAW_TURN) ) {
+ const float TRACKER_YAW_TURN_SENSITIVITY = 0.5f;
+ const float TRACKER_MIN_YAW_TURN = 15.f;
+ const float TRACKER_MAX_YAW_TURN = 50.f;
+ if ( (fabs(estimatedRotation.y) > TRACKER_MIN_YAW_TURN) &&
+ (fabs(estimatedRotation.y) < TRACKER_MAX_YAW_TURN) ) {
if (estimatedRotation.y > 0.f) {
- _bodyYawDelta += (estimatedRotation.y - FACESHIFT_MIN_YAW_TURN) * FACESHIFT_YAW_TURN_SENSITIVITY;
+ _bodyYawDelta += (estimatedRotation.y - TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
} else {
- _bodyYawDelta += (estimatedRotation.y + FACESHIFT_MIN_YAW_TURN) * FACESHIFT_YAW_TURN_SENSITIVITY;
+ _bodyYawDelta += (estimatedRotation.y + TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
}
}
}
@@ -360,10 +375,10 @@ void MyAvatar::updateFromGyros(float deltaTime) {
// restore rotation, lean to neutral positions
const float RESTORE_PERIOD = 1.f; // seconds
float restorePercentage = glm::clamp(deltaTime/RESTORE_PERIOD, 0.f, 1.f);
- _head.setYaw(glm::mix(_head.getYaw(), 0.0f, restorePercentage));
- _head.setRoll(glm::mix(_head.getRoll(), 0.0f, restorePercentage));
- _head.setLeanSideways(glm::mix(_head.getLeanSideways(), 0.0f, restorePercentage));
- _head.setLeanForward(glm::mix(_head.getLeanForward(), 0.0f, restorePercentage));
+ head->setYaw(glm::mix(head->getYaw(), 0.0f, restorePercentage));
+ head->setRoll(glm::mix(head->getRoll(), 0.0f, restorePercentage));
+ head->setLeanSideways(glm::mix(head->getLeanSideways(), 0.0f, restorePercentage));
+ head->setLeanForward(glm::mix(head->getLeanForward(), 0.0f, restorePercentage));
return;
}
@@ -372,17 +387,17 @@ void MyAvatar::updateFromGyros(float deltaTime) {
const float AVATAR_HEAD_PITCH_MAGNIFY = 1.0f;
const float AVATAR_HEAD_YAW_MAGNIFY = 1.0f;
const float AVATAR_HEAD_ROLL_MAGNIFY = 1.0f;
- _head.tweakPitch(estimatedRotation.x * AVATAR_HEAD_PITCH_MAGNIFY);
- _head.tweakYaw(estimatedRotation.y * AVATAR_HEAD_YAW_MAGNIFY);
- _head.tweakRoll(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY);
+ head->tweakPitch(estimatedRotation.x * AVATAR_HEAD_PITCH_MAGNIFY);
+ head->tweakYaw(estimatedRotation.y * AVATAR_HEAD_YAW_MAGNIFY);
+ head->tweakRoll(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY);
// Update torso lean distance based on accelerometer data
const float TORSO_LENGTH = 0.5f;
glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f);
const float MAX_LEAN = 45.0f;
- _head.setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)),
+ head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
- _head.setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)),
+ head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
// if Faceshift drive is enabled, set the avatar drive based on the head position
@@ -391,11 +406,11 @@ void MyAvatar::updateFromGyros(float deltaTime) {
}
// Move with Lean by applying thrust proportional to leaning
- glm::quat orientation = _head.getCameraOrientation();
+ glm::quat orientation = head->getCameraOrientation();
glm::vec3 front = orientation * IDENTITY_FRONT;
glm::vec3 right = orientation * IDENTITY_RIGHT;
- float leanForward = _head.getLeanForward();
- float leanSideways = _head.getLeanSideways();
+ float leanForward = head->getLeanForward();
+ float leanSideways = head->getLeanSideways();
// Degrees of 'dead zone' when leaning, and amount of acceleration to apply to lean angle
const float LEAN_FWD_DEAD_ZONE = 15.f;
@@ -426,7 +441,7 @@ static TextRenderer* textRenderer() {
void MyAvatar::renderDebugBodyPoints() {
glm::vec3 torsoPosition(getPosition());
- glm::vec3 headPosition(getHead().getEyePosition());
+ glm::vec3 headPosition(getHead()->getEyePosition());
float torsoToHead = glm::length(headPosition - torsoPosition);
glm::vec3 position;
printf("head-above-torso %.2f, scale = %0.2f\n", torsoToHead, getScale());
@@ -452,7 +467,10 @@ void MyAvatar::renderDebugBodyPoints() {
void MyAvatar::render(bool forceRenderHead) {
// render body
- if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
+ if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
+ _skeletonModel.renderCollisionProxies(1.f);
+ }
+ if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionProxies)) {
_skeletonModel.renderCollisionProxies(1.f);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
@@ -469,7 +487,7 @@ void MyAvatar::render(bool forceRenderHead) {
}
glPushMatrix();
- glm::vec3 chatPosition = getHead().getEyePosition() + getBodyUpDirection() * CHAT_MESSAGE_HEIGHT * _scale;
+ glm::vec3 chatPosition = getHead()->getEyePosition() + getBodyUpDirection() * CHAT_MESSAGE_HEIGHT * _scale;
glTranslatef(chatPosition.x, chatPosition.y, chatPosition.z);
glm::quat chatRotation = Application::getInstance()->getCamera()->getRotation();
glm::vec3 chatAxis = glm::axis(chatRotation);
@@ -579,19 +597,20 @@ void MyAvatar::saveData(QSettings* settings) {
settings->setValue("bodyPitch", _bodyPitch);
settings->setValue("bodyRoll", _bodyRoll);
- settings->setValue("headPitch", _head.getPitch());
+ settings->setValue("headPitch", getHead()->getPitch());
settings->setValue("position_x", _position.x);
settings->setValue("position_y", _position.y);
settings->setValue("position_z", _position.z);
- settings->setValue("pupilDilation", _head.getPupilDilation());
+ settings->setValue("pupilDilation", getHead()->getPupilDilation());
settings->setValue("leanScale", _leanScale);
settings->setValue("scale", _targetScale);
settings->setValue("faceModelURL", _faceModelURL);
settings->setValue("skeletonModelURL", _skeletonModelURL);
+ settings->setValue("displayName", _displayName);
settings->endGroup();
}
@@ -604,13 +623,13 @@ void MyAvatar::loadData(QSettings* settings) {
_bodyPitch = loadSetting(settings, "bodyPitch", 0.0f);
_bodyRoll = loadSetting(settings, "bodyRoll", 0.0f);
- _head.setPitch(loadSetting(settings, "headPitch", 0.0f));
+ getHead()->setPitch(loadSetting(settings, "headPitch", 0.0f));
_position.x = loadSetting(settings, "position_x", 0.0f);
_position.y = loadSetting(settings, "position_y", 0.0f);
_position.z = loadSetting(settings, "position_z", 0.0f);
- _head.setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f));
+ getHead()->setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f));
_leanScale = loadSetting(settings, "leanScale", 0.05f);
_targetScale = loadSetting(settings, "scale", 1.0f);
@@ -619,6 +638,7 @@ void MyAvatar::loadData(QSettings* settings) {
setFaceModelURL(settings->value("faceModelURL").toUrl());
setSkeletonModelURL(settings->value("skeletonModelURL").toUrl());
+ setDisplayName(settings->value("displayName").toString());
settings->endGroup();
}
@@ -645,9 +665,9 @@ void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
setOrientation(orientation);
// then vertically
- float oldPitch = _head.getPitch();
- _head.setPitch(oldPitch + deltaY * -ANGULAR_SCALE);
- rotation = glm::angleAxis(_head.getPitch() - oldPitch, orientation * IDENTITY_RIGHT);
+ float oldPitch = getHead()->getPitch();
+ getHead()->setPitch(oldPitch + deltaY * -ANGULAR_SCALE);
+ rotation = glm::angleAxis(getHead()->getPitch() - oldPitch, orientation * IDENTITY_RIGHT);
setPosition(position + rotation * (getPosition() - position));
}
@@ -667,11 +687,13 @@ void MyAvatar::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
float distance;
if (avatar->findRayIntersection(mouseOrigin, mouseDirection, distance)) {
// rescale to compensate for head embiggening
- eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) *
- (avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot();
+ eyePosition = (avatar->getHead()->calculateAverageEyePosition() - avatar->getHead()->getScalePivot()) *
+ (avatar->getScale() / avatar->getHead()->getScale()) + avatar->getHead()->getScalePivot();
_lookAtTargetAvatar = avatarPointer;
return;
+ } else {
}
+
}
_lookAtTargetAvatar.clear();
}
@@ -682,7 +704,8 @@ void MyAvatar::clearLookAtTargetAvatar() {
}
float MyAvatar::getAbsoluteHeadYaw() const {
- return glm::yaw(_head.getOrientation());
+ const Head* head = static_cast(_headData);
+ return glm::yaw(head->getOrientation());
}
glm::vec3 MyAvatar::getUprightHeadPosition() const {
@@ -696,17 +719,17 @@ void MyAvatar::renderBody(bool forceRenderHead) {
// Render head so long as the camera isn't inside it
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.10f;
Camera* myCamera = Application::getInstance()->getCamera();
- if (forceRenderHead || (glm::length(myCamera->getPosition() - _head.calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE)) {
- _head.render(1.0f);
+ if (forceRenderHead || (glm::length(myCamera->getPosition() - getHead()->calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE)) {
+ getHead()->render(1.0f);
}
- _hand.render(true);
+ getHand()->render(true);
}
void MyAvatar::updateThrust(float deltaTime) {
//
// Gather thrust information from keyboard and sensors to apply to avatar motion
//
- glm::quat orientation = getHead().getCameraOrientation();
+ glm::quat orientation = getHead()->getCameraOrientation();
glm::vec3 front = orientation * IDENTITY_FRONT;
glm::vec3 right = orientation * IDENTITY_RIGHT;
glm::vec3 up = orientation * IDENTITY_UP;
@@ -727,7 +750,7 @@ void MyAvatar::updateThrust(float deltaTime) {
_thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up;
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_MAG * deltaTime;
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_MAG * deltaTime;
- _head.setPitch(_head.getPitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_MAG * deltaTime);
+ getHead()->setPitch(getHead()->getPitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_MAG * deltaTime);
// If thrust keys are being held down, slowly increase thrust to allow reaching great speeds
if (_driveKeys[FWD] || _driveKeys[BACK] || _driveKeys[RIGHT] || _driveKeys[LEFT] || _driveKeys[UP] || _driveKeys[DOWN]) {
@@ -835,7 +858,7 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
float pelvisFloatingHeight = getPelvisFloatingHeight();
if (Application::getInstance()->getEnvironment()->findCapsulePenetration(
_position - up * (pelvisFloatingHeight - radius),
- _position + up * (getHeight() - pelvisFloatingHeight + radius), radius, penetration)) {
+ _position + up * (getSkeletonHeight() - pelvisFloatingHeight + radius), radius, penetration)) {
_lastCollisionPosition = _position;
updateCollisionSound(penetration, deltaTime, ENVIRONMENT_COLLISION_FREQUENCY);
applyHardCollision(penetration, ENVIRONMENT_SURFACE_ELASTICITY, ENVIRONMENT_SURFACE_DAMPING);
@@ -850,7 +873,7 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
float pelvisFloatingHeight = getPelvisFloatingHeight();
if (Application::getInstance()->getVoxels()->findCapsulePenetration(
_position - glm::vec3(0.0f, pelvisFloatingHeight - radius, 0.0f),
- _position + glm::vec3(0.0f, getHeight() - pelvisFloatingHeight + radius, 0.0f), radius, penetration)) {
+ _position + glm::vec3(0.0f, getSkeletonHeight() - pelvisFloatingHeight + radius, 0.0f), radius, penetration)) {
_lastCollisionPosition = _position;
updateCollisionSound(penetration, deltaTime, VOXEL_COLLISION_FREQUENCY);
applyHardCollision(penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
@@ -992,10 +1015,10 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
}
// collide our hands against them
- _hand.collideAgainstAvatar(avatar, true);
+ getHand()->collideAgainstAvatar(avatar, true);
// collide their hands against us
- avatar->getHand().collideAgainstAvatar(this, false);
+ avatar->getHand()->collideAgainstAvatar(this, false);
}
}
}
@@ -1110,7 +1133,7 @@ void MyAvatar::updateChatCircle(float deltaTime) {
void MyAvatar::setGravity(glm::vec3 gravity) {
_gravity = gravity;
- _head.setGravity(_gravity);
+ getHead()->setGravity(_gravity);
// use the gravity to determine the new world up direction, if possible
float gravityLength = glm::length(gravity);
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index d8cb4c05aa..1bc5de204b 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -86,7 +86,6 @@ public:
public slots:
void goHome();
- void setWantCollisionsOn(bool wantCollisionsOn) { _isCollisionsOn = wantCollisionsOn; }
void increaseSize();
void decreaseSize();
void resetSize();
@@ -110,7 +109,6 @@ private:
float _elapsedTimeSinceCollision;
glm::vec3 _lastCollisionPosition;
bool _speedBrakes;
- bool _isCollisionsOn;
bool _isThrustOn;
float _thrustMultiplier;
glm::vec3 _moveTarget;
diff --git a/interface/src/avatar/Profile.h b/interface/src/avatar/Profile.h
index c32d89cfea..c1f3f3c4de 100644
--- a/interface/src/avatar/Profile.h
+++ b/interface/src/avatar/Profile.h
@@ -27,7 +27,7 @@ public:
QString getUserString() const;
const QString& getUsername() const { return _username; }
-
+
void setUUID(const QUuid& uuid) { _uuid = uuid; }
const QUuid& getUUID() { return _uuid; }
@@ -45,6 +45,8 @@ public:
private:
void setUsername(const QString& username);
+
+ void setDisplayName(const QString& displaName);
QString _username;
QUuid _uuid;
diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp
index 9bf2e0f727..0396b80e58 100644
--- a/interface/src/avatar/SkeletonModel.cpp
+++ b/interface/src/avatar/SkeletonModel.cpp
@@ -8,10 +8,9 @@
#include
-#include
-
#include "Application.h"
#include "Avatar.h"
+#include "Hand.h"
#include "Menu.h"
#include "SkeletonModel.h"
@@ -33,8 +32,8 @@ void SkeletonModel::simulate(float deltaTime) {
// find the left and rightmost active Leap palms
int leftPalmIndex, rightPalmIndex;
- HandData& hand = _owningAvatar->getHand();
- hand.getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
+ Hand* hand = _owningAvatar->getHand();
+ hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
const float HAND_RESTORATION_PERIOD = 1.f; // seconds
float handRestorePercent = glm::clamp(deltaTime / HAND_RESTORATION_PERIOD, 0.f, 1.f);
@@ -52,14 +51,14 @@ void SkeletonModel::simulate(float deltaTime) {
} else if (leftPalmIndex == rightPalmIndex) {
// right hand only
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
- hand.getPalms()[leftPalmIndex]);
+ hand->getPalms()[leftPalmIndex]);
restoreLeftHandPosition(handRestorePercent);
} else {
applyPalmData(geometry.leftHandJointIndex, geometry.leftFingerJointIndices, geometry.leftFingertipJointIndices,
- hand.getPalms()[leftPalmIndex]);
+ hand->getPalms()[leftPalmIndex]);
applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices,
- hand.getPalms()[rightPalmIndex]);
+ hand->getPalms()[rightPalmIndex]);
}
}
@@ -182,8 +181,8 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const
glm::mat3 axes = glm::mat3_cast(_rotation);
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.transform * glm::translate(state.translation) *
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)));
- state.rotation = glm::angleAxis(-_owningAvatar->getHead().getLeanSideways(), glm::normalize(inverse * axes[2])) *
- glm::angleAxis(-_owningAvatar->getHead().getLeanForward(), glm::normalize(inverse * axes[0])) * joint.rotation;
+ state.rotation = glm::angleAxis(-_owningAvatar->getHead()->getLeanSideways(), glm::normalize(inverse * axes[2])) *
+ glm::angleAxis(-_owningAvatar->getHead()->getLeanForward(), glm::normalize(inverse * axes[0])) * joint.rotation;
}
void SkeletonModel::stretchArm(int jointIndex, const glm::vec3& position) {
diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp
index 79feb5eb3f..9ff34e698e 100644
--- a/interface/src/devices/SixenseManager.cpp
+++ b/interface/src/devices/SixenseManager.cpp
@@ -45,7 +45,7 @@ void SixenseManager::update(float deltaTime) {
return;
}
MyAvatar* avatar = Application::getInstance()->getAvatar();
- Hand& hand = avatar->getHand();
+ Hand* hand = avatar->getHand();
int maxControllers = sixenseGetMaxControllers();
for (int i = 0; i < maxControllers; i++) {
@@ -60,16 +60,16 @@ void SixenseManager::update(float deltaTime) {
// Either find a palm matching the sixense controller, or make a new one
PalmData* palm;
bool foundHand = false;
- for (int j = 0; j < hand.getNumPalms(); j++) {
- if (hand.getPalms()[j].getSixenseID() == data.controller_index) {
- palm = &hand.getPalms()[j];
+ for (int j = 0; j < hand->getNumPalms(); j++) {
+ if (hand->getPalms()[j].getSixenseID() == data.controller_index) {
+ palm = &(hand->getPalms()[j]);
foundHand = true;
}
}
if (!foundHand) {
- PalmData newPalm(&hand);
- hand.getPalms().push_back(newPalm);
- palm = &hand.getPalms()[hand.getNumPalms() - 1];
+ PalmData newPalm(hand);
+ hand->getPalms().push_back(newPalm);
+ palm = &(hand->getPalms()[hand->getNumPalms() - 1]);
palm->setSixenseID(data.controller_index);
printf("Found new Sixense controller, ID %i\n", data.controller_index);
}
@@ -107,7 +107,7 @@ void SixenseManager::update(float deltaTime) {
}
// initialize the "finger" based on the direction
- FingerData finger(palm, &hand);
+ FingerData finger(palm, hand);
finger.setActive(true);
finger.setRawRootPosition(position);
const float FINGER_LENGTH = 300.0f; // Millimeters
@@ -130,7 +130,7 @@ void SixenseManager::update(float deltaTime) {
// if the controllers haven't been moved in a while, disable
const int MOVEMENT_DISABLE_DURATION = 30 * 1000 * 1000;
if (usecTimestampNow() - _lastMovement > MOVEMENT_DISABLE_DURATION) {
- for (vector::iterator it = hand.getPalms().begin(); it != hand.getPalms().end(); it++) {
+ for (vector::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) {
it->setActive(false);
}
}
diff --git a/interface/src/devices/Visage.cpp b/interface/src/devices/Visage.cpp
new file mode 100644
index 0000000000..c3dfeab6b2
--- /dev/null
+++ b/interface/src/devices/Visage.cpp
@@ -0,0 +1,166 @@
+//
+// Visage.cpp
+// interface
+//
+// Created by Andrzej Kapolka on 2/11/14.
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#include
+
+#include
+
+#ifdef HAVE_VISAGE
+#include
+#endif
+
+#include "Visage.h"
+#include "renderer/FBXReader.h"
+
+namespace VisageSDK {
+#ifdef WIN32
+ void __declspec(dllimport) initializeLicenseManager(char* licenseKeyFileName);
+#else
+ void initializeLicenseManager(char* licenseKeyFileName);
+#endif
+}
+
+using namespace VisageSDK;
+
+const glm::vec3 DEFAULT_HEAD_ORIGIN(0.0f, 0.0f, 0.7f);
+
+Visage::Visage() :
+#ifdef HAVE_VISAGE
+ _leftInnerBrowIndex(0),
+ _rightInnerBrowIndex(0),
+#endif
+ _active(false),
+ _headOrigin(DEFAULT_HEAD_ORIGIN),
+ _estimatedEyePitch(0.0f),
+ _estimatedEyeYaw(0.0f) {
+
+#ifdef HAVE_VISAGE
+ switchToResourcesParentIfRequired();
+ QByteArray licensePath = "resources/visage/license.vlc";
+ initializeLicenseManager(licensePath.data());
+ _tracker = new VisageTracker2("resources/visage/Facial Features Tracker - Asymmetric.cfg");
+ if (_tracker->trackFromCam()) {
+ _data = new FaceData();
+
+ } else {
+ delete _tracker;
+ _tracker = NULL;
+ }
+#endif
+}
+
+Visage::~Visage() {
+#ifdef HAVE_VISAGE
+ if (_tracker) {
+ _tracker->stop();
+ delete _tracker;
+ delete _data;
+ }
+#endif
+}
+
+#ifdef HAVE_VISAGE
+static int leftEyeBlinkIndex = 0;
+static int rightEyeBlinkIndex = 1;
+static int centerBrowIndex = 16;
+
+static QHash createBlendshapeIndices() {
+ QHash blendshapeMap;
+ blendshapeMap.insert("Sneer", "au_nose_wrinkler");
+ blendshapeMap.insert("JawFwd", "au_jaw_z_push");
+ blendshapeMap.insert("JawLeft", "au_jaw_x_push");
+ blendshapeMap.insert("JawOpen", "au_jaw_drop");
+ blendshapeMap.insert("LipsLowerDown", "au_lower_lip_drop");
+ blendshapeMap.insert("LipsUpperUp", "au_upper_lip_raiser");
+ blendshapeMap.insert("LipsStretch_R", "au_lip_stretcher_left");
+ blendshapeMap.insert("BrowsU_R", "au_left_outer_brow_raiser");
+ blendshapeMap.insert("BrowsD_R", "au_left_brow_lowerer");
+ blendshapeMap.insert("LipsStretch_L", "au_lip_stretcher_right");
+ blendshapeMap.insert("BrowsU_L", "au_right_outer_brow_raiser");
+ blendshapeMap.insert("BrowsD_L", "au_right_brow_lowerer");
+
+ QHash blendshapeIndices;
+ for (int i = 0;; i++) {
+ QByteArray blendshape = FACESHIFT_BLENDSHAPES[i];
+ if (blendshape.isEmpty()) {
+ break;
+ }
+ if (blendshape == "EyeBlink_L") {
+ leftEyeBlinkIndex = i;
+
+ } else if (blendshape == "EyeBlink_R") {
+ rightEyeBlinkIndex = i;
+
+ } else if (blendshape == "BrowsU_C") {
+ centerBrowIndex = i;
+ }
+ QByteArray mapping = blendshapeMap.value(blendshape);
+ if (!mapping.isEmpty()) {
+ blendshapeIndices.insert(mapping, i + 1);
+ }
+ }
+
+ return blendshapeIndices;
+}
+
+static const QHash& getBlendshapeIndices() {
+ static QHash blendshapeIndices = createBlendshapeIndices();
+ return blendshapeIndices;
+}
+#endif
+
+const float TRANSLATION_SCALE = 20.0f;
+
+void Visage::update() {
+#ifdef HAVE_VISAGE
+ _active = (_tracker && _tracker->getTrackingData(_data) == TRACK_STAT_OK);
+ if (!_active) {
+ return;
+ }
+ _headRotation = glm::quat(glm::vec3(-_data->faceRotation[0], -_data->faceRotation[1], _data->faceRotation[2]));
+ _headTranslation = (glm::vec3(_data->faceTranslation[0], _data->faceTranslation[1], _data->faceTranslation[2]) -
+ _headOrigin) * TRANSLATION_SCALE;
+ _estimatedEyePitch = glm::degrees(-_data->gazeDirection[1]);
+ _estimatedEyeYaw = glm::degrees(-_data->gazeDirection[0]);
+
+ if (_blendshapeIndices.isEmpty()) {
+ _blendshapeIndices.resize(_data->actionUnitCount);
+ int maxIndex = -1;
+ for (int i = 0; i < _data->actionUnitCount; i++) {
+ QByteArray name = _data->actionUnitsNames[i];
+ if (name == "au_left_inner_brow_raiser") {
+ _leftInnerBrowIndex = i;
+ } else if (name == "au_right_inner_brow_raiser") {
+ _rightInnerBrowIndex = i;
+ }
+ int index = getBlendshapeIndices().value(name) - 1;
+ maxIndex = qMax(maxIndex, _blendshapeIndices[i] = index);
+ }
+ _blendshapeCoefficients.resize(maxIndex + 1);
+ }
+
+ qFill(_blendshapeCoefficients.begin(), _blendshapeCoefficients.end(), 0.0f);
+ for (int i = 0; i < _data->actionUnitCount; i++) {
+ if (!_data->actionUnitsUsed[i]) {
+ continue;
+ }
+ int index = _blendshapeIndices.at(i);
+ if (index != -1) {
+ _blendshapeCoefficients[index] = _data->actionUnits[i];
+ }
+ }
+ _blendshapeCoefficients[leftEyeBlinkIndex] = 1.0f - _data->eyeClosure[1];
+ _blendshapeCoefficients[rightEyeBlinkIndex] = 1.0f - _data->eyeClosure[0];
+ _blendshapeCoefficients[centerBrowIndex] = (_data->actionUnits[_leftInnerBrowIndex] +
+ _data->actionUnits[_rightInnerBrowIndex]) * 0.5f;
+#endif
+}
+
+void Visage::reset() {
+ _headOrigin += _headTranslation / TRANSLATION_SCALE;
+}
diff --git a/interface/src/devices/Visage.h b/interface/src/devices/Visage.h
new file mode 100644
index 0000000000..a5c826d1bf
--- /dev/null
+++ b/interface/src/devices/Visage.h
@@ -0,0 +1,66 @@
+//
+// Visage.h
+// interface
+//
+// Created by Andrzej Kapolka on 2/11/14.
+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#ifndef __interface__Visage__
+#define __interface__Visage__
+
+#include
+
+#include
+
+#include
+#include
+
+namespace VisageSDK {
+ class VisageTracker2;
+ struct FaceData;
+}
+
+/// Handles input from the Visage webcam feature tracking software.
+class Visage {
+public:
+
+ Visage();
+ ~Visage();
+
+ bool isActive() const { return _active; }
+
+ const glm::quat& getHeadRotation() const { return _headRotation; }
+ const glm::vec3& getHeadTranslation() const { return _headTranslation; }
+
+ float getEstimatedEyePitch() const { return _estimatedEyePitch; }
+ float getEstimatedEyeYaw() const { return _estimatedEyeYaw; }
+
+ const std::vector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
+
+ void update();
+ void reset();
+
+private:
+
+#ifdef HAVE_VISAGE
+ VisageSDK::VisageTracker2* _tracker;
+ VisageSDK::FaceData* _data;
+ int _leftInnerBrowIndex;
+ int _rightInnerBrowIndex;
+ QVector _blendshapeIndices;
+#endif
+
+ bool _active;
+ glm::quat _headRotation;
+ glm::vec3 _headTranslation;
+
+ glm::vec3 _headOrigin;
+
+ float _estimatedEyePitch;
+ float _estimatedEyeYaw;
+
+ std::vector _blendshapeCoefficients;
+};
+
+#endif /* defined(__interface__Visage__) */
diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp
index 35512d88da..8b881940ca 100644
--- a/interface/src/renderer/FBXReader.cpp
+++ b/interface/src/renderer/FBXReader.cpp
@@ -1405,6 +1405,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
cluster.jointIndex = 0;
}
extracted.mesh.clusters.append(cluster);
+ // BUG: joints that fall into this context do not get their bindTransform and
+ // inverseBindRotation data members properly set. This causes bad boneRadius
+ // and boneLength calculations for collision proxies. Affected joints are usually:
+ // hair, teeth, tongue. I tried to figure out how to fix this but was going
+ // crosseyed trying to understand FBX so I gave up for the time being -- Andrew.
}
// whether we're skinned depends on how many clusters are attached
diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h
index 8efb23c98c..b89d0954b4 100644
--- a/interface/src/renderer/FBXReader.h
+++ b/interface/src/renderer/FBXReader.h
@@ -21,6 +21,9 @@ class FBXNode;
typedef QList FBXNodeList;
+/// The names of the blendshapes expected by Faceshift, terminated with an empty string.
+extern const char* FACESHIFT_BLENDSHAPES[];
+
/// A node within an FBX document.
class FBXNode {
public:
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index 48e1d0f70c..f1916db4d1 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -421,6 +421,7 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) {
_dilatedTextures.clear();
_lodHysteresis = NetworkGeometry::NO_HYSTERESIS;
+ // we retain a reference to the base geometry so that its reference count doesn't fall to zero
_baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
}
diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h
index 1d1cdc22a7..28189d0379 100644
--- a/interface/src/renderer/Model.h
+++ b/interface/src/renderer/Model.h
@@ -227,7 +227,7 @@ private:
void deleteGeometry();
void renderMeshes(float alpha, bool translucent);
- QSharedPointer _baseGeometry;
+ QSharedPointer _baseGeometry; ///< reference required to prevent collection of base
float _lodHysteresis;
float _pupilDilation;
diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index e16b0c570d..6ffaf23564 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -64,7 +64,7 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Profile* profile, Avatar* avatar)
QImage shot = widget->grabFrameBuffer();
glm::vec3 location = avatar->getPosition();
- glm::quat orientation = avatar->getHead().getOrientation();
+ glm::quat orientation = avatar->getHead()->getOrientation();
// add metadata
shot.setText(LOCATION_X, QString::number(location.x));
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index c375f8b82d..da176f3fd9 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -35,7 +35,10 @@ AvatarData::AvatarData() :
_keyState(NO_KEY_DOWN),
_isChatCirclingEnabled(false),
_headData(NULL),
- _handData(NULL)
+ _handData(NULL),
+ _displayNameBoundingRect(),
+ _displayNameTargetAlpha(0.0f),
+ _displayNameAlpha(0.0f)
{
}
@@ -63,10 +66,6 @@ QByteArray AvatarData::toByteArray() {
if (!_headData) {
_headData = new HeadData(this);
}
- // lazily allocate memory for HandData in case we're not an Avatar instance
- if (!_handData) {
- _handData = new HandData(this);
- }
QByteArray avatarDataByteArray;
avatarDataByteArray.resize(MAX_PACKET_SIZE);
@@ -152,8 +151,8 @@ QByteArray AvatarData::toByteArray() {
// pupil dilation
destinationBuffer += packFloatToByte(destinationBuffer, _headData->_pupilDilation, 1.0f);
- // leap hand data
- destinationBuffer += _handData->encodeRemoteData(destinationBuffer);
+ // hand data
+ destinationBuffer += HandData::encodeData(_handData, destinationBuffer);
return avatarDataByteArray.left(destinationBuffer - startPosition);
}
@@ -259,7 +258,7 @@ int AvatarData::parseData(const QByteArray& packet) {
// pupil dilation
sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f);
- // leap hand data
+ // hand data
if (sourceBuffer - startPosition < packet.size()) {
// check passed, bytes match
sourceBuffer += _handData->decodeRemoteData(packet.mid(sourceBuffer - startPosition));
@@ -274,7 +273,8 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) {
QUuid avatarUUID;
QUrl faceModelURL, skeletonModelURL;
- packetStream >> avatarUUID >> faceModelURL >> skeletonModelURL;
+ QString displayName;
+ packetStream >> avatarUUID >> faceModelURL >> skeletonModelURL >> displayName;
bool hasIdentityChanged = false;
@@ -287,15 +287,20 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) {
setSkeletonModelURL(skeletonModelURL);
hasIdentityChanged = true;
}
-
+
+ if (displayName != _displayName) {
+ setDisplayName(displayName);
+ hasIdentityChanged = true;
+ }
+
return hasIdentityChanged;
}
QByteArray AvatarData::identityByteArray() {
QByteArray identityData;
QDataStream identityStream(&identityData, QIODevice::Append);
-
- identityStream << QUuid() << _faceModelURL << _skeletonModelURL;
+
+ identityStream << QUuid() << _faceModelURL << _skeletonModelURL << _displayName;
return identityData;
}
@@ -312,6 +317,13 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
qDebug() << "Changing skeleton model for avatar to" << _skeletonModelURL.toString();
}
+void AvatarData::setDisplayName(const QString& displayName) {
+ _displayName = displayName;
+
+ qDebug() << "Changing display name for avatar to" << displayName;
+}
+
+
void AvatarData::setClampedTargetScale(float targetScale) {
targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 079c14a513..1345a69f8e 100755
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -33,6 +33,7 @@ typedef unsigned long long quint64;
#include
#include
#include
+#include
#include
#include
@@ -139,9 +140,6 @@ public:
const HeadData* getHeadData() const { return _headData; }
const HandData* getHandData() const { return _handData; }
- void setHeadData(HeadData* headData) { _headData = headData; }
- void setHandData(HandData* handData) { _handData = handData; }
-
virtual const glm::vec3& getVelocity() const { return vec3Zero; }
virtual bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
@@ -153,11 +151,13 @@ public:
const QUrl& getFaceModelURL() const { return _faceModelURL; }
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
+ const QString& getDisplayName() const { return _displayName; }
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
-
- virtual float getBoundingRadius() const { return 1.f; }
+ virtual void setDisplayName(const QString& displayName);
+ virtual float getBoundingRadius() const { return 1.f; }
+
protected:
glm::vec3 _position;
glm::vec3 _handPosition;
@@ -186,6 +186,12 @@ protected:
QUrl _faceModelURL;
QUrl _skeletonModelURL;
+ QString _displayName;
+
+ QRect _displayNameBoundingRect;
+ float _displayNameTargetAlpha;
+ float _displayNameAlpha;
+
private:
// privatize the copy constructor and assignment operator so they cannot be called
AvatarData(const AvatarData&);
diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp
index 5a923eea93..e4bb187f28 100644
--- a/libraries/avatars/src/HandData.cpp
+++ b/libraries/avatars/src/HandData.cpp
@@ -113,17 +113,30 @@ _owningHandData(owningHandData)
setTrailLength(standardTrailLength);
}
+// static
+int HandData::encodeData(HandData* hand, unsigned char* destinationBuffer) {
+ if (hand) {
+ return hand->encodeRemoteData(destinationBuffer);
+ }
+ // else encode empty data:
+ // One byte for zero hands
+ // One byte for error checking.
+ *destinationBuffer = 0;
+ *(destinationBuffer + 1) = 1;
+ return 2;
+}
+
int HandData::encodeRemoteData(unsigned char* destinationBuffer) {
const unsigned char* startPosition = destinationBuffer;
- unsigned int numHands = 0;
+ unsigned int numPalms = 0;
for (unsigned int handIndex = 0; handIndex < getNumPalms(); ++handIndex) {
PalmData& palm = getPalms()[handIndex];
if (palm.isActive()) {
- numHands++;
+ numPalms++;
}
}
- *destinationBuffer++ = numHands;
+ *destinationBuffer++ = numPalms;
for (unsigned int handIndex = 0; handIndex < getNumPalms(); ++handIndex) {
PalmData& palm = getPalms()[handIndex];
@@ -162,9 +175,9 @@ int HandData::encodeRemoteData(unsigned char* destinationBuffer) {
int HandData::decodeRemoteData(const QByteArray& dataByteArray) {
const unsigned char* startPosition;
const unsigned char* sourceBuffer = startPosition = reinterpret_cast(dataByteArray.data());
- unsigned int numHands = *sourceBuffer++;
+ unsigned int numPalms = *sourceBuffer++;
- for (unsigned int handIndex = 0; handIndex < numHands; ++handIndex) {
+ for (unsigned int handIndex = 0; handIndex < numPalms; ++handIndex) {
if (handIndex >= getNumPalms())
addNewPalm();
PalmData& palm = getPalms()[handIndex];
@@ -203,7 +216,7 @@ int HandData::decodeRemoteData(const QByteArray& dataByteArray) {
}
}
// Turn off any hands which weren't used.
- for (unsigned int handIndex = numHands; handIndex < getNumPalms(); ++handIndex) {
+ for (unsigned int handIndex = numPalms; handIndex < getNumPalms(); ++handIndex) {
PalmData& palm = getPalms()[handIndex];
palm.setActive(false);
}
diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h
index 550c62e829..5f7a49e0a2 100755
--- a/libraries/avatars/src/HandData.h
+++ b/libraries/avatars/src/HandData.h
@@ -71,6 +71,8 @@ public:
void setFingerTrailLength(unsigned int length);
void updateFingerTrails();
+ static int encodeData(HandData* hand, unsigned char* destinationBuffer);
+
// Use these for sending and receiving hand data
int encodeRemoteData(unsigned char* destinationBuffer);
int decodeRemoteData(const QByteArray& dataByteArray);
diff --git a/libraries/script-engine/src/AbstractControllerScriptingInterface.h b/libraries/script-engine/src/AbstractControllerScriptingInterface.h
index d9878d0b71..1878edd4d6 100644
--- a/libraries/script-engine/src/AbstractControllerScriptingInterface.h
+++ b/libraries/script-engine/src/AbstractControllerScriptingInterface.h
@@ -52,6 +52,10 @@ public slots:
virtual void captureWheelEvents() = 0;
virtual void releaseWheelEvents() = 0;
+ virtual void captureJoystick(int joystickIndex) = 0;
+ virtual void releaseJoystick(int joystickIndex) = 0;
+
+ virtual glm::vec2 getViewportDimensions() const = 0;
signals:
void keyPressEvent(const KeyEvent& event);
diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp
index 963912fd34..c8451d84a7 100644
--- a/libraries/script-engine/src/EventTypes.cpp
+++ b/libraries/script-engine/src/EventTypes.cpp
@@ -102,6 +102,18 @@ KeyEvent::KeyEvent(const QKeyEvent& event) {
text = "END";
} else if (key == Qt::Key_Help) {
text = "HELP";
+ } else if (key == Qt::Key_CapsLock) {
+ text = "CAPS LOCK";
+ } else if (key >= Qt::Key_A && key <= Qt::Key_Z && (isMeta || isControl || isAlt)) {
+ // this little bit of hackery will fix the text character keys like a-z in cases of control/alt/meta where
+ // qt doesn't always give you the key characters and will sometimes give you crazy non-printable characters
+ const int lowerCaseAdjust = 0x20;
+ QString unicode;
+ if (isShifted) {
+ text = QString(QChar(key));
+ } else {
+ text = QString(QChar(key + lowerCaseAdjust));
+ }
}
}
@@ -208,6 +220,8 @@ void keyEventFromScriptValue(const QScriptValue& object, KeyEvent& event) {
event.key = Qt::Key_End;
} else if (event.text.toUpper() == "HELP") {
event.key = Qt::Key_Help;
+ } else if (event.text.toUpper() == "CAPS LOCK") {
+ event.key = Qt::Key_CapsLock;
} else {
event.key = event.text.at(0).unicode();
}
@@ -258,10 +272,17 @@ MouseEvent::MouseEvent() :
};
-MouseEvent::MouseEvent(const QMouseEvent& event) {
- x = event.x();
- y = event.y();
-
+MouseEvent::MouseEvent(const QMouseEvent& event) :
+ x(event.x()),
+ y(event.y()),
+ isLeftButton(event.buttons().testFlag(Qt::LeftButton)),
+ isRightButton(event.buttons().testFlag(Qt::RightButton)),
+ isMiddleButton(event.buttons().testFlag(Qt::MiddleButton)),
+ isShifted(event.modifiers().testFlag(Qt::ShiftModifier)),
+ isControl(event.modifiers().testFlag(Qt::ControlModifier)),
+ isMeta(event.modifiers().testFlag(Qt::MetaModifier)),
+ isAlt(event.modifiers().testFlag(Qt::AltModifier))
+{
// single button that caused the event
switch (event.button()) {
case Qt::LeftButton:
@@ -280,16 +301,6 @@ MouseEvent::MouseEvent(const QMouseEvent& event) {
button = "NONE";
break;
}
- // button pressed state
- isLeftButton = isLeftButton || (event.buttons().testFlag(Qt::LeftButton));
- isRightButton = isRightButton || (event.buttons().testFlag(Qt::RightButton));
- isMiddleButton = isMiddleButton || (event.buttons().testFlag(Qt::MiddleButton));
-
- // keyboard modifiers
- isShifted = event.modifiers().testFlag(Qt::ShiftModifier);
- isMeta = event.modifiers().testFlag(Qt::MetaModifier);
- isControl = event.modifiers().testFlag(Qt::ControlModifier);
- isAlt = event.modifiers().testFlag(Qt::AltModifier);
}
QScriptValue mouseEventToScriptValue(QScriptEngine* engine, const MouseEvent& event) {
diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp
index 8dd3857198..bbb7750a2f 100644
--- a/libraries/shared/src/NodeList.cpp
+++ b/libraries/shared/src/NodeList.cpp
@@ -229,6 +229,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
// send back a reply
SharedNodePointer matchingNode = sendingNodeForPacket(packet);
if (matchingNode) {
+ matchingNode->setLastHeardMicrostamp(usecTimestampNow());
QByteArray replyPacket = constructPingReplyPacket(packet);
writeDatagram(replyPacket, matchingNode, senderSockAddr);
}
@@ -239,6 +240,8 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
SharedNodePointer sendingNode = sendingNodeForPacket(packet);
if (sendingNode) {
+ sendingNode->setLastHeardMicrostamp(usecTimestampNow());
+
// activate the appropriate socket for this node, if not yet updated
activateSocketFromNodeCommunication(packet, sendingNode);
diff --git a/libraries/voxels/src/VoxelEditPacketSender.cpp b/libraries/voxels/src/VoxelEditPacketSender.cpp
index a6d3668207..90884f19f4 100644
--- a/libraries/voxels/src/VoxelEditPacketSender.cpp
+++ b/libraries/voxels/src/VoxelEditPacketSender.cpp
@@ -22,7 +22,7 @@
/// PacketTypeVoxelSet, PacketTypeVoxelSetDestructive, or PacketTypeVoxelErase. The buffer is returned to caller becomes
/// responsibility of caller and MUST be deleted by caller.
bool createVoxelEditMessage(PacketType command, short int sequence,
- int voxelCount, VoxelDetail* voxelDetails, unsigned char*& bufferOut, int& sizeOut) {
+ int voxelCount, const VoxelDetail* voxelDetails, unsigned char*& bufferOut, int& sizeOut) {
bool success = true; // assume the best
int messageSize = MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE; // just a guess for now
@@ -102,7 +102,7 @@ bool encodeVoxelEditMessageDetails(PacketType, int voxelCount, VoxelDetail* voxe
return success;
}
-void VoxelEditPacketSender::sendVoxelEditMessage(PacketType type, VoxelDetail& detail) {
+void VoxelEditPacketSender::sendVoxelEditMessage(PacketType type, const VoxelDetail& detail) {
// allows app to disable sending if for example voxels have been disabled
if (!_shouldSend) {
return; // bail early
diff --git a/libraries/voxels/src/VoxelEditPacketSender.h b/libraries/voxels/src/VoxelEditPacketSender.h
index 90085635b0..4a1aa87a1c 100644
--- a/libraries/voxels/src/VoxelEditPacketSender.h
+++ b/libraries/voxels/src/VoxelEditPacketSender.h
@@ -19,7 +19,7 @@ class VoxelEditPacketSender : public OctreeEditPacketSender {
Q_OBJECT
public:
/// Send voxel edit message immediately
- void sendVoxelEditMessage(PacketType type, VoxelDetail& detail);
+ void sendVoxelEditMessage(PacketType type, const VoxelDetail& detail);
/// Queues a single voxel edit message. Will potentially send a pending multi-command packet. Determines which voxel-server
/// node or nodes the packet should be sent to. Can be called even before voxel servers are known, in which case up to