mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 23:14:34 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into cloneables
This commit is contained in:
commit
900b373c96
21 changed files with 1234 additions and 130 deletions
|
@ -27,6 +27,9 @@ Original.CheckBox {
|
||||||
property bool wrap: true;
|
property bool wrap: true;
|
||||||
readonly property int checkSize: Math.max(boxSize - 8, 10)
|
readonly property int checkSize: Math.max(boxSize - 8, 10)
|
||||||
readonly property int checkRadius: 2
|
readonly property int checkRadius: 2
|
||||||
|
property string labelFontFamily: "Raleway"
|
||||||
|
property int labelFontSize: 14;
|
||||||
|
property int labelFontWeight: Font.DemiBold;
|
||||||
focusPolicy: Qt.ClickFocus
|
focusPolicy: Qt.ClickFocus
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
|
@ -105,6 +108,9 @@ Original.CheckBox {
|
||||||
contentItem: Label {
|
contentItem: Label {
|
||||||
text: checkBox.text
|
text: checkBox.text
|
||||||
color: checkBox.color
|
color: checkBox.color
|
||||||
|
font.family: checkBox.labelFontFamily;
|
||||||
|
font.pixelSize: checkBox.labelFontSize;
|
||||||
|
font.weight: checkBox.labelFontWeight;
|
||||||
x: 2
|
x: 2
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap
|
wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap
|
||||||
|
|
|
@ -4196,7 +4196,7 @@ bool Application::acceptSnapshot(const QString& urlString) {
|
||||||
QUrl url(urlString);
|
QUrl url(urlString);
|
||||||
QString snapshotPath = url.toLocalFile();
|
QString snapshotPath = url.toLocalFile();
|
||||||
|
|
||||||
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
|
SnapshotMetaData* snapshotData = DependencyManager::get<Snapshot>()->parseSnapshotData(snapshotPath);
|
||||||
if (snapshotData) {
|
if (snapshotData) {
|
||||||
if (!snapshotData->getURL().toString().isEmpty()) {
|
if (!snapshotData->getURL().toString().isEmpty()) {
|
||||||
DependencyManager::get<AddressManager>()->handleLookupString(snapshotData->getURL().toString());
|
DependencyManager::get<AddressManager>()->handleLookupString(snapshotData->getURL().toString());
|
||||||
|
@ -7600,13 +7600,15 @@ void Application::loadAvatarBrowser() const {
|
||||||
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
|
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
|
||||||
postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
|
postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
|
||||||
// Get a screenshot and save it
|
// 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());
|
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||||
|
|
||||||
// If we're not doing an animated snapshot as well...
|
// If we're not doing an animated snapshot as well...
|
||||||
if (!includeAnimated) {
|
if (!includeAnimated) {
|
||||||
// Tell the dependency manager that the capture of the still snapshot has taken place.
|
if (!path.isEmpty()) {
|
||||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
// Tell the dependency manager that the capture of the still snapshot has taken place.
|
||||||
|
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
||||||
|
}
|
||||||
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
|
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
|
||||||
// Get an animated GIF snapshot and save it
|
// Get an animated GIF snapshot and save it
|
||||||
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
|
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
|
||||||
|
@ -7616,17 +7618,23 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
|
||||||
|
|
||||||
void Application::takeSecondaryCameraSnapshot(const QString& filename) {
|
void Application::takeSecondaryCameraSnapshot(const QString& filename) {
|
||||||
postLambdaEvent([filename, this] {
|
postLambdaEvent([filename, this] {
|
||||||
QString snapshotPath = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
|
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
|
||||||
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||||
|
|
||||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, true);
|
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) {
|
void Application::shareSnapshot(const QString& path, const QUrl& href) {
|
||||||
postLambdaEvent([path, href] {
|
postLambdaEvent([path, href] {
|
||||||
// not much to do here, everything is done in snapshot code...
|
// 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 takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString());
|
||||||
void takeSecondaryCameraSnapshot(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(""));
|
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
|
// 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 "Application.h"
|
||||||
|
#include "SecondaryCamera.h"
|
||||||
|
#include <TextureCache.h>
|
||||||
|
#include <gpu/Context.h>
|
||||||
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
|
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
|
||||||
|
|
||||||
|
@ -38,7 +34,6 @@ public:
|
||||||
using JobModel = render::Job::ModelO<SecondaryCameraJob, RenderArgsPointer, Config>;
|
using JobModel = render::Job::ModelO<SecondaryCameraJob, RenderArgsPointer, Config>;
|
||||||
SecondaryCameraJob() {
|
SecondaryCameraJob() {
|
||||||
_cachedArgsPointer = std::make_shared<RenderArgs>(_cachedArgs);
|
_cachedArgsPointer = std::make_shared<RenderArgs>(_cachedArgs);
|
||||||
_entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
|
||||||
_attachedEntityPropertyFlags += PROP_POSITION;
|
_attachedEntityPropertyFlags += PROP_POSITION;
|
||||||
_attachedEntityPropertyFlags += PROP_ROTATION;
|
_attachedEntityPropertyFlags += PROP_ROTATION;
|
||||||
}
|
}
|
||||||
|
@ -60,12 +55,16 @@ public:
|
||||||
qWarning() << "ERROR: Cannot set mirror projection for SecondaryCamera without an attachedEntityId set.";
|
qWarning() << "ERROR: Cannot set mirror projection for SecondaryCamera without an attachedEntityId set.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
EntityItemPointer attachedEntity = qApp->getEntities()->getTree()->findEntityByID(_attachedEntityId);
|
||||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_attachedEntityId,
|
|
||||||
_attachedEntityPropertyFlags);
|
if (!attachedEntity) {
|
||||||
glm::vec3 mirrorPropertiesPosition = entityProperties.getPosition();
|
qWarning() << "ERROR: Cannot get EntityItemPointer for _attachedEntityId.";
|
||||||
glm::quat mirrorPropertiesRotation = entityProperties.getRotation();
|
return;
|
||||||
glm::vec3 mirrorPropertiesDimensions = entityProperties.getDimensions();
|
}
|
||||||
|
|
||||||
|
glm::vec3 mirrorPropertiesPosition = attachedEntity->getWorldPosition();
|
||||||
|
glm::quat mirrorPropertiesRotation = attachedEntity->getWorldOrientation();
|
||||||
|
glm::vec3 mirrorPropertiesDimensions = attachedEntity->getScaledDimensions();
|
||||||
glm::vec3 halfMirrorPropertiesDimensions = 0.5f * mirrorPropertiesDimensions;
|
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
|
// 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);
|
setMirrorProjection(srcViewFrustum);
|
||||||
} else {
|
} else {
|
||||||
if (!_attachedEntityId.isNull()) {
|
if (!_attachedEntityId.isNull()) {
|
||||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_attachedEntityId,
|
EntityItemPointer attachedEntity = qApp->getEntities()->getTree()->findEntityByID(_attachedEntityId);
|
||||||
_attachedEntityPropertyFlags);
|
if (!attachedEntity) {
|
||||||
srcViewFrustum.setPosition(entityProperties.getPosition());
|
qWarning() << "ERROR: Cannot get EntityItemPointer for _attachedEntityId.";
|
||||||
srcViewFrustum.setOrientation(entityProperties.getRotation());
|
return;
|
||||||
|
}
|
||||||
|
srcViewFrustum.setPosition(attachedEntity->getWorldPosition());
|
||||||
|
srcViewFrustum.setOrientation(attachedEntity->getWorldOrientation());
|
||||||
} else {
|
} else {
|
||||||
srcViewFrustum.setPosition(_position);
|
srcViewFrustum.setPosition(_position);
|
||||||
srcViewFrustum.setOrientation(_orientation);
|
srcViewFrustum.setOrientation(_orientation);
|
||||||
|
@ -155,7 +157,6 @@ private:
|
||||||
int _textureHeight;
|
int _textureHeight;
|
||||||
bool _mirrorProjection;
|
bool _mirrorProjection;
|
||||||
EntityPropertyFlags _attachedEntityPropertyFlags;
|
EntityPropertyFlags _attachedEntityPropertyFlags;
|
||||||
QSharedPointer<EntityScriptingInterface> _entityScriptingInterface;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void SecondaryCameraJobConfig::setPosition(glm::vec3 pos) {
|
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<RenderDeferredTask>("RenderDeferredTask", items);
|
||||||
}
|
}
|
||||||
task.addJob<EndSecondaryCameraFrame>("EndSecondaryCamera", cachedArg);
|
task.addJob<EndSecondaryCameraFrame>("EndSecondaryCamera", cachedArg);
|
||||||
}
|
}
|
|
@ -60,7 +60,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
||||||
// rotate the hips back to match the flying animation.
|
// rotate the hips back to match the flying animation.
|
||||||
|
|
||||||
const float TILT_ANGLE = 0.523f;
|
const float TILT_ANGLE = 0.523f;
|
||||||
const glm::quat tiltRot = glm::angleAxis(TILT_ANGLE, transformVectorFast(avatarToSensorMat, -Vectors::UNIT_X));
|
const glm::quat tiltRot = glm::angleAxis(TILT_ANGLE, glm::normalize(transformVectorFast(avatarToSensorMat, -Vectors::UNIT_X)));
|
||||||
|
|
||||||
glm::vec3 headPos;
|
glm::vec3 headPos;
|
||||||
int headIndex = myAvatar->getJointIndex("Head");
|
int headIndex = myAvatar->getJointIndex("Head");
|
||||||
|
|
|
@ -431,6 +431,10 @@ void WindowScriptingInterface::takeSecondaryCameraSnapshot(const QString& filena
|
||||||
qApp->takeSecondaryCameraSnapshot(filename);
|
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) {
|
void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) {
|
||||||
qApp->shareSnapshot(path, href);
|
qApp->shareSnapshot(path, href);
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,6 +370,18 @@ public slots:
|
||||||
*/
|
*/
|
||||||
void takeSecondaryCameraSnapshot(const QString& filename = QString());
|
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
|
/**jsdoc
|
||||||
* Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that
|
* 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.
|
* 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);
|
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
|
/**jsdoc
|
||||||
* Triggered when a snapshot submitted via {@link Window.shareSnapshot|shareSnapshot} is ready for sharing. The snapshot
|
* 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.
|
* may then be shared via the {@link Account.metaverseServerURL} Web API.
|
||||||
|
|
|
@ -132,8 +132,8 @@ void setupPreferences() {
|
||||||
// Snapshots
|
// Snapshots
|
||||||
static const QString SNAPSHOTS { "Snapshots" };
|
static const QString SNAPSHOTS { "Snapshots" };
|
||||||
{
|
{
|
||||||
auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };
|
auto getter = []()->QString { return DependencyManager::get<Snapshot>()->_snapshotsLocation.get(); };
|
||||||
auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); emit DependencyManager::get<Snapshot>()->snapshotLocationSet(value); };
|
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);
|
auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter);
|
||||||
preferences->addPreference(preference);
|
preferences->addPreference(preference);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QJsonDocument>
|
||||||
#include <QtCore/QJsonArray>
|
#include <QtCore/QJsonArray>
|
||||||
#include <QtNetwork/QHttpMultiPart>
|
#include <QtNetwork/QHttpMultiPart>
|
||||||
#include <QtGui/QImage>
|
#include <QPainter>
|
||||||
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
|
|
||||||
#include <AccountManager.h>
|
#include <AccountManager.h>
|
||||||
#include <AddressManager.h>
|
#include <AddressManager.h>
|
||||||
|
@ -31,20 +32,39 @@
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
#include <OffscreenUi.h>
|
#include <OffscreenUi.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
#include <SecondaryCamera.h>
|
||||||
|
#include <plugins/DisplayPlugin.h>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "display-plugins/CompositorHelper.h"
|
||||||
|
#include "scripting/WindowScriptingInterface.h"
|
||||||
|
#include "MainWindow.h"
|
||||||
|
#include "Snapshot.h"
|
||||||
#include "SnapshotUploader.h"
|
#include "SnapshotUploader.h"
|
||||||
|
#include "ToneMappingEffect.h"
|
||||||
|
|
||||||
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
|
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
|
||||||
// %1 <= username, %2 <= date and time, %3 <= current location
|
// %1 <= username, %2 <= date and time, %3 <= current location
|
||||||
const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2.jpg";
|
const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2.jpg";
|
||||||
|
|
||||||
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss";
|
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss";
|
||||||
const QString SNAPSHOTS_DIRECTORY = "Snapshots";
|
const QString SNAPSHOTS_DIRECTORY = "Snapshots";
|
||||||
|
|
||||||
const QString URL = "highfidelity_url";
|
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) {
|
SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
|
||||||
|
|
||||||
|
@ -78,14 +98,236 @@ QString Snapshot::saveSnapshot(QImage image, const QString& filename, const QStr
|
||||||
|
|
||||||
QFile* snapshotFile = savedFileForSnapshot(image, false, filename, pathname);
|
QFile* snapshotFile = savedFileForSnapshot(image, false, filename, pathname);
|
||||||
|
|
||||||
// we don't need the snapshot file, so close it, grab its filename and delete it
|
if (snapshotFile) {
|
||||||
snapshotFile->close();
|
// we don't need the snapshot file, so close it, grab its filename and delete it
|
||||||
|
snapshotFile->close();
|
||||||
|
|
||||||
QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath();
|
QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath();
|
||||||
|
|
||||||
delete snapshotFile;
|
delete snapshotFile;
|
||||||
|
|
||||||
return snapshotPath;
|
return snapshotPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) {
|
||||||
|
@ -123,12 +365,12 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt
|
||||||
if (!userSelectedPathname.isNull()) {
|
if (!userSelectedPathname.isNull()) {
|
||||||
snapshotFullPath = userSelectedPathname;
|
snapshotFullPath = userSelectedPathname;
|
||||||
} else {
|
} else {
|
||||||
snapshotFullPath = snapshotsLocation.get();
|
snapshotFullPath = _snapshotsLocation.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshotFullPath.isEmpty()) {
|
if (snapshotFullPath.isEmpty()) {
|
||||||
snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Choose Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
|
snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Choose Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
|
||||||
snapshotsLocation.set(snapshotFullPath);
|
_snapshotsLocation.set(snapshotFullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!snapshotFullPath.isEmpty()) { // not cancelled
|
if (!snapshotFullPath.isEmpty()) { // not cancelled
|
||||||
|
@ -140,7 +382,27 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt
|
||||||
snapshotFullPath.append(filename);
|
snapshotFullPath.append(filename);
|
||||||
|
|
||||||
QFile* imageFile = new QFile(snapshotFullPath);
|
QFile* imageFile = new QFile(snapshotFullPath);
|
||||||
imageFile->open(QIODevice::WriteOnly);
|
while (!imageFile->open(QIODevice::WriteOnly)) {
|
||||||
|
// It'd be better for the directory chooser to restore the cursor to its previous state
|
||||||
|
// after choosing a directory, but if the user has entered this codepath,
|
||||||
|
// something terrible has happened. Let's just show the user their cursor so they can get
|
||||||
|
// out of this awful state.
|
||||||
|
qApp->getApplicationCompositor().getReticleInterface()->setVisible(true);
|
||||||
|
qApp->getApplicationCompositor().getReticleInterface()->setAllowMouseCapture(true);
|
||||||
|
|
||||||
|
snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Write Error - Choose New Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
|
||||||
|
if (snapshotFullPath.isEmpty()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
_snapshotsLocation.set(snapshotFullPath);
|
||||||
|
|
||||||
|
if (!snapshotFullPath.endsWith(QDir::separator())) {
|
||||||
|
snapshotFullPath.append(QDir::separator());
|
||||||
|
}
|
||||||
|
snapshotFullPath.append(filename);
|
||||||
|
|
||||||
|
imageFile = new QFile(snapshotFullPath);
|
||||||
|
}
|
||||||
|
|
||||||
shot.save(imageFile, 0, IMAGE_QUALITY);
|
shot.save(imageFile, 0, IMAGE_QUALITY);
|
||||||
imageFile->close();
|
imageFile->close();
|
||||||
|
@ -210,9 +472,9 @@ void Snapshot::uploadSnapshot(const QString& filename, const QUrl& href) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Snapshot::getSnapshotsLocation() {
|
QString Snapshot::getSnapshotsLocation() {
|
||||||
return snapshotsLocation.get("");
|
return _snapshotsLocation.get("");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Snapshot::setSnapshotsLocation(const QString& location) {
|
void Snapshot::setSnapshotsLocation(const QString& location) {
|
||||||
snapshotsLocation.set(location);
|
_snapshotsLocation.set(location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QtGui/QImage>
|
||||||
|
|
||||||
#include <SettingHandle.h>
|
#include <SettingHandle.h>
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
@ -38,12 +40,14 @@ class Snapshot : public QObject, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
public:
|
public:
|
||||||
static QString saveSnapshot(QImage image, const QString& filename, const QString& pathname = QString());
|
Snapshot();
|
||||||
static QTemporaryFile* saveTempSnapshot(QImage image);
|
QString saveSnapshot(QImage image, const QString& filename, const QString& pathname = QString());
|
||||||
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
|
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;
|
Setting::Handle<QString> _snapshotsLocation{ "snapshotsLocation" };
|
||||||
static void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
|
void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void snapshotLocationSet(const QString& value);
|
void snapshotLocationSet(const QString& value);
|
||||||
|
@ -51,11 +55,28 @@ signals:
|
||||||
public slots:
|
public slots:
|
||||||
Q_INVOKABLE QString getSnapshotsLocation();
|
Q_INVOKABLE QString getSnapshotsLocation();
|
||||||
Q_INVOKABLE void setSnapshotsLocation(const QString& location);
|
Q_INVOKABLE void setSnapshotsLocation(const QString& location);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void takeNextSnapshot();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static QFile* savedFileForSnapshot(QImage& image,
|
QFile* savedFileForSnapshot(QImage& image,
|
||||||
bool isTemporary,
|
bool isTemporary,
|
||||||
const QString& userSelectedFilename = QString(),
|
const QString& userSelectedFilename = QString(),
|
||||||
const QString& userSelectedPathname = 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
|
#endif // hifi_Snapshot_h
|
||||||
|
|
|
@ -103,18 +103,40 @@ void SnapshotAnimated::captureFrames() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SnapshotAnimated::clearTempVariables() {
|
||||||
|
// Clear out the frame and frame delay vectors.
|
||||||
|
// Also release the memory not required to store the items.
|
||||||
|
SnapshotAnimated::snapshotAnimatedFrameVector.clear();
|
||||||
|
SnapshotAnimated::snapshotAnimatedFrameVector.squeeze();
|
||||||
|
SnapshotAnimated::snapshotAnimatedFrameDelayVector.clear();
|
||||||
|
SnapshotAnimated::snapshotAnimatedFrameDelayVector.squeeze();
|
||||||
|
// Reset the current frame timestamp
|
||||||
|
SnapshotAnimated::snapshotAnimatedTimestamp = 0;
|
||||||
|
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void SnapshotAnimated::processFrames() {
|
void SnapshotAnimated::processFrames() {
|
||||||
uint32_t width = SnapshotAnimated::snapshotAnimatedFrameVector[0].width();
|
uint32_t width = SnapshotAnimated::snapshotAnimatedFrameVector[0].width();
|
||||||
uint32_t height = SnapshotAnimated::snapshotAnimatedFrameVector[0].height();
|
uint32_t height = SnapshotAnimated::snapshotAnimatedFrameVector[0].height();
|
||||||
|
|
||||||
// Create the GIF from the temporary files
|
// Create the GIF from the temporary files
|
||||||
// Write out the header and beginning of the GIF file
|
// Write out the header and beginning of the GIF file
|
||||||
GifBegin(
|
if (!GifBegin(
|
||||||
&(SnapshotAnimated::snapshotAnimatedGifWriter),
|
&(SnapshotAnimated::snapshotAnimatedGifWriter),
|
||||||
qPrintable(SnapshotAnimated::snapshotAnimatedPath),
|
qPrintable(SnapshotAnimated::snapshotAnimatedPath),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
1); // "1" means "yes there is a delay" with this GifCreator library.
|
1)) { // "1" means "yes there is a delay" with this GifCreator library.
|
||||||
|
|
||||||
|
// We should never, ever get here. If we do, that means that writing a still JPG to the filesystem
|
||||||
|
// has succeeded, but that writing the tiny header to a GIF file in the same directory failed.
|
||||||
|
// If that happens, we _could_ throw up the "Folder Chooser" dialog like we do for still JPG images,
|
||||||
|
// but I have no way of testing whether or not that'll work or get properly exercised,
|
||||||
|
// so I'm not going to bother for now.
|
||||||
|
SnapshotAnimated::clearTempVariables();
|
||||||
|
qDebug() << "Animated snapshot header failed to write - aborting GIF processing.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (int itr = 0; itr < SnapshotAnimated::snapshotAnimatedFrameVector.size(); itr++) {
|
for (int itr = 0; itr < SnapshotAnimated::snapshotAnimatedFrameVector.size(); itr++) {
|
||||||
// Write each frame to the GIF
|
// Write each frame to the GIF
|
||||||
GifWriteFrame(&(SnapshotAnimated::snapshotAnimatedGifWriter),
|
GifWriteFrame(&(SnapshotAnimated::snapshotAnimatedGifWriter),
|
||||||
|
@ -126,15 +148,7 @@ void SnapshotAnimated::processFrames() {
|
||||||
// Write out the end of the GIF
|
// Write out the end of the GIF
|
||||||
GifEnd(&(SnapshotAnimated::snapshotAnimatedGifWriter));
|
GifEnd(&(SnapshotAnimated::snapshotAnimatedGifWriter));
|
||||||
|
|
||||||
// Clear out the frame and frame delay vectors.
|
SnapshotAnimated::clearTempVariables();
|
||||||
// Also release the memory not required to store the items.
|
|
||||||
SnapshotAnimated::snapshotAnimatedFrameVector.clear();
|
|
||||||
SnapshotAnimated::snapshotAnimatedFrameVector.squeeze();
|
|
||||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.clear();
|
|
||||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.squeeze();
|
|
||||||
// Reset the current frame timestamp
|
|
||||||
SnapshotAnimated::snapshotAnimatedTimestamp = 0;
|
|
||||||
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0;
|
|
||||||
|
|
||||||
// Update the "Share" dialog with the processed GIF.
|
// Update the "Share" dialog with the processed GIF.
|
||||||
emit SnapshotAnimated::snapshotAnimatedDM->processingGifCompleted(SnapshotAnimated::snapshotAnimatedPath);
|
emit SnapshotAnimated::snapshotAnimatedDM->processingGifCompleted(SnapshotAnimated::snapshotAnimatedPath);
|
||||||
|
|
|
@ -49,6 +49,7 @@ private:
|
||||||
|
|
||||||
static void captureFrames();
|
static void captureFrames();
|
||||||
static void processFrames();
|
static void processFrames();
|
||||||
|
static void clearTempVariables();
|
||||||
public:
|
public:
|
||||||
static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm);
|
static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm);
|
||||||
static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; };
|
static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; };
|
||||||
|
|
|
@ -57,7 +57,7 @@ void TouchscreenVirtualPadDevice::init() {
|
||||||
void TouchscreenVirtualPadDevice::resize() {
|
void TouchscreenVirtualPadDevice::resize() {
|
||||||
QScreen* eventScreen = qApp->primaryScreen();
|
QScreen* eventScreen = qApp->primaryScreen();
|
||||||
if (_screenDPIProvided != eventScreen->physicalDotsPerInch()) {
|
if (_screenDPIProvided != eventScreen->physicalDotsPerInch()) {
|
||||||
_screenWidthCenter = eventScreen->size().width() / 2;
|
_screenWidthCenter = eventScreen->availableSize().width() / 2;
|
||||||
_screenDPIScale.x = (float)eventScreen->physicalDotsPerInchX();
|
_screenDPIScale.x = (float)eventScreen->physicalDotsPerInchX();
|
||||||
_screenDPIScale.y = (float)eventScreen->physicalDotsPerInchY();
|
_screenDPIScale.y = (float)eventScreen->physicalDotsPerInchY();
|
||||||
_screenDPIProvided = eventScreen->physicalDotsPerInch();
|
_screenDPIProvided = eventScreen->physicalDotsPerInch();
|
||||||
|
@ -81,7 +81,7 @@ void TouchscreenVirtualPadDevice::setupControlsPositions(VirtualPad::Manager& vi
|
||||||
|
|
||||||
// Movement stick
|
// Movement stick
|
||||||
float margin = _screenDPI * VirtualPad::Manager::BASE_MARGIN_PIXELS / VirtualPad::Manager::DPI;
|
float margin = _screenDPI * VirtualPad::Manager::BASE_MARGIN_PIXELS / VirtualPad::Manager::DPI;
|
||||||
_fixedCenterPosition = glm::vec2( _fixedRadius + margin, eventScreen->size().height() - margin - _fixedRadius - _extraBottomMargin);
|
_fixedCenterPosition = glm::vec2( _fixedRadius + margin, eventScreen->availableSize().height() - margin - _fixedRadius - _extraBottomMargin);
|
||||||
_moveRefTouchPoint = _fixedCenterPosition;
|
_moveRefTouchPoint = _fixedCenterPosition;
|
||||||
virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint);
|
virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint);
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ void TouchscreenVirtualPadDevice::setupControlsPositions(VirtualPad::Manager& vi
|
||||||
float jumpBtnPixelSize = _screenDPI * VirtualPad::Manager::JUMP_BTN_FULL_PIXELS / VirtualPad::Manager::DPI;
|
float jumpBtnPixelSize = _screenDPI * VirtualPad::Manager::JUMP_BTN_FULL_PIXELS / VirtualPad::Manager::DPI;
|
||||||
float rightMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_RIGHT_MARGIN_PIXELS / VirtualPad::Manager::DPI;
|
float rightMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_RIGHT_MARGIN_PIXELS / VirtualPad::Manager::DPI;
|
||||||
float bottomMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS/ VirtualPad::Manager::DPI;
|
float bottomMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS/ VirtualPad::Manager::DPI;
|
||||||
_jumpButtonPosition = glm::vec2( eventScreen->size().width() - rightMargin - jumpBtnPixelSize, eventScreen->size().height() - bottomMargin - _jumpButtonRadius - _extraBottomMargin);
|
_jumpButtonPosition = glm::vec2( eventScreen->availableSize().width() - rightMargin - jumpBtnPixelSize, eventScreen->availableSize().height() - bottomMargin - _jumpButtonRadius - _extraBottomMargin);
|
||||||
virtualPadManager.setJumpButtonPosition(_jumpButtonPosition);
|
virtualPadManager.setJumpButtonPosition(_jumpButtonPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ void TouchscreenVirtualPadDevice::debugPoints(const QTouchEvent* event, QString
|
||||||
points << thisPoint;
|
points << thisPoint;
|
||||||
}
|
}
|
||||||
QScreen* eventScreen = event->window()->screen();
|
QScreen* eventScreen = event->window()->screen();
|
||||||
int midScreenX = eventScreen->size().width()/2;
|
int midScreenX = eventScreen->availableSize().width()/2;
|
||||||
int lefties = 0;
|
int lefties = 0;
|
||||||
int righties = 0;
|
int righties = 0;
|
||||||
vec2 currentPoint;
|
vec2 currentPoint;
|
||||||
|
|
|
@ -672,6 +672,7 @@
|
||||||
Menu.menuItemEvent.connect(menuItemEvent);
|
Menu.menuItemEvent.connect(menuItemEvent);
|
||||||
Window.domainConnectionRefused.connect(onDomainConnectionRefused);
|
Window.domainConnectionRefused.connect(onDomainConnectionRefused);
|
||||||
Window.stillSnapshotTaken.connect(onSnapshotTaken);
|
Window.stillSnapshotTaken.connect(onSnapshotTaken);
|
||||||
|
Window.snapshot360Taken.connect(onSnapshotTaken);
|
||||||
Window.processingGifStarted.connect(processingGif);
|
Window.processingGifStarted.connect(processingGif);
|
||||||
Window.connectionAdded.connect(connectionAdded);
|
Window.connectionAdded.connect(connectionAdded);
|
||||||
Window.connectionError.connect(connectionError);
|
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
|
// -Far clip plane distance
|
||||||
// -viewFinderOverlay: The in-world overlay that displays the spectator camera's view.
|
// -viewFinderOverlay: The in-world overlay that displays the spectator camera's view.
|
||||||
// -camera: The in-world entity that corresponds to the spectator camera.
|
// -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.
|
// -cameraRotation: The rotation of the spectator camera.
|
||||||
// -cameraPosition: The position 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.
|
// -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 spectatorCameraConfig = Render.getConfig("SecondaryCamera");
|
||||||
var viewFinderOverlay = false;
|
var viewFinderOverlay = false;
|
||||||
var camera = false;
|
var camera = false;
|
||||||
var cameraIsDynamic = false;
|
|
||||||
var cameraRotation;
|
var cameraRotation;
|
||||||
var cameraPosition;
|
var cameraPosition;
|
||||||
var glassPaneWidth = 0.16;
|
var glassPaneWidth = 0.16;
|
||||||
|
@ -70,11 +68,11 @@
|
||||||
spectatorCameraConfig.resetSizeSpectatorCamera(Window.innerWidth, Window.innerHeight);
|
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 }));
|
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({
|
camera = Entities.addEntity({
|
||||||
"angularDamping": 1,
|
"angularDamping": 0.95,
|
||||||
"damping": 1,
|
"damping": 0.95,
|
||||||
"collidesWith": "static,dynamic,kinematic,",
|
"collidesWith": "static,dynamic,kinematic,",
|
||||||
"collisionMask": 7,
|
"collisionMask": 7,
|
||||||
"dynamic": cameraIsDynamic,
|
"dynamic": false,
|
||||||
"modelURL": Script.resolvePath("spectator-camera.fbx"),
|
"modelURL": Script.resolvePath("spectator-camera.fbx"),
|
||||||
"registrationPoint": {
|
"registrationPoint": {
|
||||||
"x": 0.56,
|
"x": 0.56,
|
||||||
|
@ -89,8 +87,12 @@
|
||||||
}, true);
|
}, true);
|
||||||
spectatorCameraConfig.attachedEntityId = camera;
|
spectatorCameraConfig.attachedEntityId = camera;
|
||||||
updateOverlay();
|
updateOverlay();
|
||||||
setDisplay(monitorShowsCameraView);
|
if (!HMD.active) {
|
||||||
// Change button to active when window is first openend OR if the camera is on, false otherwise.
|
setMonitorShowsCameraView(false);
|
||||||
|
} else {
|
||||||
|
setDisplay(monitorShowsCameraView);
|
||||||
|
}
|
||||||
|
// Change button to active when window is first opened OR if the camera is on, false otherwise.
|
||||||
if (button) {
|
if (button) {
|
||||||
button.editProperties({ isActive: onSpectatorCameraScreen || camera });
|
button.editProperties({ isActive: onSpectatorCameraScreen || camera });
|
||||||
}
|
}
|
||||||
|
@ -150,17 +152,15 @@
|
||||||
// Relevant Variables:
|
// Relevant Variables:
|
||||||
// -button: The tablet button.
|
// -button: The tablet button.
|
||||||
// -buttonName: The name of the button.
|
// -buttonName: The name of the button.
|
||||||
// -showSpectatorInDesktop: Set to "true" to show the "SPECTATOR" app in desktop mode.
|
|
||||||
var button = false;
|
var button = false;
|
||||||
var buttonName = "SPECTATOR";
|
var buttonName = "SPECTATOR";
|
||||||
var showSpectatorInDesktop = false;
|
function addOrRemoveButton(isShuttingDown) {
|
||||||
function addOrRemoveButton(isShuttingDown, isHMDMode) {
|
|
||||||
if (!tablet) {
|
if (!tablet) {
|
||||||
print("Warning in addOrRemoveButton(): 'tablet' undefined!");
|
print("Warning in addOrRemoveButton(): 'tablet' undefined!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!button) {
|
if (!button) {
|
||||||
if ((isHMDMode || showSpectatorInDesktop) && !isShuttingDown) {
|
if (!isShuttingDown) {
|
||||||
button = tablet.addButton({
|
button = tablet.addButton({
|
||||||
text: buttonName,
|
text: buttonName,
|
||||||
icon: "icons/tablet-icons/spectator-i.svg",
|
icon: "icons/tablet-icons/spectator-i.svg",
|
||||||
|
@ -169,7 +169,7 @@
|
||||||
button.clicked.connect(onTabletButtonClicked);
|
button.clicked.connect(onTabletButtonClicked);
|
||||||
}
|
}
|
||||||
} else if (button) {
|
} else if (button) {
|
||||||
if ((!isHMDMode && !showSpectatorInDesktop) || isShuttingDown) {
|
if (isShuttingDown) {
|
||||||
button.clicked.disconnect(onTabletButtonClicked);
|
button.clicked.disconnect(onTabletButtonClicked);
|
||||||
tablet.removeButton(button);
|
tablet.removeButton(button);
|
||||||
button = false;
|
button = false;
|
||||||
|
@ -189,10 +189,12 @@
|
||||||
var tablet = null;
|
var tablet = null;
|
||||||
function startup() {
|
function startup() {
|
||||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||||
addOrRemoveButton(false, HMD.active);
|
addOrRemoveButton(false);
|
||||||
tablet.screenChanged.connect(onTabletScreenChanged);
|
tablet.screenChanged.connect(onTabletScreenChanged);
|
||||||
Window.domainChanged.connect(onDomainChanged);
|
Window.domainChanged.connect(onDomainChanged);
|
||||||
Window.geometryChanged.connect(resizeViewFinderOverlay);
|
Window.geometryChanged.connect(resizeViewFinderOverlay);
|
||||||
|
Window.stillSnapshotTaken.connect(onStillSnapshotTaken);
|
||||||
|
Window.snapshot360Taken.connect(on360SnapshotTaken);
|
||||||
Controller.keyPressEvent.connect(keyPressEvent);
|
Controller.keyPressEvent.connect(keyPressEvent);
|
||||||
HMD.displayModeChanged.connect(onHMDChanged);
|
HMD.displayModeChanged.connect(onHMDChanged);
|
||||||
viewFinderOverlay = false;
|
viewFinderOverlay = false;
|
||||||
|
@ -238,9 +240,7 @@
|
||||||
// 3. Camera is on; "Monitor Shows" is "HMD Preview": "url" is ""
|
// 3. Camera is on; "Monitor Shows" is "HMD Preview": "url" is ""
|
||||||
// 4. Camera is on; "Monitor Shows" is "Camera View": "url" is "resource://spectatorCameraFrame"
|
// 4. Camera is on; "Monitor Shows" is "Camera View": "url" is "resource://spectatorCameraFrame"
|
||||||
function setDisplay(showCameraView) {
|
function setDisplay(showCameraView) {
|
||||||
|
|
||||||
var url = (camera) ? (showCameraView ? "resource://spectatorCameraFrame" : "resource://hmdPreviewFrame") : "";
|
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
|
// FIXME: temporary hack to avoid setting the display texture to hmdPreviewFrame
|
||||||
// until it is the correct mono.
|
// until it is the correct mono.
|
||||||
|
@ -253,11 +253,8 @@
|
||||||
const MONITOR_SHOWS_CAMERA_VIEW_DEFAULT = false;
|
const MONITOR_SHOWS_CAMERA_VIEW_DEFAULT = false;
|
||||||
var monitorShowsCameraView = !!Settings.getValue('spectatorCamera/monitorShowsCameraView', MONITOR_SHOWS_CAMERA_VIEW_DEFAULT);
|
var monitorShowsCameraView = !!Settings.getValue('spectatorCamera/monitorShowsCameraView', MONITOR_SHOWS_CAMERA_VIEW_DEFAULT);
|
||||||
function setMonitorShowsCameraView(showCameraView) {
|
function setMonitorShowsCameraView(showCameraView) {
|
||||||
if (showCameraView === monitorShowsCameraView) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
monitorShowsCameraView = showCameraView;
|
|
||||||
setDisplay(showCameraView);
|
setDisplay(showCameraView);
|
||||||
|
monitorShowsCameraView = showCameraView;
|
||||||
Settings.setValue('spectatorCamera/monitorShowsCameraView', showCameraView);
|
Settings.setValue('spectatorCamera/monitorShowsCameraView', showCameraView);
|
||||||
}
|
}
|
||||||
function setMonitorShowsCameraViewAndSendToQml(showCameraView) {
|
function setMonitorShowsCameraViewAndSendToQml(showCameraView) {
|
||||||
|
@ -320,14 +317,14 @@
|
||||||
|
|
||||||
const SWITCH_VIEW_FROM_CONTROLLER_DEFAULT = false;
|
const SWITCH_VIEW_FROM_CONTROLLER_DEFAULT = false;
|
||||||
var switchViewFromController = !!Settings.getValue('spectatorCamera/switchViewFromController', SWITCH_VIEW_FROM_CONTROLLER_DEFAULT);
|
var switchViewFromController = !!Settings.getValue('spectatorCamera/switchViewFromController', SWITCH_VIEW_FROM_CONTROLLER_DEFAULT);
|
||||||
function setControllerMappingStatus(status) {
|
function setSwitchViewControllerMappingStatus(status) {
|
||||||
if (!controllerMapping) {
|
if (!switchViewControllerMapping) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (status) {
|
if (status) {
|
||||||
controllerMapping.enable();
|
switchViewControllerMapping.enable();
|
||||||
} else {
|
} else {
|
||||||
controllerMapping.disable();
|
switchViewControllerMapping.disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function setSwitchViewFromController(setting) {
|
function setSwitchViewFromController(setting) {
|
||||||
|
@ -335,21 +332,120 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switchViewFromController = setting;
|
switchViewFromController = setting;
|
||||||
setControllerMappingStatus(switchViewFromController);
|
setSwitchViewControllerMappingStatus(switchViewFromController);
|
||||||
Settings.setValue('spectatorCamera/switchViewFromController', setting);
|
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()
|
// Function Name: registerButtonMappings()
|
||||||
//
|
//
|
||||||
// Description:
|
// Description:
|
||||||
// -Updates controller button mappings for Spectator Camera.
|
// -Updates controller button mappings for Spectator Camera.
|
||||||
//
|
//
|
||||||
// Relevant Variables:
|
// Relevant Variables:
|
||||||
// -controllerMappingName: The name of the controller mapping.
|
// -switchViewControllerMappingName: The name of the controller mapping.
|
||||||
// -controllerMapping: The controller mapping itself.
|
// -switchViewControllerMapping: The controller mapping itself.
|
||||||
|
// -takeSnapshotControllerMappingName: The name of the controller mapping.
|
||||||
|
// -takeSnapshotControllerMapping: The controller mapping itself.
|
||||||
// -controllerType: "OculusTouch", "Vive", "Other".
|
// -controllerType: "OculusTouch", "Vive", "Other".
|
||||||
var controllerMappingName;
|
var switchViewControllerMapping;
|
||||||
var controllerMapping;
|
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";
|
var controllerType = "Other";
|
||||||
function registerButtonMappings() {
|
function registerButtonMappings() {
|
||||||
var VRDevices = Controller.getDeviceNames().toString();
|
var VRDevices = Controller.getDeviceNames().toString();
|
||||||
|
@ -359,30 +455,32 @@
|
||||||
} else if (VRDevices.indexOf("OculusTouch") !== -1) {
|
} else if (VRDevices.indexOf("OculusTouch") !== -1) {
|
||||||
controllerType = "OculusTouch";
|
controllerType = "OculusTouch";
|
||||||
} else {
|
} else {
|
||||||
sendToQml({ method: 'updateControllerMappingCheckbox', setting: switchViewFromController, controller: controllerType });
|
sendToQml({
|
||||||
|
method: 'updateControllerMappingCheckbox',
|
||||||
|
switchViewSetting: switchViewFromController,
|
||||||
|
takeSnapshotSetting: takeSnapshotFromController,
|
||||||
|
controller: controllerType
|
||||||
|
});
|
||||||
return; // Neither Vive nor Touch detected
|
return; // Neither Vive nor Touch detected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controllerMappingName = 'Hifi-SpectatorCamera-Mapping';
|
if (!switchViewControllerMapping) {
|
||||||
controllerMapping = Controller.newMapping(controllerMappingName);
|
registerSwitchViewControllerMapping();
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setControllerMappingStatus(switchViewFromController);
|
setSwitchViewControllerMappingStatus(switchViewFromController);
|
||||||
sendToQml({ method: 'updateControllerMappingCheckbox', setting: switchViewFromController, controller: controllerType });
|
|
||||||
|
if (!takeSnapshotControllerMapping) {
|
||||||
|
registerTakeSnapshotControllerMapping();
|
||||||
|
}
|
||||||
|
setTakeSnapshotControllerMappingStatus(switchViewFromController);
|
||||||
|
|
||||||
|
sendToQml({
|
||||||
|
method: 'updateControllerMappingCheckbox',
|
||||||
|
switchViewSetting: switchViewFromController,
|
||||||
|
takeSnapshotSetting: takeSnapshotFromController,
|
||||||
|
controller: controllerType
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function Name: onTabletButtonClicked()
|
// Function Name: onTabletButtonClicked()
|
||||||
|
@ -393,7 +491,7 @@
|
||||||
// Relevant Variables:
|
// Relevant Variables:
|
||||||
// -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML
|
// -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML
|
||||||
// -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app.
|
// -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;
|
var onSpectatorCameraScreen = false;
|
||||||
function onTabletButtonClicked() {
|
function onTabletButtonClicked() {
|
||||||
if (!tablet) {
|
if (!tablet) {
|
||||||
|
@ -405,18 +503,26 @@
|
||||||
tablet.gotoHomeScreen();
|
tablet.gotoHomeScreen();
|
||||||
} else {
|
} else {
|
||||||
tablet.loadQMLSource(SPECTATOR_CAMERA_QML_SOURCE);
|
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()
|
// Function Name: onTabletScreenChanged()
|
||||||
//
|
//
|
||||||
// Description:
|
// Description:
|
||||||
|
@ -429,6 +535,10 @@
|
||||||
if (button) {
|
if (button) {
|
||||||
button.editProperties({ isActive: onSpectatorCameraScreen || camera });
|
button.editProperties({ isActive: onSpectatorCameraScreen || camera });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (onSpectatorCameraScreen) {
|
||||||
|
updateSpectatorCameraQML();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function Name: sendToQml()
|
// Function Name: sendToQml()
|
||||||
|
@ -459,6 +569,26 @@
|
||||||
case 'changeSwitchViewFromControllerPreference':
|
case 'changeSwitchViewFromControllerPreference':
|
||||||
setSwitchViewFromController(message.params);
|
setSwitchViewFromController(message.params);
|
||||||
break;
|
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:
|
default:
|
||||||
print('Unrecognized message from SpectatorCamera.qml:', JSON.stringify(message));
|
print('Unrecognized message from SpectatorCamera.qml:', JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
@ -469,13 +599,13 @@
|
||||||
// Description:
|
// Description:
|
||||||
// -Called from C++ when HMD mode is changed. The argument "isHMDMode" is true if HMD is on; false otherwise.
|
// -Called from C++ when HMD mode is changed. The argument "isHMDMode" is true if HMD is on; false otherwise.
|
||||||
function onHMDChanged(isHMDMode) {
|
function onHMDChanged(isHMDMode) {
|
||||||
if (!controllerMapping) {
|
if (!switchViewControllerMapping || !takeSnapshotControllerMapping) {
|
||||||
registerButtonMappings();
|
registerButtonMappings();
|
||||||
}
|
}
|
||||||
setDisplay(monitorShowsCameraView);
|
if (!isHMDMode) {
|
||||||
addOrRemoveButton(false, isHMDMode);
|
setMonitorShowsCameraView(false);
|
||||||
if (!isHMDMode && !showSpectatorInDesktop) {
|
} else {
|
||||||
spectatorCameraOff();
|
setDisplay(monitorShowsCameraView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,7 +617,9 @@
|
||||||
spectatorCameraOff();
|
spectatorCameraOff();
|
||||||
Window.domainChanged.disconnect(onDomainChanged);
|
Window.domainChanged.disconnect(onDomainChanged);
|
||||||
Window.geometryChanged.disconnect(resizeViewFinderOverlay);
|
Window.geometryChanged.disconnect(resizeViewFinderOverlay);
|
||||||
addOrRemoveButton(true, HMD.active);
|
Window.stillSnapshotTaken.disconnect(onStillSnapshotTaken);
|
||||||
|
Window.snapshot360Taken.disconnect(on360SnapshotTaken);
|
||||||
|
addOrRemoveButton(true);
|
||||||
if (tablet) {
|
if (tablet) {
|
||||||
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
||||||
if (onSpectatorCameraScreen) {
|
if (onSpectatorCameraScreen) {
|
||||||
|
@ -496,8 +628,11 @@
|
||||||
}
|
}
|
||||||
HMD.displayModeChanged.disconnect(onHMDChanged);
|
HMD.displayModeChanged.disconnect(onHMDChanged);
|
||||||
Controller.keyPressEvent.disconnect(keyPressEvent);
|
Controller.keyPressEvent.disconnect(keyPressEvent);
|
||||||
if (controllerMapping) {
|
if (switchViewControllerMapping) {
|
||||||
controllerMapping.disable();
|
switchViewControllerMapping.disable();
|
||||||
|
}
|
||||||
|
if (takeSnapshotControllerMapping) {
|
||||||
|
takeSnapshotControllerMapping.disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,6 +646,7 @@
|
||||||
|
|
||||||
// These functions will be called when the script is loaded.
|
// These functions will be called when the script is loaded.
|
||||||
var CAMERA_ON_SOUND = SoundCache.getSound(Script.resolvePath("cameraOn.wav"));
|
var CAMERA_ON_SOUND = SoundCache.getSound(Script.resolvePath("cameraOn.wav"));
|
||||||
|
var SNAPSHOT_SOUND = SoundCache.getSound(Script.resourcesPath() + "sounds/snapshot/snap.wav");
|
||||||
startup();
|
startup();
|
||||||
Script.scriptEnding.connect(shutdown);
|
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