diff --git a/examples/edit.js b/examples/edit.js
index 7a16030afc..74551384c9 100644
--- a/examples/edit.js
+++ b/examples/edit.js
@@ -1617,6 +1617,11 @@ PropertiesTool = function(opts) {
pushCommandForSelections();
selectionManager._update();
}
+ } else if (data.action == "previewCamera") {
+ if (selectionManager.hasSelection()) {
+ Camera.mode = "entity";
+ Camera.cameraEntity = selectionManager.selections[0];
+ }
} else if (data.action == "rescaleDimensions") {
var multiplier = data.percentage / 100;
if (selectionManager.hasSelection()) {
diff --git a/examples/example/securityCamera.js b/examples/example/securityCamera.js
new file mode 100644
index 0000000000..6f5ca549cd
--- /dev/null
+++ b/examples/example/securityCamera.js
@@ -0,0 +1,57 @@
+//
+// securityCamera.js
+// examples/example
+//
+// Created by Thijs Wenker on November 4, 2015
+// Copyright 2015 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
+//
+
+const CAMERA_OFFSET = {x: 0, y: 4, z: -14};
+const LOOKAT_START_OFFSET = {x: -10, y: 0, z: 14};
+const LOOKAT_END_OFFSET = {x: 10, y: 0, z: 14};
+const TINY_VALUE = 0.001;
+
+var lookatTargets = [Vec3.sum(MyAvatar.position, LOOKAT_START_OFFSET), Vec3.sum(MyAvatar.position, LOOKAT_END_OFFSET)];
+var currentTarget = 0;
+
+var forward = true;
+
+var oldCameraMode = Camera.mode;
+
+var cameraLookAt = function(cameraPos, lookAtPos) {
+ var lookAtRaw = Quat.lookAt(cameraPos, lookAtPos, Vec3.UP);
+ lookAtRaw.w = -lookAtRaw.w;
+ return lookAtRaw;
+};
+
+cameraEntity = Entities.addEntity({
+ type: "Box",
+ visible: false,
+ position: Vec3.sum(MyAvatar.position, CAMERA_OFFSET)
+});
+
+Camera.mode = "entity";
+Camera.cameraEntity = cameraEntity;
+
+Script.update.connect(function(deltaTime) {
+ var cameraProperties = Entities.getEntityProperties(cameraEntity, ["position", "rotation"]);
+ var targetOrientation = cameraLookAt(cameraProperties.position, lookatTargets[currentTarget]);
+ if (Math.abs(targetOrientation.x - cameraProperties.rotation.x) < TINY_VALUE &&
+ Math.abs(targetOrientation.y - cameraProperties.rotation.y) < TINY_VALUE &&
+ Math.abs(targetOrientation.z - cameraProperties.rotation.z) < TINY_VALUE &&
+ Math.abs(targetOrientation.w - cameraProperties.rotation.w) < TINY_VALUE) {
+
+ currentTarget = (currentTarget + 1) % lookatTargets.length;
+ return;
+ }
+ Entities.editEntity(cameraEntity, {rotation: Quat.mix(cameraProperties.rotation, targetOrientation, deltaTime / 3)});
+});
+
+Script.scriptEnding.connect(function() {
+ Entities.deleteEntity(cameraEntity);
+ Camera.mode = oldCameraMode;
+ Camera.cameraEntity = null;
+});
diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html
index 412b413b2b..bc6a6920d1 100644
--- a/examples/html/entityProperties.html
+++ b/examples/html/entityProperties.html
@@ -382,7 +382,7 @@
var elHyperlinkHref = document.getElementById("property-hyperlink-href");
var elHyperlinkDescription = document.getElementById("property-hyperlink-description");
-
+ var elPreviewCameraButton = document.getElementById("preview-camera-button");
if (window.EventBridge !== undefined) {
EventBridge.scriptEventReceived.connect(function(data) {
@@ -931,6 +931,12 @@
action: "centerAtmosphereToZone",
}));
});
+ elPreviewCameraButton.addEventListener("click", function() {
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: "action",
+ action: "previewCamera"
+ }));
+ });
window.onblur = function() {
// Fake a change event
@@ -1032,7 +1038,7 @@
diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js
index 4fd24756e0..39ffbed629 100644
--- a/examples/libraries/entityPropertyDialogBox.js
+++ b/examples/libraries/entityPropertyDialogBox.js
@@ -204,7 +204,7 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Collisions Will Move:", type: "checkbox", value: properties.collisionsWillMove });
index++;
array.push({ label: "Collision Sound URL:", value: properties.collisionSoundURL });
- index++;
+ index++;
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
index++;
@@ -260,6 +260,10 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Cutoff (in degrees):", value: properties.cutoff });
index++;
}
+
+ array.push({ label: "", type: "inlineButton", buttonLabel: "Preview Camera", name: "previewCamera" });
+ index++;
+
array.push({ button: "Cancel" });
index++;
@@ -268,6 +272,11 @@ EntityPropertyDialogBox = (function () {
};
Window.inlineButtonClicked.connect(function (name) {
+ if (name == "previewCamera") {
+ Camera.mode = "entity";
+ Camera.cameraEntity = propertiesForEditedEntity.id;
+ }
+
if (name == "resetDimensions") {
Window.reloadNonBlockingForm([
{ value: propertiesForEditedEntity.naturalDimensions.x.toFixed(decimals), oldIndex: dimensionX },
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index c77bb9a114..ba000725df 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1226,6 +1226,19 @@ void Application::paintGL() {
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
}
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
+ } else if (_myCamera.getMode() == CAMERA_MODE_ENTITY) {
+ EntityItemPointer cameraEntity = _myCamera.getCameraEntityPointer();
+ if (cameraEntity != nullptr) {
+ if (isHMDMode()) {
+ glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());
+ _myCamera.setRotation(cameraEntity->getRotation() * hmdRotation);
+ glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix());
+ _myCamera.setPosition(cameraEntity->getPosition() + (hmdRotation * hmdOffset));
+ } else {
+ _myCamera.setRotation(cameraEntity->getRotation());
+ _myCamera.setPosition(cameraEntity->getPosition());
+ }
+ }
}
// Update camera position
if (!isHMDMode()) {
@@ -2676,8 +2689,8 @@ void Application::cycleCamera() {
menu->setIsOptionChecked(MenuOption::ThirdPerson, false);
menu->setIsOptionChecked(MenuOption::FullscreenMirror, true);
- } else if (menu->isOptionChecked(MenuOption::IndependentMode)) {
- // do nothing if in independe mode
+ } else if (menu->isOptionChecked(MenuOption::IndependentMode) || menu->isOptionChecked(MenuOption::CameraEntityMode)) {
+ // do nothing if in independent or camera entity modes
return;
}
cameraMenuChanged(); // handle the menu change
@@ -2704,6 +2717,10 @@ void Application::cameraMenuChanged() {
if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
_myCamera.setMode(CAMERA_MODE_INDEPENDENT);
}
+ } else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) {
+ if (_myCamera.getMode() != CAMERA_MODE_ENTITY) {
+ _myCamera.setMode(CAMERA_MODE_ENTITY);
+ }
}
}
diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp
index 17c745bdba..53a3500bff 100644
--- a/interface/src/Camera.cpp
+++ b/interface/src/Camera.cpp
@@ -28,6 +28,8 @@ CameraMode stringToMode(const QString& mode) {
return CAMERA_MODE_MIRROR;
} else if (mode == "independent") {
return CAMERA_MODE_INDEPENDENT;
+ } else if (mode == "entity") {
+ return CAMERA_MODE_ENTITY;
}
return CAMERA_MODE_NULL;
}
@@ -41,6 +43,8 @@ QString modeToString(CameraMode mode) {
return "mirror";
} else if (mode == CAMERA_MODE_INDEPENDENT) {
return "independent";
+ } else if (mode == CAMERA_MODE_ENTITY) {
+ return "entity";
}
return "unknown";
}
@@ -94,6 +98,17 @@ void Camera::setMode(CameraMode mode) {
emit modeUpdated(modeToString(mode));
}
+QUuid Camera::getCameraEntity() const {
+ if (_cameraEntity != nullptr) {
+ return _cameraEntity->getID();
+ }
+ return QUuid();
+};
+
+void Camera::setCameraEntity(QUuid entityID) {
+ _cameraEntity = qApp->getEntities()->getTree()->findEntityByID(entityID);
+}
+
void Camera::setProjection(const glm::mat4& projection) {
_projection = projection;
}
@@ -118,6 +133,9 @@ void Camera::setModeString(const QString& mode) {
case CAMERA_MODE_INDEPENDENT:
Menu::getInstance()->setIsOptionChecked(MenuOption::IndependentMode, true);
break;
+ case CAMERA_MODE_ENTITY:
+ Menu::getInstance()->setIsOptionChecked(MenuOption::CameraEntityMode, true);
+ break;
default:
break;
}
diff --git a/interface/src/Camera.h b/interface/src/Camera.h
index 7f7515cf5f..017bd742a4 100644
--- a/interface/src/Camera.h
+++ b/interface/src/Camera.h
@@ -24,6 +24,7 @@ enum CameraMode
CAMERA_MODE_FIRST_PERSON,
CAMERA_MODE_MIRROR,
CAMERA_MODE_INDEPENDENT,
+ CAMERA_MODE_ENTITY,
NUM_CAMERA_MODES
};
@@ -36,6 +37,7 @@ class Camera : public QObject {
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition)
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation)
Q_PROPERTY(QString mode READ getModeString WRITE setModeString)
+ Q_PROPERTY(QUuid cameraEntity READ getCameraEntity WRITE setCameraEntity)
public:
Camera();
@@ -49,6 +51,8 @@ public:
void loadViewFrustum(ViewFrustum& frustum) const;
ViewFrustum toViewFrustum() const;
+ EntityItemPointer getCameraEntityPointer() const { return _cameraEntity; }
+
public slots:
QString getModeString() const;
void setModeString(const QString& mode);
@@ -68,6 +72,9 @@ public slots:
const glm::mat4& getProjection() const { return _projection; }
void setProjection(const glm::mat4& projection);
+ QUuid getCameraEntity() const;
+ void setCameraEntity(QUuid entityID);
+
PickRay computePickRay(float x, float y);
// These only work on independent cameras
@@ -97,6 +104,7 @@ private:
glm::quat _rotation;
bool _isKeepLookingAt{ false };
glm::vec3 _lookingAt;
+ EntityItemPointer _cameraEntity;
};
#endif // hifi_Camera_h
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 24033325f6..97e8368da0 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -268,6 +268,9 @@ Menu::Menu() {
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu,
MenuOption::IndependentMode, 0,
false, qApp, SLOT(cameraMenuChanged())));
+ cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu,
+ MenuOption::CameraEntityMode, 0,
+ false, qApp, SLOT(cameraMenuChanged())));
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu,
MenuOption::FullscreenMirror, 0, // QML Qt::Key_H,
false, qApp, SLOT(cameraMenuChanged())));
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index dfa2cfa41b..7546ed59f2 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -155,6 +155,7 @@ namespace MenuOption {
const QString Bookmarks = "Bookmarks";
const QString CachesSize = "RAM Caches Size";
const QString CalibrateCamera = "Calibrate Camera";
+ const QString CameraEntityMode = "Entity Mode";
const QString CenterPlayerInView = "Center Player In View";
const QString Chat = "Chat...";
const QString Collisions = "Collisions";