diff --git a/unpublishedScripts/marketplace/camera-move/_debug.js b/unpublishedScripts/marketplace/camera-move/_debug.js
index 1b90acffcf..ecec259663 100644
--- a/unpublishedScripts/marketplace/camera-move/_debug.js
+++ b/unpublishedScripts/marketplace/camera-move/_debug.js
@@ -1,11 +1,28 @@
-_debug = {
+/* eslint-env jquery, browser */
+/* eslint-disable comma-dangle, no-empty */
+/* global EventBridge: true, PARAMS, signal, assert, log, debugPrint,
+ bridgedSettings, _utils, jquerySettings, */
+var _debug = {
handleUncaughtException: function onerror(message, fileName, lineNumber, colNumber, err) {
- var output = _utils.normalizeStackTrace(err || { message: message });
- console.error('window.onerror: ' + output, err);
+ if (message === onerror.lastMessage) {
+ return;
+ }
+ onerror.lastMessage = message;
+ var error = (err || Error.lastError);
+ // var stack = error && error.stack;
+ var output = _utils.normalizeStackTrace(error || { message: message });
+ window.console.error(['window.onerror: ', output, message]); // eslint-disable-line no-console
var errorNode = document.querySelector('#errors'),
textNode = errorNode && errorNode.querySelector('.output');
- if (textNode) textNode.innerText = output;
- if (errorNode) errorNode.style.display = 'block';
+ if (textNode) {
+ textNode.innerText = output;
+ }
+ if (errorNode) {
+ errorNode.style.display = 'block';
+ }
+ if (error){
+ error.onerrored = true;
+ }
},
loadScriptNodes: function loadScriptNodes(selector) {
// scripts are loaded this way to ensure refreshing the client script refreshes dependencies too
@@ -20,6 +37,7 @@ _debug = {
},
// TESTING MOCK (allows the UI to be tested using a normal web browser, outside of Interface
openEventBridgeMock: function openEventBridgeMock(onEventBridgeOpened) {
+ var updatedValues = openEventBridgeMock.updatedValues = {};
// emulate EventBridge's API
EventBridge = {
emitWebEvent: signal(function emitWebEvent(message){}),
@@ -27,63 +45,77 @@ _debug = {
};
EventBridge.emitWebEvent.connect(onEmitWebEvent);
onEventBridgeOpened(EventBridge);
- setTimeout(function() {
- assert(!bridgedSettings.onUnhandledMessage);
- bridgedSettings.onUnhandledMessage = function(msg) {
- return true;
- };
- // manually trigger bootstrapping responses
- $('.slider .control').parent().css('visibility','visible');
- bridgedSettings.handleExtraParams({uuid: PARAMS.uuid, ns: PARAMS.ns, extraParams: {
- mock: true,
- appVersion: 'browsermock',
- toggleKey: { text: 'SPACE', isShifted: true },
- } });
- bridgedSettings.setValue('ui-show-advanced-options', true);
- if (/fps/.test(location.hash)) setTimeout(function() { $('#fps').each(function(){ this.scrollIntoView(); }); }, 100);
- },1);
+ assert(!bridgedSettings.onUnhandledMessage);
+ bridgedSettings.onUnhandledMessage = function(msg) {
+ if (1||!msg || msg.method !== 'valueUpdated') {
+ log('bridgedSettings.onUnhandledMessage', msg);
+ }
+ return true;
+ };
+ // manually trigger bootstrapping responses
+ bridgedSettings.handleExtraParams({uuid: PARAMS.uuid, ns: PARAMS.ns, extraParams: {
+ mock: true,
+ appVersion: 'browsermock',
+ toggleKey: { text: 'SPACE', isShifted: true },
+ mode: {
+ toolbar: true,
+ browser: true,
+ desktop: true,
+ tablet: false,
+ hmd: false,
+ },
+ } });
+ bridgedSettings.setValue('ui-show-advanced-options', true);
function log(msg) {
+ // eslint-disable-next-line no-console
console.log.apply(console, ['[mock] ' + msg].concat([].slice.call(arguments,1)));
}
- var updatedValues = {};
// generate mock data in response to outgoing web events
function onEmitWebEvent(message) {
- try { var obj = JSON.parse(message); } catch(e) {}
+ try {
+ var obj = JSON.parse(message);
+ } catch (e) {}
if (!obj) {
// message isn't JSON or doesn't expect a reply so just log it and bail early
log('consuming non-callback web event', message);
return;
}
- switch(obj.method) {
+ switch (obj.method) {
case 'valueUpdated': {
+ log('valueUpdated',obj.params);
updatedValues[obj.params[0]] = obj.params[1];
- } break;
+ return;
+ }
case 'Settings.getValue': {
var key = obj.params[0];
var node = jquerySettings.findNodeByKey(key, true);
- var type = node && (node.dataset.type || node.getAttribute('type'));
- switch(type) {
+ // log('Settings.getValue.findNodeByKey', key, node);
+ var type = node && (node.dataset.type || node.type || node.attributes['type']);
+ switch (type) {
case 'checkbox': {
- obj.result = /tooltip/i.test(key) || PARAMS.tooltiptest ? true : Math.random() > .5;
+ obj.result = /tooltip/i.test(key) || PARAMS.tooltiptest ? true : Math.random() > 0.5;
} break;
case 'radio-group': {
var radios = $(node).find('input[type=radio]').toArray();
- while(Math.random() < .9) { radios.push(radios.shift()); }
+ while (Math.random() < 0.9) {
+ radios.push(radios.shift());
+ }
obj.result = radios[0].value;
} break;
+ case 'spinner':
case 'number': {
var step = node.step || 1, precision = (1/step).toString().length - 1;
var magnitude = node.max || (precision >=1 ? Math.pow(10, precision-1) : 10);
obj.result = parseFloat((Math.random() * magnitude).toFixed(precision||1));
} break;
default: {
- log('unhandled node type for making dummy data: ' + [key, node && node.type, type, node && node.getAttribute('type')] + ' @ ' + (node && node.id));
+ log('unhandled node type for making dummy data: ' + [key, node && node.type, type, node && node.type] + ' @ ' + (node && node.id));
obj.result = updatedValues[key] || false;
} break;
}
- log('mock getValue data %c%s = %c%s', 'color:blue',
+ debugPrint('mock getValue data %c%s = %c%s', 'color:blue',
JSON.stringify(key), 'color:green', JSON.stringify(obj.result));
} break;
default: {
diff --git a/unpublishedScripts/marketplace/camera-move/_json-persist.js b/unpublishedScripts/marketplace/camera-move/_json-persist.js
new file mode 100644
index 0000000000..be253ff16b
--- /dev/null
+++ b/unpublishedScripts/marketplace/camera-move/_json-persist.js
@@ -0,0 +1,128 @@
+/* eslint-env jquery, browser */
+/* eslint-disable comma-dangle, no-empty */
+/* global _utils, PARAMS, VERSION, signal, assert, log, debugPrint,
+ bridgedSettings, POPUP */
+
+// JSON export / import helpers proto module
+var SettingsJSON = (function() {
+ _utils.exists;
+ assert.exists;
+
+ return {
+ setPath: setPath,
+ rollupPaths: rollupPaths,
+ encodeNodes: encodeNodes,
+ exportAll: exportAll,
+ showSettings: showSettings,
+ applyJSON: applyJSON,
+ promptJSON: promptJSON,
+ popupJSON: popupJSON,
+ };
+
+ function encodeNodes(resolver) {
+ return resolver.getAllNodes().reduce((function(out, input, i) {
+ debugPrint('input['+i+']', input.id);
+ var id = input.id,
+ key = resolver.getKey(id);
+ debugPrint('toJSON', id, key, input.id);
+ setPath(out, key.split('/'), resolver.getValue(key));
+ return out;
+ }).bind(this), {});
+ }
+
+ function setPath(obj, path, value) {
+ var key = path.pop();
+ obj = path.reduce(function(obj, subkey) {
+ return obj[subkey] = obj[subkey] || {};
+ }, obj);
+ debugPrint('setPath', key, Object.keys(obj));
+ obj[key] = value;
+ }
+
+ function rollupPaths(obj, output, path) {
+ path = path || [];
+ output = output || {};
+ // log('rollupPaths', Object.keys(obj||{}), Object.keys(output), path);
+ for (var p in obj) {
+ path.push(p);
+ var value = obj[p];
+ if (value && typeof value === 'object') {
+ rollupPaths(obj[p], output, path);
+ } else {
+ output[path.join('/')] = value;
+ }
+ path.pop();
+ }
+ return output;
+ }
+
+ function exportAll(resolver, name) {
+ var settings = encodeNodes(resolver);
+ Object.keys(settings).forEach(function(prop) {
+ if (typeof settings[prop] === 'object') {
+ _utils.sortedAssign(settings[prop]);
+ }
+ });
+ return {
+ version: VERSION,
+ name: name || undefined,
+ settings: settings,
+ _metadata: { timestamp: new Date(), PARAMS: PARAMS, url: location.href, }
+ };
+ }
+
+ function showSettings(resolver, saveName) {
+ popupJSON(saveName || '(current settings)', Object.assign(exportAll(resolver, saveName), {
+ extraParams: bridgedSettings.extraParams,
+ }));
+ }
+
+ function popupJSON(title, tmp) {
+ var HTML = document.getElementById('POPUP').innerHTML
+ .replace(/\bxx-script\b/g, 'script')
+ .replace('JSON', JSON.stringify(tmp, 0, 2).replace(/\n/g, '
'));
+ if (arguments[3] === '_overlayWebWindow') {
+ bridgedSettings.sendEvent({
+ method: 'overlayWebWindow',
+ options: {
+ title: 'app-camera-move-export' + (title ? '::'+title : ''),
+ content: HTML,
+ },
+ });
+ } else {
+ // make the browser address bar less ugly by putting spaces and friedly name as a "URL footer"
+ var footer = '<\!-- #' + HTML.substr(0,256).replace(/./g,' ') + (title || 'Camera Move Settings');
+ window.open("data:text/html;escape," + encodeURIComponent(HTML) + footer,"app-camera-move-export");
+ }
+ }
+
+ function applyJSON(resolver, name, tmp) {
+ assert('version' in tmp && 'settings' in tmp, 'invalid settings record: ' + JSON.stringify(tmp));
+ var settings = rollupPaths(tmp.settings);
+ for (var p in settings) {
+ if (/^[.]/.test(p)) {
+ continue;
+ }
+ var key = resolver.getId(p, true);
+ if (!key) {
+ log('$applySettings -- skipping unregistered Settings key: ', p);
+ } else {
+ resolver.setValue(p, settings[p], name+'.settings.'+p);
+ }
+ }
+ }
+
+ function promptJSON() {
+ var json = window.prompt('(paste JSON here)', '');
+ if (!json) {
+ return;
+ }
+ try {
+ json = JSON.parse(json);
+ } catch (e) {
+ throw new Error('Could not parse pasted JSON: ' + e + '\n\n' + (json||'').replace(/").children(0).text(hash);
+ // LESS needs some help to recompile inline styles, so a fresh copy of the source nodes is swapped-in
+ var newNodes = lessManager.elements.clone().appendTo(document.body);
+ // less.modifyVars && less.modifyVars(true, globalVars);
+ // note: refresh(reload, modifyVars, clearFileCache)
+ less.refresh(false, globalVars).then(function(result) {
+ log('less.refresh completed OK', result);
+ })['catch'](function(err) {
+ log('less ERROR:', err);
+ });
+ var oldNodes = onViewportUpdated.lastNodes;
+ oldNodes && oldNodes.remove();
+ onViewportUpdated.lastNodes = newNodes;
+ }
+ onViewportUpdated.lastHash = hash;
+ }
+}
diff --git a/unpublishedScripts/marketplace/camera-move/_tooltips.js b/unpublishedScripts/marketplace/camera-move/_tooltips.js
new file mode 100644
index 0000000000..c1df716d24
--- /dev/null
+++ b/unpublishedScripts/marketplace/camera-move/_tooltips.js
@@ -0,0 +1,147 @@
+/* eslint-env jquery, browser */
+/* eslint-disable comma-dangle, no-empty */
+/* global PARAMS, signal, assert, log, debugPrint */
+
+// ----------------------------------------------------------------------------
+// manage jquery-tooltipster hover tooltips
+var TooltipManager = (function(global) {
+ function TooltipManager(options) {
+ this.options = options;
+ assert(options.elements, 'TooltipManager constructor expects options.elements');
+ assert(options.tooltips, 'TooltipManager constructor expects options.tooltips');
+ var TOOLTIPS = this.TOOLTIPS = {};
+ $(options.tooltips).find('[for]').each(function() {
+ var id = $(this).attr('for');
+ TOOLTIPS[id] = $(this).html();
+ });
+
+ var _self = this;
+ options.elements.each(function() {
+ var element = $($(this).closest('.tooltip-target').get(0) || this),
+ input = element.is('input, button') ? element : element.find('input, button'),
+ parent = element.is('.row, button') ? element : element.parent(),
+ id = element.attr('for') || input.prop('id'),
+ tip = TOOLTIPS[id] || element.prop('title');
+
+ var tooltipSide = element.data('tooltipSide');
+
+ debugPrint('binding tooltip', tooltipSide, element[0].nodeName, id || element[0], tip);
+ if (!tip) {
+ return log('missing tooltip:', this.nodeName, id || this.id || this.name || $(this).text());
+ }
+ if (element.is('.tooltipstered')) {
+ log('already tooltipstered!?', this.id, this.name, id);
+ return;
+ }
+ var instance = element.tooltipster({
+ theme: ['tooltipster-noir'],
+ side: tooltipSide || (
+ input.is('button') ? 'top' :
+ input.closest('.slider').get(0) || input.closest('.column').get(0) ? ['top','bottom'] :
+ ['right','top','bottom', 'left']
+ ),
+ content: tip,
+ updateAnimation: 'scale',
+ trigger: !options.testmode ? 'hover' : 'click',
+ distance: element.is('.slider.row') ? -20 : undefined,// element
+ delay: [500, 1000],
+ contentAsHTML: true,
+ interactive: options.testmode,
+ minWidth: options.viewport && options.viewport.min.width,
+ maxWidth: options.viewport && options.viewport.max.width,
+ }).tooltipster('instance');
+
+ instance.on('close', function(event) {
+ if (options.keepopen === element) {
+ debugPrint(event.type, 'canceling close keepopen === element', id);
+ event.stop();
+ options.keepopen = null;
+ }
+ });
+ instance.on('before', function(event) {
+ debugPrint(event.type, 'before', event);
+ !options.testmode && _self.closeAll();
+ !options.enabled && event.stop();
+ return;
+ });
+ parent.find(':focusable, input, [tabindex], button, .control')
+ .add(parent)
+ .add(input.closest(':focusable, input, [tabindex]'))
+ .on({
+ click: function(evt) {
+ if (input.is('button')) {
+ return setTimeout(instance.close.bind(instance,null),50);
+ }
+ options.keepopen = element; 0&&instance.open();
+ },
+ focus: instance.open.bind(instance, null),
+ blur: function(evt) {
+ instance.close(); _self.openFocusedTooltip();
+ },
+ });
+ });
+ Object.assign(this, {
+ openFocusedTooltip: function() {
+ if (!this.options.enabled) {
+ return;
+ }
+ setTimeout(function() {
+ if (!document.activeElement || document.activeElement === document.body ||
+ !$(document.activeElement).closest('section')) {
+ return;
+ }
+ var tip = $([])
+ .add($(document.activeElement))
+ .add($(document.activeElement).find('.tooltipstered'))
+ .add($(document.activeElement).closest('.tooltipstered'))
+ .filter('.tooltipstered');
+ if (tip.is('.tooltipstered')) {
+ // log('opening focused tooltip', tip.length, tip[0].id);
+ tip.tooltipster('open');
+ }
+ },1);
+ },
+ rapidClose: function(instance, reopen) {
+ if (!instance.status().open) {
+ return;
+ }
+ instance.elementTooltip() && $(instance.elementTooltip()).hide();
+ instance.close(function() {
+ reopen && instance.open();
+ });
+ },
+ openAll: function() {
+ $('.tooltipstered').tooltipster('open');
+ },
+ closeAll: function() {
+ $.tooltipster.instances().forEach(function(instance) {
+ this.rapidClose(instance);
+ }.bind(this));
+ },
+ updateViewport: function(viewport) {
+ var options = {
+ minWidth: viewport.min.width,
+ maxWidth: viewport.max.width,
+ };
+ $.tooltipster.setDefaults(options);
+ log('updating tooltipster options', JSON.stringify(options));
+ $.tooltipster.instances().forEach(function(instance) {
+ instance.option('minWidth', options.minWidth);
+ instance.option('maxWidth', options.maxWidth);
+ this.rapidClose(instance, instance.status().open);
+ }.bind(this));
+ },
+ enable: function() {
+ this.options.enabled = true;
+ if (this.options.testmode) {
+ this.openAll();
+ }
+ },
+ disable: function() {
+ this.options.enabled = false;
+ this.closeAll();
+ },
+ });
+ }
+ return TooltipManager;
+})(this);
diff --git a/unpublishedScripts/marketplace/camera-move/app-camera-move.js b/unpublishedScripts/marketplace/camera-move/app-camera-move.js
index 25c11e5a93..7ec38d6fab 100644
--- a/unpublishedScripts/marketplace/camera-move/app-camera-move.js
+++ b/unpublishedScripts/marketplace/camera-move/app-camera-move.js
@@ -9,7 +9,7 @@
// This Client script sets up the Camera Control Tablet App, which can be used to configure and
// drive your avatar with easing/smoothing movement constraints for a less jittery filming experience.
-/* eslint-disable comma-dangle */
+/* eslint-disable comma-dangle, no-empty */
"use strict";
var VERSION = '0.0.0d',
@@ -19,7 +19,7 @@ var VERSION = '0.0.0d',
text: '\nCam Drive',
icon: Script.resolvePath('Eye-Camera.svg'),
},
- DEFAULT_TOGGLE_KEY = '{ "text": "SPACE" }';
+ DEFAULT_TOGGLE_KEY = { text: 'SPACE' };
var MINIMAL_CURSOR_SCALE = 0.5,
FILENAME = Script.resolvePath(''),
@@ -27,10 +27,7 @@ var MINIMAL_CURSOR_SCALE = 0.5,
false || (FILENAME.match(/[?]debug[=](\w+)/)||[])[1] ||
Settings.getValue(NAMESPACE + '/debug')
),
- EPSILON = 1e-6,
- DEG_TO_RAD = Math.PI / 180.0;
-
-WANT_DEBUG = 1;
+ EPSILON = 1e-6;
function log() {
print( NAMESPACE + ' | ' + [].slice.call(arguments).join(' ') );
@@ -42,6 +39,7 @@ var require = Script.require,
overlayDebugOutput = function(){};
if (WANT_DEBUG) {
+ log('WANT_DEBUG is true; instrumenting debug rigging', WANT_DEBUG);
_instrumentDebugValues();
}
@@ -49,7 +47,9 @@ var _utils = require('./modules/_utils.js'),
assert = _utils.assert,
CustomSettingsApp = require('./modules/custom-settings-app/CustomSettingsApp.js'),
movementUtils = require('./modules/movement-utils.js?'+ +new Date),
- configUtils = require('./modules/config-utils.js');
+ configUtils = require('./modules/config-utils.js'),
+ AvatarUpdater = require('./avatar-updater.js');
+
Object.assign = Object.assign || _utils.assign;
@@ -78,11 +78,13 @@ var DEFAULTS = {
'rotation-x-speed': 45,
'rotation-y-speed': 60,
'rotation-z-speed': 1,
- 'rotation-mouse-multiplier': 1.0,
- 'rotation-keyboard-multiplier': 1.0,
+ 'mouse-multiplier': 1.0,
+ 'keyboard-multiplier': 1.0,
'ui-enable-tooltips': true,
'ui-show-advanced-options': false,
+
+ // 'toggle-key': DEFAULT_TOGGLE_KEY,
};
// map setting names to/from corresponding Menu and API properties
@@ -117,9 +119,19 @@ var APPLICATION_SETTINGS = {
cameraControls.setEnabled(!!nv);
},
},
+ // 'toggle-key': {
+ // get: function() { try {
+ // return JSON.parse(cameraConfig.getValue('toggle-key'));
+ // } catch (e) {
+ // return DEFAULT_TOGGLE_KEY;
+ // } },
+ // set: function(nv) {
+ // assert(typeof nv === 'object', 'new toggle-key is not an object: ' + nv);
+ // cameraConfig.setValue('toggle-key', JSON.parse(JSON.stringify(nv)));
+ // },
+ // },
};
-
var DEBUG_INFO = {
// these values are also sent to the tablet app after EventBridge initialization
appVersion: VERSION,
@@ -157,9 +169,9 @@ var globalState = {
// current input controls' effective velocities
currentVelocities: new movementUtils.VelocityTracker({
translation: Vec3.ZERO,
- step_translation: Vec3.ZERO,
+ step_translation: Vec3.ZERO, // eslint-disable-line camelcase
rotation: Vec3.ZERO,
- step_rotation: Vec3.ZERO,
+ step_rotation: Vec3.ZERO, // eslint-disable-line camelcase
zoom: Vec3.ZERO,
}),
};
@@ -186,9 +198,11 @@ function main() {
defaultValues: DEFAULTS,
});
- var toggleKey = JSON.parse(DEFAULT_TOGGLE_KEY);
+ var toggleKey = DEFAULT_TOGGLE_KEY;
if (cameraConfig.getValue('toggle-key')) {
- try { toggleKey = JSON.parse(cameraConfig.getValue('toggle-key')); } catch(e) {}
+ try {
+ toggleKey = JSON.parse(cameraConfig.getValue('toggle-key'));
+ } catch (e) {}
}
// set up a monitor to observe configuration changes between the two sources
var MONITOR_INTERVAL_MS = 1000;
@@ -219,11 +233,11 @@ function main() {
});
settingsApp.onUnhandledMessage = function(msg) {
- switch(msg.method) {
+ switch (msg.method) {
case 'window.close': {
this.toggle(false);
} break;
- case 'reloadClientScript': {
+ case 'reloadClientScript': {
log('reloadClientScript...');
_utils.reloadClientScript(FILENAME);
} break;
@@ -236,10 +250,9 @@ function main() {
}, 500);
} break;
case 'reset': {
- //if (!Window.confirm('Reset all camera move settings to system defaults?')) {
+ // if (!Window.confirm('Reset all camera move settings to system defaults?')) {
// return;
- //}
- var novalue = Uuid.generate();
+ // }
var resetValues = {};
Object.keys(DEFAULTS).reduce(function(out, key) {
var resolved = cameraConfig.resolve(key);
@@ -259,7 +272,7 @@ function main() {
}
} break;
case 'overlayWebWindow': {
- _overlayWebWindow(msg.options);
+ _utils._overlayWebWindow(msg.options);
} break;
default: {
log('onUnhandledMessage', JSON.stringify(msg,0,2));
@@ -273,8 +286,8 @@ function main() {
namespace: DEFAULTS.namespace,
mouseSmooth: cameraConfig.getValue('enable-mouse-smooth'),
xexcludeNames: [ 'Keyboard.C', 'Keyboard.E', 'Actions.TranslateY' ],
- mouseMultiplier: cameraConfig.getValue('rotation-mouse-multiplier'),
- keyboardMultiplier: cameraConfig.getValue('rotation-keyboard-multiplier'),
+ mouseMultiplier: cameraConfig.getValue('mouse-multiplier'),
+ keyboardMultiplier: cameraConfig.getValue('keyboard-multiplier'),
eventFilter: function eventFilter(from, event, defaultFilter) {
var result = defaultFilter(from, event),
driveKeyName = event.driveKeyName;
@@ -299,15 +312,21 @@ function main() {
Script.scriptEnding.connect(eventMapper, 'disable');
applicationConfig.register({
'enable-mouse-smooth': { object: [ eventMapper.options, 'mouseSmooth' ] },
- 'rotation-keyboard-multiplier': { object: [ eventMapper.options, 'keyboardMultiplier' ] },
- 'rotation-mouse-multiplier': { object: [ eventMapper.options, 'mouseMultiplier' ] },
+ 'keyboard-multiplier': { object: [ eventMapper.options, 'keyboardMultiplier' ] },
+ 'mouse-multiplier': { object: [ eventMapper.options, 'mouseMultiplier' ] },
});
// ----------------------------------------------------------------------------
// set up the top-level camera controls manager / animator
+ var avatarUpdater = new AvatarUpdater({
+ debugChannel: _debugChannel,
+ globalState: globalState,
+ getCameraMovementSettings: getCameraMovementSettings,
+ getMovementState: _utils.bind(eventMapper, 'getState'),
+ });
cameraControls = new movementUtils.CameraControls({
namespace: DEFAULTS.namespace,
- update: update,
+ update: avatarUpdater,
threadMode: cameraConfig.getValue('thread-update-mode'),
fps: cameraConfig.getValue('fps'),
getRuntimeSeconds: _utils.getRuntimeSeconds,
@@ -358,7 +377,7 @@ function main() {
function updateButtonText() {
var lines = [
settingsApp.isActive ? '(app open)' : '',
- cameraControls.enabled ? (update.momentaryFPS||0).toFixed(2) + 'fps' : BUTTON_CONFIG.text.trim()
+ cameraControls.enabled ? (avatarUpdater.update.momentaryFPS||0).toFixed(2) + 'fps' : BUTTON_CONFIG.text.trim()
];
button && button.editProperties({ text: lines.join('\n') });
}
@@ -375,15 +394,15 @@ function main() {
fpsTimeout = 0;
}
eventMapper.disable();
- _resetMyAvatarMotor({ MyAvatar: MyAvatar });
+ avatarUpdater._resetMyAvatarMotor({ MyAvatar: MyAvatar });
updateButtonText();
}
- cameraConfig.getValue('debug') && Overlays.editOverlay(overlayDebugOutput.overlayID, { visible: enabled });
+ overlayDebugOutput.overlayID && Overlays.editOverlay(overlayDebugOutput.overlayID, { visible: enabled });
});
var resetIfChanged = [
'minimal-cursor', 'drive-mode', 'fps', 'thread-update-mode',
- 'rotation-mouse-multiplier', 'rotation-keyboard-multiplier',
+ 'mouse-multiplier', 'keyboard-multiplier',
'enable-mouse-smooth', 'constant-delta-time',
].filter(Boolean).map(_utils.bind(cameraConfig, 'resolve'));
@@ -430,7 +449,7 @@ function onCameraControlsEnabled() {
Reticle.scale = MINIMAL_CURSOR_SCALE;
}
log('cameraConfig', JSON.stringify({
- cameraConfig: getCameraMovementSettings(cameraConfig),
+ cameraConfig: getCameraMovementSettings(),
//DEFAULTS: DEFAULTS
}));
}
@@ -463,20 +482,22 @@ function onCameraModeChanged(mode, oldMode) {
}
// consolidate and normalize cameraConfig settings
-function getCameraMovementSettings(cameraConfig) {
+function getCameraMovementSettings() {
return {
epsilon: EPSILON,
debug: cameraConfig.getValue('debug'),
driveMode: cameraConfig.getValue('drive-mode'),
threadMode: cameraConfig.getValue('thread-update-mode'),
+ fps: cameraConfig.getValue('fps'),
useHead: cameraConfig.getValue('use-head'),
stayGrounded: cameraConfig.getValue('stay-grounded'),
preventRoll: cameraConfig.getValue('prevent-roll'),
useConstantDeltaTime: cameraConfig.getValue('constant-delta-time'),
+ collisionsEnabled: applicationConfig.getValue('Avatar/Enable Avatar Collisions'),
mouseSmooth: cameraConfig.getValue('enable-mouse-smooth'),
- mouseMultiplier: cameraConfig.getValue('rotation-mouse-multiplier'),
- keyboardMultiplier: cameraConfig.getValue('rotation-keyboard-multiplier'),
+ mouseMultiplier: cameraConfig.getValue('mouse-multiplier'),
+ keyboardMultiplier: cameraConfig.getValue('keyboard-multiplier'),
rotation: _getEasingGroup(cameraConfig, 'rotation'),
translation: _getEasingGroup(cameraConfig, 'translation'),
@@ -489,12 +510,8 @@ function getCameraMovementSettings(cameraConfig) {
if (group === 'zoom') {
// BoomIn / TranslateCameraZ support is only partially plumbed -- for now use scaled translation easings
group = 'translation';
- multiplier = 0.01;
- } else if (group === 'rotation') {
- // degrees -> radians
- //multiplier = DEG_TO_RAD;
+ multiplier = 0.001;
}
-
return {
easeIn: cameraConfig.getFloat(group + '-ease-in'),
easeOut: cameraConfig.getFloat(group + '-ease-out'),
@@ -508,232 +525,7 @@ function getCameraMovementSettings(cameraConfig) {
}
}
-var DEFAULT_MOTOR_TIMESCALE = 1e6; // a large value that matches Interface's default
-var EASED_MOTOR_TIMESCALE = 0.01; // a small value to make Interface quickly apply MyAvatar.motorVelocity
-var EASED_MOTOR_THRESHOLD = 0.1; // above this speed (m/s) EASED_MOTOR_TIMESCALE is used
-var ACCELERATION_MULTIPLIERS = { translation: 1, rotation: 1, zoom: 1 };
-var STAYGROUNDED_PITCH_THRESHOLD = 45.0; // degrees; ground level is maintained when pitch is within this threshold
-var MIN_DELTA_TIME = 0.0001; // to avoid math overflow, never consider dt less than this value
-
-update.frameCount = 0;
-update.endTime = _utils.getRuntimeSeconds();
-
-function update(dt) {
- update.frameCount++;
- var startTime = _utils.getRuntimeSeconds();
- var settings = getCameraMovementSettings(cameraConfig);
-
- var collisions = applicationConfig.getValue('Avatar/Enable Avatar Collisions'),
- independentCamera = Camera.mode === 'independent',
- headPitch = MyAvatar.headPitch;
-
- var actualDeltaTime = Math.max(MIN_DELTA_TIME, (startTime - update.endTime)),
- deltaTime;
-
- if (settings.useConstantDeltaTime) {
- deltaTime = settings.threadMode === movementUtils.CameraControls.ANIMATION_FRAME ?
- (1 / cameraControls.fps) : (1 / 90);
- } else if (settings.threadMode === movementUtils.CameraControls.SCRIPT_UPDATE) {
- deltaTime = dt;
- } else {
- deltaTime = actualDeltaTime;
- }
-
- var orientationProperty = settings.useHead ? 'headOrientation' : 'orientation',
- currentOrientation = independentCamera ? Camera.orientation : MyAvatar[orientationProperty],
- currentPosition = MyAvatar.position;
-
- var previousValues = globalState.previousValues,
- pendingChanges = globalState.pendingChanges,
- currentVelocities = globalState.currentVelocities;
-
- var movementState = eventMapper.getState({ update: deltaTime }),
- targetState = movementUtils.applyEasing(deltaTime, 'easeIn', settings, movementState, ACCELERATION_MULTIPLIERS),
- dragState = movementUtils.applyEasing(deltaTime, 'easeOut', settings, currentVelocities, ACCELERATION_MULTIPLIERS);
-
- currentVelocities.integrate(targetState, currentVelocities, dragState, settings);
-
- var currentSpeed = Vec3.length(currentVelocities.translation),
- targetSpeed = Vec3.length(movementState.translation),
- verticalHold = movementState.isGrounded && settings.stayGrounded && Math.abs(headPitch) < STAYGROUNDED_PITCH_THRESHOLD;
-
- var deltaOrientation = Quat.fromVec3Degrees(Vec3.multiply(deltaTime, currentVelocities.rotation)),
- targetOrientation = Quat.normalize(Quat.multiply(currentOrientation, deltaOrientation));
-
- var targetVelocity = Vec3.multiplyQbyV(targetOrientation, currentVelocities.translation);
-
- if (verticalHold) {
- targetVelocity.y = 0;
- }
-
- var deltaPosition = Vec3.multiply(deltaTime, targetVelocity);
-
- _resetMyAvatarMotor(pendingChanges);
-
- if (!independentCamera) {
- var DriveModes = movementUtils.DriveModes;
- switch(settings.driveMode) {
- case DriveModes.MOTOR: {
- if (currentSpeed > EPSILON || targetSpeed > EPSILON) {
- var motorTimescale = (currentSpeed > EASED_MOTOR_THRESHOLD ? EASED_MOTOR_TIMESCALE : DEFAULT_MOTOR_TIMESCALE);
- var motorPitch = Quat.fromPitchYawRollDegrees(headPitch, 180, 0),
- motorVelocity = Vec3.multiplyQbyV(motorPitch, currentVelocities.translation);
- if (verticalHold) {
- motorVelocity.y = 0;
- }
- Object.assign(pendingChanges.MyAvatar, {
- motorVelocity: motorVelocity,
- motorTimescale: motorTimescale,
- });
- }
- break;
- }
- case DriveModes.THRUST: {
- var thrustVector = currentVelocities.translation,
- maxThrust = settings.translation.maxVelocity,
- thrust;
- if (targetSpeed > EPSILON) {
- thrust = movementUtils.calculateThrust(maxThrust * 5, thrustVector, previousValues.thrust);
- } else if (currentSpeed > 1 && Vec3.length(previousValues.thrust) > 1) {
- thrust = Vec3.multiply(-currentSpeed / 10.0, thrustVector);
- } else {
- thrust = Vec3.ZERO;
- }
- if (thrust) {
- thrust = Vec3.multiplyQbyV(MyAvatar[orientationProperty], thrust);
- if (verticalHold) {
- thrust.y = 0;
- }
- }
- previousValues.thrust = pendingChanges.MyAvatar.setThrust = thrust;
- break;
- }
- case DriveModes.JITTER_TEST:
- case DriveModes.POSITION: {
- pendingChanges.MyAvatar.position = Vec3.sum(currentPosition, deltaPosition);
- break;
- }
- default: {
- throw new Error('unknown driveMode: ' + settings.driveMode);
- break;
- }
- }
- }
-
- var finalOrientation;
- switch (Camera.mode) {
- case 'mirror': // fall through
- case 'independent':
- targetOrientation = settings.preventRoll ? Quat.cancelOutRoll(targetOrientation) : targetOrientation;
- var boomVector = Vec3.multiply(-currentVelocities.zoom.z, Quat.getFront(targetOrientation)),
- deltaCameraPosition = Vec3.sum(boomVector, deltaPosition);
- Object.assign(pendingChanges.Camera, {
- position: Vec3.sum(Camera.position, deltaCameraPosition),
- orientation: targetOrientation,
- });
- break;
- case 'entity':
- finalOrientation = targetOrientation;
- break;
- default: // 'first person', 'third person'
- finalOrientation = targetOrientation;
- break;
- }
-
- if (settings.driveMode === movementUtils.DriveModes.JITTER_TEST) {
- finalOrientation = Quat.multiply(MyAvatar[orientationProperty], Quat.fromPitchYawRollDegrees(0, 60 * deltaTime, 0));
- // Quat.fromPitchYawRollDegrees(0, _utils.getRuntimeSeconds() * 60, 0)
- }
-
- if (finalOrientation) {
- if (settings.preventRoll) {
- finalOrientation = Quat.cancelOutRoll(finalOrientation);
- }
- previousValues.finalOrientation = pendingChanges.MyAvatar[orientationProperty] = Quat.normalize(finalOrientation);
- }
-
- if (!movementState.mouseSmooth && movementState.isRightMouseButton) {
- // directly apply mouse pitch and yaw when mouse smoothing is disabled
- _applyDirectPitchYaw(deltaTime, movementState, settings);
- }
-
- var endTime = _utils.getRuntimeSeconds();
- var cycleTime = endTime - update.endTime;
- update.endTime = endTime;
-
- var submitted = pendingChanges.submit();
-
- update.momentaryFPS = 1 / actualDeltaTime;
-
- if (settings.debug && update.frameCount % 120 === 0) {
- Messages.sendLocalMessage(_debugChannel, JSON.stringify({
- threadMode: cameraControls.threadMode,
- driveMode: settings.driveMode,
- orientationProperty: orientationProperty,
- isGrounded: movementState.isGrounded,
- targetAnimationFPS: cameraControls.threadMode === movementUtils.CameraControls.ANIMATION_FRAME ? cameraControls.fps : undefined,
- actualFPS: 1 / actualDeltaTime,
- effectiveAnimationFPS: 1 / deltaTime,
- seconds: {
- startTime: startTime,
- endTime: endTime,
- },
- milliseconds: {
- actualDeltaTime: actualDeltaTime * 1000,
- deltaTime: deltaTime * 1000,
- cycleTime: cycleTime * 1000,
- calculationTime: (endTime - startTime) * 1000,
- },
- finalOrientation: finalOrientation,
- thrust: thrust,
- maxVelocity: settings.translation,
- targetVelocity: targetVelocity,
- currentSpeed: currentSpeed,
- targetSpeed: targetSpeed,
- }, 0, 2));
- }
-}
-
-if (0) {
- Script.update.connect(gc);
- Script.scriptEnding.connect(function() {
- Script.update.disconnect(gc);
- });
-}
-
-function _applyDirectPitchYaw(deltaTime, movementState, settings) {
- var orientationProperty = settings.useHead ? 'headOrientation' : 'orientation',
- rotation = movementState.rotation,
- speed = Vec3.multiply(-DEG_TO_RAD / 2.0, settings.rotation.speed);
-
- var previousValues = globalState.previousValues,
- pendingChanges = globalState.pendingChanges,
- currentVelocities = globalState.currentVelocities;
-
- var previous = previousValues.pitchYawRoll,
- target = Vec3.multiply(deltaTime, Vec3.multiplyVbyV(rotation, speed)),
- pitchYawRoll = Vec3.mix(previous, target, 0.5),
- orientation = Quat.fromVec3Degrees(pitchYawRoll);
-
- previousValues.pitchYawRoll = pitchYawRoll;
-
- if (pendingChanges.MyAvatar.headOrientation || pendingChanges.MyAvatar.orientation) {
- var newOrientation = Quat.multiply(MyAvatar[orientationProperty], orientation);
- delete pendingChanges.MyAvatar.headOrientation;
- delete pendingChanges.MyAvatar.orientation;
- if (settings.preventRoll) {
- newOrientation = Quat.cancelOutRoll(newOrientation);
- }
- MyAvatar[orientationProperty] = newOrientation;
- } else if (pendingChanges.Camera.orientation) {
- var cameraOrientation = Quat.multiply(Camera.orientation, orientation);
- if (settings.preventRoll) {
- cameraOrientation = Quat.cancelOutRoll(cameraOrientation);
- }
- Camera.orientation = cameraOrientation;
- }
- currentVelocities.rotation = Vec3.ZERO;
-}
+// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
function _startConfigationMonitor(applicationConfig, cameraConfig, interval) {
@@ -753,20 +545,6 @@ function _startConfigationMonitor(applicationConfig, cameraConfig, interval) {
}, interval);
}
-// ----------------------------------------------------------------------------
-
-function _resetMyAvatarMotor(targetObject) {
- if (MyAvatar.motorTimescale !== DEFAULT_MOTOR_TIMESCALE) {
- targetObject.MyAvatar.motorTimescale = DEFAULT_MOTOR_TIMESCALE;
- }
- if (MyAvatar.motorReferenceFrame !== 'avatar') {
- targetObject.MyAvatar.motorReferenceFrame = 'avatar';
- }
- if (Vec3.length(MyAvatar.motorVelocity)) {
- targetObject.MyAvatar.motorVelocity = Vec3.ZERO;
- }
-}
-
// ----------------------------------------------------------------------------
function _fixedPrecisionStringifiyFilter(key, value, object) {
if (typeof value === 'object' && value && 'w' in value) {
@@ -789,7 +567,7 @@ function _createOverlayDebugOutput(options) {
Script.scriptEnding.connect(function() {
Overlays.deleteOverlay(overlayDebugOutput.overlayID);
- Messages.unsubscribe(debugChannel);
+ Messages.unsubscribe(_debugChannel);
Messages.messageReceived.disconnect(onMessageReceived);
});
function overlayDebugOutput(output) {
@@ -835,8 +613,8 @@ function _instrumentDebugValues() {
require = Script.require(Script.resolvePath('./modules/_utils.js') + cacheBuster).makeDebugRequire(Script.resolvePath('.'));
APP_HTML_URL += cacheBuster;
overlayDebugOutput = _createOverlayDebugOutput({
- lineHeight: 10,
- font: { size: 10 },
+ lineHeight: 12,
+ font: { size: 12 },
width: 250, height: 800 });
// auto-disable camera move mode when debugging
Script.scriptEnding.connect(function() {
@@ -847,11 +625,13 @@ function _instrumentDebugValues() {
// Show fatal (unhandled) exceptions in a BSOD popup
Script.unhandledException.connect(function onUnhandledException(error) {
log('UNHANDLED EXCEPTION!!', error, error && error.stack);
- try { cameraControls.disable(); } catch(e) {}
+ try {
+ cameraControls.disable();
+ } catch (e) {}
Script.unhandledException.disconnect(onUnhandledException);
if (WANT_DEBUG) {
// show blue screen of death with the error details
- _utils.BSOD({
+ new _utils.BSOD({
error: error,
buttons: [ 'Abort', 'Retry', 'Fail' ],
debugInfo: DEBUG_INFO,
diff --git a/unpublishedScripts/marketplace/camera-move/app.html b/unpublishedScripts/marketplace/camera-move/app.html
index c27e179df5..a86813421d 100644
--- a/unpublishedScripts/marketplace/camera-move/app.html
+++ b/unpublishedScripts/marketplace/camera-move/app.html
@@ -10,13 +10,14 @@
.content { display: none }
-
+
+
@@ -33,19 +34,24 @@
browserUtils = new _utils.BrowserUtils(window);
}
+
+
+
+
+
+} catch(e) { alert(e) }
-
@@ -144,12 +173,6 @@
@@ -193,11 +216,11 @@