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 @@
- +
@@ -1044,6 +1050,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";