Merge pull request #13141 from zfox23/spectatorCameraImprovements

Spectator Camera V2
This commit is contained in:
Zach Fox 2018-05-16 13:38:07 -07:00 committed by GitHub
commit 585957409d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1168 additions and 106 deletions

View file

@ -27,6 +27,9 @@ Original.CheckBox {
property bool wrap: true;
readonly property int checkSize: Math.max(boxSize - 8, 10)
readonly property int checkRadius: 2
property string labelFontFamily: "Raleway"
property int labelFontSize: 14;
property int labelFontWeight: Font.DemiBold;
focusPolicy: Qt.ClickFocus
hoverEnabled: true
@ -105,6 +108,9 @@ Original.CheckBox {
contentItem: Label {
text: checkBox.text
color: checkBox.color
font.family: checkBox.labelFontFamily;
font.pixelSize: checkBox.labelFontSize;
font.weight: checkBox.labelFontWeight;
x: 2
verticalAlignment: Text.AlignVCenter
wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap

View file

@ -4196,7 +4196,7 @@ bool Application::acceptSnapshot(const QString& urlString) {
QUrl url(urlString);
QString snapshotPath = url.toLocalFile();
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
SnapshotMetaData* snapshotData = DependencyManager::get<Snapshot>()->parseSnapshotData(snapshotPath);
if (snapshotData) {
if (!snapshotData->getURL().toString().isEmpty()) {
DependencyManager::get<AddressManager>()->handleLookupString(snapshotData->getURL().toString());
@ -7600,7 +7600,7 @@ void Application::loadAvatarBrowser() const {
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
// Get a screenshot and save it
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename,
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename,
TestScriptingInterface::getInstance()->getTestResultsLocation());
// If we're not doing an animated snapshot as well...
@ -7616,17 +7616,23 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
void Application::takeSecondaryCameraSnapshot(const QString& filename) {
postLambdaEvent([filename, this] {
QString snapshotPath = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
TestScriptingInterface::getInstance()->getTestResultsLocation());
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, true);
});
}
void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) {
postLambdaEvent([filename, cubemapOutputFormat, cameraPosition] {
DependencyManager::get<Snapshot>()->save360Snapshot(cameraPosition, cubemapOutputFormat, filename);
});
}
void Application::shareSnapshot(const QString& path, const QUrl& href) {
postLambdaEvent([path, href] {
// not much to do here, everything is done in snapshot code...
Snapshot::uploadSnapshot(path, href);
DependencyManager::get<Snapshot>()->uploadSnapshot(path, href);
});
}

View file

@ -282,6 +282,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 glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename = QString());
void shareSnapshot(const QString& filename, const QUrl& href = QUrl(""));

View file

@ -9,15 +9,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "SecondaryCamera.h"
#include <glm/gtx/transform.hpp>
#include <EntityScriptingInterface.h>
#include <gpu/Context.h>
#include <TextureCache.h>
#include "Application.h"
#include "SecondaryCamera.h"
#include <TextureCache.h>
#include <gpu/Context.h>
#include <glm/gtx/transform.hpp>
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
@ -38,7 +34,6 @@ public:
using JobModel = render::Job::ModelO<SecondaryCameraJob, RenderArgsPointer, Config>;
SecondaryCameraJob() {
_cachedArgsPointer = std::make_shared<RenderArgs>(_cachedArgs);
_entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
_attachedEntityPropertyFlags += PROP_POSITION;
_attachedEntityPropertyFlags += PROP_ROTATION;
}
@ -60,12 +55,16 @@ public:
qWarning() << "ERROR: Cannot set mirror projection for SecondaryCamera without an attachedEntityId set.";
return;
}
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_attachedEntityId,
_attachedEntityPropertyFlags);
glm::vec3 mirrorPropertiesPosition = entityProperties.getPosition();
glm::quat mirrorPropertiesRotation = entityProperties.getRotation();
glm::vec3 mirrorPropertiesDimensions = entityProperties.getDimensions();
EntityItemPointer attachedEntity = qApp->getEntities()->getTree()->findEntityByID(_attachedEntityId);
if (!attachedEntity) {
qWarning() << "ERROR: Cannot get EntityItemPointer for _attachedEntityId.";
return;
}
glm::vec3 mirrorPropertiesPosition = attachedEntity->getWorldPosition();
glm::quat mirrorPropertiesRotation = attachedEntity->getWorldOrientation();
glm::vec3 mirrorPropertiesDimensions = attachedEntity->getScaledDimensions();
glm::vec3 halfMirrorPropertiesDimensions = 0.5f * mirrorPropertiesDimensions;
// setup mirror from world as inverse of world from mirror transformation using inverted x and z for mirrored image
@ -120,10 +119,13 @@ public:
setMirrorProjection(srcViewFrustum);
} else {
if (!_attachedEntityId.isNull()) {
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_attachedEntityId,
_attachedEntityPropertyFlags);
srcViewFrustum.setPosition(entityProperties.getPosition());
srcViewFrustum.setOrientation(entityProperties.getRotation());
EntityItemPointer attachedEntity = qApp->getEntities()->getTree()->findEntityByID(_attachedEntityId);
if (!attachedEntity) {
qWarning() << "ERROR: Cannot get EntityItemPointer for _attachedEntityId.";
return;
}
srcViewFrustum.setPosition(attachedEntity->getWorldPosition());
srcViewFrustum.setOrientation(attachedEntity->getWorldOrientation());
} else {
srcViewFrustum.setPosition(_position);
srcViewFrustum.setOrientation(_orientation);
@ -155,7 +157,6 @@ private:
int _textureHeight;
bool _mirrorProjection;
EntityPropertyFlags _attachedEntityPropertyFlags;
QSharedPointer<EntityScriptingInterface> _entityScriptingInterface;
};
void SecondaryCameraJobConfig::setPosition(glm::vec3 pos) {
@ -216,4 +217,4 @@ void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inp
task.addJob<RenderDeferredTask>("RenderDeferredTask", items);
}
task.addJob<EndSecondaryCameraFrame>("EndSecondaryCamera", cachedArg);
}
}

View file

@ -431,6 +431,10 @@ void WindowScriptingInterface::takeSecondaryCameraSnapshot(const QString& filena
qApp->takeSecondaryCameraSnapshot(filename);
}
void WindowScriptingInterface::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) {
qApp->takeSecondaryCamera360Snapshot(cameraPosition, cubemapOutputFormat, filename);
}
void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) {
qApp->shareSnapshot(path, href);
}

View file

@ -370,6 +370,18 @@ public slots:
*/
void takeSecondaryCameraSnapshot(const QString& filename = QString());
/**jsdoc
* Takes a 360 snapshot given a position of the secondary camera (which does not need to have been previously set up).
* @function Window.takeSecondaryCameraSnapshot
* @param {vec3} [cameraPosition] - The (x, y, z) position of the camera for the 360 snapshot
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
*
* var filename = QString();
*/
void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat = false, const QString& filename = QString());
/**jsdoc
* Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that
* indicates whether or not a user connection was successfully made using the Web API.
@ -578,6 +590,16 @@ signals:
*/
void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify);
/**jsdoc
* Triggered when a still equirectangular snapshot has been taken by calling {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}
* @function Window.snapshot360Taken
* @param {string} pathStillSnapshot - The path and name of the snapshot image file.
* @param {boolean} notify - The value of the <code>notify</code> parameter that {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}
* was called with.
* @returns {Signal}
*/
void snapshot360Taken(const QString& path360Snapshot, 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.

View file

@ -132,8 +132,8 @@ void setupPreferences() {
// Snapshots
static const QString SNAPSHOTS { "Snapshots" };
{
auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };
auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); emit DependencyManager::get<Snapshot>()->snapshotLocationSet(value); };
auto getter = []()->QString { return DependencyManager::get<Snapshot>()->_snapshotsLocation.get(); };
auto setter = [](const QString& value) { DependencyManager::get<Snapshot>()->_snapshotsLocation.set(value); emit DependencyManager::get<Snapshot>()->snapshotLocationSet(value); };
auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter);
preferences->addPreference(preference);
}

View file

@ -21,7 +21,8 @@
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonArray>
#include <QtNetwork/QHttpMultiPart>
#include <QtGui/QImage>
#include <QPainter>
#include <QtConcurrent/QtConcurrentRun>
#include <AccountManager.h>
#include <AddressManager.h>
@ -31,20 +32,38 @@
#include <NodeList.h>
#include <OffscreenUi.h>
#include <SharedUtil.h>
#include <SecondaryCamera.h>
#include <plugins/DisplayPlugin.h>
#include "Application.h"
#include "scripting/WindowScriptingInterface.h"
#include "MainWindow.h"
#include "Snapshot.h"
#include "SnapshotUploader.h"
#include "ToneMappingEffect.h"
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
// %1 <= username, %2 <= date and time, %3 <= current location
const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2.jpg";
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss";
const QString SNAPSHOTS_DIRECTORY = "Snapshots";
const QString URL = "highfidelity_url";
static const int SNAPSHOT_360_TIMER_INTERVAL = 350;
Setting::Handle<QString> Snapshot::snapshotsLocation("snapshotsLocation");
Snapshot::Snapshot() {
_snapshotTimer.setSingleShot(false);
_snapshotTimer.setTimerType(Qt::PreciseTimer);
_snapshotTimer.setInterval(SNAPSHOT_360_TIMER_INTERVAL);
connect(&_snapshotTimer, &QTimer::timeout, this, &Snapshot::takeNextSnapshot);
_snapshotIndex = 0;
_oldEnabled = false;
_oldAttachedEntityId = 0;
_oldOrientation = 0;
_oldvFoV = 0;
_oldNearClipPlaneDistance = 0;
_oldFarClipPlaneDistance = 0;
}
SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
@ -88,6 +107,224 @@ QString Snapshot::saveSnapshot(QImage image, const QString& filename, const QStr
return snapshotPath;
}
static const float CUBEMAP_SIDE_PIXEL_DIMENSION = 2048.0f;
static const float SNAPSHOT_360_FOV = 90.0f;
static const float SNAPSHOT_360_NEARCLIP = 0.3f;
static const float SNAPSHOT_360_FARCLIP = 16384.0f;
static const glm::quat CAMERA_ORIENTATION_DOWN(glm::quat(glm::radians(glm::vec3(-90.0f, 0.0f, 0.0f))));
static const glm::quat CAMERA_ORIENTATION_FRONT(glm::quat(glm::radians(glm::vec3(0.0f, 0.0f, 0.0f))));
static const glm::quat CAMERA_ORIENTATION_LEFT(glm::quat(glm::radians(glm::vec3(0.0f, 90.0f, 0.0f))));
static const glm::quat CAMERA_ORIENTATION_BACK(glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f))));
static const glm::quat CAMERA_ORIENTATION_RIGHT(glm::quat(glm::radians(glm::vec3(0.0f, 270.0f, 0.0f))));
static const glm::quat CAMERA_ORIENTATION_UP(glm::quat(glm::radians(glm::vec3(90.0f, 0.0f, 0.0f))));
void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) {
_snapshotFilename = filename;
_cubemapOutputFormat = cubemapOutputFormat;
SecondaryCameraJobConfig* secondaryCameraRenderConfig = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
// Save initial values of secondary camera render config
_oldEnabled = secondaryCameraRenderConfig->isEnabled();
_oldAttachedEntityId = secondaryCameraRenderConfig->property("attachedEntityId");
_oldOrientation = secondaryCameraRenderConfig->property("orientation");
_oldvFoV = secondaryCameraRenderConfig->property("vFoV");
_oldNearClipPlaneDistance = secondaryCameraRenderConfig->property("nearClipPlaneDistance");
_oldFarClipPlaneDistance = secondaryCameraRenderConfig->property("farClipPlaneDistance");
if (!_oldEnabled) {
secondaryCameraRenderConfig->enableSecondaryCameraRenderConfigs(true);
}
// Initialize some secondary camera render config options for 360 snapshot capture
static_cast<ToneMappingConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(0);
secondaryCameraRenderConfig->resetSizeSpectatorCamera(static_cast<int>(CUBEMAP_SIDE_PIXEL_DIMENSION), static_cast<int>(CUBEMAP_SIDE_PIXEL_DIMENSION));
secondaryCameraRenderConfig->setProperty("attachedEntityId", "");
secondaryCameraRenderConfig->setPosition(cameraPosition);
secondaryCameraRenderConfig->setProperty("vFoV", SNAPSHOT_360_FOV);
secondaryCameraRenderConfig->setProperty("nearClipPlaneDistance", SNAPSHOT_360_NEARCLIP);
secondaryCameraRenderConfig->setProperty("farClipPlaneDistance", SNAPSHOT_360_FARCLIP);
// Setup for Down Image capture
secondaryCameraRenderConfig->setOrientation(CAMERA_ORIENTATION_DOWN);
_snapshotIndex = 0;
_snapshotTimer.start(SNAPSHOT_360_TIMER_INTERVAL);
}
void Snapshot::takeNextSnapshot() {
SecondaryCameraJobConfig* config = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
// Order is:
// 0. Down
// 1. Front
// 2. Left
// 3. Back
// 4. Right
// 5. Up
if (_snapshotIndex < 6) {
_imageArray[_snapshotIndex] = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
}
if (_snapshotIndex == 0) {
// Setup for Front Image capture
config->setOrientation(CAMERA_ORIENTATION_FRONT);
} else if (_snapshotIndex == 1) {
// Setup for Left Image capture
config->setOrientation(CAMERA_ORIENTATION_LEFT);
} else if (_snapshotIndex == 2) {
// Setup for Back Image capture
config->setOrientation(CAMERA_ORIENTATION_BACK);
} else if (_snapshotIndex == 3) {
// Setup for Right Image capture
config->setOrientation(CAMERA_ORIENTATION_RIGHT);
} else if (_snapshotIndex == 4) {
// Setup for Up Image capture
config->setOrientation(CAMERA_ORIENTATION_UP);
} else if (_snapshotIndex > 5) {
_snapshotTimer.stop();
// Reset secondary camera render config
static_cast<ToneMappingConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(1);
config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height());
config->setProperty("attachedEntityId", _oldAttachedEntityId);
config->setProperty("vFoV", _oldvFoV);
config->setProperty("nearClipPlaneDistance", _oldNearClipPlaneDistance);
config->setProperty("farClipPlaneDistance", _oldFarClipPlaneDistance);
if (!_oldEnabled) {
config->enableSecondaryCameraRenderConfigs(false);
}
// Process six QImages
if (_cubemapOutputFormat) {
QtConcurrent::run([this]() { convertToCubemap(); });
} else {
QtConcurrent::run([this]() { convertToEquirectangular(); });
}
}
_snapshotIndex++;
}
void Snapshot::convertToCubemap() {
float outputImageHeight = CUBEMAP_SIDE_PIXEL_DIMENSION * 3.0f;
float outputImageWidth = CUBEMAP_SIDE_PIXEL_DIMENSION * 4.0f;
QImage outputImage(outputImageWidth, outputImageHeight, QImage::Format_RGB32);
QPainter painter(&outputImage);
QPoint destPos;
// Paint DownImage
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION, CUBEMAP_SIDE_PIXEL_DIMENSION * 2.0f);
painter.drawImage(destPos, _imageArray[0]);
// Paint FrontImage
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION, CUBEMAP_SIDE_PIXEL_DIMENSION);
painter.drawImage(destPos, _imageArray[1]);
// Paint LeftImage
destPos = QPoint(0, CUBEMAP_SIDE_PIXEL_DIMENSION);
painter.drawImage(destPos, _imageArray[2]);
// Paint BackImage
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION * 3.0f, CUBEMAP_SIDE_PIXEL_DIMENSION);
painter.drawImage(destPos, _imageArray[3]);
// Paint RightImage
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION * 2.0f, CUBEMAP_SIDE_PIXEL_DIMENSION);
painter.drawImage(destPos, _imageArray[4]);
// Paint UpImage
destPos = QPoint(CUBEMAP_SIDE_PIXEL_DIMENSION, 0);
painter.drawImage(destPos, _imageArray[5]);
painter.end();
emit DependencyManager::get<WindowScriptingInterface>()->snapshot360Taken(saveSnapshot(outputImage, _snapshotFilename), true);
}
void Snapshot::convertToEquirectangular() {
// I got help from StackOverflow while writing this code:
// https://stackoverflow.com/questions/34250742/converting-a-cubemap-into-equirectangular-panorama
int cubeFaceWidth = static_cast<int>(CUBEMAP_SIDE_PIXEL_DIMENSION);
int cubeFaceHeight = static_cast<int>(CUBEMAP_SIDE_PIXEL_DIMENSION);
float outputImageHeight = CUBEMAP_SIDE_PIXEL_DIMENSION * 2.0f;
float outputImageWidth = outputImageHeight * 2.0f;
QImage outputImage(outputImageWidth, outputImageHeight, QImage::Format_RGB32);
outputImage.fill(0);
QRgb sourceColorValue;
float phi, theta;
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 = glm::sin(phi) * glm::sin(theta) * -1.0f;
float y = glm::cos(theta);
float z = glm::cos(phi) * glm::sin(theta) * -1.0f;
float a = std::max(std::max(std::abs(x), std::abs(y)), std::abs(z));
float xa = x / a;
float ya = y / a;
float 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 = _imageArray[4];
} else if (xa == -1) {
// Left image
xPixel = (int)((((za + 1.0f) / 2.0f)) * cubeFaceWidth);
yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
sourceImage = _imageArray[2];
} 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 = _imageArray[0];
} else if (ya == -1) {
// Up image
xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
yPixel = (int)((((za + 1.0f) / 2.0f)) * cubeFaceHeight);
sourceImage = _imageArray[5];
} else if (za == 1) {
// Front image
xPixel = (int)((((xa + 1.0f) / 2.0f)) * cubeFaceWidth);
yPixel = (int)((((ya + 1.0f) / 2.0f)) * cubeFaceHeight);
sourceImage = _imageArray[1];
} 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 = _imageArray[3];
} 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<WindowScriptingInterface>()->snapshot360Taken(saveSnapshot(outputImage, _snapshotFilename), true);
}
QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) {
// return whatever we get back from saved file for snapshot
return static_cast<QTemporaryFile*>(savedFileForSnapshot(image, true));
@ -123,12 +360,12 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt
if (!userSelectedPathname.isNull()) {
snapshotFullPath = userSelectedPathname;
} else {
snapshotFullPath = snapshotsLocation.get();
snapshotFullPath = _snapshotsLocation.get();
}
if (snapshotFullPath.isEmpty()) {
snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Choose Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
snapshotsLocation.set(snapshotFullPath);
_snapshotsLocation.set(snapshotFullPath);
}
if (!snapshotFullPath.isEmpty()) { // not cancelled
@ -210,9 +447,9 @@ void Snapshot::uploadSnapshot(const QString& filename, const QUrl& href) {
}
QString Snapshot::getSnapshotsLocation() {
return snapshotsLocation.get("");
return _snapshotsLocation.get("");
}
void Snapshot::setSnapshotsLocation(const QString& location) {
snapshotsLocation.set(location);
_snapshotsLocation.set(location);
}

View file

@ -17,6 +17,8 @@
#include <QString>
#include <QStandardPaths>
#include <QUrl>
#include <QTimer>
#include <QtGui/QImage>
#include <SettingHandle.h>
#include <DependencyManager.h>
@ -38,12 +40,14 @@ class Snapshot : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
static QString saveSnapshot(QImage image, const QString& filename, const QString& pathname = QString());
static QTemporaryFile* saveTempSnapshot(QImage image);
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
Snapshot();
QString saveSnapshot(QImage image, const QString& filename, const QString& pathname = QString());
void save360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename);
QTemporaryFile* saveTempSnapshot(QImage image);
SnapshotMetaData* parseSnapshotData(QString snapshotPath);
static Setting::Handle<QString> snapshotsLocation;
static void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
Setting::Handle<QString> _snapshotsLocation{ "snapshotsLocation" };
void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
signals:
void snapshotLocationSet(const QString& value);
@ -51,11 +55,28 @@ signals:
public slots:
Q_INVOKABLE QString getSnapshotsLocation();
Q_INVOKABLE void setSnapshotsLocation(const QString& location);
private slots:
void takeNextSnapshot();
private:
static QFile* savedFileForSnapshot(QImage& image,
QFile* savedFileForSnapshot(QImage& image,
bool isTemporary,
const QString& userSelectedFilename = QString(),
const QString& userSelectedPathname = QString());
QString _snapshotFilename;
bool _cubemapOutputFormat;
QTimer _snapshotTimer;
qint16 _snapshotIndex;
bool _oldEnabled;
QVariant _oldAttachedEntityId;
QVariant _oldOrientation;
QVariant _oldvFoV;
QVariant _oldNearClipPlaneDistance;
QVariant _oldFarClipPlaneDistance;
QImage _imageArray[6];
void convertToCubemap();
void convertToEquirectangular();
};
#endif // hifi_Snapshot_h

View file

@ -672,6 +672,7 @@
Menu.menuItemEvent.connect(menuItemEvent);
Window.domainConnectionRefused.connect(onDomainConnectionRefused);
Window.stillSnapshotTaken.connect(onSnapshotTaken);
Window.snapshot360Taken.connect(onSnapshotTaken);
Window.processingGifStarted.connect(processingGif);
Window.connectionAdded.connect(connectionAdded);
Window.connectionError.connect(connectionError);

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

View file

@ -0,0 +1,623 @@
//
// SpectatorCamera.qml
// qml/hifi
//
// Spectator Camera v2.0
//
// Created by Zach Fox on 2018-04-18
// Copyright 2018 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
//
import Hifi 1.0 as Hifi
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtGraphicalEffects 1.0
import "qrc:////qml//styles-uit" as HifiStylesUit
import "qrc:////qml//controls-uit" as HifiControlsUit
import "qrc:////qml//controls" as HifiControls
import "qrc:////qml//hifi" as Hifi
Rectangle {
HifiStylesUit.HifiConstants { id: hifi; }
id: root;
property bool processing360Snapshot: false;
// Style
color: "#404040";
// The letterbox used for popup messages
Hifi.LetterboxMessage {
id: letterboxMessage;
z: 998; // Force the popup on top of everything else
}
function letterbox(headerGlyph, headerText, message) {
letterboxMessage.headerGlyph = headerGlyph;
letterboxMessage.headerText = headerText;
letterboxMessage.text = message;
letterboxMessage.visible = true;
letterboxMessage.popupRadius = 0;
}
//
// TITLE BAR START
//
Rectangle {
id: titleBarContainer;
// Size
width: root.width;
height: 60;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
color: "#121212";
// "Spectator" text
HifiStylesUit.RalewaySemiBold {
id: titleBarText;
text: "Spectator Camera";
// Anchors
anchors.left: parent.left;
anchors.leftMargin: 30;
width: paintedWidth;
height: parent.height;
size: 22;
// Style
color: hifi.colors.white;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
Switch {
id: masterSwitch;
focusPolicy: Qt.ClickFocus;
width: 65;
height: 30;
anchors.verticalCenter: parent.verticalCenter;
anchors.right: parent.right;
anchors.rightMargin: 30;
hoverEnabled: true;
onHoveredChanged: {
if (hovered) {
switchHandle.color = hifi.colors.blueHighlight;
} else {
switchHandle.color = hifi.colors.lightGray;
}
}
onClicked: {
sendToScript({method: (checked ? 'spectatorCameraOn' : 'spectatorCameraOff')});
sendToScript({method: 'updateCameravFoV', vFoV: fieldOfViewSlider.value});
}
background: Rectangle {
color: parent.checked ? "#1FC6A6" : hifi.colors.white;
implicitWidth: masterSwitch.switchWidth;
implicitHeight: masterSwitch.height;
radius: height/2;
}
indicator: Rectangle {
id: switchHandle;
implicitWidth: masterSwitch.height - 4;
implicitHeight: implicitWidth;
radius: implicitWidth/2;
border.color: "#E3E3E3";
color: "#404040";
x: Math.max(4, Math.min(parent.width - width - 4, parent.visualPosition * parent.width - (width / 2) - 4))
y: parent.height / 2 - height / 2;
Behavior on x {
enabled: !masterSwitch.down
SmoothedAnimation { velocity: 200 }
}
}
}
}
//
// TITLE BAR END
//
Rectangle {
z: 999;
id: processingSnapshot;
anchors.fill: parent;
visible: root.processing360Snapshot;
color: Qt.rgba(0.0, 0.0, 0.0, 0.85);
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup/section.
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
propagateComposedEvents: false;
}
AnimatedImage {
id: processingImage;
source: "processing.gif"
width: 74;
height: width;
anchors.verticalCenter: parent.verticalCenter;
anchors.horizontalCenter: parent.horizontalCenter;
}
HifiStylesUit.RalewaySemiBold {
text: "Processing...";
// Anchors
anchors.top: processingImage.bottom;
anchors.topMargin: 4;
anchors.horizontalCenter: parent.horizontalCenter;
width: paintedWidth;
// Text size
size: 26;
// Style
color: hifi.colors.white;
verticalAlignment: Text.AlignVCenter;
}
}
//
// SPECTATOR CONTROLS START
//
Item {
id: spectatorControlsContainer;
// Anchors
anchors.top: titleBarContainer.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
// Instructions or Preview
Rectangle {
id: spectatorCameraImageContainer;
anchors.left: parent.left;
anchors.top: parent.top;
anchors.right: parent.right;
height: 250;
color: masterSwitch.checked ? "transparent" : "black";
AnimatedImage {
source: "static.gif"
visible: !masterSwitch.checked;
anchors.fill: parent;
opacity: 0.15;
}
// Instructions (visible when display texture isn't set)
HifiStylesUit.FiraSansRegular {
id: spectatorCameraInstructions;
text: "Turn on Spectator Camera for a preview\nof " + (HMD.active ? "what your monitor shows." : "the camera's view.");
size: 16;
color: hifi.colors.white;
visible: !masterSwitch.checked;
anchors.fill: parent;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
// Spectator Camera Preview
Hifi.ResourceImageItem {
id: spectatorCameraPreview;
visible: masterSwitch.checked;
url: showCameraView.checked || !HMD.active ? "resource://spectatorCameraFrame" : "resource://hmdPreviewFrame";
ready: masterSwitch.checked;
mirrorVertically: true;
anchors.fill: parent;
onVisibleChanged: {
ready = masterSwitch.checked;
update();
}
}
Item {
visible: HMD.active;
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: 40;
LinearGradient {
anchors.fill: parent;
start: Qt.point(0, 0);
end: Qt.point(0, height);
gradient: Gradient {
GradientStop { position: 0.0; color: hifi.colors.black }
GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0) }
}
}
HifiStylesUit.HiFiGlyphs {
id: monitorShowsSwitchLabelGlyph;
text: hifi.glyphs.screen;
size: 32;
color: hifi.colors.white;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.leftMargin: 16;
}
HifiStylesUit.RalewayLight {
id: monitorShowsSwitchLabel;
text: "Monitor View:";
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.left: monitorShowsSwitchLabelGlyph.right;
anchors.leftMargin: 8;
size: 20;
width: paintedWidth;
height: parent.height;
color: hifi.colors.white;
verticalAlignment: Text.AlignVCenter;
}
Item {
anchors.left: monitorShowsSwitchLabel.right;
anchors.leftMargin: 14;
anchors.right: parent.right;
anchors.rightMargin: 10;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
HifiControlsUit.RadioButton {
id: showCameraView;
text: "Camera View";
width: 125;
anchors.left: parent.left;
anchors.leftMargin: 10;
anchors.verticalCenter: parent.verticalCenter;
colorScheme: hifi.colorSchemes.dark;
onClicked: {
if (showHmdPreview.checked) {
showHmdPreview.checked = false;
}
if (!showCameraView.checked && !showHmdPreview.checked) {
showCameraView.checked = true;
}
}
onCheckedChanged: {
if (checked) {
sendToScript({method: 'setMonitorShowsCameraView', params: true});
}
}
}
HifiControlsUit.RadioButton {
id: showHmdPreview;
text: "VR Preview";
anchors.left: showCameraView.right;
anchors.leftMargin: 10;
width: 125;
anchors.verticalCenter: parent.verticalCenter;
colorScheme: hifi.colorSchemes.dark;
onClicked: {
if (showCameraView.checked) {
showCameraView.checked = false;
}
if (!showCameraView.checked && !showHmdPreview.checked) {
showHmdPreview.checked = true;
}
}
onCheckedChanged: {
if (checked) {
sendToScript({method: 'setMonitorShowsCameraView', params: false});
}
}
}
}
}
HifiControlsUit.Button {
id: takeSnapshotButton;
enabled: masterSwitch.checked;
text: "SNAP PICTURE";
colorScheme: hifi.colorSchemes.light;
color: hifi.buttons.white;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 8;
anchors.right: take360SnapshotButton.left;
anchors.rightMargin: 12;
width: 135;
height: 35;
onClicked: {
sendToScript({method: 'takeSecondaryCameraSnapshot'});
}
}
HifiControlsUit.Button {
id: take360SnapshotButton;
enabled: masterSwitch.checked;
text: "SNAP 360";
colorScheme: hifi.colorSchemes.light;
color: hifi.buttons.white;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 12;
width: 135;
height: 35;
onClicked: {
root.processing360Snapshot = true;
sendToScript({method: 'takeSecondaryCamera360Snapshot'});
}
}
}
Item {
anchors.top: spectatorCameraImageContainer.bottom;
anchors.topMargin: 8;
anchors.left: parent.left;
anchors.leftMargin: 26;
anchors.right: parent.right;
anchors.rightMargin: 26;
anchors.bottom: parent.bottom;
Item {
id: fieldOfView;
visible: masterSwitch.checked;
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: 35;
HifiStylesUit.RalewaySemiBold {
id: fieldOfViewLabel;
text: "Field of View (" + fieldOfViewSlider.value + "\u00B0): ";
size: 20;
color: hifi.colors.white;
anchors.left: parent.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: 172;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
}
HifiControlsUit.Slider {
id: fieldOfViewSlider;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: resetvFoV.left;
anchors.rightMargin: 8;
anchors.left: fieldOfViewLabel.right;
anchors.leftMargin: 8;
colorScheme: hifi.colorSchemes.dark;
from: 10.0;
to: 120.0;
value: 45.0;
stepSize: 1;
onValueChanged: {
sendToScript({method: 'updateCameravFoV', vFoV: value});
}
onPressedChanged: {
if (!pressed) {
sendToScript({method: 'updateCameravFoV', vFoV: value});
}
}
}
HifiControlsUit.GlyphButton {
id: resetvFoV;
anchors.verticalCenter: parent.verticalCenter;
anchors.right: parent.right;
anchors.rightMargin: -8;
height: parent.height - 8;
width: height;
glyph: hifi.glyphs.reload;
onClicked: {
fieldOfViewSlider.value = 45.0;
}
}
}
Item {
visible: HMD.active;
anchors.top: fieldOfView.bottom;
anchors.topMargin: 18;
anchors.left: parent.left;
anchors.right: parent.right;
height: childrenRect.height;
HifiStylesUit.RalewaySemiBold {
id: shortcutsHeaderText;
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: paintedHeight;
text: "Shortcuts";
size: 20;
color: hifi.colors.white;
}
// "Switch View From Controller" Checkbox
HifiControlsUit.CheckBox {
id: switchViewFromControllerCheckBox;
color: hifi.colors.white;
colorScheme: hifi.colorSchemes.dark;
anchors.left: parent.left;
anchors.top: shortcutsHeaderText.bottom;
anchors.topMargin: 8;
text: "";
labelFontSize: 20;
labelFontWeight: Font.Normal;
boxSize: 24;
onClicked: {
sendToScript({method: 'changeSwitchViewFromControllerPreference', params: checked});
}
}
// "Take Snapshot" Checkbox
HifiControlsUit.CheckBox {
id: takeSnapshotFromControllerCheckBox;
color: hifi.colors.white;
colorScheme: hifi.colorSchemes.dark;
anchors.left: parent.left;
anchors.top: switchViewFromControllerCheckBox.bottom;
anchors.topMargin: 4;
text: "";
labelFontSize: 20;
labelFontWeight: Font.Normal;
boxSize: 24;
onClicked: {
sendToScript({method: 'changeTakeSnapshotFromControllerPreference', params: checked});
}
}
}
HifiControlsUit.Button {
text: "Change Snapshot Location";
colorScheme: hifi.colorSchemes.dark;
color: hifi.buttons.none;
anchors.bottom: spectatorDescriptionContainer.top;
anchors.bottomMargin: 16;
anchors.left: parent.left;
anchors.right: parent.right;
height: 35;
onClicked: {
sendToScript({method: 'openSettings'});
}
}
Item {
id: spectatorDescriptionContainer;
// Size
height: childrenRect.height;
// Anchors
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 20;
// "Spectator" app description text
HifiStylesUit.RalewayRegular {
id: spectatorDescriptionText;
text: "While you're using a VR headset, you can use this app to change what your monitor shows. " +
"Try it when streaming or recording video.";
// Text size
size: 20;
// Size
height: paintedHeight;
// Anchors
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: parent.top;
// Style
color: hifi.colors.white;
wrapMode: Text.Wrap;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// "Learn More" text
HifiStylesUit.RalewayRegular {
id: spectatorLearnMoreText;
text: "Learn More About Spectator";
// Text size
size: 20;
// Size
width: paintedWidth;
height: paintedHeight;
// Anchors
anchors.top: spectatorDescriptionText.bottom;
anchors.topMargin: 10;
anchors.left: parent.left;
anchors.right: parent.right;
// Style
color: hifi.colors.blueAccent;
wrapMode: Text.WordWrap;
font.underline: true;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
letterbox(hifi.glyphs.question,
"Spectator Camera",
"By default, your monitor shows a preview of what you're seeing in VR. " +
"Using the Spectator Camera app, your monitor can display the view " +
"from a virtual hand-held camera - perfect for taking selfies or filming " +
"your friends!<br>" +
"<h3>Streaming and Recording</h3>" +
"We recommend OBS for streaming and recording the contents of your monitor to services like " +
"Twitch, YouTube Live, and Facebook Live.<br><br>" +
"To get started using OBS, click this link now. The page will open in an external browser:<br>" +
'<font size="4"><a href="https://obsproject.com/forum/threads/official-overview-guide.402/">OBS Official Overview Guide</a></font><br><br>' +
'<b>Snapshots</b> taken using Spectator Camera will be saved in your Snapshots Directory - change via Settings -> General.');
}
onEntered: parent.color = hifi.colors.blueHighlight;
onExited: parent.color = hifi.colors.blueAccent;
}
}
}
}
}
//
// SPECTATOR CONTROLS END
//
//
// FUNCTION DEFINITIONS START
//
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the SpectatorCamera JavaScript.
// Messages are in format "{method, params}", like json-rpc.
//
// Description:
// Called when a message is received from spectatorCamera.js.
//
function fromScript(message) {
switch (message.method) {
case 'updateSpectatorCameraCheckbox':
masterSwitch.checked = message.params;
break;
case 'updateMonitorShowsSwitch':
showCameraView.checked = message.params;
showHmdPreview.checked = !message.params;
break;
case 'updateControllerMappingCheckbox':
switchViewFromControllerCheckBox.checked = message.switchViewSetting;
switchViewFromControllerCheckBox.enabled = true;
takeSnapshotFromControllerCheckBox.checked = message.takeSnapshotSetting;
takeSnapshotFromControllerCheckBox.enabled = true;
if (message.controller === "OculusTouch") {
switchViewFromControllerCheckBox.text = "Left Thumbstick: Switch Monitor View";
takeSnapshotFromControllerCheckBox.text = "Right Thumbstick: Take Snapshot";
} else if (message.controller === "Vive") {
switchViewFromControllerCheckBox.text = "Left Thumb Pad: Switch Monitor View";
takeSnapshotFromControllerCheckBox.text = "Right Thumb Pad: Take Snapshot";
} else {
switchViewFromControllerCheckBox.text = "Pressing Ctrl+0 Switches Monitor View";
switchViewFromControllerCheckBox.checked = true;
switchViewFromControllerCheckBox.enabled = false;
takeSnapshotFromControllerCheckBox.visible = false;
}
break;
case 'finishedProcessing360Snapshot':
root.processing360Snapshot = false;
break;
default:
console.log('Unrecognized message from spectatorCamera.js:', JSON.stringify(message));
}
}
signal sendToScript(var message);
//
// FUNCTION DEFINITIONS END
//
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -0,0 +1,4 @@
{
"scriptURL": "http://mpassets-staging.highfidelity.com/26156ea5-cdff-43c2-9581-d6b0fa5e00ef-v1/spectatorCamera.js",
"homeURL": "http://mpassets-staging.highfidelity.com/26156ea5-cdff-43c2-9581-d6b0fa5e00ef-v1/SpectatorCamera.qml"
}

View file

@ -46,7 +46,6 @@
// -Far clip plane distance
// -viewFinderOverlay: The in-world overlay that displays the spectator camera's view.
// -camera: The in-world entity that corresponds to the spectator camera.
// -cameraIsDynamic: "false" for now - maybe it shouldn't be? False means that the camera won't drift when you let go...
// -cameraRotation: The rotation of the spectator camera.
// -cameraPosition: The position of the spectator camera.
// -glassPaneWidth: The width of the glass pane above the spectator camera that holds the viewFinderOverlay.
@ -56,7 +55,6 @@
var spectatorCameraConfig = Render.getConfig("SecondaryCamera");
var viewFinderOverlay = false;
var camera = false;
var cameraIsDynamic = false;
var cameraRotation;
var cameraPosition;
var glassPaneWidth = 0.16;
@ -70,11 +68,11 @@
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": 1,
"damping": 1,
"angularDamping": 0.95,
"damping": 0.95,
"collidesWith": "static,dynamic,kinematic,",
"collisionMask": 7,
"dynamic": cameraIsDynamic,
"dynamic": false,
"modelURL": Script.resolvePath("spectator-camera.fbx"),
"registrationPoint": {
"x": 0.56,
@ -89,8 +87,12 @@
}, true);
spectatorCameraConfig.attachedEntityId = camera;
updateOverlay();
setDisplay(monitorShowsCameraView);
// Change button to active when window is first openend OR if the camera is on, false otherwise.
if (!HMD.active) {
setMonitorShowsCameraView(false);
} else {
setDisplay(monitorShowsCameraView);
}
// Change button to active when window is first opened OR if the camera is on, false otherwise.
if (button) {
button.editProperties({ isActive: onSpectatorCameraScreen || camera });
}
@ -150,17 +152,15 @@
// Relevant Variables:
// -button: The tablet button.
// -buttonName: The name of the button.
// -showSpectatorInDesktop: Set to "true" to show the "SPECTATOR" app in desktop mode.
var button = false;
var buttonName = "SPECTATOR";
var showSpectatorInDesktop = false;
function addOrRemoveButton(isShuttingDown, isHMDMode) {
function addOrRemoveButton(isShuttingDown) {
if (!tablet) {
print("Warning in addOrRemoveButton(): 'tablet' undefined!");
return;
}
if (!button) {
if ((isHMDMode || showSpectatorInDesktop) && !isShuttingDown) {
if (!isShuttingDown) {
button = tablet.addButton({
text: buttonName,
icon: "icons/tablet-icons/spectator-i.svg",
@ -169,7 +169,7 @@
button.clicked.connect(onTabletButtonClicked);
}
} else if (button) {
if ((!isHMDMode && !showSpectatorInDesktop) || isShuttingDown) {
if (isShuttingDown) {
button.clicked.disconnect(onTabletButtonClicked);
tablet.removeButton(button);
button = false;
@ -189,10 +189,12 @@
var tablet = null;
function startup() {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
addOrRemoveButton(false, HMD.active);
addOrRemoveButton(false);
tablet.screenChanged.connect(onTabletScreenChanged);
Window.domainChanged.connect(onDomainChanged);
Window.geometryChanged.connect(resizeViewFinderOverlay);
Window.stillSnapshotTaken.connect(onStillSnapshotTaken);
Window.snapshot360Taken.connect(on360SnapshotTaken);
Controller.keyPressEvent.connect(keyPressEvent);
HMD.displayModeChanged.connect(onHMDChanged);
viewFinderOverlay = false;
@ -238,9 +240,7 @@
// 3. Camera is on; "Monitor Shows" is "HMD Preview": "url" is ""
// 4. Camera is on; "Monitor Shows" is "Camera View": "url" is "resource://spectatorCameraFrame"
function setDisplay(showCameraView) {
var url = (camera) ? (showCameraView ? "resource://spectatorCameraFrame" : "resource://hmdPreviewFrame") : "";
sendToQml({ method: 'showPreviewTextureNotInstructions', setting: !!url, url: url});
// FIXME: temporary hack to avoid setting the display texture to hmdPreviewFrame
// until it is the correct mono.
@ -253,11 +253,8 @@
const MONITOR_SHOWS_CAMERA_VIEW_DEFAULT = false;
var monitorShowsCameraView = !!Settings.getValue('spectatorCamera/monitorShowsCameraView', MONITOR_SHOWS_CAMERA_VIEW_DEFAULT);
function setMonitorShowsCameraView(showCameraView) {
if (showCameraView === monitorShowsCameraView) {
return;
}
monitorShowsCameraView = showCameraView;
setDisplay(showCameraView);
monitorShowsCameraView = showCameraView;
Settings.setValue('spectatorCamera/monitorShowsCameraView', showCameraView);
}
function setMonitorShowsCameraViewAndSendToQml(showCameraView) {
@ -320,14 +317,14 @@
const SWITCH_VIEW_FROM_CONTROLLER_DEFAULT = false;
var switchViewFromController = !!Settings.getValue('spectatorCamera/switchViewFromController', SWITCH_VIEW_FROM_CONTROLLER_DEFAULT);
function setControllerMappingStatus(status) {
if (!controllerMapping) {
function setSwitchViewControllerMappingStatus(status) {
if (!switchViewControllerMapping) {
return;
}
if (status) {
controllerMapping.enable();
switchViewControllerMapping.enable();
} else {
controllerMapping.disable();
switchViewControllerMapping.disable();
}
}
function setSwitchViewFromController(setting) {
@ -335,21 +332,120 @@
return;
}
switchViewFromController = setting;
setControllerMappingStatus(switchViewFromController);
setSwitchViewControllerMappingStatus(switchViewFromController);
Settings.setValue('spectatorCamera/switchViewFromController', setting);
}
const TAKE_SNAPSHOT_FROM_CONTROLLER_DEFAULT = false;
var takeSnapshotFromController = !!Settings.getValue('spectatorCamera/takeSnapshotFromController', TAKE_SNAPSHOT_FROM_CONTROLLER_DEFAULT);
function setTakeSnapshotControllerMappingStatus(status) {
if (!takeSnapshotControllerMapping) {
return;
}
if (status) {
takeSnapshotControllerMapping.enable();
} else {
takeSnapshotControllerMapping.disable();
}
}
function setTakeSnapshotFromController(setting) {
if (setting === takeSnapshotFromController) {
return;
}
takeSnapshotFromController = setting;
setTakeSnapshotControllerMappingStatus(takeSnapshotFromController);
Settings.setValue('spectatorCamera/takeSnapshotFromController', setting);
}
// Function Name: registerButtonMappings()
//
// Description:
// -Updates controller button mappings for Spectator Camera.
//
// Relevant Variables:
// -controllerMappingName: The name of the controller mapping.
// -controllerMapping: The controller mapping itself.
// -switchViewControllerMappingName: The name of the controller mapping.
// -switchViewControllerMapping: The controller mapping itself.
// -takeSnapshotControllerMappingName: The name of the controller mapping.
// -takeSnapshotControllerMapping: The controller mapping itself.
// -controllerType: "OculusTouch", "Vive", "Other".
var controllerMappingName;
var controllerMapping;
var switchViewControllerMapping;
var switchViewControllerMappingName = 'Hifi-SpectatorCamera-Mapping-SwitchView';
function registerSwitchViewControllerMapping() {
switchViewControllerMapping = Controller.newMapping(switchViewControllerMappingName);
if (controllerType === "OculusTouch") {
switchViewControllerMapping.from(Controller.Standard.LS).to(function (value) {
if (value === 1.0) {
setMonitorShowsCameraViewAndSendToQml(!monitorShowsCameraView);
}
return;
});
} else if (controllerType === "Vive") {
switchViewControllerMapping.from(Controller.Standard.LeftPrimaryThumb).to(function (value) {
if (value === 1.0) {
setMonitorShowsCameraViewAndSendToQml(!monitorShowsCameraView);
}
return;
});
}
}
var takeSnapshotControllerMapping;
var takeSnapshotControllerMappingName = 'Hifi-SpectatorCamera-Mapping-TakeSnapshot';
function onStillSnapshotTaken() {
Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 1;
}
function maybeTakeSnapshot() {
if (camera) {
Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 0;
// Wait a moment before taking the snapshot for the tonemapping curve to update
Script.setTimeout(function () {
Audio.playSound(SNAPSHOT_SOUND, {
position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z },
localOnly: true,
volume: 1.0
});
Window.takeSecondaryCameraSnapshot();
}, 250);
}
}
function on360SnapshotTaken() {
if (monitorShowsCameraView) {
setDisplay(true);
}
sendToQml({
method: 'finishedProcessing360Snapshot'
});
}
function maybeTake360Snapshot() {
if (camera) {
Audio.playSound(SNAPSHOT_SOUND, {
position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z },
localOnly: true,
volume: 1.0
});
if (HMD.active && monitorShowsCameraView) {
setDisplay(false);
}
Window.takeSecondaryCamera360Snapshot(Entities.getEntityProperties(camera, ["positon"]).position);
}
}
function registerTakeSnapshotControllerMapping() {
takeSnapshotControllerMapping = Controller.newMapping(takeSnapshotControllerMappingName);
if (controllerType === "OculusTouch") {
takeSnapshotControllerMapping.from(Controller.Standard.RS).to(function (value) {
if (value === 1.0) {
maybeTakeSnapshot();
}
return;
});
} else if (controllerType === "Vive") {
takeSnapshotControllerMapping.from(Controller.Standard.RightPrimaryThumb).to(function (value) {
if (value === 1.0) {
maybeTakeSnapshot();
}
return;
});
}
}
var controllerType = "Other";
function registerButtonMappings() {
var VRDevices = Controller.getDeviceNames().toString();
@ -359,30 +455,32 @@
} else if (VRDevices.indexOf("OculusTouch") !== -1) {
controllerType = "OculusTouch";
} else {
sendToQml({ method: 'updateControllerMappingCheckbox', setting: switchViewFromController, controller: controllerType });
sendToQml({
method: 'updateControllerMappingCheckbox',
switchViewSetting: switchViewFromController,
takeSnapshotSetting: takeSnapshotFromController,
controller: controllerType
});
return; // Neither Vive nor Touch detected
}
}
controllerMappingName = 'Hifi-SpectatorCamera-Mapping';
controllerMapping = Controller.newMapping(controllerMappingName);
if (controllerType === "OculusTouch") {
controllerMapping.from(Controller.Standard.LS).to(function (value) {
if (value === 1.0) {
setMonitorShowsCameraViewAndSendToQml(!monitorShowsCameraView);
}
return;
});
} else if (controllerType === "Vive") {
controllerMapping.from(Controller.Standard.LeftPrimaryThumb).to(function (value) {
if (value === 1.0) {
setMonitorShowsCameraViewAndSendToQml(!monitorShowsCameraView);
}
return;
});
if (!switchViewControllerMapping) {
registerSwitchViewControllerMapping();
}
setControllerMappingStatus(switchViewFromController);
sendToQml({ method: 'updateControllerMappingCheckbox', setting: switchViewFromController, controller: controllerType });
setSwitchViewControllerMappingStatus(switchViewFromController);
if (!takeSnapshotControllerMapping) {
registerTakeSnapshotControllerMapping();
}
setTakeSnapshotControllerMappingStatus(switchViewFromController);
sendToQml({
method: 'updateControllerMappingCheckbox',
switchViewSetting: switchViewFromController,
takeSnapshotSetting: takeSnapshotFromController,
controller: controllerType
});
}
// Function Name: onTabletButtonClicked()
@ -393,7 +491,7 @@
// Relevant Variables:
// -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML
// -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app.
var SPECTATOR_CAMERA_QML_SOURCE = "hifi/SpectatorCamera.qml";
var SPECTATOR_CAMERA_QML_SOURCE = Script.resolvePath("SpectatorCamera.qml");
var onSpectatorCameraScreen = false;
function onTabletButtonClicked() {
if (!tablet) {
@ -405,18 +503,26 @@
tablet.gotoHomeScreen();
} else {
tablet.loadQMLSource(SPECTATOR_CAMERA_QML_SOURCE);
sendToQml({ method: 'updateSpectatorCameraCheckbox', params: !!camera });
sendToQml({ method: 'updateMonitorShowsSwitch', params: monitorShowsCameraView });
if (!controllerMapping) {
registerButtonMappings();
} else {
sendToQml({ method: 'updateControllerMappingCheckbox', setting: switchViewFromController, controller: controllerType });
}
Menu.setIsOptionChecked("Disable Preview", false);
Menu.setIsOptionChecked("Mono Preview", true);
}
}
function updateSpectatorCameraQML() {
sendToQml({ method: 'updateSpectatorCameraCheckbox', params: !!camera });
sendToQml({ method: 'updateMonitorShowsSwitch', params: monitorShowsCameraView });
if (!switchViewControllerMapping || !takeSnapshotControllerMapping) {
registerButtonMappings();
} else {
sendToQml({
method: 'updateControllerMappingCheckbox',
switchViewSetting: switchViewFromController,
takeSnapshotSetting: takeSnapshotFromController,
controller: controllerType
});
}
Menu.setIsOptionChecked("Disable Preview", false);
Menu.setIsOptionChecked("Mono Preview", true);
}
// Function Name: onTabletScreenChanged()
//
// Description:
@ -429,6 +535,10 @@
if (button) {
button.editProperties({ isActive: onSpectatorCameraScreen || camera });
}
if (onSpectatorCameraScreen) {
updateSpectatorCameraQML();
}
}
// Function Name: sendToQml()
@ -459,6 +569,26 @@
case 'changeSwitchViewFromControllerPreference':
setSwitchViewFromController(message.params);
break;
case 'changeTakeSnapshotFromControllerPreference':
setTakeSnapshotFromController(message.params);
break;
case 'updateCameravFoV':
spectatorCameraConfig.vFoV = message.vFoV;
break;
case 'takeSecondaryCameraSnapshot':
maybeTakeSnapshot();
break;
case 'takeSecondaryCamera360Snapshot':
maybeTake360Snapshot();
break;
case 'openSettings':
if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false))
|| (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) {
Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
} else {
tablet.pushOntoStack("hifi/tablet/TabletGeneralPreferences.qml");
}
break;
default:
print('Unrecognized message from SpectatorCamera.qml:', JSON.stringify(message));
}
@ -469,13 +599,13 @@
// Description:
// -Called from C++ when HMD mode is changed. The argument "isHMDMode" is true if HMD is on; false otherwise.
function onHMDChanged(isHMDMode) {
if (!controllerMapping) {
if (!switchViewControllerMapping || !takeSnapshotControllerMapping) {
registerButtonMappings();
}
setDisplay(monitorShowsCameraView);
addOrRemoveButton(false, isHMDMode);
if (!isHMDMode && !showSpectatorInDesktop) {
spectatorCameraOff();
if (!isHMDMode) {
setMonitorShowsCameraView(false);
} else {
setDisplay(monitorShowsCameraView);
}
}
@ -487,7 +617,9 @@
spectatorCameraOff();
Window.domainChanged.disconnect(onDomainChanged);
Window.geometryChanged.disconnect(resizeViewFinderOverlay);
addOrRemoveButton(true, HMD.active);
Window.stillSnapshotTaken.disconnect(onStillSnapshotTaken);
Window.snapshot360Taken.disconnect(on360SnapshotTaken);
addOrRemoveButton(true);
if (tablet) {
tablet.screenChanged.disconnect(onTabletScreenChanged);
if (onSpectatorCameraScreen) {
@ -496,8 +628,11 @@
}
HMD.displayModeChanged.disconnect(onHMDChanged);
Controller.keyPressEvent.disconnect(keyPressEvent);
if (controllerMapping) {
controllerMapping.disable();
if (switchViewControllerMapping) {
switchViewControllerMapping.disable();
}
if (takeSnapshotControllerMapping) {
takeSnapshotControllerMapping.disable();
}
}
@ -511,6 +646,7 @@
// These functions will be called when the script is loaded.
var CAMERA_ON_SOUND = SoundCache.getSound(Script.resolvePath("cameraOn.wav"));
var SNAPSHOT_SOUND = SoundCache.getSound(Script.resourcesPath() + "sounds/snapshot/snap.wav");
startup();
Script.scriptEnding.connect(shutdown);

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB