mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-09 15:33:02 +02:00
Merge pull request #13141 from zfox23/spectatorCameraImprovements
Spectator Camera V2
This commit is contained in:
commit
585957409d
17 changed files with 1168 additions and 106 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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(""));
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
//
|
||||
}
|
BIN
unpublishedScripts/marketplace/spectator-camera/processing.gif
Normal file
BIN
unpublishedScripts/marketplace/spectator-camera/processing.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
unpublishedScripts/marketplace/spectator-camera/snap.wav
Normal file
BIN
unpublishedScripts/marketplace/spectator-camera/snap.wav
Normal file
Binary file not shown.
|
@ -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"
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
BIN
unpublishedScripts/marketplace/spectator-camera/static.gif
Normal file
BIN
unpublishedScripts/marketplace/spectator-camera/static.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 899 KiB |
Loading…
Reference in a new issue