Merge pull request #3232 from huffman/19783

Code Review for Job #19783
This commit is contained in:
Brad Hefta-Gaub 2014-08-01 16:34:50 -07:00
commit 211bed946f
16 changed files with 723 additions and 9 deletions

View file

@ -60,6 +60,413 @@ var jointList = MyAvatar.getJointNames();
var mode = 0;
var exportMenu = null;
var ExportMenu = function(opts) {
var self = this;
var windowDimensions = Controller.getViewportDimensions();
var pos = { x: windowDimensions.x / 2, y: windowDimensions.y - 100 };
this._onClose = opts.onClose || function() {};
this._position = { x: 0.0, y: 0.0, z: 0.0 };
this._scale = 1.0;
var minScale = 1;
var maxScale = 32768;
var titleWidth = 120;
var locationWidth = 100;
var scaleWidth = 144;
var exportWidth = 100;
var cancelWidth = 100;
var margin = 4;
var height = 30;
var outerHeight = height + (2 * margin);
var buttonColor = { red: 128, green: 128, blue: 128};
var SCALE_MINUS = scaleWidth * 40.0 / 100.0;
var SCALE_PLUS = scaleWidth * 63.0 / 100.0;
var fullWidth = locationWidth + scaleWidth + exportWidth + cancelWidth + (2 * margin);
var offset = fullWidth / 2;
pos.x -= offset;
var background= Overlays.addOverlay("text", {
x: pos.x,
y: pos.y,
opacity: 1,
width: fullWidth,
height: outerHeight,
backgroundColor: { red: 200, green: 200, blue: 200 },
text: "",
});
var titleText = Overlays.addOverlay("text", {
x: pos.x,
y: pos.y - height,
font: { size: 14 },
width: titleWidth,
height: height,
backgroundColor: { red: 255, green: 255, blue: 255 },
color: { red: 255, green: 255, blue: 255 },
text: "Export Models"
});
var locationButton = Overlays.addOverlay("text", {
x: pos.x + margin,
y: pos.y + margin,
width: locationWidth,
height: height,
color: { red: 255, green: 255, blue: 255 },
text: "0, 0, 0",
});
var scaleOverlay = Overlays.addOverlay("image", {
x: pos.x + margin + locationWidth,
y: pos.y + margin,
width: scaleWidth,
height: height,
subImage: { x: 0, y: 3, width: 144, height: height},
imageURL: toolIconUrl + "voxel-size-selector.svg",
alpha: 0.9,
});
var scaleViewWidth = 40;
var scaleView = Overlays.addOverlay("text", {
x: pos.x + margin + locationWidth + SCALE_MINUS,
y: pos.y + margin,
width: scaleViewWidth,
height: height,
alpha: 0.0,
color: { red: 255, green: 255, blue: 255 },
text: "1"
});
var exportButton = Overlays.addOverlay("text", {
x: pos.x + margin + locationWidth + scaleWidth,
y: pos.y + margin,
width: exportWidth,
height: height,
color: { red: 0, green: 255, blue: 255 },
text: "Export"
});
var cancelButton = Overlays.addOverlay("text", {
x: pos.x + margin + locationWidth + scaleWidth + exportWidth,
y: pos.y + margin,
width: cancelWidth,
height: height,
color: { red: 255, green: 255, blue: 255 },
text: "Cancel"
});
var voxelPreview = Overlays.addOverlay("cube", {
position: { x: 0, y: 0, z: 0},
size: this._scale,
color: { red: 255, green: 255, blue: 0},
alpha: 1,
solid: false,
visible: true,
lineWidth: 4
});
this.parsePosition = function(str) {
var parts = str.split(',');
if (parts.length == 3) {
var x = parseFloat(parts[0]);
var y = parseFloat(parts[1]);
var z = parseFloat(parts[2]);
if (isFinite(x) && isFinite(y) && isFinite(z)) {
return { x: x, y: y, z: z };
}
}
return null;
};
this.showPositionPrompt = function() {
var positionStr = self._position.x + ", " + self._position.y + ", " + self._position.z;
while (1) {
positionStr = Window.prompt("Position to export form:", positionStr);
if (positionStr == null) {
break;
}
var position = self.parsePosition(positionStr);
if (position != null) {
self.setPosition(position.x, position.y, position.z);
break;
}
Window.alert("The position you entered was invalid.");
}
};
this.setScale = function(scale) {
self._scale = Math.min(maxScale, Math.max(minScale, scale));
Overlays.editOverlay(scaleView, { text: self._scale });
Overlays.editOverlay(voxelPreview, { size: self._scale });
}
this.decreaseScale = function() {
self.setScale(self._scale /= 2);
}
this.increaseScale = function() {
self.setScale(self._scale *= 2);
}
this.exportModels = function() {
var x = self._position.x;
var y = self._position.y;
var z = self._position.z;
var s = self._scale;
var filename = "models__" + Window.location.hostname + "__" + x + "_" + y + "_" + z + "_" + s + "__.svo";
filename = Window.save("Select where to save", filename, "*.svo")
if (filename) {
var success = Clipboard.exportModels(filename, x, y, z, s);
if (!success) {
Window.alert("Export failed: no models found in selected area.");
}
}
self.close();
};
this.getPosition = function() {
return self._position;
};
this.setPosition = function(x, y, z) {
self._position = { x: x, y: y, z: z };
var positionStr = x + ", " + y + ", " + z;
Overlays.editOverlay(locationButton, { text: positionStr });
Overlays.editOverlay(voxelPreview, { position: self._position });
};
this.mouseReleaseEvent = function(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == locationButton) {
self.showPositionPrompt();
} else if (clickedOverlay == exportButton) {
self.exportModels();
} else if (clickedOverlay == cancelButton) {
self.close();
} else if (clickedOverlay == scaleOverlay) {
var x = event.x - pos.x - margin - locationWidth;
print(x);
if (x < SCALE_MINUS) {
self.decreaseScale();
} else if (x > SCALE_PLUS) {
self.increaseScale();
}
}
};
this.close = function() {
this.cleanup();
this._onClose();
};
this.cleanup = function() {
Overlays.deleteOverlay(background);
Overlays.deleteOverlay(titleText);
Overlays.deleteOverlay(locationButton);
Overlays.deleteOverlay(exportButton);
Overlays.deleteOverlay(cancelButton);
Overlays.deleteOverlay(voxelPreview);
Overlays.deleteOverlay(scaleOverlay);
Overlays.deleteOverlay(scaleView);
};
print("CONNECTING!");
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
};
var ModelImporter = function(opts) {
var self = this;
var height = 30;
var margin = 4;
var outerHeight = height + (2 * margin);
var titleWidth = 120;
var cancelWidth = 100;
var fullWidth = titleWidth + cancelWidth + (2 * margin);
var localModels = Overlays.addOverlay("localmodels", {
position: { x: 1, y: 1, z: 1 },
scale: 1,
visible: false
});
var importScale = 1;
var importBoundaries = Overlays.addOverlay("cube", {
position: { x: 0, y: 0, z: 0 },
size: 1,
color: { red: 128, blue: 128, green: 128 },
lineWidth: 4,
solid: false,
visible: false
});
var pos = { x: windowDimensions.x / 2 - (fullWidth / 2), y: windowDimensions.y - 100 };
var background = Overlays.addOverlay("text", {
x: pos.x,
y: pos.y,
opacity: 1,
width: fullWidth,
height: outerHeight,
backgroundColor: { red: 200, green: 200, blue: 200 },
visible: false,
text: "",
});
var titleText = Overlays.addOverlay("text", {
x: pos.x + margin,
y: pos.y + margin,
font: { size: 14 },
width: titleWidth,
height: height,
backgroundColor: { red: 255, green: 255, blue: 255 },
color: { red: 255, green: 255, blue: 255 },
visible: false,
text: "Import Models"
});
var cancelButton = Overlays.addOverlay("text", {
x: pos.x + margin + titleWidth,
y: pos.y + margin,
width: cancelWidth,
height: height,
color: { red: 255, green: 255, blue: 255 },
visible: false,
text: "Close"
});
this._importing = false;
this.setImportVisible = function(visible) {
Overlays.editOverlay(importBoundaries, { visible: visible });
Overlays.editOverlay(localModels, { visible: visible });
Overlays.editOverlay(cancelButton, { visible: visible });
Overlays.editOverlay(titleText, { visible: visible });
Overlays.editOverlay(background, { visible: visible });
};
var importPosition = { x: 0, y: 0, z: 0 };
this.moveImport = function(position) {
importPosition = position;
Overlays.editOverlay(localModels, {
position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }
});
Overlays.editOverlay(importBoundaries, {
position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }
});
}
this.mouseMoveEvent = function(event) {
if (self._importing) {
var pickRay = Camera.computePickRay(event.x, event.y);
var intersection = Voxels.findRayIntersection(pickRay);
var distance = 2;// * self._scale;
if (false) {//intersection.intersects) {
var intersectionDistance = Vec3.length(Vec3.subtract(pickRay.origin, intersection.intersection));
if (intersectionDistance < distance) {
distance = intersectionDistance * 0.99;
}
}
var targetPosition = {
x: pickRay.origin.x + (pickRay.direction.x * distance),
y: pickRay.origin.y + (pickRay.direction.y * distance),
z: pickRay.origin.z + (pickRay.direction.z * distance)
};
if (targetPosition.x < 0) targetPosition.x = 0;
if (targetPosition.y < 0) targetPosition.y = 0;
if (targetPosition.z < 0) targetPosition.z = 0;
var nudgeFactor = 1;
var newPosition = {
x: Math.floor(targetPosition.x / nudgeFactor) * nudgeFactor,
y: Math.floor(targetPosition.y / nudgeFactor) * nudgeFactor,
z: Math.floor(targetPosition.z / nudgeFactor) * nudgeFactor
}
self.moveImport(newPosition);
}
}
this.mouseReleaseEvent = function(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == cancelButton) {
self._importing = false;
self.setImportVisible(false);
}
};
// Would prefer to use {4} for the coords, but it would only capture the last digit.
var fileRegex = /__(.+)__(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)__/;
this.doImport = function() {
if (!self._importing) {
var filename = Window.browse("Select models to import", "", "*.svo")
if (filename) {
parts = fileRegex.exec(filename);
if (parts == null) {
Window.alert("The file you selected does not contain source domain or location information");
} else {
var hostname = parts[1];
var x = parts[2];
var y = parts[3];
var z = parts[4];
var s = parts[5];
importScale = s;
if (hostname != location.hostname) {
if (!Window.confirm(("These models were not originally exported from this domain. Continue?"))) {
return;
}
} else {
if (Window.confirm(("Would you like to import back to the source location?"))) {
var success = Clipboard.importModels(filename);
if (success) {
Clipboard.pasteModels(x, y, z, 1);
} else {
Window.alert("There was an error importing the model file.");
}
return;
}
}
}
var success = Clipboard.importModels(filename);
if (success) {
self._importing = true;
self.setImportVisible(true);
Overlays.editOverlay(importBoundaries, { size: s });
} else {
Window.alert("There was an error importing the model file.");
}
}
}
}
this.paste = function() {
if (self._importing) {
// self._importing = false;
// self.setImportVisible(false);
Clipboard.pasteModels(importPosition.x, importPosition.y, importPosition.z, 1);
}
}
this.cleanup = function() {
Overlays.deleteOverlay(localModels);
Overlays.deleteOverlay(importBoundaries);
Overlays.deleteOverlay(cancelButton);
Overlays.deleteOverlay(titleText);
Overlays.deleteOverlay(background);
}
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
Controller.mouseMoveEvent.connect(this.mouseMoveEvent);
};
var modelImporter = new ModelImporter();
function isLocked(properties) {
// special case to lock the ground plane model in hq.
if (location.hostname == "hq.highfidelity.io" &&
@ -1069,7 +1476,7 @@ function mouseReleaseEvent(event) {
var modelMenuAddedDelete = false;
function setupModelMenus() {
print("setupModelMenus()");
// add our menuitems
// adj our menuitems
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...",
shortcutKeyEvent: { text: "`" }, afterItem: "Models" });
@ -1081,6 +1488,12 @@ function setupModelMenus() {
} else {
print("delete exists... don't add ours");
}
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" });
}
function cleanupModelMenus() {
@ -1090,6 +1503,12 @@ function cleanupModelMenus() {
// delete our menuitems
Menu.removeMenuItem("Edit", "Delete");
}
Menu.removeMenuItem("Edit", "Paste Models");
Menu.removeSeparator("File", "Models");
Menu.removeMenuItem("File", "Export Models");
Menu.removeMenuItem("File", "Import Models");
}
function scriptEnding() {
@ -1098,6 +1517,10 @@ function scriptEnding() {
toolBar.cleanup();
cleanupModelMenus();
tooltip.cleanup();
modelImporter.cleanup();
if (exportMenu) {
exportMenu.close();
}
}
Script.scriptEnding.connect(scriptEnding);
@ -1175,6 +1598,18 @@ function handeMenuEvent(menuItem){
Models.editModel(selectedModelID, selectedModelProperties);
}
} else if (menuItem == "Paste Models") {
modelImporter.paste();
} else if (menuItem == "Export Models") {
if (!exportMenu) {
exportMenu = new ExportMenu({
onClose: function() {
exportMenu = null;
}
});
}
} else if (menuItem == "Import Models") {
modelImporter.doImport();
}
tooltip.show(false);
}
@ -1221,4 +1656,4 @@ Controller.keyReleaseEvent.connect(function(event) {
if (event.text == "BACKSPACE") {
handeMenuEvent("Delete");
}
});
});

View file

@ -140,6 +140,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_voxelImporter(NULL),
_importSucceded(false),
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
_modelClipboardRenderer(),
_modelClipboard(),
_wantToKillLocalVoxels(false),
_viewFrustum(),
_lastQueriedViewFrustum(),
@ -174,6 +176,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow())
{
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
@ -1509,6 +1512,33 @@ struct SendVoxelsOperationArgs {
const unsigned char* newBaseOctCode;
};
bool Application::exportModels(const QString& filename, float x, float y, float z, float scale) {
QVector<ModelItem*> models;
_models.getTree()->findModelsInCube(AACube(glm::vec3(x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE), scale / (float)TREE_SCALE), models);
if (models.size() > 0) {
glm::vec3 root(x, y, z);
ModelTree exportTree;
for (int i = 0; i < models.size(); i++) {
ModelItemProperties properties;
ModelItemID id = models.at(i)->getModelItemID();
id.isKnownID = false;
properties.copyFromNewModelItem(*models.at(i));
properties.setPosition(properties.getPosition() - root);
exportTree.addModel(id, properties);
}
exportTree.writeToSVOFile(filename.toLocal8Bit().constData());
} else {
qDebug() << "No models were selected";
return false;
}
// restore the main window's active state
_window->activateWindow();
return true;
}
bool Application::sendVoxelsOperation(OctreeElement* element, void* extraData) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
SendVoxelsOperationArgs* args = (SendVoxelsOperationArgs*)extraData;
@ -1590,6 +1620,19 @@ void Application::importVoxels() {
emit importDone();
}
bool Application::importModels(const QString& filename) {
_modelClipboard.eraseAllOctreeElements();
bool success = _modelClipboard.readFromSVOFile(filename.toLocal8Bit().constData());
if (success) {
_modelClipboard.reaverageOctreeElements();
}
return success;
}
void Application::pasteModels(float x, float y, float z) {
_modelClipboard.sendModels(&_modelEditSender, x, y, z);
}
void Application::cutVoxels(const VoxelDetail& sourceVoxel) {
copyVoxels(sourceVoxel);
deleteVoxelAt(sourceVoxel);
@ -1753,6 +1796,10 @@ void Application::init() {
_models.init();
_models.setViewFrustum(getViewFrustum());
_modelClipboardRenderer.init();
_modelClipboardRenderer.setViewFrustum(getViewFrustum());
_modelClipboardRenderer.setTree(&_modelClipboard);
_metavoxels.init();
_particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);

View file

@ -201,6 +201,8 @@ public:
bool getImportSucceded() { return _importSucceded; }
VoxelSystem* getSharedVoxelSystem() { return &_sharedVoxelSystem; }
VoxelTree* getClipboard() { return &_clipboard; }
ModelTree* getModelClipboard() { return &_modelClipboard; }
ModelTreeRenderer* getModelClipboardRenderer() { return &_modelClipboardRenderer; }
Environment* getEnvironment() { return &_environment; }
bool isMousePressed() const { return _mousePressed; }
bool isMouseHidden() const { return _mouseHidden; }
@ -227,6 +229,7 @@ public:
float getPacketsPerSecond() const { return _packetsPerSecond; }
float getBytesPerSecond() const { return _bytesPerSecond; }
const glm::vec3& getViewMatrixTranslation() const { return _viewMatrixTranslation; }
void setViewMatrixTranslation(const glm::vec3& translation) { _viewMatrixTranslation = translation; }
/// if you need to access the application settings, use lockSettings()/unlockSettings()
QSettings* lockSettings() { _settingsMutex.lock(); return _settings; }
@ -314,6 +317,10 @@ public slots:
void nodeKilled(SharedNodePointer node);
void packetSent(quint64 length);
void pasteModels(float x, float y, float z);
bool exportModels(const QString& filename, float x, float y, float z, float scale);
bool importModels(const QString& filename);
void importVoxels(); // doesn't include source voxel because it goes to clipboard
void cutVoxels(const VoxelDetail& sourceVoxel);
void copyVoxels(const VoxelDetail& sourceVoxel);
@ -462,6 +469,8 @@ private:
ParticleCollisionSystem _particleCollisionSystem;
ModelTreeRenderer _models;
ModelTreeRenderer _modelClipboardRenderer;
ModelTree _modelClipboard;
QByteArray _voxelsFilename;
bool _wantToKillLocalVoxels;

View file

@ -100,3 +100,16 @@ void ClipboardScriptingInterface::nudgeVoxel(float x, float y, float z, float s,
Application::getInstance()->nudgeVoxelsByVector(sourceVoxel, nudgeVecInTreeSpace);
}
bool ClipboardScriptingInterface::exportModels(const QString& filename, float x, float y, float z, float s) {
return Application::getInstance()->exportModels(filename, x, y, z, s);
}
bool ClipboardScriptingInterface::importModels(const QString& filename) {
return Application::getInstance()->importModels(filename);
}
void ClipboardScriptingInterface::pasteModels(float x, float y, float z, float s) {
Application::getInstance()->pasteModels(x, y, z);
}

View file

@ -42,6 +42,10 @@ public slots:
void nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec);
void nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec);
bool importModels(const QString& filename);
bool exportModels(const QString& filename, float x, float y, float z, float s);
void pasteModels(float x, float y, float z, float s);
};
#endif // hifi_ClipboardScriptingInterface_h

View file

@ -67,6 +67,15 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin
return retVal;
}
QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showBrowse", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(const QString&, title), Q_ARG(const QString&, directory), Q_ARG(const QString&, nameFilter),
Q_ARG(QFileDialog::AcceptMode, QFileDialog::AcceptSave));
return retVal;
}
QScriptValue WindowScriptingInterface::s3Browse(const QString& nameFilter) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showS3Browse", Qt::BlockingQueuedConnection,
@ -182,18 +191,26 @@ QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const
/// \param const QString& directory directory to start the file browser at
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter) {
QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
QFileDialog::AcceptMode acceptMode) {
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
// filename if the directory is valid.
QString path = "";
QFileInfo fileInfo = QFileInfo(directory);
qDebug() << "File: " << directory << fileInfo.isFile();
if (fileInfo.isDir()) {
fileInfo.setFile(directory, "__HIFI_INVALID_FILE__");
path = fileInfo.filePath();
}
QFileDialog fileDialog(Application::getInstance()->getWindow(), title, path, nameFilter);
fileDialog.setFileMode(QFileDialog::ExistingFile);
fileDialog.setAcceptMode(acceptMode);
qDebug() << "Opening!";
QUrl fileUrl(directory);
if (acceptMode == QFileDialog::AcceptSave) {
fileDialog.setFileMode(QFileDialog::Directory);
fileDialog.selectFile(fileUrl.fileName());
}
if (fileDialog.exec()) {
return QScriptValue(fileDialog.selectedFiles().first());
}

View file

@ -31,6 +31,7 @@ public slots:
QScriptValue form(const QString& title, QScriptValue array);
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
QScriptValue s3Browse(const QString& nameFilter = "");
private slots:
@ -38,7 +39,8 @@ private slots:
QScriptValue showConfirm(const QString& message);
QScriptValue showForm(const QString& title, QScriptValue form);
QScriptValue showPrompt(const QString& message, const QString& defaultText);
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter);
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
QFileDialog::AcceptMode acceptMode = QFileDialog::AcceptOpen);
QScriptValue showS3Browse(const QString& nameFilter);
private:

View file

@ -0,0 +1,40 @@
//
// LocalModelsOverlay.cpp
// interface/src/ui/overlays
//
// Created by Ryan Huffman on 07/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 "Application.h"
#include "LocalModelsOverlay.h"
LocalModelsOverlay::LocalModelsOverlay(ModelTreeRenderer* modelTreeRenderer) :
Volume3DOverlay(),
_modelTreeRenderer(modelTreeRenderer) {
}
LocalModelsOverlay::~LocalModelsOverlay() {
}
void LocalModelsOverlay::update(float deltatime) {
_modelTreeRenderer->update();
}
void LocalModelsOverlay::render() {
if (_visible) {
glPushMatrix(); {
Application* app = Application::getInstance();
glm::vec3 oldTranslation = app->getViewMatrixTranslation();
app->setViewMatrixTranslation(oldTranslation + _position);
_modelTreeRenderer->render();
Application::getInstance()->setViewMatrixTranslation(oldTranslation);
} glPopMatrix();
}
}

View file

@ -0,0 +1,32 @@
//
// LocalModelsOverlay.h
// interface/src/ui/overlays
//
// Created by Ryan Huffman on 07/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_LocalModelsOverlay_h
#define hifi_LocalModelsOverlay_h
#include "models/ModelTreeRenderer.h"
#include "Volume3DOverlay.h"
class LocalModelsOverlay : public Volume3DOverlay {
Q_OBJECT
public:
LocalModelsOverlay(ModelTreeRenderer* modelTreeRenderer);
~LocalModelsOverlay();
virtual void update(float deltatime);
virtual void render();
private:
ModelTreeRenderer *_modelTreeRenderer;
};
#endif // hifi_LocalModelsOverlay_h

View file

@ -14,6 +14,7 @@
#include "Cube3DOverlay.h"
#include "ImageOverlay.h"
#include "Line3DOverlay.h"
#include "LocalModelsOverlay.h"
#include "LocalVoxelsOverlay.h"
#include "ModelOverlay.h"
#include "Overlays.h"
@ -158,6 +159,12 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "localmodels") {
thisOverlay = new LocalModelsOverlay(Application::getInstance()->getModelClipboardRenderer());
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "model") {
thisOverlay = new ModelOverlay();
thisOverlay->init(_parent);

View file

@ -1157,6 +1157,38 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) {
_defaultSettings = false;
}
void ModelItemProperties::copyFromNewModelItem(const ModelItem& modelItem) {
_position = modelItem.getPosition() * (float) TREE_SCALE;
_color = modelItem.getXColor();
_radius = modelItem.getRadius() * (float) TREE_SCALE;
_shouldDie = modelItem.getShouldDie();
_modelURL = modelItem.getModelURL();
_modelRotation = modelItem.getModelRotation();
_animationURL = modelItem.getAnimationURL();
_animationIsPlaying = modelItem.getAnimationIsPlaying();
_animationFrameIndex = modelItem.getAnimationFrameIndex();
_animationFPS = modelItem.getAnimationFPS();
_glowLevel = modelItem.getGlowLevel();
_sittingPoints = modelItem.getSittingPoints();
_id = modelItem.getID();
_idSet = true;
_positionChanged = true;
_colorChanged = true;
_radiusChanged = true;
_shouldDieChanged = true;
_modelURLChanged = true;
_modelRotationChanged = true;
_animationURLChanged = true;
_animationIsPlayingChanged = true;
_animationFrameIndexChanged = true;
_animationFPSChanged = true;
_glowLevelChanged = true;
_defaultSettings = true;
}
QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const ModelItemProperties& properties) {
return properties.copyToScriptValue(engine);
}

View file

@ -74,6 +74,7 @@ public:
void copyToModelItem(ModelItem& modelItem) const;
void copyFromModelItem(const ModelItem& modelItem);
void copyFromNewModelItem(const ModelItem& modelItem);
const glm::vec3& getPosition() const { return _position; }
xColor getColor() const { return _color; }

View file

@ -9,6 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ModelEditPacketSender.h"
#include "ModelItem.h"
#include "ModelTree.h"
ModelTree::ModelTree(bool shouldReaverage) : Octree(shouldReaverage) {
@ -108,6 +111,7 @@ bool FindAndUpdateModelOperator::PostRecursion(OctreeElement* element) {
return !_found; // if we haven't yet found it, keep looking
}
// TODO: improve this to not use multiple recursions
void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& senderNode) {
// First, look for the existing model in the tree..
@ -199,6 +203,32 @@ void ModelTree::deleteModel(const ModelItemID& modelID) {
}
}
void ModelTree::sendModels(ModelEditPacketSender* packetSender, float x, float y, float z) {
SendModelsOperationArgs args;
args.packetSender = packetSender;
args.root = glm::vec3(x, y, z);
recurseTreeWithOperation(sendModelsOperation, &args);
packetSender->releaseQueuedMessages();
}
bool ModelTree::sendModelsOperation(OctreeElement* element, void* extraData) {
SendModelsOperationArgs* args = static_cast<SendModelsOperationArgs*>(extraData);
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
const QList<ModelItem>& modelList = modelTreeElement->getModels();
for (int i = 0; i < modelList.size(); i++) {
uint32_t creatorTokenID = ModelItem::getNextCreatorTokenID();
ModelItemID id(NEW_MODEL, creatorTokenID, false);
ModelItemProperties properties;
properties.copyFromNewModelItem(modelList.at(i));
properties.setPosition(properties.getPosition() + args->root);
args->packetSender->queueModelEditMessage(PacketTypeModelAddOrEdit, id, properties);
}
return true;
}
// scans the tree and handles mapping locally created models to know IDs.
// in the event that this tree is also viewing the scene, then we need to also
// search the tree to make sure we don't have a duplicate model from the viewing
@ -353,8 +383,28 @@ public:
QVector<ModelItem*> _foundModels;
};
void ModelTree::findModelsInCube(const AACube& cube, QVector<ModelItem*>& foundModels) {
FindModelsInCubeArgs args(cube);
lockForRead();
recurseTreeWithOperation(findInCubeOperation, &args);
unlock();
// swap the two lists of model pointers instead of copy
foundModels.swap(args._foundModels);
}
bool ModelTree::findInCubeOperation(OctreeElement* element, void* extraData) {
FindModelsInCubeArgs* args = static_cast<FindModelsInCubeArgs*>(extraData);
const AACube& elementCube = element->getAACube();
if (elementCube.touches(args->_cube)) {
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
modelTreeElement->getModelsInside(args->_cube, args->_foundModels);
return true;
}
return false;
}
bool ModelTree::findInCubeForUpdateOperation(OctreeElement* element, void* extraData) {
FindModelsInCubeArgs* args = static_cast< FindModelsInCubeArgs*>(extraData);
FindModelsInCubeArgs* args = static_cast<FindModelsInCubeArgs*>(extraData);
const AACube& elementCube = element->getAACube();
if (elementCube.touches(args->_cube)) {
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
@ -364,7 +414,7 @@ bool ModelTree::findInCubeForUpdateOperation(OctreeElement* element, void* extra
return false;
}
void ModelTree::findModelsForUpdate(const AACube& cube, QVector<ModelItem*> foundModels) {
void ModelTree::findModelsForUpdate(const AACube& cube, QVector<ModelItem*>& foundModels) {
FindModelsInCubeArgs args(cube);
lockForRead();
recurseTreeWithOperation(findInCubeForUpdateOperation, &args);

View file

@ -36,7 +36,6 @@ public:
/// Type safe version of getRoot()
ModelTreeElement* getRoot() { return static_cast<ModelTreeElement*>(_rootElement); }
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
// own definition. Implement these to allow your octree based server to support editing
virtual bool getWantSVOfileVersions() const { return true; }
@ -62,12 +61,13 @@ public:
/// \param foundModels[out] vector of const ModelItem*
/// \remark Side effect: any initial contents in foundModels will be lost
void findModels(const glm::vec3& center, float radius, QVector<const ModelItem*>& foundModels);
void findModelsInCube(const AACube& cube, QVector<ModelItem*>& foundModels);
/// finds all models that touch a cube
/// \param cube the query cube
/// \param foundModels[out] vector of non-const ModelItem*
/// \remark Side effect: any initial contents in models will be lost
void findModelsForUpdate(const AACube& cube, QVector<ModelItem*> foundModels);
void findModelsForUpdate(const AACube& cube, QVector<ModelItem*>& foundModels);
void addNewlyCreatedHook(NewlyCreatedModelHook* hook);
void removeNewlyCreatedHook(NewlyCreatedModelHook* hook);
@ -83,11 +83,15 @@ public:
void setFBXService(ModelItemFBXService* service) { _fbxService = service; }
const FBXGeometry* getGeometryForModel(const ModelItem& modelItem) {
return _fbxService ? _fbxService->getGeometryForModel(modelItem) : NULL;
}
void sendModels(ModelEditPacketSender* packetSender, float x, float y, float z);
private:
static bool sendModelsOperation(OctreeElement* element, void* extraData);
static bool updateOperation(OctreeElement* element, void* extraData);
static bool findInCubeOperation(OctreeElement* element, void* extraData);
static bool findAndUpdateOperation(OctreeElement* element, void* extraData);
static bool findAndUpdateWithIDandPropertiesOperation(OctreeElement* element, void* extraData);
static bool findNearPointOperation(OctreeElement* element, void* extraData);

View file

@ -399,6 +399,19 @@ void ModelTreeElement::getModels(const glm::vec3& searchPosition, float searchRa
}
}
void ModelTreeElement::getModelsInside(const AACube& box, QVector<ModelItem*>& foundModels) {
QList<ModelItem>::iterator modelItr = _modelItems->begin();
QList<ModelItem>::iterator modelEnd = _modelItems->end();
AACube modelCube;
while(modelItr != modelEnd) {
ModelItem* model = &(*modelItr);
if (box.contains(model->getPosition())) {
foundModels.push_back(model);
}
++modelItr;
}
}
void ModelTreeElement::getModelsForUpdate(const AACube& box, QVector<ModelItem*>& foundModels) {
QList<ModelItem>::iterator modelItr = _modelItems->begin();
QList<ModelItem>::iterator modelEnd = _modelItems->end();

View file

@ -44,6 +44,12 @@ public:
bool isViewing;
};
class SendModelsOperationArgs {
public:
glm::vec3 root;
ModelEditPacketSender* packetSender;
};
class ModelTreeElement : public OctreeElement {
@ -132,6 +138,8 @@ public:
/// \param models[out] vector of non-const ModelItem*
void getModelsForUpdate(const AACube& box, QVector<ModelItem*>& foundModels);
void getModelsInside(const AACube& box, QVector<ModelItem*>& foundModels);
const ModelItem* getModelWithID(uint32_t id) const;
bool removeModelWithID(uint32_t id);