From 68584c654b0612f1c7ff47c6326c61b973bfb2ce Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Wed, 12 Feb 2014 02:12:52 +0100
Subject: [PATCH 01/14] Allow drag-and-drop of snapshots into Interface

---
 interface/src/Application.cpp     |  34 +++++++++-
 interface/src/Application.h       |   3 +-
 interface/src/GLCanvas.cpp        |  19 +++++-
 interface/src/GLCanvas.h          |   3 +
 interface/src/Menu.cpp            | 107 ++++++++++++++----------------
 interface/src/Menu.h              |   4 +-
 interface/src/ui/Snapshot.cpp     |  63 ++++++++++++++++--
 interface/src/ui/Snapshot.h       |  27 ++++++--
 libraries/shared/src/NodeList.cpp |   3 +-
 9 files changed, 190 insertions(+), 73 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 160d6b7c2c..0bb5d03eaa 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -48,6 +48,8 @@
 #include <QXmlStreamReader>
 #include <QXmlStreamAttributes>
 #include <QMediaPlayer>
+#include <QMimeData> 
+#include <QMessageBox>
 
 #include <AudioInjector.h>
 #include <Logging.h>
@@ -197,7 +199,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
     connect(audioThread, SIGNAL(started()), &_audio, SLOT(start()));
 
     audioThread->start();
-
+    
     connect(nodeList, SIGNAL(domainChanged(const QString&)), SLOT(domainChanged(const QString&)));
     connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded);
     connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled);
@@ -1378,6 +1380,32 @@ void Application::wheelEvent(QWheelEvent* event) {
     }
 }
 
+void Application::dropEvent(QDropEvent *event) {
+    QString snapshotPath;
+    const QMimeData *mimeData = event->mimeData();
+    foreach (QUrl url, mimeData->urls()) {
+        if (url.url().toLower().endsWith("jpg")) {
+            snapshotPath = url.url().remove("file://");
+            break;
+        }
+    }
+    
+    SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
+    if (snapshotData != NULL) {
+        if (!snapshotData->getDomain().isEmpty()) {
+            Menu::getInstance()->goToDomain(snapshotData->getDomain());
+        }
+        
+        _myAvatar->setPosition(snapshotData->getLocation());
+        _myAvatar->setOrientation(snapshotData->getOrientation());
+    } else {
+        QMessageBox msgBox;
+        msgBox.setText("No location details were found in this JPG, try dragging in an authentic Hifi snapshot.");
+        msgBox.setStandardButtons(QMessageBox::Ok);
+        msgBox.exec();
+    }
+}
+
 void Application::sendPingPackets() {
     QByteArray pingPacket = NodeList::getInstance()->constructPingPacket();
     controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer
@@ -3794,7 +3822,7 @@ void Application::updateWindowTitle(){
     
     QString title = QString() + _profile.getUsername() + " " + nodeList->getSessionUUID().toString()
         + " @ " + nodeList->getDomainHostname() + buildVersion;
-
+    
     qDebug("Application title set to: %s", title.toStdString().c_str());
     _window->setWindowTitle(title);
 }
@@ -4170,6 +4198,6 @@ void Application::takeSnapshot() {
     player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
     player->play();
 
-    Snapshot::saveSnapshot(_glWidget, _profile.getUsername(), _myAvatar->getPosition());
+    Snapshot::saveSnapshot(_glWidget, &_profile, _myAvatar);
 }
 
diff --git a/interface/src/Application.h b/interface/src/Application.h
index ff6e08758b..9508c0c9a5 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -127,7 +127,8 @@ public:
     void touchUpdateEvent(QTouchEvent* event);
 
     void wheelEvent(QWheelEvent* event);
-
+    void dropEvent(QDropEvent *event);
+    
     void makeVoxel(glm::vec3 position,
                    float scale,
                    unsigned char red,
diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp
index cd6f49383e..cfff3b8696 100644
--- a/interface/src/GLCanvas.cpp
+++ b/interface/src/GLCanvas.cpp
@@ -9,6 +9,8 @@
 #include "Application.h"
 
 #include "GLCanvas.h"
+#include <QMimeData>
+#include <QUrl>
 
 GLCanvas::GLCanvas() : QGLWidget(QGLFormat(QGL::NoDepthBuffer, QGL::NoStencilBuffer)) {
 }
@@ -16,6 +18,7 @@ GLCanvas::GLCanvas() : QGLWidget(QGLFormat(QGL::NoDepthBuffer, QGL::NoStencilBuf
 void GLCanvas::initializeGL() {
     Application::getInstance()->initializeGL();
     setAttribute(Qt::WA_AcceptTouchEvents);
+    setAcceptDrops(true);
 }
 
 void GLCanvas::paintGL() {
@@ -67,4 +70,18 @@ bool GLCanvas::event(QEvent* event) {
 
 void GLCanvas::wheelEvent(QWheelEvent* event) {
     Application::getInstance()->wheelEvent(event);
-}
\ No newline at end of file
+}
+
+void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
+    const QMimeData *mimeData = event->mimeData();
+    foreach (QUrl url, mimeData->urls()) {
+        if (url.url().toLower().endsWith("jpg")) {
+            event->acceptProposedAction();
+            break;
+        }
+    }
+}
+
+void GLCanvas::dropEvent(QDropEvent* event) {
+    Application::getInstance()->dropEvent(event);
+}
diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h
index ad181f4456..0f0cb5c7d0 100644
--- a/interface/src/GLCanvas.h
+++ b/interface/src/GLCanvas.h
@@ -31,6 +31,9 @@ protected:
     virtual bool event(QEvent* event);
     
     virtual void wheelEvent(QWheelEvent* event);
+
+    virtual void dragEnterEvent(QDragEnterEvent *event);
+    virtual void dropEvent(QDropEvent* event);
 };
 
 #endif /* defined(__hifi__GLCanvas__) */
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 7eb5807c6f..a73506243d 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -109,7 +109,7 @@ Menu::Menu() :
                                   MenuOption::GoToDomain,
                                   Qt::CTRL | Qt::Key_D,
                                    this,
-                                   SLOT(goToDomain()));
+                                   SLOT(goToDomainDialog()));
     addActionToQMenuAndActionHash(fileMenu,
                                   MenuOption::GoToLocation,
                                   Qt::CTRL | Qt::SHIFT | Qt::Key_L,
@@ -889,7 +889,18 @@ void Menu::editPreferences() {
     sendFakeEnterEvent();
 }
 
-void Menu::goToDomain() {
+void Menu::goToDomain(const QString newDomain) {
+    if (NodeList::getInstance()->getDomainHostname() != newDomain) {
+        
+        // send a node kill request, indicating to other clients that they should play the "disappeared" effect
+        Application::getInstance()->getAvatar()->sendKillAvatar();
+        
+        // give our nodeList the new domain-server hostname
+        NodeList::getInstance()->setDomainHostname(newDomain);
+    }
+}
+
+void Menu::goToDomainDialog() {
 
     QString currentDomainHostname = NodeList::getInstance()->getDomainHostname();
 
@@ -913,17 +924,46 @@ void Menu::goToDomain() {
             // the user input a new hostname, use that
             newHostname = domainDialog.textValue();
         }
-
-        // send a node kill request, indicating to other clients that they should play the "disappeared" effect
-        Application::getInstance()->getAvatar()->sendKillAvatar();
-
-        // give our nodeList the new domain-server hostname
-        NodeList::getInstance()->setDomainHostname(domainDialog.textValue());
+        
+        goToDomain(newHostname);
     }
 
     sendFakeEnterEvent();
 }
 
+bool Menu::goToDestination(QString destination) {
+    
+    QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
+    
+    const int NUMBER_OF_COORDINATE_ITEMS = 3;
+    const int X_ITEM = 0;
+    const int Y_ITEM = 1;
+    const int Z_ITEM = 2;
+    if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
+        
+        double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble();
+        double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble();
+        double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble();
+        
+        glm::vec3 newAvatarPos(x, y, z);
+        
+        MyAvatar* myAvatar = Application::getInstance()->getAvatar();
+        glm::vec3 avatarPos = myAvatar->getPosition();
+        if (newAvatarPos != avatarPos) {
+            // send a node kill request, indicating to other clients that they should play the "disappeared" effect
+            MyAvatar::sendKillAvatar();
+            
+            qDebug("Going To Location: %f, %f, %f...", x, y, z);
+            myAvatar->setPosition(newAvatarPos);
+        }
+        
+        return true;
+    }
+    
+    // no coordinates were parsed
+    return false;
+}
+
 void Menu::goTo() {
     
     QInputDialog gotoDialog(Application::getInstance()->getWindow());
@@ -939,31 +979,8 @@ void Menu::goTo() {
         
         destination = gotoDialog.textValue();
         
-        QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
-        
-        const int NUMBER_OF_COORDINATE_ITEMS = 3;
-        const int X_ITEM = 0;
-        const int Y_ITEM = 1;
-        const int Z_ITEM = 2;
-        if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
-            
-            double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble();
-            double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble();
-            double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble();
-            
-            glm::vec3 newAvatarPos(x, y, z);
-            
-            MyAvatar* myAvatar = Application::getInstance()->getAvatar();
-            glm::vec3 avatarPos = myAvatar->getPosition();
-            if (newAvatarPos != avatarPos) {
-                // send a node kill request, indicating to other clients that they should play the "disappeared" effect
-                MyAvatar::sendKillAvatar();
-                
-                qDebug("Going To Location: %f, %f, %f...", x, y, z);
-                myAvatar->setPosition(newAvatarPos);
-            }
-            
-        } else {
+        // go to coordinate destination or to Username
+        if (!goToDestination(destination)) {
             // there's a username entered by the user, make a request to the data-server
             DataServerClient::getValuesForKeysAndUserString(
                                                             QStringList()
@@ -994,29 +1011,7 @@ void Menu::goToLocation() {
 
     int dialogReturn = coordinateDialog.exec();
     if (dialogReturn == QDialog::Accepted && !coordinateDialog.textValue().isEmpty()) {
-        QByteArray newCoordinates;
-
-        QString delimiterPattern(",");
-        QStringList coordinateItems = coordinateDialog.textValue().split(delimiterPattern);
-
-        const int NUMBER_OF_COORDINATE_ITEMS = 3;
-        const int X_ITEM = 0;
-        const int Y_ITEM = 1;
-        const int Z_ITEM = 2;
-        if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
-            double x = coordinateItems[X_ITEM].toDouble();
-            double y = coordinateItems[Y_ITEM].toDouble();
-            double z = coordinateItems[Z_ITEM].toDouble();
-            glm::vec3 newAvatarPos(x, y, z);
-
-            if (newAvatarPos != avatarPos) {
-                // send a node kill request, indicating to other clients that they should play the "disappeared" effect
-                MyAvatar::sendKillAvatar();
-
-                qDebug("Going To Location: %f, %f, %f...", x, y, z);
-                myAvatar->setPosition(newAvatarPos);
-            }
-        }
+        goToDestination(coordinateDialog.textValue());
     }
 
     sendFakeEnterEvent();
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 742b9fc66f..780face29a 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -84,6 +84,8 @@ public:
                                            const char* member = NULL,
                                            QAction::MenuRole role = QAction::NoRole);
     virtual void removeAction(QMenu* menu, const QString& actionName);
+    bool goToDestination(QString destination);
+    void goToDomain(const QString newDomain);
 
 public slots:
     void bandwidthDetails();
@@ -100,7 +102,7 @@ private slots:
     void aboutApp();
     void login();
     void editPreferences();
-    void goToDomain();
+    void goToDomainDialog();
     void goToLocation();
     void bandwidthDetailsClosed();
     void voxelStatsDetailsClosed();
diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index f0fef33cee..33d2087594 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -14,6 +14,8 @@
 #include <QFileInfo>
 #include <QDebug>
 
+#include <glm/glm.hpp>
+
 // filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
 // %1 <= username, %2 <= date and time, %3 <= current location
 const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2@%3.jpg";
@@ -21,18 +23,69 @@ const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2@%3.jpg";
 const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss";
 const QString SNAPSHOTS_DIRECTORY = "Snapshots";
 
-void Snapshot::saveSnapshot(QGLWidget* widget, QString username, glm::vec3 location) {
-    QImage shot = widget->grabFrameBuffer();
+const QString LOCATION_X = "location-x";
+const QString LOCATION_Y = "location-y";
+const QString LOCATION_Z = "location-z";
 
+const QString ORIENTATION_X = "orientation-x";
+const QString ORIENTATION_Y = "orientation-y";
+const QString ORIENTATION_Z = "orientation-z";
+const QString ORIENTATION_W = "orientation-w";
+
+const QString DOMAIN_KEY = "domain";
+
+
+SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
+    
+    if (!QFile(snapshotPath).exists()) {
+        return NULL;
+    }
+    
+    QImage shot(snapshotPath);
+    
+    // no location data stored
+    if (shot.text(LOCATION_X).isEmpty() || shot.text(LOCATION_Y).isEmpty() || shot.text(LOCATION_Z).isEmpty()) {
+        return NULL;
+    }
+    
+    SnapshotMetaData* data = new SnapshotMetaData();
+    data->setLocation(glm::vec3(shot.text(LOCATION_X).toFloat(),
+                                shot.text(LOCATION_Y).toFloat(),
+                                shot.text(LOCATION_Z).toFloat()));
+    
+    data->setOrientation(glm::quat(shot.text(ORIENTATION_X).toFloat(),
+                                   shot.text(ORIENTATION_Y).toFloat(),
+                                   shot.text(ORIENTATION_Z).toFloat(),
+                                   shot.text(ORIENTATION_W).toFloat()));
+    
+    data->setDomain(shot.text(DOMAIN_KEY));
+                                   
+    return data;
+}
+
+void Snapshot::saveSnapshot(QGLWidget* widget, Profile* profile, MyAvatar* avatar) {
+    QImage shot = widget->grabFrameBuffer();
+    
+    glm::vec3 location = avatar->getPosition();
+    glm::quat orientation = avatar->getHead().getOrientation();
+    
     // add metadata
-    shot.setText("location-x", QString::number(location.x));
-    shot.setText("location-y", QString::number(location.y));
-    shot.setText("location-z", QString::number(location.z));
+    shot.setText(LOCATION_X, QString::number(location.x));
+    shot.setText(LOCATION_Y, QString::number(location.y));
+    shot.setText(LOCATION_Z, QString::number(location.z));
+    
+    shot.setText(ORIENTATION_X, QString::number(orientation.x));
+    shot.setText(ORIENTATION_Y, QString::number(orientation.y));
+    shot.setText(ORIENTATION_Z, QString::number(orientation.z));
+    shot.setText(ORIENTATION_W, QString::number(orientation.w));
+    
+    shot.setText(DOMAIN_KEY, profile->getLastDomain());
 
     QString formattedLocation = QString("%1_%2_%3").arg(location.x).arg(location.y).arg(location.z);
     // replace decimal . with '-'
     formattedLocation.replace('.', '-');
     
+    QString username = profile->getUsername();
     // normalize username, replace all non alphanumeric with '-'
     username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
     
diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h
index 26315678f9..05a2fc3147 100644
--- a/interface/src/ui/Snapshot.h
+++ b/interface/src/ui/Snapshot.h
@@ -13,15 +13,32 @@
 #include <QImage>
 #include <QGLWidget>
 
-#include <glm/glm.hpp>
+#include "avatar/MyAvatar.h"
+#include "avatar/Profile.h"
+
+class SnapshotMetaData {
+public:
+    
+    QString getDomain() { return _domain; }
+    void setDomain(QString domain) { _domain = domain; }
+    
+    glm::vec3 getLocation() { return _location; }
+    void setLocation(glm::vec3 location) { _location = location; }
+    
+    glm::quat getOrientation() { return _orientation; }
+    void setOrientation(glm::quat orientation) { _orientation = orientation; }
+    
+private:
+    QString _domain;
+    glm::vec3 _location;
+    glm::quat _orientation;;
+};
 
 class Snapshot {
 
 public:
-    static void saveSnapshot(QGLWidget* widget, QString username, glm::vec3 location);
-
-private:
-    QString _username;
+    static void saveSnapshot(QGLWidget* widget, Profile* profile, MyAvatar* avatar);
+    static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
 };
 
 #endif /* defined(__hifi__Snapshot__) */
diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp
index c0bc7f0010..8dd3857198 100644
--- a/libraries/shared/src/NodeList.cpp
+++ b/libraries/shared/src/NodeList.cpp
@@ -807,7 +807,8 @@ void NodeList::loadData(QSettings *settings) {
     } else {
         _domainHostname = DEFAULT_DOMAIN_HOSTNAME;
     }
-
+    
+    emit domainChanged(_domainHostname);
     settings->endGroup();
 }
 

From 0244ead5dfc09b9ab7e5bdc11bc91fa0d55379c3 Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Wed, 12 Feb 2014 21:06:30 +0100
Subject: [PATCH 02/14] fixed glm::quat initialization

---
 interface/src/ui/Snapshot.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index 33d2087594..bd4de19c86 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -53,10 +53,10 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
                                 shot.text(LOCATION_Y).toFloat(),
                                 shot.text(LOCATION_Z).toFloat()));
     
-    data->setOrientation(glm::quat(shot.text(ORIENTATION_X).toFloat(),
+    data->setOrientation(glm::quat(shot.text(ORIENTATION_W).toFloat(),
+                                   shot.text(ORIENTATION_X).toFloat(),
                                    shot.text(ORIENTATION_Y).toFloat(),
-                                   shot.text(ORIENTATION_Z).toFloat(),
-                                   shot.text(ORIENTATION_W).toFloat()));
+                                   shot.text(ORIENTATION_Z).toFloat()));
     
     data->setDomain(shot.text(DOMAIN_KEY));
                                    

From 08c8f47cd4cb2b2b9fa7cf2776ea64cd126c133a Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Thu, 13 Feb 2014 20:05:59 +0100
Subject: [PATCH 03/14] added SNAPSHOT_EXTENSION constant

---
 interface/src/Application.cpp | 2 +-
 interface/src/Application.h   | 2 ++
 interface/src/GLCanvas.cpp    | 2 +-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index eaaea9c5be..4ede4a9405 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1384,7 +1384,7 @@ void Application::dropEvent(QDropEvent *event) {
     QString snapshotPath;
     const QMimeData *mimeData = event->mimeData();
     foreach (QUrl url, mimeData->urls()) {
-        if (url.url().toLower().endsWith("jpg")) {
+        if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
             snapshotPath = url.url().remove("file://");
             break;
         }
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 9508c0c9a5..54a804ab72 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -93,6 +93,8 @@ static const float NODE_KILLED_RED   = 1.0f;
 static const float NODE_KILLED_GREEN = 0.0f;
 static const float NODE_KILLED_BLUE  = 0.0f;
 
+static const QString SNAPSHOT_EXTENSION  = ".jpg";
+
 class Application : public QApplication {
     Q_OBJECT
 
diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp
index cfff3b8696..7bc79e56d8 100644
--- a/interface/src/GLCanvas.cpp
+++ b/interface/src/GLCanvas.cpp
@@ -75,7 +75,7 @@ void GLCanvas::wheelEvent(QWheelEvent* event) {
 void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
     const QMimeData *mimeData = event->mimeData();
     foreach (QUrl url, mimeData->urls()) {
-        if (url.url().toLower().endsWith("jpg")) {
+        if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
             event->acceptProposedAction();
             break;
         }

From b7352335f23e2c61b4e3b7854f786136ca93f60a Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 14 Feb 2014 16:31:53 -0800
Subject: [PATCH 04/14] Adding collisions options to menu at a second location.

And adding CollideWithParticle option which will be working soon...
---
 interface/src/Menu.cpp | 40 ++++++++++++++++++++++++++++++++--------
 1 file changed, 32 insertions(+), 8 deletions(-)

diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 4a3733f760..6e2cf17c88 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -166,14 +166,19 @@ Menu::Menu() :
     
     addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
 
-    QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options");
-
-    QObject* avatar = appInstance->getAvatar();
-    addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, 0, false, avatar, SLOT(updateCollisionFlags()));
-    addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, 0, false, avatar, SLOT(updateCollisionFlags()));
-    addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, 0, false, avatar, SLOT(updateCollisionFlags()));
-    // TODO: make this option work
-    //addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, 0, false, avatar, SLOT(updateCollisionFlags()));
+    {
+        // add collision options to the general edit menu
+        QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options");
+        QObject* avatar = appInstance->getAvatar();
+        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, 
+                0, false, avatar, SLOT(updateCollisionFlags()));
+        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, 
+                0, true, avatar, SLOT(updateCollisionFlags()));
+        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, 
+                0, false, avatar, SLOT(updateCollisionFlags()));
+        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, 
+                0, true, avatar, SLOT(updateCollisionFlags()));
+    }
     
     QMenu* toolsMenu = addMenu("Tools");
 
@@ -341,6 +346,20 @@ Menu::Menu() :
                                            SLOT(setTCPEnabled(bool)));
     addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
 
+    {
+        // add collision options to the avatar menu
+        QMenu* collisionsOptionsMenu = avatarOptionsMenu->addMenu("Collision Options");
+        QObject* avatar = appInstance->getAvatar();
+        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, 
+                0, false, avatar, SLOT(updateCollisionFlags()));
+        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, 
+                0, true, avatar, SLOT(updateCollisionFlags()));
+        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, 
+                0, false, avatar, SLOT(updateCollisionFlags()));
+        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, 
+                0, true, avatar, SLOT(updateCollisionFlags()));
+    }
+    
     QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
 
     addCheckableActionToQMenuAndActionHash(handOptionsMenu,
@@ -515,6 +534,11 @@ void Menu::loadSettings(QSettings* settings) {
     Application::getInstance()->getProfile()->loadData(settings);
     Application::getInstance()->updateWindowTitle();
     NodeList::getInstance()->loadData(settings);
+
+    // MyAvatar caches some menu options, so we have to update them whenever we load settings.
+    // TODO: cache more settings in MyAvatar that are checked with very high frequency.
+    MyAvatar* myAvatar = Application::getInstance()->getAvatar();
+    myAvatar->updateCollisionFlags();
 }
 
 void Menu::saveSettings(QSettings* settings) {

From 335141049cfb2d582b886f3ebbdac7d4bcb71d75 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 14 Feb 2014 16:33:19 -0800
Subject: [PATCH 05/14] Cleaning up collision check API's and re-enabling
 collisions with particles.

---
 interface/src/avatar/Avatar.cpp               | 112 +++++++++---------
 interface/src/avatar/Avatar.h                 |  34 ++----
 interface/src/avatar/Hand.cpp                 |  32 +++--
 interface/src/avatar/Head.cpp                 |   8 +-
 interface/src/avatar/Head.h                   |   2 +-
 interface/src/avatar/MyAvatar.cpp             |   4 +-
 interface/src/renderer/Model.cpp              |  66 ++++++-----
 interface/src/renderer/Model.h                |  23 ++--
 libraries/avatars/src/AvatarData.h            |   8 +-
 .../particles/src/ParticleCollisionSystem.cpp |  76 +++++++-----
 .../particles/src/ParticleCollisionSystem.h   |   1 +
 libraries/shared/src/CollisionInfo.cpp        |  42 +++++++
 libraries/shared/src/CollisionInfo.h          |  76 ++++++++++--
 13 files changed, 298 insertions(+), 186 deletions(-)
 create mode 100644 libraries/shared/src/CollisionInfo.cpp

diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index f36c03cba6..74a3838dc1 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -273,27 +273,19 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
 }
 
 bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
-        ModelCollisionList& collisions, int skeletonSkipIndex) {
-    bool didPenetrate = false;
-    glm::vec3 skeletonPenetration;
-    ModelCollisionInfo collisionInfo;
-    /* Temporarily disabling collisions against the skeleton because the collision proxies up
-     * near the neck are bad and prevent the hand from hitting the face.
-    if (_skeletonModel.findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo, 1.0f, skeletonSkipIndex)) {
-        collisionInfo._model = &_skeletonModel;
-        collisions.push_back(collisionInfo);
-        didPenetrate = true; 
-    }
-    */
-    if (_head.getFaceModel().findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo)) {
-        collisionInfo._model = &(_head.getFaceModel());
-        collisions.push_back(collisionInfo);
-        didPenetrate = true; 
-    }
-    return didPenetrate;
+        CollisionList& collisions, int skeletonSkipIndex) {
+    // 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);
 }
 
-bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
+bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
+    if (_collisionFlags & COLLISION_GROUP_PARTICLES) {
+        return false;
+    }
+    bool collided = false;
+    // first do the hand collisions
     const HandData* handData = getHandData();
     if (handData) {
         for (int i = 0; i < NUM_HANDS; i++) {
@@ -311,41 +303,55 @@ bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float s
                         break;
                     }
                 }
+
+                int jointIndex = -1;
                 glm::vec3 handPosition;
                 if (i == 0) {
                     _skeletonModel.getLeftHandPosition(handPosition);
+                    jointIndex = _skeletonModel.getLeftHandJointIndex();
                 }
                 else {
                     _skeletonModel.getRightHandPosition(handPosition);
+                    jointIndex = _skeletonModel.getRightHandJointIndex();
                 }
                 glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
                 glm::vec3 diskNormal = palm->getNormal();
-                float diskThickness = 0.08f;
+                const float DISK_THICKNESS = 0.08f;
 
                 // collide against the disk
-                if (findSphereDiskPenetration(sphereCenter, sphereRadius, 
-                            diskCenter, HAND_PADDLE_RADIUS, diskThickness, diskNormal,
-                            collision._penetration)) {
-                    collision._addedVelocity = palm->getVelocity();
-                    return true;
+                glm::vec3 penetration;
+                if (findSphereDiskPenetration(particleCenter, particleRadius, 
+                            diskCenter, HAND_PADDLE_RADIUS, DISK_THICKNESS, diskNormal,
+                            penetration)) {
+                    CollisionInfo* collision = collisions.getNewCollision();
+                    if (collision) {
+                        collision->_type = PADDLE_HAND_COLLISION;
+                        collision->_flags = jointIndex;
+                        collision->_penetration = penetration;
+                        collision->_addedVelocity = palm->getVelocity();
+                        collided = true;
+                    } else {
+                        // collisions are full, so we might as well bail now
+                        return collided;
+                    }
                 }
             }
         }
     }
-    return false;
-}
-
-/* adebug TODO: make this work again
-bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
-    int jointIndex = _skeletonModel.findSphereCollision(sphereCenter, sphereRadius, collision._penetration);
-    if (jointIndex != -1) {
-        collision._penetration /= (float)(TREE_SCALE);
-        collision._addedVelocity = getVelocity();
-        return true;
+    // then collide against the models
+    int preNumCollisions = collisions.size();
+    if (_skeletonModel.findSphereCollisions(particleCenter, particleRadius, collisions)) {
+        // the Model doesn't have velocity info, so we have to set it for each new collision
+        int postNumCollisions = collisions.size();
+        for (int i = preNumCollisions; i < postNumCollisions; ++i) {
+            CollisionInfo* collision = collisions.getCollision(i);
+            collision->_penetration /= (float)(TREE_SCALE);
+            collision->_addedVelocity = getVelocity();
+        }
+        collided = true;
     }
-    return false;
+    return collided;
 }
-*/
 
 void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
     AvatarData::setFaceModelURL(faceModelURL);
@@ -430,9 +436,9 @@ void Avatar::updateCollisionFlags() {
     if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) {
         _collisionFlags |= COLLISION_GROUP_VOXELS;
     }
-    //if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
-    //    _collisionFlags |= COLLISION_GROUP_PARTICLES;
-    //}
+    if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
+        _collisionFlags |= COLLISION_GROUP_PARTICLES;
+    }
 }
 
 void Avatar::setScale(float scale) {
@@ -449,34 +455,34 @@ float Avatar::getHeight() const {
     return extents.maximum.y - extents.minimum.y;
 }
 
-bool Avatar::collisionWouldMoveAvatar(ModelCollisionInfo& collision) const {
-    // ATM only the Skeleton is pokeable
-    // TODO: make poke affect head
-    if (!collision._model) {
+bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
+    if (!collision._data || collision._type != MODEL_COLLISION) {
         return false;
     }
-    if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
+    Model* model = static_cast<Model*>(collision._data);
+    int jointIndex = collision._flags;
+
+    if (model == &(_skeletonModel) && jointIndex != -1) {
         // collision response of skeleton is temporarily disabled
         return false;
         //return _skeletonModel.collisionHitsMoveableJoint(collision);
     }
-    if (collision._model == &(_head.getFaceModel())) {
+    if (model == &(_head.getFaceModel())) {
+        // ATM we always handle MODEL_COLLISIONS against the face.
         return true;
     }
     return false;
 }
 
-void Avatar::applyCollision(ModelCollisionInfo& collision) {
-    if (!collision._model) {
+void Avatar::applyCollision(CollisionInfo& collision) {
+    if (!collision._data || collision._type != MODEL_COLLISION) {
         return;
     }
-    if (collision._model == &(_head.getFaceModel())) {
+    // TODO: make skeleton also respond to collisions
+    Model* model = static_cast<Model*>(collision._data);
+    if (model == &(_head.getFaceModel())) {
         _head.applyCollision(collision);
     }
-    // TODO: make skeleton respond to collisions
-    //if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
-    //    _skeletonModel.applyCollision(collision);
-    //}
 }
 
 float Avatar::getPelvisFloatingHeight() const {
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 2fc26a36b5..cc1168ca88 100755
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -57,8 +57,6 @@ enum ScreenTintLayer {
     NUM_SCREEN_TINT_LAYERS
 };
 
-typedef QVector<ModelCollisionInfo> ModelCollisionList;
-
 // Where one's own Avatar begins in the world (will be overwritten if avatar data file is found)
 // this is basically in the center of the ground plane. Slightly adjusted. This was asked for by
 // Grayson as he's building a street around here for demo dinner 2
@@ -97,26 +95,19 @@ public:
     /// Checks for penetration between the described sphere and the avatar.
     /// \param penetratorCenter the center of the penetration test sphere
     /// \param penetratorRadius the radius of the penetration test sphere
-    /// \param collisions[out] a list of collisions
+    /// \param collisions[out] a list to which collisions get appended
     /// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
     /// \return whether or not the sphere penetrated
     bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
-        ModelCollisionList& collisions, int skeletonSkipIndex = -1);
+        CollisionList& collisions, int skeletonSkipIndex = -1);
 
-    /// Checks for collision between the a sphere and the avatar's (paddle) hands.
-    /// \param collisionCenter the center of the penetration test sphere
-    /// \param collisionRadius the radius of the penetration test sphere
-    /// \param collision[out] the details of the collision point
-    /// \return whether or not the sphere collided
-    bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
+    /// Checks for collision between the a spherical particle and the avatar (including paddle hands)
+    /// \param collisionCenter the center of particle's bounding sphere
+    /// \param collisionRadius the radius of particle's bounding sphere
+    /// \param collisions[out] a list to which collisions get appended
+    /// \return whether or not the particle collided
+    bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions);
 
-    /// Checks for collision between the a sphere and the avatar's skeleton (including hand capsules).
-    /// \param collisionCenter the center of the penetration test sphere
-    /// \param collisionRadius the radius of the penetration test sphere
-    /// \param collision[out] the details of the collision point
-    /// \return whether or not the sphere collided
-    //bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
-    
     virtual bool isMyAvatar() { return false; }
     
     virtual void setFaceModelURL(const QUrl& faceModelURL);
@@ -126,13 +117,13 @@ public:
 
     static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
 
-    float getHeight() const;
-
     /// \return true if we expect the avatar would move as a result of the collision
-    bool collisionWouldMoveAvatar(ModelCollisionInfo& collision) const;
+    bool collisionWouldMoveAvatar(CollisionInfo& collision) const;
 
     /// \param collision a data structure for storing info about collisions against Models
-    void applyCollision(ModelCollisionInfo& collision);
+    void applyCollision(CollisionInfo& collision);
+
+    float getBoundingRadius() const { return 0.5f * getHeight(); }
 
 public slots:
     void updateCollisionFlags();
@@ -164,6 +155,7 @@ protected:
     glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
     void setScale(float scale);
 
+    float getHeight() const;
     float getPelvisFloatingHeight() const;
     float getPelvisToHeadLength() const;
 
diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp
index af66c08bf4..7c9905557e 100644
--- a/interface/src/avatar/Hand.cpp
+++ b/interface/src/avatar/Hand.cpp
@@ -125,6 +125,10 @@ void Hand::simulate(float deltaTime, bool isMine) {
     }
 }
 
+// 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);
+
 void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
     if (!avatar || avatar == _owningAvatar) {
         // don't collide with our own hands (that is done elsewhere)
@@ -137,7 +141,6 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
             continue;
         }        
         glm::vec3 totalPenetration;
-        ModelCollisionList collisions;
         if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
             //  Check for palm collisions
             glm::vec3 myPalmPosition = palm.getPosition();
@@ -171,20 +174,22 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
                 }
             }
         }
-        if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) {
-            for (int j = 0; j < collisions.size(); ++j) {
+        handCollisions.clear();
+        if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions)) {
+            for (int j = 0; j < handCollisions.size(); ++j) {
+                CollisionInfo* collision = handCollisions.getCollision(j);
                 if (isMyHand) {
-                    if (!avatar->collisionWouldMoveAvatar(collisions[j])) {
+                    if (!avatar->collisionWouldMoveAvatar(*collision)) {
                         // we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is 
                         // not expected to respond to the collision (hand hit unmovable part of their Avatar)
-                        totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
+                        totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
                     }
                 } else {
                     // when !isMyHand then avatar is MyAvatar and we apply the collision
                     // which might not do anything (hand hit unmovable part of MyAvatar) however
                     // we don't resolve the hand's penetration because we expect the remote 
                     // simulation to do the right thing.
-                    avatar->applyCollision(collisions[j]);
+                    avatar->applyCollision(*collision);
                 }
             }
         }
@@ -200,7 +205,6 @@ void Hand::collideAgainstOurself() {
         return;
     }
 
-    ModelCollisionList collisions;
     int leftPalmIndex, rightPalmIndex;   
     getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
     float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
@@ -210,16 +214,18 @@ void Hand::collideAgainstOurself() {
         if (!palm.isActive()) {
             continue;
         }        
-        glm::vec3 totalPenetration;
-        // and the current avatar (ignoring everything below the parent of the parent of the last free joint)
-        collisions.clear();
         const Model& skeletonModel = _owningAvatar->getSkeletonModel();
+        // ignoring everything below the parent of the parent of the last free joint
         int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
             skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
                 (i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
-        if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions, skipIndex)) {
-            for (int j = 0; j < collisions.size(); ++j) {
-                totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
+
+        handCollisions.clear();
+        glm::vec3 totalPenetration;
+        if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions, skipIndex)) {
+            for (int j = 0; j < handCollisions.size(); ++j) {
+                CollisionInfo* collision = handCollisions.getCollision(j);
+                totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
             }
         }
         // resolve penetration
diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp
index bb88530aa7..ddb0660364 100644
--- a/interface/src/avatar/Head.cpp
+++ b/interface/src/avatar/Head.cpp
@@ -219,7 +219,7 @@ float Head::getTweakedRoll() const {
     return glm::clamp(_roll + _tweakedRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
 }
 
-void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
+void Head::applyCollision(CollisionInfo& collision) {
     // HACK: the collision proxies for the FaceModel are bad.  As a temporary workaround
     // we collide against a hard coded collision proxy.
     // TODO: get a better collision proxy here.
@@ -229,7 +229,7 @@ void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
     // collide the contactPoint against the collision proxy to obtain a new penetration
     // NOTE: that penetration is in opposite direction (points the way out for the point, not the sphere)
     glm::vec3 penetration;
-    if (findPointSpherePenetration(collisionInfo._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) {
+    if (findPointSpherePenetration(collision._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) {
         // compute lean angles
         Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
         glm::quat bodyRotation = owningAvatar->getOrientation();
@@ -239,8 +239,8 @@ void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
             glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f);
             float neckLength = glm::length(_position - neckPosition);
             if (neckLength > 0.f) {
-                float forward = glm::dot(collisionInfo._penetration, zAxis) / neckLength;
-                float sideways = - glm::dot(collisionInfo._penetration, xAxis) / neckLength;
+                float forward = glm::dot(collision._penetration, zAxis) / neckLength;
+                float sideways = - glm::dot(collision._penetration, xAxis) / neckLength;
                 addLean(sideways, forward);
             }
         }
diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h
index eae8223903..c88e654d95 100644
--- a/interface/src/avatar/Head.h
+++ b/interface/src/avatar/Head.h
@@ -80,7 +80,7 @@ public:
     float getTweakedYaw() const;
     float getTweakedRoll() const;
 
-    void  applyCollision(ModelCollisionInfo& collisionInfo);
+    void  applyCollision(CollisionInfo& collisionInfo);
     
 private:
     // disallow copies of the Head, copy of owning Avatar is disallowed too
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 85c0c2b35a..6673abe88b 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -955,7 +955,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
         // no need to compute a bunch of stuff if we have one or fewer avatars
         return;
     }
-    float myBoundingRadius = 0.5f * getHeight();
+    float myBoundingRadius = getBoundingRadius();
 
     // HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis
     // TODO: make the collision work without assuming avatar orientation
@@ -975,7 +975,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
         if (_distanceToNearestAvatar > distance) {
             _distanceToNearestAvatar = distance;
         }
-        float theirBoundingRadius = 0.5f * avatar->getHeight();
+        float theirBoundingRadius = avatar->getBoundingRadius();
         if (distance < myBoundingRadius + theirBoundingRadius) {
             Extents theirStaticExtents = _skeletonModel.getStaticExtents();
             glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum;
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index 7686b1ac7f..97cee8a05e 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -446,9 +446,9 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
     return false;
 }
 
-bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius,
-    ModelCollisionInfo& collisionInfo, float boneScale, int skipIndex) const {
-    int jointIndex = -1;
+bool Model::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
+    CollisionList& collisions, float boneScale, int skipIndex) const {
+    bool collided = false;
     const glm::vec3 relativeCenter = penetratorCenter - _translation;
     const FBXGeometry& geometry = _geometry->getFBXGeometry();
     glm::vec3 totalPenetration;
@@ -477,22 +477,22 @@ bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetra
         if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
                 startRadius, endRadius, bonePenetration)) {
             totalPenetration = addPenetrations(totalPenetration, bonePenetration);
-            // BUG: we currently overwrite the jointIndex with the last one found
-            // which can cause incorrect collisions when colliding against more than
-            // one joint.
-            // TODO: fix this.
-            jointIndex = i;
+            CollisionInfo* collision = collisions.getNewCollision();
+            if (collision) {
+                collision->_type = MODEL_COLLISION;
+                collision->_data = (void*)(this);
+                collision->_flags = i;
+                collision->_contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
+                collision->_penetration = totalPenetration;
+                collided = true;
+            } else {
+                // collisions are full, so we might as well break
+                break;
+            }
         }
         outerContinue: ;
     }
-    if (jointIndex != -1) {
-        // don't store collisionInfo._model at this stage, let the outer context do that
-        collisionInfo._penetration = totalPenetration;
-        collisionInfo._jointIndex = jointIndex;
-        collisionInfo._contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
-        return true;
-    }
-    return false;
+    return collided;
 }
 
 void Model::updateJointState(int index) {
@@ -725,24 +725,30 @@ void Model::renderCollisionProxies(float alpha) {
     glPopMatrix();
 }
 
-bool Model::collisionHitsMoveableJoint(ModelCollisionInfo& collision) const {
-    // the joint is pokable by a collision if it exists and is free to move
-    const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._jointIndex];
-    if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
-        return false;
+bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
+    if (collision._type == MODEL_COLLISION) {
+        // the joint is pokable by a collision if it exists and is free to move
+        const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._flags];
+        if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
+            return false;
+        }
+        // an empty freeLineage means the joint can't move
+        const FBXGeometry& geometry = _geometry->getFBXGeometry();
+        int jointIndex = collision._flags;
+        const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
+        return !freeLineage.isEmpty();
     }
-    // an empty freeLineage means the joint can't move
-    const FBXGeometry& geometry = _geometry->getFBXGeometry();
-    const QVector<int>& freeLineage = geometry.joints.at(collision._jointIndex).freeLineage;
-    return !freeLineage.isEmpty();
+    return false;
 }
 
-void Model::applyCollision(ModelCollisionInfo& collision) {
-    // This needs work.  At the moment it can wiggle joints that are free to move (such as arms)
-    // but unmovable joints (such as torso) cannot be influenced at all.
+void Model::applyCollision(CollisionInfo& collision) {
+    if (collision._type != MODEL_COLLISION) {
+        return;
+    }
+
     glm::vec3 jointPosition(0.f);
-    if (getJointPosition(collision._jointIndex, jointPosition)) {
-        int jointIndex = collision._jointIndex;
+    int jointIndex = collision._flags;
+    if (getJointPosition(jointIndex, jointPosition)) {
         const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex];
         if (joint.parentIndex != -1) {
             // compute the approximate distance (travel) that the joint needs to move
diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h
index bab25bed7a..6f61d6e204 100644
--- a/interface/src/renderer/Model.h
+++ b/interface/src/renderer/Model.h
@@ -17,16 +17,6 @@
 #include "ProgramObject.h"
 #include "TextureCache.h"
 
-class Model;
-
-// TODO: Andrew to move this into its own file
-class ModelCollisionInfo : public CollisionInfo {
-public:
-    ModelCollisionInfo() : CollisionInfo(), _model(NULL), _jointIndex(-1) {}
-    Model* _model;
-    int _jointIndex;
-};
-
 /// A generic 3D model displaying geometry loaded from a URL.
 class Model : public QObject {
     Q_OBJECT
@@ -162,17 +152,18 @@ public:
 
     bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
 
-    bool findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius,
-        ModelCollisionInfo& collision, float boneScale = 1.0f, int skipIndex = -1) const;
+    bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
+        CollisionList& collisions, float boneScale = 1.0f, int skipIndex = -1) const;
     
     void renderCollisionProxies(float alpha);
 
+    /// \param collision details about the collisions
     /// \return true if the collision is against a moveable joint
-    bool collisionHitsMoveableJoint(ModelCollisionInfo& collision) const;
+    bool collisionHitsMoveableJoint(CollisionInfo& collision) const;
 
-    /// \param collisionInfo info about the collision
-    /// Use the collisionInfo to affect the model
-    void  applyCollision(ModelCollisionInfo& collisionInfo);
+    /// \param collision details about the collision
+    /// Use the collision to affect the model
+    void applyCollision(CollisionInfo& collision);
 
 protected:
 
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 08604a95f1..be171ed145 100755
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -135,14 +135,10 @@ public:
 
     virtual const glm::vec3& getVelocity() const { return vec3Zero; }
 
-    virtual bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
+    virtual bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
         return false;
     }
 
-    virtual bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
-        return false;
-    }
-    
     bool hasIdentityChangedAfterParsing(const QByteArray& packet);
     QByteArray identityByteArray();
     
@@ -150,6 +146,8 @@ public:
     const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
     virtual void setFaceModelURL(const QUrl& faceModelURL);
     virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
+
+    virtual float getBoundingRadius() const { return 1.f; }
     
 protected:
     glm::vec3 _position;
diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp
index bb0260c2bf..2d272a8f1f 100644
--- a/libraries/particles/src/ParticleCollisionSystem.cpp
+++ b/libraries/particles/src/ParticleCollisionSystem.cpp
@@ -19,9 +19,11 @@
 #include "ParticleEditPacketSender.h"
 #include "ParticleTree.h"
 
+const int MAX_COLLISIONS_PER_PARTICLE = 16;
+
 ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender,
     ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio,
-    AvatarHashMap* avatars) {
+    AvatarHashMap* avatars) : _collisions(MAX_COLLISIONS_PER_PARTICLE) {
     init(packetSender, particles, voxels, audio, avatars);
 }
 
@@ -181,39 +183,53 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
     const float COLLISION_FREQUENCY = 0.5f;
     glm::vec3 penetration;
 
+    _collisions.clear();
     foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
         AvatarData* avatar = avatarPointer.data();
-        CollisionInfo collisionInfo;
-        collisionInfo._damping = DAMPING;
-        collisionInfo._elasticity = ELASTICITY;
-        if (avatar->findSphereCollisionWithHands(center, radius, collisionInfo)) {
-            // TODO: Andrew to resurrect particles-vs-avatar body collisions
-            //avatar->findSphereCollisionWithSkeleton(center, radius, collisionInfo)) { 
-            collisionInfo._addedVelocity /= (float)(TREE_SCALE);
-            glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity();
-            if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) {
-                // only collide when particle and collision point are moving toward each other
-                // (doing this prevents some "collision snagging" when particle penetrates the object)
 
-                // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar.
-                // NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
-                // TODO: make this less hacky when we have more per-collision details
-                float elasticity = ELASTICITY;
-                float attenuationFactor = glm::length(collisionInfo._addedVelocity) / HALTING_SPEED;
-                float damping = DAMPING;
-                if (attenuationFactor < 1.f) {
-                    collisionInfo._addedVelocity *= attenuationFactor;
-                    elasticity *= attenuationFactor;
-                    // NOTE: the math below keeps the damping piecewise continuous,
-                    // while ramping it up to 1.0 when attenuationFactor = 0
-                    damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
+        // use a very generous bounding radius since the arms can stretch
+        float totalRadius = 2.f * avatar->getBoundingRadius() + radius;
+        glm::vec3 relativePosition = center - avatar->getPosition();
+        if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
+            continue;
+        }
+
+        if (avatar->findParticleCollisions(center, radius, _collisions)) {
+            int numCollisions = _collisions.size();
+            for (int i = 0; i < numCollisions; ++i) {
+                CollisionInfo* collision = _collisions.getCollision(i);
+                collision->_damping = DAMPING;
+                collision->_elasticity = ELASTICITY;
+    
+                collision->_addedVelocity /= (float)(TREE_SCALE);
+                glm::vec3 relativeVelocity = collision->_addedVelocity - particle->getVelocity();
+    
+                if (glm::dot(relativeVelocity, collision->_penetration) <= 0.f) {
+                    // only collide when particle and collision point are moving toward each other
+                    // (doing this prevents some "collision snagging" when particle penetrates the object)
+    
+                    // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against them.
+                    if (collision->_type == PADDLE_HAND_COLLISION) {
+                        // NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
+                        // TODO: make this less hacky when we have more per-collision details
+                        float elasticity = ELASTICITY;
+                        float attenuationFactor = glm::length(collision->_addedVelocity) / HALTING_SPEED;
+                        float damping = DAMPING;
+                        if (attenuationFactor < 1.f) {
+                            collision->_addedVelocity *= attenuationFactor;
+                            elasticity *= attenuationFactor;
+                            // NOTE: the math below keeps the damping piecewise continuous,
+                            // while ramping it up to 1 when attenuationFactor = 0
+                            damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
+                        }
+                    }
+                    // HACK END
+    
+                    updateCollisionSound(particle, collision->_penetration, COLLISION_FREQUENCY);
+                    collision->_penetration /= (float)(TREE_SCALE);
+                    particle->applyHardCollision(*collision);
+                    queueParticlePropertiesUpdate(particle);
                 }
-                // HACK END
-
-                updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY);
-                collisionInfo._penetration /= (float)(TREE_SCALE);
-                particle->applyHardCollision(collisionInfo);
-                queueParticlePropertiesUpdate(particle);
             }
         }
     }
diff --git a/libraries/particles/src/ParticleCollisionSystem.h b/libraries/particles/src/ParticleCollisionSystem.h
index c525d3ddfc..3bff843743 100644
--- a/libraries/particles/src/ParticleCollisionSystem.h
+++ b/libraries/particles/src/ParticleCollisionSystem.h
@@ -66,6 +66,7 @@ private:
     VoxelTree* _voxels;
     AbstractAudioInterface* _audio;
     AvatarHashMap* _avatars;
+    CollisionList _collisions;
 };
 
 #endif /* defined(__hifi__ParticleCollisionSystem__) */
diff --git a/libraries/shared/src/CollisionInfo.cpp b/libraries/shared/src/CollisionInfo.cpp
new file mode 100644
index 0000000000..5d74d591c6
--- /dev/null
+++ b/libraries/shared/src/CollisionInfo.cpp
@@ -0,0 +1,42 @@
+//
+//  CollisionInfo.cpp
+//  hifi
+//
+//  Created by Andrew Meadows on 2014.02.14
+//  Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+//
+
+#include "CollisionInfo.h"
+
+CollisionList::CollisionList(int maxSize) :
+    _maxSize(maxSize),
+    _size(0) {
+    _collisions.resize(_maxSize);
+}
+
+CollisionInfo* CollisionList::getNewCollision() {
+    // return pointer to existing CollisionInfo, or NULL of list is full
+    return (_size < _maxSize) ? &(_collisions[++_size]) : NULL;
+}
+
+CollisionInfo* CollisionList::getCollision(int index) {
+    return (index > -1 && index < _size) ? &(_collisions[index]) : NULL;
+}
+
+void CollisionList::clear() {
+    for (int i = 0; i < _size; ++i) {
+        // we only clear the important stuff
+        CollisionInfo& collision = _collisions[i];
+        collision._type = BASE_COLLISION;
+        collision._data = NULL; // CollisionInfo does not own whatever this points to.
+        collision._flags = 0;
+        // we rely on the consumer to properly overwrite these fields when the collision is "created"
+        //collision._damping;
+        //collision._elasticity;
+        //collision._contactPoint;
+        //collision._penetration;
+        //collision._addedVelocity;
+    }
+    _size = 0;
+}
+
diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h
index 1fa95cd83a..acd127435c 100644
--- a/libraries/shared/src/CollisionInfo.h
+++ b/libraries/shared/src/CollisionInfo.h
@@ -11,15 +11,42 @@
 
 #include <glm/glm.hpp>
 
-const uint32_t COLLISION_GROUP_ENVIRONMENT = 1U << 0;
-const uint32_t COLLISION_GROUP_AVATARS     = 1U << 1;
-const uint32_t COLLISION_GROUP_VOXELS      = 1U << 2;
-const uint32_t COLLISION_GROUP_PARTICLES   = 1U << 3;
+#include <QVector>
+
+enum CollisionType {
+    BASE_COLLISION = 0,
+    PADDLE_HAND_COLLISION,
+    MODEL_COLLISION,
+};
+
+const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0;
+const quint32 COLLISION_GROUP_AVATARS     = 1U << 1;
+const quint32 COLLISION_GROUP_VOXELS      = 1U << 2;
+const quint32 COLLISION_GROUP_PARTICLES   = 1U << 3;
+
+// CollisionInfo contains details about the collision between two things: BodyA and BodyB.
+// The assumption is that the context that analyzes the collision knows about BodyA but
+// does not necessarily know about BodyB.  Hence the data storred in the CollisionInfo
+// is expected to be relative to BodyA (for example the penetration points from A into B).
 
 class CollisionInfo {
 public:
     CollisionInfo() 
-        : _damping(0.f),
+        : _type(0),
+        _data(NULL),
+        _flags(0),
+        _damping(0.f),
+        _elasticity(1.f),
+        _contactPoint(0.f), 
+        _penetration(0.f), 
+        _addedVelocity(0.f) {
+    }
+
+    CollisionInfo(qint32 type)
+        : _type(type),
+        _data(NULL),
+        _flags(0),
+        _damping(0.f),
         _elasticity(1.f),
         _contactPoint(0.f), 
         _penetration(0.f), 
@@ -28,13 +55,40 @@ public:
 
     ~CollisionInfo() {}
 
-    //glm::vec3 _normal;
-    float _damping;
-    float _elasticity;
-    glm::vec3 _contactPoint; // world-frame point on bodyA that is deepest into bodyB
-    glm::vec3 _penetration; // depth that bodyA penetrates into bodyB
-    glm::vec3 _addedVelocity;
+    qint32 _type;             // type of Collision (will determine what is supposed to be in _data and _flags)
+    void* _data;              // pointer to user supplied data
+    quint32 _flags;           // 32 bits for whatever
+
+    float _damping;           // range [0,1] of friction coeficient
+    float _elasticity;        // range [0,1] of energy conservation
+    glm::vec3 _contactPoint;  // world-frame point on BodyA that is deepest into BodyB
+    glm::vec3 _penetration;   // depth that BodyA penetrates into BodyB
+    glm::vec3 _addedVelocity; // velocity of BodyB
 };
 
+// CollisionList is intended to be a recycled container.  Fill the CollisionInfo's,
+// use them, and then clear them for the next frame or context.
+
+class CollisionList {
+public:
+    CollisionList(int maxSize);
+
+    /// \return pointer to next collision. NULL if list is full.
+    CollisionInfo* getNewCollision();
+
+    /// \return pointer to collision by index.  NULL if index out of bounds.
+    CollisionInfo* getCollision(int index);
+
+    /// \return number of valid collisions
+    int size() const { return _size; }
+
+    /// Clear valid collisions.
+    void clear();
+
+private:
+    int _maxSize;
+    int _size;
+    QVector<CollisionInfo> _collisions;
+};
 
 #endif /* defined(__hifi__CollisionInfo__) */

From e4ef09e06d91528233b0f96e1d7bcc42d6d4b293 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Mon, 17 Feb 2014 09:12:38 -0800
Subject: [PATCH 06/14] Helper method for adding avatar collision submenu

---
 interface/src/Menu.cpp | 45 +++++++++++++++++-------------------------
 interface/src/Menu.h   |  2 ++
 2 files changed, 20 insertions(+), 27 deletions(-)

diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index beb8369f44..697b21cd8c 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -168,19 +168,7 @@ Menu::Menu() :
     
     addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
 
-    {
-        // add collision options to the general edit menu
-        QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options");
-        QObject* avatar = appInstance->getAvatar();
-        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, 
-                0, false, avatar, SLOT(updateCollisionFlags()));
-        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, 
-                0, true, avatar, SLOT(updateCollisionFlags()));
-        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, 
-                0, false, avatar, SLOT(updateCollisionFlags()));
-        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, 
-                0, true, avatar, SLOT(updateCollisionFlags()));
-    }
+    addAvatarCollisionSubMenu(editMenu);
     
     QMenu* toolsMenu = addMenu("Tools");
 
@@ -348,19 +336,7 @@ Menu::Menu() :
                                            SLOT(setTCPEnabled(bool)));
     addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
 
-    {
-        // add collision options to the avatar menu
-        QMenu* collisionsOptionsMenu = avatarOptionsMenu->addMenu("Collision Options");
-        QObject* avatar = appInstance->getAvatar();
-        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, 
-                0, false, avatar, SLOT(updateCollisionFlags()));
-        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, 
-                0, true, avatar, SLOT(updateCollisionFlags()));
-        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, 
-                0, false, avatar, SLOT(updateCollisionFlags()));
-        addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, 
-                0, true, avatar, SLOT(updateCollisionFlags()));
-    }
+    addAvatarCollisionSubMenu(avatarOptionsMenu);
     
     QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
 
@@ -1230,6 +1206,22 @@ void Menu::updateFrustumRenderModeAction() {
     }
 }
 
+void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) {
+    // add avatar collisions subMenu to overMenu
+    QMenu* subMenu = overMenu->addMenu("Collision Options");
+
+    Application* appInstance = Application::getInstance();
+    QObject* avatar = appInstance->getAvatar();
+    addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment, 
+            0, false, avatar, SLOT(updateCollisionFlags()));
+    addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars, 
+            0, true, avatar, SLOT(updateCollisionFlags()));
+    addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels, 
+            0, false, avatar, SLOT(updateCollisionFlags()));
+    addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles, 
+            0, true, avatar, SLOT(updateCollisionFlags()));
+}
+
 QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) {
     int lastIndex;
     lastIndex = string.lastIndexOf(search);
@@ -1240,4 +1232,3 @@ QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string)
     
     return string;
 }
-
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 84f5325e8f..1ac5341ed9 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -139,6 +139,8 @@ private:
 
     void updateFrustumRenderModeAction();
 
+    void addAvatarCollisionSubMenu(QMenu* overMenu);
+
     QHash<QString, QAction*> _actionHash;
     int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback
     BandwidthDialog* _bandwidthDialog;

From b28a18dab04f842f3a23d349c02307baacb55484 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Mon, 17 Feb 2014 09:17:34 -0800
Subject: [PATCH 07/14] Remove warning about null-op sprintf() in g++

---
 libraries/octree/src/OctreeSceneStats.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp
index 59287e3c5c..8a5a731cff 100644
--- a/libraries/octree/src/OctreeSceneStats.cpp
+++ b/libraries/octree/src/OctreeSceneStats.cpp
@@ -791,7 +791,6 @@ const char* OctreeSceneStats::getItemValue(Item item) {
             break;
         }
         default:
-            sprintf(_itemValueBuffer, "");
             break;
     }
     return _itemValueBuffer;

From 9e7597a6ada5fc945d06d02d4f1cc8f9b51ccce0 Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Mon, 17 Feb 2014 19:06:55 +0100
Subject: [PATCH 08/14] bad merge fix

---
 interface/src/Menu.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 81cb5fd8d9..7ee13e0cb1 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -117,7 +117,6 @@ private slots:
     void login();
     void editPreferences();
     void goToDomainDialog();
-    void goToDomain();
     void goToLocation();
     void bandwidthDetailsClosed();
     void voxelStatsDetailsClosed();

From 2ba742675755394df0c350a224a26b33da2ecc97 Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Mon, 17 Feb 2014 19:25:53 +0100
Subject: [PATCH 09/14] windows build fix

---
 interface/src/ui/Snapshot.cpp | 2 --
 1 file changed, 2 deletions(-)

diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index bd4de19c86..d0a38eec4e 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -14,8 +14,6 @@
 #include <QFileInfo>
 #include <QDebug>
 
-#include <glm/glm.hpp>
-
 // filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
 // %1 <= username, %2 <= date and time, %3 <= current location
 const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2@%3.jpg";

From 677e6f5ce9372e1c2f88daa9d48e1dda6c0f0126 Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Mon, 17 Feb 2014 20:19:35 +0100
Subject: [PATCH 10/14] windows build fix (2)

---
 interface/src/ui/Snapshot.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h
index 05a2fc3147..5ee8fb4f8d 100644
--- a/interface/src/ui/Snapshot.h
+++ b/interface/src/ui/Snapshot.h
@@ -13,6 +13,7 @@
 #include <QImage>
 #include <QGLWidget>
 
+#include <glm/glm.hpp>
 #include "avatar/MyAvatar.h"
 #include "avatar/Profile.h"
 

From 0df0c23c327f1a486b01a95af8974e3fe92b398a Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Mon, 17 Feb 2014 21:14:28 +0100
Subject: [PATCH 11/14] Windows build fix (3)

---
 interface/src/ui/Snapshot.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h
index 5ee8fb4f8d..57a388020d 100644
--- a/interface/src/ui/Snapshot.h
+++ b/interface/src/ui/Snapshot.h
@@ -9,6 +9,8 @@
 #ifndef __hifi__Snapshot__
 #define __hifi__Snapshot__
 
+#include "InterfaceConfig.h"
+
 #include <QString>
 #include <QImage>
 #include <QGLWidget>

From 6c2f3b687e7188b8ae31ff91a9a1e7410667fb89 Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Tue, 18 Feb 2014 01:50:55 +0100
Subject: [PATCH 12/14] removed unnecessary references

---
 interface/src/ui/Snapshot.cpp | 1 -
 interface/src/ui/Snapshot.h   | 1 -
 2 files changed, 2 deletions(-)

diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index d0a38eec4e..8cfc759308 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -12,7 +12,6 @@
 
 #include <QDateTime>
 #include <QFileInfo>
-#include <QDebug>
 
 // filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
 // %1 <= username, %2 <= date and time, %3 <= current location
diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h
index 57a388020d..14c59552ec 100644
--- a/interface/src/ui/Snapshot.h
+++ b/interface/src/ui/Snapshot.h
@@ -15,7 +15,6 @@
 #include <QImage>
 #include <QGLWidget>
 
-#include <glm/glm.hpp>
 #include "avatar/MyAvatar.h"
 #include "avatar/Profile.h"
 

From d36ced9b92884b7e4aa383cd36d3f6a3bcbed18e Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Tue, 18 Feb 2014 07:33:03 +0100
Subject: [PATCH 13/14] fixed misplaced #endif

---
 interface/src/devices/Transmitter.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/interface/src/devices/Transmitter.h b/interface/src/devices/Transmitter.h
index 1fa392a280..12f2b302f7 100644
--- a/interface/src/devices/Transmitter.h
+++ b/interface/src/devices/Transmitter.h
@@ -45,5 +45,6 @@ private:
     TouchState _touchState;
     timeval* _lastReceivedPacket;
 
-#endif /* defined(__hifi__Transmitter__) */
 };
+
+#endif /* defined(__hifi__Transmitter__) */

From 7621d70c2453a73f4f5997b602ca16aa1ee381dd Mon Sep 17 00:00:00 2001
From: stojce <stojce@me.com>
Date: Tue, 18 Feb 2014 08:11:59 +0100
Subject: [PATCH 14/14] use base class - replaced MyAvatar with Avatar

---
 interface/src/ui/Snapshot.cpp | 2 +-
 interface/src/ui/Snapshot.h   | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index 8cfc759308..e16b0c570d 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -60,7 +60,7 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
     return data;
 }
 
-void Snapshot::saveSnapshot(QGLWidget* widget, Profile* profile, MyAvatar* avatar) {
+void Snapshot::saveSnapshot(QGLWidget* widget, Profile* profile, Avatar* avatar) {
     QImage shot = widget->grabFrameBuffer();
     
     glm::vec3 location = avatar->getPosition();
diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h
index 14c59552ec..13c6945349 100644
--- a/interface/src/ui/Snapshot.h
+++ b/interface/src/ui/Snapshot.h
@@ -15,7 +15,7 @@
 #include <QImage>
 #include <QGLWidget>
 
-#include "avatar/MyAvatar.h"
+#include "avatar/Avatar.h"
 #include "avatar/Profile.h"
 
 class SnapshotMetaData {
@@ -39,7 +39,7 @@ private:
 class Snapshot {
 
 public:
-    static void saveSnapshot(QGLWidget* widget, Profile* profile, MyAvatar* avatar);
+    static void saveSnapshot(QGLWidget* widget, Profile* profile, Avatar* avatar);
     static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
 };