diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 6487254e26..fe7b6c2555 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -7278,9 +7278,9 @@ void Application::takeSecondaryCameraSnapshot(const QString& filename) {
});
}
-void Application::takeSecondaryCamera360Snapshot(const QString& filename) {
- postLambdaEvent([filename, this] {
- Snapshot::save360Snapshot(filename);
+void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const QString& filename) {
+ postLambdaEvent([filename, cameraPosition, this] {
+ Snapshot::save360Snapshot(cameraPosition, filename);
});
}
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 3c0babdc5d..2dddf3e6be 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -273,7 +273,7 @@ public:
void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString());
void takeSecondaryCameraSnapshot(const QString& filename = QString());
- void takeSecondaryCamera360Snapshot(const QString& filename = QString());
+ void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const QString& filename = QString());
void shareSnapshot(const QString& filename, const QUrl& href = QUrl(""));
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index 2d94e3e02e..f5416bf4c6 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -431,8 +431,8 @@ void WindowScriptingInterface::takeSecondaryCameraSnapshot(const QString& filena
qApp->takeSecondaryCameraSnapshot(filename);
}
-void WindowScriptingInterface::takeSecondaryCamera360Snapshot(const QString& filename) {
- qApp->takeSecondaryCamera360Snapshot(filename);
+void WindowScriptingInterface::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const QString& filename) {
+ qApp->takeSecondaryCamera360Snapshot(cameraPosition, filename);
}
void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) {
diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h
index d3105acd72..23df0a47f5 100644
--- a/interface/src/scripting/WindowScriptingInterface.h
+++ b/interface/src/scripting/WindowScriptingInterface.h
@@ -376,7 +376,7 @@ public slots:
*
* var filename = QString();
*/
- void takeSecondaryCamera360Snapshot(const QString& filename = QString());
+ void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const QString& filename = QString());
/**jsdoc
* Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that
@@ -586,6 +586,16 @@ signals:
*/
void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify);
+ /**jsdoc
+ * Triggered when a still equirectangular snapshot has been taken by calling {@link Window.take360Snapshot|take360Snapshot}
+ * @function Window.equirectangularSnapshotTaken
+ * @param {string} pathStillSnapshot - The path and name of the snapshot image file.
+ * @param {boolean} notify - The value of the notify
parameter that {@link Window.take360Snapshot|take360Snapshot}
+ * was called with.
+ * @returns {Signal}
+ */
+ void equirectangularSnapshotTaken(const QString& pathEquirectangularSnapshot, bool notify);
+
/**jsdoc
* Triggered when a snapshot submitted via {@link Window.shareSnapshot|shareSnapshot} is ready for sharing. The snapshot
* may then be shared via the {@link Account.metaverseServerURL} Web API.
diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index 5feca7ee70..852869b98e 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include
#include
@@ -91,57 +92,66 @@ QString Snapshot::saveSnapshot(QImage image, const QString& filename) {
return snapshotPath;
}
-void Snapshot::save360Snapshot(const QString& filename) {
+
+qint16 Snapshot::snapshotIndex = 0;
+QVariant Snapshot::oldAttachedEntityId = 0;
+QVariant Snapshot::oldOrientation = 0;
+QVariant Snapshot::oldvFoV = 0;
+QVariant Snapshot::oldNearClipPlaneDistance = 0;
+QVariant Snapshot::oldFarClipPlaneDistance = 0;
+
+QImage Snapshot::downImage;
+QImage Snapshot::frontImage;
+QImage Snapshot::leftImage;
+QImage Snapshot::backImage;
+QImage Snapshot::rightImage;
+QImage Snapshot::upImage;
+
+void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const QString& filename) {
SecondaryCameraJobConfig* secondaryCameraRenderConfig = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
// Save initial values of secondary camera render config
- auto oldAttachedEntityId = secondaryCameraRenderConfig->property("attachedEntityId");
- auto oldOrientation = secondaryCameraRenderConfig->property("orientation");
- auto oldvFoV = secondaryCameraRenderConfig->property("vFoV");
- auto oldNearClipPlaneDistance = secondaryCameraRenderConfig->property("nearClipPlaneDistance");
- auto oldFarClipPlaneDistance = secondaryCameraRenderConfig->property("farClipPlaneDistance");
+ oldAttachedEntityId = secondaryCameraRenderConfig->property("attachedEntityId");
+ oldOrientation = secondaryCameraRenderConfig->property("orientation");
+ oldvFoV = secondaryCameraRenderConfig->property("vFoV");
+ oldNearClipPlaneDistance = secondaryCameraRenderConfig->property("nearClipPlaneDistance");
+ oldFarClipPlaneDistance = secondaryCameraRenderConfig->property("farClipPlaneDistance");
// Initialize some secondary camera render config options for 360 snapshot capture
secondaryCameraRenderConfig->resetSizeSpectatorCamera(2048, 2048);
secondaryCameraRenderConfig->setProperty("attachedEntityId", "");
+ secondaryCameraRenderConfig->setPosition(cameraPosition);
secondaryCameraRenderConfig->setProperty("vFoV", 90.0f);
- secondaryCameraRenderConfig->setProperty("nearClipPlaneDistance", 0.5f);
- secondaryCameraRenderConfig->setProperty("farClipPlaneDistance", 1000.0f);
+ secondaryCameraRenderConfig->setProperty("nearClipPlaneDistance", 0.3f);
+ secondaryCameraRenderConfig->setProperty("farClipPlaneDistance", 5000.0f);
secondaryCameraRenderConfig->setOrientation(glm::quat(glm::radians(glm::vec3(-90.0f, 0.0f, 0.0f))));
- qint16 snapshotIndex = 0;
+ snapshotIndex = 0;
QTimer* snapshotTimer = new QTimer();
snapshotTimer->setSingleShot(false);
- snapshotTimer->setInterval(200);
+ snapshotTimer->setInterval(250);
connect(snapshotTimer, &QTimer::timeout, [&] {
SecondaryCameraJobConfig* config = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
- qDebug() << "ZRF HERE" << snapshotIndex;
if (snapshotIndex == 0) {
- QImage downImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
- Snapshot::saveSnapshot(downImage, "down");
+ downImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
config->setOrientation(glm::quat(glm::radians(glm::vec3(0.0f, 0.0f, 0.0f))));
} else if (snapshotIndex == 1) {
- QImage frontImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
- Snapshot::saveSnapshot(frontImage, "front");
+ frontImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
config->setOrientation(glm::quat(glm::radians(glm::vec3(0.0f, 90.0f, 0.0f))));
} else if (snapshotIndex == 2) {
- QImage leftImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
- Snapshot::saveSnapshot(leftImage, "left");
+ leftImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
config->setOrientation(glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f))));
} else if (snapshotIndex == 3) {
- QImage backImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
- Snapshot::saveSnapshot(backImage, "back");
+ backImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
config->setOrientation(glm::quat(glm::radians(glm::vec3(0.0f, 270.0f, 0.0f))));
} else if (snapshotIndex == 4) {
- QImage rightImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
- Snapshot::saveSnapshot(rightImage, "right");
+ rightImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
config->setOrientation(glm::quat(glm::radians(glm::vec3(90.0f, 0.0f, 0.0f))));
} else if (snapshotIndex == 5) {
- QImage upImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
- Snapshot::saveSnapshot(upImage, "up");
- } else if (snapshotIndex == 6) {
+ upImage = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
+ } else {
// Reset secondary camera render config
config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height());
config->setProperty("attachedEntityId", oldAttachedEntityId);
@@ -149,25 +159,97 @@ void Snapshot::save360Snapshot(const QString& filename) {
config->setProperty("nearClipPlaneDistance", oldNearClipPlaneDistance);
config->setProperty("farClipPlaneDistance", oldFarClipPlaneDistance);
- QFile* snapshotFile = savedFileForSnapshot(qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), false, filename);
-
- // we don't need the snapshot file, so close it, grab its filename and delete it
- snapshotFile->close();
-
- QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath();
-
- delete snapshotFile;
+ // Process six QImages
+ QtConcurrent::run(convertToEquirectangular);
snapshotTimer->stop();
snapshotTimer->deleteLater();
-
- emit DependencyManager::get()->stillSnapshotTaken(snapshotPath, true);
}
snapshotIndex++;
});
snapshotTimer->start();
}
+void Snapshot::convertToEquirectangular() {
+ float outputImageWidth = 8192.0f;
+ float outputImageHeight = 4096.0f;
+ QImage outputImage(outputImageWidth, outputImageHeight, QImage::Format_RGB32);
+ outputImage.fill(0);
+ QRgb sourceColorValue;
+ float phi, theta;
+ int cubeFaceWidth = 2048.0f;
+ int cubeFaceHeight = 2048.0f;
+
+ for (int j = 0; j < outputImageHeight; j++) {
+ theta = (1.0f - ((float)j / outputImageHeight)) * PI;
+
+ for (int i = 0; i < outputImageWidth; i++) {
+ phi = ((float)i / outputImageWidth) * 2.0f * PI;
+
+ float x, y, z;
+ x = glm::sin(phi) * glm::sin(theta) * -1.0f;
+ y = glm::cos(theta);
+ z = glm::cos(phi) * glm::sin(theta) * -1.0f;
+
+ float xa, ya, za;
+ float a;
+
+ a = std::max(std::max(std::abs(x), std::abs(y)), std::abs(z));
+
+ xa = x / a;
+ ya = y / a;
+ za = z / a;
+
+ // Pixel in the source images
+ int xPixel, yPixel;
+ QImage sourceImage;
+
+ if (xa == 1) {
+ // Right image
+ xPixel = (int)((((za + 1.0f) / 2.0f) - 1.0f) * cubeFaceWidth);
+ yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = rightImage;
+ } else if (xa == -1) {
+ // Left image
+ xPixel = (int)((((za + 1.0f) / 2.0f)) * cubeFaceWidth);
+ yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = leftImage;
+ } else if (ya == 1) {
+ // Down image
+ xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
+ yPixel = (int)((((za + 1.0f) / 2.0f) - 1.0f) * cubeFaceHeight);
+ sourceImage = downImage;
+ } else if (ya == -1) {
+ // Up image
+ xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
+ yPixel = (int)((((za + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = upImage;
+ } else if (za == 1) {
+ // Front image
+ xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
+ yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = frontImage;
+ } else if (za == -1) {
+ // Back image
+ xPixel = (int)((((xa + 1.0f) / 2.0f) - 1.0f) * cubeFaceWidth);
+ yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
+ sourceImage = backImage;
+ } else {
+ qDebug() << "Unknown face encountered when processing 360 Snapshot";
+ xPixel = 0;
+ yPixel = 0;
+ }
+
+ xPixel = std::min(std::abs(xPixel), 2047);
+ yPixel = std::min(std::abs(yPixel), 2047);
+
+ sourceColorValue = sourceImage.pixel(xPixel, yPixel);
+ outputImage.setPixel(i, j, sourceColorValue);
+ }
+ }
+
+ emit DependencyManager::get()->equirectangularSnapshotTaken(saveSnapshot(outputImage, QString()), true);
+}
QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) {
// return whatever we get back from saved file for snapshot
diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h
index 834daea0dc..0e8f3f83bd 100644
--- a/interface/src/ui/Snapshot.h
+++ b/interface/src/ui/Snapshot.h
@@ -38,7 +38,7 @@ class Snapshot : public QObject, public Dependency {
SINGLETON_DEPENDENCY
public:
static QString saveSnapshot(QImage image, const QString& filename);
- static void save360Snapshot(const QString& filename);
+ static void save360Snapshot(const glm::vec3& cameraPosition, const QString& filename);
static QTemporaryFile* saveTempSnapshot(QImage image);
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
@@ -53,6 +53,20 @@ public slots:
Q_INVOKABLE void setSnapshotsLocation(const QString& location);
private:
static QFile* savedFileForSnapshot(QImage & image, bool isTemporary, const QString& userSelectedFilename = QString());
+
+ static qint16 snapshotIndex;
+ static QVariant oldAttachedEntityId;
+ static QVariant oldOrientation;
+ static QVariant oldvFoV;
+ static QVariant oldNearClipPlaneDistance;
+ static QVariant oldFarClipPlaneDistance;
+ static QImage downImage;
+ static QImage frontImage;
+ static QImage leftImage;
+ static QImage backImage;
+ static QImage rightImage;
+ static QImage upImage;
+ static void convertToEquirectangular();
};
#endif // hifi_Snapshot_h
diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js
index ba37f6ee4e..3b3ac4dfe6 100644
--- a/scripts/system/notifications.js
+++ b/scripts/system/notifications.js
@@ -672,6 +672,7 @@
Menu.menuItemEvent.connect(menuItemEvent);
Window.domainConnectionRefused.connect(onDomainConnectionRefused);
Window.stillSnapshotTaken.connect(onSnapshotTaken);
+ Window.equirectangularSnapshotTaken.connect(onSnapshotTaken);
Window.processingGifStarted.connect(processingGif);
Window.connectionAdded.connect(connectionAdded);
Window.connectionError.connect(connectionError);
diff --git a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml
index c4dc2c29f6..c85f990a6d 100644
--- a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml
+++ b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml
@@ -239,7 +239,7 @@ Rectangle {
// Instructions (visible when display texture isn't set)
HifiStylesUit.FiraSansRegular {
id: spectatorCameraInstructions;
- text: "Turn on Spectator Camera for a preview\nof what your monitor shows.";
+ text: "Turn on Spectator Camera for a preview\nof " + (HMD.active ? "what your monitor shows." : "the camera's view.");
size: 16;
color: hifi.colors.lightGrayText;
visible: !cameraToggleButton.camIsOn;
@@ -252,7 +252,7 @@ Rectangle {
Hifi.ResourceImageItem {
id: spectatorCameraPreview;
visible: cameraToggleButton.camIsOn;
- url: monitorShowsSwitch.checked ? "resource://spectatorCameraFrame" : "resource://hmdPreviewFrame";
+ url: monitorShowsSwitch.checked || !HMD.active ? "resource://spectatorCameraFrame" : "resource://hmdPreviewFrame";
ready: cameraToggleButton.camIsOn;
mirrorVertically: true;
anchors.fill: parent;
@@ -267,6 +267,7 @@ Rectangle {
// "Monitor Shows" Switch Label Glyph
HifiStylesUit.HiFiGlyphs {
id: monitorShowsSwitchLabelGlyph;
+ visible: HMD.active;
text: hifi.glyphs.screen;
size: 32;
color: hifi.colors.blueHighlight;
@@ -277,6 +278,7 @@ Rectangle {
// "Monitor Shows" Switch Label
HifiStylesUit.RalewayLight {
id: monitorShowsSwitchLabel;
+ visible: HMD.active;
text: "MONITOR SHOWS:";
anchors.top: spectatorCameraImageContainer.bottom;
anchors.topMargin: 20;
@@ -291,6 +293,7 @@ Rectangle {
// "Monitor Shows" Switch
HifiControlsUit.Switch {
id: monitorShowsSwitch;
+ visible: HMD.active;
height: 30;
anchors.left: parent.left;
anchors.right: parent.right;
@@ -307,6 +310,7 @@ Rectangle {
// "Switch View From Controller" Checkbox
HifiControlsUit.CheckBox {
id: switchViewFromControllerCheckBox;
+ visible: HMD.active;
colorScheme: hifi.colorSchemes.dark;
anchors.left: parent.left;
anchors.top: monitorShowsSwitch.bottom;
@@ -335,15 +339,18 @@ Rectangle {
HifiControlsUit.Button {
id: take360SnapshotButton;
- text: "Take 360 Snapshot"
+ text: "Take 360 Snapshot";
+ enabled: cameraToggleButton.camIsOn;
colorScheme: hifi.colorSchemes.dark;
color: hifi.buttons.blue;
- anchors.top: takeSnapshotFromControllerCheckBox.visible ? takeSnapshotFromControllerCheckBox.bottom : switchViewFromControllerCheckBox.bottom;
+ anchors.top: takeSnapshotFromControllerCheckBox.visible ? takeSnapshotFromControllerCheckBox.bottom : spectatorCameraImageContainer.bottom;
anchors.topMargin: 8;
anchors.left: parent.left;
anchors.right: parent.right;
height: 40;
onClicked: {
+ take360SnapshotButton.enabled = false;
+ take360SnapshotButton.text = "360 SNAPSHOT PROCESSING...";
sendToScript({method: 'takeSecondaryCamera360Snapshot'});
}
}
@@ -400,6 +407,10 @@ Rectangle {
spectatorCameraPreview.url = message.url;
spectatorCameraPreview.visible = message.setting;
break;
+ case 'enable360SnapshotButton':
+ take360SnapshotButton.text = "Take 360 Snapshot";
+ take360SnapshotButton.enabled = cameraToggleButton.camIsOn;
+ break;
default:
console.log('Unrecognized message from spectatorCamera.js:', JSON.stringify(message));
}
diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js
index c95e112d49..66219b65f7 100644
--- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js
+++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js
@@ -68,8 +68,8 @@
spectatorCameraConfig.resetSizeSpectatorCamera(Window.innerWidth, Window.innerHeight);
cameraRotation = Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollDegrees(15, -155, 0)), cameraPosition = inFrontOf(0.85, Vec3.sum(MyAvatar.position, { x: 0, y: 0.28, z: 0 }));
camera = Entities.addEntity({
- "angularDamping": 0.8,
- "damping": 0.8,
+ "angularDamping": 0.95,
+ "damping": 0.95,
"collidesWith": "static,dynamic,kinematic,",
"collisionMask": 7,
"dynamic": true,
@@ -193,6 +193,7 @@
tablet.screenChanged.connect(onTabletScreenChanged);
Window.domainChanged.connect(onDomainChanged);
Window.geometryChanged.connect(resizeViewFinderOverlay);
+ Window.equirectangularSnapshotTaken.connect(onEquirectangularSnapshotTaken);
Controller.keyPressEvent.connect(keyPressEvent);
HMD.displayModeChanged.connect(onHMDChanged);
viewFinderOverlay = false;
@@ -403,6 +404,11 @@
Window.takeSecondaryCameraSnapshot();
}
}
+ function onEquirectangularSnapshotTaken() {
+ sendToQml({
+ method: 'enable360SnapshotButton'
+ });
+ }
function maybeTake360Snapshot() {
if (camera) {
Audio.playSound(SNAPSHOT_SOUND, {
@@ -410,7 +416,7 @@
localOnly: true,
volume: 1.0
});
- Window.takeSecondaryCamera360Snapshot();
+ Window.takeSecondaryCamera360Snapshot(Entities.getEntityProperties(camera, ["positon"]).position);
}
}
function registerTakeSnapshotControllerMapping() {
@@ -583,6 +589,7 @@
spectatorCameraOff();
Window.domainChanged.disconnect(onDomainChanged);
Window.geometryChanged.disconnect(resizeViewFinderOverlay);
+ Window.equirectangularSnapshotTaken.disconnect(onEquirectangularSnapshotTaken);
addOrRemoveButton(true);
if (tablet) {
tablet.screenChanged.disconnect(onTabletScreenChanged);