content/hifi-content/zfox/tabletCam/v1.2/tabletCam.js
2022-02-14 02:04:11 +01:00

532 lines
19 KiB
JavaScript

"use strict";
/*jslint vars:true, plusplus:true, forin:true*/
/*global Tablet, Script, */
/* eslint indent: ["error", 4, { "outerIIFEBody": 1 }] */
//
// tabletCam.js
//
// Created by Zach Fox on 2018-12-07
// 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
//
(function () { // BEGIN LOCAL_SCOPE
var AppUi = Script.require('appUi');
var secondaryCameraConfig = Render.getConfig("SecondaryCamera");
var tabletCamEntity = false;
var previousNearClipDistance = false;
var previousFarClipDistance = false;
var previousvFoV = false;
var prevToneMapping = 0;
var NEAR_CLIP_DISTANCE = 0.001;
var FAR_CLIP_DISTANCE = 16384;
var vFoV = 60;
var secondaryCameraResolutionWidth = 1000;
var secondaryCameraResolutionHeight = secondaryCameraResolutionWidth / aspectRatio;
var PREVIEW_SHORT_SIDE_RESOLUTION = 400;
var secondaryCameraResolutionPreviewWidth = PREVIEW_SHORT_SIDE_RESOLUTION;
var secondaryCameraResolutionPreviewHeight = PREVIEW_SHORT_SIDE_RESOLUTION / aspectRatio;
var tabletCamRunning = false;
function enableTabletCam() {
wireSignals(true);
setTakePhotoControllerMappingStatus(true);
secondaryCameraConfig.enableSecondaryCameraRenderConfigs(true);
setSnapshotQuality(snapshotQuality);
tabletCamEntity = Entities.addEntity({
"type": "Box",
"localSize": {
"x": 0.02,
"y": 0.02,
"z": 0.02
},
"localPosition": {
"x": 0,
"y": HMD.active ? 0.18 : (frontCamInUse ? -0.18 : -0.02),
"z": HMD.active ? -0.02 : (frontCamInUse ? 1 : 0.05)
},
"localRotation": {
"x": 0,
"y": frontCamInUse ? 0 : 1,
"z": 0,
"w": frontCamInUse ? 1 : 0
},
"damping": 0,
"angularDamping": 0,
"collisionless": true,
"ignoreForCollisions": true,
"shape": "Cube",
"color": {
"red": 0,
"green": 0,
"blue": 0
},
"queryAACube": {
"x": -0.08660253882408142,
"y": -0.08660253882408142,
"z": -0.08660253882408142,
"scale": 0.17320507764816284
},
"grab": {
"grabbable": false,
"equippableLeftRotation": {
"x": -0.0000152587890625,
"y": -0.0000152587890625,
"z": -0.0000152587890625,
"w": 1
},
"equippableRightRotation": {
"x": -0.0000152587890625,
"y": -0.0000152587890625,
"z": -0.0000152587890625,
"w": 1
}
},
"clientOnly": true,
"isVisibleInSecondaryCamera": false,
"name": "Tablet Cam Camera Entity",
"parentID": HMD.active ? HMD.tabletID : MyAvatar.sessionUUID,
"parentJointIndex": HMD.active ? 65535 : MyAvatar.getJointIndex("HeadTop_End"),
"visible": false
}, true);
previousFarClipDistance = secondaryCameraConfig.farClipPlaneDistance;
previousNearClipDistance = secondaryCameraConfig.nearClipPlaneDistance;
previousvFoV = secondaryCameraConfig.vFoV;
prevToneMapping = Render.getConfig("SecondaryCameraJob.ToneMapping").curve;
secondaryCameraConfig.nearClipPlaneDistance = NEAR_CLIP_DISTANCE;
secondaryCameraConfig.farClipPlaneDistance = FAR_CLIP_DISTANCE;
secondaryCameraConfig.vFoV = vFoV;
var toneMappingCurve = HMD.active ? 0 : 1;
Render.getConfig("SecondaryCameraJob.ToneMapping").curve = toneMappingCurve;
secondaryCameraConfig.attachedEntityId = tabletCamEntity;
tabletCamRunning = true;
updateTabletCamOverlay();
// Remove the existing tabletCamEntity model from the domain if one exists.
// It's easy for this to happen if the user crashes while the Tablet Cam is on.
// We do this down here (after the new one is rezzed) so that we don't accidentally delete
// the newly-rezzed model.
var entityIDs = Entities.findEntitiesByName("Tablet Cam Camera Entity", MyAvatar.position, 100, false);
entityIDs.forEach(function (currentEntityID) {
var currentEntityOwner = Entities.getEntityProperties(currentEntityID, ['owningAvatarID']).owningAvatarID;
if (currentEntityOwner === MyAvatar.sessionUUID && currentEntityID !== tabletCamEntity) {
Entities.deleteEntity(currentEntityID);
}
});
}
var frontCamInUse = true;
function switchCams() {
if (!tabletCamEntity || (HMD.active && !tabletCamOverlay)) {
console.log("User tried to switch cams, but TabletCam wasn't ready!");
return;
}
frontCamInUse = !frontCamInUse;
Entities.editEntity(tabletCamEntity, {
"localRotation": {
"x": 0,
"y": frontCamInUse ? 0 : 1,
"z": 0,
"w": frontCamInUse ? 1 : 0
},
"localPosition": {
"x": 0,
"y": HMD.active ? 0.18 : (frontCamInUse ? -0.18 : -0.02),
"z": HMD.active ? -0.02 : (frontCamInUse ? 1 : 0.05)
},
});
Overlays.editOverlay(tabletCamOverlay, {
"dimensions": {
"x": frontCamInUse ? tabletCamOverlayDim.x : -tabletCamOverlayDim.x,
"y": tabletCamOverlayDim.y,
"z": 0
}
});
}
function disableTabletCam() {
function deleteTabletCamEntity() {
if (flash) {
Entities.deleteEntity(flash);
flash = false;
}
if (tabletCamEntity) {
Entities.deleteEntity(tabletCamEntity);
tabletCamEntity = false;
}
}
wireSignals(false);
setTakePhotoControllerMappingStatus(false);
Render.getConfig("SecondaryCameraJob.ToneMapping").curve = prevToneMapping;
if (tabletCamRunning) {
secondaryCameraConfig.farClipPlaneDistance = previousFarClipDistance;
secondaryCameraConfig.nearClipPlaneDistance = previousNearClipDistance;
secondaryCameraConfig.vFoV = previousvFoV;
secondaryCameraConfig.attachedEntityId = false;
secondaryCameraConfig.enableSecondaryCameraRenderConfigs(false);
}
deleteTabletCamEntity();
if (tabletCamOverlay) {
Overlays.deleteOverlay(tabletCamOverlay);
}
tabletCamOverlay = false;
}
var tabletCamOverlayWidth = 0.292;
var tabletCamOverlayHeight = 0.292;
var tabletCamOverlayDim = { x: tabletCamOverlayWidth, y: -tabletCamOverlayHeight };
var tabletCamOverlay = false;
function updateTabletCamOverlay() {
if (!HMD.active) {
return;
}
if (tabletCamOverlay) {
Overlays.deleteOverlay(tabletCamOverlay);
}
tabletCamOverlay = Overlays.addOverlay("image3d", {
url: "resource://spectatorCameraFrame",
emissive: true,
parentID: HMD.tabletID,
alpha: 1,
localRotation: { w: 1, x: 0, y: 0, z: 0 },
localPosition: { x: 0, y: 0.0225, z: -0.01 },
dimensions: tabletCamOverlayDim
});
}
function onDomainChanged() {
if (tabletCamRunning) {
disableTabletCam();
}
}
function tabletVisibilityChanged() {
if (!ui.tablet.tabletShown && ui.isOpen) {
ui.close();
}
}
var flash = false;
function setFlashStatus(enabled) {
if (!tabletCamEntity) {
return;
}
var cameraPosition = Entities.getEntityProperties(tabletCamEntity, ["positon"]).position;
if (enabled) {
Audio.playSound(SOUND_FLASH_ON, {
position: cameraPosition,
localOnly: true,
volume: 0.8
});
flash = Entities.addEntity({
"collidesWith": "",
"collisionMask": 0,
"color": {
"blue": 173,
"green": 252,
"red": 255
},
"cutoff": 90,
"dimensions": {
"x": 4,
"y": 4,
"z": 4
},
"dynamic": false,
"falloffRadius": 0.20000000298023224,
"intensity": 27,
"isSpotlight": true,
"localRotation": { w: 1, x: 0, y: 0, z: 0 },
"localPosition": { x: 0, y: 0, z: -0.005 },
"name": "Tablet Camera Flash",
"type": "Light",
"parentID": tabletCamEntity,
}, true);
} else {
if (flash) {
Audio.playSound(SOUND_FLASH_OFF, {
position: cameraPosition,
localOnly: true,
volume: 0.8
});
Entities.deleteEntity(flash);
flash = false;
}
}
}
function takePhoto() {
var tabletCamEntityPosition = Entities.getEntityProperties(tabletCamEntity, ["position"]).position;
Audio.playSound(SOUND_SNAPSHOT, {
position: { x: tabletCamEntityPosition.x, y: tabletCamEntityPosition.y, z: tabletCamEntityPosition.z },
localOnly: true,
volume: 1.0
});
Window.takeSecondaryCameraSnapshot();
}
function maybeTakePhoto() {
if (tabletCamEntity) {
Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 0;
secondaryCameraConfig.resetSizeSpectatorCamera(secondaryCameraResolutionWidth, secondaryCameraResolutionHeight);
// Wait a moment before taking the photo for the
// tonemapping curve and resolution to update
Script.setTimeout(function () {
takePhoto();
}, 250);
}
}
var snapshotQuality = Settings.getValue("tabletCam/quality", "normal");
function setSnapshotQuality(quality) {
snapshotQuality = quality;
Settings.setValue("tabletCam/quality", snapshotQuality);
var shortSideTargetResolution = 1000;
if (snapshotQuality === "low") {
shortSideTargetResolution = 500;
} else if (snapshotQuality === "normal") {
shortSideTargetResolution = 1000;
} else if (snapshotQuality === "high") {
shortSideTargetResolution = 2160;
} else if (snapshotQuality === "extreme") {
shortSideTargetResolution = 4320;
}
if (tallOrientation) {
secondaryCameraResolutionWidth = shortSideTargetResolution;
secondaryCameraResolutionHeight = secondaryCameraResolutionWidth / aspectRatio;
secondaryCameraResolutionPreviewWidth = PREVIEW_SHORT_SIDE_RESOLUTION;
secondaryCameraResolutionPreviewHeight = secondaryCameraResolutionPreviewWidth / aspectRatio;
} else {
secondaryCameraResolutionHeight = shortSideTargetResolution;
secondaryCameraResolutionWidth = secondaryCameraResolutionHeight / aspectRatio;
secondaryCameraResolutionPreviewHeight = PREVIEW_SHORT_SIDE_RESOLUTION;
secondaryCameraResolutionPreviewWidth = secondaryCameraResolutionPreviewHeight / aspectRatio;
}
secondaryCameraConfig.resetSizeSpectatorCamera(secondaryCameraResolutionPreviewWidth, secondaryCameraResolutionPreviewHeight);
}
var aspectRatio = parseFloat(Settings.getValue("tabletCam/aspectRatio", "0.8"));
function setAspectRatio(ratio) {
aspectRatio = ratio;
Settings.setValue("tabletCam/aspectRatio", aspectRatio);
setSnapshotQuality(snapshotQuality);
}
var tallOrientation = true;
function setOrientation(orientation) {
tallOrientation = orientation;
setSnapshotQuality(snapshotQuality);
}
function photoDirChanged(snapshotPath) {
Window.browseDirChanged.disconnect(photoDirChanged);
if (snapshotPath !== "") { // not cancelled
Snapshot.setSnapshotsLocation(snapshotPath);
ui.sendMessage({
method: "photoDirectoryChanged",
photoDirectory: snapshotPath
});
}
}
function fromQml(message) {
switch (message.method) {
case 'switchCams':
setFlashStatus(false);
switchCams();
break;
case 'switchOrientation':
setOrientation(!tallOrientation);
break;
case 'setFlashStatus':
setFlashStatus(message.enabled);
break;
case 'takePhoto':
maybeTakePhoto();
break;
case 'updateCameravFoV':
vFoV = message.vFoV;
secondaryCameraConfig.vFoV = vFoV;
break;
case 'setSnapshotQuality':
setSnapshotQuality(message.quality);
break;
case 'setAspectRatio':
setAspectRatio(message.aspectRatio);
break;
case 'activeViewChanged':
if (!ui.isOpen) {
return;
}
if (message.activeView === "settingsView" || message.activeView === "reviewView") {
disableTabletCam();
} else if (message.activeView === "mainView") {
enableTabletCam();
}
break;
case 'setPhotoDirectory':
Window.browseDirChanged.connect(photoDirChanged);
Window.browseDirAsync("Choose Photo Directory", "", "");
break;
default:
print('Unrecognized message from TabletCam.qml.');
}
}
function setTakePhotoControllerMappingStatus(status) {
if (!takePhotoControllerMapping) {
return;
}
if (status) {
takePhotoControllerMapping.enable();
} else {
takePhotoControllerMapping.disable();
}
}
var takePhotoControllerMapping;
var takePhotoControllerMappingName = 'Hifi-TabletCam-Mapping-TakePhoto';
function registerTakePhotoControllerMapping() {
takePhotoControllerMapping = Controller.newMapping(takePhotoControllerMappingName);
if (controllerType === "OculusTouch") {
takePhotoControllerMapping.from(Controller.Standard.RS).to(function (value) {
if (value === 1.0) {
maybeTakePhoto();
}
return;
});
} else if (controllerType === "Vive") {
takePhotoControllerMapping.from(Controller.Standard.RightPrimaryThumb).to(function (value) {
if (value === 1.0) {
maybeTakePhoto();
}
return;
});
}
}
var controllerType = "Other";
function registerButtonMappings() {
var VRDevices = Controller.getDeviceNames().toString();
if (VRDevices) {
if (VRDevices.indexOf("Vive") !== -1) {
controllerType = "Vive";
} else if (VRDevices.indexOf("OculusTouch") !== -1) {
controllerType = "OculusTouch";
} else {
return; // Neither Vive nor Touch detected
}
}
if (!takePhotoControllerMapping) {
registerTakePhotoControllerMapping();
}
}
function onHMDChanged(isHMDMode) {
registerButtonMappings();
disableTabletCam();
}
var cameraRollPaths = JSON.parse(Settings.getValue("tabletCam/cameraRollPaths", '{"paths": []}'));
function onStillSnapshotTaken(path) {
var tempObject = {};
tempObject.imagePath = "file:///" + path;
cameraRollPaths.paths.unshift(tempObject);
if (cameraRollPaths.paths.length > 15) {
cameraRollPaths.paths.pop();
}
Settings.setValue("tabletCam/cameraRollPaths", JSON.stringify(cameraRollPaths));
if (!HMD.active) {
Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 1;
}
secondaryCameraConfig.resetSizeSpectatorCamera(secondaryCameraResolutionPreviewWidth, secondaryCameraResolutionPreviewHeight);
ui.sendMessage({
method: 'stillSnapshotTaken',
lastStillSnapshotPath: tempObject.imagePath
});
}
var signalsWired = false;
function wireSignals(shouldWire) {
if (signalsWired === shouldWire) {
return;
}
signalsWired = shouldWire;
if (shouldWire) {
Window.stillSnapshotTaken.connect(onStillSnapshotTaken);
} else {
Window.stillSnapshotTaken.disconnect(onStillSnapshotTaken);
}
}
var ui;
function startup() {
ui = new AppUi({
buttonName: "CAMERA",
home: Script.resolvePath("./TabletCam.qml"),
// Selfie by Path Lord from the Noun Project
graphicsDirectory: Script.resolvePath("./"),
onOpened: enableTabletCam,
onClosed: disableTabletCam,
onMessage: fromQml
});
Window.domainChanged.connect(onDomainChanged);
ui.tablet.tabletShownChanged.connect(tabletVisibilityChanged);
HMD.displayModeChanged.connect(onHMDChanged);
registerButtonMappings();
}
startup();
function shutdown() {
disableTabletCam();
Window.domainChanged.disconnect(onDomainChanged);
ui.tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
HMD.displayModeChanged.disconnect(onHMDChanged);
if (takePhotoControllerMapping) {
takePhotoControllerMapping.disable();
}
wireSignals(false);
}
Script.scriptEnding.connect(shutdown);
var SOUND_SNAPSHOT = SoundCache.getSound(Script.resolvePath("snap.wav"));
var SOUND_FLASH_ON = SoundCache.getSound(Script.resolvePath("flashOn.wav"));
var SOUND_FLASH_OFF = SoundCache.getSound(Script.resolvePath("flashOff.wav"));
}()); // END LOCAL_SCOPE