mirror of
https://github.com/lubosz/overte.git
synced 2025-04-27 16:15:27 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into 19507
This commit is contained in:
commit
8ee282e7d2
50 changed files with 1329 additions and 389 deletions
assignment-client/src/audio
examples
interface
resources/sounds/mention
src
Application.cppApplication.hBuckyBalls.hMenu.cppMenu.hModelUploader.cppModelUploader.hXmppClient.cppXmppClient.h
avatar
models
renderer
ui
ui
libraries
avatars/src
networking/src
octree/src
shared/src
tests/physics/src
|
@ -369,7 +369,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
statsObject["average_mixes_per_listener"] = 0.0;
|
||||
}
|
||||
|
||||
// ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
||||
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
||||
|
||||
_sumListeners = 0;
|
||||
_sumMixes = 0;
|
||||
|
|
|
@ -37,6 +37,7 @@ var radiusMinimum = 0.05;
|
|||
var radiusMaximum = 0.5;
|
||||
|
||||
var modelURLs = [
|
||||
"https://s3-us-west-1.amazonaws.com/highfidelity-public/models/music/EVHFrankenstein.fbx",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx",
|
||||
|
@ -51,9 +52,15 @@ var currentModelURL = 1;
|
|||
var numModels = modelURLs.length;
|
||||
|
||||
|
||||
function getNewVoxelPosition() {
|
||||
var camera = Camera.getPosition();
|
||||
var forwardVector = Quat.getFront(MyAvatar.orientation);
|
||||
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, 2.0));
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
//print("event.text=" + event.text);
|
||||
debugPrint("event.text=" + event.text);
|
||||
if (event.text == "ESC") {
|
||||
if (leftRecentlyDeleted) {
|
||||
leftRecentlyDeleted = false;
|
||||
|
@ -63,7 +70,20 @@ function keyPressEvent(event) {
|
|||
rightRecentlyDeleted = false;
|
||||
rightModelAlreadyInHand = false;
|
||||
}
|
||||
} else if (event.text == "DELETE") {
|
||||
} else if (event.text == "m") {
|
||||
var URL = Window.prompt("Model URL", "Enter URL, e.g. http://foo.com/model.fbx");
|
||||
Window.alert("Your response was: " + prompt);
|
||||
var modelPosition = getNewVoxelPosition();
|
||||
var properties = { position: { x: modelPosition.x,
|
||||
y: modelPosition.y,
|
||||
z: modelPosition.z },
|
||||
radius: modelRadius,
|
||||
modelURL: URL
|
||||
};
|
||||
newModel = Models.addModel(properties);
|
||||
|
||||
} else if (event.text == "DELETE" || event.text == "BACKSPACE") {
|
||||
|
||||
if (leftModelAlreadyInHand) {
|
||||
print("want to delete leftHandModel=" + leftHandModel);
|
||||
Models.deleteModel(leftHandModel);
|
||||
|
@ -122,6 +142,7 @@ function checkControllerSide(whichSide) {
|
|||
var BUTTON_FWD;
|
||||
var BUTTON_3;
|
||||
var palmPosition;
|
||||
var palmRotation;
|
||||
var modelAlreadyInHand;
|
||||
var handMessage;
|
||||
|
||||
|
@ -129,12 +150,14 @@ function checkControllerSide(whichSide) {
|
|||
BUTTON_FWD = LEFT_BUTTON_FWD;
|
||||
BUTTON_3 = LEFT_BUTTON_3;
|
||||
palmPosition = Controller.getSpatialControlPosition(LEFT_PALM);
|
||||
palmRotation = Controller.getSpatialControlRawRotation(LEFT_PALM);
|
||||
modelAlreadyInHand = leftModelAlreadyInHand;
|
||||
handMessage = "LEFT";
|
||||
} else {
|
||||
BUTTON_FWD = RIGHT_BUTTON_FWD;
|
||||
BUTTON_3 = RIGHT_BUTTON_3;
|
||||
palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM);
|
||||
palmRotation = Controller.getSpatialControlRawRotation(RIGHT_PALM);
|
||||
modelAlreadyInHand = rightModelAlreadyInHand;
|
||||
handMessage = "RIGHT";
|
||||
}
|
||||
|
@ -149,8 +172,7 @@ function checkControllerSide(whichSide) {
|
|||
|
||||
if (closestModel.isKnownID) {
|
||||
|
||||
//debugPrint
|
||||
print(handMessage + " HAND- CAUGHT SOMETHING!!");
|
||||
debugPrint(handMessage + " HAND- CAUGHT SOMETHING!!");
|
||||
|
||||
if (whichSide == LEFT_PALM) {
|
||||
leftModelAlreadyInHand = true;
|
||||
|
@ -165,9 +187,10 @@ function checkControllerSide(whichSide) {
|
|||
y: modelPosition.y,
|
||||
z: modelPosition.z },
|
||||
radius: modelRadius,
|
||||
modelRotation: palmRotation,
|
||||
};
|
||||
|
||||
print(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
|
||||
debugPrint(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
|
||||
|
||||
Models.editModel(closestModel, properties);
|
||||
|
||||
|
@ -189,10 +212,11 @@ function checkControllerSide(whichSide) {
|
|||
y: modelPosition.y,
|
||||
z: modelPosition.z },
|
||||
radius: modelRadius,
|
||||
modelRotation: palmRotation,
|
||||
modelURL: modelURLs[currentModelURL]
|
||||
};
|
||||
|
||||
print("modelRadius=" +modelRadius);
|
||||
debugPrint("modelRadius=" +modelRadius);
|
||||
|
||||
newModel = Models.addModel(properties);
|
||||
if (whichSide == LEFT_PALM) {
|
||||
|
@ -225,16 +249,16 @@ function checkControllerSide(whichSide) {
|
|||
|
||||
// If holding the model keep it in the palm
|
||||
if (grabButtonPressed) {
|
||||
//debugPrint
|
||||
print(">>>>> " + handMessage + "-MODEL IN HAND, grabbing, hold and move");
|
||||
debugPrint(">>>>> " + handMessage + "-MODEL IN HAND, grabbing, hold and move");
|
||||
var modelPosition = getModelHoldPosition(whichSide);
|
||||
var properties = { position: { x: modelPosition.x,
|
||||
y: modelPosition.y,
|
||||
z: modelPosition.z },
|
||||
radius: modelRadius,
|
||||
modelRotation: palmRotation,
|
||||
};
|
||||
|
||||
print(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
|
||||
debugPrint(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
|
||||
|
||||
Models.editModel(handModel, properties);
|
||||
} else {
|
||||
|
|
|
@ -178,6 +178,10 @@ function disableArtificialGravity() {
|
|||
MyAvatar.motionBehaviors = MyAvatar.motionBehaviors & ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
|
||||
updateButton(3, false);
|
||||
}
|
||||
// call this immediately so that avatar doesn't fall before voxel data arrives
|
||||
// Ideally we would only do this on LOGIN, not when starting the script
|
||||
// in the middle of a session.
|
||||
disableArtificialGravity();
|
||||
|
||||
function enableArtificialGravity() {
|
||||
// NOTE: setting the gravity automatically sets the AVATAR_MOTION_OBEY_LOCAL_GRAVITY behavior bit.
|
||||
|
@ -276,7 +280,6 @@ function update(deltaTime) {
|
|||
}
|
||||
Script.update.connect(update);
|
||||
|
||||
|
||||
// we also handle click detection in our mousePressEvent()
|
||||
function mousePressEvent(event) {
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
|
|
BIN
interface/resources/sounds/mention/Mentioned A.wav
Normal file
BIN
interface/resources/sounds/mention/Mentioned A.wav
Normal file
Binary file not shown.
BIN
interface/resources/sounds/mention/Mentioned B.wav
Normal file
BIN
interface/resources/sounds/mention/Mentioned B.wav
Normal file
Binary file not shown.
BIN
interface/resources/sounds/mention/Mentioned C.wav
Normal file
BIN
interface/resources/sounds/mention/Mentioned C.wav
Normal file
Binary file not shown.
|
@ -63,11 +63,11 @@
|
|||
#include <UUID.h>
|
||||
#include <OctreeSceneStats.h>
|
||||
#include <LocalVoxelsList.h>
|
||||
#include <ModelUploader.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "InterfaceVersion.h"
|
||||
#include "Menu.h"
|
||||
#include "ModelUploader.h"
|
||||
#include "Util.h"
|
||||
#include "devices/OculusManager.h"
|
||||
#include "devices/TV3DManager.h"
|
||||
|
@ -3090,6 +3090,16 @@ void Application::setMenuShortcutsEnabled(bool enabled) {
|
|||
setShortcutsEnabled(_window->menuBar(), enabled);
|
||||
}
|
||||
|
||||
void Application::uploadModel(ModelType modelType) {
|
||||
ModelUploader* uploader = new ModelUploader(modelType);
|
||||
QThread* thread = new QThread();
|
||||
thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit()));
|
||||
thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
|
||||
uploader->connect(thread, SIGNAL(started()), SLOT(send()));
|
||||
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void Application::updateWindowTitle(){
|
||||
|
||||
QString buildVersion = " (build " + applicationVersion() + ")";
|
||||
|
@ -3417,22 +3427,16 @@ void Application::toggleRunningScriptsWidget() {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::uploadFST(bool isHead) {
|
||||
ModelUploader* uploader = new ModelUploader(isHead);
|
||||
QThread* thread = new QThread();
|
||||
thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit()));
|
||||
thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
|
||||
uploader->connect(thread, SIGNAL(started()), SLOT(send()));
|
||||
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void Application::uploadHead() {
|
||||
uploadFST(true);
|
||||
uploadModel(HEAD_MODEL);
|
||||
}
|
||||
|
||||
void Application::uploadSkeleton() {
|
||||
uploadFST(false);
|
||||
uploadModel(SKELETON_MODEL);
|
||||
}
|
||||
|
||||
void Application::uploadAttachment() {
|
||||
uploadModel(ATTACHMENT_MODEL);
|
||||
}
|
||||
|
||||
ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor) {
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
#include "scripting/ControllerScriptingInterface.h"
|
||||
#include "ui/BandwidthDialog.h"
|
||||
#include "ui/BandwidthMeter.h"
|
||||
#include "ui/ModelsBrowser.h"
|
||||
#include "ui/OctreeStatsDialog.h"
|
||||
#include "ui/RearMirrorTools.h"
|
||||
#include "ui/SnapshotShareDialog.h"
|
||||
|
@ -296,9 +297,9 @@ public slots:
|
|||
void reloadAllScripts();
|
||||
void toggleRunningScriptsWidget();
|
||||
|
||||
void uploadFST(bool isHead);
|
||||
void uploadHead();
|
||||
void uploadSkeleton();
|
||||
void uploadAttachment();
|
||||
|
||||
void bumpSettings() { ++_numChangedSettings; }
|
||||
|
||||
|
@ -376,13 +377,11 @@ private:
|
|||
|
||||
void setMenuShortcutsEnabled(bool enabled);
|
||||
|
||||
void uploadModel(ModelType modelType);
|
||||
|
||||
static void attachNewHeadToNode(Node *newNode);
|
||||
static void* networkReceive(void* args); // network receive thread
|
||||
|
||||
void findAxisAlignment();
|
||||
|
||||
void displayRearMirrorTools();
|
||||
|
||||
MainWindow* _window;
|
||||
GLCanvas* _glWidget; // our GLCanvas has a couple extra features
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#include "InterfaceConfig.h"
|
||||
#include "Util.h"
|
||||
|
||||
|
||||
const int NUM_BBALLS = 200;
|
||||
|
||||
class BuckyBalls {
|
||||
|
@ -42,9 +41,7 @@ private:
|
|||
float _bballRadius[NUM_BBALLS];
|
||||
float _bballColliding[NUM_BBALLS];
|
||||
int _bballElement[NUM_BBALLS];
|
||||
int _bballIsGrabbed[2];
|
||||
|
||||
|
||||
int _bballIsGrabbed[2];
|
||||
};
|
||||
|
||||
#endif // hifi_BuckyBalls_h
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "Menu.h"
|
||||
#include "scripting/MenuScriptingInterface.h"
|
||||
#include "Util.h"
|
||||
#include "ui/AttachmentsDialog.h"
|
||||
#include "ui/InfoView.h"
|
||||
#include "ui/MetavoxelEditor.h"
|
||||
#include "ui/ModelsBrowser.h"
|
||||
|
@ -157,6 +158,8 @@ Menu::Menu() :
|
|||
addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model");
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0, Application::getInstance(), SLOT(uploadHead()));
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadSkeleton, 0, Application::getInstance(), SLOT(uploadSkeleton()));
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadAttachment, 0,
|
||||
Application::getInstance(), SLOT(uploadAttachment()));
|
||||
addDisabledActionAndSeparator(fileMenu, "Settings");
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings()));
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings()));
|
||||
|
@ -187,10 +190,12 @@ Menu::Menu() :
|
|||
SLOT(editPreferences()),
|
||||
QAction::PreferencesRole);
|
||||
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments()));
|
||||
|
||||
addDisabledActionAndSeparator(editMenu, "Physics");
|
||||
QObject* avatar = appInstance->getAvatar();
|
||||
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, true,
|
||||
avatar, SLOT(updateMotionBehaviors()));
|
||||
avatar, SLOT(updateMotionBehaviorsFromMenu()));
|
||||
|
||||
|
||||
addAvatarCollisionSubMenu(editMenu);
|
||||
|
@ -210,6 +215,10 @@ Menu::Menu() :
|
|||
toggleChat();
|
||||
connect(&xmppClient, SIGNAL(connected()), this, SLOT(toggleChat()));
|
||||
connect(&xmppClient, SIGNAL(disconnected()), this, SLOT(toggleChat()));
|
||||
|
||||
QDir::setCurrent(Application::resourcesPath());
|
||||
// init chat window to listen chat
|
||||
_chatWindow = new ChatWindow(Application::getInstance()->getWindow());
|
||||
#endif
|
||||
|
||||
QMenu* viewMenu = addMenu("View");
|
||||
|
@ -479,6 +488,7 @@ void Menu::loadSettings(QSettings* settings) {
|
|||
|
||||
_audioJitterBufferSamples = loadSetting(settings, "audioJitterBufferSamples", 0);
|
||||
_fieldOfView = loadSetting(settings, "fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES);
|
||||
_realWorldFieldOfView = loadSetting(settings, "realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES);
|
||||
_faceshiftEyeDeflection = loadSetting(settings, "faceshiftEyeDeflection", DEFAULT_FACESHIFT_EYE_DEFLECTION);
|
||||
_maxVoxels = loadSetting(settings, "maxVoxels", DEFAULT_MAX_VOXELS_PER_SYSTEM);
|
||||
_maxVoxelPacketsPerSecond = loadSetting(settings, "maxVoxelsPPS", DEFAULT_MAX_VOXEL_PPS);
|
||||
|
@ -831,6 +841,15 @@ void Menu::editPreferences() {
|
|||
}
|
||||
}
|
||||
|
||||
void Menu::editAttachments() {
|
||||
if (!_attachmentsDialog) {
|
||||
_attachmentsDialog = new AttachmentsDialog();
|
||||
_attachmentsDialog->show();
|
||||
} else {
|
||||
_attachmentsDialog->close();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::goToDomain(const QString newDomain) {
|
||||
if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) {
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
|
|
|
@ -64,6 +64,7 @@ struct ViewFrustumOffset {
|
|||
|
||||
class QSettings;
|
||||
|
||||
class AttachmentsDialog;
|
||||
class BandwidthDialog;
|
||||
class LodToolsDialog;
|
||||
class MetavoxelEditor;
|
||||
|
@ -84,6 +85,9 @@ public:
|
|||
void setAudioJitterBufferSamples(float audioJitterBufferSamples) { _audioJitterBufferSamples = audioJitterBufferSamples; }
|
||||
float getFieldOfView() const { return _fieldOfView; }
|
||||
void setFieldOfView(float fieldOfView) { _fieldOfView = fieldOfView; }
|
||||
float getRealWorldFieldOfView() const { return _realWorldFieldOfView; }
|
||||
void setRealWorldFieldOfView(float realWorldFieldOfView) { _realWorldFieldOfView = realWorldFieldOfView; }
|
||||
|
||||
float getFaceshiftEyeDeflection() const { return _faceshiftEyeDeflection; }
|
||||
void setFaceshiftEyeDeflection(float faceshiftEyeDeflection) { _faceshiftEyeDeflection = faceshiftEyeDeflection; }
|
||||
QString getSnapshotsLocation() const;
|
||||
|
@ -171,6 +175,7 @@ public slots:
|
|||
private slots:
|
||||
void aboutApp();
|
||||
void editPreferences();
|
||||
void editAttachments();
|
||||
void goToDomainDialog();
|
||||
void goToLocation();
|
||||
void nameLocation();
|
||||
|
@ -228,6 +233,7 @@ private:
|
|||
int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback
|
||||
BandwidthDialog* _bandwidthDialog;
|
||||
float _fieldOfView; /// in Degrees, doesn't apply to HMD like Oculus
|
||||
float _realWorldFieldOfView; // The actual FOV set by the user's monitor size and view distance
|
||||
float _faceshiftEyeDeflection;
|
||||
FrustumDrawMode _frustumDrawMode;
|
||||
ViewFrustumOffset _viewFrustumOffset;
|
||||
|
@ -252,6 +258,7 @@ private:
|
|||
SimpleMovingAverage _fastFPSAverage;
|
||||
QAction* _loginAction;
|
||||
QPointer<PreferencesDialog> _preferencesDialog;
|
||||
QPointer<AttachmentsDialog> _attachmentsDialog;
|
||||
QAction* _chatAction;
|
||||
QString _snapshotsLocation;
|
||||
};
|
||||
|
@ -261,6 +268,7 @@ namespace MenuOption {
|
|||
const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
|
||||
const QString AmbientOcclusion = "Ambient Occlusion";
|
||||
const QString Atmosphere = "Atmosphere";
|
||||
const QString Attachments = "Attachments...";
|
||||
const QString AudioNoiseReduction = "Audio Noise Reduction";
|
||||
const QString AudioScope = "Audio Scope";
|
||||
const QString AudioScopePause = "Pause Audio Scope";
|
||||
|
@ -362,6 +370,7 @@ namespace MenuOption {
|
|||
const QString TestPing = "Test Ping";
|
||||
const QString TransmitterDrive = "Transmitter Drive";
|
||||
const QString TurnWithHead = "Turn using Head";
|
||||
const QString UploadAttachment = "Upload Attachment Model";
|
||||
const QString UploadHead = "Upload Head Model";
|
||||
const QString UploadSkeleton = "Upload Skeleton Model";
|
||||
const QString Visage = "Visage";
|
||||
|
|
|
@ -59,11 +59,11 @@ static const int MAX_CHECK = 30;
|
|||
static const int QCOMPRESS_HEADER_POSITION = 0;
|
||||
static const int QCOMPRESS_HEADER_SIZE = 4;
|
||||
|
||||
ModelUploader::ModelUploader(bool isHead) :
|
||||
ModelUploader::ModelUploader(ModelType modelType) :
|
||||
_lodCount(-1),
|
||||
_texturesCount(-1),
|
||||
_totalSize(0),
|
||||
_isHead(isHead),
|
||||
_modelType(modelType),
|
||||
_readyToSend(false),
|
||||
_dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType)),
|
||||
_numberOfChecks(MAX_CHECK)
|
||||
|
@ -190,7 +190,7 @@ bool ModelUploader::zip() {
|
|||
}
|
||||
|
||||
// open the dialog to configure the rest
|
||||
ModelPropertiesDialog properties(_isHead, mapping, basePath, geometry);
|
||||
ModelPropertiesDialog properties(_modelType, mapping, basePath, geometry);
|
||||
if (properties.exec() == QDialog::Rejected) {
|
||||
return false;
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ bool ModelUploader::zip() {
|
|||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"model_name\"");
|
||||
textPart.setBody(nameField);
|
||||
_dataMultiPart->append(textPart);
|
||||
_url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + nameField + ".fst";
|
||||
_url = S3_URL + "/models/" + MODEL_TYPE_NAMES[_modelType] + "/" + nameField + ".fst";
|
||||
} else {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
|
@ -260,11 +260,7 @@ bool ModelUploader::zip() {
|
|||
QHttpPart textPart;
|
||||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
|
||||
" name=\"model_category\"");
|
||||
if (_isHead) {
|
||||
textPart.setBody("heads");
|
||||
} else {
|
||||
textPart.setBody("skeletons");
|
||||
}
|
||||
textPart.setBody(MODEL_TYPE_NAMES[_modelType]);
|
||||
_dataMultiPart->append(textPart);
|
||||
|
||||
_readyToSend = true;
|
||||
|
@ -510,9 +506,9 @@ bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const
|
|||
return true;
|
||||
}
|
||||
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping,
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
|
||||
const QString& basePath, const FBXGeometry& geometry) :
|
||||
_isHead(isHead),
|
||||
_modelType(modelType),
|
||||
_originalMapping(originalMapping),
|
||||
_basePath(basePath),
|
||||
_geometry(geometry) {
|
||||
|
@ -531,10 +527,12 @@ ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& or
|
|||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setSingleStep(0.01);
|
||||
|
||||
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
|
||||
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
||||
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
||||
if (!isHead) {
|
||||
if (_modelType != ATTACHMENT_MODEL) {
|
||||
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
|
||||
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
||||
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
||||
}
|
||||
if (_modelType == SKELETON_MODEL) {
|
||||
form->addRow("Root Joint:", _rootJoint = createJointBox());
|
||||
form->addRow("Lean Joint:", _leanJoint = createJointBox());
|
||||
form->addRow("Head Joint:", _headJoint = createJointBox());
|
||||
|
@ -573,10 +571,12 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
|
|||
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
|
||||
|
||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
||||
if (!_isHead) {
|
||||
if (_modelType != ATTACHMENT_MODEL) {
|
||||
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
||||
}
|
||||
if (_modelType == SKELETON_MODEL) {
|
||||
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
|
||||
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
|
||||
insertJointMapping(joints, "jointHead", _headJoint->currentText());
|
||||
|
@ -604,10 +604,12 @@ void ModelPropertiesDialog::reset() {
|
|||
_scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble());
|
||||
|
||||
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
|
||||
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
||||
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
||||
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
||||
if (!_isHead) {
|
||||
if (_modelType != ATTACHMENT_MODEL) {
|
||||
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
||||
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
||||
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
||||
}
|
||||
if (_modelType == SKELETON_MODEL) {
|
||||
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
|
||||
setJointText(_leanJoint, jointHash.value("jointLean").toString());
|
||||
setJointText(_headJoint, jointHash.value("jointHead").toString());
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
#include <FBXReader.h>
|
||||
|
||||
#include "ui/ModelsBrowser.h"
|
||||
|
||||
class QComboBox;
|
||||
class QDoubleSpinBox;
|
||||
class QFileInfo;
|
||||
|
@ -30,7 +32,7 @@ class ModelUploader : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelUploader(bool isHead);
|
||||
ModelUploader(ModelType type);
|
||||
~ModelUploader();
|
||||
|
||||
public slots:
|
||||
|
@ -49,7 +51,7 @@ private:
|
|||
int _lodCount;
|
||||
int _texturesCount;
|
||||
int _totalSize;
|
||||
bool _isHead;
|
||||
ModelType _modelType;
|
||||
bool _readyToSend;
|
||||
|
||||
QHttpMultiPart* _dataMultiPart;
|
||||
|
@ -73,7 +75,7 @@ class ModelPropertiesDialog : public QDialog {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping,
|
||||
ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
|
||||
const QString& basePath, const FBXGeometry& geometry);
|
||||
|
||||
QVariantHash getMapping() const;
|
||||
|
@ -87,7 +89,7 @@ private:
|
|||
QComboBox* createJointBox(bool withNone = true) const;
|
||||
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
|
||||
|
||||
bool _isHead;
|
||||
ModelType _modelType;
|
||||
QVariantHash _originalMapping;
|
||||
QString _basePath;
|
||||
FBXGeometry _geometry;
|
||||
|
|
|
@ -36,6 +36,7 @@ void XmppClient::xmppConnected() {
|
|||
_publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM);
|
||||
_publicChatRoom->setNickName(AccountManager::getInstance().getAccountInfo().getUsername());
|
||||
_publicChatRoom->join();
|
||||
emit joinedPublicChatRoom();
|
||||
}
|
||||
|
||||
void XmppClient::xmppError(QXmppClient::Error error) {
|
||||
|
|
|
@ -28,6 +28,9 @@ public:
|
|||
QXmppClient& getXMPPClient() { return _xmppClient; }
|
||||
const QXmppMucRoom* getPublicChatRoom() const { return _publicChatRoom; }
|
||||
|
||||
signals:
|
||||
void joinedPublicChatRoom();
|
||||
|
||||
private slots:
|
||||
void xmppConnected();
|
||||
void xmppError(QXmppClient::Error error);
|
||||
|
|
|
@ -126,6 +126,7 @@ void Avatar::simulate(float deltaTime) {
|
|||
_skeletonModel.simulate(deltaTime);
|
||||
}
|
||||
_skeletonModel.simulate(deltaTime, _hasNewJointRotations);
|
||||
simulateAttachments(deltaTime);
|
||||
_hasNewJointRotations = false;
|
||||
|
||||
glm::vec3 headPosition = _position;
|
||||
|
@ -232,6 +233,17 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
_skeletonModel.renderBoundingCollisionShapes(0.7f);
|
||||
}
|
||||
}
|
||||
// If this is the avatar being looked at, render a little ball above their head
|
||||
if (_isLookAtTarget) {
|
||||
const float LOOK_AT_INDICATOR_RADIUS = 0.03f;
|
||||
const float LOOK_AT_INDICATOR_HEIGHT = 0.60f;
|
||||
const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f };
|
||||
glPushMatrix();
|
||||
glColor4fv(LOOK_AT_INDICATOR_COLOR);
|
||||
glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z);
|
||||
glutSolidSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
// quick check before falling into the code below:
|
||||
// (a 10 degree breadth of an almost 2 meter avatar kicks in at about 12m)
|
||||
|
@ -338,6 +350,7 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
return;
|
||||
}
|
||||
_skeletonModel.render(1.0f, modelRenderMode);
|
||||
renderAttachments(modelRenderMode);
|
||||
getHand()->render(false);
|
||||
}
|
||||
getHead()->render(1.0f, modelRenderMode);
|
||||
|
@ -347,6 +360,29 @@ bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode render
|
|||
return true;
|
||||
}
|
||||
|
||||
void Avatar::simulateAttachments(float deltaTime) {
|
||||
for (int i = 0; i < _attachmentModels.size(); i++) {
|
||||
const AttachmentData& attachment = _attachmentData.at(i);
|
||||
Model* model = _attachmentModels.at(i);
|
||||
int jointIndex = getJointIndex(attachment.jointName);
|
||||
glm::vec3 jointPosition;
|
||||
glm::quat jointRotation;
|
||||
if (_skeletonModel.getJointPosition(jointIndex, jointPosition) &&
|
||||
_skeletonModel.getJointRotation(jointIndex, jointRotation)) {
|
||||
model->setTranslation(jointPosition + jointRotation * attachment.translation * _skeletonModel.getScale());
|
||||
model->setRotation(jointRotation * attachment.rotation);
|
||||
model->setScale(_skeletonModel.getScale() * attachment.scale);
|
||||
model->simulate(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::renderAttachments(Model::RenderMode renderMode) {
|
||||
foreach (Model* model, _attachmentModels) {
|
||||
model->render(1.0f, renderMode);
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::updateJointMappings() {
|
||||
// no-op; joint mappings come from skeleton model
|
||||
}
|
||||
|
@ -667,6 +703,25 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar());
|
||||
}
|
||||
|
||||
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
AvatarData::setAttachmentData(attachmentData);
|
||||
|
||||
// make sure we have as many models as attachments
|
||||
while (_attachmentModels.size() < attachmentData.size()) {
|
||||
Model* model = new Model(this);
|
||||
model->init();
|
||||
_attachmentModels.append(model);
|
||||
}
|
||||
while (_attachmentModels.size() > attachmentData.size()) {
|
||||
delete _attachmentModels.takeLast();
|
||||
}
|
||||
|
||||
// update the urls
|
||||
for (int i = 0; i < attachmentData.size(); i++) {
|
||||
_attachmentModels[i]->setURL(attachmentData.at(i).modelURL);
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::setDisplayName(const QString& displayName) {
|
||||
AvatarData::setDisplayName(displayName);
|
||||
_displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName);
|
||||
|
|
|
@ -79,7 +79,7 @@ public:
|
|||
//setters
|
||||
void setDisplayingLookatVectors(bool displayingLookatVectors) { getHead()->setRenderLookatVectors(displayingLookatVectors); }
|
||||
void setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction);
|
||||
|
||||
void setIsLookAtTarget(const bool isLookAtTarget) { _isLookAtTarget = isLookAtTarget; }
|
||||
//getters
|
||||
bool isInitialized() const { return _initialized; }
|
||||
SkeletonModel& getSkeletonModel() { return _skeletonModel; }
|
||||
|
@ -131,6 +131,7 @@ public:
|
|||
|
||||
virtual void setFaceModelURL(const QUrl& faceModelURL);
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
||||
virtual void setDisplayName(const QString& displayName);
|
||||
virtual void setBillboard(const QByteArray& billboard);
|
||||
|
||||
|
@ -160,6 +161,7 @@ signals:
|
|||
|
||||
protected:
|
||||
SkeletonModel _skeletonModel;
|
||||
QVector<Model*> _attachmentModels;
|
||||
float _bodyYawDelta;
|
||||
glm::vec3 _velocity;
|
||||
float _leanScale;
|
||||
|
@ -188,6 +190,9 @@ protected:
|
|||
virtual void renderBody(RenderMode renderMode, float glowLevel = 0.0f);
|
||||
virtual bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const;
|
||||
|
||||
void simulateAttachments(float deltaTime);
|
||||
void renderAttachments(Model::RenderMode renderMode);
|
||||
|
||||
virtual void updateJointMappings();
|
||||
|
||||
private:
|
||||
|
@ -195,6 +200,7 @@ private:
|
|||
bool _initialized;
|
||||
QScopedPointer<Texture> _billboardTexture;
|
||||
bool _shouldRenderBillboard;
|
||||
bool _isLookAtTarget;
|
||||
|
||||
void renderBillboard();
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <NodeList.h>
|
||||
|
||||
#include <GeometryUtil.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Avatar.h"
|
||||
|
|
|
@ -45,7 +45,13 @@ const float PITCH_SPEED = 100.0f; // degrees/sec
|
|||
const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions
|
||||
const float COLLISION_RADIUS_SCALE = 0.125f;
|
||||
|
||||
const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
|
||||
const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f;
|
||||
|
||||
// TODO: normalize avatar speed for standard avatar size, then scale all motion logic
|
||||
// to properly follow avatar size.
|
||||
float DEFAULT_MOTOR_TIMESCALE = 0.25f;
|
||||
float MAX_AVATAR_SPEED = 300.0f;
|
||||
float MAX_MOTOR_SPEED = MAX_AVATAR_SPEED;
|
||||
|
||||
MyAvatar::MyAvatar() :
|
||||
Avatar(),
|
||||
|
@ -55,13 +61,15 @@ MyAvatar::MyAvatar() :
|
|||
_shouldJump(false),
|
||||
_gravity(0.0f, -1.0f, 0.0f),
|
||||
_distanceToNearestAvatar(std::numeric_limits<float>::max()),
|
||||
_lastCollisionPosition(0, 0, 0),
|
||||
_speedBrakes(false),
|
||||
_wasPushing(false),
|
||||
_isPushing(false),
|
||||
_thrust(0.0f),
|
||||
_isThrustOn(false),
|
||||
_thrustMultiplier(1.0f),
|
||||
_motionBehaviors(0),
|
||||
_motorVelocity(0.0f),
|
||||
_motorTimescale(DEFAULT_MOTOR_TIMESCALE),
|
||||
_maxMotorSpeed(MAX_MOTOR_SPEED),
|
||||
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
|
||||
_lastBodyPenetration(0.0f),
|
||||
_lastFloorContactPoint(0.0f),
|
||||
_lookAtTargetAvatar(),
|
||||
_shouldRender(true),
|
||||
_billboardValid(false),
|
||||
|
@ -115,135 +123,62 @@ void MyAvatar::update(float deltaTime) {
|
|||
|
||||
void MyAvatar::simulate(float deltaTime) {
|
||||
|
||||
glm::quat orientation = getOrientation();
|
||||
|
||||
if (_scale != _targetScale) {
|
||||
float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale;
|
||||
setScale(scale);
|
||||
Application::getInstance()->getCamera()->setScale(scale);
|
||||
}
|
||||
|
||||
// Collect thrust forces from keyboard and devices
|
||||
updateThrust(deltaTime);
|
||||
|
||||
// update the movement of the hand and process handshaking with other avatars...
|
||||
updateHandMovementAndTouching(deltaTime);
|
||||
|
||||
// apply gravity
|
||||
// For gravity, always move the avatar by the amount driven by gravity, so that the collision
|
||||
// routines will detect it and collide every frame when pulled by gravity to a surface
|
||||
const float MIN_DISTANCE_AFTER_COLLISION_FOR_GRAVITY = 0.02f;
|
||||
if (glm::length(_position - _lastCollisionPosition) > MIN_DISTANCE_AFTER_COLLISION_FOR_GRAVITY) {
|
||||
updateOrientation(deltaTime);
|
||||
|
||||
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
|
||||
fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) +
|
||||
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
|
||||
|
||||
bool walkingOnFloor = false;
|
||||
float gravityLength = glm::length(_gravity);
|
||||
if (gravityLength > EPSILON) {
|
||||
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
|
||||
glm::vec3 startCap;
|
||||
boundingShape.getStartPoint(startCap);
|
||||
glm::vec3 bottomOfBoundingCapsule = startCap + (boundingShape.getRadius() / gravityLength) * _gravity;
|
||||
|
||||
float fallThreshold = 2.f * deltaTime * gravityLength;
|
||||
walkingOnFloor = (glm::distance(bottomOfBoundingCapsule, _lastFloorContactPoint) < fallThreshold);
|
||||
}
|
||||
|
||||
if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f ||
|
||||
! walkingOnFloor) {
|
||||
// apply gravity
|
||||
_velocity += _scale * _gravity * (GRAVITY_EARTH * deltaTime);
|
||||
}
|
||||
|
||||
// update motor and thrust
|
||||
updateMotorFromKeyboard(deltaTime, walkingOnFloor);
|
||||
applyMotor(deltaTime);
|
||||
applyThrust(deltaTime);
|
||||
|
||||
// add thrust to velocity
|
||||
_velocity += _thrust * deltaTime;
|
||||
|
||||
// update body yaw by body yaw delta
|
||||
orientation = orientation * glm::quat(glm::radians(
|
||||
glm::vec3(_bodyPitchDelta, _bodyYawDelta, _bodyRollDelta) * deltaTime));
|
||||
// decay body rotation momentum
|
||||
|
||||
const float BODY_SPIN_FRICTION = 7.5f;
|
||||
float bodySpinMomentum = 1.0f - BODY_SPIN_FRICTION * deltaTime;
|
||||
if (bodySpinMomentum < 0.0f) { bodySpinMomentum = 0.0f; }
|
||||
_bodyPitchDelta *= bodySpinMomentum;
|
||||
_bodyYawDelta *= bodySpinMomentum;
|
||||
_bodyRollDelta *= bodySpinMomentum;
|
||||
|
||||
float MINIMUM_ROTATION_RATE = 2.0f;
|
||||
if (fabs(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { _bodyYawDelta = 0.0f; }
|
||||
if (fabs(_bodyRollDelta) < MINIMUM_ROTATION_RATE) { _bodyRollDelta = 0.0f; }
|
||||
if (fabs(_bodyPitchDelta) < MINIMUM_ROTATION_RATE) { _bodyPitchDelta = 0.0f; }
|
||||
|
||||
const float MAX_STATIC_FRICTION_SPEED = 0.5f;
|
||||
const float STATIC_FRICTION_STRENGTH = _scale * 20.0f;
|
||||
applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_SPEED, STATIC_FRICTION_STRENGTH);
|
||||
|
||||
// Damp avatar velocity
|
||||
const float LINEAR_DAMPING_STRENGTH = 0.5f;
|
||||
const float SPEED_BRAKE_POWER = _scale * 10.0f;
|
||||
const float SQUARED_DAMPING_STRENGTH = 0.007f;
|
||||
|
||||
const float SLOW_NEAR_RADIUS = 5.0f;
|
||||
float linearDamping = LINEAR_DAMPING_STRENGTH;
|
||||
const float NEAR_AVATAR_DAMPING_FACTOR = 50.0f;
|
||||
if (_distanceToNearestAvatar < _scale * SLOW_NEAR_RADIUS) {
|
||||
linearDamping *= 1.0f + NEAR_AVATAR_DAMPING_FACTOR *
|
||||
((SLOW_NEAR_RADIUS - _distanceToNearestAvatar) / SLOW_NEAR_RADIUS);
|
||||
}
|
||||
if (_speedBrakes) {
|
||||
applyDamping(deltaTime, _velocity, linearDamping * SPEED_BRAKE_POWER, SQUARED_DAMPING_STRENGTH * SPEED_BRAKE_POWER);
|
||||
} else {
|
||||
applyDamping(deltaTime, _velocity, linearDamping, SQUARED_DAMPING_STRENGTH);
|
||||
}
|
||||
|
||||
if (OculusManager::isConnected()) {
|
||||
// these angles will be in radians
|
||||
float yaw, pitch, roll;
|
||||
OculusManager::getEulerAngles(yaw, pitch, roll);
|
||||
// ... so they need to be converted to degrees before we do math...
|
||||
|
||||
// The neck is limited in how much it can yaw, so we check its relative
|
||||
// yaw from the body and yaw the body if necessary.
|
||||
yaw *= DEGREES_PER_RADIAN;
|
||||
float bodyToHeadYaw = yaw - _oculusYawOffset;
|
||||
const float MAX_NECK_YAW = 85.0f; // degrees
|
||||
if ((fabs(bodyToHeadYaw) > 2.0f * MAX_NECK_YAW) && (yaw * _oculusYawOffset < 0.0f)) {
|
||||
// We've wrapped around the range for yaw so adjust
|
||||
// the measured yaw to be relative to _oculusYawOffset.
|
||||
if (yaw > 0.0f) {
|
||||
yaw -= 360.0f;
|
||||
} else {
|
||||
yaw += 360.0f;
|
||||
}
|
||||
bodyToHeadYaw = yaw - _oculusYawOffset;
|
||||
// update position
|
||||
if (glm::length2(_velocity) < EPSILON) {
|
||||
_velocity = glm::vec3(0.0f);
|
||||
} else {
|
||||
_position += _velocity * deltaTime;
|
||||
}
|
||||
|
||||
float delta = fabs(bodyToHeadYaw) - MAX_NECK_YAW;
|
||||
if (delta > 0.0f) {
|
||||
yaw = MAX_NECK_YAW;
|
||||
if (bodyToHeadYaw < 0.0f) {
|
||||
delta *= -1.0f;
|
||||
bodyToHeadYaw = -MAX_NECK_YAW;
|
||||
} else {
|
||||
bodyToHeadYaw = MAX_NECK_YAW;
|
||||
}
|
||||
// constrain _oculusYawOffset to be within range [-180,180]
|
||||
_oculusYawOffset = fmod((_oculusYawOffset + delta) + 180.0f, 360.0f) - 180.0f;
|
||||
|
||||
// We must adjust the body orientation using a delta rotation (rather than
|
||||
// doing yaw math) because the body's yaw ranges are not the same
|
||||
// as what the Oculus API provides.
|
||||
glm::vec3 UP_AXIS = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), UP_AXIS);
|
||||
orientation = orientation * bodyCorrection;
|
||||
}
|
||||
Head* head = getHead();
|
||||
head->setBaseYaw(bodyToHeadYaw);
|
||||
|
||||
head->setBasePitch(pitch * DEGREES_PER_RADIAN);
|
||||
head->setBaseRoll(roll * DEGREES_PER_RADIAN);
|
||||
}
|
||||
|
||||
// update the euler angles
|
||||
setOrientation(orientation);
|
||||
|
||||
// update moving flag based on speed
|
||||
const float MOVING_SPEED_THRESHOLD = 0.01f;
|
||||
float speed = glm::length(_velocity);
|
||||
_moving = speed > MOVING_SPEED_THRESHOLD;
|
||||
|
||||
_moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD;
|
||||
updateChatCircle(deltaTime);
|
||||
|
||||
_position += _velocity * deltaTime;
|
||||
|
||||
// update avatar skeleton and simulate hand and head
|
||||
getHand()->collideAgainstOurself();
|
||||
getHand()->simulate(deltaTime, true);
|
||||
|
||||
_skeletonModel.simulate(deltaTime);
|
||||
simulateAttachments(deltaTime);
|
||||
|
||||
// copy out the skeleton joints from the model
|
||||
_jointData.resize(_skeletonModel.getJointStateCount());
|
||||
|
@ -261,9 +196,6 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
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.0f);
|
||||
|
||||
// now that we're done stepping the avatar forward in time, compute new collisions
|
||||
if (_collisionGroups != 0) {
|
||||
Camera* myCamera = Application::getInstance()->getCamera();
|
||||
|
@ -317,14 +249,17 @@ void MyAvatar::updateFromGyros(float deltaTime) {
|
|||
}
|
||||
|
||||
// Set the rotation of the avatar's head (as seen by others, not affecting view frustum)
|
||||
// to be scaled. Pitch is greater to emphasize nodding behavior / synchrony.
|
||||
const float AVATAR_HEAD_PITCH_MAGNIFY = 1.0f;
|
||||
const float AVATAR_HEAD_YAW_MAGNIFY = 1.0f;
|
||||
const float AVATAR_HEAD_ROLL_MAGNIFY = 1.0f;
|
||||
// to be scaled such that when the user's physical head is pointing at edge of screen, the
|
||||
// avatar head is at the edge of the in-world view frustum. So while a real person may move
|
||||
// their head only 30 degrees or so, this may correspond to a 90 degree field of view.
|
||||
// Note that roll is magnified by a constant because it is not related to field of view.
|
||||
|
||||
float magnifyFieldOfView = Menu::getInstance()->getFieldOfView() / Menu::getInstance()->getRealWorldFieldOfView();
|
||||
|
||||
Head* head = getHead();
|
||||
head->setDeltaPitch(estimatedRotation.x * AVATAR_HEAD_PITCH_MAGNIFY);
|
||||
head->setDeltaYaw(estimatedRotation.y * AVATAR_HEAD_YAW_MAGNIFY);
|
||||
head->setDeltaRoll(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY);
|
||||
head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView);
|
||||
head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView);
|
||||
head->setDeltaRoll(estimatedRotation.z);
|
||||
|
||||
// Update torso lean distance based on accelerometer data
|
||||
const float TORSO_LENGTH = 0.5f;
|
||||
|
@ -407,18 +342,15 @@ void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const {
|
|||
|
||||
Faceshift* faceshift = Application::getInstance()->getFaceshift();
|
||||
|
||||
float pixelsPerDegree = screenHeight / Menu::getInstance()->getFieldOfView();
|
||||
|
||||
// Display small target box at center or head mouse target that can also be used to measure LOD
|
||||
float headPitch = getHead()->getFinalPitch();
|
||||
float headYaw = getHead()->getFinalYaw();
|
||||
//
|
||||
// It should be noted that the following constant is a function
|
||||
// how far the viewer's head is away from both the screen and the size of the screen,
|
||||
// which are both things we cannot know without adding a calibration phase.
|
||||
//
|
||||
const float PIXELS_PER_VERTICAL_DEGREE = 20.0f;
|
||||
|
||||
float aspectRatio = (float) screenWidth / (float) screenHeight;
|
||||
int headMouseX = screenWidth / 2.f - headYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE;
|
||||
int headMouseY = screenHeight / 2.f - headPitch * PIXELS_PER_VERTICAL_DEGREE;
|
||||
int headMouseX = screenWidth / 2.f - headYaw * aspectRatio * pixelsPerDegree;
|
||||
int headMouseY = screenHeight / 2.f - headPitch * pixelsPerDegree;
|
||||
|
||||
glColor3f(1.0f, 1.0f, 1.0f);
|
||||
glDisable(GL_LINE_SMOOTH);
|
||||
|
@ -435,8 +367,8 @@ void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const {
|
|||
|
||||
float avgEyePitch = faceshift->getEstimatedEyePitch();
|
||||
float avgEyeYaw = faceshift->getEstimatedEyeYaw();
|
||||
int eyeTargetX = (screenWidth / 2) - avgEyeYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE;
|
||||
int eyeTargetY = (screenHeight / 2) - avgEyePitch * PIXELS_PER_VERTICAL_DEGREE;
|
||||
int eyeTargetX = (screenWidth / 2) - avgEyeYaw * aspectRatio * pixelsPerDegree;
|
||||
int eyeTargetY = (screenHeight / 2) - avgEyePitch * pixelsPerDegree;
|
||||
|
||||
glColor3f(0.0f, 1.0f, 1.0f);
|
||||
glDisable(GL_LINE_SMOOTH);
|
||||
|
@ -491,6 +423,24 @@ void MyAvatar::saveData(QSettings* settings) {
|
|||
|
||||
settings->setValue("faceModelURL", _faceModelURL);
|
||||
settings->setValue("skeletonModelURL", _skeletonModelURL);
|
||||
|
||||
settings->beginWriteArray("attachmentData");
|
||||
for (int i = 0; i < _attachmentData.size(); i++) {
|
||||
settings->setArrayIndex(i);
|
||||
const AttachmentData& attachment = _attachmentData.at(i);
|
||||
settings->setValue("modelURL", attachment.modelURL);
|
||||
settings->setValue("jointName", attachment.jointName);
|
||||
settings->setValue("translation_x", attachment.translation.x);
|
||||
settings->setValue("translation_y", attachment.translation.y);
|
||||
settings->setValue("translation_z", attachment.translation.z);
|
||||
glm::vec3 eulers = safeEulerAngles(attachment.rotation);
|
||||
settings->setValue("rotation_x", eulers.x);
|
||||
settings->setValue("rotation_y", eulers.y);
|
||||
settings->setValue("rotation_z", eulers.z);
|
||||
settings->setValue("scale", attachment.scale);
|
||||
}
|
||||
settings->endArray();
|
||||
|
||||
settings->setValue("displayName", _displayName);
|
||||
|
||||
settings->endGroup();
|
||||
|
@ -519,6 +469,28 @@ void MyAvatar::loadData(QSettings* settings) {
|
|||
|
||||
setFaceModelURL(settings->value("faceModelURL", DEFAULT_HEAD_MODEL_URL).toUrl());
|
||||
setSkeletonModelURL(settings->value("skeletonModelURL").toUrl());
|
||||
|
||||
QVector<AttachmentData> attachmentData;
|
||||
int attachmentCount = settings->beginReadArray("attachmentData");
|
||||
for (int i = 0; i < attachmentCount; i++) {
|
||||
settings->setArrayIndex(i);
|
||||
AttachmentData attachment;
|
||||
attachment.modelURL = settings->value("modelURL").toUrl();
|
||||
attachment.jointName = settings->value("jointName").toString();
|
||||
attachment.translation.x = loadSetting(settings, "translation_x", 0.0f);
|
||||
attachment.translation.y = loadSetting(settings, "translation_y", 0.0f);
|
||||
attachment.translation.z = loadSetting(settings, "translation_z", 0.0f);
|
||||
glm::vec3 eulers;
|
||||
eulers.x = loadSetting(settings, "rotation_x", 0.0f);
|
||||
eulers.y = loadSetting(settings, "rotation_y", 0.0f);
|
||||
eulers.z = loadSetting(settings, "rotation_z", 0.0f);
|
||||
attachment.rotation = glm::quat(eulers);
|
||||
attachment.scale = loadSetting(settings, "scale", 1.0f);
|
||||
attachmentData.append(attachment);
|
||||
}
|
||||
settings->endArray();
|
||||
setAttachmentData(attachmentData);
|
||||
|
||||
setDisplayName(settings->value("displayName").toString());
|
||||
|
||||
settings->endGroup();
|
||||
|
@ -537,23 +509,6 @@ void MyAvatar::sendKillAvatar() {
|
|||
NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer);
|
||||
}
|
||||
|
||||
void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
|
||||
// first orbit horizontally
|
||||
glm::quat orientation = getOrientation();
|
||||
const float ANGULAR_SCALE = 0.5f;
|
||||
glm::quat rotation = glm::angleAxis(glm::radians(- deltaX * ANGULAR_SCALE), orientation * IDENTITY_UP);
|
||||
setPosition(position + rotation * (getPosition() - position));
|
||||
orientation = rotation * orientation;
|
||||
setOrientation(orientation);
|
||||
|
||||
// then vertically
|
||||
float oldPitch = getHead()->getBasePitch();
|
||||
getHead()->setBasePitch(oldPitch - deltaY * ANGULAR_SCALE);
|
||||
rotation = glm::angleAxis(glm::radians((getHead()->getBasePitch() - oldPitch)), orientation * IDENTITY_RIGHT);
|
||||
|
||||
setPosition(position + rotation * (getPosition() - position));
|
||||
}
|
||||
|
||||
void MyAvatar::updateLookAtTargetAvatar() {
|
||||
//
|
||||
// Look at the avatar whose eyes are closest to the ray in direction of my avatar's head
|
||||
|
@ -564,6 +519,7 @@ void MyAvatar::updateLookAtTargetAvatar() {
|
|||
float smallestAngleTo = MIN_LOOKAT_ANGLE;
|
||||
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
|
||||
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
|
||||
avatar->setIsLookAtTarget(false);
|
||||
if (!avatar->isMyAvatar()) {
|
||||
float angleTo = glm::angle(getHead()->getFinalOrientation() * glm::vec3(0.0f, 0.0f, -1.0f),
|
||||
glm::normalize(avatar->getHead()->getEyePosition() - getHead()->getEyePosition()));
|
||||
|
@ -574,6 +530,9 @@ void MyAvatar::updateLookAtTargetAvatar() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (_lookAtTargetAvatar) {
|
||||
static_cast<Avatar*>(_lookAtTargetAvatar.data())->setIsLookAtTarget(true);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::clearLookAtTargetAvatar() {
|
||||
|
@ -617,7 +576,8 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ?
|
||||
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
|
||||
_skeletonModel.render(1.0f, modelRenderMode);
|
||||
|
||||
renderAttachments(modelRenderMode);
|
||||
|
||||
// Render head so long as the camera isn't inside it
|
||||
if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) {
|
||||
getHead()->render(1.0f, modelRenderMode);
|
||||
|
@ -633,6 +593,214 @@ bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode rend
|
|||
(glm::length(cameraPosition - head->calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale);
|
||||
}
|
||||
|
||||
void MyAvatar::updateOrientation(float deltaTime) {
|
||||
// Gather rotation information from keyboard
|
||||
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime;
|
||||
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime;
|
||||
getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime);
|
||||
|
||||
// update body yaw by body yaw delta
|
||||
glm::quat orientation = getOrientation() * glm::quat(glm::radians(
|
||||
glm::vec3(_bodyPitchDelta, _bodyYawDelta, _bodyRollDelta) * deltaTime));
|
||||
|
||||
// decay body rotation momentum
|
||||
const float BODY_SPIN_FRICTION = 7.5f;
|
||||
float bodySpinMomentum = 1.0f - BODY_SPIN_FRICTION * deltaTime;
|
||||
if (bodySpinMomentum < 0.0f) { bodySpinMomentum = 0.0f; }
|
||||
_bodyPitchDelta *= bodySpinMomentum;
|
||||
_bodyYawDelta *= bodySpinMomentum;
|
||||
_bodyRollDelta *= bodySpinMomentum;
|
||||
|
||||
float MINIMUM_ROTATION_RATE = 2.0f;
|
||||
if (fabs(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { _bodyYawDelta = 0.0f; }
|
||||
if (fabs(_bodyRollDelta) < MINIMUM_ROTATION_RATE) { _bodyRollDelta = 0.0f; }
|
||||
if (fabs(_bodyPitchDelta) < MINIMUM_ROTATION_RATE) { _bodyPitchDelta = 0.0f; }
|
||||
|
||||
if (OculusManager::isConnected()) {
|
||||
// these angles will be in radians
|
||||
float yaw, pitch, roll;
|
||||
OculusManager::getEulerAngles(yaw, pitch, roll);
|
||||
// ... so they need to be converted to degrees before we do math...
|
||||
|
||||
// The neck is limited in how much it can yaw, so we check its relative
|
||||
// yaw from the body and yaw the body if necessary.
|
||||
yaw *= DEGREES_PER_RADIAN;
|
||||
float bodyToHeadYaw = yaw - _oculusYawOffset;
|
||||
const float MAX_NECK_YAW = 85.0f; // degrees
|
||||
if ((fabs(bodyToHeadYaw) > 2.0f * MAX_NECK_YAW) && (yaw * _oculusYawOffset < 0.0f)) {
|
||||
// We've wrapped around the range for yaw so adjust
|
||||
// the measured yaw to be relative to _oculusYawOffset.
|
||||
if (yaw > 0.0f) {
|
||||
yaw -= 360.0f;
|
||||
} else {
|
||||
yaw += 360.0f;
|
||||
}
|
||||
bodyToHeadYaw = yaw - _oculusYawOffset;
|
||||
}
|
||||
|
||||
float delta = fabs(bodyToHeadYaw) - MAX_NECK_YAW;
|
||||
if (delta > 0.0f) {
|
||||
yaw = MAX_NECK_YAW;
|
||||
if (bodyToHeadYaw < 0.0f) {
|
||||
delta *= -1.0f;
|
||||
bodyToHeadYaw = -MAX_NECK_YAW;
|
||||
} else {
|
||||
bodyToHeadYaw = MAX_NECK_YAW;
|
||||
}
|
||||
// constrain _oculusYawOffset to be within range [-180,180]
|
||||
_oculusYawOffset = fmod((_oculusYawOffset + delta) + 180.0f, 360.0f) - 180.0f;
|
||||
|
||||
// We must adjust the body orientation using a delta rotation (rather than
|
||||
// doing yaw math) because the body's yaw ranges are not the same
|
||||
// as what the Oculus API provides.
|
||||
glm::vec3 UP_AXIS = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), UP_AXIS);
|
||||
orientation = orientation * bodyCorrection;
|
||||
}
|
||||
Head* head = getHead();
|
||||
head->setBaseYaw(bodyToHeadYaw);
|
||||
|
||||
head->setBasePitch(pitch * DEGREES_PER_RADIAN);
|
||||
head->setBaseRoll(roll * DEGREES_PER_RADIAN);
|
||||
}
|
||||
|
||||
// update the euler angles
|
||||
setOrientation(orientation);
|
||||
}
|
||||
|
||||
void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) {
|
||||
// Increase motor velocity until its length is equal to _maxMotorSpeed.
|
||||
if (!(_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED)) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 localVelocity = _velocity;
|
||||
if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) {
|
||||
glm::quat orientation = getHead()->getCameraOrientation();
|
||||
localVelocity = glm::inverse(orientation) * _velocity;
|
||||
}
|
||||
|
||||
// Compute keyboard input
|
||||
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
|
||||
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
|
||||
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
|
||||
|
||||
glm::vec3 direction = front + right + up;
|
||||
float directionLength = glm::length(direction);
|
||||
|
||||
// Compute motor magnitude
|
||||
if (directionLength > EPSILON) {
|
||||
direction /= directionLength;
|
||||
// the finalMotorSpeed depends on whether we are walking or not
|
||||
const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f;
|
||||
const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED;
|
||||
float finalMaxMotorSpeed = walking ? MAX_WALKING_SPEED : _maxMotorSpeed;
|
||||
|
||||
float motorLength = glm::length(_motorVelocity);
|
||||
if (motorLength < MIN_KEYBOARD_CONTROL_SPEED) {
|
||||
// an active keyboard motor should never be slower than this
|
||||
_motorVelocity = MIN_KEYBOARD_CONTROL_SPEED * direction;
|
||||
} else {
|
||||
float MOTOR_LENGTH_TIMESCALE = 1.5f;
|
||||
float tau = glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f);
|
||||
float INCREASE_FACTOR = 2.0f;
|
||||
//_motorVelocity *= 1.0f + tau * INCREASE_FACTOR;
|
||||
motorLength *= 1.0f + tau * INCREASE_FACTOR;
|
||||
if (motorLength > finalMaxMotorSpeed) {
|
||||
motorLength = finalMaxMotorSpeed;
|
||||
}
|
||||
_motorVelocity = motorLength * direction;
|
||||
}
|
||||
_isPushing = true;
|
||||
} else {
|
||||
// motor opposes motion (wants to be at rest)
|
||||
_motorVelocity = - localVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
float MyAvatar::computeMotorTimescale() {
|
||||
// The timescale of the motor is the approximate time it takes for the motor to
|
||||
// accomplish its intended velocity. A short timescale makes the motor strong,
|
||||
// and a long timescale makes it weak. The value of timescale to use depends
|
||||
// on what the motor is doing:
|
||||
//
|
||||
// (1) braking --> short timescale (aggressive motor assertion)
|
||||
// (2) pushing --> medium timescale (mild motor assertion)
|
||||
// (3) inactive --> long timescale (gentle friction for low speeds)
|
||||
//
|
||||
// TODO: recover extra braking behavior when flying close to nearest avatar
|
||||
|
||||
float MIN_MOTOR_TIMESCALE = 0.125f;
|
||||
float MAX_MOTOR_TIMESCALE = 0.5f;
|
||||
float MIN_BRAKE_SPEED = 0.4f;
|
||||
|
||||
float timescale = MAX_MOTOR_TIMESCALE;
|
||||
float speed = glm::length(_velocity);
|
||||
bool areThrusting = (glm::length2(_thrust) > EPSILON);
|
||||
|
||||
if (_wasPushing && !(_isPushing || areThrusting) && speed > MIN_BRAKE_SPEED) {
|
||||
// we don't change _wasPushing for this case -->
|
||||
// keeps the brakes on until we go below MIN_BRAKE_SPEED
|
||||
timescale = MIN_MOTOR_TIMESCALE;
|
||||
} else {
|
||||
if (_isPushing) {
|
||||
timescale = _motorTimescale;
|
||||
}
|
||||
_wasPushing = _isPushing || areThrusting;
|
||||
}
|
||||
_isPushing = false;
|
||||
return timescale;
|
||||
}
|
||||
|
||||
void MyAvatar::applyMotor(float deltaTime) {
|
||||
if (!( _motionBehaviors & AVATAR_MOTION_MOTOR_ENABLED)) {
|
||||
// nothing to do --> early exit
|
||||
return;
|
||||
}
|
||||
glm::vec3 targetVelocity = _motorVelocity;
|
||||
if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) {
|
||||
// rotate _motorVelocity into world frame
|
||||
glm::quat rotation = getHead()->getCameraOrientation();
|
||||
targetVelocity = rotation * _motorVelocity;
|
||||
}
|
||||
|
||||
glm::vec3 targetDirection(0.f);
|
||||
if (glm::length2(targetVelocity) > EPSILON) {
|
||||
targetDirection = glm::normalize(targetVelocity);
|
||||
}
|
||||
glm::vec3 deltaVelocity = targetVelocity - _velocity;
|
||||
|
||||
if (_motionBehaviors & AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY && glm::length2(_gravity) > EPSILON) {
|
||||
// For now we subtract the component parallel to gravity but what we need to do is:
|
||||
// TODO: subtract the component perp to the local surface normal (motor only pushes in surface plane).
|
||||
glm::vec3 gravityDirection = glm::normalize(_gravity);
|
||||
glm::vec3 parallelDelta = glm::dot(deltaVelocity, gravityDirection) * gravityDirection;
|
||||
if (glm::dot(targetVelocity, _velocity) > 0.0f) {
|
||||
// remove parallel part from deltaVelocity
|
||||
deltaVelocity -= parallelDelta;
|
||||
}
|
||||
}
|
||||
|
||||
// simple critical damping
|
||||
float timescale = computeMotorTimescale();
|
||||
float tau = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
|
||||
_velocity += tau * deltaVelocity;
|
||||
}
|
||||
|
||||
void MyAvatar::applyThrust(float deltaTime) {
|
||||
_velocity += _thrust * deltaTime;
|
||||
float speed = glm::length(_velocity);
|
||||
// cap the speed that thrust can achieve
|
||||
if (speed > MAX_AVATAR_SPEED) {
|
||||
_velocity *= MAX_AVATAR_SPEED / speed;
|
||||
}
|
||||
// zero thrust so we don't pile up thrust from other sources
|
||||
_thrust = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
/* Keep this code for the short term as reference in case we need to further tune the new model
|
||||
* to achieve legacy movement response.
|
||||
void MyAvatar::updateThrust(float deltaTime) {
|
||||
//
|
||||
// Gather thrust information from keyboard and sensors to apply to avatar motion
|
||||
|
@ -678,10 +846,6 @@ void MyAvatar::updateThrust(float deltaTime) {
|
|||
}
|
||||
_lastBodyPenetration = glm::vec3(0.0f);
|
||||
|
||||
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime;
|
||||
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime;
|
||||
getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * 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]) {
|
||||
const float THRUST_INCREASE_RATE = 1.05f;
|
||||
|
@ -712,8 +876,34 @@ void MyAvatar::updateThrust(float deltaTime) {
|
|||
if (_isThrustOn || (_speedBrakes && (glm::length(_velocity) < MIN_SPEED_BRAKE_VELOCITY))) {
|
||||
_speedBrakes = false;
|
||||
}
|
||||
_velocity += _thrust * deltaTime;
|
||||
|
||||
// Zero thrust out now that we've added it to velocity in this frame
|
||||
_thrust = glm::vec3(0.0f);
|
||||
|
||||
// apply linear damping
|
||||
const float MAX_STATIC_FRICTION_SPEED = 0.5f;
|
||||
const float STATIC_FRICTION_STRENGTH = _scale * 20.0f;
|
||||
applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_SPEED, STATIC_FRICTION_STRENGTH);
|
||||
|
||||
const float LINEAR_DAMPING_STRENGTH = 0.5f;
|
||||
const float SPEED_BRAKE_POWER = _scale * 10.0f;
|
||||
const float SQUARED_DAMPING_STRENGTH = 0.007f;
|
||||
|
||||
const float SLOW_NEAR_RADIUS = 5.0f;
|
||||
float linearDamping = LINEAR_DAMPING_STRENGTH;
|
||||
const float NEAR_AVATAR_DAMPING_FACTOR = 50.0f;
|
||||
if (_distanceToNearestAvatar < _scale * SLOW_NEAR_RADIUS) {
|
||||
linearDamping *= 1.0f + NEAR_AVATAR_DAMPING_FACTOR *
|
||||
((SLOW_NEAR_RADIUS - _distanceToNearestAvatar) / SLOW_NEAR_RADIUS);
|
||||
}
|
||||
if (_speedBrakes) {
|
||||
applyDamping(deltaTime, _velocity, linearDamping * SPEED_BRAKE_POWER, SQUARED_DAMPING_STRENGTH * SPEED_BRAKE_POWER);
|
||||
} else {
|
||||
applyDamping(deltaTime, _velocity, linearDamping, SQUARED_DAMPING_STRENGTH);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void MyAvatar::updateHandMovementAndTouching(float deltaTime) {
|
||||
glm::quat orientation = getOrientation();
|
||||
|
@ -760,7 +950,6 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
|
|||
if (Application::getInstance()->getEnvironment()->findCapsulePenetration(
|
||||
_position - up * (pelvisFloatingHeight - radius),
|
||||
_position + up * (getSkeletonHeight() - pelvisFloatingHeight + radius), radius, penetration)) {
|
||||
_lastCollisionPosition = _position;
|
||||
updateCollisionSound(penetration, deltaTime, ENVIRONMENT_COLLISION_FREQUENCY);
|
||||
applyHardCollision(penetration, ENVIRONMENT_SURFACE_ELASTICITY, ENVIRONMENT_SURFACE_DAMPING);
|
||||
}
|
||||
|
@ -772,12 +961,59 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
|
|||
myCollisions.clear();
|
||||
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
|
||||
if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions)) {
|
||||
const float VOXEL_ELASTICITY = 0.4f;
|
||||
const float VOXEL_ELASTICITY = 0.0f;
|
||||
const float VOXEL_DAMPING = 0.0f;
|
||||
for (int i = 0; i < myCollisions.size(); ++i) {
|
||||
CollisionInfo* collision = myCollisions[i];
|
||||
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
|
||||
|
||||
if (glm::length2(_gravity) > EPSILON) {
|
||||
if (myCollisions.size() == 1) {
|
||||
// trivial case
|
||||
CollisionInfo* collision = myCollisions[0];
|
||||
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
|
||||
_lastFloorContactPoint = collision->_contactPoint - collision->_penetration;
|
||||
} else {
|
||||
// This is special collision handling for when walking on a voxel field which
|
||||
// prevents snagging at corners and seams.
|
||||
|
||||
// sift through the collisions looking for one against the "floor"
|
||||
int floorIndex = 0;
|
||||
float distanceToFloor = 0.0f;
|
||||
float penetrationWithFloor = 0.0f;
|
||||
for (int i = 0; i < myCollisions.size(); ++i) {
|
||||
CollisionInfo* collision = myCollisions[i];
|
||||
float distance = glm::dot(_gravity, collision->_contactPoint - _position);
|
||||
if (distance > distanceToFloor) {
|
||||
distanceToFloor = distance;
|
||||
penetrationWithFloor = glm::dot(_gravity, collision->_penetration);
|
||||
floorIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// step through the collisions again and apply each that is not redundant
|
||||
glm::vec3 oldPosition = _position;
|
||||
for (int i = 0; i < myCollisions.size(); ++i) {
|
||||
CollisionInfo* collision = myCollisions[i];
|
||||
if (i == floorIndex) {
|
||||
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
|
||||
_lastFloorContactPoint = collision->_contactPoint - collision->_penetration;
|
||||
} else {
|
||||
float distance = glm::dot(_gravity, collision->_contactPoint - oldPosition);
|
||||
float penetration = glm::dot(_gravity, collision->_penetration);
|
||||
if (fabsf(distance - distanceToFloor) > penetrationWithFloor || penetration > penetrationWithFloor) {
|
||||
// resolution of the deepest penetration would not resolve this one
|
||||
// so we apply the collision
|
||||
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no gravity -- apply all collisions
|
||||
for (int i = 0; i < myCollisions.size(); ++i) {
|
||||
CollisionInfo* collision = myCollisions[i];
|
||||
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
|
||||
}
|
||||
}
|
||||
|
||||
const float VOXEL_COLLISION_FREQUENCY = 0.5f;
|
||||
updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY);
|
||||
}
|
||||
|
@ -1141,8 +1377,7 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::updateMotionBehaviors() {
|
||||
_motionBehaviors = 0;
|
||||
void MyAvatar::updateMotionBehaviorsFromMenu() {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
|
||||
_motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
|
||||
// Environmental and Local gravities are incompatible. Environmental setting trumps local.
|
||||
|
@ -1162,8 +1397,14 @@ void MyAvatar::setCollisionGroups(quint32 collisionGroups) {
|
|||
menu->setIsOptionChecked(MenuOption::CollideWithParticles, (bool)(_collisionGroups & COLLISION_GROUP_PARTICLES));
|
||||
}
|
||||
|
||||
void MyAvatar::setMotionBehaviors(quint32 flags) {
|
||||
_motionBehaviors = flags;
|
||||
void MyAvatar::setMotionBehaviorsByScript(quint32 flags) {
|
||||
// start with the defaults
|
||||
_motionBehaviors = AVATAR_MOTION_DEFAULTS;
|
||||
|
||||
// add the set scriptable bits
|
||||
_motionBehaviors += flags & AVATAR_MOTION_SCRIPTABLE_BITS;
|
||||
|
||||
// reconcile incompatible settings from menu (if any)
|
||||
Menu* menu = Menu::getInstance();
|
||||
menu->setIsOptionChecked(MenuOption::ObeyEnvironmentalGravity, (bool)(_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY));
|
||||
// Environmental and Local gravities are incompatible. Environmental setting trumps local.
|
||||
|
|
|
@ -28,7 +28,7 @@ enum AvatarHandState
|
|||
class MyAvatar : public Avatar {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
|
||||
Q_PROPERTY(quint32 motionBehaviors READ getMotionBehaviors WRITE setMotionBehaviors)
|
||||
Q_PROPERTY(quint32 motionBehaviors READ getMotionBehaviorsForScript WRITE setMotionBehaviorsByScript)
|
||||
Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setLocalGravity)
|
||||
|
||||
public:
|
||||
|
@ -77,8 +77,6 @@ public:
|
|||
|
||||
static void sendKillAvatar();
|
||||
|
||||
void orbit(const glm::vec3& position, int deltaX, int deltaY);
|
||||
|
||||
Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; }
|
||||
AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
|
||||
void updateLookAtTargetAvatar();
|
||||
|
@ -90,8 +88,9 @@ public:
|
|||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
|
||||
virtual void setCollisionGroups(quint32 collisionGroups);
|
||||
void setMotionBehaviors(quint32 flags);
|
||||
quint32 getMotionBehaviors() const { return _motionBehaviors; }
|
||||
|
||||
void setMotionBehaviorsByScript(quint32 flags);
|
||||
quint32 getMotionBehaviorsForScript() const { return _motionBehaviors & AVATAR_MOTION_SCRIPTABLE_BITS; }
|
||||
|
||||
void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration);
|
||||
|
||||
|
@ -109,7 +108,7 @@ public slots:
|
|||
glm::vec3 getThrust() { return _thrust; };
|
||||
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
|
||||
|
||||
void updateMotionBehaviors();
|
||||
void updateMotionBehaviorsFromMenu();
|
||||
|
||||
signals:
|
||||
void transformChanged();
|
||||
|
@ -123,17 +122,18 @@ private:
|
|||
glm::vec3 _gravity;
|
||||
glm::vec3 _environmentGravity;
|
||||
float _distanceToNearestAvatar; // How close is the nearest avatar?
|
||||
|
||||
// motion stuff
|
||||
glm::vec3 _lastCollisionPosition;
|
||||
bool _speedBrakes;
|
||||
glm::vec3 _thrust; // final acceleration for the current frame
|
||||
bool _isThrustOn;
|
||||
float _thrustMultiplier;
|
||||
|
||||
bool _wasPushing;
|
||||
bool _isPushing;
|
||||
glm::vec3 _thrust; // final acceleration from outside sources for the current frame
|
||||
|
||||
glm::vec3 _motorVelocity; // intended velocity of avatar motion
|
||||
float _motorTimescale; // timescale for avatar motor to achieve its desired velocity
|
||||
float _maxMotorSpeed;
|
||||
quint32 _motionBehaviors;
|
||||
|
||||
glm::vec3 _lastBodyPenetration;
|
||||
glm::vec3 _lastFloorContactPoint;
|
||||
QWeakPointer<AvatarData> _lookAtTargetAvatar;
|
||||
glm::vec3 _targetAvatarPosition;
|
||||
bool _shouldRender;
|
||||
|
@ -141,7 +141,11 @@ private:
|
|||
float _oculusYawOffset;
|
||||
|
||||
// private methods
|
||||
void updateThrust(float deltaTime);
|
||||
void updateOrientation(float deltaTime);
|
||||
void updateMotorFromKeyboard(float deltaTime, bool walking);
|
||||
float computeMotorTimescale();
|
||||
void applyMotor(float deltaTime);
|
||||
void applyThrust(float deltaTime);
|
||||
void updateHandMovementAndTouching(float deltaTime);
|
||||
void updateCollisionWithAvatars(float deltaTime);
|
||||
void updateCollisionWithEnvironment(float deltaTime, float radius);
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include "InterfaceConfig.h"
|
||||
|
||||
#include "Menu.h"
|
||||
#include "ModelTreeRenderer.h"
|
||||
|
||||
ModelTreeRenderer::ModelTreeRenderer() :
|
||||
|
@ -118,6 +118,15 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
|
|||
}
|
||||
}
|
||||
|
||||
float ModelTreeRenderer::getSizeScale() const {
|
||||
return Menu::getInstance()->getVoxelSizeScale();
|
||||
}
|
||||
|
||||
int ModelTreeRenderer::getBoundaryLevelAdjust() const {
|
||||
return Menu::getInstance()->getBoundaryLevelAdjust();
|
||||
}
|
||||
|
||||
|
||||
void ModelTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
|
||||
static_cast<ModelTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ public:
|
|||
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
|
||||
virtual PacketType getExpectedPacketType() const { return PacketTypeModelData; }
|
||||
virtual void renderElement(OctreeElement* element, RenderArgs* args);
|
||||
virtual float getSizeScale() const;
|
||||
virtual int getBoundaryLevelAdjust() const;
|
||||
|
||||
void update();
|
||||
|
||||
|
|
|
@ -509,6 +509,24 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo
|
|||
}
|
||||
}
|
||||
|
||||
bool Model::getJointPosition(int jointIndex, glm::vec3& position) const {
|
||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
position = _translation + extractTranslation(_jointStates[jointIndex].transform);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) const {
|
||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
rotation = _jointStates[jointIndex].combinedRotation *
|
||||
(fromBind ? _geometry->getFBXGeometry().joints[jointIndex].inverseBindRotation :
|
||||
_geometry->getFBXGeometry().joints[jointIndex].inverseDefaultRotation);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Model::clearShapes() {
|
||||
for (int i = 0; i < _jointShapes.size(); ++i) {
|
||||
delete _jointShapes[i];
|
||||
|
@ -592,18 +610,10 @@ void Model::rebuildShapes() {
|
|||
capsule->setRotation(combinedRotations[i] * joint.shapeRotation);
|
||||
_jointShapes.push_back(capsule);
|
||||
|
||||
glm::vec3 endPoint;
|
||||
capsule->getEndPoint(endPoint);
|
||||
glm::vec3 startPoint;
|
||||
capsule->getStartPoint(startPoint);
|
||||
|
||||
// add some points that bound a sphere at the center of the capsule
|
||||
glm::vec3 axis = glm::vec3(radius);
|
||||
shapeExtents.addPoint(worldPosition + axis);
|
||||
shapeExtents.addPoint(worldPosition - axis);
|
||||
|
||||
// add the two furthest surface points of the capsule
|
||||
axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint);
|
||||
glm::vec3 axis;
|
||||
capsule->computeNormalizedAxis(axis);
|
||||
axis = halfHeight * axis + glm::vec3(radius);
|
||||
shapeExtents.addPoint(worldPosition + axis);
|
||||
shapeExtents.addPoint(worldPosition - axis);
|
||||
|
||||
|
@ -637,7 +647,7 @@ void Model::rebuildShapes() {
|
|||
glm::quat inverseRotation = glm::inverse(_rotation);
|
||||
glm::vec3 rootPosition = extractTranslation(transforms[rootIndex]);
|
||||
_boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition);
|
||||
_boundingShape.setPosition(_translation - _rotation * _boundingShapeLocalOffset);
|
||||
_boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset);
|
||||
_boundingShape.setRotation(_rotation);
|
||||
}
|
||||
|
||||
|
@ -957,24 +967,6 @@ void Model::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint
|
|||
// nothing by default
|
||||
}
|
||||
|
||||
bool Model::getJointPosition(int jointIndex, glm::vec3& position) const {
|
||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
position = _translation + extractTranslation(_jointStates[jointIndex].transform);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) const {
|
||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
rotation = _jointStates[jointIndex].combinedRotation *
|
||||
(fromBind ? _geometry->getFBXGeometry().joints[jointIndex].inverseBindRotation :
|
||||
_geometry->getFBXGeometry().joints[jointIndex].inverseDefaultRotation);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation, bool useRotation,
|
||||
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment) {
|
||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||
|
|
|
@ -180,6 +180,9 @@ public:
|
|||
/// Returns the extended length from the right hand to its first free ancestor.
|
||||
float getRightArmLength() const;
|
||||
|
||||
bool getJointPosition(int jointIndex, glm::vec3& position) const;
|
||||
bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const;
|
||||
|
||||
void clearShapes();
|
||||
void rebuildShapes();
|
||||
void updateShapePositions();
|
||||
|
@ -269,9 +272,6 @@ protected:
|
|||
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||
|
||||
bool getJointPosition(int jointIndex, glm::vec3& position) const;
|
||||
bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const;
|
||||
|
||||
bool setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation = glm::quat(),
|
||||
bool useRotation = false, int lastFreeIndex = -1, bool allIntermediatesFree = false,
|
||||
const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f));
|
||||
|
|
154
interface/src/ui/AttachmentsDialog.cpp
Normal file
154
interface/src/ui/AttachmentsDialog.cpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
//
|
||||
// AttachmentsDialog.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/4/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFormLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Application.h"
|
||||
#include "AttachmentsDialog.h"
|
||||
|
||||
AttachmentsDialog::AttachmentsDialog() :
|
||||
QDialog(Application::getInstance()->getWindow()) {
|
||||
|
||||
setWindowTitle("Edit Attachments");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
setLayout(layout);
|
||||
|
||||
QScrollArea* area = new QScrollArea();
|
||||
layout->addWidget(area);
|
||||
area->setWidgetResizable(true);
|
||||
QWidget* container = new QWidget();
|
||||
container->setLayout(_attachments = new QVBoxLayout());
|
||||
container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
||||
area->setWidget(container);
|
||||
|
||||
foreach (const AttachmentData& data, Application::getInstance()->getAvatar()->getAttachmentData()) {
|
||||
addAttachment(data);
|
||||
}
|
||||
|
||||
QPushButton* newAttachment = new QPushButton("New Attachment");
|
||||
connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment()));
|
||||
layout->addWidget(newAttachment);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
layout->addWidget(buttons);
|
||||
connect(buttons, SIGNAL(accepted()), SLOT(deleteLater()));
|
||||
|
||||
setMinimumSize(600, 600);
|
||||
}
|
||||
|
||||
void AttachmentsDialog::updateAttachmentData() {
|
||||
QVector<AttachmentData> data;
|
||||
for (int i = 0; i < _attachments->count(); i++) {
|
||||
data.append(static_cast<AttachmentPanel*>(_attachments->itemAt(i)->widget())->getAttachmentData());
|
||||
}
|
||||
Application::getInstance()->getAvatar()->setAttachmentData(data);
|
||||
}
|
||||
|
||||
void AttachmentsDialog::addAttachment(const AttachmentData& data) {
|
||||
_attachments->addWidget(new AttachmentPanel(this, data));
|
||||
}
|
||||
|
||||
static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||
box->setSingleStep(0.01);
|
||||
box->setMinimum(-FLT_MAX);
|
||||
box->setMaximum(FLT_MAX);
|
||||
box->setValue(value);
|
||||
dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
return box;
|
||||
}
|
||||
|
||||
static QDoubleSpinBox* createRotationBox(AttachmentsDialog* dialog, float value) {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||
box->setMinimum(-180.0);
|
||||
box->setMaximum(180.0);
|
||||
box->setWrapping(true);
|
||||
box->setValue(value);
|
||||
dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
return box;
|
||||
}
|
||||
|
||||
AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) {
|
||||
QFormLayout* layout = new QFormLayout();
|
||||
setLayout(layout);
|
||||
|
||||
QHBoxLayout* urlBox = new QHBoxLayout();
|
||||
layout->addRow("Model URL:", urlBox);
|
||||
urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1);
|
||||
_modelURL->setText(data.modelURL.toString());
|
||||
dialog->connect(_modelURL, SIGNAL(returnPressed()), SLOT(updateAttachmentData()));
|
||||
QPushButton* chooseURL = new QPushButton("Choose");
|
||||
urlBox->addWidget(chooseURL);
|
||||
connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL()));
|
||||
|
||||
layout->addRow("Joint:", _jointName = new QComboBox());
|
||||
QSharedPointer<NetworkGeometry> geometry = Application::getInstance()->getAvatar()->getSkeletonModel().getGeometry();
|
||||
if (geometry && geometry->isLoaded()) {
|
||||
foreach (const FBXJoint& joint, geometry->getFBXGeometry().joints) {
|
||||
_jointName->addItem(joint.name);
|
||||
}
|
||||
}
|
||||
_jointName->setCurrentText(data.jointName);
|
||||
dialog->connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(updateAttachmentData()));
|
||||
|
||||
QHBoxLayout* translationBox = new QHBoxLayout();
|
||||
translationBox->addWidget(_translationX = createTranslationBox(dialog, data.translation.x));
|
||||
translationBox->addWidget(_translationY = createTranslationBox(dialog, data.translation.y));
|
||||
translationBox->addWidget(_translationZ = createTranslationBox(dialog, data.translation.z));
|
||||
layout->addRow("Translation:", translationBox);
|
||||
|
||||
QHBoxLayout* rotationBox = new QHBoxLayout();
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation));
|
||||
rotationBox->addWidget(_rotationX = createRotationBox(dialog, eulers.x));
|
||||
rotationBox->addWidget(_rotationY = createRotationBox(dialog, eulers.y));
|
||||
rotationBox->addWidget(_rotationZ = createRotationBox(dialog, eulers.z));
|
||||
layout->addRow("Rotation:", rotationBox);
|
||||
|
||||
layout->addRow("Scale:", _scale = new QDoubleSpinBox());
|
||||
_scale->setSingleStep(0.01);
|
||||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setValue(data.scale);
|
||||
dialog->connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
|
||||
|
||||
QPushButton* remove = new QPushButton("Delete");
|
||||
layout->addRow(remove);
|
||||
connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater()));
|
||||
}
|
||||
|
||||
AttachmentData AttachmentPanel::getAttachmentData() const {
|
||||
AttachmentData data;
|
||||
data.modelURL = _modelURL->text();
|
||||
data.jointName = _jointName->currentText();
|
||||
data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value());
|
||||
data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value())));
|
||||
data.scale = _scale->value();
|
||||
return data;
|
||||
}
|
||||
|
||||
void AttachmentPanel::chooseModelURL() {
|
||||
ModelsBrowser modelBrowser(ATTACHMENT_MODEL, this);
|
||||
connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&)));
|
||||
modelBrowser.browse();
|
||||
}
|
||||
|
||||
void AttachmentPanel::setModelURL(const QString& url) {
|
||||
_modelURL->setText(url);
|
||||
emit _modelURL->returnPressed();
|
||||
}
|
73
interface/src/ui/AttachmentsDialog.h
Normal file
73
interface/src/ui/AttachmentsDialog.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// AttachmentsDialog.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/4/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AttachmentsDialog_h
|
||||
#define hifi_AttachmentsDialog_h
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include <AvatarData.h>
|
||||
|
||||
class QComboBox;
|
||||
class QDoubleSpinner;
|
||||
class QLineEdit;
|
||||
class QVBoxLayout;
|
||||
|
||||
/// Allows users to edit the avatar attachments.
|
||||
class AttachmentsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AttachmentsDialog();
|
||||
|
||||
public slots:
|
||||
|
||||
void updateAttachmentData();
|
||||
|
||||
private slots:
|
||||
|
||||
void addAttachment(const AttachmentData& data = AttachmentData());
|
||||
|
||||
private:
|
||||
|
||||
QVBoxLayout* _attachments;
|
||||
};
|
||||
|
||||
/// A panel controlling a single attachment.
|
||||
class AttachmentPanel : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData());
|
||||
|
||||
AttachmentData getAttachmentData() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void chooseModelURL();
|
||||
void setModelURL(const QString& url);
|
||||
|
||||
private:
|
||||
|
||||
QLineEdit* _modelURL;
|
||||
QComboBox* _jointName;
|
||||
QDoubleSpinBox* _translationX;
|
||||
QDoubleSpinBox* _translationY;
|
||||
QDoubleSpinBox* _translationZ;
|
||||
QDoubleSpinBox* _rotationX;
|
||||
QDoubleSpinBox* _rotationY;
|
||||
QDoubleSpinBox* _rotationZ;
|
||||
QDoubleSpinBox* _scale;
|
||||
};
|
||||
|
||||
#endif // hifi_AttachmentsDialog_h
|
|
@ -26,17 +26,23 @@
|
|||
|
||||
#include "ChatWindow.h"
|
||||
|
||||
|
||||
|
||||
const int NUM_MESSAGES_TO_TIME_STAMP = 20;
|
||||
|
||||
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)");
|
||||
const QRegularExpression regexHifiLinks("([#@]\\S+)");
|
||||
const QString mentionSoundsPath("/sounds/mention/");
|
||||
const QString mentionRegex("@(\\b%1\\b)");
|
||||
|
||||
ChatWindow::ChatWindow(QWidget* parent) :
|
||||
FramelessDialog(parent, 0, POSITION_RIGHT),
|
||||
ui(new Ui::ChatWindow),
|
||||
numMessagesAfterLastTimeStamp(0),
|
||||
_mousePressed(false),
|
||||
_mouseStartPosition()
|
||||
_mouseStartPosition(),
|
||||
_trayIcon(parent),
|
||||
_effectPlayer()
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
|
@ -77,16 +83,47 @@ ChatWindow::ChatWindow(QWidget* parent) :
|
|||
ui->usersWidget->hide();
|
||||
ui->messagesScrollArea->hide();
|
||||
ui->messagePlainTextEdit->hide();
|
||||
connect(&xmppClient, SIGNAL(connected()), this, SLOT(connected()));
|
||||
connect(&XmppClient::getInstance(), SIGNAL(joinedPublicChatRoom()), this, SLOT(connected()));
|
||||
}
|
||||
connect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage)));
|
||||
connect(&_trayIcon, SIGNAL(messageClicked()), this, SLOT(notificationClicked()));
|
||||
#endif
|
||||
|
||||
QDir mentionSoundsDir(Application::resourcesPath() + mentionSoundsPath);
|
||||
_mentionSounds = mentionSoundsDir.entryList(QDir::Files);
|
||||
_trayIcon.setIcon(QIcon( Application::resourcesPath() + "/images/hifi-logo.svg"));
|
||||
}
|
||||
|
||||
void ChatWindow::notificationClicked() {
|
||||
if (parentWidget()->isMinimized()) {
|
||||
parentWidget()->showNormal();
|
||||
}
|
||||
if (isHidden()) {
|
||||
show();
|
||||
}
|
||||
|
||||
// find last mention
|
||||
int messageCount = ui->messagesVBoxLayout->count();
|
||||
for (unsigned int i = messageCount; i > 0; i--) {
|
||||
ChatMessageArea* area = (ChatMessageArea*)ui->messagesVBoxLayout->itemAt(i - 1)->widget();
|
||||
QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getAccountInfo().getUsername()));
|
||||
if (area->toPlainText().contains(usernameMention)) {
|
||||
int top = area->geometry().top();
|
||||
int height = area->geometry().height();
|
||||
|
||||
QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar();
|
||||
verticalScrollBar->setSliderPosition(top - verticalScrollBar->size().height() + height);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
ChatWindow::~ChatWindow() {
|
||||
#ifdef HAVE_QXMPP
|
||||
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
|
||||
disconnect(&xmppClient, SIGNAL(connected()), this, SLOT(connected()));
|
||||
disconnect(&xmppClient, SIGNAL(joinedPublicChatRoom()), this, SLOT(connected()));
|
||||
disconnect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage)));
|
||||
|
||||
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
|
||||
|
@ -105,9 +142,15 @@ void ChatWindow::keyPressEvent(QKeyEvent* event) {
|
|||
|
||||
void ChatWindow::showEvent(QShowEvent* event) {
|
||||
FramelessDialog::showEvent(event);
|
||||
|
||||
if (!event->spontaneous()) {
|
||||
ui->messagePlainTextEdit->setFocus();
|
||||
}
|
||||
|
||||
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
|
||||
if (xmppClient.isConnected()) {
|
||||
participantsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatWindow::eventFilter(QObject* sender, QEvent* event) {
|
||||
|
@ -304,6 +347,21 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
|
|||
} else {
|
||||
lastMessageStamp = QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getAccountInfo().getUsername()));
|
||||
if (isHidden() && message.body().contains(usernameMention)) {
|
||||
if (_effectPlayer.state() != QMediaPlayer::PlayingState) {
|
||||
// get random sound
|
||||
QFileInfo inf = QFileInfo(Application::resourcesPath() +
|
||||
mentionSoundsPath +
|
||||
_mentionSounds.at(rand() % _mentionSounds.size()));
|
||||
_effectPlayer.setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
|
||||
_effectPlayer.play();
|
||||
}
|
||||
|
||||
_trayIcon.show();
|
||||
_trayIcon.showMessage(windowTitle(), message.body());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include <QDateTime>
|
||||
#include <QDockWidget>
|
||||
#include <QMediaPlayer>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QTimer>
|
||||
|
||||
#include <Application.h>
|
||||
|
@ -63,6 +65,9 @@ private:
|
|||
QDateTime lastMessageStamp;
|
||||
bool _mousePressed;
|
||||
QPoint _mouseStartPosition;
|
||||
QSystemTrayIcon _trayIcon;
|
||||
QStringList _mentionSounds;
|
||||
QMediaPlayer _effectPlayer;
|
||||
|
||||
private slots:
|
||||
void connected();
|
||||
|
@ -71,6 +76,7 @@ private slots:
|
|||
void error(QXmppClient::Error error);
|
||||
void participantsChanged();
|
||||
void messageReceived(const QXmppMessage& message);
|
||||
void notificationClicked();
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -22,10 +22,11 @@
|
|||
|
||||
#include "ModelsBrowser.h"
|
||||
|
||||
const char* MODEL_TYPE_NAMES[] = { "heads", "skeletons", "attachments" };
|
||||
|
||||
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
||||
static const QString PUBLIC_URL = "http://public.highfidelity.io";
|
||||
static const QString HEAD_MODELS_LOCATION = "models/heads";
|
||||
static const QString SKELETON_MODELS_LOCATION = "models/skeletons/";
|
||||
static const QString MODELS_LOCATION = "models/";
|
||||
|
||||
static const QString PREFIX_PARAMETER_NAME = "prefix";
|
||||
static const QString MARKER_PARAMETER_NAME = "marker";
|
||||
|
@ -243,11 +244,7 @@ void ModelHandler::queryNewFiles(QString marker) {
|
|||
// Build query
|
||||
QUrl url(S3_URL);
|
||||
QUrlQuery query;
|
||||
if (_type == Head) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
|
||||
} else if (_type == Skeleton) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
|
||||
}
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, MODELS_LOCATION + MODEL_TYPE_NAMES[_type]);
|
||||
|
||||
if (!marker.isEmpty()) {
|
||||
query.addQueryItem(MARKER_PARAMETER_NAME, marker);
|
||||
|
|
|
@ -16,12 +16,14 @@
|
|||
#include <QStandardItemModel>
|
||||
#include <QTreeView>
|
||||
|
||||
|
||||
enum ModelType {
|
||||
Head,
|
||||
Skeleton
|
||||
HEAD_MODEL,
|
||||
SKELETON_MODEL,
|
||||
ATTACHMENT_MODEL
|
||||
};
|
||||
|
||||
extern const char* MODEL_TYPE_NAMES[];
|
||||
|
||||
class ModelHandler : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
|
|
@ -48,7 +48,7 @@ void PreferencesDialog::openHeadModelBrowser() {
|
|||
setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
|
||||
show();
|
||||
|
||||
ModelsBrowser modelBrowser(Head);
|
||||
ModelsBrowser modelBrowser(HEAD_MODEL);
|
||||
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl);
|
||||
modelBrowser.browse();
|
||||
|
||||
|
@ -60,7 +60,7 @@ void PreferencesDialog::openBodyModelBrowser() {
|
|||
setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
|
||||
show();
|
||||
|
||||
ModelsBrowser modelBrowser(Skeleton);
|
||||
ModelsBrowser modelBrowser(SKELETON_MODEL);
|
||||
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl);
|
||||
modelBrowser.browse();
|
||||
|
||||
|
|
|
@ -198,9 +198,6 @@ color: #0e7077</string>
|
|||
<property name="indent">
|
||||
<number>25</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring></cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -510,7 +507,7 @@ color: #0e7077</string>
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="headLabel">
|
||||
<widget class="QLabel" name="headLabel_3">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
|
@ -547,7 +544,7 @@ color: #0e7077</string>
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_1">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="snapshotLocationEdit">
|
||||
<property name="sizePolicy">
|
||||
|
@ -564,7 +561,7 @@ color: #0e7077</string>
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<spacer name="horizontalSpacer_1">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
|
|
|
@ -599,8 +599,9 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) {
|
|||
|
||||
QUuid avatarUUID;
|
||||
QUrl faceModelURL, skeletonModelURL;
|
||||
QVector<AttachmentData> attachmentData;
|
||||
QString displayName;
|
||||
packetStream >> avatarUUID >> faceModelURL >> skeletonModelURL >> displayName;
|
||||
packetStream >> avatarUUID >> faceModelURL >> skeletonModelURL >> attachmentData >> displayName;
|
||||
|
||||
bool hasIdentityChanged = false;
|
||||
|
||||
|
@ -618,7 +619,12 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) {
|
|||
setDisplayName(displayName);
|
||||
hasIdentityChanged = true;
|
||||
}
|
||||
|
||||
|
||||
if (attachmentData != _attachmentData) {
|
||||
setAttachmentData(attachmentData);
|
||||
hasIdentityChanged = true;
|
||||
}
|
||||
|
||||
return hasIdentityChanged;
|
||||
}
|
||||
|
||||
|
@ -626,7 +632,7 @@ QByteArray AvatarData::identityByteArray() {
|
|||
QByteArray identityData;
|
||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||
|
||||
identityStream << QUuid() << _faceModelURL << _skeletonModelURL << _displayName;
|
||||
identityStream << QUuid() << _faceModelURL << _skeletonModelURL << _attachmentData << _displayName;
|
||||
|
||||
return identityData;
|
||||
}
|
||||
|
@ -654,6 +660,10 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
updateJointMappings();
|
||||
}
|
||||
|
||||
void AvatarData::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
_attachmentData = attachmentData;
|
||||
}
|
||||
|
||||
void AvatarData::setDisplayName(const QString& displayName) {
|
||||
_displayName = displayName;
|
||||
|
||||
|
@ -762,3 +772,23 @@ void AvatarData::updateJointMappings() {
|
|||
connect(networkReply, SIGNAL(finished()), this, SLOT(setJointMappingsFromNetworkReply()));
|
||||
}
|
||||
}
|
||||
|
||||
AttachmentData::AttachmentData() :
|
||||
scale(1.0f) {
|
||||
}
|
||||
|
||||
bool AttachmentData::operator==(const AttachmentData& other) const {
|
||||
return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation &&
|
||||
rotation == other.rotation && scale == other.scale;
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) {
|
||||
return out << attachment.modelURL << attachment.jointName <<
|
||||
attachment.translation << attachment.rotation << attachment.scale;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) {
|
||||
return in >> attachment.modelURL >> attachment.jointName >>
|
||||
attachment.translation >> attachment.rotation >> attachment.scale;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,14 +45,32 @@ typedef unsigned long long quint64;
|
|||
|
||||
#include <CollisionInfo.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include <Node.h>
|
||||
|
||||
#include "HeadData.h"
|
||||
#include "HandData.h"
|
||||
|
||||
// avatar motion behaviors
|
||||
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 0;
|
||||
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 1;
|
||||
const quint32 AVATAR_MOTION_MOTOR_ENABLED = 1U << 0;
|
||||
const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 1;
|
||||
const quint32 AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME = 1U << 2;
|
||||
const quint32 AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY = 1U << 3;
|
||||
|
||||
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 4;
|
||||
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 5;
|
||||
|
||||
const quint32 AVATAR_MOTION_DEFAULTS =
|
||||
AVATAR_MOTION_MOTOR_ENABLED |
|
||||
AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED |
|
||||
AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME;
|
||||
|
||||
// these bits will be expanded as features are exposed
|
||||
const quint32 AVATAR_MOTION_SCRIPTABLE_BITS =
|
||||
AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY |
|
||||
AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
|
||||
|
||||
|
||||
// First bitset
|
||||
const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits
|
||||
|
@ -79,8 +97,10 @@ enum KeyState {
|
|||
|
||||
const glm::vec3 vec3Zero(0.0f);
|
||||
|
||||
class QDataStream;
|
||||
class QNetworkAccessManager;
|
||||
|
||||
class AttachmentData;
|
||||
class JointData;
|
||||
|
||||
class AvatarData : public QObject {
|
||||
|
@ -210,9 +230,11 @@ public:
|
|||
const QUrl& getFaceModelURL() const { return _faceModelURL; }
|
||||
QString getFaceModelURLString() const { return _faceModelURL.toString(); }
|
||||
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
|
||||
const QVector<AttachmentData>& getAttachmentData() const { return _attachmentData; }
|
||||
const QString& getDisplayName() const { return _displayName; }
|
||||
virtual void setFaceModelURL(const QUrl& faceModelURL);
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
||||
virtual void setDisplayName(const QString& displayName);
|
||||
|
||||
virtual void setBillboard(const QByteArray& billboard);
|
||||
|
@ -275,6 +297,7 @@ protected:
|
|||
|
||||
QUrl _faceModelURL;
|
||||
QUrl _skeletonModelURL;
|
||||
QVector<AttachmentData> _attachmentData;
|
||||
QString _displayName;
|
||||
|
||||
QRect _displayNameBoundingRect;
|
||||
|
@ -309,4 +332,20 @@ public:
|
|||
glm::quat rotation;
|
||||
};
|
||||
|
||||
class AttachmentData {
|
||||
public:
|
||||
QUrl modelURL;
|
||||
QString jointName;
|
||||
glm::vec3 translation;
|
||||
glm::quat rotation;
|
||||
float scale;
|
||||
|
||||
AttachmentData();
|
||||
|
||||
bool operator==(const AttachmentData& other) const;
|
||||
};
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment);
|
||||
QDataStream& operator>>(QDataStream& in, AttachmentData& attachment);
|
||||
|
||||
#endif // hifi_AvatarData_h
|
||||
|
|
|
@ -127,8 +127,9 @@ void AvatarHashMap::processAvatarIdentityPacket(const QByteArray &packet, const
|
|||
while (!identityStream.atEnd()) {
|
||||
|
||||
QUrl faceMeshURL, skeletonURL;
|
||||
QVector<AttachmentData> attachmentData;
|
||||
QString displayName;
|
||||
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> displayName;
|
||||
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName;
|
||||
|
||||
// mesh URL for a UUID, find avatar in our list
|
||||
AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer);
|
||||
|
@ -142,6 +143,10 @@ void AvatarHashMap::processAvatarIdentityPacket(const QByteArray &packet, const
|
|||
matchingAvatar->setSkeletonModelURL(skeletonURL);
|
||||
}
|
||||
|
||||
if (matchingAvatar->getAttachmentData() != attachmentData) {
|
||||
matchingAvatar->setAttachmentData(attachmentData);
|
||||
}
|
||||
|
||||
if (matchingAvatar->getDisplayName() != displayName) {
|
||||
matchingAvatar->setDisplayName(displayName);
|
||||
}
|
||||
|
@ -171,4 +176,4 @@ void AvatarHashMap::processKillAvatar(const QByteArray& datagram) {
|
|||
if (matchedAvatar != _avatarHash.end()) {
|
||||
erase(matchedAvatar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) {
|
|||
|
||||
statsPacketStream << statsObject.toVariantMap();
|
||||
|
||||
return writeDatagram(statsPacket, _domainHandler.getSockAddr(), QUuid());
|
||||
return writeUnverifiedDatagram(statsPacket, _domainHandler.getSockAddr());
|
||||
}
|
||||
|
||||
void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) {
|
||||
|
|
|
@ -49,6 +49,8 @@ PacketVersion versionForPacketType(PacketType type) {
|
|||
switch (type) {
|
||||
case PacketTypeAvatarData:
|
||||
return 3;
|
||||
case PacketTypeAvatarIdentity:
|
||||
return 1;
|
||||
case PacketTypeEnvironmentData:
|
||||
return 1;
|
||||
case PacketTypeParticleData:
|
||||
|
|
|
@ -1208,6 +1208,7 @@ ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) c
|
|||
// By doing this, we don't need to test each child voxel's position vs the LOD boundary
|
||||
bool OctreeElement::calculateShouldRender(const ViewFrustum* viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const {
|
||||
bool shouldRender = false;
|
||||
|
||||
if (hasContent()) {
|
||||
float furthestDistance = furthestDistanceToCamera(*viewFrustum);
|
||||
float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize);
|
||||
|
|
|
@ -140,19 +140,22 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar
|
|||
|
||||
bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) {
|
||||
RenderArgs* args = static_cast<RenderArgs*>(extraData);
|
||||
//if (true || element->isInView(*args->_viewFrustum)) {
|
||||
if (element->isInView(*args->_viewFrustum)) {
|
||||
if (element->hasContent()) {
|
||||
args->_renderer->renderElement(element, args);
|
||||
if (element->calculateShouldRender(args->_viewFrustum, args->_sizeScale, args->_boundaryLevelAdjust)) {
|
||||
args->_renderer->renderElement(element, args);
|
||||
} else {
|
||||
return false; // if we shouldn't render, then we also should stop recursing.
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return true; // continue recursing
|
||||
}
|
||||
// if not in view stop recursing
|
||||
return false;
|
||||
}
|
||||
|
||||
void OctreeRenderer::render() {
|
||||
RenderArgs args = { 0, this, _viewFrustum };
|
||||
RenderArgs args = { 0, this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust() };
|
||||
if (_tree) {
|
||||
_tree->lockForRead();
|
||||
_tree->recurseTreeWithOperation(renderOperation, &args);
|
||||
|
|
|
@ -31,6 +31,8 @@ public:
|
|||
int _renderedItems;
|
||||
OctreeRenderer* _renderer;
|
||||
ViewFrustum* _viewFrustum;
|
||||
float _sizeScale;
|
||||
int _boundaryLevelAdjust;
|
||||
};
|
||||
|
||||
|
||||
|
@ -46,6 +48,8 @@ public:
|
|||
virtual PacketType getMyQueryMessageType() const = 0;
|
||||
virtual PacketType getExpectedPacketType() const = 0;
|
||||
virtual void renderElement(OctreeElement* element, RenderArgs* args) = 0;
|
||||
virtual float getSizeScale() const { return DEFAULT_OCTREE_SIZE_SCALE; }
|
||||
virtual int getBoundaryLevelAdjust() const { return 0; }
|
||||
|
||||
virtual void setTree(Octree* newTree);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
const float DEFAULT_KEYHOLE_RADIUS = 3.0f;
|
||||
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 90.0f;
|
||||
const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.f;
|
||||
const float DEFAULT_ASPECT_RATIO = 16.f/9.f;
|
||||
const float DEFAULT_NEAR_CLIP = 0.08f;
|
||||
const float DEFAULT_FAR_CLIP = 50.0f * TREE_SCALE;
|
||||
|
|
|
@ -34,6 +34,7 @@ CapsuleShape::CapsuleShape(float radius, float halfHeight, const glm::vec3& posi
|
|||
CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint) :
|
||||
Shape(Shape::CAPSULE_SHAPE), _radius(radius), _halfHeight(0.0f) {
|
||||
glm::vec3 axis = endPoint - startPoint;
|
||||
_position = 0.5f * (endPoint + startPoint);
|
||||
float height = glm::length(axis);
|
||||
if (height > EPSILON) {
|
||||
_halfHeight = 0.5f * height;
|
||||
|
@ -50,12 +51,12 @@ CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm:
|
|||
|
||||
/// \param[out] startPoint is the center of start cap
|
||||
void CapsuleShape::getStartPoint(glm::vec3& startPoint) const {
|
||||
startPoint = getPosition() - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
|
||||
startPoint = _position - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
|
||||
}
|
||||
|
||||
/// \param[out] endPoint is the center of the end cap
|
||||
void CapsuleShape::getEndPoint(glm::vec3& endPoint) const {
|
||||
endPoint = getPosition() + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
|
||||
endPoint = _position + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
|
||||
}
|
||||
|
||||
void CapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
#include "Shape.h"
|
||||
|
||||
// adebug bookmark TODO: convert to new world-frame approach
|
||||
// default axis of CapsuleShape is Y-axis
|
||||
|
||||
class CapsuleShape : public Shape {
|
||||
|
|
|
@ -23,6 +23,12 @@ CollisionInfo* CollisionList::getNewCollision() {
|
|||
return (_size < _maxSize) ? &(_collisions[_size++]) : NULL;
|
||||
}
|
||||
|
||||
void CollisionList::deleteLastCollision() {
|
||||
if (_size > 0) {
|
||||
--_size;
|
||||
}
|
||||
}
|
||||
|
||||
CollisionInfo* CollisionList::getCollision(int index) {
|
||||
return (index > -1 && index < _size) ? &(_collisions[index]) : NULL;
|
||||
}
|
||||
|
|
|
@ -81,6 +81,9 @@ public:
|
|||
/// \return pointer to next collision. NULL if list is full.
|
||||
CollisionInfo* getNewCollision();
|
||||
|
||||
/// \forget about collision at the end
|
||||
void deleteLastCollision();
|
||||
|
||||
/// \return pointer to collision by index. NULL if index out of bounds.
|
||||
CollisionInfo* getCollision(int index);
|
||||
|
||||
|
|
|
@ -591,7 +591,95 @@ bool listList(const ListShape* listA, const ListShape* listB, CollisionList& col
|
|||
}
|
||||
|
||||
// helper function
|
||||
bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
|
||||
bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter,
|
||||
float cubeSide, CollisionList& collisions) {
|
||||
// sphere is A
|
||||
// cube is B
|
||||
// BA = B - A = from center of A to center of B
|
||||
float halfCubeSide = 0.5f * cubeSide;
|
||||
glm::vec3 BA = cubeCenter - sphereCenter;
|
||||
float distance = glm::length(BA);
|
||||
if (distance > EPSILON) {
|
||||
float maxBA = glm::max(glm::max(glm::abs(BA.x), glm::abs(BA.y)), glm::abs(BA.z));
|
||||
if (maxBA > halfCubeSide + sphereRadius) {
|
||||
// sphere misses cube entirely
|
||||
return false;
|
||||
}
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (!collision) {
|
||||
return false;
|
||||
}
|
||||
if (maxBA > halfCubeSide) {
|
||||
// sphere hits cube but its center is outside cube
|
||||
|
||||
// compute contact anti-pole on cube (in cube frame)
|
||||
glm::vec3 cubeContact = glm::abs(BA);
|
||||
if (cubeContact.x > halfCubeSide) {
|
||||
cubeContact.x = halfCubeSide;
|
||||
}
|
||||
if (cubeContact.y > halfCubeSide) {
|
||||
cubeContact.y = halfCubeSide;
|
||||
}
|
||||
if (cubeContact.z > halfCubeSide) {
|
||||
cubeContact.z = halfCubeSide;
|
||||
}
|
||||
glm::vec3 signs = glm::sign(BA);
|
||||
cubeContact.x *= signs.x;
|
||||
cubeContact.y *= signs.y;
|
||||
cubeContact.z *= signs.z;
|
||||
|
||||
// compute penetration direction
|
||||
glm::vec3 direction = BA - cubeContact;
|
||||
float lengthDirection = glm::length(direction);
|
||||
if (lengthDirection < EPSILON) {
|
||||
// sphereCenter is touching cube surface, so we can't use the difference between those two
|
||||
// points to compute the penetration direction. Instead we use the unitary components of
|
||||
// cubeContact.
|
||||
direction = cubeContact / halfCubeSide;
|
||||
glm::modf(BA, direction);
|
||||
lengthDirection = glm::length(direction);
|
||||
} else if (lengthDirection > sphereRadius) {
|
||||
collisions.deleteLastCollision();
|
||||
return false;
|
||||
}
|
||||
direction /= lengthDirection;
|
||||
|
||||
// compute collision details
|
||||
collision->_contactPoint = sphereCenter + sphereRadius * direction;
|
||||
collision->_penetration = sphereRadius * direction - (BA - cubeContact);
|
||||
} else {
|
||||
// sphere center is inside cube
|
||||
// --> push out nearest face
|
||||
glm::vec3 direction;
|
||||
BA /= maxBA;
|
||||
glm::modf(BA, direction);
|
||||
direction = glm::normalize(direction);
|
||||
|
||||
// compute collision details
|
||||
collision->_penetration = (halfCubeSide + sphereRadius - distance * glm::dot(BA, direction)) * direction;
|
||||
collision->_contactPoint = sphereCenter + sphereRadius * direction;
|
||||
}
|
||||
return true;
|
||||
} else if (sphereRadius + halfCubeSide > distance) {
|
||||
// NOTE: for cocentric approximation we collide sphere and cube as two spheres which means
|
||||
// this algorithm will probably be wrong when both sphere and cube are very small (both ~EPSILON)
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (collision) {
|
||||
// the penetration and contactPoint are undefined, so we pick a penetration direction (-yAxis)
|
||||
collision->_penetration = (sphereRadius + halfCubeSide) * glm::vec3(0.0f, -1.0f, 0.0f);
|
||||
// contactPoint is on surface of A
|
||||
collision->_contactPoint = sphereCenter + collision->_penetration;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// helper function
|
||||
/* KEEP THIS CODE -- this is how to collide the cube with stark face normals (no rounding).
|
||||
* We might want to use this code later for sealing boundaries between adjacent voxels.
|
||||
bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter,
|
||||
float cubeSide, CollisionList& collisions) {
|
||||
glm::vec3 BA = cubeCenter - sphereCenter;
|
||||
float distance = glm::length(BA);
|
||||
if (distance > EPSILON) {
|
||||
|
@ -606,50 +694,16 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
|
|||
if (glm::dot(surfaceAB, BA) > 0.f) {
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (collision) {
|
||||
/* KEEP THIS CODE -- this is how to collide the cube with stark face normals (no rounding).
|
||||
* We might want to use this code later for sealing boundaries between adjacent voxels.
|
||||
// penetration is parallel to box side direction
|
||||
BA /= maxBA;
|
||||
glm::vec3 direction;
|
||||
glm::modf(BA, direction);
|
||||
direction = glm::normalize(direction);
|
||||
*/
|
||||
|
||||
// For rounded normals at edges and corners:
|
||||
// At this point imagine that sphereCenter touches a "normalized" cube with rounded edges.
|
||||
// This cube has a sidelength of 2 and its smoothing radius is sphereRadius/maxBA.
|
||||
// We're going to try to compute the "negative normal" (and hence direction of penetration)
|
||||
// of this surface.
|
||||
|
||||
float radius = sphereRadius / (distance * maxBA); // normalized radius
|
||||
float shortLength = maxBA - radius;
|
||||
glm::vec3 direction = BA;
|
||||
if (shortLength > 0.0f) {
|
||||
direction = glm::abs(BA) - glm::vec3(shortLength);
|
||||
// Set any negative components to zero, and adopt the sign of the original BA component.
|
||||
// Unfortunately there isn't an easy way to make this fast.
|
||||
if (direction.x < 0.0f) {
|
||||
direction.x = 0.f;
|
||||
} else if (BA.x < 0.f) {
|
||||
direction.x = -direction.x;
|
||||
}
|
||||
if (direction.y < 0.0f) {
|
||||
direction.y = 0.f;
|
||||
} else if (BA.y < 0.f) {
|
||||
direction.y = -direction.y;
|
||||
}
|
||||
if (direction.z < 0.0f) {
|
||||
direction.z = 0.f;
|
||||
} else if (BA.z < 0.f) {
|
||||
direction.z = -direction.z;
|
||||
}
|
||||
}
|
||||
direction = glm::normalize(direction);
|
||||
|
||||
// penetration is the projection of surfaceAB on direction
|
||||
collision->_penetration = glm::dot(surfaceAB, direction) * direction;
|
||||
// contactPoint is on surface of A
|
||||
collision->_contactPoint = sphereCenter - sphereRadius * direction;
|
||||
collision->_contactPoint = sphereCenter + sphereRadius * direction;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -667,6 +721,7 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
|
|||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
|
||||
return sphereAACube(sphereA->getPosition(), sphereA->getRadius(), cubeCenter, cubeSide, collisions);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDataStream>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "StreamUtils.h"
|
||||
|
@ -47,6 +49,22 @@ std::ostream& operator<<(std::ostream& s, const glm::mat4& m) {
|
|||
return s;
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const glm::vec3& vector) {
|
||||
return out << vector.x << vector.y << vector.z;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& in, glm::vec3& vector) {
|
||||
return in >> vector.x >> vector.y >> vector.z;
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const glm::quat& quaternion) {
|
||||
return out << quaternion.x << quaternion.y << quaternion.z << quaternion.w;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& in, glm::quat& quaternion) {
|
||||
return in >> quaternion.x >> quaternion.y >> quaternion.z >> quaternion.w;
|
||||
}
|
||||
|
||||
// less common utils can be enabled with DEBUG
|
||||
#ifdef DEBUG
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
class QDataStream;
|
||||
|
||||
namespace StreamUtil {
|
||||
// dump the buffer, 32 bytes per row, each byte in hex, separated by whitespace
|
||||
|
@ -29,6 +30,12 @@ std::ostream& operator<<(std::ostream& s, const glm::vec3& v);
|
|||
std::ostream& operator<<(std::ostream& s, const glm::quat& q);
|
||||
std::ostream& operator<<(std::ostream& s, const glm::mat4& m);
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const glm::vec3& vector);
|
||||
QDataStream& operator>>(QDataStream& in, glm::vec3& vector);
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const glm::quat& quaternion);
|
||||
QDataStream& operator>>(QDataStream& in, glm::quat& quaternion);
|
||||
|
||||
// less common utils can be enabled with DEBUG
|
||||
#ifdef DEBUG
|
||||
#include "CollisionInfo.h"
|
||||
|
|
|
@ -681,58 +681,164 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
|
|||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::sphereTouchesAACube() {
|
||||
void ShapeColliderTests::sphereTouchesAACubeFaces() {
|
||||
CollisionList collisions(16);
|
||||
|
||||
glm::vec3 cubeCenter(1.23f, 4.56f, 7.89f);
|
||||
float cubeSide = 2.34f;
|
||||
|
||||
float sphereRadius = 1.13f;
|
||||
glm::vec3 sphereCenter(0.0f);
|
||||
SphereShape sphere(sphereRadius, sphereCenter);
|
||||
|
||||
QVector<glm::vec3> axes;
|
||||
axes.push_back(xAxis);
|
||||
axes.push_back(-xAxis);
|
||||
axes.push_back(yAxis);
|
||||
axes.push_back(-yAxis);
|
||||
axes.push_back(zAxis);
|
||||
axes.push_back(-zAxis);
|
||||
|
||||
for (int i = 0; i < axes.size(); ++i) {
|
||||
glm::vec3 axis = axes[i];
|
||||
// outside
|
||||
{
|
||||
collisions.clear();
|
||||
float overlap = 0.25f;
|
||||
float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
|
||||
sphereCenter = cubeCenter + sphereOffset * axis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl;
|
||||
}
|
||||
CollisionInfo* collision = collisions[0];
|
||||
if (!collision) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: no CollisionInfo. axis = " << axis << std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedPenetration = - overlap * axis;
|
||||
if (glm::distance(expectedPenetration, collision->_penetration) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: penetration = " << collision->_penetration
|
||||
<< " expected " << expectedPenetration
|
||||
<< " axis = " << axis
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContact = sphereCenter - sphereRadius * axis;
|
||||
if (glm::distance(expectedContact, collision->_contactPoint) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: contactaPoint = " << collision->_contactPoint
|
||||
<< " expected " << expectedContact
|
||||
<< " axis = " << axis
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// inside
|
||||
{
|
||||
collisions.clear();
|
||||
float overlap = 1.25f * sphereRadius;
|
||||
float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
|
||||
sphereCenter = cubeCenter + sphereOffset * axis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube."
|
||||
<< " axis = " << axis
|
||||
<< std::endl;
|
||||
}
|
||||
CollisionInfo* collision = collisions[0];
|
||||
if (!collision) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: no CollisionInfo on y-axis."
|
||||
<< " axis = " << axis
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedPenetration = - overlap * axis;
|
||||
if (glm::distance(expectedPenetration, collision->_penetration) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: penetration = " << collision->_penetration
|
||||
<< " expected " << expectedPenetration
|
||||
<< " axis = " << axis
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContact = sphereCenter - sphereRadius * axis;
|
||||
if (glm::distance(expectedContact, collision->_contactPoint) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: contactaPoint = " << collision->_contactPoint
|
||||
<< " expected " << expectedContact
|
||||
<< " axis = " << axis
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::sphereTouchesAACubeEdges() {
|
||||
CollisionList collisions(20);
|
||||
|
||||
glm::vec3 cubeCenter(0.0f, 0.0f, 0.0f);
|
||||
float cubeSide = 2.0f;
|
||||
|
||||
float sphereRadius = 1.0f;
|
||||
glm::vec3 sphereCenter(0.0f);
|
||||
SphereShape sphere(sphereRadius, sphereCenter);
|
||||
|
||||
float sphereOffset = (0.5f * cubeSide + sphereRadius - 0.25f);
|
||||
QVector<glm::vec3> axes;
|
||||
// edges
|
||||
axes.push_back(glm::vec3(0.0f, 1.0f, 1.0f));
|
||||
axes.push_back(glm::vec3(0.0f, 1.0f, -1.0f));
|
||||
axes.push_back(glm::vec3(0.0f, -1.0f, 1.0f));
|
||||
axes.push_back(glm::vec3(0.0f, -1.0f, -1.0f));
|
||||
axes.push_back(glm::vec3(1.0f, 1.0f, 0.0f));
|
||||
axes.push_back(glm::vec3(1.0f, -1.0f, 0.0f));
|
||||
axes.push_back(glm::vec3(-1.0f, 1.0f, 0.0f));
|
||||
axes.push_back(glm::vec3(-1.0f, -1.0f, 0.0f));
|
||||
axes.push_back(glm::vec3(1.0f, 0.0f, 1.0f));
|
||||
axes.push_back(glm::vec3(1.0f, 0.0f, -1.0f));
|
||||
axes.push_back(glm::vec3(-1.0f, 0.0f, 1.0f));
|
||||
axes.push_back(glm::vec3(-1.0f, 0.0f, -1.0f));
|
||||
// and corners
|
||||
axes.push_back(glm::vec3(1.0f, 1.0f, 1.0f));
|
||||
axes.push_back(glm::vec3(1.0f, 1.0f, -1.0f));
|
||||
axes.push_back(glm::vec3(1.0f, -1.0f, 1.0f));
|
||||
axes.push_back(glm::vec3(1.0f, -1.0f, -1.0f));
|
||||
axes.push_back(glm::vec3(-1.0f, 1.0f, 1.0f));
|
||||
axes.push_back(glm::vec3(-1.0f, 1.0f, -1.0f));
|
||||
axes.push_back(glm::vec3(-1.0f, -1.0f, 1.0f));
|
||||
axes.push_back(glm::vec3(-1.0f, -1.0f, -1.0f));
|
||||
|
||||
// top
|
||||
sphereCenter = cubeCenter + sphereOffset * yAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
|
||||
}
|
||||
for (int i =0; i < axes.size(); ++i) {
|
||||
glm::vec3 axis = axes[i];
|
||||
float lengthAxis = glm::length(axis);
|
||||
axis /= lengthAxis;
|
||||
float overlap = 0.25f;
|
||||
|
||||
// bottom
|
||||
sphereCenter = cubeCenter - sphereOffset * yAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
|
||||
}
|
||||
|
||||
// left
|
||||
sphereCenter = cubeCenter + sphereOffset * xAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
|
||||
}
|
||||
|
||||
// right
|
||||
sphereCenter = cubeCenter - sphereOffset * xAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
|
||||
}
|
||||
|
||||
// forward
|
||||
sphereCenter = cubeCenter + sphereOffset * zAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
|
||||
}
|
||||
|
||||
// back
|
||||
sphereCenter = cubeCenter - sphereOffset * zAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
|
||||
sphereCenter = cubeCenter + (lengthAxis * 0.5f * cubeSide + sphereRadius - overlap) * axis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl;
|
||||
}
|
||||
CollisionInfo* collision = collisions[i];
|
||||
if (!collision) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: no CollisionInfo. axis = " << axis << std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedPenetration = - overlap * axis;
|
||||
if (glm::distance(expectedPenetration, collision->_penetration) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: penetration = " << collision->_penetration
|
||||
<< " expected " << expectedPenetration
|
||||
<< " axis = " << axis
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContact = sphereCenter - sphereRadius * axis;
|
||||
if (glm::distance(expectedContact, collision->_contactPoint) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: contactaPoint = " << collision->_contactPoint
|
||||
<< " expected " << expectedContact
|
||||
<< " axis = " << axis
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -802,6 +908,7 @@ void ShapeColliderTests::runAllTests() {
|
|||
capsuleMissesCapsule();
|
||||
capsuleTouchesCapsule();
|
||||
|
||||
sphereTouchesAACube();
|
||||
sphereTouchesAACubeFaces();
|
||||
sphereTouchesAACubeEdges();
|
||||
sphereMissesAACube();
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@ namespace ShapeColliderTests {
|
|||
void capsuleMissesCapsule();
|
||||
void capsuleTouchesCapsule();
|
||||
|
||||
void sphereTouchesAACube();
|
||||
void sphereTouchesAACubeFaces();
|
||||
void sphereTouchesAACubeEdges();
|
||||
void sphereMissesAACube();
|
||||
|
||||
void runAllTests();
|
||||
|
|
Loading…
Reference in a new issue