Merge branch 'master' of https://github.com/highfidelity/hifi into record_feature

This commit is contained in:
Atlante45 2014-08-18 15:41:19 -07:00
commit 6d567e67a9
31 changed files with 2326 additions and 651 deletions

View file

@ -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) {

View file

@ -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

View file

@ -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");
}

View file

@ -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();

View file

@ -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>&lt;b&gt;Import&lt;/b&gt; %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>

View file

@ -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;
@ -1621,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);
}
@ -1746,7 +1747,7 @@ void Application::init() {
// Cleanup of the original shared tree
_sharedVoxelSystem.init();
_voxelImporter = new VoxelImporter(_window);
_voxelImportDialog = new VoxelImportDialog(_window);
_environment.init();

View file

@ -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;

View file

@ -213,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);
}

View file

@ -14,14 +14,11 @@
#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"
@ -610,6 +607,7 @@ void SkeletonModel::buildShapes() {
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();
@ -628,26 +626,31 @@ void SkeletonModel::buildShapes() {
if (type == Shape::SPHERE_SHAPE) {
shape = new VerletSphereShape(radius, &(points[i]));
shape->setEntity(this);
points[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, &(points[parentIndex]), &(points[i]));
shape->setEntity(this);
points[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)
points[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);

View file

@ -70,10 +70,7 @@ void SkeletonRagdoll::buildConstraints() {
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, &(_points[i]));
_fixedConstraints.push_back(anchor);
} else {
if (parentIndex != -1) {
DistanceConstraint* bone = new DistanceConstraint(&(_points[i]), &(_points[parentIndex]));
bone->setDistance(state.getDistanceToParent());
_boneConstraints.push_back(bone);

View file

@ -33,7 +33,9 @@ public:
virtual void initPoints();
virtual void buildConstraints();
protected:
void updateMuscles();
private:
Model* _model;
QVector<MuscleConstraint*> _muscleConstraints;

View file

@ -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);
}

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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());

View file

@ -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

View file

@ -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();
}

View file

@ -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

View file

@ -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());
}

View file

@ -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;

View file

@ -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) {

View file

@ -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;

View file

@ -96,10 +96,10 @@ float ContactPoint::enforce() {
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);
_contactPoint = _relativeMassA * pointA + _relativeMassB * pointB;
if (constraintViolation) {
for (int i = 0; i < _numPoints; ++i) {
for (int i = 0; i < _numPointsA; ++i) {
VerletPoint* point = _points[i];
glm::vec3 offset = _offsets[i];
@ -111,8 +111,31 @@ float ContactPoint::enforce() {
// 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 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

View file

@ -163,10 +163,10 @@ bool PhysicsSimulation::addRagdoll(Ragdoll* doll) {
}
void PhysicsSimulation::removeRagdoll(Ragdoll* doll) {
int numDolls = _otherRagdolls.size();
if (doll->_simulation != 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) {
@ -205,10 +205,11 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter
}
}
bool collidedWithOtherRagdoll = false;
int iterations = 0;
float error = 0.0f;
do {
computeCollisions();
collidedWithOtherRagdoll = computeCollisions() || collidedWithOtherRagdoll;
updateContacts();
resolveCollisions();
@ -225,6 +226,14 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter
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();
}
@ -237,7 +246,7 @@ void PhysicsSimulation::moveRagdolls(float 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() {

View file

@ -53,9 +53,11 @@ public:
protected:
void moveRagdolls(float deltaTime);
void computeCollisions();
void resolveCollisions();
/// \return true if main ragdoll collides with other avatar
bool computeCollisions();
void resolveCollisions();
void enforceContacts();
void applyContactFriction();
void updateContacts();

View file

@ -19,7 +19,8 @@
#include "PhysicsSimulation.h"
#include "SharedUtil.h" // for EPSILON
Ragdoll::Ragdoll() : _massScale(1.0f), _translation(0.0f), _translationInSimulationFrame(0.0f), _simulation(NULL) {
Ragdoll::Ragdoll() : _massScale(1.0f), _translation(0.0f), _translationInSimulationFrame(0.0f),
_accumulatedMovement(0.0f), _simulation(NULL) {
}
Ragdoll::~Ragdoll() {
@ -116,3 +117,27 @@ void Ragdoll::setMassScale(float scale) {
_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;
}

View file

@ -56,6 +56,10 @@ public:
virtual void initPoints() = 0;
virtual void buildConstraints() = 0;
void removeRootOffset(bool accumulateMovement);
glm::vec3 getAndClearAccumulatedMovement();
protected:
float _massScale;
glm::vec3 _translation; // world-frame
@ -66,6 +70,12 @@ protected:
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);

View file

@ -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;

View file

@ -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; }