mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-03 23:44:06 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels
This commit is contained in:
commit
a9c4ce2a6a
53 changed files with 3433 additions and 944 deletions
|
@ -59,6 +59,13 @@ UnitTest.prototype.assertEquals = function(expected, actual, message) {
|
|||
}
|
||||
};
|
||||
|
||||
UnitTest.prototype.assertContains = function (expected, actual, message) {
|
||||
this.numAssertions++;
|
||||
if (actual.indexOf(expected) == -1) {
|
||||
throw new AssertionException(expected, actual, message);
|
||||
}
|
||||
};
|
||||
|
||||
UnitTest.prototype.assertHasProperty = function(property, actual, message) {
|
||||
this.numAssertions++;
|
||||
if (actual[property] === undefined) {
|
||||
|
|
|
@ -24,6 +24,7 @@ function setupMenus() {
|
|||
Menu.removeMenuItem("Edit", "Paste");
|
||||
Menu.removeMenuItem("Edit", "Delete");
|
||||
Menu.removeMenuItem("Edit", "Nudge");
|
||||
Menu.removeMenuItem("Edit", "Replace from File");
|
||||
Menu.removeMenuItem("File", "Export Voxels");
|
||||
Menu.removeMenuItem("File", "Import Voxels");
|
||||
|
||||
|
@ -32,6 +33,7 @@ function setupMenus() {
|
|||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Copy", shortcutKey: "CTRL+C", afterItem: "Cut" });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste", shortcutKey: "CTRL+V", afterItem: "Copy" });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Nudge", shortcutKey: "CTRL+N", afterItem: "Paste" });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Replace from File", shortcutKey: "CTRL+R", afterItem: "Nudge" });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", shortcutKeyEvent: { text: "backspace" }, afterItem: "Nudge" });
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Voxels", shortcutKey: "CTRL+E", afterItem: "Voxels" });
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Voxels", shortcutKey: "CTRL+I", afterItem: "Export Voxels" });
|
||||
|
@ -60,7 +62,6 @@ function menuItemEvent(menuItem) {
|
|||
print("deleting...");
|
||||
Clipboard.deleteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
|
||||
}
|
||||
|
||||
if (menuItem == "Export Voxels") {
|
||||
print("export");
|
||||
Clipboard.exportVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
|
||||
|
@ -73,6 +74,12 @@ function menuItemEvent(menuItem) {
|
|||
print("nudge");
|
||||
Clipboard.nudgeVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s, { x: -1, y: 0, z: 0 });
|
||||
}
|
||||
if (menuItem == "Replace from File") {
|
||||
var filename = Window.browse("Select file to load replacement", "", "Voxel Files (*.png *.svo *.schematic)");
|
||||
if (filename) {
|
||||
Clipboard.importVoxel(filename, selectedVoxel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var selectCube = Overlays.addOverlay("cube", {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -145,3 +145,98 @@ test("Test timeout", function() {
|
|||
this.assertEquals(0, req.status, "status should be `0`");
|
||||
this.assertEquals(4, req.errorCode, "4 is the timeout error code for QNetworkReply::NetworkError");
|
||||
});
|
||||
|
||||
|
||||
var localFile = Window.browse("Find defaultScripts.js file ...", "", "defaultScripts.js (defaultScripts.js)");
|
||||
|
||||
if (localFile !== null) {
|
||||
|
||||
localFile = "file:///" + localFile;
|
||||
|
||||
test("Test GET local file synchronously", function () {
|
||||
var req = new XMLHttpRequest();
|
||||
|
||||
var statesVisited = [true, false, false, false, false]
|
||||
req.onreadystatechange = function () {
|
||||
statesVisited[req.readyState] = true;
|
||||
};
|
||||
|
||||
req.open("GET", localFile, false);
|
||||
req.send();
|
||||
|
||||
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
|
||||
this.assertEquals(200, req.status, "status should be `200`");
|
||||
this.assertEquals("OK", req.statusText, "statusText should be `OK`");
|
||||
this.assertEquals(0, req.errorCode);
|
||||
this.assertNotEquals("", req.getAllResponseHeaders(), "headers should not be null");
|
||||
this.assertContains("High Fidelity", req.response.substring(0, 100), "expected text not found in response")
|
||||
|
||||
for (var i = 0; i <= req.DONE; i++) {
|
||||
this.assertEquals(true, statesVisited[i], i + " should be set");
|
||||
}
|
||||
});
|
||||
|
||||
test("Test GET nonexistent local file", function () {
|
||||
var nonexistentFile = localFile.replace(".js", "NoExist.js");
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", nonexistentFile, false);
|
||||
req.send();
|
||||
|
||||
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
|
||||
this.assertEquals(404, req.status, "status should be `404`");
|
||||
this.assertEquals("Not Found", req.statusText, "statusText should be `Not Found`");
|
||||
this.assertNotEquals(0, req.errorCode);
|
||||
});
|
||||
|
||||
test("Test GET local file already open", function () {
|
||||
// Can't open file exclusively in order to test.
|
||||
});
|
||||
|
||||
test("Test GET local file with data not implemented", function () {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", localFile, true);
|
||||
req.send("data");
|
||||
|
||||
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
|
||||
this.assertEquals(501, req.status, "status should be `501`");
|
||||
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
|
||||
this.assertNotEquals(0, req.errorCode);
|
||||
});
|
||||
|
||||
test("Test GET local file asynchronously not implemented", function () {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", localFile, true);
|
||||
req.send();
|
||||
|
||||
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
|
||||
this.assertEquals(501, req.status, "status should be `501`");
|
||||
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
|
||||
this.assertNotEquals(0, req.errorCode);
|
||||
});
|
||||
|
||||
test("Test POST local file not implemented", function () {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("POST", localFile, false);
|
||||
req.send();
|
||||
|
||||
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
|
||||
this.assertEquals(501, req.status, "status should be `501`");
|
||||
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
|
||||
this.assertNotEquals(0, req.errorCode);
|
||||
});
|
||||
|
||||
test("Test local file username and password not implemented", function () {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", localFile, false, "username", "password");
|
||||
req.send();
|
||||
|
||||
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
|
||||
this.assertEquals(501, req.status, "status should be `501`");
|
||||
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
|
||||
this.assertNotEquals(0, req.errorCode);
|
||||
});
|
||||
|
||||
} else {
|
||||
print("Local file operation not tested");
|
||||
}
|
||||
|
|
|
@ -186,6 +186,14 @@ ToolBar = function(x, y, direction) {
|
|||
return this.tools.length;
|
||||
}
|
||||
|
||||
this.selectTool = function (tool, select) {
|
||||
this.tools[tool].select(select);
|
||||
}
|
||||
|
||||
this.toolSelected = function (tool) {
|
||||
return this.tools[tool].selected();
|
||||
}
|
||||
|
||||
this.cleanup = function() {
|
||||
for(var tool in this.tools) {
|
||||
this.tools[tool].cleanup();
|
||||
|
|
|
@ -245,28 +245,28 @@
|
|||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="22"/>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="23"/>
|
||||
<location filename="src/ui/VoxelImportDialog.cpp" line="22"/>
|
||||
<location filename="src/ui/VoxelImportDialog.cpp" line="23"/>
|
||||
<source>Import Voxels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="24"/>
|
||||
<location filename="src/ui/VoxelImportDialog.cpp" line="24"/>
|
||||
<source>Loading ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="25"/>
|
||||
<location filename="src/ui/VoxelImportDialog.cpp" line="25"/>
|
||||
<source>Place voxels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="26"/>
|
||||
<location filename="src/ui/VoxelImportDialog.cpp" line="26"/>
|
||||
<source><b>Import</b> %1 as voxels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="27"/>
|
||||
<location filename="src/ui/VoxelImportDialog.cpp" line="27"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
|
@ -137,7 +137,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_frameCount(0),
|
||||
_fps(60.0f),
|
||||
_justStarted(true),
|
||||
_voxelImporter(NULL),
|
||||
_voxelImportDialog(NULL),
|
||||
_voxelImporter(),
|
||||
_importSucceded(false),
|
||||
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
|
||||
_modelClipboardRenderer(),
|
||||
|
@ -432,7 +433,7 @@ Application::~Application() {
|
|||
delete idleTimer;
|
||||
|
||||
_sharedVoxelSystem.changeTree(new VoxelTree);
|
||||
delete _voxelImporter;
|
||||
delete _voxelImportDialog;
|
||||
|
||||
// let the avatar mixer know we're out
|
||||
MyAvatar::sendKillAvatar();
|
||||
|
@ -467,8 +468,8 @@ void Application::saveSettings() {
|
|||
Menu::getInstance()->saveSettings();
|
||||
_rearMirrorTools->saveSettings(_settings);
|
||||
|
||||
if (_voxelImporter) {
|
||||
_voxelImporter->saveSettings(_settings);
|
||||
if (_voxelImportDialog) {
|
||||
_voxelImportDialog->saveSettings(_settings);
|
||||
}
|
||||
_settings->sync();
|
||||
_numChangedSettings = 0;
|
||||
|
@ -1050,9 +1051,22 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
case Qt::Key_R:
|
||||
if (isShifted) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::FrustumRenderMode);
|
||||
} else if (isMeta) {
|
||||
if (_myAvatar->isRecording()) {
|
||||
_myAvatar->stopRecording();
|
||||
} else {
|
||||
_myAvatar->startRecording();
|
||||
_audio.setRecorder(_myAvatar->getRecorder());
|
||||
}
|
||||
} else {
|
||||
if (_myAvatar->isPlaying()) {
|
||||
_myAvatar->stopPlaying();
|
||||
} else {
|
||||
_myAvatar->startPlaying();
|
||||
_audio.setPlayer(_myAvatar->getPlayer());
|
||||
}
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case Qt::Key_Percent:
|
||||
Menu::getInstance()->triggerOption(MenuOption::Stats);
|
||||
break;
|
||||
|
@ -1608,17 +1622,17 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) {
|
|||
void Application::importVoxels() {
|
||||
_importSucceded = false;
|
||||
|
||||
if (!_voxelImporter) {
|
||||
_voxelImporter = new VoxelImporter(_window);
|
||||
_voxelImporter->loadSettings(_settings);
|
||||
if (!_voxelImportDialog) {
|
||||
_voxelImportDialog = new VoxelImportDialog(_window);
|
||||
_voxelImportDialog->loadSettings(_settings);
|
||||
}
|
||||
|
||||
if (!_voxelImporter->exec()) {
|
||||
if (!_voxelImportDialog->exec()) {
|
||||
qDebug() << "Import succeeded." << endl;
|
||||
_importSucceded = true;
|
||||
} else {
|
||||
qDebug() << "Import failed." << endl;
|
||||
if (_sharedVoxelSystem.getTree() == _voxelImporter->getVoxelTree()) {
|
||||
if (_sharedVoxelSystem.getTree() == _voxelImporter.getVoxelTree()) {
|
||||
_sharedVoxelSystem.killLocalVoxels();
|
||||
_sharedVoxelSystem.changeTree(&_clipboard);
|
||||
}
|
||||
|
@ -1733,7 +1747,7 @@ void Application::init() {
|
|||
// Cleanup of the original shared tree
|
||||
_sharedVoxelSystem.init();
|
||||
|
||||
_voxelImporter = new VoxelImporter(_window);
|
||||
_voxelImportDialog = new VoxelImportDialog(_window);
|
||||
|
||||
_environment.init();
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
#include "ui/overlays/Overlays.h"
|
||||
#include "ui/ApplicationOverlay.h"
|
||||
#include "ui/RunningScriptsWidget.h"
|
||||
#include "ui/VoxelImportDialog.h"
|
||||
#include "voxels/VoxelFade.h"
|
||||
#include "voxels/VoxelHideShowThread.h"
|
||||
#include "voxels/VoxelImporter.h"
|
||||
|
@ -193,6 +194,7 @@ public:
|
|||
Camera* getCamera() { return &_myCamera; }
|
||||
ViewFrustum* getViewFrustum() { return &_viewFrustum; }
|
||||
ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; }
|
||||
VoxelImporter* getVoxelImporter() { return &_voxelImporter; }
|
||||
VoxelSystem* getVoxels() { return &_voxels; }
|
||||
VoxelTree* getVoxelTree() { return _voxels.getTree(); }
|
||||
const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
|
||||
|
@ -463,7 +465,8 @@ private:
|
|||
|
||||
VoxelSystem _voxels;
|
||||
VoxelTree _clipboard; // if I copy/paste
|
||||
VoxelImporter* _voxelImporter;
|
||||
VoxelImportDialog* _voxelImportDialog;
|
||||
VoxelImporter _voxelImporter;
|
||||
bool _importSucceded;
|
||||
VoxelSystem _sharedVoxelSystem;
|
||||
ViewFrustum _sharedVoxelSystemViewFrustum;
|
||||
|
|
|
@ -82,7 +82,6 @@ Audio::Audio(QObject* parent) :
|
|||
_noiseGateSampleCounter(0),
|
||||
_noiseGateOpen(false),
|
||||
_noiseGateEnabled(true),
|
||||
_peqEnabled(false),
|
||||
_toneInjectionEnabled(false),
|
||||
_noiseGateFramesToClose(0),
|
||||
_totalInputAudioSamples(0),
|
||||
|
@ -102,6 +101,7 @@ Audio::Audio(QObject* parent) :
|
|||
_scopeOutputOffset(0),
|
||||
_framesPerScope(DEFAULT_FRAMES_PER_SCOPE),
|
||||
_samplesPerScope(NETWORK_SAMPLES_PER_FRAME * _framesPerScope),
|
||||
_peqEnabled(false),
|
||||
_scopeInput(0),
|
||||
_scopeOutputLeft(0),
|
||||
_scopeOutputRight(0),
|
||||
|
@ -475,7 +475,7 @@ void Audio::handleAudioInput() {
|
|||
|
||||
int16_t* ioBuffer = (int16_t*)inputByteArray.data();
|
||||
|
||||
_peq.render( ioBuffer, ioBuffer, inputByteArray.size() / sizeof(int16_t) );
|
||||
_peq.render(ioBuffer, ioBuffer, inputByteArray.size() / sizeof(int16_t));
|
||||
}
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted && _audioOutput) {
|
||||
|
@ -676,6 +676,11 @@ void Audio::handleAudioInput() {
|
|||
NodeList* nodeList = NodeList::getInstance();
|
||||
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||
|
||||
|
||||
if (_recorder && _recorder.data()->isRecording()) {
|
||||
_recorder.data()->record(reinterpret_cast<char*>(networkAudioSamples), numNetworkBytes);
|
||||
}
|
||||
|
||||
if (audioMixer && audioMixer->getActiveSocket()) {
|
||||
MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar();
|
||||
glm::vec3 headPosition = interfaceAvatar->getHead()->getPosition();
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "InterfaceConfig.h"
|
||||
#include "AudioStreamStats.h"
|
||||
#include "Recorder.h"
|
||||
#include "RingBufferHistory.h"
|
||||
#include "MovingMinMaxAvg.h"
|
||||
#include "AudioFilter.h"
|
||||
|
@ -102,6 +103,9 @@ public:
|
|||
|
||||
float getAudioOutputMsecsUnplayed() const;
|
||||
float getAudioOutputAverageMsecsUnplayed() const { return (float)_audioOutputMsecsUnplayedStats.getWindowAverage(); }
|
||||
|
||||
void setRecorder(RecorderPointer recorder) { _recorder = recorder; }
|
||||
void setPlayer(PlayerPointer player) { _player = player; }
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
|
@ -308,6 +312,9 @@ private:
|
|||
MovingMinMaxAvg<quint64> _packetSentTimeGaps;
|
||||
|
||||
AudioOutputIODevice _audioOutputIODevice;
|
||||
|
||||
WeakRecorderPointer _recorder;
|
||||
WeakPlayerPointer _player;
|
||||
};
|
||||
|
||||
|
||||
|
|
321
interface/src/Recorder.cpp
Normal file
321
interface/src/Recorder.cpp
Normal file
|
@ -0,0 +1,321 @@
|
|||
//
|
||||
// Recorder.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 8/7/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 <GLMHelpers.h>
|
||||
|
||||
#include <QMetaObject>
|
||||
|
||||
#include "Recorder.h"
|
||||
|
||||
void RecordingFrame::setBlendshapeCoefficients(QVector<float> blendshapeCoefficients) {
|
||||
_blendshapeCoefficients = blendshapeCoefficients;
|
||||
}
|
||||
|
||||
void RecordingFrame::setJointRotations(QVector<glm::quat> jointRotations) {
|
||||
_jointRotations = jointRotations;
|
||||
}
|
||||
|
||||
void RecordingFrame::setTranslation(glm::vec3 translation) {
|
||||
_translation = translation;
|
||||
}
|
||||
|
||||
void RecordingFrame::setRotation(glm::quat rotation) {
|
||||
_rotation = rotation;
|
||||
}
|
||||
|
||||
void RecordingFrame::setScale(float scale) {
|
||||
_scale = scale;
|
||||
}
|
||||
|
||||
void RecordingFrame::setHeadRotation(glm::quat headRotation) {
|
||||
_headRotation = headRotation;
|
||||
}
|
||||
|
||||
void RecordingFrame::setLeanSideways(float leanSideways) {
|
||||
_leanSideways = leanSideways;
|
||||
}
|
||||
|
||||
void RecordingFrame::setLeanForward(float leanForward) {
|
||||
_leanForward = leanForward;
|
||||
}
|
||||
|
||||
Recording::Recording() : _audio(NULL) {
|
||||
}
|
||||
|
||||
Recording::~Recording() {
|
||||
delete _audio;
|
||||
}
|
||||
|
||||
void Recording::addFrame(int timestamp, RecordingFrame &frame) {
|
||||
_timestamps << timestamp;
|
||||
_frames << frame;
|
||||
}
|
||||
|
||||
void Recording::addAudioPacket(QByteArray byteArray) {
|
||||
if (!_audio) {
|
||||
_audio = new Sound(byteArray);
|
||||
}
|
||||
_audio->append(byteArray);
|
||||
}
|
||||
|
||||
void Recording::clear() {
|
||||
_timestamps.clear();
|
||||
_frames.clear();
|
||||
delete _audio;
|
||||
_audio = NULL;
|
||||
}
|
||||
|
||||
Recorder::Recorder(AvatarData* avatar) :
|
||||
_recording(new Recording()),
|
||||
_avatar(avatar)
|
||||
{
|
||||
_timer.invalidate();
|
||||
}
|
||||
|
||||
bool Recorder::isRecording() const {
|
||||
return _timer.isValid();
|
||||
}
|
||||
|
||||
qint64 Recorder::elapsed() const {
|
||||
if (isRecording()) {
|
||||
return _timer.elapsed();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Recorder::startRecording() {
|
||||
qDebug() << "Recorder::startRecording()";
|
||||
_recording->clear();
|
||||
_timer.start();
|
||||
|
||||
RecordingFrame frame;
|
||||
frame.setBlendshapeCoefficients(_avatar->getHeadData()->getBlendshapeCoefficients());
|
||||
frame.setJointRotations(_avatar->getJointRotations());
|
||||
frame.setTranslation(_avatar->getPosition());
|
||||
frame.setRotation(_avatar->getOrientation());
|
||||
frame.setScale(_avatar->getTargetScale());
|
||||
|
||||
const HeadData* head = _avatar->getHeadData();
|
||||
glm::quat rotation = glm::quat(glm::radians(glm::vec3(head->getFinalPitch(),
|
||||
head->getFinalYaw(),
|
||||
head->getFinalRoll())));
|
||||
frame.setHeadRotation(rotation);
|
||||
frame.setLeanForward(_avatar->getHeadData()->getLeanForward());
|
||||
frame.setLeanSideways(_avatar->getHeadData()->getLeanSideways());
|
||||
|
||||
_recording->addFrame(0, frame);
|
||||
}
|
||||
|
||||
void Recorder::stopRecording() {
|
||||
qDebug() << "Recorder::stopRecording()";
|
||||
_timer.invalidate();
|
||||
|
||||
qDebug().nospace() << "Recorded " << _recording->getFrameNumber() << " during " << _recording->getLength() << " msec (" << _recording->getFrameNumber() / (_recording->getLength() / 1000.0f) << " fps)";
|
||||
}
|
||||
|
||||
void Recorder::saveToFile(QString file) {
|
||||
if (_recording->isEmpty()) {
|
||||
qDebug() << "Cannot save recording to file, recording is empty.";
|
||||
}
|
||||
|
||||
writeRecordingToFile(_recording, file);
|
||||
}
|
||||
|
||||
void Recorder::record() {
|
||||
if (isRecording()) {
|
||||
const RecordingFrame& referenceFrame = _recording->getFrame(0);
|
||||
RecordingFrame frame;
|
||||
frame.setBlendshapeCoefficients(_avatar->getHeadData()->getBlendshapeCoefficients());
|
||||
frame.setJointRotations(_avatar->getJointRotations());
|
||||
frame.setTranslation(_avatar->getPosition() - referenceFrame.getTranslation());
|
||||
frame.setRotation(glm::inverse(referenceFrame.getRotation()) * _avatar->getOrientation());
|
||||
frame.setScale(_avatar->getTargetScale() / referenceFrame.getScale());
|
||||
|
||||
|
||||
const HeadData* head = _avatar->getHeadData();
|
||||
glm::quat rotation = glm::quat(glm::radians(glm::vec3(head->getFinalPitch(),
|
||||
head->getFinalYaw(),
|
||||
head->getFinalRoll())));
|
||||
frame.setHeadRotation(rotation);
|
||||
frame.setLeanForward(_avatar->getHeadData()->getLeanForward());
|
||||
frame.setLeanSideways(_avatar->getHeadData()->getLeanSideways());
|
||||
|
||||
_recording->addFrame(_timer.elapsed(), frame);
|
||||
}
|
||||
}
|
||||
|
||||
void Recorder::record(char* samples, int size) {
|
||||
QByteArray byteArray(samples, size);
|
||||
_recording->addAudioPacket(byteArray);
|
||||
}
|
||||
|
||||
|
||||
Player::Player(AvatarData* avatar) :
|
||||
_recording(new Recording()),
|
||||
_avatar(avatar),
|
||||
_audioThread(NULL)
|
||||
{
|
||||
_timer.invalidate();
|
||||
_options.setLoop(false);
|
||||
_options.setVolume(1.0f);
|
||||
}
|
||||
|
||||
bool Player::isPlaying() const {
|
||||
return _timer.isValid();
|
||||
}
|
||||
|
||||
qint64 Player::elapsed() const {
|
||||
if (isPlaying()) {
|
||||
return _timer.elapsed();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
glm::quat Player::getHeadRotation() {
|
||||
if (!computeCurrentFrame()) {
|
||||
qWarning() << "Incorrect use of Player::getHeadRotation()";
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
if (_currentFrame == 0) {
|
||||
return _recording->getFrame(_currentFrame).getHeadRotation();
|
||||
}
|
||||
return _recording->getFrame(0).getHeadRotation() *
|
||||
_recording->getFrame(_currentFrame).getHeadRotation();
|
||||
}
|
||||
|
||||
float Player::getLeanSideways() {
|
||||
if (!computeCurrentFrame()) {
|
||||
qWarning() << "Incorrect use of Player::getLeanSideways()";
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return _recording->getFrame(_currentFrame).getLeanSideways();
|
||||
}
|
||||
|
||||
float Player::getLeanForward() {
|
||||
if (!computeCurrentFrame()) {
|
||||
qWarning() << "Incorrect use of Player::getLeanForward()";
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return _recording->getFrame(_currentFrame).getLeanForward();
|
||||
}
|
||||
|
||||
void Player::startPlaying() {
|
||||
if (_recording && _recording->getFrameNumber() > 0) {
|
||||
qDebug() << "Recorder::startPlaying()";
|
||||
_currentFrame = 0;
|
||||
|
||||
// Setup audio thread
|
||||
_audioThread = new QThread();
|
||||
_options.setPosition(_avatar->getPosition());
|
||||
_options.setOrientation(_avatar->getOrientation());
|
||||
_injector.reset(new AudioInjector(_recording->getAudio(), _options), &QObject::deleteLater);
|
||||
_injector->moveToThread(_audioThread);
|
||||
_audioThread->start();
|
||||
QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection);
|
||||
|
||||
_timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
void Player::stopPlaying() {
|
||||
if (!isPlaying()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_timer.invalidate();
|
||||
|
||||
_avatar->clearJointsData();
|
||||
|
||||
// Cleanup audio thread
|
||||
_injector->stop();
|
||||
_injector.clear();
|
||||
_audioThread->exit();
|
||||
_audioThread->deleteLater();
|
||||
qDebug() << "Recorder::stopPlaying()";
|
||||
}
|
||||
|
||||
void Player::loadFromFile(QString file) {
|
||||
if (_recording) {
|
||||
_recording->clear();
|
||||
} else {
|
||||
_recording = RecordingPointer(new Recording());
|
||||
}
|
||||
readRecordingFromFile(_recording, file);
|
||||
}
|
||||
|
||||
void Player::loadRecording(RecordingPointer recording) {
|
||||
_recording = recording;
|
||||
}
|
||||
|
||||
void Player::play() {
|
||||
computeCurrentFrame();
|
||||
if (_currentFrame < 0 || _currentFrame >= _recording->getFrameNumber() - 1) {
|
||||
// If it's the end of the recording, stop playing
|
||||
stopPlaying();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentFrame == 0) {
|
||||
_avatar->setPosition(_recording->getFrame(_currentFrame).getTranslation());
|
||||
_avatar->setOrientation(_recording->getFrame(_currentFrame).getRotation());
|
||||
_avatar->setTargetScale(_recording->getFrame(_currentFrame).getScale());
|
||||
_avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations());
|
||||
HeadData* head = const_cast<HeadData*>(_avatar->getHeadData());
|
||||
head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients());
|
||||
} else {
|
||||
_avatar->setPosition(_recording->getFrame(0).getTranslation() +
|
||||
_recording->getFrame(_currentFrame).getTranslation());
|
||||
_avatar->setOrientation(_recording->getFrame(0).getRotation() *
|
||||
_recording->getFrame(_currentFrame).getRotation());
|
||||
_avatar->setTargetScale(_recording->getFrame(0).getScale() *
|
||||
_recording->getFrame(_currentFrame).getScale());
|
||||
_avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations());
|
||||
HeadData* head = const_cast<HeadData*>(_avatar->getHeadData());
|
||||
head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients());
|
||||
}
|
||||
|
||||
_options.setPosition(_avatar->getPosition());
|
||||
_options.setOrientation(_avatar->getOrientation());
|
||||
_injector->setOptions(_options);
|
||||
}
|
||||
|
||||
bool Player::computeCurrentFrame() {
|
||||
if (!isPlaying()) {
|
||||
_currentFrame = -1;
|
||||
return false;
|
||||
}
|
||||
if (_currentFrame < 0) {
|
||||
_currentFrame = 0;
|
||||
}
|
||||
|
||||
while (_currentFrame < _recording->getFrameNumber() - 1 &&
|
||||
_recording->getFrameTimestamp(_currentFrame) < _timer.elapsed()) {
|
||||
++_currentFrame;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void writeRecordingToFile(RecordingPointer recording, QString file) {
|
||||
// TODO
|
||||
qDebug() << "Writing recording to " << file;
|
||||
}
|
||||
|
||||
RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file) {
|
||||
// TODO
|
||||
qDebug() << "Reading recording from " << file;
|
||||
return recording;
|
||||
}
|
170
interface/src/Recorder.h
Normal file
170
interface/src/Recorder.h
Normal file
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// Recorder.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 8/7/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_Recorder_h
|
||||
#define hifi_Recorder_h
|
||||
|
||||
#include <QBitArray>
|
||||
#include <QElapsedTimer>
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
#include <QVector>
|
||||
#include <QWeakPointer>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <AudioInjector.h>
|
||||
#include <AvatarData.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <Sound.h>
|
||||
|
||||
class Recorder;
|
||||
class Recording;
|
||||
class Player;
|
||||
|
||||
typedef QSharedPointer<Recording> RecordingPointer;
|
||||
typedef QSharedPointer<Recorder> RecorderPointer;
|
||||
typedef QWeakPointer<Recorder> WeakRecorderPointer;
|
||||
typedef QSharedPointer<Player> PlayerPointer;
|
||||
typedef QWeakPointer<Player> WeakPlayerPointer;
|
||||
|
||||
/// Stores the different values associated to one recording frame
|
||||
class RecordingFrame {
|
||||
public:
|
||||
QVector<float> getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||
QVector<glm::quat> getJointRotations() const { return _jointRotations; }
|
||||
glm::vec3 getTranslation() const { return _translation; }
|
||||
glm::quat getRotation() const { return _rotation; }
|
||||
float getScale() const { return _scale; }
|
||||
glm::quat getHeadRotation() const { return _headRotation; }
|
||||
float getLeanSideways() const { return _leanSideways; }
|
||||
float getLeanForward() const { return _leanForward; }
|
||||
|
||||
protected:
|
||||
void setBlendshapeCoefficients(QVector<float> blendshapeCoefficients);
|
||||
void setJointRotations(QVector<glm::quat> jointRotations);
|
||||
void setTranslation(glm::vec3 translation);
|
||||
void setRotation(glm::quat rotation);
|
||||
void setScale(float scale);
|
||||
void setHeadRotation(glm::quat headRotation);
|
||||
void setLeanSideways(float leanSideways);
|
||||
void setLeanForward(float leanForward);
|
||||
|
||||
private:
|
||||
QVector<float> _blendshapeCoefficients;
|
||||
QVector<glm::quat> _jointRotations;
|
||||
glm::vec3 _translation;
|
||||
glm::quat _rotation;
|
||||
float _scale;
|
||||
glm::quat _headRotation;
|
||||
float _leanSideways;
|
||||
float _leanForward;
|
||||
|
||||
friend class Recorder;
|
||||
friend void writeRecordingToFile(RecordingPointer recording, QString file);
|
||||
friend RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file);
|
||||
};
|
||||
|
||||
/// Stores a recording
|
||||
class Recording {
|
||||
public:
|
||||
Recording();
|
||||
~Recording();
|
||||
|
||||
bool isEmpty() const { return _timestamps.isEmpty(); }
|
||||
int getLength() const { return _timestamps.last(); } // in ms
|
||||
|
||||
int getFrameNumber() const { return _frames.size(); }
|
||||
qint32 getFrameTimestamp(int i) const { return _timestamps[i]; }
|
||||
const RecordingFrame& getFrame(int i) const { return _frames[i]; }
|
||||
Sound* getAudio() const { return _audio; }
|
||||
|
||||
protected:
|
||||
void addFrame(int timestamp, RecordingFrame& frame);
|
||||
void addAudioPacket(QByteArray byteArray);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
QVector<qint32> _timestamps;
|
||||
QVector<RecordingFrame> _frames;
|
||||
|
||||
Sound* _audio;
|
||||
|
||||
friend class Recorder;
|
||||
friend class Player;
|
||||
friend void writeRecordingToFile(RecordingPointer recording, QString file);
|
||||
friend RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file);
|
||||
};
|
||||
|
||||
/// Records a recording
|
||||
class Recorder {
|
||||
public:
|
||||
Recorder(AvatarData* avatar);
|
||||
|
||||
bool isRecording() const;
|
||||
qint64 elapsed() const;
|
||||
|
||||
RecordingPointer getRecording() const { return _recording; }
|
||||
|
||||
public slots:
|
||||
void startRecording();
|
||||
void stopRecording();
|
||||
void saveToFile(QString file);
|
||||
void record();
|
||||
void record(char* samples, int size);
|
||||
|
||||
private:
|
||||
QElapsedTimer _timer;
|
||||
RecordingPointer _recording;
|
||||
|
||||
AvatarData* _avatar;
|
||||
};
|
||||
|
||||
/// Plays back a recording
|
||||
class Player {
|
||||
public:
|
||||
Player(AvatarData* avatar);
|
||||
|
||||
bool isPlaying() const;
|
||||
qint64 elapsed() const;
|
||||
|
||||
// Those should only be called if isPlaying() returns true
|
||||
glm::quat getHeadRotation();
|
||||
float getLeanSideways();
|
||||
float getLeanForward();
|
||||
|
||||
|
||||
public slots:
|
||||
void startPlaying();
|
||||
void stopPlaying();
|
||||
void loadFromFile(QString file);
|
||||
void loadRecording(RecordingPointer recording);
|
||||
void play();
|
||||
|
||||
private:
|
||||
bool computeCurrentFrame();
|
||||
|
||||
QElapsedTimer _timer;
|
||||
RecordingPointer _recording;
|
||||
int _currentFrame;
|
||||
|
||||
QSharedPointer<AudioInjector> _injector;
|
||||
AudioInjectorOptions _options;
|
||||
|
||||
AvatarData* _avatar;
|
||||
QThread* _audioThread;
|
||||
};
|
||||
|
||||
void writeRecordingToFile(RecordingPointer recording, QString file);
|
||||
RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file);
|
||||
|
||||
#endif // hifi_Recorder_h
|
|
@ -29,6 +29,7 @@
|
|||
#include "Menu.h"
|
||||
#include "ModelReferential.h"
|
||||
#include "Physics.h"
|
||||
#include "Recorder.h"
|
||||
#include "world.h"
|
||||
#include "devices/OculusManager.h"
|
||||
#include "renderer/TextureCache.h"
|
||||
|
@ -725,6 +726,17 @@ bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList&
|
|||
return collided;
|
||||
}
|
||||
|
||||
QVector<glm::quat> Avatar::getJointRotations() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
return AvatarData::getJointRotations();
|
||||
}
|
||||
QVector<glm::quat> jointRotations(_skeletonModel.getJointStateCount());
|
||||
for (int i = 0; i < _skeletonModel.getJointStateCount(); ++i) {
|
||||
_skeletonModel.getJointState(i, jointRotations[i]);
|
||||
}
|
||||
return jointRotations;
|
||||
}
|
||||
|
||||
glm::quat Avatar::getJointRotation(int index) const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
return AvatarData::getJointRotation(index);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "Hand.h"
|
||||
#include "Head.h"
|
||||
#include "InterfaceConfig.h"
|
||||
#include "Recorder.h"
|
||||
#include "SkeletonModel.h"
|
||||
#include "world.h"
|
||||
|
||||
|
@ -121,6 +122,7 @@ public:
|
|||
|
||||
virtual bool isMyAvatar() { return false; }
|
||||
|
||||
virtual QVector<glm::quat> getJointRotations() const;
|
||||
virtual glm::quat getJointRotation(int index) const;
|
||||
virtual int getJointIndex(const QString& name) const;
|
||||
virtual QStringList getJointNames() const;
|
||||
|
@ -220,8 +222,6 @@ private:
|
|||
void renderBillboard();
|
||||
|
||||
float getBillboardSize() const;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_Avatar_h
|
||||
|
|
|
@ -64,13 +64,18 @@ void Head::reset() {
|
|||
void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
||||
// Update audio trailing average for rendering facial animations
|
||||
if (isMine) {
|
||||
FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker();
|
||||
if ((_isFaceshiftConnected = faceTracker)) {
|
||||
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
||||
_isFaceshiftConnected = true;
|
||||
} else if (Application::getInstance()->getDDE()->isActive()) {
|
||||
faceTracker = Application::getInstance()->getDDE();
|
||||
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
|
||||
// Only use face trackers when not playing back a recording.
|
||||
if (!myAvatar->isPlaying()) {
|
||||
FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker();
|
||||
if ((_isFaceshiftConnected = faceTracker)) {
|
||||
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
||||
_isFaceshiftConnected = true;
|
||||
} else if (Application::getInstance()->getDDE()->isActive()) {
|
||||
faceTracker = Application::getInstance()->getDDE();
|
||||
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,8 +48,6 @@ public:
|
|||
void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; }
|
||||
void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; }
|
||||
void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; }
|
||||
void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; }
|
||||
void setLeanForward(float leanForward) { _leanForward = leanForward; }
|
||||
|
||||
/// \return orientationBase+Delta
|
||||
glm::quat getFinalOrientationInLocalFrame() const;
|
||||
|
@ -57,7 +55,6 @@ public:
|
|||
/// \return orientationBody * (orientationBase+Delta)
|
||||
glm::quat getFinalOrientationInWorldFrame() const;
|
||||
|
||||
|
||||
/// \return orientationBody * orientationBasePitch
|
||||
glm::quat getCameraOrientation () const;
|
||||
|
||||
|
@ -71,8 +68,6 @@ public:
|
|||
glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
|
||||
glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
||||
glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
|
||||
float getLeanSideways() const { return _leanSideways; }
|
||||
float getLeanForward() const { return _leanForward; }
|
||||
float getFinalLeanSideways() const { return _leanSideways + _deltaLeanSideways; }
|
||||
float getFinalLeanForward() const { return _leanForward + _deltaLeanForward; }
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "ModelReferential.h"
|
||||
#include "MyAvatar.h"
|
||||
#include "Physics.h"
|
||||
#include "Recorder.h"
|
||||
#include "devices/Faceshift.h"
|
||||
#include "devices/OculusManager.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
|
@ -83,10 +84,11 @@ MyAvatar::MyAvatar() :
|
|||
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
|
||||
_driveKeys[i] = 0.0f;
|
||||
}
|
||||
_skeletonModel.setEnableShapes(true);
|
||||
// The skeleton is both a PhysicsEntity and Ragdoll, so we add it to the simulation once for each type.
|
||||
_physicsSimulation.setEntity(&_skeletonModel);
|
||||
_physicsSimulation.setRagdoll(&_skeletonModel);
|
||||
|
||||
_skeletonModel.setEnableShapes(true);
|
||||
Ragdoll* ragdoll = _skeletonModel.buildRagdoll();
|
||||
_physicsSimulation.setRagdoll(ragdoll);
|
||||
}
|
||||
|
||||
MyAvatar::~MyAvatar() {
|
||||
|
@ -135,6 +137,12 @@ void MyAvatar::update(float deltaTime) {
|
|||
|
||||
void MyAvatar::simulate(float deltaTime) {
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
|
||||
// Play back recording
|
||||
if (_player && _player->isPlaying()) {
|
||||
_player->play();
|
||||
}
|
||||
|
||||
if (_scale != _targetScale) {
|
||||
float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale;
|
||||
setScale(scale);
|
||||
|
@ -147,7 +155,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
updateOrientation(deltaTime);
|
||||
updatePosition(deltaTime);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("hand");
|
||||
// update avatar skeleton and simulate hand and head
|
||||
|
@ -205,12 +213,21 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
|
||||
{
|
||||
PerformanceTimer perfTimer("ragdoll");
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
|
||||
Ragdoll* ragdoll = _skeletonModel.getRagdoll();
|
||||
if (ragdoll && Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
|
||||
const float minError = 0.00001f;
|
||||
const float maxIterations = 3;
|
||||
const quint64 maxUsec = 4000;
|
||||
_physicsSimulation.setTranslation(_position);
|
||||
_physicsSimulation.stepForward(deltaTime, minError, maxIterations, maxUsec);
|
||||
|
||||
// harvest any displacement of the Ragdoll that is a result of collisions
|
||||
glm::vec3 ragdollDisplacement = ragdoll->getAndClearAccumulatedMovement();
|
||||
const float MAX_RAGDOLL_DISPLACEMENT_2 = 1.0f;
|
||||
float length2 = glm::length2(ragdollDisplacement);
|
||||
if (length2 > EPSILON && length2 < MAX_RAGDOLL_DISPLACEMENT_2) {
|
||||
setPosition(getPosition() + ragdollDisplacement);
|
||||
}
|
||||
} else {
|
||||
_skeletonModel.moveShapesTowardJoints(1.0f);
|
||||
}
|
||||
|
@ -242,6 +259,11 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
// Record avatars movements.
|
||||
if (_recorder && _recorder->isRecording()) {
|
||||
_recorder->record();
|
||||
}
|
||||
|
||||
// consider updating our billboard
|
||||
maybeUpdateBillboard();
|
||||
}
|
||||
|
@ -250,7 +272,9 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
void MyAvatar::updateFromTrackers(float deltaTime) {
|
||||
glm::vec3 estimatedPosition, estimatedRotation;
|
||||
|
||||
if (Application::getInstance()->getPrioVR()->hasHeadRotation()) {
|
||||
if (isPlaying()) {
|
||||
estimatedRotation = glm::degrees(safeEulerAngles(_player->getHeadRotation()));
|
||||
} else if (Application::getInstance()->getPrioVR()->hasHeadRotation()) {
|
||||
estimatedRotation = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getHeadRotation()));
|
||||
estimatedRotation.x *= -1.0f;
|
||||
estimatedRotation.z *= -1.0f;
|
||||
|
@ -292,7 +316,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
|
|||
|
||||
|
||||
Head* head = getHead();
|
||||
if (OculusManager::isConnected()) {
|
||||
if (OculusManager::isConnected() || isPlaying()) {
|
||||
head->setDeltaPitch(estimatedRotation.x);
|
||||
head->setDeltaYaw(estimatedRotation.y);
|
||||
} else {
|
||||
|
@ -302,6 +326,11 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
|
|||
}
|
||||
head->setDeltaRoll(estimatedRotation.z);
|
||||
|
||||
if (isPlaying()) {
|
||||
head->setLeanSideways(_player->getLeanSideways());
|
||||
head->setLeanForward(_player->getLeanForward());
|
||||
return;
|
||||
}
|
||||
// the priovr can give us exact lean
|
||||
if (Application::getInstance()->getPrioVR()->isActive()) {
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getTorsoRotation()));
|
||||
|
@ -309,7 +338,6 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
|
|||
head->setLeanForward(eulers.x);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update torso lean distance based on accelerometer data
|
||||
const float TORSO_LENGTH = 0.5f;
|
||||
glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f);
|
||||
|
@ -480,6 +508,45 @@ bool MyAvatar::setJointReferential(int id, int jointIndex) {
|
|||
}
|
||||
}
|
||||
|
||||
bool MyAvatar::isRecording() const {
|
||||
return _recorder && _recorder->isRecording();
|
||||
}
|
||||
|
||||
RecorderPointer MyAvatar::startRecording() {
|
||||
if (!_recorder) {
|
||||
_recorder = RecorderPointer(new Recorder(this));
|
||||
}
|
||||
_recorder->startRecording();
|
||||
return _recorder;
|
||||
}
|
||||
|
||||
void MyAvatar::stopRecording() {
|
||||
if (_recorder) {
|
||||
_recorder->stopRecording();
|
||||
}
|
||||
}
|
||||
|
||||
bool MyAvatar::isPlaying() const {
|
||||
return _player && _player->isPlaying();
|
||||
}
|
||||
|
||||
PlayerPointer MyAvatar::startPlaying() {
|
||||
if (!_player) {
|
||||
_player = PlayerPointer(new Player(this));
|
||||
}
|
||||
if (_recorder) {
|
||||
_player->loadRecording(_recorder->getRecording());
|
||||
_player->startPlaying();
|
||||
}
|
||||
return _player;
|
||||
}
|
||||
|
||||
void MyAvatar::stopPlaying() {
|
||||
if (_player) {
|
||||
_player->stopPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setLocalGravity(glm::vec3 gravity) {
|
||||
_motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
|
||||
// Environmental and Local gravities are incompatible. Since Local is being set here
|
||||
|
@ -862,6 +929,14 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const {
|
|||
|
||||
const float JOINT_PRIORITY = 2.0f;
|
||||
|
||||
void MyAvatar::setJointRotations(QVector<glm::quat> jointRotations) {
|
||||
for (int i = 0; i < jointRotations.size(); ++i) {
|
||||
if (i < _jointData.size()) {
|
||||
_skeletonModel.setJointState(i, true, jointRotations[i], JOINT_PRIORITY + 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setJointData(int index, const glm::quat& rotation) {
|
||||
Avatar::setJointData(index, rotation);
|
||||
if (QThread::currentThread() == thread()) {
|
||||
|
@ -876,6 +951,15 @@ void MyAvatar::clearJointData(int index) {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::clearJointsData() {
|
||||
for (int i = 0; i < _jointData.size(); ++i) {
|
||||
Avatar::clearJointData(i);
|
||||
if (QThread::currentThread() == thread()) {
|
||||
_skeletonModel.clearJointState(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) {
|
||||
Avatar::setFaceModelURL(faceModelURL);
|
||||
_billboardValid = false;
|
||||
|
@ -1604,10 +1688,10 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
|||
if (simulation != &(_physicsSimulation)) {
|
||||
skeleton->setEnableShapes(true);
|
||||
_physicsSimulation.addEntity(skeleton);
|
||||
_physicsSimulation.addRagdoll(skeleton);
|
||||
_physicsSimulation.addRagdoll(skeleton->getRagdoll());
|
||||
}
|
||||
} else if (simulation == &(_physicsSimulation)) {
|
||||
_physicsSimulation.removeRagdoll(skeleton);
|
||||
_physicsSimulation.removeRagdoll(skeleton->getRagdoll());
|
||||
_physicsSimulation.removeEntity(skeleton);
|
||||
skeleton->setEnableShapes(false);
|
||||
}
|
||||
|
|
|
@ -112,8 +112,10 @@ public:
|
|||
void updateLookAtTargetAvatar();
|
||||
void clearLookAtTargetAvatar();
|
||||
|
||||
virtual void setJointRotations(QVector<glm::quat> jointRotations);
|
||||
virtual void setJointData(int index, const glm::quat& rotation);
|
||||
virtual void clearJointData(int index);
|
||||
virtual void clearJointsData();
|
||||
virtual void setFaceModelURL(const QUrl& faceModelURL);
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
||||
|
@ -155,6 +157,17 @@ public slots:
|
|||
bool setModelReferential(int id);
|
||||
bool setJointReferential(int id, int jointIndex);
|
||||
|
||||
const RecorderPointer getRecorder() const { return _recorder; }
|
||||
bool isRecording() const;
|
||||
RecorderPointer startRecording();
|
||||
void stopRecording();
|
||||
|
||||
const PlayerPointer getPlayer() const { return _player; }
|
||||
bool isPlaying() const;
|
||||
PlayerPointer startPlaying();
|
||||
void stopPlaying();
|
||||
|
||||
|
||||
signals:
|
||||
void transformChanged();
|
||||
|
||||
|
@ -192,6 +205,9 @@ private:
|
|||
QList<AnimationHandlePointer> _animationHandles;
|
||||
PhysicsSimulation _physicsSimulation;
|
||||
|
||||
RecorderPointer _recorder;
|
||||
PlayerPointer _player;
|
||||
|
||||
// private methods
|
||||
float computeDistanceToFloor(const glm::vec3& startPoint);
|
||||
void updateOrientation(float deltaTime);
|
||||
|
|
|
@ -14,22 +14,25 @@
|
|||
|
||||
#include <VerletCapsuleShape.h>
|
||||
#include <VerletSphereShape.h>
|
||||
#include <DistanceConstraint.h>
|
||||
#include <FixedConstraint.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Avatar.h"
|
||||
#include "Hand.h"
|
||||
#include "Menu.h"
|
||||
#include "MuscleConstraint.h"
|
||||
#include "SkeletonModel.h"
|
||||
#include "SkeletonRagdoll.h"
|
||||
|
||||
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
|
||||
Model(parent),
|
||||
Ragdoll(),
|
||||
_owningAvatar(owningAvatar),
|
||||
_boundingShape(),
|
||||
_boundingShapeLocalOffset(0.0f) {
|
||||
_boundingShapeLocalOffset(0.0f),
|
||||
_ragdoll(NULL) {
|
||||
}
|
||||
|
||||
SkeletonModel::~SkeletonModel() {
|
||||
delete _ragdoll;
|
||||
_ragdoll = NULL;
|
||||
}
|
||||
|
||||
void SkeletonModel::setJointStates(QVector<JointState> states) {
|
||||
|
@ -59,9 +62,15 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
|
||||
Model::simulate(deltaTime, fullUpdate);
|
||||
|
||||
if (!(isActive() && _owningAvatar->isMyAvatar())) {
|
||||
if (!isActive() || !_owningAvatar->isMyAvatar()) {
|
||||
return; // only simulate for own avatar
|
||||
}
|
||||
|
||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
if (myAvatar->isPlaying()) {
|
||||
// Don't take inputs if playing back a recording.
|
||||
return;
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
PrioVR* prioVR = Application::getInstance()->getPrioVR();
|
||||
|
@ -155,9 +164,6 @@ void SkeletonModel::getBodyShapes(QVector<const Shape*>& shapes) const {
|
|||
void SkeletonModel::renderIKConstraints() {
|
||||
renderJointConstraints(getRightHandJointIndex());
|
||||
renderJointConstraints(getLeftHandJointIndex());
|
||||
//if (isActive() && _owningAvatar->isMyAvatar()) {
|
||||
// renderRagdoll();
|
||||
//}
|
||||
}
|
||||
|
||||
class IndexValue {
|
||||
|
@ -489,21 +495,25 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco
|
|||
}
|
||||
|
||||
void SkeletonModel::renderRagdoll() {
|
||||
if (!_ragdoll) {
|
||||
return;
|
||||
}
|
||||
const QVector<VerletPoint>& points = _ragdoll->getPoints();
|
||||
const int BALL_SUBDIVISIONS = 6;
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_LIGHTING);
|
||||
glPushMatrix();
|
||||
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
int numPoints = _ragdollPoints.size();
|
||||
int numPoints = points.size();
|
||||
float alpha = 0.3f;
|
||||
float radius1 = 0.008f;
|
||||
float radius2 = 0.01f;
|
||||
glm::vec3 simulationTranslation = getTranslationInSimulationFrame();
|
||||
glm::vec3 simulationTranslation = _ragdoll->getTranslationInSimulationFrame();
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
glPushMatrix();
|
||||
// NOTE: ragdollPoints are in simulation-frame but we want them to be model-relative
|
||||
glm::vec3 position = _ragdollPoints[i]._position - simulationTranslation;
|
||||
glm::vec3 position = points[i]._position - simulationTranslation;
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
// draw each point as a yellow hexagon with black border
|
||||
glColor4f(0.0f, 0.0f, 0.0f, alpha);
|
||||
|
@ -517,109 +527,18 @@ void SkeletonModel::renderRagdoll() {
|
|||
glEnable(GL_LIGHTING);
|
||||
}
|
||||
|
||||
// virtual
|
||||
void SkeletonModel::initRagdollPoints() {
|
||||
clearRagdollConstraintsAndPoints();
|
||||
_muscleConstraints.clear();
|
||||
|
||||
initRagdollTransform();
|
||||
// one point for each joint
|
||||
int numStates = _jointStates.size();
|
||||
_ragdollPoints.fill(VerletPoint(), numStates);
|
||||
for (int i = 0; i < numStates; ++i) {
|
||||
const JointState& state = _jointStates.at(i);
|
||||
// _ragdollPoints start in model-frame
|
||||
_ragdollPoints[i].initPosition(state.getPosition());
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::buildRagdollConstraints() {
|
||||
// NOTE: the length of DistanceConstraints is computed and locked in at this time
|
||||
// so make sure the ragdoll positions are in a normal configuration before here.
|
||||
const int numPoints = _ragdollPoints.size();
|
||||
assert(numPoints == _jointStates.size());
|
||||
|
||||
float minBone = FLT_MAX;
|
||||
float maxBone = -FLT_MAX;
|
||||
QMultiMap<int, int> families;
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
const JointState& state = _jointStates.at(i);
|
||||
int parentIndex = state.getParentIndex();
|
||||
if (parentIndex == -1) {
|
||||
FixedConstraint* anchor = new FixedConstraint(&_translationInSimulationFrame, &(_ragdollPoints[i]));
|
||||
_fixedConstraints.push_back(anchor);
|
||||
} else {
|
||||
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[i]), &(_ragdollPoints[parentIndex]));
|
||||
bone->setDistance(state.getDistanceToParent());
|
||||
_boneConstraints.push_back(bone);
|
||||
families.insert(parentIndex, i);
|
||||
}
|
||||
float boneLength = glm::length(state.getPositionInParentFrame());
|
||||
if (boneLength > maxBone) {
|
||||
maxBone = boneLength;
|
||||
} else if (boneLength < minBone) {
|
||||
minBone = boneLength;
|
||||
}
|
||||
}
|
||||
// Joints that have multiple children effectively have rigid constraints between the children
|
||||
// in the parent frame, so we add DistanceConstraints between children in the same family.
|
||||
QMultiMap<int, int>::iterator itr = families.begin();
|
||||
while (itr != families.end()) {
|
||||
QList<int> children = families.values(itr.key());
|
||||
int numChildren = children.size();
|
||||
if (numChildren > 1) {
|
||||
for (int i = 1; i < numChildren; ++i) {
|
||||
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[i-1]]), &(_ragdollPoints[children[i]]));
|
||||
_boneConstraints.push_back(bone);
|
||||
}
|
||||
if (numChildren > 2) {
|
||||
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[numChildren-1]]), &(_ragdollPoints[children[0]]));
|
||||
_boneConstraints.push_back(bone);
|
||||
}
|
||||
}
|
||||
++itr;
|
||||
}
|
||||
|
||||
float MAX_STRENGTH = 0.6f;
|
||||
float MIN_STRENGTH = 0.05f;
|
||||
// each joint gets a MuscleConstraint to its parent
|
||||
for (int i = 1; i < numPoints; ++i) {
|
||||
const JointState& state = _jointStates.at(i);
|
||||
int p = state.getParentIndex();
|
||||
if (p == -1) {
|
||||
continue;
|
||||
}
|
||||
MuscleConstraint* constraint = new MuscleConstraint(&(_ragdollPoints[p]), &(_ragdollPoints[i]));
|
||||
_muscleConstraints.push_back(constraint);
|
||||
|
||||
// Short joints are more susceptible to wiggle so we modulate the strength based on the joint's length:
|
||||
// long = weak and short = strong.
|
||||
constraint->setIndices(p, i);
|
||||
float boneLength = glm::length(state.getPositionInParentFrame());
|
||||
|
||||
float strength = MIN_STRENGTH + (MAX_STRENGTH - MIN_STRENGTH) * (maxBone - boneLength) / (maxBone - minBone);
|
||||
if (!families.contains(i)) {
|
||||
// Although muscles only pull on the children not parents, nevertheless those joints that have
|
||||
// parents AND children are more stable than joints at the end such as fingers. For such joints we
|
||||
// bestow maximum strength which helps reduce wiggle.
|
||||
strength = MAX_MUSCLE_STRENGTH;
|
||||
}
|
||||
constraint->setStrength(strength);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SkeletonModel::updateVisibleJointStates() {
|
||||
if (_showTrueJointTransforms) {
|
||||
if (_showTrueJointTransforms || !_ragdoll) {
|
||||
// no need to update visible transforms
|
||||
return;
|
||||
}
|
||||
const QVector<VerletPoint>& ragdollPoints = _ragdoll->getPoints();
|
||||
QVector<glm::vec3> points;
|
||||
points.reserve(_jointStates.size());
|
||||
glm::quat invRotation = glm::inverse(_rotation);
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
JointState& state = _jointStates[i];
|
||||
points.push_back(_ragdollPoints[i]._position);
|
||||
points.push_back(ragdollPoints[i]._position);
|
||||
|
||||
// get the parent state (this is the state that we want to rotate)
|
||||
int parentIndex = state.getParentIndex();
|
||||
|
@ -653,15 +572,14 @@ void SkeletonModel::updateVisibleJointStates() {
|
|||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void SkeletonModel::stepRagdollForward(float deltaTime) {
|
||||
setRagdollTransform(_translation, _rotation);
|
||||
Ragdoll::stepRagdollForward(deltaTime);
|
||||
updateMuscles();
|
||||
int numConstraints = _muscleConstraints.size();
|
||||
for (int i = 0; i < numConstraints; ++i) {
|
||||
_muscleConstraints[i]->enforce();
|
||||
SkeletonRagdoll* SkeletonModel::buildRagdoll() {
|
||||
if (!_ragdoll) {
|
||||
_ragdoll = new SkeletonRagdoll(this);
|
||||
if (_enableShapes) {
|
||||
buildShapes();
|
||||
}
|
||||
}
|
||||
return _ragdoll;
|
||||
}
|
||||
|
||||
float DENSITY_OF_WATER = 1000.0f; // kg/m^3
|
||||
|
@ -679,11 +597,17 @@ void SkeletonModel::buildShapes() {
|
|||
return;
|
||||
}
|
||||
|
||||
initRagdollPoints();
|
||||
float massScale = getMassScale();
|
||||
if (!_ragdoll) {
|
||||
_ragdoll = new SkeletonRagdoll(this);
|
||||
}
|
||||
_ragdoll->initPoints();
|
||||
QVector<VerletPoint>& points = _ragdoll->getPoints();
|
||||
|
||||
float massScale = _ragdoll->getMassScale();
|
||||
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
const int numStates = _jointStates.size();
|
||||
float totalMass = 0.0f;
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
JointState& state = _jointStates[i];
|
||||
const FBXJoint& joint = state.getFBXJoint();
|
||||
|
@ -700,28 +624,33 @@ void SkeletonModel::buildShapes() {
|
|||
Shape* shape = NULL;
|
||||
int parentIndex = joint.parentIndex;
|
||||
if (type == Shape::SPHERE_SHAPE) {
|
||||
shape = new VerletSphereShape(radius, &(_ragdollPoints[i]));
|
||||
shape = new VerletSphereShape(radius, &(points[i]));
|
||||
shape->setEntity(this);
|
||||
_ragdollPoints[i].setMass(massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume()));
|
||||
float mass = massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
|
||||
points[i].setMass(mass);
|
||||
totalMass += mass;
|
||||
} else if (type == Shape::CAPSULE_SHAPE) {
|
||||
assert(parentIndex != -1);
|
||||
shape = new VerletCapsuleShape(radius, &(_ragdollPoints[parentIndex]), &(_ragdollPoints[i]));
|
||||
shape = new VerletCapsuleShape(radius, &(points[parentIndex]), &(points[i]));
|
||||
shape->setEntity(this);
|
||||
_ragdollPoints[i].setMass(massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume()));
|
||||
float mass = massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
|
||||
points[i].setMass(mass);
|
||||
totalMass += mass;
|
||||
}
|
||||
if (parentIndex != -1) {
|
||||
// always disable collisions between joint and its parent
|
||||
if (shape) {
|
||||
disableCollisions(i, parentIndex);
|
||||
}
|
||||
} else {
|
||||
// give the base joint a very large mass since it doesn't actually move
|
||||
// in the local-frame simulation (it defines the origin)
|
||||
_ragdollPoints[i].setMass(VERY_BIG_MASS);
|
||||
}
|
||||
}
|
||||
_shapes.push_back(shape);
|
||||
}
|
||||
|
||||
// set the mass of the root
|
||||
if (numStates > 0) {
|
||||
points[0].setMass(totalMass);
|
||||
}
|
||||
|
||||
// This method moves the shapes to their default positions in Model frame.
|
||||
computeBoundingShape(geometry);
|
||||
|
||||
|
@ -729,17 +658,11 @@ void SkeletonModel::buildShapes() {
|
|||
// joints that are currently colliding.
|
||||
disableCurrentSelfCollisions();
|
||||
|
||||
buildRagdollConstraints();
|
||||
_ragdoll->buildConstraints();
|
||||
|
||||
// ... then move shapes back to current joint positions
|
||||
if (_ragdollPoints.size() == numStates) {
|
||||
int numStates = _jointStates.size();
|
||||
for (int i = 0; i < numStates; ++i) {
|
||||
// ragdollPoints start in model-frame
|
||||
_ragdollPoints[i].initPosition(_jointStates.at(i).getPosition());
|
||||
}
|
||||
}
|
||||
enforceRagdollConstraints();
|
||||
_ragdoll->slamPointPositions();
|
||||
_ragdoll->enforceConstraints();
|
||||
}
|
||||
|
||||
void SkeletonModel::moveShapesTowardJoints(float deltaTime) {
|
||||
|
@ -747,8 +670,9 @@ void SkeletonModel::moveShapesTowardJoints(float deltaTime) {
|
|||
// unravel a skelton that has become tangled in its constraints. So let's keep this
|
||||
// around for a while just in case.
|
||||
const int numStates = _jointStates.size();
|
||||
assert(_jointStates.size() == _ragdollPoints.size());
|
||||
if (_ragdollPoints.size() != numStates) {
|
||||
QVector<VerletPoint>& ragdollPoints = _ragdoll->getPoints();
|
||||
assert(_jointStates.size() == ragdollPoints.size());
|
||||
if (ragdollPoints.size() != numStates) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -757,32 +681,22 @@ void SkeletonModel::moveShapesTowardJoints(float deltaTime) {
|
|||
float fraction = glm::clamp(deltaTime / RAGDOLL_FOLLOWS_JOINTS_TIMESCALE, 0.0f, 1.0f);
|
||||
|
||||
float oneMinusFraction = 1.0f - fraction;
|
||||
glm::vec3 simulationTranslation = getTranslationInSimulationFrame();
|
||||
glm::vec3 simulationTranslation = _ragdoll->getTranslationInSimulationFrame();
|
||||
for (int i = 0; i < numStates; ++i) {
|
||||
// ragdollPoints are in simulation-frame but jointStates are in model-frame
|
||||
_ragdollPoints[i].initPosition(oneMinusFraction * _ragdollPoints[i]._position +
|
||||
ragdollPoints[i].initPosition(oneMinusFraction * ragdollPoints[i]._position +
|
||||
fraction * (simulationTranslation + _rotation * (_jointStates.at(i).getPosition())));
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::updateMuscles() {
|
||||
int numConstraints = _muscleConstraints.size();
|
||||
for (int i = 0; i < numConstraints; ++i) {
|
||||
MuscleConstraint* constraint = _muscleConstraints[i];
|
||||
int j = constraint->getParentIndex();
|
||||
int k = constraint->getChildIndex();
|
||||
assert(j != -1 && k != -1);
|
||||
// ragdollPoints are in simulation-frame but jointStates are in model-frame
|
||||
constraint->setChildOffset(_rotation * (_jointStates.at(k).getPosition() - _jointStates.at(j).getPosition()));
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
|
||||
// compute default joint transforms
|
||||
int numStates = _jointStates.size();
|
||||
QVector<glm::mat4> transforms;
|
||||
transforms.fill(glm::mat4(), numStates);
|
||||
|
||||
QVector<VerletPoint>& ragdollPoints = _ragdoll->getPoints();
|
||||
|
||||
// compute the default transforms and slam the ragdoll positions accordingly
|
||||
// (which puts the shapes where we want them)
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
|
@ -791,7 +705,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
|
|||
int parentIndex = joint.parentIndex;
|
||||
if (parentIndex == -1) {
|
||||
transforms[i] = _jointStates[i].getTransform();
|
||||
_ragdollPoints[i].initPosition(extractTranslation(transforms[i]));
|
||||
ragdollPoints[i].initPosition(extractTranslation(transforms[i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -799,7 +713,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
|
|||
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
|
||||
* joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform;
|
||||
// setting the ragdollPoints here slams the VerletShapes into their default positions
|
||||
_ragdollPoints[i].initPosition(extractTranslation(transforms[i]));
|
||||
ragdollPoints[i].initPosition(extractTranslation(transforms[i]));
|
||||
}
|
||||
|
||||
// compute bounding box that encloses all shapes
|
||||
|
@ -918,9 +832,12 @@ const int BALL_SUBDIVISIONS = 10;
|
|||
|
||||
// virtual
|
||||
void SkeletonModel::renderJointCollisionShapes(float alpha) {
|
||||
if (!_ragdoll) {
|
||||
return;
|
||||
}
|
||||
glPushMatrix();
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
glm::vec3 simulationTranslation = getTranslationInSimulationFrame();
|
||||
glm::vec3 simulationTranslation = _ragdoll->getTranslationInSimulationFrame();
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
Shape* shape = _shapes[i];
|
||||
if (!shape) {
|
||||
|
|
|
@ -15,18 +15,20 @@
|
|||
#include "renderer/Model.h"
|
||||
|
||||
#include <CapsuleShape.h>
|
||||
#include <Ragdoll.h>
|
||||
#include "SkeletonRagdoll.h"
|
||||
|
||||
class Avatar;
|
||||
class MuscleConstraint;
|
||||
class SkeletonRagdoll;
|
||||
|
||||
/// A skeleton loaded from a model.
|
||||
class SkeletonModel : public Model, public Ragdoll {
|
||||
class SkeletonModel : public Model {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
SkeletonModel(Avatar* owningAvatar, QObject* parent = NULL);
|
||||
~SkeletonModel();
|
||||
|
||||
void setJointStates(QVector<JointState> states);
|
||||
|
||||
|
@ -96,12 +98,11 @@ public:
|
|||
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
|
||||
|
||||
virtual void updateVisibleJointStates();
|
||||
|
||||
// virtual overrride from Ragdoll
|
||||
virtual void stepRagdollForward(float deltaTime);
|
||||
|
||||
SkeletonRagdoll* buildRagdoll();
|
||||
SkeletonRagdoll* getRagdoll() { return _ragdoll; }
|
||||
|
||||
void moveShapesTowardJoints(float fraction);
|
||||
void updateMuscles();
|
||||
|
||||
void computeBoundingShape(const FBXGeometry& geometry);
|
||||
void renderBoundingCollisionShapes(float alpha);
|
||||
|
@ -115,10 +116,6 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
// virtual overrrides from Ragdoll
|
||||
void initRagdollPoints();
|
||||
void buildRagdollConstraints();
|
||||
|
||||
void buildShapes();
|
||||
|
||||
/// \param jointIndex index of joint in model
|
||||
|
@ -147,7 +144,7 @@ private:
|
|||
|
||||
CapsuleShape _boundingShape;
|
||||
glm::vec3 _boundingShapeLocalOffset;
|
||||
QVector<MuscleConstraint*> _muscleConstraints;
|
||||
SkeletonRagdoll* _ragdoll;
|
||||
};
|
||||
|
||||
#endif // hifi_SkeletonModel_h
|
||||
|
|
145
interface/src/avatar/SkeletonRagdoll.cpp
Normal file
145
interface/src/avatar/SkeletonRagdoll.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
//
|
||||
// SkeletonRagdoll.cpp
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Created by Andrew Meadows 2014.08.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 <DistanceConstraint.h>
|
||||
#include <FixedConstraint.h>
|
||||
|
||||
#include "SkeletonRagdoll.h"
|
||||
#include "MuscleConstraint.h"
|
||||
#include "../renderer/Model.h"
|
||||
|
||||
SkeletonRagdoll::SkeletonRagdoll(Model* model) : Ragdoll(), _model(model) {
|
||||
assert(_model);
|
||||
}
|
||||
|
||||
SkeletonRagdoll::~SkeletonRagdoll() {
|
||||
}
|
||||
|
||||
// virtual
|
||||
void SkeletonRagdoll::stepForward(float deltaTime) {
|
||||
setTransform(_model->getTranslation(), _model->getRotation());
|
||||
Ragdoll::stepForward(deltaTime);
|
||||
updateMuscles();
|
||||
int numConstraints = _muscleConstraints.size();
|
||||
for (int i = 0; i < numConstraints; ++i) {
|
||||
_muscleConstraints[i]->enforce();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonRagdoll::slamPointPositions() {
|
||||
QVector<JointState>& jointStates = _model->getJointStates();
|
||||
int numStates = jointStates.size();
|
||||
for (int i = 0; i < numStates; ++i) {
|
||||
_points[i].initPosition(jointStates.at(i).getPosition());
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void SkeletonRagdoll::initPoints() {
|
||||
clearConstraintsAndPoints();
|
||||
_muscleConstraints.clear();
|
||||
|
||||
initTransform();
|
||||
// one point for each joint
|
||||
QVector<JointState>& jointStates = _model->getJointStates();
|
||||
int numStates = jointStates.size();
|
||||
_points.fill(VerletPoint(), numStates);
|
||||
slamPointPositions();
|
||||
}
|
||||
|
||||
// virtual
|
||||
void SkeletonRagdoll::buildConstraints() {
|
||||
QVector<JointState>& jointStates = _model->getJointStates();
|
||||
|
||||
// NOTE: the length of DistanceConstraints is computed and locked in at this time
|
||||
// so make sure the ragdoll positions are in a normal configuration before here.
|
||||
const int numPoints = _points.size();
|
||||
assert(numPoints == jointStates.size());
|
||||
|
||||
float minBone = FLT_MAX;
|
||||
float maxBone = -FLT_MAX;
|
||||
QMultiMap<int, int> families;
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
const JointState& state = jointStates.at(i);
|
||||
int parentIndex = state.getParentIndex();
|
||||
if (parentIndex != -1) {
|
||||
DistanceConstraint* bone = new DistanceConstraint(&(_points[i]), &(_points[parentIndex]));
|
||||
bone->setDistance(state.getDistanceToParent());
|
||||
_boneConstraints.push_back(bone);
|
||||
families.insert(parentIndex, i);
|
||||
}
|
||||
float boneLength = glm::length(state.getPositionInParentFrame());
|
||||
if (boneLength > maxBone) {
|
||||
maxBone = boneLength;
|
||||
} else if (boneLength < minBone) {
|
||||
minBone = boneLength;
|
||||
}
|
||||
}
|
||||
// Joints that have multiple children effectively have rigid constraints between the children
|
||||
// in the parent frame, so we add DistanceConstraints between children in the same family.
|
||||
QMultiMap<int, int>::iterator itr = families.begin();
|
||||
while (itr != families.end()) {
|
||||
QList<int> children = families.values(itr.key());
|
||||
int numChildren = children.size();
|
||||
if (numChildren > 1) {
|
||||
for (int i = 1; i < numChildren; ++i) {
|
||||
DistanceConstraint* bone = new DistanceConstraint(&(_points[children[i-1]]), &(_points[children[i]]));
|
||||
_boneConstraints.push_back(bone);
|
||||
}
|
||||
if (numChildren > 2) {
|
||||
DistanceConstraint* bone = new DistanceConstraint(&(_points[children[numChildren-1]]), &(_points[children[0]]));
|
||||
_boneConstraints.push_back(bone);
|
||||
}
|
||||
}
|
||||
++itr;
|
||||
}
|
||||
|
||||
float MAX_STRENGTH = 0.6f;
|
||||
float MIN_STRENGTH = 0.05f;
|
||||
// each joint gets a MuscleConstraint to its parent
|
||||
for (int i = 1; i < numPoints; ++i) {
|
||||
const JointState& state = jointStates.at(i);
|
||||
int p = state.getParentIndex();
|
||||
if (p == -1) {
|
||||
continue;
|
||||
}
|
||||
MuscleConstraint* constraint = new MuscleConstraint(&(_points[p]), &(_points[i]));
|
||||
_muscleConstraints.push_back(constraint);
|
||||
|
||||
// Short joints are more susceptible to wiggle so we modulate the strength based on the joint's length:
|
||||
// long = weak and short = strong.
|
||||
constraint->setIndices(p, i);
|
||||
float boneLength = glm::length(state.getPositionInParentFrame());
|
||||
|
||||
float strength = MIN_STRENGTH + (MAX_STRENGTH - MIN_STRENGTH) * (maxBone - boneLength) / (maxBone - minBone);
|
||||
if (!families.contains(i)) {
|
||||
// Although muscles only pull on the children not parents, nevertheless those joints that have
|
||||
// parents AND children are more stable than joints at the end such as fingers. For such joints we
|
||||
// bestow maximum strength which helps reduce wiggle.
|
||||
strength = MAX_MUSCLE_STRENGTH;
|
||||
}
|
||||
constraint->setStrength(strength);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonRagdoll::updateMuscles() {
|
||||
QVector<JointState>& jointStates = _model->getJointStates();
|
||||
int numConstraints = _muscleConstraints.size();
|
||||
glm::quat rotation = _model->getRotation();
|
||||
for (int i = 0; i < numConstraints; ++i) {
|
||||
MuscleConstraint* constraint = _muscleConstraints[i];
|
||||
int j = constraint->getParentIndex();
|
||||
int k = constraint->getChildIndex();
|
||||
assert(j != -1 && k != -1);
|
||||
// ragdollPoints are in simulation-frame but jointStates are in model-frame
|
||||
constraint->setChildOffset(rotation * (jointStates.at(k).getPosition() - jointStates.at(j).getPosition()));
|
||||
}
|
||||
}
|
44
interface/src/avatar/SkeletonRagdoll.h
Normal file
44
interface/src/avatar/SkeletonRagdoll.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// SkeletonkRagdoll.h
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Created by Andrew Meadows 2014.08.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_SkeletonRagdoll_h
|
||||
#define hifi_SkeletonRagdoll_h
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include <Ragdoll.h>
|
||||
|
||||
#include "../renderer/JointState.h"
|
||||
|
||||
class MuscleConstraint;
|
||||
class Model;
|
||||
|
||||
class SkeletonRagdoll : public Ragdoll {
|
||||
public:
|
||||
|
||||
SkeletonRagdoll(Model* model);
|
||||
virtual ~SkeletonRagdoll();
|
||||
|
||||
void slamPointPositions();
|
||||
virtual void stepForward(float deltaTime);
|
||||
|
||||
virtual void initPoints();
|
||||
virtual void buildConstraints();
|
||||
|
||||
protected:
|
||||
void updateMuscles();
|
||||
|
||||
private:
|
||||
Model* _model;
|
||||
QVector<MuscleConstraint*> _muscleConstraints;
|
||||
};
|
||||
|
||||
#endif // hifi_SkeletonRagdoll_h
|
|
@ -692,6 +692,14 @@ bool Model::getVisibleJointState(int index, glm::quat& rotation) const {
|
|||
return !state.rotationIsDefault(rotation);
|
||||
}
|
||||
|
||||
void Model::clearJointState(int index) {
|
||||
if (index != -1 && index < _jointStates.size()) {
|
||||
JointState& state = _jointStates[index];
|
||||
state.setRotationInConstrainedFrame(glm::quat());
|
||||
state._animationPriority = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) {
|
||||
if (index != -1 && index < _jointStates.size()) {
|
||||
JointState& state = _jointStates[index];
|
||||
|
|
|
@ -118,6 +118,9 @@ public:
|
|||
/// \return whether or not the joint state is "valid" (that is, non-default)
|
||||
bool getVisibleJointState(int index, glm::quat& rotation) const;
|
||||
|
||||
/// Clear the joint states
|
||||
void clearJointState(int index);
|
||||
|
||||
/// Sets the joint state at the specified index.
|
||||
void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f);
|
||||
|
||||
|
@ -164,6 +167,9 @@ public:
|
|||
const QVector<LocalLight>& getLocalLights() const { return _localLights; }
|
||||
|
||||
void setShowTrueJointTransforms(bool show) { _showTrueJointTransforms = show; }
|
||||
|
||||
QVector<JointState>& getJointStates() { return _jointStates; }
|
||||
const QVector<JointState>& getJointStates() const { return _jointStates; }
|
||||
|
||||
protected:
|
||||
QSharedPointer<NetworkGeometry> _geometry;
|
||||
|
|
|
@ -87,6 +87,37 @@ bool ClipboardScriptingInterface::importVoxels() {
|
|||
return Application::getInstance()->getImportSucceded();
|
||||
}
|
||||
|
||||
bool ClipboardScriptingInterface::importVoxels(const QString& filename) {
|
||||
qDebug() << "Importing ... ";
|
||||
|
||||
VoxelImporter* importer = Application::getInstance()->getVoxelImporter();
|
||||
|
||||
if (!importer->validImportFile(filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QEventLoop loop;
|
||||
connect(importer, SIGNAL(importDone()), &loop, SLOT(quit()));
|
||||
importer->import(filename);
|
||||
loop.exec();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClipboardScriptingInterface::importVoxels(const QString& filename, float x, float y, float z, float s) {
|
||||
bool success = importVoxels(filename);
|
||||
|
||||
if (success) {
|
||||
pasteVoxel(x, y, z, s);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ClipboardScriptingInterface::importVoxels(const QString& filename, const VoxelDetail& destinationVoxel) {
|
||||
return importVoxels(filename, destinationVoxel.x, destinationVoxel.y, destinationVoxel.z, destinationVoxel.s);
|
||||
}
|
||||
|
||||
void ClipboardScriptingInterface::nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec) {
|
||||
nudgeVoxel(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s, nudgeVec);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,9 @@ public slots:
|
|||
void exportVoxel(float x, float y, float z, float s);
|
||||
|
||||
bool importVoxels();
|
||||
bool importVoxels(const QString& filename);
|
||||
bool importVoxels(const QString& filename, float x, float y, float z, float s);
|
||||
bool importVoxels(const QString& filename, const VoxelDetail& destinationVoxel);
|
||||
|
||||
void nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec);
|
||||
void nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec);
|
||||
|
|
|
@ -100,14 +100,52 @@ QScriptValue WindowScriptingInterface::showConfirm(const QString& message) {
|
|||
return QScriptValue(response == QMessageBox::Yes);
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::chooseDirectory() {
|
||||
QPushButton* button = reinterpret_cast<QPushButton*>(sender());
|
||||
|
||||
QString title = button->property("title").toString();
|
||||
QString path = button->property("path").toString();
|
||||
QRegExp displayAs = button->property("displayAs").toRegExp();
|
||||
QRegExp validateAs = button->property("validateAs").toRegExp();
|
||||
QString errorMessage = button->property("errorMessage").toString();
|
||||
|
||||
QString directory = QFileDialog::getExistingDirectory(button, title, path);
|
||||
if (directory.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateAs.exactMatch(directory)) {
|
||||
QMessageBox::warning(NULL, "Invalid Directory", errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
button->setProperty("path", directory);
|
||||
|
||||
displayAs.indexIn(directory);
|
||||
QString buttonText = displayAs.cap(1) != "" ? displayAs.cap(1) : ".";
|
||||
button->setText(buttonText);
|
||||
}
|
||||
|
||||
QString WindowScriptingInterface::jsRegExp2QtRegExp(QString string) {
|
||||
// Converts string representation of RegExp from JavaScript format to Qt format.
|
||||
return string.mid(1, string.length() - 2) // No enclosing slashes.
|
||||
.replace("\\/", "/"); // No escaping of forward slash.
|
||||
}
|
||||
|
||||
/// Display a form layout with an edit box
|
||||
/// \param const QString& title title to display
|
||||
/// \param const QScriptValue form to display (array containing labels and values)
|
||||
/// \return QScriptValue result form (unchanged is dialog canceled)
|
||||
/// \param const QScriptValue form to display as an array of objects:
|
||||
/// - label, value
|
||||
/// - label, directory, title, display regexp, validate regexp, error message
|
||||
/// - button ("Cancel")
|
||||
/// \return QScriptValue `true` if 'OK' was clicked, `false` otherwise
|
||||
QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptValue form) {
|
||||
|
||||
if (form.isArray() && form.property("length").toInt32() > 0) {
|
||||
QDialog* editDialog = new QDialog(Application::getInstance()->getWindow());
|
||||
editDialog->setWindowTitle(title);
|
||||
|
||||
bool cancelButton = false;
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
editDialog->setLayout(layout);
|
||||
|
@ -127,44 +165,104 @@ QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptVal
|
|||
area->setWidget(container);
|
||||
|
||||
QVector<QLineEdit*> edits;
|
||||
QVector<QPushButton*> directories;
|
||||
for (int i = 0; i < form.property("length").toInt32(); ++i) {
|
||||
QScriptValue item = form.property(i);
|
||||
edits.push_back(new QLineEdit(item.property("value").toString()));
|
||||
formLayout->addRow(item.property("label").toString(), edits.back());
|
||||
|
||||
if (item.property("button").toString() != "") {
|
||||
cancelButton = cancelButton || item.property("button").toString().toLower() == "cancel";
|
||||
|
||||
} else if (item.property("directory").toString() != "") {
|
||||
QString path = item.property("directory").toString();
|
||||
QString title = item.property("title").toString();
|
||||
if (title == "") {
|
||||
title = "Choose Directory";
|
||||
}
|
||||
QString displayAsString = item.property("displayAs").toString();
|
||||
QRegExp displayAs = QRegExp(displayAsString != "" ? jsRegExp2QtRegExp(displayAsString) : "^(.*)$");
|
||||
QString validateAsString = item.property("validateAs").toString();
|
||||
QRegExp validateAs = QRegExp(validateAsString != "" ? jsRegExp2QtRegExp(validateAsString) : ".*");
|
||||
QString errorMessage = item.property("errorMessage").toString();
|
||||
if (errorMessage == "") {
|
||||
errorMessage = "Invalid directory";
|
||||
}
|
||||
|
||||
QPushButton* directory = new QPushButton(displayAs.cap(1));
|
||||
directory->setProperty("title", title);
|
||||
directory->setProperty("path", path);
|
||||
directory->setProperty("displayAs", displayAs);
|
||||
directory->setProperty("validateAs", validateAs);
|
||||
directory->setProperty("errorMessage", errorMessage);
|
||||
displayAs.indexIn(path);
|
||||
directory->setText(displayAs.cap(1) != "" ? displayAs.cap(1) : ".");
|
||||
|
||||
directory->setMinimumWidth(200);
|
||||
directories.push_back(directory);
|
||||
|
||||
formLayout->addRow(new QLabel(item.property("label").toString()), directory);
|
||||
connect(directory, SIGNAL(clicked(bool)), SLOT(chooseDirectory()));
|
||||
|
||||
} else {
|
||||
QLineEdit* edit = new QLineEdit(item.property("value").toString());
|
||||
edit->setMinimumWidth(200);
|
||||
edits.push_back(edit);
|
||||
formLayout->addRow(new QLabel(item.property("label").toString()), edit);
|
||||
}
|
||||
}
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok
|
||||
| (cancelButton ? QDialogButtonBox::Cancel : QDialogButtonBox::NoButton)
|
||||
);
|
||||
connect(buttons, SIGNAL(accepted()), editDialog, SLOT(accept()));
|
||||
connect(buttons, SIGNAL(rejected()), editDialog, SLOT(reject()));
|
||||
layout->addWidget(buttons);
|
||||
|
||||
if (editDialog->exec() == QDialog::Accepted) {
|
||||
int result = editDialog->exec();
|
||||
if (result == QDialog::Accepted) {
|
||||
int e = -1;
|
||||
int d = -1;
|
||||
for (int i = 0; i < form.property("length").toInt32(); ++i) {
|
||||
QScriptValue item = form.property(i);
|
||||
QScriptValue value = item.property("value");
|
||||
bool ok = true;
|
||||
if (value.isNumber()) {
|
||||
value = edits.at(i)->text().toDouble(&ok);
|
||||
} else if (value.isString()) {
|
||||
value = edits.at(i)->text();
|
||||
} else if (value.isBool()) {
|
||||
if (edits.at(i)->text() == "true") {
|
||||
value = true;
|
||||
} else if (edits.at(i)->text() == "false") {
|
||||
value = false;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
item.setProperty("value", value);
|
||||
|
||||
if (item.property("button").toString() != "") {
|
||||
// Nothing to do
|
||||
} else if (item.property("directory").toString() != "") {
|
||||
d += 1;
|
||||
value = directories.at(d)->property("path").toString();
|
||||
item.setProperty("directory", value);
|
||||
form.setProperty(i, item);
|
||||
} else {
|
||||
e += 1;
|
||||
bool ok = true;
|
||||
if (value.isNumber()) {
|
||||
value = edits.at(e)->text().toDouble(&ok);
|
||||
} else if (value.isString()) {
|
||||
value = edits.at(e)->text();
|
||||
} else if (value.isBool()) {
|
||||
if (edits.at(e)->text() == "true") {
|
||||
value = true;
|
||||
} else if (edits.at(e)->text() == "false") {
|
||||
value = false;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
item.setProperty("value", value);
|
||||
form.setProperty(i, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete editDialog;
|
||||
|
||||
return (result == QDialog::Accepted);
|
||||
}
|
||||
|
||||
return form;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Display a prompt with a text box
|
||||
|
|
|
@ -42,9 +42,12 @@ private slots:
|
|||
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
|
||||
QFileDialog::AcceptMode acceptMode = QFileDialog::AcceptOpen);
|
||||
QScriptValue showS3Browse(const QString& nameFilter);
|
||||
void chooseDirectory();
|
||||
|
||||
private:
|
||||
WindowScriptingInterface();
|
||||
|
||||
QString jsRegExp2QtRegExp(QString string);
|
||||
};
|
||||
|
||||
#endif // hifi_WindowScriptingInterface_h
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// ImportDialog.cpp
|
||||
// VoxelImportDialog.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Clement Brisset on 8/12/13.
|
||||
|
@ -20,7 +20,11 @@
|
|||
|
||||
#include "Application.h"
|
||||
|
||||
#include "ImportDialog.h"
|
||||
#include "VoxelImportDialog.h"
|
||||
#include "voxels/VoxelImporter.h"
|
||||
|
||||
const QString SETTINGS_GROUP_NAME = "VoxelImport";
|
||||
const QString IMPORT_DIALOG_SETTINGS_KEY = "VoxelImportDialogSettings";
|
||||
|
||||
const QString WINDOW_NAME = QObject::tr("Import Voxels");
|
||||
const QString IMPORT_BUTTON_NAME = QObject::tr("Import Voxels");
|
||||
|
@ -97,12 +101,14 @@ QString HiFiIconProvider::type(const QFileInfo &info) const {
|
|||
return QFileIconProvider::type(info);
|
||||
}
|
||||
|
||||
ImportDialog::ImportDialog(QWidget* parent) :
|
||||
VoxelImportDialog::VoxelImportDialog(QWidget* parent) :
|
||||
QFileDialog(parent, WINDOW_NAME, DOWNLOAD_LOCATION, NULL),
|
||||
_progressBar(this),
|
||||
_importButton(IMPORT_BUTTON_NAME, this),
|
||||
_cancelButton(CANCEL_BUTTON_NAME, this),
|
||||
_mode(importMode) {
|
||||
_importButton(IMPORT_BUTTON_NAME, this),
|
||||
_importer(Application::getInstance()->getVoxelImporter()),
|
||||
_mode(importMode),
|
||||
_progressBar(this),
|
||||
_didImport(false) {
|
||||
|
||||
setOption(QFileDialog::DontUseNativeDialog, true);
|
||||
setFileMode(QFileDialog::ExistingFile);
|
||||
|
@ -113,41 +119,54 @@ ImportDialog::ImportDialog(QWidget* parent) :
|
|||
|
||||
_progressBar.setRange(0, 100);
|
||||
|
||||
connect(&_importButton, SIGNAL(pressed()), SLOT(accept()));
|
||||
connect(&_cancelButton, SIGNAL(pressed()), SIGNAL(canceled()));
|
||||
connect(&_importButton, SIGNAL(pressed()), this, SLOT(accept()));
|
||||
connect(&_cancelButton, SIGNAL(pressed()), this, SLOT(cancel()));
|
||||
connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
|
||||
}
|
||||
|
||||
void ImportDialog::reset() {
|
||||
setMode(importMode);
|
||||
_progressBar.setValue(0);
|
||||
void VoxelImportDialog::cancel() {
|
||||
switch (getMode()) {
|
||||
case importMode:
|
||||
_importer->cancel();
|
||||
close();
|
||||
break;
|
||||
default:
|
||||
_importer->reset();
|
||||
setMode(importMode);
|
||||
break;
|
||||
}
|
||||
emit canceled();
|
||||
}
|
||||
|
||||
void ImportDialog::setMode(dialogMode mode) {
|
||||
void VoxelImportDialog::saveSettings(QSettings* settings) {
|
||||
settings->beginGroup(SETTINGS_GROUP_NAME);
|
||||
settings->setValue(IMPORT_DIALOG_SETTINGS_KEY, saveState());
|
||||
settings->endGroup();
|
||||
}
|
||||
|
||||
void VoxelImportDialog::loadSettings(QSettings* settings) {
|
||||
settings->beginGroup(SETTINGS_GROUP_NAME);
|
||||
restoreState(settings->value(IMPORT_DIALOG_SETTINGS_KEY).toByteArray());
|
||||
settings->endGroup();
|
||||
}
|
||||
|
||||
bool VoxelImportDialog::prompt() {
|
||||
reset();
|
||||
exec();
|
||||
return _didImport;
|
||||
}
|
||||
|
||||
void VoxelImportDialog::reset() {
|
||||
setMode(importMode);
|
||||
_didImport = false;
|
||||
}
|
||||
|
||||
void VoxelImportDialog::setMode(dialogMode mode) {
|
||||
dialogMode previousMode = _mode;
|
||||
_mode = mode;
|
||||
|
||||
switch (_mode) {
|
||||
case loadingMode:
|
||||
_importButton.setEnabled(false);
|
||||
_importButton.setText(LOADING_BUTTON_NAME);
|
||||
findChild<QWidget*>("sidebar")->setEnabled(false);
|
||||
findChild<QWidget*>("treeView")->setEnabled(false);
|
||||
findChild<QWidget*>("backButton")->setEnabled(false);
|
||||
findChild<QWidget*>("forwardButton")->setEnabled(false);
|
||||
findChild<QWidget*>("toParentButton")->setEnabled(false);
|
||||
break;
|
||||
case placeMode:
|
||||
_progressBar.setValue(100);
|
||||
_importButton.setEnabled(true);
|
||||
_importButton.setText(PLACE_BUTTON_NAME);
|
||||
findChild<QWidget*>("sidebar")->setEnabled(false);
|
||||
findChild<QWidget*>("treeView")->setEnabled(false);
|
||||
findChild<QWidget*>("backButton")->setEnabled(false);
|
||||
findChild<QWidget*>("forwardButton")->setEnabled(false);
|
||||
findChild<QWidget*>("toParentButton")->setEnabled(false);
|
||||
break;
|
||||
case importMode:
|
||||
default:
|
||||
_progressBar.setValue(0);
|
||||
_importButton.setEnabled(true);
|
||||
_importButton.setText(IMPORT_BUTTON_NAME);
|
||||
|
@ -157,22 +176,60 @@ void ImportDialog::setMode(dialogMode mode) {
|
|||
findChild<QWidget*>("forwardButton")->setEnabled(true);
|
||||
findChild<QWidget*>("toParentButton")->setEnabled(true);
|
||||
break;
|
||||
case loadingMode:
|
||||
// Connect to VoxelImporter signals
|
||||
connect(_importer, SIGNAL(importProgress(int)), this, SLOT(updateProgressBar(int)));
|
||||
connect(_importer, SIGNAL(importDone()), this, SLOT(afterImport()));
|
||||
|
||||
_importButton.setEnabled(false);
|
||||
_importButton.setText(LOADING_BUTTON_NAME);
|
||||
findChild<QWidget*>("sidebar")->setEnabled(false);
|
||||
findChild<QWidget*>("treeView")->setEnabled(false);
|
||||
findChild<QWidget*>("backButton")->setEnabled(false);
|
||||
findChild<QWidget*>("forwardButton")->setEnabled(false);
|
||||
findChild<QWidget*>("toParentButton")->setEnabled(false);
|
||||
break;
|
||||
case finishedMode:
|
||||
if (previousMode == loadingMode) {
|
||||
// Disconnect from VoxelImporter signals
|
||||
disconnect(_importer, SIGNAL(importProgress(int)), this, SLOT(setProgressBarValue(int)));
|
||||
disconnect(_importer, SIGNAL(importDone()), this, SLOT(afterImport()));
|
||||
}
|
||||
setMode(importMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ImportDialog::setProgressBarValue(int value) {
|
||||
void VoxelImportDialog::setProgressBarValue(int value) {
|
||||
_progressBar.setValue(value);
|
||||
}
|
||||
|
||||
void ImportDialog::accept() {
|
||||
emit accepted();
|
||||
void VoxelImportDialog::accept() {
|
||||
if (getMode() == importMode) {
|
||||
QString filename = getCurrentFile();
|
||||
|
||||
// If file is invalid we ignore the call
|
||||
if (!_importer->validImportFile(filename)) {
|
||||
return;
|
||||
}
|
||||
// Let's prepare the dialog window for import
|
||||
setMode(loadingMode);
|
||||
|
||||
_importer->import(filename);
|
||||
}
|
||||
}
|
||||
|
||||
void ImportDialog::saveCurrentFile(QString filename) {
|
||||
void VoxelImportDialog::afterImport() {
|
||||
setMode(finishedMode);
|
||||
_didImport = true;
|
||||
close();
|
||||
}
|
||||
|
||||
void VoxelImportDialog::saveCurrentFile(QString filename) {
|
||||
_currentFile = QFileInfo(filename).isFile() ? filename : "";
|
||||
}
|
||||
|
||||
void ImportDialog::setLayout() {
|
||||
void VoxelImportDialog::setLayout() {
|
||||
QGridLayout* gridLayout = (QGridLayout*) layout();
|
||||
gridLayout->addWidget(&_progressBar, 2, 0, 2, 1);
|
||||
gridLayout->addWidget(&_cancelButton, 2, 1, 2, 1);
|
||||
|
@ -258,7 +315,7 @@ void ImportDialog::setLayout() {
|
|||
|
||||
}
|
||||
|
||||
void ImportDialog::setImportTypes() {
|
||||
void VoxelImportDialog::setImportTypes() {
|
||||
QFile config(Application::resourcesPath() + "config/config.json");
|
||||
config.open(QFile::ReadOnly | QFile::Text);
|
||||
QJsonDocument document = QJsonDocument::fromJson(config.readAll());
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// ImportDialog.h
|
||||
// VoxelImportDialog.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Clement Brisset on 8/12/13.
|
||||
|
@ -9,8 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ImportDialog_h
|
||||
#define hifi_ImportDialog_h
|
||||
#ifndef hifi_VoxelImportDialog_h
|
||||
#define hifi_VoxelImportDialog_h
|
||||
|
||||
#include "InterfaceConfig.h"
|
||||
|
||||
|
@ -23,6 +23,8 @@
|
|||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "voxels/VoxelImporter.h"
|
||||
|
||||
class HiFiIconProvider : public QFileIconProvider {
|
||||
public:
|
||||
HiFiIconProvider(const QHash<QString, QString> map) { iconsMap = map; };
|
||||
|
@ -35,39 +37,45 @@ public:
|
|||
enum dialogMode {
|
||||
importMode,
|
||||
loadingMode,
|
||||
placeMode
|
||||
finishedMode
|
||||
};
|
||||
|
||||
class ImportDialog : public QFileDialog {
|
||||
class VoxelImportDialog : public QFileDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ImportDialog(QWidget* parent = NULL);
|
||||
void reset();
|
||||
VoxelImportDialog(QWidget* parent = NULL);
|
||||
|
||||
QString getCurrentFile() const { return _currentFile; }
|
||||
dialogMode getMode() const { return _mode; }
|
||||
|
||||
void setMode(dialogMode mode);
|
||||
void reset();
|
||||
bool prompt();
|
||||
void loadSettings(QSettings* settings);
|
||||
void saveSettings(QSettings* settings);
|
||||
|
||||
signals:
|
||||
void canceled();
|
||||
|
||||
public slots:
|
||||
void setProgressBarValue(int value);
|
||||
|
||||
private slots:
|
||||
void setProgressBarValue(int value);
|
||||
void accept();
|
||||
void cancel();
|
||||
void saveCurrentFile(QString filename);
|
||||
void afterImport();
|
||||
|
||||
private:
|
||||
QString _currentFile;
|
||||
QProgressBar _progressBar;
|
||||
QPushButton _importButton;
|
||||
QPushButton _cancelButton;
|
||||
QString _currentFile;
|
||||
QPushButton _importButton;
|
||||
VoxelImporter* _importer;
|
||||
dialogMode _mode;
|
||||
QProgressBar _progressBar;
|
||||
bool _didImport;
|
||||
|
||||
void setLayout();
|
||||
void setImportTypes();
|
||||
};
|
||||
|
||||
#endif // hifi_ImportDialog_h
|
||||
#endif // hifi_VoxelImportDialog_h
|
|
@ -20,8 +20,7 @@
|
|||
|
||||
#include "voxels/VoxelImporter.h"
|
||||
|
||||
const QString SETTINGS_GROUP_NAME = "VoxelImport";
|
||||
const QString IMPORT_DIALOG_SETTINGS_KEY = "ImportDialogSettings";
|
||||
const QStringList SUPPORTED_EXTENSIONS = QStringList() << "png" << "svo" << "schematic";
|
||||
|
||||
class ImportTask : public QObject, public QRunnable {
|
||||
public:
|
||||
|
@ -32,104 +31,46 @@ private:
|
|||
QString _filename;
|
||||
};
|
||||
|
||||
VoxelImporter::VoxelImporter(QWidget* parent) :
|
||||
QObject(parent),
|
||||
VoxelImporter::VoxelImporter() :
|
||||
_voxelTree(true),
|
||||
_importDialog(parent),
|
||||
_task(NULL),
|
||||
_didImport(false)
|
||||
_task(NULL)
|
||||
{
|
||||
LocalVoxelsList::getInstance()->addPersistantTree(IMPORT_TREE_NAME, &_voxelTree);
|
||||
|
||||
connect(&_voxelTree, SIGNAL(importProgress(int)), &_importDialog, SLOT(setProgressBarValue(int)));
|
||||
connect(&_importDialog, SIGNAL(canceled()), this, SLOT(cancel()));
|
||||
connect(&_importDialog, SIGNAL(accepted()), this, SLOT(import()));
|
||||
}
|
||||
|
||||
void VoxelImporter::saveSettings(QSettings* settings) {
|
||||
settings->beginGroup(SETTINGS_GROUP_NAME);
|
||||
settings->setValue(IMPORT_DIALOG_SETTINGS_KEY, _importDialog.saveState());
|
||||
settings->endGroup();
|
||||
}
|
||||
|
||||
void VoxelImporter::loadSettings(QSettings* settings) {
|
||||
settings->beginGroup(SETTINGS_GROUP_NAME);
|
||||
_importDialog.restoreState(settings->value(IMPORT_DIALOG_SETTINGS_KEY).toByteArray());
|
||||
settings->endGroup();
|
||||
connect(&_voxelTree, SIGNAL(importProgress(int)), this, SIGNAL(importProgress(int)));
|
||||
}
|
||||
|
||||
VoxelImporter::~VoxelImporter() {
|
||||
cleanupTask();
|
||||
}
|
||||
|
||||
void VoxelImporter::cancel() {
|
||||
if (_task) {
|
||||
disconnect(_task, 0, 0, 0);
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
void VoxelImporter::reset() {
|
||||
_voxelTree.eraseAllOctreeElements();
|
||||
_importDialog.reset();
|
||||
|
||||
cleanupTask();
|
||||
}
|
||||
|
||||
int VoxelImporter::exec() {
|
||||
reset();
|
||||
_importDialog.exec();
|
||||
|
||||
if (!_didImport) {
|
||||
// if the import is rejected, we make sure to cleanup before leaving
|
||||
void VoxelImporter::import(const QString& filename) {
|
||||
// If present, abort existing import
|
||||
if (_task) {
|
||||
cleanupTask();
|
||||
return 1;
|
||||
} else {
|
||||
_didImport = false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelImporter::import() {
|
||||
switch (_importDialog.getMode()) {
|
||||
case loadingMode:
|
||||
_importDialog.setMode(placeMode);
|
||||
return;
|
||||
case placeMode:
|
||||
// Means the user chose to import
|
||||
_didImport = true;
|
||||
_importDialog.close();
|
||||
return;
|
||||
case importMode:
|
||||
default:
|
||||
QString filename = _importDialog.getCurrentFile();
|
||||
// if it's not a file, we ignore the call
|
||||
if (!QFileInfo(filename).isFile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's prepare the dialog window for import
|
||||
_importDialog.setMode(loadingMode);
|
||||
|
||||
// If not already done, we switch to the local tree
|
||||
if (Application::getInstance()->getSharedVoxelSystem()->getTree() != &_voxelTree) {
|
||||
Application::getInstance()->getSharedVoxelSystem()->changeTree(&_voxelTree);
|
||||
}
|
||||
|
||||
// Creation and launch of the import task on the thread pool
|
||||
_task = new ImportTask(filename);
|
||||
connect(_task, SIGNAL(destroyed()), SLOT(import()));
|
||||
QThreadPool::globalInstance()->start(_task);
|
||||
break;
|
||||
// If not already done, we switch to the local tree
|
||||
if (Application::getInstance()->getSharedVoxelSystem()->getTree() != &_voxelTree) {
|
||||
Application::getInstance()->getSharedVoxelSystem()->changeTree(&_voxelTree);
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelImporter::cancel() {
|
||||
switch (_importDialog.getMode()) {
|
||||
case loadingMode:
|
||||
disconnect(_task, 0, 0, 0);
|
||||
cleanupTask();
|
||||
case placeMode:
|
||||
_importDialog.setMode(importMode);
|
||||
break;
|
||||
case importMode:
|
||||
default:
|
||||
_importDialog.close();
|
||||
break;
|
||||
}
|
||||
// Creation and launch of the import task on the thread pool
|
||||
_task = new ImportTask(filename);
|
||||
connect(_task, SIGNAL(destroyed()), SLOT(finishImport()));
|
||||
QThreadPool::globalInstance()->start(_task);
|
||||
}
|
||||
|
||||
void VoxelImporter::cleanupTask() {
|
||||
|
@ -140,6 +81,16 @@ void VoxelImporter::cleanupTask() {
|
|||
}
|
||||
}
|
||||
|
||||
void VoxelImporter::finishImport() {
|
||||
cleanupTask();
|
||||
emit importDone();
|
||||
}
|
||||
|
||||
bool VoxelImporter::validImportFile(const QString& filename) {
|
||||
QFileInfo fileInfo = QFileInfo(filename);
|
||||
return fileInfo.isFile() && SUPPORTED_EXTENSIONS.indexOf(fileInfo.suffix().toLower()) != -1;
|
||||
}
|
||||
|
||||
ImportTask::ImportTask(const QString &filename)
|
||||
: _filename(filename)
|
||||
{
|
||||
|
@ -151,7 +102,7 @@ void ImportTask::run() {
|
|||
// We start by cleaning up the shared voxel system just in case
|
||||
voxelSystem->killLocalVoxels();
|
||||
|
||||
// Then we call the righ method for the job
|
||||
// Then we call the right method for the job
|
||||
if (_filename.endsWith(".png", Qt::CaseInsensitive)) {
|
||||
voxelSystem->getTree()->readFromSquareARGB32Pixels(_filename.toLocal8Bit().data());
|
||||
} else if (_filename.endsWith(".svo", Qt::CaseInsensitive)) {
|
||||
|
@ -163,6 +114,6 @@ void ImportTask::run() {
|
|||
qDebug() << "[ERROR] Invalid file extension." << endl;
|
||||
}
|
||||
|
||||
// Here we reaverage the tree so that he is ready for preview
|
||||
// Here we reaverage the tree so that it is ready for preview
|
||||
voxelSystem->getTree()->reaverageOctreeElements();
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
#include <QThread>
|
||||
#include <QRunnable>
|
||||
#include <QStringList>
|
||||
|
||||
#include "ui/ImportDialog.h"
|
||||
#include "voxels/VoxelSystem.h"
|
||||
|
||||
class ImportTask;
|
||||
|
@ -23,28 +23,29 @@ class ImportTask;
|
|||
class VoxelImporter : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VoxelImporter(QWidget* parent = NULL);
|
||||
VoxelImporter();
|
||||
~VoxelImporter();
|
||||
|
||||
void reset();
|
||||
void loadSettings(QSettings* settings);
|
||||
void saveSettings(QSettings* settings);
|
||||
|
||||
void cancel();
|
||||
VoxelTree* getVoxelTree() { return &_voxelTree; }
|
||||
bool validImportFile(const QString& filename);
|
||||
|
||||
public slots:
|
||||
int exec();
|
||||
void import();
|
||||
void cancel();
|
||||
void import(const QString& filename);
|
||||
|
||||
signals:
|
||||
void importDone();
|
||||
void importProgress(int);
|
||||
|
||||
private:
|
||||
VoxelTree _voxelTree;
|
||||
ImportDialog _importDialog;
|
||||
|
||||
ImportTask* _task;
|
||||
bool _didImport;
|
||||
|
||||
void cleanupTask();
|
||||
|
||||
private slots:
|
||||
void finishImport();
|
||||
};
|
||||
|
||||
#endif // hifi_VoxelImporter_h
|
||||
|
|
|
@ -27,7 +27,6 @@ AudioInjector::AudioInjector(QObject* parent) :
|
|||
_options(),
|
||||
_shouldStop(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions) :
|
||||
|
@ -35,7 +34,10 @@ AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorO
|
|||
_options(injectorOptions),
|
||||
_shouldStop(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AudioInjector::setOptions(AudioInjectorOptions& options) {
|
||||
_options = options;
|
||||
}
|
||||
|
||||
const uchar MAX_INJECTOR_VOLUME = 0xFF;
|
||||
|
@ -73,9 +75,11 @@ void AudioInjector::injectAudio() {
|
|||
packetStream << loopbackFlag;
|
||||
|
||||
// pack the position for injected audio
|
||||
int positionOptionOffset = injectAudioPacket.size();
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&_options.getPosition()), sizeof(_options.getPosition()));
|
||||
|
||||
// pack our orientation for injected audio
|
||||
int orientationOptionOffset = injectAudioPacket.size();
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&_options.getOrientation()), sizeof(_options.getOrientation()));
|
||||
|
||||
// pack zero for radius
|
||||
|
@ -101,6 +105,12 @@ void AudioInjector::injectAudio() {
|
|||
|
||||
int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
|
||||
soundByteArray.size() - currentSendPosition);
|
||||
memcpy(injectAudioPacket.data() + positionOptionOffset,
|
||||
&_options.getPosition(),
|
||||
sizeof(_options.getPosition()));
|
||||
memcpy(injectAudioPacket.data() + orientationOptionOffset,
|
||||
&_options.getOrientation(),
|
||||
sizeof(_options.getOrientation()));
|
||||
|
||||
// resize the QByteArray to the right size
|
||||
injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy);
|
||||
|
|
|
@ -29,6 +29,7 @@ public:
|
|||
public slots:
|
||||
void injectAudio();
|
||||
void stop() { _shouldStop = true; }
|
||||
void setOptions(AudioInjectorOptions& options);
|
||||
signals:
|
||||
void finished();
|
||||
private:
|
||||
|
|
|
@ -19,7 +19,6 @@ AudioInjectorOptions::AudioInjectorOptions(QObject* parent) :
|
|||
_orientation(glm::vec3(0.0f, 0.0f, 0.0f)),
|
||||
_loopbackAudioInterface(NULL)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AudioInjectorOptions::AudioInjectorOptions(const AudioInjectorOptions& other) {
|
||||
|
@ -29,3 +28,11 @@ AudioInjectorOptions::AudioInjectorOptions(const AudioInjectorOptions& other) {
|
|||
_orientation = other._orientation;
|
||||
_loopbackAudioInterface = other._loopbackAudioInterface;
|
||||
}
|
||||
|
||||
void AudioInjectorOptions::operator=(const AudioInjectorOptions& other) {
|
||||
_position = other._position;
|
||||
_volume = other._volume;
|
||||
_loop = other._loop;
|
||||
_orientation = other._orientation;
|
||||
_loopbackAudioInterface = other._loopbackAudioInterface;
|
||||
}
|
|
@ -30,6 +30,7 @@ class AudioInjectorOptions : public QObject {
|
|||
public:
|
||||
AudioInjectorOptions(QObject* parent = 0);
|
||||
AudioInjectorOptions(const AudioInjectorOptions& other);
|
||||
void operator=(const AudioInjectorOptions& other);
|
||||
|
||||
const glm::vec3& getPosition() const { return _position; }
|
||||
void setPosition(const glm::vec3& position) { _position = position; }
|
||||
|
@ -37,8 +38,8 @@ public:
|
|||
float getVolume() const { return _volume; }
|
||||
void setVolume(float volume) { _volume = volume; }
|
||||
|
||||
float getLoop() const { return _loop; }
|
||||
void setLoop(float loop) { _loop = loop; }
|
||||
bool getLoop() const { return _loop; }
|
||||
void setLoop(bool loop) { _loop = loop; }
|
||||
|
||||
const glm::quat& getOrientation() const { return _orientation; }
|
||||
void setOrientation(const glm::quat& orientation) { _orientation = orientation; }
|
||||
|
|
|
@ -82,6 +82,17 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) :
|
|||
connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(replyError(QNetworkReply::NetworkError)));
|
||||
}
|
||||
|
||||
Sound::Sound(const QByteArray byteArray, QObject* parent) :
|
||||
QObject(parent),
|
||||
_byteArray(byteArray),
|
||||
_hasDownloaded(true)
|
||||
{
|
||||
}
|
||||
|
||||
void Sound::append(const QByteArray byteArray) {
|
||||
_byteArray.append(byteArray);
|
||||
}
|
||||
|
||||
void Sound::replyFinished() {
|
||||
|
||||
QNetworkReply* reply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
|
|
@ -22,6 +22,8 @@ class Sound : public QObject {
|
|||
public:
|
||||
Sound(const QUrl& sampleURL, QObject* parent = NULL);
|
||||
Sound(float volume, float frequency, float duration, float decay, QObject* parent = NULL);
|
||||
Sound(const QByteArray byteArray, QObject* parent = NULL);
|
||||
void append(const QByteArray byteArray);
|
||||
|
||||
bool hasDownloaded() const { return _hasDownloaded; }
|
||||
|
||||
|
|
|
@ -683,6 +683,41 @@ glm::quat AvatarData::getJointRotation(const QString& name) const {
|
|||
return getJointRotation(getJointIndex(name));
|
||||
}
|
||||
|
||||
QVector<glm::quat> AvatarData::getJointRotations() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVector<glm::quat> result;
|
||||
QMetaObject::invokeMethod(const_cast<AvatarData*>(this),
|
||||
"getJointRotation", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QVector<glm::quat>, result));
|
||||
return result;
|
||||
}
|
||||
QVector<glm::quat> jointRotations(_jointData.size());
|
||||
for (int i = 0; i < _jointData.size(); ++i) {
|
||||
jointRotations[i] = _jointData[i].rotation;
|
||||
}
|
||||
return jointRotations;
|
||||
}
|
||||
|
||||
void AvatarData::setJointRotations(QVector<glm::quat> jointRotations) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVector<glm::quat> result;
|
||||
QMetaObject::invokeMethod(const_cast<AvatarData*>(this),
|
||||
"setJointRotation", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QVector<glm::quat>, jointRotations));
|
||||
}
|
||||
for (int i = 0; i < jointRotations.size(); ++i) {
|
||||
if (i < _jointData.size()) {
|
||||
setJointData(i, jointRotations[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarData::clearJointsData() {
|
||||
for (int i = 0; i < _jointData.size(); ++i) {
|
||||
clearJointData(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) {
|
||||
QDataStream packetStream(packet);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||
|
|
|
@ -210,7 +210,12 @@ public:
|
|||
Q_INVOKABLE void clearJointData(const QString& name);
|
||||
Q_INVOKABLE bool isJointDataValid(const QString& name) const;
|
||||
Q_INVOKABLE glm::quat getJointRotation(const QString& name) const;
|
||||
|
||||
|
||||
Q_INVOKABLE virtual QVector<glm::quat> getJointRotations() const;
|
||||
Q_INVOKABLE virtual void setJointRotations(QVector<glm::quat> jointRotations);
|
||||
|
||||
Q_INVOKABLE virtual void clearJointsData();
|
||||
|
||||
/// Returns the index of the joint with the specified name, or -1 if not found/unknown.
|
||||
Q_INVOKABLE virtual int getJointIndex(const QString& name) const { return _jointIndices.value(name) - 1; }
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ public:
|
|||
|
||||
void setBlendshape(QString name, float val);
|
||||
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; }
|
||||
|
||||
float getPupilDilation() const { return _pupilDilation; }
|
||||
void setPupilDilation(float pupilDilation) { _pupilDilation = pupilDilation; }
|
||||
|
@ -68,6 +69,15 @@ public:
|
|||
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }
|
||||
void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; }
|
||||
|
||||
|
||||
float getLeanSideways() const { return _leanSideways; }
|
||||
float getLeanForward() const { return _leanForward; }
|
||||
virtual float getFinalLeanSideways() const { return _leanSideways; }
|
||||
virtual float getFinalLeanForward() const { return _leanForward; }
|
||||
|
||||
void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; }
|
||||
void setLeanForward(float leanForward) { _leanForward = leanForward; }
|
||||
|
||||
friend class AvatarData;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -11,9 +11,15 @@
|
|||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QImage>
|
||||
|
||||
#include "ArrayBufferClass.h"
|
||||
#include "ArrayBufferPrototype.h"
|
||||
|
||||
static const int QCOMPRESS_HEADER_POSITION = 0;
|
||||
static const int QCOMPRESS_HEADER_SIZE = 4;
|
||||
|
||||
Q_DECLARE_METATYPE(QByteArray*)
|
||||
|
||||
ArrayBufferPrototype::ArrayBufferPrototype(QObject* parent) : QObject(parent) {
|
||||
|
@ -43,6 +49,41 @@ QByteArray ArrayBufferPrototype::slice(qint32 begin) const {
|
|||
return ba->mid(begin, -1);
|
||||
}
|
||||
|
||||
QByteArray ArrayBufferPrototype::compress() const {
|
||||
// Compresses the ArrayBuffer data in Zlib format.
|
||||
QByteArray* ba = thisArrayBuffer();
|
||||
|
||||
QByteArray buffer = qCompress(*ba);
|
||||
buffer.remove(QCOMPRESS_HEADER_POSITION, QCOMPRESS_HEADER_SIZE); // Remove Qt's custom header to make it proper Zlib.
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
QByteArray ArrayBufferPrototype::recodeImage(const QString& sourceFormat, const QString& targetFormat, qint32 maxSize) const {
|
||||
// Recodes image data if sourceFormat and targetFormat are different.
|
||||
// Rescales image data if either dimension is greater than the specified maximum.
|
||||
QByteArray* ba = thisArrayBuffer();
|
||||
|
||||
bool mustRecode = sourceFormat.toLower() != targetFormat.toLower();
|
||||
|
||||
QImage image = QImage::fromData(*ba);
|
||||
if (image.width() > maxSize || image.height() > maxSize) {
|
||||
image = image.scaled(maxSize, maxSize, Qt::KeepAspectRatio);
|
||||
mustRecode = true;
|
||||
}
|
||||
|
||||
if (mustRecode) {
|
||||
QBuffer buffer;
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
std::string str = targetFormat.toUpper().toStdString();
|
||||
const char* format = str.c_str();
|
||||
image.save(&buffer, format);
|
||||
return buffer.data();
|
||||
}
|
||||
|
||||
return *ba;
|
||||
}
|
||||
|
||||
QByteArray* ArrayBufferPrototype::thisArrayBuffer() const {
|
||||
return qscriptvalue_cast<QByteArray*>(thisObject().data());
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ public:
|
|||
public slots:
|
||||
QByteArray slice(qint32 begin, qint32 end) const;
|
||||
QByteArray slice(qint32 begin) const;
|
||||
QByteArray compress() const;
|
||||
QByteArray recodeImage(const QString& sourceFormat, const QString& targetFormat, qint32 maxSize) const;
|
||||
|
||||
private:
|
||||
QByteArray* thisArrayBuffer() const;
|
||||
|
|
|
@ -13,10 +13,15 @@
|
|||
//
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QFile>
|
||||
|
||||
#include <NetworkAccessManager.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include "XMLHttpRequestClass.h"
|
||||
#include "ScriptEngine.h"
|
||||
|
||||
Q_DECLARE_METATYPE(QByteArray*)
|
||||
|
||||
XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) :
|
||||
_engine(engine),
|
||||
|
@ -33,6 +38,7 @@ XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) :
|
|||
_onReadyStateChange(QScriptValue::NullValue),
|
||||
_readyState(XMLHttpRequestClass::UNSENT),
|
||||
_errorCode(QNetworkReply::NoError),
|
||||
_file(NULL),
|
||||
_timeout(0),
|
||||
_timer(this),
|
||||
_numRedirects(0) {
|
||||
|
@ -52,6 +58,20 @@ QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEn
|
|||
QScriptValue XMLHttpRequestClass::getStatus() const {
|
||||
if (_reply) {
|
||||
return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
if(_url.isLocalFile()) {
|
||||
switch (_errorCode) {
|
||||
case QNetworkReply::NoError:
|
||||
return QScriptValue(200);
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
return QScriptValue(404);
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
return QScriptValue(409);
|
||||
case QNetworkReply::TimeoutError:
|
||||
return QScriptValue(408);
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
return QScriptValue(501);
|
||||
}
|
||||
}
|
||||
return QScriptValue(0);
|
||||
}
|
||||
|
@ -60,6 +80,20 @@ QString XMLHttpRequestClass::getStatusText() const {
|
|||
if (_reply) {
|
||||
return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
}
|
||||
if (_url.isLocalFile()) {
|
||||
switch (_errorCode) {
|
||||
case QNetworkReply::NoError:
|
||||
return "OK";
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
return "Not Found";
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
return "Conflict";
|
||||
case QNetworkReply::TimeoutError:
|
||||
return "Timeout";
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
return "Not Implemented";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -104,6 +138,13 @@ QScriptValue XMLHttpRequestClass::getAllResponseHeaders() const {
|
|||
}
|
||||
return QString(headers.data());
|
||||
}
|
||||
if (_url.isLocalFile()) {
|
||||
QString headers = QString("Content-Type: application/octet-stream\n");
|
||||
headers.append("Content-Length: ");
|
||||
headers.append(QString("%1").arg(_rawResponseData.length()));
|
||||
headers.append("\n");
|
||||
return headers;
|
||||
}
|
||||
return QScriptValue("");
|
||||
}
|
||||
|
||||
|
@ -111,6 +152,14 @@ QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const {
|
|||
if (_reply && _reply->hasRawHeader(name.toLatin1())) {
|
||||
return QScriptValue(QString(_reply->rawHeader(name.toLatin1())));
|
||||
}
|
||||
if (_url.isLocalFile()) {
|
||||
if (name.toLower() == "content-type") {
|
||||
return QString("application/octet-stream");
|
||||
}
|
||||
if (name.toLower() == "content-length") {
|
||||
return QString("%1").arg(_rawResponseData.length());
|
||||
}
|
||||
}
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
|
@ -126,34 +175,72 @@ void XMLHttpRequestClass::setReadyState(ReadyState readyState) {
|
|||
void XMLHttpRequestClass::open(const QString& method, const QString& url, bool async, const QString& username,
|
||||
const QString& password) {
|
||||
if (_readyState == UNSENT) {
|
||||
_async = async;
|
||||
_url.setUrl(url);
|
||||
if (!username.isEmpty()) {
|
||||
_url.setUserName(username);
|
||||
}
|
||||
if (!password.isEmpty()) {
|
||||
_url.setPassword(password);
|
||||
}
|
||||
_request.setUrl(_url);
|
||||
_method = method;
|
||||
setReadyState(OPENED);
|
||||
_url.setUrl(url);
|
||||
_async = async;
|
||||
|
||||
if (_url.isLocalFile()) {
|
||||
if (_method.toUpper() == "GET" && !_async && username.isEmpty() && password.isEmpty()) {
|
||||
_file = new QFile(_url.toLocalFile());
|
||||
if (!_file->exists()) {
|
||||
qDebug() << "Can't find file " << _url.fileName();
|
||||
abortRequest();
|
||||
_errorCode = QNetworkReply::ContentNotFoundError;
|
||||
setReadyState(DONE);
|
||||
emit requestComplete();
|
||||
} else if (!_file->open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Can't open file " << _url.fileName();
|
||||
abortRequest();
|
||||
//_errorCode = QNetworkReply::ContentConflictError; // TODO: Use this status when update to Qt 5.3
|
||||
_errorCode = QNetworkReply::ContentAccessDenied;
|
||||
setReadyState(DONE);
|
||||
emit requestComplete();
|
||||
} else {
|
||||
setReadyState(OPENED);
|
||||
}
|
||||
} else {
|
||||
notImplemented();
|
||||
}
|
||||
} else {
|
||||
if (url.toLower().left(33) == "https://data.highfidelity.io/api/") {
|
||||
_url.setQuery("access_token=" + AccountManager::getInstance().getAccountInfo().getAccessToken().token);
|
||||
}
|
||||
if (!username.isEmpty()) {
|
||||
_url.setUserName(username);
|
||||
}
|
||||
if (!password.isEmpty()) {
|
||||
_url.setPassword(password);
|
||||
}
|
||||
_request.setUrl(_url);
|
||||
setReadyState(OPENED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XMLHttpRequestClass::send() {
|
||||
send(QString::Null());
|
||||
send(QScriptValue::NullValue);
|
||||
}
|
||||
|
||||
void XMLHttpRequestClass::send(const QString& data) {
|
||||
void XMLHttpRequestClass::send(const QScriptValue& data) {
|
||||
if (_readyState == OPENED && !_reply) {
|
||||
if (!data.isNull()) {
|
||||
_sendData = new QBuffer(this);
|
||||
_sendData->setData(data.toUtf8());
|
||||
if (_url.isLocalFile()) {
|
||||
notImplemented();
|
||||
return;
|
||||
} else {
|
||||
_sendData = new QBuffer(this);
|
||||
if (data.isObject()) {
|
||||
QByteArray ba = qscriptvalue_cast<QByteArray>(data);
|
||||
_sendData->setData(ba);
|
||||
} else {
|
||||
_sendData->setData(data.toString().toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doSend();
|
||||
|
||||
if (!_async) {
|
||||
if (!_async && !_url.isLocalFile()) {
|
||||
QEventLoop loop;
|
||||
connect(this, SIGNAL(requestComplete()), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
|
@ -162,14 +249,24 @@ void XMLHttpRequestClass::send(const QString& data) {
|
|||
}
|
||||
|
||||
void XMLHttpRequestClass::doSend() {
|
||||
_reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData);
|
||||
|
||||
connectToReply(_reply);
|
||||
|
||||
if (!_url.isLocalFile()) {
|
||||
_reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData);
|
||||
connectToReply(_reply);
|
||||
}
|
||||
|
||||
if (_timeout > 0) {
|
||||
_timer.start(_timeout);
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
|
||||
}
|
||||
|
||||
if (_url.isLocalFile()) {
|
||||
setReadyState(HEADERS_RECEIVED);
|
||||
setReadyState(LOADING);
|
||||
_rawResponseData = _file->readAll();
|
||||
_file->close();
|
||||
requestFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void XMLHttpRequestClass::requestTimeout() {
|
||||
|
@ -188,9 +285,16 @@ void XMLHttpRequestClass::requestError(QNetworkReply::NetworkError code) {
|
|||
void XMLHttpRequestClass::requestFinished() {
|
||||
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
|
||||
|
||||
_errorCode = _reply->error();
|
||||
if (!_url.isLocalFile()) {
|
||||
_errorCode = _reply->error();
|
||||
} else {
|
||||
_errorCode = QNetworkReply::NoError;
|
||||
}
|
||||
|
||||
if (_errorCode == QNetworkReply::NoError) {
|
||||
_rawResponseData.append(_reply->readAll());
|
||||
if (!_url.isLocalFile()) {
|
||||
_rawResponseData.append(_reply->readAll());
|
||||
}
|
||||
|
||||
if (_responseType == "json") {
|
||||
_responseData = _engine->evaluate("(" + QString(_rawResponseData.data()) + ")");
|
||||
|
@ -199,11 +303,13 @@ void XMLHttpRequestClass::requestFinished() {
|
|||
_responseData = QScriptValue::NullValue;
|
||||
}
|
||||
} else if (_responseType == "arraybuffer") {
|
||||
_responseData = QScriptValue(_rawResponseData.data());
|
||||
QScriptValue data = _engine->newVariant(QVariant::fromValue(_rawResponseData));
|
||||
_responseData = _engine->newObject(reinterpret_cast<ScriptEngine*>(_engine)->getArrayBufferClass(), data);
|
||||
} else {
|
||||
_responseData = QScriptValue(QString(_rawResponseData.data()));
|
||||
}
|
||||
}
|
||||
|
||||
setReadyState(DONE);
|
||||
emit requestComplete();
|
||||
}
|
||||
|
@ -217,6 +323,19 @@ void XMLHttpRequestClass::abortRequest() {
|
|||
delete _reply;
|
||||
_reply = NULL;
|
||||
}
|
||||
|
||||
if (_file != NULL) {
|
||||
_file->close();
|
||||
_file = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void XMLHttpRequestClass::notImplemented() {
|
||||
abortRequest();
|
||||
//_errorCode = QNetworkReply::OperationNotImplementedError; TODO: Use this status code when update to Qt 5.3
|
||||
_errorCode = QNetworkReply::ContentOperationNotPermittedError;
|
||||
setReadyState(DONE);
|
||||
emit requestComplete();
|
||||
}
|
||||
|
||||
void XMLHttpRequestClass::connectToReply(QNetworkReply* reply) {
|
||||
|
|
|
@ -84,7 +84,7 @@ public slots:
|
|||
void open(const QString& method, const QString& url, bool async = true, const QString& username = "",
|
||||
const QString& password = "");
|
||||
void send();
|
||||
void send(const QString& data);
|
||||
void send(const QScriptValue& data);
|
||||
QScriptValue getAllResponseHeaders() const;
|
||||
QScriptValue getResponseHeader(const QString& name) const;
|
||||
|
||||
|
@ -97,6 +97,7 @@ private:
|
|||
void connectToReply(QNetworkReply* reply);
|
||||
void disconnectFromReply(QNetworkReply* reply);
|
||||
void abortRequest();
|
||||
void notImplemented();
|
||||
|
||||
QScriptEngine* _engine;
|
||||
bool _async;
|
||||
|
@ -112,6 +113,7 @@ private:
|
|||
QScriptValue _onReadyStateChange;
|
||||
ReadyState _readyState;
|
||||
QNetworkReply::NetworkError _errorCode;
|
||||
QFile* _file;
|
||||
int _timeout;
|
||||
QTimer _timer;
|
||||
int _numRedirects;
|
||||
|
|
|
@ -88,8 +88,75 @@ ContactPoint::ContactPoint(const CollisionInfo& collision, quint32 frame) :
|
|||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
float ContactPoint::enforce() {
|
||||
glm::vec3 pointA = _shapeA->getTranslation() + _offsetA;
|
||||
glm::vec3 pointB = _shapeB->getTranslation() + _offsetB;
|
||||
glm::vec3 penetration = pointA - pointB;
|
||||
float pDotN = glm::dot(penetration, _normal);
|
||||
bool constraintViolation = (pDotN > CONTACT_PENETRATION_ALLOWANCE);
|
||||
|
||||
// the contact point will be the average of the two points on the shapes
|
||||
_contactPoint = _relativeMassA * pointA + _relativeMassB * pointB;
|
||||
|
||||
if (constraintViolation) {
|
||||
for (int i = 0; i < _numPointsA; ++i) {
|
||||
VerletPoint* point = _points[i];
|
||||
glm::vec3 offset = _offsets[i];
|
||||
|
||||
// split delta into parallel and perpendicular components
|
||||
glm::vec3 delta = _contactPoint + offset - point->_position;
|
||||
glm::vec3 paraDelta = glm::dot(delta, _normal) * _normal;
|
||||
glm::vec3 perpDelta = delta - paraDelta;
|
||||
|
||||
// use the relative sizes of the components to decide how much perpenducular delta to use
|
||||
// perpendicular < parallel ==> static friction ==> perpFactor = 1.0
|
||||
// perpendicular > parallel ==> dynamic friction ==> cap to length of paraDelta ==> perpFactor < 1.0
|
||||
float paraLength = _relativeMassB * glm::length(paraDelta);
|
||||
float perpLength = _relativeMassA * glm::length(perpDelta);
|
||||
float perpFactor = (perpLength > paraLength && perpLength > EPSILON) ? (paraLength / perpLength) : 1.0f;
|
||||
|
||||
// recombine the two components to get the final delta
|
||||
delta = paraDelta + perpFactor * perpDelta;
|
||||
|
||||
glm::vec3 targetPosition = point->_position + delta;
|
||||
_distances[i] = glm::distance(_contactPoint, targetPosition);
|
||||
point->_position += delta;
|
||||
}
|
||||
for (int i = _numPointsA; i < _numPoints; ++i) {
|
||||
VerletPoint* point = _points[i];
|
||||
glm::vec3 offset = _offsets[i];
|
||||
|
||||
// split delta into parallel and perpendicular components
|
||||
glm::vec3 delta = _contactPoint + offset - point->_position;
|
||||
glm::vec3 paraDelta = glm::dot(delta, _normal) * _normal;
|
||||
glm::vec3 perpDelta = delta - paraDelta;
|
||||
|
||||
// use the relative sizes of the components to decide how much perpenducular delta to use
|
||||
// perpendicular < parallel ==> static friction ==> perpFactor = 1.0
|
||||
// perpendicular > parallel ==> dynamic friction ==> cap to length of paraDelta ==> perpFactor < 1.0
|
||||
float paraLength = _relativeMassA * glm::length(paraDelta);
|
||||
float perpLength = _relativeMassB * glm::length(perpDelta);
|
||||
float perpFactor = (perpLength > paraLength && perpLength > EPSILON) ? (paraLength / perpLength) : 1.0f;
|
||||
|
||||
// recombine the two components to get the final delta
|
||||
delta = paraDelta + perpFactor * perpDelta;
|
||||
|
||||
glm::vec3 targetPosition = point->_position + delta;
|
||||
_distances[i] = glm::distance(_contactPoint, targetPosition);
|
||||
point->_position += delta;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < _numPoints; ++i) {
|
||||
_distances[i] = glm::length(glm::length(_offsets[i]));
|
||||
}
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// virtual
|
||||
void ContactPoint::applyFriction() {
|
||||
// TODO: Andrew to re-implement this in a different way
|
||||
/*
|
||||
for (int i = 0; i < _numPoints; ++i) {
|
||||
glm::vec3& position = _points[i]->_position;
|
||||
// TODO: use a fast distance approximation
|
||||
|
@ -103,56 +170,7 @@ float ContactPoint::enforce() {
|
|||
position = center - (0.5f * constrainedDistance) * direction;
|
||||
}
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void ContactPoint::buildConstraints() {
|
||||
glm::vec3 pointA = _shapeA->getTranslation() + _offsetA;
|
||||
glm::vec3 pointB = _shapeB->getTranslation() + _offsetB;
|
||||
glm::vec3 penetration = pointA - pointB;
|
||||
float pDotN = glm::dot(penetration, _normal);
|
||||
bool constraintViolation = (pDotN > CONTACT_PENETRATION_ALLOWANCE);
|
||||
|
||||
// the contact point will be the average of the two points on the shapes
|
||||
_contactPoint = 0.5f * (pointA + pointB);
|
||||
|
||||
// TODO: Andrew to compute more correct lagrangian weights that provide a more realistic response.
|
||||
//
|
||||
// HACK: since the weights are naively equal for all points (which is what the above TODO is about) we
|
||||
// don't want to use the full-strength delta because otherwise there can be annoying oscillations. We
|
||||
// reduce this problem by in the short-term by attenuating the delta that is applied, the tradeoff is
|
||||
// that this makes it easier for limbs to tunnel through during collisions.
|
||||
const float HACK_STRENGTH = 0.5f;
|
||||
|
||||
if (constraintViolation) {
|
||||
for (int i = 0; i < _numPoints; ++i) {
|
||||
VerletPoint* point = _points[i];
|
||||
glm::vec3 offset = _offsets[i];
|
||||
|
||||
// split delta into parallel and perpendicular components
|
||||
glm::vec3 delta = _contactPoint + offset - point->_position;
|
||||
glm::vec3 paraDelta = glm::dot(delta, _normal) * _normal;
|
||||
glm::vec3 perpDelta = delta - paraDelta;
|
||||
|
||||
// use the relative sizes of the components to decide how much perpenducular delta to use
|
||||
// perpendicular < parallel ==> static friction ==> perpFactor = 1.0
|
||||
// perpendicular > parallel ==> dynamic friction ==> cap to length of paraDelta ==> perpFactor < 1.0
|
||||
float paraLength = glm::length(paraDelta);
|
||||
float perpLength = glm::length(perpDelta);
|
||||
float perpFactor = (perpLength > paraLength && perpLength > EPSILON) ? (paraLength / perpLength) : 1.0f;
|
||||
|
||||
// recombine the two components to get the final delta
|
||||
delta = paraDelta + perpFactor * perpDelta;
|
||||
|
||||
glm::vec3 targetPosition = point->_position + delta;
|
||||
_distances[i] = glm::distance(_contactPoint, targetPosition);
|
||||
point->_position += HACK_STRENGTH * delta;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < _numPoints; ++i) {
|
||||
_distances[i] = glm::length(glm::length(_offsets[i]));
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void ContactPoint::updateContact(const CollisionInfo& collision, quint32 frame) {
|
||||
|
|
|
@ -26,8 +26,8 @@ public:
|
|||
ContactPoint(const CollisionInfo& collision, quint32 frame);
|
||||
|
||||
virtual float enforce();
|
||||
|
||||
void buildConstraints();
|
||||
|
||||
void applyFriction();
|
||||
void updateContact(const CollisionInfo& collision, quint32 frame);
|
||||
quint32 getLastFrame() const { return _lastFrame; }
|
||||
|
||||
|
|
|
@ -47,12 +47,12 @@ PhysicsSimulation::~PhysicsSimulation() {
|
|||
void PhysicsSimulation::setRagdoll(Ragdoll* ragdoll) {
|
||||
if (_ragdoll != ragdoll) {
|
||||
if (_ragdoll) {
|
||||
_ragdoll->_ragdollSimulation = NULL;
|
||||
_ragdoll->_simulation = NULL;
|
||||
}
|
||||
_ragdoll = ragdoll;
|
||||
if (_ragdoll) {
|
||||
assert(!(_ragdoll->_ragdollSimulation));
|
||||
_ragdoll->_ragdollSimulation = this;
|
||||
assert(!(_ragdoll->_simulation));
|
||||
_ragdoll->_simulation = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ bool PhysicsSimulation::addRagdoll(Ragdoll* doll) {
|
|||
// list is full
|
||||
return false;
|
||||
}
|
||||
if (doll->_ragdollSimulation == this) {
|
||||
if (doll->_simulation == this) {
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
if (doll == _otherRagdolls[i]) {
|
||||
// already in list
|
||||
|
@ -153,8 +153,8 @@ bool PhysicsSimulation::addRagdoll(Ragdoll* doll) {
|
|||
}
|
||||
}
|
||||
// add to list
|
||||
assert(!(doll->_ragdollSimulation));
|
||||
doll->_ragdollSimulation = this;
|
||||
assert(!(doll->_simulation));
|
||||
doll->_simulation = this;
|
||||
_otherRagdolls.push_back(doll);
|
||||
|
||||
// set the massScale of otherRagdolls artificially high
|
||||
|
@ -163,10 +163,10 @@ bool PhysicsSimulation::addRagdoll(Ragdoll* doll) {
|
|||
}
|
||||
|
||||
void PhysicsSimulation::removeRagdoll(Ragdoll* doll) {
|
||||
int numDolls = _otherRagdolls.size();
|
||||
if (doll->_ragdollSimulation != this) {
|
||||
if (!doll || doll->_simulation != this) {
|
||||
return;
|
||||
}
|
||||
int numDolls = _otherRagdolls.size();
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
if (doll == _otherRagdolls[i]) {
|
||||
if (i == numDolls - 1) {
|
||||
|
@ -178,7 +178,7 @@ void PhysicsSimulation::removeRagdoll(Ragdoll* doll) {
|
|||
_otherRagdolls.pop_back();
|
||||
_otherRagdolls[i] = lastDoll;
|
||||
}
|
||||
doll->_ragdollSimulation = NULL;
|
||||
doll->_simulation = NULL;
|
||||
doll->setMassScale(1.0f);
|
||||
break;
|
||||
}
|
||||
|
@ -195,49 +195,58 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter
|
|||
quint64 expiry = startTime + maxUsec;
|
||||
|
||||
moveRagdolls(deltaTime);
|
||||
buildContactConstraints();
|
||||
enforceContacts();
|
||||
int numDolls = _otherRagdolls.size();
|
||||
{
|
||||
PerformanceTimer perfTimer("enforce");
|
||||
_ragdoll->enforceRagdollConstraints();
|
||||
_ragdoll->enforceConstraints();
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
_otherRagdolls[i]->enforceRagdollConstraints();
|
||||
_otherRagdolls[i]->enforceConstraints();
|
||||
}
|
||||
}
|
||||
|
||||
bool collidedWithOtherRagdoll = false;
|
||||
int iterations = 0;
|
||||
float error = 0.0f;
|
||||
do {
|
||||
computeCollisions();
|
||||
collidedWithOtherRagdoll = computeCollisions() || collidedWithOtherRagdoll;
|
||||
updateContacts();
|
||||
resolveCollisions();
|
||||
|
||||
{ // enforce constraints
|
||||
PerformanceTimer perfTimer("enforce");
|
||||
error = _ragdoll->enforceRagdollConstraints();
|
||||
error = _ragdoll->enforceConstraints();
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
error = glm::max(error, _otherRagdolls[i]->enforceRagdollConstraints());
|
||||
error = glm::max(error, _otherRagdolls[i]->enforceConstraints());
|
||||
}
|
||||
}
|
||||
enforceContactConstraints();
|
||||
applyContactFriction();
|
||||
++iterations;
|
||||
|
||||
now = usecTimestampNow();
|
||||
} while (_collisions.size() != 0 && (iterations < maxIterations) && (error > minError) && (now < expiry));
|
||||
|
||||
// the collisions may have moved the main ragdoll from the simulation center
|
||||
// so we remove this offset (potentially storing it as movement of the Ragdoll owner)
|
||||
_ragdoll->removeRootOffset(collidedWithOtherRagdoll);
|
||||
|
||||
// also remove any offsets from the other ragdolls
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
_otherRagdolls[i]->removeRootOffset(false);
|
||||
}
|
||||
pruneContacts();
|
||||
}
|
||||
|
||||
void PhysicsSimulation::moveRagdolls(float deltaTime) {
|
||||
PerformanceTimer perfTimer("integrate");
|
||||
_ragdoll->stepRagdollForward(deltaTime);
|
||||
_ragdoll->stepForward(deltaTime);
|
||||
int numDolls = _otherRagdolls.size();
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
_otherRagdolls[i]->stepRagdollForward(deltaTime);
|
||||
_otherRagdolls[i]->stepForward(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSimulation::computeCollisions() {
|
||||
bool PhysicsSimulation::computeCollisions() {
|
||||
PerformanceTimer perfTimer("collide");
|
||||
_collisions.clear();
|
||||
|
||||
|
@ -258,11 +267,13 @@ void PhysicsSimulation::computeCollisions() {
|
|||
}
|
||||
|
||||
// collide main ragdoll with others
|
||||
bool otherCollisions = false;
|
||||
int numEntities = _otherEntities.size();
|
||||
for (int i = 0; i < numEntities; ++i) {
|
||||
const QVector<Shape*> otherShapes = _otherEntities.at(i)->getShapes();
|
||||
ShapeCollider::collideShapesWithShapes(shapes, otherShapes, _collisions);
|
||||
otherCollisions = ShapeCollider::collideShapesWithShapes(shapes, otherShapes, _collisions) || otherCollisions;
|
||||
}
|
||||
return otherCollisions;
|
||||
}
|
||||
|
||||
void PhysicsSimulation::resolveCollisions() {
|
||||
|
@ -288,16 +299,7 @@ void PhysicsSimulation::resolveCollisions() {
|
|||
}
|
||||
}
|
||||
|
||||
void PhysicsSimulation::buildContactConstraints() {
|
||||
PerformanceTimer perfTimer("contacts");
|
||||
QMap<quint64, ContactPoint>::iterator itr = _contacts.begin();
|
||||
while (itr != _contacts.end()) {
|
||||
itr.value().buildConstraints();
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSimulation::enforceContactConstraints() {
|
||||
void PhysicsSimulation::enforceContacts() {
|
||||
PerformanceTimer perfTimer("contacts");
|
||||
QMap<quint64, ContactPoint>::iterator itr = _contacts.begin();
|
||||
while (itr != _contacts.end()) {
|
||||
|
@ -306,6 +308,15 @@ void PhysicsSimulation::enforceContactConstraints() {
|
|||
}
|
||||
}
|
||||
|
||||
void PhysicsSimulation::applyContactFriction() {
|
||||
PerformanceTimer perfTimer("contacts");
|
||||
QMap<quint64, ContactPoint>::iterator itr = _contacts.begin();
|
||||
while (itr != _contacts.end()) {
|
||||
itr.value().applyFriction();
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSimulation::updateContacts() {
|
||||
PerformanceTimer perfTimer("contacts");
|
||||
int numCollisions = _collisions.size();
|
||||
|
|
|
@ -53,11 +53,13 @@ public:
|
|||
|
||||
protected:
|
||||
void moveRagdolls(float deltaTime);
|
||||
void computeCollisions();
|
||||
void resolveCollisions();
|
||||
|
||||
void buildContactConstraints();
|
||||
void enforceContactConstraints();
|
||||
/// \return true if main ragdoll collides with other avatar
|
||||
bool computeCollisions();
|
||||
|
||||
void resolveCollisions();
|
||||
void enforceContacts();
|
||||
void applyContactFriction();
|
||||
void updateContacts();
|
||||
void pruneContacts();
|
||||
|
||||
|
|
|
@ -19,27 +19,28 @@
|
|||
#include "PhysicsSimulation.h"
|
||||
#include "SharedUtil.h" // for EPSILON
|
||||
|
||||
Ragdoll::Ragdoll() : _massScale(1.0f), _ragdollTranslation(0.0f), _translationInSimulationFrame(0.0f), _ragdollSimulation(NULL) {
|
||||
Ragdoll::Ragdoll() : _massScale(1.0f), _translation(0.0f), _translationInSimulationFrame(0.0f),
|
||||
_accumulatedMovement(0.0f), _simulation(NULL) {
|
||||
}
|
||||
|
||||
Ragdoll::~Ragdoll() {
|
||||
clearRagdollConstraintsAndPoints();
|
||||
if (_ragdollSimulation) {
|
||||
_ragdollSimulation->removeRagdoll(this);
|
||||
clearConstraintsAndPoints();
|
||||
if (_simulation) {
|
||||
_simulation->removeRagdoll(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Ragdoll::stepRagdollForward(float deltaTime) {
|
||||
if (_ragdollSimulation) {
|
||||
updateSimulationTransforms(_ragdollTranslation - _ragdollSimulation->getTranslation(), _ragdollRotation);
|
||||
void Ragdoll::stepForward(float deltaTime) {
|
||||
if (_simulation) {
|
||||
updateSimulationTransforms(_translation - _simulation->getTranslation(), _rotation);
|
||||
}
|
||||
int numPoints = _ragdollPoints.size();
|
||||
int numPoints = _points.size();
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
_ragdollPoints[i].integrateForward();
|
||||
_points[i].integrateForward();
|
||||
}
|
||||
}
|
||||
|
||||
void Ragdoll::clearRagdollConstraintsAndPoints() {
|
||||
void Ragdoll::clearConstraintsAndPoints() {
|
||||
int numConstraints = _boneConstraints.size();
|
||||
for (int i = 0; i < numConstraints; ++i) {
|
||||
delete _boneConstraints[i];
|
||||
|
@ -50,10 +51,10 @@ void Ragdoll::clearRagdollConstraintsAndPoints() {
|
|||
delete _fixedConstraints[i];
|
||||
}
|
||||
_fixedConstraints.clear();
|
||||
_ragdollPoints.clear();
|
||||
_points.clear();
|
||||
}
|
||||
|
||||
float Ragdoll::enforceRagdollConstraints() {
|
||||
float Ragdoll::enforceConstraints() {
|
||||
float maxDistance = 0.0f;
|
||||
// enforce the bone constraints first
|
||||
int numConstraints = _boneConstraints.size();
|
||||
|
@ -68,16 +69,16 @@ float Ragdoll::enforceRagdollConstraints() {
|
|||
return maxDistance;
|
||||
}
|
||||
|
||||
void Ragdoll::initRagdollTransform() {
|
||||
_ragdollTranslation = glm::vec3(0.0f);
|
||||
_ragdollRotation = glm::quat();
|
||||
void Ragdoll::initTransform() {
|
||||
_translation = glm::vec3(0.0f);
|
||||
_rotation = glm::quat();
|
||||
_translationInSimulationFrame = glm::vec3(0.0f);
|
||||
_rotationInSimulationFrame = glm::quat();
|
||||
}
|
||||
|
||||
void Ragdoll::setRagdollTransform(const glm::vec3& translation, const glm::quat& rotation) {
|
||||
_ragdollTranslation = translation;
|
||||
_ragdollRotation = rotation;
|
||||
void Ragdoll::setTransform(const glm::vec3& translation, const glm::quat& rotation) {
|
||||
_translation = translation;
|
||||
_rotation = rotation;
|
||||
}
|
||||
|
||||
void Ragdoll::updateSimulationTransforms(const glm::vec3& translation, const glm::quat& rotation) {
|
||||
|
@ -93,9 +94,9 @@ void Ragdoll::updateSimulationTransforms(const glm::vec3& translation, const glm
|
|||
glm::quat deltaRotation = rotation * glm::inverse(_rotationInSimulationFrame);
|
||||
|
||||
// apply the deltas to all ragdollPoints
|
||||
int numPoints = _ragdollPoints.size();
|
||||
int numPoints = _points.size();
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
_ragdollPoints[i].move(deltaPosition, deltaRotation, _translationInSimulationFrame);
|
||||
_points[i].move(deltaPosition, deltaRotation, _translationInSimulationFrame);
|
||||
}
|
||||
|
||||
// remember the current transform
|
||||
|
@ -109,10 +110,34 @@ void Ragdoll::setMassScale(float scale) {
|
|||
scale = glm::clamp(glm::abs(scale), MIN_SCALE, MAX_SCALE);
|
||||
if (scale != _massScale) {
|
||||
float rescale = scale / _massScale;
|
||||
int numPoints = _ragdollPoints.size();
|
||||
int numPoints = _points.size();
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
_ragdollPoints[i].setMass(rescale * _ragdollPoints[i].getMass());
|
||||
_points[i].setMass(rescale * _points[i].getMass());
|
||||
}
|
||||
_massScale = scale;
|
||||
}
|
||||
}
|
||||
|
||||
void Ragdoll::removeRootOffset(bool accumulateMovement) {
|
||||
const int numPoints = _points.size();
|
||||
if (numPoints > 0) {
|
||||
// shift all points so that the root aligns with the the ragdoll's position in the simulation
|
||||
glm::vec3 offset = _translationInSimulationFrame - _points[0]._position;
|
||||
float offsetLength = glm::length(offset);
|
||||
if (offsetLength > EPSILON) {
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
_points[i].shift(offset);
|
||||
}
|
||||
const float MIN_ROOT_OFFSET = 0.02f;
|
||||
if (accumulateMovement && offsetLength > MIN_ROOT_OFFSET) {
|
||||
_accumulatedMovement -= (1.0f - MIN_ROOT_OFFSET / offsetLength) * offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 Ragdoll::getAndClearAccumulatedMovement() {
|
||||
glm::vec3 movement = _accumulatedMovement;
|
||||
_accumulatedMovement = glm::vec3(0.0f);
|
||||
return movement;
|
||||
}
|
||||
|
|
|
@ -33,44 +33,54 @@ public:
|
|||
Ragdoll();
|
||||
virtual ~Ragdoll();
|
||||
|
||||
virtual void stepRagdollForward(float deltaTime);
|
||||
virtual void stepForward(float deltaTime);
|
||||
|
||||
/// \return max distance of point movement
|
||||
float enforceRagdollConstraints();
|
||||
float enforceConstraints();
|
||||
|
||||
// both const and non-const getPoints()
|
||||
const QVector<VerletPoint>& getRagdollPoints() const { return _ragdollPoints; }
|
||||
QVector<VerletPoint>& getRagdollPoints() { return _ragdollPoints; }
|
||||
const QVector<VerletPoint>& getPoints() const { return _points; }
|
||||
QVector<VerletPoint>& getPoints() { return _points; }
|
||||
|
||||
void initRagdollTransform();
|
||||
void initTransform();
|
||||
|
||||
/// set the translation and rotation of the Ragdoll and adjust all VerletPoints.
|
||||
void setRagdollTransform(const glm::vec3& translation, const glm::quat& rotation);
|
||||
void setTransform(const glm::vec3& translation, const glm::quat& rotation);
|
||||
|
||||
const glm::vec3& getTranslationInSimulationFrame() const { return _translationInSimulationFrame; }
|
||||
|
||||
void setMassScale(float scale);
|
||||
float getMassScale() const { return _massScale; }
|
||||
|
||||
protected:
|
||||
void clearRagdollConstraintsAndPoints();
|
||||
virtual void initRagdollPoints() = 0;
|
||||
virtual void buildRagdollConstraints() = 0;
|
||||
void clearConstraintsAndPoints();
|
||||
virtual void initPoints() = 0;
|
||||
virtual void buildConstraints() = 0;
|
||||
|
||||
void removeRootOffset(bool accumulateMovement);
|
||||
|
||||
glm::vec3 getAndClearAccumulatedMovement();
|
||||
|
||||
protected:
|
||||
float _massScale;
|
||||
glm::vec3 _ragdollTranslation; // world-frame
|
||||
glm::quat _ragdollRotation; // world-frame
|
||||
glm::vec3 _translation; // world-frame
|
||||
glm::quat _rotation; // world-frame
|
||||
glm::vec3 _translationInSimulationFrame;
|
||||
glm::quat _rotationInSimulationFrame;
|
||||
|
||||
QVector<VerletPoint> _ragdollPoints;
|
||||
QVector<VerletPoint> _points;
|
||||
QVector<DistanceConstraint*> _boneConstraints;
|
||||
QVector<FixedConstraint*> _fixedConstraints;
|
||||
|
||||
// The collisions are typically done in a simulation frame that is slaved to the center of one of the Ragdolls.
|
||||
// To allow the Ragdoll to provide feedback of its own displacement we store it in _accumulatedMovement.
|
||||
// The owner of the Ragdoll can harvest this displacement to update the rest of the object positions in the simulation.
|
||||
glm::vec3 _accumulatedMovement;
|
||||
|
||||
private:
|
||||
void updateSimulationTransforms(const glm::vec3& translation, const glm::quat& rotation);
|
||||
|
||||
friend class PhysicsSimulation;
|
||||
PhysicsSimulation* _ragdollSimulation;
|
||||
PhysicsSimulation* _simulation;
|
||||
};
|
||||
|
||||
#endif // hifi_Ragdoll_h
|
||||
|
|
|
@ -39,6 +39,11 @@ void VerletPoint::move(const glm::vec3& deltaPosition, const glm::quat& deltaRot
|
|||
_lastPosition += deltaPosition + (deltaRotation * arm - arm);
|
||||
}
|
||||
|
||||
void VerletPoint::shift(const glm::vec3& deltaPosition) {
|
||||
_position += deltaPosition;
|
||||
_lastPosition += deltaPosition;
|
||||
}
|
||||
|
||||
void VerletPoint::setMass(float mass) {
|
||||
const float MIN_MASS = 1.0e-6f;
|
||||
const float MAX_MASS = 1.0e18f;
|
||||
|
|
|
@ -25,6 +25,7 @@ public:
|
|||
void accumulateDelta(const glm::vec3& delta);
|
||||
void applyAccumulatedDelta();
|
||||
void move(const glm::vec3& deltaPosition, const glm::quat& deltaRotation, const glm::vec3& oldPivot);
|
||||
void shift(const glm::vec3& deltaPosition);
|
||||
|
||||
void setMass(float mass);
|
||||
float getMass() const { return _mass; }
|
||||
|
|
Loading…
Reference in a new issue