+ Throw the camera in the air to take a 360 degrees snapshot!
+
+
+
+
+
+
You can also click on the thumbstick on your right controller.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/applications/cam360/cam360.js b/applications/cam360/cam360.js
new file mode 100644
index 0000000..fb42aec
--- /dev/null
+++ b/applications/cam360/cam360.js
@@ -0,0 +1,605 @@
+"use strict";
+//
+// cam360.js
+//
+// Created by Zach Fox on 2018-10-26
+// Copyright 2018 High Fidelity, Inc.
+// Copyright 2022, Overte e.V.
+//
+// Application to take 360 degrees photo by throwing a camera in the air (as in Ready Player One (RPO)) or as a standard positionned camera.
+// version 2.0
+//
+// 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
+
+ // Function Name: inFrontOf()
+ // Description:
+ // - Returns the position in front of the given "position" argument, where the forward vector is based off
+ // the "orientation" argument and the amount in front is based off the "distance" argument.
+ function inFrontOf(distance, position, orientation) {
+ return Vec3.sum(position || MyAvatar.position,
+ Vec3.multiply(distance, Quat.getForward(orientation || MyAvatar.orientation)));
+ }
+
+ // Function Name: rpo360On()
+ var CAMERA_NAME = "CAM360 Camera";
+ var SETTING_LAST_360_CAPTURE = "overte_app_cam360_last_capture";
+ var secondaryCameraConfig = Render.getConfig("SecondaryCamera");
+ var camera = false;
+ var cameraRotation;
+ var cameraPosition;
+ var cameraGravity = {x: 0, y: -5, z: 0};
+ var velocityLoopInterval = false;
+ var isThrowMode = true;
+
+ function rpo360On() {
+ // Rez the camera model, and attach
+ // the secondary camera to the rezzed model.
+ cameraRotation = MyAvatar.orientation;
+ cameraPosition = inFrontOf(1.0, Vec3.sum(MyAvatar.position, { x: 0, y: 0.3, z: 0 }));
+ var properties;
+ var hostType = "";
+ if (isThrowMode) {
+ properties = {
+ "angularDamping": 0.08,
+ "canCastShadow": false,
+ "damping": 0.01,
+ "collisionMask": 7,
+ "modelURL": Script.resolvePath("resources/models/cam360white.fst"),
+ "name": CAMERA_NAME,
+ "rotation": cameraRotation,
+ "position": cameraPosition,
+ "shapeType": "simple-compound",
+ "type": "Model",
+ "grab": {
+ "grabbable": true
+ },
+ "script": Script.resolvePath("grabDetection.js"),
+ "userData": "",
+ "isVisibleInSecondaryCamera": false,
+ "gravity": cameraGravity,
+ "dynamic": true
+ };
+ hostType = "local";
+ } else {
+ properties = {
+ "canCastShadow": false,
+ "collisionMask": 7,
+ "modelURL": Script.resolvePath("resources/models/cam360black.fst"),
+ "name": CAMERA_NAME,
+ "rotation": cameraRotation,
+ "position": cameraPosition,
+ "shapeType": "sphere",
+ "type": "Model",
+ "grab": {
+ "grabbable": true
+ },
+ "userData": "",
+ "isVisibleInSecondaryCamera": false
+ };
+ hostType = "avatar";
+ }
+
+ camera = Entities.addEntity(properties, hostType);
+ secondaryCameraConfig.attachedEntityId = camera;
+
+ // Play a little sound to let the user know we've rezzed the camera
+ Audio.playSound(SOUND_CAMERA_ON, {
+ "volume": 0.15,
+ "position": cameraPosition,
+ "localOnly": true
+ });
+
+ // Remove the existing camera model from the domain if one exists.
+ // It's easy for this to happen if the user crashes while the RPO360 Camera 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(CAMERA_NAME, MyAvatar.position, 100, false);
+ entityIDs.forEach(function (currentEntityID) {
+ var currentEntityOwner = Entities.getEntityProperties(currentEntityID, ['owningAvatarID']).owningAvatarID;
+ if (currentEntityOwner === MyAvatar.sessionUUID && currentEntityID !== camera) {
+ Entities.deleteEntity(currentEntityID);
+ }
+ });
+
+ setTakePhotoControllerMappingStatus();
+
+ // Start the velocity loop interval at 70ms
+ // This is used to determine when the 360 photo should be snapped
+ if (isThrowMode) {
+ velocityLoopInterval = Script.setInterval(velocityLoop, 70);
+ }
+ }
+
+ // Function Name: velocityLoop()
+ var hasBeenThrown = false;
+ var snapshotVelocity = false;
+ var snapshotAngularVelocity = false;
+ var velocityWasPositive = false;
+ var cameraReleaseTime = false;
+ var MIN_AIRTIME_MS = 500;
+ var flash = false;
+ var useFlash = false;
+ function velocityLoop() {
+ // Get the velocity and angular velocity of the camera model
+ var properties = Entities.getEntityProperties(camera, ["velocity", "angularVelocity", "userData"]);
+ var velocity = properties.velocity;
+ var angularVelocity = properties.angularVelocity;
+ var releasedState = properties.userData;
+
+ if (releasedState === "RELEASED" && !hasBeenThrown) {
+ hasBeenThrown = true;
+ // Record the time at which a user has thrown the camera
+ cameraReleaseTime = Date.now();
+ }
+
+ // If we've been thrown UP...
+ if (hasBeenThrown && velocity.y > 0) {
+ // Set this flag to true
+ velocityWasPositive = true;
+ }
+
+ // If we've been thrown UP in the past, but now we're coming DOWN...
+ if (hasBeenThrown && velocityWasPositive && velocity.y < 0) {
+ // Reset the state machine
+ hasBeenThrown = false;
+ velocityWasPositive = false;
+ // Don't take a snapshot if the camera hasn't been in the air for very long
+ if (Date.now() - cameraReleaseTime <= MIN_AIRTIME_MS) {
+ Entities.editEntity(camera, {
+ "userData": ""
+ });
+ return;
+ }
+ // Save these properties so that the camera falls realistically
+ // after it's taken the 360 snapshot
+ snapshotVelocity = velocity;
+ snapshotAngularVelocity = angularVelocity;
+ // Freeze the camera model and make it not grabbable
+ Entities.editEntity(camera, {
+ "velocity": {"x": 0, "y": 0, "z": 0},
+ "angularVelocity": {"x": 0, "y": 0, "z": 0},
+ "gravity": {"x": 0, "y": 0, "z": 0},
+ "grab": {
+ "grabbable": false
+ },
+ "userData": ""
+ });
+ // Add a "flash" to the camera that illuminates the ground below the camera
+ if (useFlash) {
+ flash = Entities.addEntity({
+ "collidesWith": "",
+ "collisionMask": 0,
+ "color": {
+ "blue": 173,
+ "green": 252,
+ "red": 255
+ },
+ "dimensions": {
+ "x": 100,
+ "y": 100,
+ "z": 100
+ },
+ "dynamic": false,
+ "falloffRadius": 10,
+ "intensity": 1,
+ "isSpotlight": false,
+ "localRotation": { w: 1, x: 0, y: 0, z: 0 },
+ "name": CAMERA_NAME + "_Flash",
+ "type": "Light",
+ "parentID": camera
+ }, "local");
+ }
+ // Take the snapshot!
+ maybeTake360Snapshot();
+ }
+ }
+
+ function capture() {
+ if (!isThrowMode) {
+ if (useFlash) {
+ flash = Entities.addEntity({
+ "collidesWith": "",
+ "collisionMask": 0,
+ "color": {
+ "blue": 173,
+ "green": 252,
+ "red": 255
+ },
+ "dimensions": {
+ "x": 100,
+ "y": 100,
+ "z": 100
+ },
+ "dynamic": false,
+ "falloffRadius": 10,
+ "intensity": 1,
+ "isSpotlight": false,
+ "localRotation": { w: 1, x: 0, y: 0, z: 0 },
+ "name": CAMERA_NAME + "_Flash",
+ "type": "Light",
+ "parentID": camera
+ }, "avatar");
+ }
+ // Take the snapshot!
+ maybeTake360Snapshot();
+ }
+ }
+
+ // Function Name: rpo360Off()
+ var WAIT_AFTER_DOMAIN_SWITCH_BEFORE_CAMERA_DELETE_MS = 1 * 1000;
+ function rpo360Off(isChangingDomains) {
+ if (velocityLoopInterval) {
+ Script.clearInterval(velocityLoopInterval);
+ velocityLoopInterval = false;
+ }
+
+ function deleteCamera() {
+ if (flash) {
+ Entities.deleteEntity(flash);
+ flash = false;
+ }
+ if (camera) {
+ Entities.deleteEntity(camera);
+ camera = false;
+ }
+ //buttonActive(ui.isOpen);
+ }
+
+ secondaryCameraConfig.attachedEntityId = false;
+ if (camera) {
+ // Workaround for Avatar Entities not immediately having properties after
+ // the "Window.domainChanged()" signal is emitted.
+ // May no longer be necessary; untested...
+ if (isChangingDomains) {
+ Script.setTimeout(function () {
+ deleteCamera();
+ rpo360On();
+ }, WAIT_AFTER_DOMAIN_SWITCH_BEFORE_CAMERA_DELETE_MS);
+ } else {
+ deleteCamera();
+ }
+ }
+ setTakePhotoControllerMappingStatus();
+ }
+
+ var isCurrentlyTaking360Snapshot = false;
+ var processing360Snapshot = false;
+ function maybeTake360Snapshot() {
+ // Don't take a snapshot if we're currently in the middle of taking one
+ // or if the camera entity doesn't exist
+ if (!isCurrentlyTaking360Snapshot && camera) {
+ isCurrentlyTaking360Snapshot = true;
+ var currentCameraPosition = Entities.getEntityProperties(camera, ["position"]).position;
+ // Play a sound at the current camera position
+ Audio.playSound(SOUND_SNAPSHOT, {
+ "position": { "x": currentCameraPosition.x, "y": currentCameraPosition.y, "z": currentCameraPosition.z },
+ "localOnly": isThrowMode,
+ "volume": 0.8
+ });
+ Window.takeSecondaryCamera360Snapshot(currentCameraPosition);
+ used360AppToTakeThisSnapshot = true;
+ processing360Snapshot = true;
+
+ // Let the UI know we're processing a 360 snapshot now
+ tablet.emitScriptEvent(JSON.stringify({
+ "channel": channel,
+ "method": "startedProcessing360Snapshot"
+ }));
+ }
+ }
+
+ function on360SnapshotTaken(path) {
+ isCurrentlyTaking360Snapshot = false;
+ // Make the camera fall back to the ground with the same
+ // physical properties as when it froze in the air
+ if (isThrowMode) {
+ Entities.editEntity(camera, {
+ "velocity": snapshotVelocity,
+ "angularVelocity": snapshotAngularVelocity,
+ "gravity": cameraGravity,
+ "grab": {
+ "grabbable": true
+ }
+ });
+ }
+ // Delete the flash entity
+ if (flash) {
+ Entities.deleteEntity(flash);
+ flash = false;
+ }
+ //console.log('360 Snapshot taken. Path: ' + path);
+
+ //update UI
+ tablet.emitScriptEvent(JSON.stringify({
+ "channel": channel,
+ "method": "last360ThumbnailURL",
+ "last360ThumbnailURL": path
+ }));
+ last360ThumbnailURL = path;
+ Settings.setValue(SETTING_LAST_360_CAPTURE, last360ThumbnailURL);
+ processing360Snapshot = false;
+ tablet.emitScriptEvent(JSON.stringify({
+ "channel": channel,
+ "method": "finishedProcessing360Snapshot"
+ }));
+ }
+
+
+ var last360ThumbnailURL = Settings.getValue(SETTING_LAST_360_CAPTURE, "");
+ var used360AppToTakeThisSnapshot = false;
+
+ function onDomainChanged() {
+ rpo360Off(true);
+ }
+
+ // These functions will be called when the script is loaded.
+ var SOUND_CAMERA_ON = SoundCache.getSound(Script.resolvePath("resources/sounds/cameraOn.wav"));
+ var SOUND_SNAPSHOT = SoundCache.getSound(Script.resolvePath("resources/sounds/snap.wav"));
+
+
+ var jsMainFileName = "cam360.js";
+ var ROOT = Script.resolvePath('').split(jsMainFileName)[0];
+
+ var APP_NAME = "CAM360";
+ var APP_URL = ROOT + "cam360.html";
+ var APP_ICON_INACTIVE = ROOT + "resources/images/icons/cam360-i.svg";
+ var APP_ICON_ACTIVE = ROOT + "resources/images/icons/cam360-a.svg";
+ var appStatus = false;
+ var channel = "cam360.ak.overte.com";
+
+ var timestamp = 0;
+ var INTERCALL_DELAY = 200; //0.3 sec
+ var DEG_TO_RAD = Math.PI/180;
+
+ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+
+ Window.domainChanged.connect(onDomainChanged);
+ Window.snapshot360Taken.connect(on360SnapshotTaken);
+ HMD.displayModeChanged.connect(onHMDChanged);
+
+ camera = false;
+
+ tablet.screenChanged.connect(onScreenChanged);
+
+ var button = tablet.addButton({
+ text: APP_NAME,
+ icon: APP_ICON_INACTIVE,
+ activeIcon: APP_ICON_ACTIVE
+ });
+
+
+ function clicked(){
+ if (appStatus === true) {
+ tablet.webEventReceived.disconnect(onAppWebEventReceived);
+ tablet.gotoHomeScreen();
+ appStatus = false;
+ }else{
+ tablet.gotoWebScreen(APP_URL);
+ tablet.webEventReceived.connect(onAppWebEventReceived);
+ appStatus = true;
+ }
+
+ button.editProperties({
+ isActive: appStatus || camera
+ });
+ }
+
+ button.clicked.connect(clicked);
+
+
+ function onAppWebEventReceived(message){
+ var d = new Date();
+ var n = d.getTime();
+ var messageObj = JSON.parse(message);
+ if (messageObj.channel === channel) {
+ if (messageObj.method === "rpo360On" && (n - timestamp) > INTERCALL_DELAY) {
+ d = new Date();
+ timestamp = d.getTime();
+ rpo360On();
+
+ } else if (messageObj.method === "rpo360Off" && (n - timestamp) > INTERCALL_DELAY) {
+ d = new Date();
+ timestamp = d.getTime();
+ rpo360Off();
+
+ } else if (messageObj.method === "openSettings" && (n - timestamp) > INTERCALL_DELAY) {
+ d = new Date();
+ timestamp = d.getTime();
+ 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");
+ }
+
+ } else if (messageObj.method === "disableFlash" && (n - timestamp) > INTERCALL_DELAY) {
+ d = new Date();
+ timestamp = d.getTime();
+ useFlash = false;
+
+ } else if (messageObj.method === "enableFlash" && (n - timestamp) > INTERCALL_DELAY) {
+ d = new Date();
+ timestamp = d.getTime();
+ useFlash = true;
+
+ } else if (messageObj.method === "uiReady" && (n - timestamp) > INTERCALL_DELAY) {
+ d = new Date();
+ timestamp = d.getTime();
+ tablet.emitScriptEvent(JSON.stringify({
+ "channel": channel,
+ "method": "initializeUI",
+ "masterSwitchOn": !!camera,
+ "last360ThumbnailURL": last360ThumbnailURL,
+ "processing360Snapshot": processing360Snapshot,
+ "useFlash": useFlash,
+ "isThrowMode": isThrowMode
+ }));
+
+ } else if (messageObj.method === "ThrowMode" && (n - timestamp) > INTERCALL_DELAY) {
+ d = new Date();
+ timestamp = d.getTime();
+ isThrowMode = true;
+ if (camera) {
+ rpo360Off();
+ rpo360On();
+ }
+ } else if (messageObj.method === "PositionMode" && (n - timestamp) > INTERCALL_DELAY) {
+ d = new Date();
+ timestamp = d.getTime();
+ isThrowMode = false;
+ if (camera) {
+ rpo360Off();
+ rpo360On();
+ }
+ } else if (messageObj.method === "Capture" && (n - timestamp) > INTERCALL_DELAY) {
+ d = new Date();
+ timestamp = d.getTime();
+ if (camera) {
+ capture();
+ }
+ }
+
+ }
+ }
+ var udateSignateDisconnected = true;
+ function onScreenChanged(type, url) {
+ if (type === "Web" && url.indexOf(APP_URL) !== -1) {
+ appStatus = true;
+ Script.update.connect(myTimer);
+ udateSignateDisconnected = false;
+ } else {
+ appStatus = false;
+ if (!udateSignateDisconnected) {
+ Script.update.disconnect(myTimer);
+ udateSignateDisconnected = true;
+ }
+ }
+
+ button.editProperties({
+ isActive: appStatus || camera
+ });
+ }
+
+ function myTimer(deltaTime) {
+ var yaw = 0.0;
+ var pitch = 0.0;
+ var roll = 0.0;
+ var euler;
+ if (!HMD.active) {
+ //Use cuser camera for destop
+ euler = Quat.safeEulerAngles(Camera.orientation);
+ yaw = -euler.y;
+ pitch = -euler.x;
+ roll = -euler.z;
+ } else {
+ //Use Tablet orientation for HMD
+ var tabletRotation = Entities.getEntityProperties(HMD.tabletID, ["rotation"]).rotation;
+ var noRoll = Quat.cancelOutRoll(tabletRotation); //Pushing the roll is getting quite complexe
+ euler = Quat.safeEulerAngles(noRoll);
+ yaw = euler.y - 180;
+ if (yaw < -180) { yaw = yaw + 360;}
+ yaw = -yaw;
+ pitch = euler.x;
+ roll = 0;
+ }
+
+ tablet.emitScriptEvent(JSON.stringify({
+ "channel": channel,
+ "method": "yawPitchRoll",
+ "yaw": yaw,
+ "pitch": pitch,
+ "roll": roll
+ }));
+
+ }
+
+ function cleanup() {
+
+ if (appStatus) {
+ tablet.gotoHomeScreen();
+ tablet.webEventReceived.disconnect(onAppWebEventReceived);
+ if (!udateSignateDisconnected) {
+ Script.update.disconnect(myTimer);
+ udateSignateDisconnected = true;
+ }
+ }
+
+ tablet.screenChanged.disconnect(onScreenChanged);
+ tablet.removeButton(button);
+
+ rpo360Off();
+
+ if (takePhotoControllerMapping) {
+ takePhotoControllerMapping.disable();
+ }
+
+ Window.domainChanged.disconnect(onDomainChanged);
+ Window.snapshot360Taken.disconnect(on360SnapshotTaken);
+ HMD.displayModeChanged.disconnect(onHMDChanged);
+ }
+
+ Script.scriptEnding.connect(cleanup);
+
+ //controller
+ function setTakePhotoControllerMappingStatus() {
+ if (!takePhotoControllerMapping) {
+ return;
+ }
+ if (!isThrowMode) {
+ takePhotoControllerMapping.enable();
+ } else {
+ takePhotoControllerMapping.disable();
+ }
+ }
+
+ var takePhotoControllerMapping;
+ var takePhotoControllerMappingName = 'Overte-cam360-Mapping-Capture';
+ function registerTakePhotoControllerMapping() {
+ takePhotoControllerMapping = Controller.newMapping(takePhotoControllerMappingName);
+ if (controllerType === "OculusTouch") {
+ takePhotoControllerMapping.from(Controller.Standard.RS).to(function (value) {
+ if (value === 1.0) {
+ if (camera) {
+ capture();
+ }
+ }
+ return;
+ });
+ } else if (controllerType === "Vive") {
+ takePhotoControllerMapping.from(Controller.Standard.RightPrimaryThumb).to(function (value) {
+ if (value === 1.0) {
+ if (camera) {
+ capture();
+ }
+ }
+ 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();
+ }
+
+}());
diff --git a/applications/cam360/grabDetection.js b/applications/cam360/grabDetection.js
new file mode 100644
index 0000000..4f69709
--- /dev/null
+++ b/applications/cam360/grabDetection.js
@@ -0,0 +1,50 @@
+"use strict";
+//
+// grabDetection.js
+//
+// Created by Alezia Kurdis on August 26th, 2022
+// Copyright 2022 Overte e.V.
+//
+// Distributed under the Apache License, Version 2.0
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+(function(){
+
+ var _this;
+
+ DetectGrabbed = function() {
+ _this = this;
+ };
+
+ DetectGrabbed.prototype = {
+ setRightHand: function () {
+ //print("I am being held in a right hand... entity:" + this.entityID);
+ },
+ setLeftHand: function () {
+ //print("I am being held in a left hand... entity:" + this.entityID);
+ },
+ startDistantGrab: function () {
+ //print("I am being distance held... entity:" + this.entityID);
+ },
+ continueDistantGrab: function () {
+ //print("I continue to be distance held... entity:" + this.entityID);
+ },
+ startNearGrab: function () {
+ //print("I was just grabbed... entity:" + this.entityID);
+ },
+ continueNearGrab: function () {
+ //print("I am still being grabbed... entity:" + this.entityID);
+ },
+
+ releaseGrab: function () {
+ //print("I was released... entity:" + this.entityID);
+ Entities.editEntity(this.entityID, {"userData": "RELEASED"});
+ },
+
+ preload: function(entityID) {
+ this.entityID = entityID;
+ },
+ };
+
+ return new DetectGrabbed();
+})
diff --git a/applications/cam360/resources/fonts/FiraSans-SemiBold.ttf b/applications/cam360/resources/fonts/FiraSans-SemiBold.ttf
new file mode 100644
index 0000000..821a43d
Binary files /dev/null and b/applications/cam360/resources/fonts/FiraSans-SemiBold.ttf differ
diff --git a/applications/cam360/resources/images/default.jpg b/applications/cam360/resources/images/default.jpg
new file mode 100644
index 0000000..02ddb22
Binary files /dev/null and b/applications/cam360/resources/images/default.jpg differ
diff --git a/applications/cam360/resources/images/icons/cam360-a.svg b/applications/cam360/resources/images/icons/cam360-a.svg
new file mode 100644
index 0000000..f20774c
--- /dev/null
+++ b/applications/cam360/resources/images/icons/cam360-a.svg
@@ -0,0 +1,58 @@
+
+
diff --git a/applications/cam360/resources/images/icons/cam360-i.svg b/applications/cam360/resources/images/icons/cam360-i.svg
new file mode 100644
index 0000000..8bfab42
--- /dev/null
+++ b/applications/cam360/resources/images/icons/cam360-i.svg
@@ -0,0 +1,64 @@
+
+
diff --git a/applications/cam360/resources/images/off.png b/applications/cam360/resources/images/off.png
new file mode 100644
index 0000000..caf17ab
Binary files /dev/null and b/applications/cam360/resources/images/off.png differ
diff --git a/applications/cam360/resources/images/on.png b/applications/cam360/resources/images/on.png
new file mode 100644
index 0000000..a3e279f
Binary files /dev/null and b/applications/cam360/resources/images/on.png differ
diff --git a/applications/cam360/resources/images/processing.gif b/applications/cam360/resources/images/processing.gif
new file mode 100644
index 0000000..77caa12
Binary files /dev/null and b/applications/cam360/resources/images/processing.gif differ
diff --git a/applications/cam360/resources/marzipano/LICENSE.txt b/applications/cam360/resources/marzipano/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/applications/cam360/resources/marzipano/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/applications/cam360/resources/marzipano/marzipano.js b/applications/cam360/resources/marzipano/marzipano.js
new file mode 100644
index 0000000..3e84187
--- /dev/null
+++ b/applications/cam360/resources/marzipano/marzipano.js
@@ -0,0 +1,16 @@
+// Marzipano - a 360° media viewer for the modern web (v0.10.2)
+//
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+!function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Marzipano=t()}(function(){var Vt;return function r(n,o,s){function a(e,t){if(!o[e]){if(!n[e]){var i="function"==typeof require&&require;if(!t&&i)return i(e,!0);if(h)return h(e,!0);throw(i=new Error("Cannot find module '"+e+"'")).code="MODULE_NOT_FOUND",i}i=o[e]={exports:{}},n[e][0].call(i.exports,function(t){return a(n[e][1][t]||t)},i,i.exports,r,n,o,s)}return o[e].exports}for(var h="function"==typeof require&&require,t=0;te[1][i])return 1;if(e[0][i]!==e[1][i])return-1;if(0===i)return 0}}function o(t,e,i){var r=a;"string"==typeof e&&(i=e,e=void 0),void 0===e&&(e=!1),i&&(r=s(i));var n,o=""+r.version;for(n in t)if(t.hasOwnProperty(n)&&r[n]){if("string"!=typeof t[n])throw new Error("Browser version in the minVersion map should be a string: "+n+": "+String(t));return x([o,t[n]])<0}return e}return a.test=function(t){for(var e=0;ef.EPSILON?(t[0]=e[0]/r,t[1]=e[1]/r,t[2]=e[2]/r):(t[0]=1,t[1]=0,t[2]=0);return i},i.getAngle=function(t,e){e=y(t,e);return Math.acos(2*e*e-1)},i.multiply=c,i.rotateX=function(t,e,i){i*=.5;var r=e[0],n=e[1],o=e[2],s=e[3],e=Math.sin(i),i=Math.cos(i);return t[0]=r*i+s*e,t[1]=n*i+o*e,t[2]=o*i-n*e,t[3]=s*i-r*e,t},i.rotateY=function(t,e,i){i*=.5;var r=e[0],n=e[1],o=e[2],s=e[3],e=Math.sin(i),i=Math.cos(i);return t[0]=r*i-o*e,t[1]=n*i+s*e,t[2]=o*i+r*e,t[3]=s*i-n*e,t},i.rotateZ=function(t,e,i){i*=.5;var r=e[0],n=e[1],o=e[2],s=e[3],e=Math.sin(i),i=Math.cos(i);return t[0]=r*i+n*e,t[1]=n*i-r*e,t[2]=o*i+s*e,t[3]=s*i-o*e,t},i.calculateW=function(t,e){var i=e[0],r=e[1],e=e[2];return t[0]=i,t[1]=r,t[2]=e,t[3]=Math.sqrt(Math.abs(1-i*i-r*r-e*e)),t},i.exp=p,i.ln=d,i.pow=function(t,e,i){return d(t,e),_(t,t,i),p(t,t),t},i.slerp=m,i.random=function(t){var e=f.RANDOM(),i=f.RANDOM(),r=f.RANDOM(),n=Math.sqrt(1-e),e=Math.sqrt(e);return t[0]=n*Math.sin(2*Math.PI*i),t[1]=n*Math.cos(2*Math.PI*i),t[2]=e*Math.sin(2*Math.PI*r),t[3]=e*Math.cos(2*Math.PI*r),t},i.invert=function(t,e){var i=e[0],r=e[1],n=e[2],o=e[3],e=i*i+r*r+n*n+o*o,e=e?1/e:0;return t[0]=-i*e,t[1]=-r*e,t[2]=-n*e,t[3]=o*e,t},i.conjugate=function(t,e){return t[0]=-e[0],t[1]=-e[1],t[2]=-e[2],t[3]=e[3],t},i.fromMat3=v,i.fromEuler=function(t,e,i,r){var n=.5*Math.PI/180;e*=n,i*=n,r*=n;var o=Math.sin(e),s=Math.cos(e),n=Math.sin(i),e=Math.cos(i),i=Math.sin(r),r=Math.cos(r);return t[0]=o*e*r-s*n*i,t[1]=s*n*r+o*e*i,t[2]=s*e*i-o*n*r,t[3]=s*e*r+o*n*i,t},i.str=function(t){return"quat("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},i.setAxes=i.sqlerp=i.rotationTo=i.equals=i.exactEquals=i.normalize=i.sqrLen=i.squaredLength=i.len=i.length=i.lerp=i.dot=i.scale=i.mul=i.add=i.set=i.copy=i.fromValues=i.clone=void 0;var f=h(t("./common.js")),r=h(t("./mat3.js")),n=h(t("./vec3.js")),o=h(t("./vec4.js"));function a(){if("function"!=typeof WeakMap)return null;var t=new WeakMap;return a=function(){return t},t}function h(t){if(t&&t.__esModule)return t;if(null===t||"object"!==s(t)&&"function"!=typeof t)return{default:t};var e=a();if(e&&e.has(t))return e.get(t);var i,r,n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(i in t)Object.prototype.hasOwnProperty.call(t,i)&&((r=o?Object.getOwnPropertyDescriptor(t,i):null)&&(r.get||r.set)?Object.defineProperty(n,i,r):n[i]=t[i]);return n.default=t,e&&e.set(t,n),n}function u(){var t=new f.ARRAY_TYPE(4);return f.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t[3]=1,t}function l(t,e,i){i*=.5;var r=Math.sin(i);return t[0]=r*e[0],t[1]=r*e[1],t[2]=r*e[2],t[3]=Math.cos(i),t}function c(t,e,i){var r=e[0],n=e[1],o=e[2],s=e[3],a=i[0],h=i[1],e=i[2],i=i[3];return t[0]=r*i+s*a+n*e-o*h,t[1]=n*i+s*h+o*a-r*e,t[2]=o*i+s*e+r*h-n*a,t[3]=s*i-r*a-n*h-o*e,t}function p(t,e){var i=e[0],r=e[1],n=e[2],o=e[3],s=Math.sqrt(i*i+r*r+n*n),e=Math.exp(o),o=0f.EPSILON?(i=Math.acos(e),e=Math.sin(i),n=Math.sin((1-r)*i)/e,Math.sin(r*i)/e):(n=1-r,r),t[0]=n*o+r*u,t[1]=n*s+r*l,t[2]=n*a+r*c,t[3]=n*h+r*p,t}function v(t,e){var i,r,n,o=e[0]+e[4]+e[8];return 0e[0]&&(i=1),e[8]>e[3*i+i]&&(i=2),r=(i+1)%3,o=(i+2)%3,n=Math.sqrt(e[3*i+i]-e[3*r+r]-e[3*o+o]+1),t[i]=.5*n,n=.5/n,t[3]=(e[3*r+o]-e[3*o+r])*n,t[r]=(e[3*r+i]+e[3*i+r])*n,t[o]=(e[3*o+i]+e[3*i+o])*n),t}t=o.clone;i.clone=t;t=o.fromValues;i.fromValues=t;t=o.copy;i.copy=t;t=o.set;i.set=t;t=o.add;i.add=t,i.mul=c;var _=o.scale;i.scale=_;var y=o.dot;i.dot=y;t=o.lerp;i.lerp=t;t=o.length;i.length=t,i.len=t;t=o.squaredLength;i.squaredLength=t,i.sqrLen=t;var g=o.normalize;i.normalize=g;t=o.exactEquals;i.exactEquals=t;o=o.equals;i.equals=o;var w,b,M,o=(w=n.create(),b=n.fromValues(1,0,0),M=n.fromValues(0,1,0),function(t,e,i){var r=n.dot(e,i);return r<-.999999?(n.cross(w,b,e),n.len(w)<1e-6&&n.cross(w,M,e),n.normalize(w,w),l(t,w,Math.PI),t):.999999e[i]}):r.sort()),r}function P(t,e){for(var i,r=e[0].toUpperCase()+e.slice(1),n=0;nl(s.y)?s.x:s.y,o=J(n,o),t.lastInterval=e):(s=a.velocity,i=a.velocityX,r=a.velocityY,o=a.direction),e.velocity=s,e.velocityX=i,e.velocityY=r,e.direction=o}(i,e),t=t.element,w(e.srcEvent.target,t)&&(t=e.srcEvent.target),e.target=t}(t,i),t.emit("hammer.input",i),t.recognize(i),t.session.prevInput=i}function Z(t){for(var e=[],i=0;i=l(e)?0e.threshold&&n&e.direction},attrTest:function(t){return It.prototype.attrTest.call(this,t)&&(this.state&Tt||!(this.state&Tt)&&this.directionTest(t))},emit:function(t){this.pX=t.deltaX,this.pY=t.deltaY;var e=At(t.direction);e&&this.manager.emit(this.options.event+e,t),this._super.emit.call(this,t)}}),d(Dt,It,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[wt]},attrTest:function(t){return this._super.attrTest.call(this,t)&&(Math.abs(t.scale-1)>this.options.threshold||this.state&Tt)},emit:function(t){var e;this._super.emit.call(this,t),1!==t.scale&&(e=t.scale<1?"in":"out",this.manager.emit(this.options.event+e,t))}}),d(jt,Rt,{defaults:{event:"press",pointers:1,time:500,threshold:5},getTouchAction:function(){return["auto"]},process:function(t){var e=this.options,i=t.pointers.length===e.pointers,r=t.distancee.time;if(this._input=t,!r||!i||t.eventType&(j|H)&&!n)this.reset();else if(t.eventType&D)this.reset(),this._timer=c(function(){this.state=St,this.tryEmit()},e.time,this);else if(t.eventType&j)return St;return 32},reset:function(){clearTimeout(this._timer)},emit:function(t){this.state===St&&(t&&t.eventType&j?this.manager.emit(this.options.event+"up",t):(this._input.timeStamp=h(),this.manager.emit(this.options.event,this._input)))}}),d(Ht,It,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[wt]},attrTest:function(t){return this._super.attrTest.call(this,t)&&(Math.abs(t.rotation)>this.options.threshold||this.state&Tt)}}),d(kt,It,{defaults:{event:"swipe",threshold:10,velocity:.65,direction:W|V,pointers:1},getTouchAction:function(){return zt.prototype.getTouchAction.call(this)},attrTest:function(t){var e,i=this.options.direction;return i&(W|V)?e=t.velocity:i&W?e=t.velocityX:i&V&&(e=t.velocityY),this._super.attrTest.call(this,t)&&i&t.direction&&t.distance>this.options.threshold&&l(e)>this.options.velocity&&t.eventType&j},emit:function(t){var e=At(t.direction);e&&this.manager.emit(this.options.event+e,t),this.manager.emit(this.options.event,t)}}),d(Yt,Rt,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:2,posThreshold:10},getTouchAction:function(){return[gt]},process:function(t){var e=this.options,i=t.pointers.length===e.pointers,r=t.distance=this._geometry.levelList.length||t<0))throw new Error("Level index out of range: "+t);this._fixedLevelIndex=t,this.emit("fixedLevelChange",this._fixedLevelIndex)}},s.prototype._selectLevel=function(){var t=null!=this._fixedLevelIndex?this._geometry.levelList[this._fixedLevelIndex]:this._view.selectLevel(this._geometry.selectableLevelList);return t},s.prototype.visibleTiles=function(t){var e=this._selectLevel();return this._geometry.visibleTiles(this._view,e,t)},s.prototype.pinLevel=function(t){for(var t=this._geometry.levelList[t],e=this._geometry.levelTiles(t),i=0;io.length)for(t=0;ti?Math.min(h+o*v,f):Math.max(h-o*v,0),n=l*o,yt.pitch&&(t.pitch=Math.min(y,t.pitch+n))),null!=g&&t.fov!==y&&(i=.5*u*u/_,c=Math.abs(g-t.fov)>i?Math.min(u+o*_,d):Math.max(u-o*_,0),o=c*o,gt.fov&&(t.fov=Math.min(g,t.fov+o))),s=e,a=r,h=l,u=c,t}}}},{"./util/defaults":81}],28:[function(t,e,i){"use strict";var r=t("../util/mod");function n(t){if(!isFinite(t)||Math.floor(t)!==t||t<0)throw new Error("LruMap: invalid capacity");this._capacity=t,this._keys=new Array(this._capacity),this._values=new Array(this._capacity),this._start=0,this._size=0}n.prototype._index=function(t){return r(this._start+t,this._capacity)},n.prototype.get=function(t){for(var e=0;ethis._layers.length)throw new Error("Invalid layer position");this.validateLayer(t);var i=t.geometry().type,r=t.view().type,n=this._rendererRegistry.get(i,r);if(!n)throw new Error("No "+this.type+" renderer avaiable for "+i+" geometry and "+r+" view");n=this.createRenderer(n);this._layers.splice(e,0,t),this._renderers.splice(e,0,n),t.addEventListener("viewChange",this._emitRenderInvalid),t.addEventListener("effectsChange",this._emitRenderInvalid),t.addEventListener("fixedLevelChange",this._emitRenderInvalid),t.addEventListener("textureStoreChange",this._emitRenderInvalid),this._emitRenderInvalid()},c.prototype.moveLayer=function(t,e){var i=this._layers.indexOf(t);if(i<0)throw new Error("No such layer in stage");if(e<0||e>=this._layers.length)throw new Error("Invalid layer position");t=this._layers.splice(i,1)[0];i=this._renderers.splice(i,1)[0];this._layers.splice(e,0,t),this._renderers.splice(e,0,i),this._emitRenderInvalid()},c.prototype.removeLayer=function(t){var e=this._layers.indexOf(t);if(e<0)throw new Error("No such layer in stage");t=this._layers.splice(e,1)[0],e=this._renderers.splice(e,1)[0];this.destroyRenderer(e),t.removeEventListener("viewChange",this._emitRenderInvalid),t.removeEventListener("effectsChange",this._emitRenderInvalid),t.removeEventListener("fixedLevelChange",this._emitRenderInvalid),t.removeEventListener("textureStoreChange",this._emitRenderInvalid),this._emitRenderInvalid()},c.prototype.removeAllLayers=function(){for(;0>6}return t+=t<<3,t^=t>>11,0<=(t+=t<<15)?t:-t}},{}],89:[function(t,e,i){"use strict";e.exports=function(t,e){function i(){}t.super_=e,i.prototype=e.prototype,t.prototype=new i,t.prototype.constructor=t}},{}],90:[function(t,e,i){"use strict";e.exports=function(t){return 0==(t&t-1)}},{}],91:[function(t,e,i){"use strict";e.exports=function(t,e){return(+t%(e=+e)+e)%e}},{}],92:[function(t,e,i){"use strict";e.exports=function(){}},{}],93:[function(t,e,i){"use strict";e.exports="undefined"!=typeof performance&&performance.now?function(){return performance.now()}:function(){return Date.now()}},{}],94:[function(t,e,i){"use strict";e.exports=function(t){var e,i=!1;return function(){return i||(i=!0,e=t.apply(null,arguments)),e}}},{}],95:[function(t,e,i){"use strict";e.exports=function(){if("undefined"!=typeof window){if(window.devicePixelRatio)return window.devicePixelRatio;var t=window.screen;if(t&&t.deviceXDPI&&t.logicalXDPI)return t.deviceXDPI/t.logicalXDPI;if(t&&t.systemXDPI&&t.logicalXDPI)return t.systemXDPI/t.logicalXDPI}return 1}},{}],96:[function(t,e,i){"use strict";var n=t("./dom").setTransform,o=t("./decimal");e.exports=function(t,e,i,r){r=r||"",r="translateX("+o(e)+"px) translateY("+o(i)+"px) translateZ(0) "+r,n(t,r)}},{"./decimal":80,"./dom":85}],97:[function(t,e,i){"use strict";e.exports=function(t){return 180*t/Math.PI}},{}],98:[function(t,e,i){"use strict";e.exports=function(t){return"number"==typeof t&&isFinite(t)}},{}],99:[function(t,e,i){"use strict";var s=t("./noop");e.exports=function(o){return function(){var t=arguments.length?Array.prototype.slice.call(arguments,0,arguments.length-1):[],e=arguments.length?arguments[arguments.length-1]:s,i=null,r=!1;function n(){!arguments[0]||r?e.apply(null,arguments):i=o.apply(null,t)}return t.push(n),n(!0),function(){r=!0,i.apply(null,arguments)}}}},{"./noop":92}],100:[function(t,e,i){"use strict";var a=t("./now");e.exports=function(i,r,n){var o=!1,s=a();return r(0),requestAnimationFrame(function t(){var e;o||((e=(a()-s)/i)<1?(r(e),requestAnimationFrame(t)):(r(1),n()))}),function(){o=!0,n.apply(null,arguments)}}},{"./now":93}],101:[function(t,e,i){"use strict";e.exports=function(t){var e=typeof t;if("object"==e){if(null===t)return"null";if("[object Array]"===Object.prototype.toString.call(t))return"array";if("[object RegExp]"===Object.prototype.toString.call(t))return"regexp"}return e}},{}],102:[function(t,e,i){"use strict";var r=t("minimal-event-emitter"),u=t("gl-matrix").mat4,a=t("gl-matrix").vec4,o=t("../util/pixelRatio"),p=t("../util/real"),f=t("../util/clamp"),n=t("../util/clearOwnProperties"),l=[1,0,1,0],c=[-1,-1,1,1];function s(t,e){if(!t||null==t.mediaAspectRatio)throw new Error("mediaAspectRatio must be defined");this._x=t&&null!=t.x?t.x:.5,this._y=t&&null!=t.y?t.y:.5,this._zoom=t&&null!=t.zoom?t.zoom:1,this._mediaAspectRatio=t.mediaAspectRatio,this._width=t&&null!=t.width?t.width:0,this._height=t&&null!=t.height?t.height:0,this._limiter=e||null,this._projMatrix=u.create(),this._invProjMatrix=u.create(),this._frustum=[0,0,0,0],this._projectionChanged=!0,this._params={},this._vec=a.create(),this._update()}r(s),s.prototype.destroy=function(){n(this)},s.prototype.x=function(){return this._x},s.prototype.y=function(){return this._y},s.prototype.zoom=function(){return this._zoom},s.prototype.mediaAspectRatio=function(){return this._mediaAspectRatio},s.prototype.width=function(){return this._width},s.prototype.height=function(){return this._height},s.prototype.size=function(t){return(t=t||{}).width=this._width,t.height=this._height,t},s.prototype.parameters=function(t){return(t=t||{}).x=this._x,t.y=this._y,t.zoom=this._zoom,t.mediaAspectRatio=this._mediaAspectRatio,t},s.prototype.limiter=function(){return this._limiter},s.prototype.setX=function(t){this._resetParams(),this._params.x=t,this._update(this._params)},s.prototype.setY=function(t){this._resetParams(),this._params.y=t,this._update(this._params)},s.prototype.setZoom=function(t){this._resetParams(),this._params.zoom=t,this._update(this._params)},s.prototype.offsetX=function(t){this.setX(this._x+t)},s.prototype.offsetY=function(t){this.setY(this._y+t)},s.prototype.offsetZoom=function(t){this.setZoom(this._zoom+t)},s.prototype.setMediaAspectRatio=function(t){this._resetParams(),this._params.mediaAspectRatio=t,this._update(this._params)},s.prototype.setSize=function(t){this._resetParams(),this._params.width=t.width,this._params.height=t.height,this._update(this._params)},s.prototype.setParameters=function(t){this._resetParams(),this._params.x=t.x,this._params.y=t.y,this._params.zoom=t.zoom,this._params.mediaAspectRatio=t.mediaAspectRatio,this._update(this._params)},s.prototype.setLimiter=function(t){this._limiter=t||null,this._update()},s.prototype._resetParams=function(){var t=this._params;t.x=null,t.y=null,t.zoom=null,t.mediaAspectRatio=null,t.width=null,t.height=null},s.prototype._update=function(t){null==t&&(this._resetParams(),t=this._params);var e=this._x,i=this._y,r=this._zoom,n=this._mediaAspectRatio,o=this._width,s=this._height;if(t.x=null!=t.x?t.x:e,t.y=null!=t.y?t.y:i,t.zoom=null!=t.zoom?t.zoom:r,t.mediaAspectRatio=null!=t.mediaAspectRatio?t.mediaAspectRatio:n,t.width=null!=t.width?t.width:o,t.height=null!=t.height?t.height:s,this._limiter&&!(t=this._limiter(t)))throw new Error("Bad view limiter");var a=t.x,h=t.y,u=t.zoom,l=t.mediaAspectRatio,c=t.width,t=t.height;if(!(p(a)&&p(h)&&p(u)&&p(l)&&p(c)&&p(t)))throw new Error("Bad view - suspect a broken limiter");u=f(u,1e-6,1/0),this._x=a,this._y=h,this._zoom=u,this._mediaAspectRatio=l,this._width=c,this._height=t,a===e&&h===i&&u===r&&l===n&&c===o&&t===s||(this._projectionChanged=!0,this.emit("change")),c===o&&t===s||this.emit("resize")},s.prototype._zoomX=function(){return this._zoom},s.prototype._zoomY=function(){var t=this._mediaAspectRatio,e=this._width/this._height,i=this._zoom,e=i*t/e;return isNaN(e)&&(e=i),e},s.prototype.updateWithControlParameters=function(t){var e=this.zoom(),i=this._zoomX(),r=this._zoomY();this.offsetX(t.axisScaledX*i+t.x*e),this.offsetY(t.axisScaledY*r+t.y*e),this.offsetZoom(t.zoom*e)},s.prototype._updateProjection=function(){var t,e,i,r,n,o,s=this._projMatrix,a=this._invProjMatrix,h=this._frustum;this._projectionChanged&&(t=this._x,e=this._y,o=this._zoomX(),n=this._zoomY(),i=h[0]=.5-e+.5*n,r=h[1]=t-.5+.5*o,n=h[2]=.5-e-.5*n,o=h[3]=t-.5-.5*o,u.ortho(s,o,r,n,i,-1,1),u.invert(a,s),this._projectionChanged=!1)},s.prototype.projection=function(){return this._updateProjection(),this._projMatrix},s.prototype.inverseProjection=function(){return this._updateProjection(),this._invProjMatrix},s.prototype.intersects=function(t){this._updateProjection();for(var e=this._frustum,i=0;ir){s=!0;break}}if(!s)return!1}return!0},s.prototype.selectLevel=function(t){for(var e=o()*this.width(),i=this._zoom,r=0;r=e)return n}return t[t.length-1]},s.prototype.coordinatesToScreen=function(t,e){var i=this._vec;e=e||{};var r=this._width,n=this._height;if(r<=0||n<=0)return e.x=null,e.y=null;var o=t&&null!=t.x?t.x:.5,t=t&&null!=t.y?t.y:.5;a.set(i,o-.5,.5-t,-1,1),a.transformMat4(i,i,this.projection());for(var s=0;s<3;s++)i[s]/=i[3];return e.x=r*(i[0]+1)/2,e.y=n*(1-i[1])/2,e},s.prototype.screenToCoordinates=function(t,e){var i=this._vec;e=e||{};var r=this._width,n=this._height,r=2*t.x/r-1,n=1-2*t.y/n;return a.set(i,r,n,1,1),a.transformMat4(i,i,this.inverseProjection()),e.x=.5+i[0],e.y=.5-i[1],e},s.limit={x:function(e,i){return function(t){return t.x=f(t.x,e,i),t}},y:function(e,i){return function(t){return t.y=f(t.y,e,i),t}},zoom:function(e,i){return function(t){return t.zoom=f(t.zoom,e,i),t}},resolution:function(i){return function(t){if(t.width<=0||t.height<=0)return t;var e=t.width,e=o()*e/i;return t.zoom=f(t.zoom,e,1/0),t}},visibleX:function(r,n){return function(t){var e=n-r;t.zoom>e&&(t.zoom=e);var i=r+.5*t.zoom,e=n-.5*t.zoom;return t.x=f(t.x,i,e),t}},visibleY:function(r,n){return function(t){if(t.width<=0||t.height<=0)return t;var e=t.width/t.height/t.mediaAspectRatio,i=(n-r)*e;t.zoom>i&&(t.zoom=i);i=r+.5*t.zoom/e,e=n-.5*t.zoom/e;return t.y=f(t.y,i,e),t}},letterbox:function(){return function(t){if(t.width<=0||t.height<=0)return t;var e,i,r,n,o=t.width/t.height,s=o/t.mediaAspectRatio;return t.mediaAspectRatio>=o&&(t.zoom=Math.min(t.zoom,1)),t.mediaAspectRatio<=o&&(t.zoom=Math.min(t.zoom,s)),1s?r=n=.5:(r=0+.5*t.zoom/s,n=1-.5*t.zoom/s),t.x=f(t.x,e,i),t.y=f(t.y,r,n),t}}},s.type=s.prototype.type="flat",e.exports=s},{"../util/clamp":75,"../util/clearOwnProperties":76,"../util/pixelRatio":95,"../util/real":98,"gl-matrix":3,"minimal-event-emitter":14}],103:[function(t,e,i){"use strict";var r=t("minimal-event-emitter"),u=t("gl-matrix").mat4,h=t("gl-matrix").vec4,o=t("../util/pixelRatio"),l=t("../util/convertFov"),n=t("../util/mod"),v=t("../util/real"),s=t("../util/clamp"),a=t("../util/decimal"),c=t("../util/compose"),p=t("../util/clearOwnProperties"),f=Math.PI/4;function d(t,e){this._yaw=t&&null!=t.yaw?t.yaw:0,this._pitch=t&&null!=t.pitch?t.pitch:0,this._roll=t&&null!=t.roll?t.roll:0,this._fov=t&&null!=t.fov?t.fov:f,this._width=t&&null!=t.width?t.width:0,this._height=t&&null!=t.height?t.height:0,this._projectionCenterX=t&&null!=t.projectionCenterX?t.projectionCenterX:0,this._projectionCenterY=t&&null!=t.projectionCenterY?t.projectionCenterY:0,this._limiter=e||null,this._projMatrix=u.create(),this._invProjMatrix=u.create(),this._frustum=[h.create(),h.create(),h.create(),h.create(),h.create()],this._projectionChanged=!0,this._params={},this._fovs={},this._tmpVec=h.create(),this._update()}r(d),d.prototype.destroy=function(){p(this)},d.prototype.yaw=function(){return this._yaw},d.prototype.pitch=function(){return this._pitch},d.prototype.roll=function(){return this._roll},d.prototype.projectionCenterX=function(){return this._projectionCenterX},d.prototype.projectionCenterY=function(){return this._projectionCenterY},d.prototype.fov=function(){return this._fov},d.prototype.width=function(){return this._width},d.prototype.height=function(){return this._height},d.prototype.size=function(t){return(t=t||{}).width=this._width,t.height=this._height,t},d.prototype.parameters=function(t){return(t=t||{}).yaw=this._yaw,t.pitch=this._pitch,t.roll=this._roll,t.fov=this._fov,t},d.prototype.limiter=function(){return this._limiter},d.prototype.setYaw=function(t){this._resetParams(),this._params.yaw=t,this._update(this._params)},d.prototype.setPitch=function(t){this._resetParams(),this._params.pitch=t,this._update(this._params)},d.prototype.setRoll=function(t){this._resetParams(),this._params.roll=t,this._update(this._params)},d.prototype.setFov=function(t){this._resetParams(),this._params.fov=t,this._update(this._params)},d.prototype.setProjectionCenterX=function(t){this._resetParams(),this._params.projectionCenterX=t,this._update(this._params)},d.prototype.setProjectionCenterY=function(t){this._resetParams(),this._params.projectionCenterY=t,this._update(this._params)},d.prototype.offsetYaw=function(t){this.setYaw(this._yaw+t)},d.prototype.offsetPitch=function(t){this.setPitch(this._pitch+t)},d.prototype.offsetRoll=function(t){this.setRoll(this._roll+t)},d.prototype.offsetFov=function(t){this.setFov(this._fov+t)},d.prototype.setSize=function(t){this._resetParams(),this._params.width=t.width,this._params.height=t.height,this._update(this._params)},d.prototype.setParameters=function(t){this._resetParams(),this._params.yaw=t.yaw,this._params.pitch=t.pitch,this._params.roll=t.roll,this._params.fov=t.fov,this._params.projectionCenterX=t.projectionCenterX,this._params.projectionCenterY=t.projectionCenterY,this._update(this._params)},d.prototype.setLimiter=function(t){this._limiter=t||null,this._update()},d.prototype._resetParams=function(){var t=this._params;t.yaw=null,t.pitch=null,t.roll=null,t.fov=null,t.width=null,t.height=null},d.prototype._update=function(t){null==t&&(this._resetParams(),t=this._params);var e=this._yaw,i=this._pitch,r=this._roll,n=this._fov,o=this._projectionCenterX,s=this._projectionCenterY,a=this._width,h=this._height;if(t.yaw=null!=t.yaw?t.yaw:e,t.pitch=null!=t.pitch?t.pitch:i,t.roll=null!=t.roll?t.roll:r,t.fov=null!=t.fov?t.fov:n,t.width=null!=t.width?t.width:a,t.height=null!=t.height?t.height:h,t.projectionCenterX=null!=t.projectionCenterX?t.projectionCenterX:o,t.projectionCenterY=null!=t.projectionCenterY?t.projectionCenterY:s,this._limiter&&!(t=this._limiter(t)))throw new Error("Bad view limiter");var u=(t=this._normalize(t)).yaw,l=t.pitch,c=t.roll,p=t.fov,f=t.width,d=t.height,m=t.projectionCenterX,t=t.projectionCenterY;if(!(v(u)&&v(l)&&v(c)&&v(p)&&v(f)&&v(d)&&v(m)&&v(t)))throw new Error("Bad view - suspect a broken limiter");this._yaw=u,this._pitch=l,this._roll=c,this._fov=p,this._width=f,this._height=d,this._projectionCenterX=m,this._projectionCenterY=t,u===e&&l===i&&c===r&&p===n&&f===a&&d===h&&m===o&&t===s||(this._projectionChanged=!0,this.emit("change")),f===a&&d===h||this.emit("resize")},d.prototype._normalize=function(t){this._normalizeCoordinates(t);var e=l.htov(Math.PI,t.width,t.height),e=isNaN(e)?Math.PI:Math.min(Math.PI,e);return t.fov=s(t.fov,1e-6,e-1e-6),t},d.prototype._normalizeCoordinates=function(t){return"yaw"in t&&(t.yaw=n(t.yaw-Math.PI,-2*Math.PI)+Math.PI),"pitch"in t&&(t.pitch=n(t.pitch-Math.PI,-2*Math.PI)+Math.PI),"roll"in t&&(t.roll=n(t.roll-Math.PI,-2*Math.PI)+Math.PI),t},d.prototype.normalizeToClosest=function(t,e){var i=this._yaw,r=this._pitch,n=t.yaw,o=t.pitch,s=n-2*Math.PI,t=n+2*Math.PI;Math.abs(s-i)=e)return n}return t[t.length-1]},d.prototype.coordinatesToScreen=function(t,e){var i=this._tmpVec;e=e||{};var r=this._width,n=this._height;if(r<=0||n<=0)return e.x=null,e.y=null;var o=t.yaw,s=t.pitch,a=Math.sin(o)*Math.cos(s),t=-Math.sin(s),s=-Math.cos(o)*Math.cos(s);return h.set(i,a,t,s,1),h.transformMat4(i,i,this.projection()),0<=i[3]?(e.x=r*(i[0]/i[3]+1)/2,e.y=n*(1-i[1]/i[3])/2,e):(e.x=null,e.y=null)},d.prototype.screenToCoordinates=function(t,e){var i=this._tmpVec;e=e||{};var r=this._width,n=this._height,r=2*t.x/r-1,n=1-2*t.y/n;h.set(i,r,n,1,1),h.transformMat4(i,i,this.inverseProjection());n=Math.sqrt(i[0]*i[0]+i[1]*i[1]+i[2]*i[2]);return e.yaw=Math.atan2(i[0],-i[2]),e.pitch=Math.acos(i[1]/n)-Math.PI/2,this._normalizeCoordinates(e),e},d.prototype.coordinatesToPerspectiveTransform=function(t,e,i){i=i||"";var r=this._height,n=this._width,o=this._fov,s=.5*r/Math.tan(o/2),o="";return o+="translateX("+a(n/2)+"px) ",o+="translateY("+a(r/2)+"px) ",o+="translateX(-50%) translateY(-50%) ",o+="perspective("+a(s)+"px) ",o+="translateZ("+a(s)+"px) ",o+="rotateZ("+a(-this._roll)+"rad) ",o+="rotateX("+a(-this._pitch)+"rad) ",o+="rotateY("+a(this._yaw)+"rad) ",o+="rotateY("+a(-t.yaw)+"rad) ",o+="rotateX("+a(t.pitch)+"rad) ",o+="translateZ("+a(-e)+"px) ",o+=i+" "},d.limit={yaw:function(e,i){return function(t){return t.yaw=s(t.yaw,e,i),t}},pitch:function(e,i){return function(t){return t.pitch=s(t.pitch,e,i),t}},roll:function(e,i){return function(t){return t.roll=s(t.roll,e,i),t}},hfov:function(n,o){return function(t){var e,i=t.width,r=t.height;return 0- The 'Throw' mode: Rez the cam, grab it, then toss it high in the sky to capture an incredible view of of any event. - The 'Position' mode: Position then click to capture. Ideal to generate an ambient light texture for any scene. This includes a 360° media viewer.",
+ "jsfile": "cam360/cam360.js",
+ "icon": "cam360/resources/images/icons/cam360-i.svg",
+ "caption": "CAM360"
}
]
};