diff --git a/examples/entityScripts/changeColorOnHover.js b/examples/entityScripts/changeColorOnHover.js
index de3f5f3784..638c1bece4 100644
--- a/examples/entityScripts/changeColorOnHover.js
+++ b/examples/entityScripts/changeColorOnHover.js
@@ -22,13 +22,21 @@
this.oldColorKnown = true;
print("storing old color... this.oldColor=" + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
};
+
+ this.preload = function(entityID) {
+ print("preload");
+ this.storeOldColor(entityID);
+ };
+
this.hoverEnterEntity = function(entityID, mouseEvent) {
+ print("hoverEnterEntity");
if (!this.oldColorKnown) {
this.storeOldColor(entityID);
}
Entities.editEntity(entityID, { color: { red: 0, green: 255, blue: 255} });
};
this.hoverLeaveEntity = function(entityID, mouseEvent) {
+ print("hoverLeaveEntity");
if (this.oldColorKnown) {
print("leave restoring old color... this.oldColor="
+ this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
diff --git a/examples/entityScripts/playSoundOnClick.js b/examples/entityScripts/playSoundOnClick.js
index b261bb269a..4bc523a7aa 100644
--- a/examples/entityScripts/playSoundOnClick.js
+++ b/examples/entityScripts/playSoundOnClick.js
@@ -12,13 +12,23 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
- var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
+ var bird;
+
+ function playSound(entityID) {
+ var options = new AudioInjectionOptions();
+ var position = MyAvatar.position;
+ options.position = position;
+ options.volume = 0.5;
+ Audio.playSound(bird, options);
+ };
+
+ this.preload = function(entityID) {
+ print("preload("+entityID.id+")");
+ bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
+ };
+
this.clickDownOnEntity = function(entityID, mouseEvent) {
print("clickDownOnEntity()...");
- var options = new AudioInjectionOptions();
- var position = MyAvatar.position;
- options.position = position;
- options.volume = 0.5;
- Audio.playSound(bird, options);
+ playSound();
};
})
diff --git a/examples/entityScripts/playSoundOnEnterOrLeave.js b/examples/entityScripts/playSoundOnEnterOrLeave.js
index 228a8a36d0..98702dcfdd 100644
--- a/examples/entityScripts/playSoundOnEnterOrLeave.js
+++ b/examples/entityScripts/playSoundOnEnterOrLeave.js
@@ -12,9 +12,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
- var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
+ var bird;
- function playSound(entityID) {
+ function playSound() {
var options = new AudioInjectionOptions();
var position = MyAvatar.position;
options.position = position;
@@ -22,6 +22,11 @@
Audio.playSound(bird, options);
};
+ this.preload = function(entityID) {
+ print("preload("+entityID.id+")");
+ bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
+ };
+
this.enterEntity = function(entityID) {
playSound();
};
diff --git a/examples/html/gridControls.html b/examples/html/gridControls.html
new file mode 100644
index 0000000000..e7bf1cdf8c
--- /dev/null
+++ b/examples/html/gridControls.html
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js
index 29bf1bdd79..22f75cb187 100644
--- a/examples/libraries/entitySelectionTool.js
+++ b/examples/libraries/entitySelectionTool.js
@@ -394,12 +394,12 @@ SelectionDisplay = (function () {
var baseOverlayAngles = { x: 0, y: 0, z: 0 };
var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles);
var baseOfEntityProjectionOverlay = Overlays.addOverlay("rectangle3d", {
- position: { x:0, y: 0, z: 0},
- size: 1,
+ position: { x: 1, y: 0, z: 0},
color: { red: 51, green: 152, blue: 203 },
alpha: 0.5,
solid: true,
visible: false,
+ width: 300, height: 200,
rotation: baseOverlayRotation,
ignoreRayIntersection: true, // always ignore this
});
@@ -570,6 +570,7 @@ SelectionDisplay = (function () {
xRailOverlay,
yRailOverlay,
zRailOverlay,
+ baseOfEntityProjectionOverlay,
].concat(stretchHandles);
overlayNames[highlightBox] = "highlightBox";
@@ -878,20 +879,24 @@ SelectionDisplay = (function () {
translateHandlesVisible = false;
}
- var rotation = SelectionManager.worldRotation;
- var dimensions = SelectionManager.worldDimensions;
- var position = SelectionManager.worldPosition;
+ var rotation = selectionManager.worldRotation;
+ var dimensions = selectionManager.worldDimensions;
+ var position = selectionManager.worldPosition;
Overlays.editOverlay(baseOfEntityProjectionOverlay,
{
- visible: true,
- solid:true,
- lineWidth: 2.0,
- position: { x: position.x,
- y: 0,
- z: position.z },
-
- dimensions: { x: dimensions.x, y: 0, z: dimensions.z },
+ visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL",
+ solid: true,
+ // lineWidth: 2.0,
+ position: {
+ x: position.x,
+ y: grid.getOrigin().y,
+ z: position.z
+ },
+ dimensions: {
+ x: dimensions.x,
+ y: dimensions.z
+ },
rotation: rotation,
});
@@ -1098,6 +1103,7 @@ SelectionDisplay = (function () {
var initialXZPick = null;
var isConstrained = false;
+ var constrainMajorOnly = false;
var startPosition = null;
var duplicatedEntityIDs = null;
var translateXZTool = {
@@ -1162,15 +1168,23 @@ SelectionDisplay = (function () {
if (isConstrained) {
Overlays.editOverlay(xRailOverlay, { visible: false });
Overlays.editOverlay(zRailOverlay, { visible: false });
+ isConstrained = false;
}
}
+ constrainMajorOnly = event.isControl;
+ var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, selectionManager.worldDimensions));
+ vector = Vec3.subtract(
+ grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly),
+ cornerPosition);
+
var wantDebug = false;
for (var i = 0; i < SelectionManager.selections.length; i++) {
var properties = SelectionManager.savedProperties[SelectionManager.selections[i].id];
+ var newPosition = Vec3.sum(properties.position, { x: vector.x, y: 0, z: vector.z });
Entities.editEntity(SelectionManager.selections[i], {
- position: Vec3.sum(properties.position, vector),
+ position: newPosition,
});
if (wantDebug) {
diff --git a/examples/libraries/gridTool.js b/examples/libraries/gridTool.js
new file mode 100644
index 0000000000..34e25d6733
--- /dev/null
+++ b/examples/libraries/gridTool.js
@@ -0,0 +1,189 @@
+Grid = function(opts) {
+ var that = {};
+
+ var color = { red: 100, green: 152, blue: 203 };
+ var gridColor = { red: 100, green: 152, blue: 203 };
+ var gridAlpha = 1.0;
+ var origin = { x: 0, y: 0, z: 0 };
+ var majorGridEvery = 5;
+ var minorGridSpacing = 0.2;
+ var halfSize = 40;
+ var yOffset = 0.001;
+
+ var worldSize = 16384;
+
+ var minorGridWidth = 0.5;
+ var majorGridWidth = 1.5;
+
+ var snapToGrid = true;
+
+ var gridOverlay = Overlays.addOverlay("grid", {
+ position: { x: 0 , y: 0, z: 0 },
+ visible: true,
+ color: { red: 0, green: 0, blue: 128 },
+ alpha: 1.0,
+ rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
+ minorGridWidth: 0.1,
+ majorGridEvery: 2,
+ });
+
+ that.getMinorIncrement = function() { return minorGridSpacing; };
+ that.getMajorIncrement = function() { return minorGridSpacing * majorGridEvery; };
+
+ that.visible = false;
+
+ that.getOrigin = function() {
+ return origin;
+ }
+
+ that.getSnapToGrid = function() { return snapToGrid; };
+
+ that.setVisible = function(visible, noUpdate) {
+ that.visible = visible;
+ updateGrid();
+
+ if (!noUpdate) {
+ that.emitUpdate();
+ }
+ }
+
+ that.snapToGrid = function(position, majorOnly) {
+ if (!snapToGrid) {
+ return position;
+ }
+
+ var spacing = majorOnly ? (minorGridSpacing * majorGridEvery) : minorGridSpacing;
+
+ position = Vec3.subtract(position, origin);
+
+ position.x = Math.round(position.x / spacing) * spacing;
+ position.y = Math.round(position.y / spacing) * spacing;
+ position.z = Math.round(position.z / spacing) * spacing;
+
+ return Vec3.sum(position, origin);
+ }
+
+ that.setPosition = function(newPosition, noUpdate) {
+ origin = Vec3.subtract(newPosition, { x: 0, y: yOffset, z: 0 });
+ origin.x = 0;
+ origin.z = 0;
+ updateGrid();
+
+ if (!noUpdate) {
+ that.emitUpdate();
+ }
+ };
+
+ that.emitUpdate = function() {
+ if (that.onUpdate) {
+ that.onUpdate({
+ origin: origin,
+ minorGridSpacing: minorGridSpacing,
+ majorGridEvery: majorGridEvery,
+ gridSize: halfSize,
+ visible: that.visible,
+ snapToGrid: snapToGrid,
+ gridColor: gridColor,
+ });
+ }
+ };
+
+ that.update = function(data) {
+ if (data.snapToGrid !== undefined) {
+ snapToGrid = data.snapToGrid;
+ }
+
+ if (data.origin) {
+ var pos = data.origin;
+ pos.x = pos.x === undefined ? origin.x : pos.x;
+ pos.y = pos.y === undefined ? origin.y : pos.y;
+ pos.z = pos.z === undefined ? origin.z : pos.z;
+ that.setPosition(pos, true);
+ }
+
+ if (data.minorGridSpacing) {
+ minorGridSpacing = data.minorGridSpacing;
+ }
+
+ if (data.majorGridEvery) {
+ majorGridEvery = data.majorGridEvery;
+ }
+
+ if (data.gridColor) {
+ gridColor = data.gridColor;
+ }
+
+ if (data.gridSize) {
+ halfSize = data.gridSize;
+ }
+
+ if (data.visible !== undefined) {
+ that.setVisible(data.visible, true);
+ }
+
+ updateGrid();
+ }
+
+ function updateGrid() {
+ Overlays.editOverlay(gridOverlay, {
+ position: { x: origin.y, y: origin.y, z: -origin.y },
+ visible: that.visible,
+ minorGridWidth: minorGridSpacing,
+ majorGridEvery: majorGridEvery,
+ color: gridColor,
+ alpha: gridAlpha,
+ });
+ }
+
+ function cleanup() {
+ Overlays.deleteOverlay(gridOverlay);
+ }
+
+ that.addListener = function(callback) {
+ that.onUpdate = callback;
+ }
+
+ Script.scriptEnding.connect(cleanup);
+ updateGrid();
+
+ that.onUpdate = null;
+
+ return that;
+};
+
+GridTool = function(opts) {
+ var that = {};
+
+ var horizontalGrid = opts.horizontalGrid;
+ var verticalGrid = opts.verticalGrid;
+ var listeners = [];
+
+ var url = Script.resolvePath('html/gridControls.html');
+ var webView = new WebWindow(url, 200, 280);
+
+ horizontalGrid.addListener(function(data) {
+ webView.eventBridge.emitScriptEvent(JSON.stringify(data));
+ });
+
+ webView.eventBridge.webEventReceived.connect(function(data) {
+ data = JSON.parse(data);
+ if (data.type == "init") {
+ horizontalGrid.emitUpdate();
+ } else if (data.type == "update") {
+ horizontalGrid.update(data);
+ for (var i = 0; i < listeners.length; i++) {
+ listeners[i](data);
+ }
+ }
+ });
+
+ that.addListener = function(callback) {
+ listeners.push(callback);
+ }
+
+ that.setVisible = function(visible) {
+ webView.setVisible(visible);
+ }
+
+ return that;
+};
diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js
index 57f3f29670..68e0d0c146 100644
--- a/examples/newEditEntities.js
+++ b/examples/newEditEntities.js
@@ -35,6 +35,10 @@ var entityPropertyDialogBox = EntityPropertyDialogBox;
Script.include("libraries/entityCameraTool.js");
var cameraManager = new CameraManager();
+Script.include("libraries/gridTool.js");
+var grid = Grid();
+gridTool = GridTool({ horizontalGrid: grid });
+
selectionManager.setEventListener(selectionDisplay.updateHandles);
var windowDimensions = Controller.getViewportDimensions();
@@ -51,11 +55,13 @@ var wantEntityGlow = false;
var SPAWN_DISTANCE = 1;
var DEFAULT_DIMENSION = 0.20;
+var MENU_GRID_TOOL_ENABLED = 'Grid Tool';
var MENU_INSPECT_TOOL_ENABLED = "Inspect Tool";
var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus";
var SETTING_INSPECT_TOOL_ENABLED = "inspectToolEnabled";
var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
+var SETTING_GRID_TOOL_ENABLED = 'GridToolEnabled';
var modelURLs = [
HIFI_PUBLIC_BUCKET + "models/entities/2-Terrain:%20Alder.fbx",
@@ -262,10 +268,12 @@ var toolBar = (function () {
if (activeButton === toolBar.clicked(clickedOverlay)) {
isActive = !isActive;
if (!isActive) {
+ gridTool.setVisible(false);
selectionManager.clearSelections();
cameraManager.disable();
} else {
cameraManager.enable();
+ gridTool.setVisible(Menu.isOptionChecked(MENU_GRID_TOOL_ENABLED));
}
return true;
}
@@ -597,7 +605,9 @@ function setupModelMenus() {
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" });
Menu.addMenuItem({ menuName: "Developer", menuItemName: "Debug Ryans Rotation Problems", isCheckable: true });
- Menu.addMenuItem({ menuName: "View", menuItemName: MENU_INSPECT_TOOL_ENABLED, afterItem: "Edit Entities Help...",
+ Menu.addMenuItem({ menuName: "View", menuItemName: MENU_GRID_TOOL_ENABLED, afterItem: "Edit Entities Help...", isCheckable: true,
+ isChecked: Settings.getValue(SETTING_GRID_TOOL_ENABLED) == 'true'});
+ Menu.addMenuItem({ menuName: "View", menuItemName: MENU_INSPECT_TOOL_ENABLED, afterItem: MENU_GRID_TOOL_ENABLED,
isCheckable: true, isChecked: Settings.getValue(SETTING_INSPECT_TOOL_ENABLED) == "true" });
Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_INSPECT_TOOL_ENABLED,
isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" });
@@ -623,6 +633,8 @@ function cleanupModelMenus() {
Menu.removeMenuItem("File", "Import Models");
Menu.removeMenuItem("Developer", "Debug Ryans Rotation Problems");
+ Settings.setValue(SETTING_GRID_TOOL_ENABLED, Menu.isOptionChecked(MENU_GRID_TOOL_ENABLED));
+ Menu.removeMenuItem("View", MENU_GRID_TOOL_ENABLED);
Menu.removeMenuItem("View", MENU_INSPECT_TOOL_ENABLED);
Menu.removeMenuItem("View", MENU_EASE_ON_FOCUS);
}
@@ -734,6 +746,10 @@ function handeMenuEvent(menuItem) {
}
} else if (menuItem == "Import Models") {
modelImporter.doImport();
+ } else if (menuItem == MENU_GRID_TOOL_ENABLED) {
+ if (isActive) {
+ gridTool.setVisible(Menu.isOptionChecked(MENU_GRID_TOOL_ENABLED));
+ }
}
tooltip.show(false);
}
@@ -759,25 +775,32 @@ Controller.keyReleaseEvent.connect(function (event) {
if (isActive) {
cameraManager.enable();
}
+ } else if (event.text == 'g') {
+ if (isActive && selectionManager.hasSelection()) {
+ var newPosition = selectionManager.worldPosition;
+ newPosition = Vec3.subtract(newPosition, { x: 0, y: selectionManager.worldDimensions.y * 0.5, z: 0 });
+ grid.setPosition(newPosition);
+ }
} else if (isActive) {
var delta = null;
+ var increment = event.isShifted ? grid.getMajorIncrement() : grid.getMinorIncrement();
if (event.text == 'UP') {
if (event.isControl || event.isAlt) {
- delta = { x: 0, y: 1, z: 0 };
+ delta = { x: 0, y: increment, z: 0 };
} else {
- delta = { x: 0, y: 0, z: -1 };
+ delta = { x: 0, y: 0, z: -increment };
}
} else if (event.text == 'DOWN') {
if (event.isControl || event.isAlt) {
- delta = { x: 0, y: -1, z: 0 };
+ delta = { x: 0, y: -increment, z: 0 };
} else {
- delta = { x: 0, y: 0, z: 1 };
+ delta = { x: 0, y: 0, z: increment };
}
} else if (event.text == 'LEFT') {
- delta = { x: -1, y: 0, z: 0 };
+ delta = { x: -increment, y: 0, z: 0 };
} else if (event.text == 'RIGHT') {
- delta = { x: 1, y: 0, z: 0 };
+ delta = { x: increment, y: 0, z: 0 };
}
if (delta != null) {
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index d54a638239..b1c969b66f 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -92,6 +92,7 @@
#include "scripting/MenuScriptingInterface.h"
#include "scripting/SettingsScriptingInterface.h"
#include "scripting/WindowScriptingInterface.h"
+#include "scripting/WebWindowClass.h"
#include "ui/DataWebDialog.h"
#include "ui/InfoView.h"
@@ -3897,6 +3898,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
// register `location` on the global object.
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter);
+
+ scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp
index 2f8ddb1095..1876b6c624 100644
--- a/interface/src/entities/EntityTreeRenderer.cpp
+++ b/interface/src/entities/EntityTreeRenderer.cpp
@@ -81,6 +81,9 @@ void EntityTreeRenderer::init() {
_lastAvatarPosition = avatarPosition + glm::vec3(1.f, 1.f, 1.f);
connect(entityTree, &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity);
+ connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::checkAndCallPreload);
+ connect(entityTree, &EntityTree::entityScriptChanging, this, &EntityTreeRenderer::checkAndCallPreload);
+ connect(entityTree, &EntityTree::changingEntityID, this, &EntityTreeRenderer::changingEntityID);
}
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) {
@@ -770,3 +773,20 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
_entityScripts.remove(entityID);
}
+void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID) {
+ // load the entity script if needed...
+ QScriptValue entityScript = loadEntityScript(entityID);
+ if (entityScript.property("preload").isValid()) {
+ QScriptValueList entityArgs = createEntityArgs(entityID);
+ entityScript.property("preload").call(entityScript, entityArgs);
+ }
+}
+
+void EntityTreeRenderer::changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID) {
+ if (_entityScripts.contains(oldEntityID)) {
+ EntityScriptDetails details = _entityScripts[oldEntityID];
+ _entityScripts.remove(oldEntityID);
+ _entityScripts[newEntityID] = details;
+ }
+}
+
diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h
index ff9066dd6d..7c1c81b6c9 100644
--- a/interface/src/entities/EntityTreeRenderer.h
+++ b/interface/src/entities/EntityTreeRenderer.h
@@ -104,6 +104,8 @@ signals:
public slots:
void deletingEntity(const EntityItemID& entityID);
+ void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
+ void checkAndCallPreload(const EntityItemID& entityID);
private:
QList _releasedModels;
diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp
new file mode 100644
index 0000000000..d280d8eecf
--- /dev/null
+++ b/interface/src/scripting/WebWindowClass.cpp
@@ -0,0 +1,68 @@
+//
+// WebWindowClass.cpp
+// interface/src/scripting
+//
+// Created by Ryan Huffman on 11/06/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
+#include
+#include
+#include
+
+#include "WindowScriptingInterface.h"
+#include "WebWindowClass.h"
+
+ScriptEventBridge::ScriptEventBridge(QObject* parent) : QObject(parent) {
+}
+
+void ScriptEventBridge::emitWebEvent(const QString& data) {
+ emit webEventReceived(data);
+}
+
+void ScriptEventBridge::emitScriptEvent(const QString& data) {
+ emit scriptEventReceived(data);
+}
+
+WebWindowClass::WebWindowClass(const QString& url, int width, int height)
+ : QObject(NULL),
+ _window(new QWidget(NULL, Qt::Tool)),
+ _eventBridge(new ScriptEventBridge(this)) {
+
+ QWebView* webView = new QWebView(_window);
+ webView->page()->mainFrame()->addToJavaScriptWindowObject("EventBridge", _eventBridge);
+ webView->setUrl(url);
+ QVBoxLayout* layout = new QVBoxLayout(_window);
+ _window->setLayout(layout);
+ layout->addWidget(webView);
+ layout->setSpacing(0);
+ layout->setContentsMargins(0, 0, 0, 0);
+ _window->setGeometry(0, 0, width, height);
+
+ connect(this, &WebWindowClass::destroyed, _window, &QWidget::deleteLater);
+}
+
+WebWindowClass::~WebWindowClass() {
+}
+
+void WebWindowClass::setVisible(bool visible) {
+ QMetaObject::invokeMethod(_window, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
+}
+
+QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
+ WebWindowClass* retVal;
+ QString file = context->argument(0).toString();
+ QMetaObject::invokeMethod(WindowScriptingInterface::getInstance(), "doCreateWebWindow", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(WebWindowClass*, retVal),
+ Q_ARG(const QString&, file),
+ Q_ARG(int, context->argument(1).toInteger()),
+ Q_ARG(int, context->argument(2).toInteger()));
+
+ connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater);
+
+ return engine->newQObject(retVal);
+}
diff --git a/interface/src/scripting/WebWindowClass.h b/interface/src/scripting/WebWindowClass.h
new file mode 100644
index 0000000000..7b77299774
--- /dev/null
+++ b/interface/src/scripting/WebWindowClass.h
@@ -0,0 +1,51 @@
+//
+// WebWindowClass.h
+// interface/src/scripting
+//
+// Created by Ryan Huffman on 11/06/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_WebWindowClass_h
+#define hifi_WebWindowClass_h
+
+#include
+#include
+
+class ScriptEventBridge : public QObject {
+ Q_OBJECT
+public:
+ ScriptEventBridge(QObject* parent = NULL);
+
+public slots:
+ void emitWebEvent(const QString& data);
+ void emitScriptEvent(const QString& data);
+
+signals:
+ void webEventReceived(const QString& data);
+ void scriptEventReceived(const QString& data);
+
+};
+
+class WebWindowClass : public QObject {
+ Q_OBJECT
+ Q_PROPERTY(QObject* eventBridge READ getEventBridge)
+public:
+ WebWindowClass(const QString& url, int width, int height);
+ ~WebWindowClass();
+
+ static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
+
+public slots:
+ void setVisible(bool visible);
+ ScriptEventBridge* getEventBridge() const { return _eventBridge; }
+
+private:
+ QWidget* _window;
+ ScriptEventBridge* _eventBridge;
+};
+
+#endif
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index ed6f0cf600..8c2066f253 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -34,6 +34,10 @@ WindowScriptingInterface::WindowScriptingInterface() :
{
}
+WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& url, int width, int height) {
+ return new WebWindowClass(url, width, height);
+}
+
QScriptValue WindowScriptingInterface::hasFocus() {
return Application::getInstance()->getGLWidget()->hasFocus();
}
diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h
index 24c21765b5..5529d31efd 100644
--- a/interface/src/scripting/WindowScriptingInterface.h
+++ b/interface/src/scripting/WindowScriptingInterface.h
@@ -15,6 +15,11 @@
#include
#include
#include
+#include
+#include
+#include
+
+#include "WebWindowClass.h"
class WindowScriptingInterface : public QObject {
Q_OBJECT
@@ -72,6 +77,8 @@ private slots:
void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); }
void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); }
+
+ WebWindowClass* doCreateWebWindow(const QString& url, int width, int height);
private:
WindowScriptingInterface();
diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp
new file mode 100644
index 0000000000..c628199fe3
--- /dev/null
+++ b/interface/src/ui/overlays/Grid3DOverlay.cpp
@@ -0,0 +1,118 @@
+//
+// Grid3DOverlay.cpp
+// interface/src/ui/overlays
+//
+// Created by Ryan Huffman on 11/06/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 "Grid3DOverlay.h"
+
+#include "Application.h"
+
+ProgramObject Grid3DOverlay::_gridProgram;
+
+Grid3DOverlay::Grid3DOverlay() : Base3DOverlay(),
+ _minorGridWidth(1.0),
+ _majorGridEvery(5) {
+}
+
+Grid3DOverlay::~Grid3DOverlay() {
+}
+
+void Grid3DOverlay::render(RenderArgs* args) {
+ if (!_visible) {
+ return; // do nothing if we're not visible
+ }
+
+ if (!_gridProgram.isLinked()) {
+ if (!_gridProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/grid.frag")) {
+ qDebug() << "Failed to compile: " + _gridProgram.log();
+ return;
+ }
+ if (!_gridProgram.link()) {
+ qDebug() << "Failed to link: " + _gridProgram.log();
+ return;
+ }
+ }
+
+ // Render code largely taken from MetavoxelEditor::render()
+ glDisable(GL_LIGHTING);
+
+ glDepthMask(GL_FALSE);
+
+ glPushMatrix();
+
+ glm::quat rotation = getRotation();
+
+ glm::vec3 axis = glm::axis(rotation);
+
+ glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
+
+ glLineWidth(1.5f);
+
+ // center the grid around the camera position on the plane
+ glm::vec3 rotated = glm::inverse(rotation) * Application::getInstance()->getCamera()->getPosition();
+ float spacing = _minorGridWidth;
+
+ float alpha = getAlpha();
+ xColor color = getColor();
+ glm::vec3 position = getPosition();
+
+ const int GRID_DIVISIONS = 300;
+ const float MAX_COLOR = 255.0f;
+ float scale = GRID_DIVISIONS * spacing;
+
+ glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
+
+ _gridProgram.bind();
+
+ // Minor grid
+ glPushMatrix();
+ {
+ glTranslatef(_minorGridWidth * (floorf(rotated.x / spacing) - GRID_DIVISIONS / 2),
+ spacing * (floorf(rotated.y / spacing) - GRID_DIVISIONS / 2), position.z);
+
+ glScalef(scale, scale, scale);
+
+ Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS);
+ }
+ glPopMatrix();
+
+ // Major grid
+ glPushMatrix();
+ {
+ glLineWidth(4.0f);
+ spacing *= _majorGridEvery;
+ glTranslatef(spacing * (floorf(rotated.x / spacing) - GRID_DIVISIONS / 2),
+ spacing * (floorf(rotated.y / spacing) - GRID_DIVISIONS / 2), position.z);
+
+ scale *= _majorGridEvery;
+ glScalef(scale, scale, scale);
+
+ Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS);
+ }
+ glPopMatrix();
+
+ _gridProgram.release();
+
+ glPopMatrix();
+
+ glEnable(GL_LIGHTING);
+ glDepthMask(GL_TRUE);
+}
+
+void Grid3DOverlay::setProperties(const QScriptValue& properties) {
+ Base3DOverlay::setProperties(properties);
+
+ if (properties.property("minorGridWidth").isValid()) {
+ _minorGridWidth = properties.property("minorGridWidth").toVariant().toFloat();
+ }
+
+ if (properties.property("majorGridEvery").isValid()) {
+ _majorGridEvery = properties.property("majorGridEvery").toVariant().toInt();
+ }
+}
diff --git a/interface/src/ui/overlays/Grid3DOverlay.h b/interface/src/ui/overlays/Grid3DOverlay.h
new file mode 100644
index 0000000000..b1675f15d7
--- /dev/null
+++ b/interface/src/ui/overlays/Grid3DOverlay.h
@@ -0,0 +1,44 @@
+//
+// Grid3DOverlay.h
+// interface/src/ui/overlays
+//
+// Created by Ryan Huffman on 11/06/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_Grid3DOverlay_h
+#define hifi_Grid3DOverlay_h
+
+// include this before QGLWidget, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
+#include
+
+#include
+#include
+
+#include "Base3DOverlay.h"
+
+#include "renderer/ProgramObject.h"
+
+class Grid3DOverlay : public Base3DOverlay {
+ Q_OBJECT
+
+public:
+ Grid3DOverlay();
+ ~Grid3DOverlay();
+
+ virtual void render(RenderArgs* args);
+ virtual void setProperties(const QScriptValue& properties);
+
+private:
+ float _minorGridWidth;
+ int _majorGridEvery;
+
+ static ProgramObject _gridProgram;
+};
+
+#endif // hifi_Grid3DOverlay_h
diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp
index df7a5fbcea..0192f9c216 100644
--- a/interface/src/ui/overlays/Overlays.cpp
+++ b/interface/src/ui/overlays/Overlays.cpp
@@ -23,6 +23,7 @@
#include "Overlays.h"
#include "Rectangle3DOverlay.h"
#include "Sphere3DOverlay.h"
+#include "Grid3DOverlay.h"
#include "TextOverlay.h"
#include "Text3DOverlay.h"
@@ -155,6 +156,8 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
thisOverlay = new Rectangle3DOverlay();
} else if (type == "line3d") {
thisOverlay = new Line3DOverlay();
+ } else if (type == "grid") {
+ thisOverlay = new Grid3DOverlay();
} else if (type == "localvoxels") {
thisOverlay = new LocalVoxelsOverlay();
} else if (type == "localmodels") {
diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp
index 199bd92030..bc2823e15c 100644
--- a/libraries/entities/src/EntityTree.cpp
+++ b/libraries/entities/src/EntityTree.cpp
@@ -124,6 +124,7 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp
} else {
// check to see if we need to simulate this entity...
EntityItem::SimulationState oldState = existingEntity->getSimulationState();
+ QString entityScriptBefore = existingEntity->getScript();
UpdateEntityOperator theOperator(this, containingElement, existingEntity, properties);
recurseTreeWithOperator(&theOperator);
@@ -131,6 +132,12 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp
EntityItem::SimulationState newState = existingEntity->getSimulationState();
changeEntityState(existingEntity, oldState, newState);
+
+ QString entityScriptAfter = existingEntity->getScript();
+ if (entityScriptBefore != entityScriptAfter) {
+ emitEntityScriptChanging(entityID); // the entity script has changed
+ }
+
}
containingElement = getContainingElement(entityID);
@@ -168,6 +175,7 @@ EntityItem* EntityTree::addEntity(const EntityItemID& entityID, const EntityItem
if (result) {
// this does the actual adding of the entity
addEntityItem(result);
+ emitAddingEntity(entityID);
}
return result;
}
@@ -184,6 +192,14 @@ void EntityTree::trackDeletedEntity(const EntityItemID& entityID) {
}
}
+void EntityTree::emitAddingEntity(const EntityItemID& entityItemID) {
+ emit addingEntity(entityItemID);
+}
+
+void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID) {
+ emit entityScriptChanging(entityItemID);
+}
+
void EntityTree::deleteEntity(const EntityItemID& entityID) {
emit deletingEntity(entityID);
@@ -290,6 +306,7 @@ void EntityTree::handleAddEntityResponse(const QByteArray& packet) {
EntityItemID creatorTokenVersion = searchEntityID.convertToCreatorTokenVersion();
EntityItemID knownIDVersion = searchEntityID.convertToKnownIDVersion();
+
// First look for and find the "viewed version" of this entity... it's possible we got
// the known ID version sent to us between us creating our local version, and getting this
// remapping message. If this happened, we actually want to find and delete that version of
@@ -310,6 +327,10 @@ void EntityTree::handleAddEntityResponse(const QByteArray& packet) {
creatorTokenContainingElement->updateEntityItemID(creatorTokenVersion, knownIDVersion);
setContainingElement(creatorTokenVersion, NULL);
setContainingElement(knownIDVersion, creatorTokenContainingElement);
+
+ // because the ID of the entity is switching, we need to emit these signals for any
+ // listeners who care about the changing of IDs
+ emit changingEntityID(creatorTokenVersion, knownIDVersion);
}
}
unlock();
@@ -981,7 +1002,6 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons
return processedBytes;
}
-
EntityTreeElement* EntityTree::getContainingElement(const EntityItemID& entityItemID) /*const*/ {
// TODO: do we need to make this thread safe? Or is it acceptable as is
if (_entityToElementMap.contains(entityItemID)) {
diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h
index 8d1acc0d01..6fe2d256c2 100644
--- a/libraries/entities/src/EntityTree.h
+++ b/libraries/entities/src/EntityTree.h
@@ -140,10 +140,16 @@ public:
void trackDeletedEntity(const EntityItemID& entityID);
+ void emitAddingEntity(const EntityItemID& entityItemID);
+ void emitEntityScriptChanging(const EntityItemID& entityItemID);
+
QList& getMovingEntities() { return _movingEntities; }
signals:
void deletingEntity(const EntityItemID& entityID);
+ void addingEntity(const EntityItemID& entityID);
+ void entityScriptChanging(const EntityItemID& entityItemID);
+ void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
private:
diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp
index 079fb1bba7..1dea2bcd85 100644
--- a/libraries/entities/src/EntityTreeElement.cpp
+++ b/libraries/entities/src/EntityTreeElement.cpp
@@ -726,7 +726,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead);
entityItem = _myTree->findEntityByEntityItemID(entityItemID);
}
-
+
// If the item already exists in our tree, we want do the following...
// 1) allow the existing item to read from the databuffer
// 2) check to see if after reading the item, the containing element is still correct, fix it if needed
@@ -734,10 +734,13 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
// TODO: Do we need to also do this?
// 3) remember the old cube for the entity so we can mark it as dirty
if (entityItem) {
+ QString entityScriptBefore = entityItem->getScript();
bool bestFitBefore = bestFitEntityBounds(entityItem);
EntityTreeElement* currentContainingElement = _myTree->getContainingElement(entityItemID);
EntityItem::SimulationState oldState = entityItem->getSimulationState();
+
bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
+
EntityItem::SimulationState newState = entityItem->getSimulationState();
if (oldState != newState) {
_myTree->changeEntityState(entityItem, oldState, newState);
@@ -755,6 +758,12 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
}
}
}
+
+ QString entityScriptAfter = entityItem->getScript();
+ if (entityScriptBefore != entityScriptAfter) {
+ _myTree->emitEntityScriptChanging(entityItemID); // the entity script has changed
+ }
+
} else {
entityItem = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args);
if (entityItem) {
@@ -762,6 +771,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
addEntityItem(entityItem); // add this new entity to this elements entities
entityItemID = entityItem->getEntityItemID();
_myTree->setContainingElement(entityItemID, this);
+ _myTree->emitAddingEntity(entityItemID); // we just added an entity
EntityItem::SimulationState newState = entityItem->getSimulationState();
_myTree->changeEntityState(entityItem, EntityItem::Static, newState);
}
diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 7445822f55..40602645f1 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -322,6 +322,11 @@ QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* ob
return QScriptValue::NullValue;
}
+void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) {
+ QScriptValue scriptFun = newFunction(fun, numArguments);
+ globalObject().setProperty(name, scriptFun);
+}
+
void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
QScriptEngine::FunctionSignature setter, QScriptValue object) {
QScriptValue setterFunction = newFunction(setter, 1);
@@ -636,7 +641,7 @@ void ScriptEngine::stopTimer(QTimer *timer) {
}
}
-QUrl ScriptEngine::resolveInclude(const QString& include) const {
+QUrl ScriptEngine::resolvePath(const QString& include) const {
// first lets check to see if it's already a full URL
QUrl url(include);
if (!url.scheme().isEmpty()) {
@@ -662,7 +667,7 @@ void ScriptEngine::print(const QString& message) {
}
void ScriptEngine::include(const QString& includeFile) {
- QUrl url = resolveInclude(includeFile);
+ QUrl url = resolvePath(includeFile);
QString includeContents;
if (url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "ftp") {
@@ -700,7 +705,7 @@ void ScriptEngine::include(const QString& includeFile) {
}
void ScriptEngine::load(const QString& loadFile) {
- QUrl url = resolveInclude(loadFile);
+ QUrl url = resolvePath(loadFile);
emit loadScript(url.toString(), false);
}
diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h
index f96b4655a9..bb279b8887 100644
--- a/libraries/script-engine/src/ScriptEngine.h
+++ b/libraries/script-engine/src/ScriptEngine.h
@@ -65,6 +65,7 @@ public:
QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
QScriptEngine::FunctionSignature setter, QScriptValue object = QScriptValue::NullValue);
+ void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1);
Q_INVOKABLE void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
@@ -103,6 +104,7 @@ public slots:
void include(const QString& includeFile);
void load(const QString& loadfile);
void print(const QString& message);
+ QUrl resolvePath(const QString& path) const;
void nodeKilled(SharedNodePointer node);
@@ -131,7 +133,6 @@ protected:
int _numAvatarSoundSentBytes;
private:
- QUrl resolveInclude(const QString& include) const;
void sendAvatarIdentityPacket();
void sendAvatarBillboardPacket();