From 89ebf4998f775b4b72d471823eead80d6f53060d Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 10:28:56 -0400 Subject: [PATCH 01/16] relocate/archive original doppleganger mirror into unpublishedScripts/marketplace/doppleganger-mirror --- .../marketplace/doppleganger-mirror}/app-doppleganger.js | 4 ++-- .../marketplace/doppleganger-mirror}/doppleganger-a.svg | 0 .../marketplace/doppleganger-mirror}/doppleganger-i.svg | 0 .../marketplace/doppleganger-mirror}/doppleganger.js | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename {scripts/system => unpublishedScripts/marketplace/doppleganger-mirror}/app-doppleganger.js (95%) rename {interface/resources/icons/tablet-icons => unpublishedScripts/marketplace/doppleganger-mirror}/doppleganger-a.svg (100%) rename {interface/resources/icons/tablet-icons => unpublishedScripts/marketplace/doppleganger-mirror}/doppleganger-i.svg (100%) rename {scripts/system => unpublishedScripts/marketplace/doppleganger-mirror}/doppleganger.js (100%) diff --git a/scripts/system/app-doppleganger.js b/unpublishedScripts/marketplace/doppleganger-mirror/app-doppleganger.js similarity index 95% rename from scripts/system/app-doppleganger.js rename to unpublishedScripts/marketplace/doppleganger-mirror/app-doppleganger.js index d7f85e5767..f4c7bf99c0 100644 --- a/scripts/system/app-doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-mirror/app-doppleganger.js @@ -14,8 +14,8 @@ var DopplegangerClass = Script.require('./doppleganger.js'); var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'), button = tablet.addButton({ - icon: "icons/tablet-icons/doppleganger-i.svg", - activeIcon: "icons/tablet-icons/doppleganger-a.svg", + icon: Script.resolvePath('./doppleganger-i.svg'), + activeIcon: Script.resolvePath('./doppleganger-a.svg'), text: 'MIRROR' }); diff --git a/interface/resources/icons/tablet-icons/doppleganger-a.svg b/unpublishedScripts/marketplace/doppleganger-mirror/doppleganger-a.svg similarity index 100% rename from interface/resources/icons/tablet-icons/doppleganger-a.svg rename to unpublishedScripts/marketplace/doppleganger-mirror/doppleganger-a.svg diff --git a/interface/resources/icons/tablet-icons/doppleganger-i.svg b/unpublishedScripts/marketplace/doppleganger-mirror/doppleganger-i.svg similarity index 100% rename from interface/resources/icons/tablet-icons/doppleganger-i.svg rename to unpublishedScripts/marketplace/doppleganger-mirror/doppleganger-i.svg diff --git a/scripts/system/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-mirror/doppleganger.js similarity index 100% rename from scripts/system/doppleganger.js rename to unpublishedScripts/marketplace/doppleganger-mirror/doppleganger.js From 1a9db003e51187b206e05d14523ac98831bb232d Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 16:01:59 -0400 Subject: [PATCH 02/16] doppleganger + attachments --- .../doppleganger-attachments/Hat4.svg | 328 +++++++++++ .../app-doppleganger-attachments.js | 120 ++++ .../doppleganger-attachments.js | 237 ++++++++ .../doppleganger-attachments/doppleganger.js | 515 ++++++++++++++++++ .../doppleganger-attachments/model-helper.js | 333 +++++++++++ .../doppleganger-attachments/utils.js | 100 ++++ 6 files changed, 1633 insertions(+) create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/utils.js diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg new file mode 100644 index 0000000000..8d628cf1cd --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg @@ -0,0 +1,328 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js new file mode 100644 index 0000000000..c487232aa5 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -0,0 +1,120 @@ +// doppleganger-app.js +// +// Created by Timothy Dedischew on 04/21/2017. +// Copyright 2017 High Fidelity, Inc. +// +// This Client script creates an instance of a Doppleganger that can be toggled on/off via tablet button. +// (for more info see doppleganger.js) +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var require = function DEBUG_REQUIRE(id) { + return Script.require(id + '?'+new Date().getTime().toString(36)); +}; + +var DopplegangerClass = require('./doppleganger.js'), + DopplegangerAttachments = require('./doppleganger-attachments.js'), + modelHelper = require('./model-helper.js').modelHelper; + +var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'), + button = tablet.addButton({ + icon: Script.resolvePath("./Hat4.svg"), + // activeIcon: Script.resolvePath("./app-a.svg"), + text: 'Mannequin' + }); + +Script.scriptEnding.connect(function() { + tablet.removeButton(button); + button = null; +}); + +var doppleganger = new DopplegangerClass({ + avatar: MyAvatar, + mirrored: false, + autoUpdate: true, + type: 'overlay' +}); + +// add support for displaying regular (non-soft) attachments on the doppleganger +{ + var RECHECK_ATTACHMENT_MS = 1000; + var dopplegangerAttachments = new DopplegangerAttachments(doppleganger), + attachmentInterval = null, + lastHash = dopplegangerAttachments.getAttachmentsHash(); + + // monitor for attachment changes, but only when the doppleganger is active + doppleganger.activeChanged.connect(function(active, reason) { + if (attachmentInterval) { + Script.clearInterval(attachmentInterval); + } + if (active) { + attachmentInterval = Script.setInterval(checkAttachmentsHash, RECHECK_ATTACHMENT_MS); + } else { + attachmentInterval = null; + } + }); + function checkAttachmentsHash() { + var currentHash = dopplegangerAttachments.getAttachmentsHash(); + if (currentHash !== lastHash) { + lastHash = currentHash; + print('app-doppleganger | detect attachment change'); + dopplegangerAttachments.refreshAttachments(); + } + } +} + +// hide the doppleganger if this client script is unloaded +Script.scriptEnding.connect(doppleganger, 'stop'); + +// hide the doppleganger if the user switches domains (which might place them arbitrarily far away in world space) +function onDomainChanged() { + if (doppleganger.active) { + doppleganger.stop('domain_changed'); + } +} +Window.domainChanged.connect(onDomainChanged); +Window.domainConnectionRefused.connect(onDomainChanged); +Script.scriptEnding.connect(function() { + Window.domainChanged.disconnect(onDomainChanged); + Window.domainConnectionRefused.disconnect(onDomainChanged); +}); + +// toggle on/off via tablet button +button.clicked.connect(doppleganger, 'toggle'); + +// highlight tablet button based on current doppleganger state +doppleganger.activeChanged.connect(function(active, reason) { + if (button) { + button.editProperties({ isActive: active }); + print('doppleganger.activeChanged', active, reason); + } +}); + +// alert the user if there was an error applying their skeletonModelURL +doppleganger.modelLoaded.connect(function(error, result) { + if (doppleganger.active && error) { + Window.alert('doppleganger | ' + error + '\n' + doppleganger.skeletonModelURL); + } +}); + +// add debug indicators, but only if the user has configured the settings value +if (Settings.getValue('debug.doppleganger', false)) { + DopplegangerClass.addDebugControls(doppleganger); +} + +UserActivityLogger.logAction('doppleganger_app_load'); +doppleganger.activeChanged.connect(function(active, reason) { + if (active) { + UserActivityLogger.logAction('doppleganger_enable'); + } else { + if (reason === 'stop') { + // user intentionally toggled the doppleganger + UserActivityLogger.logAction('doppleganger_disable'); + } else { + print('doppleganger stopped:', reason); + UserActivityLogger.logAction('doppleganger_autodisable', { reason: reason }); + } + } +}); diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js new file mode 100644 index 0000000000..85c3d3589e --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -0,0 +1,237 @@ +/* eslint-env commonjs */ +/* eslint-disable comma-dangle, no-empty */ + +"use strict"; +module.exports = DopplegangerAttachments; + +DopplegangerAttachments.version = '0.0.0'; + +var require = function DEBUG_REQUIRE(id) { + return Script.require(id + '?'+new Date().getTime().toString(36)); +}; + +var _modelHelper = require('./model-helper.js'), + modelHelper = _modelHelper.modelHelper, + ModelReadyWatcher = _modelHelper.ModelReadyWatcher, + utils = require('./utils.js'); + +function log() { + print('doppleganger-attachments | ' + [].slice.call(arguments).join(' ')); +} + +function DopplegangerAttachments(doppleganger, options) { + utils.assign(this, { + _options: options, + doppleganger: doppleganger, + attachments: undefined, + manualJointSync: true, + }); + this._initialize(); + log('DopplegangerAttachments...', JSON.stringify(options)); +} +DopplegangerAttachments.prototype = { + // "hash" the current attachments (so that changes can be detected) + getAttachmentsHash: function() { + return JSON.stringify(this.doppleganger.avatar.getAttachmentsVariant()); + }, + // create a pure Object copy of the current native attachments variant + _cloneAttachmentsVariant: function() { + return JSON.parse(JSON.stringify(this.doppleganger.avatar.getAttachmentsVariant())); + }, + // fetch and resolve attachments to include jointIndex and other relevant $metadata + _getResolvedAttachments: function() { + var attachments = this._cloneAttachmentsVariant(), + objectID = this.doppleganger.objectID; + function toString() { + return '[attachment #' + this.$index + ' ' + this.$path + ' @ ' + this.jointName + '{' + this.$jointIndex + '}]'; + } + return attachments.map(function(attachment, i) { + var jointIndex = modelHelper.getJointIndex(objectID, attachment.jointName), + path = (attachment.modelUrl+'').split(/[?#]/)[0].split('/').slice(-3).join('/'); + return Object.defineProperties(attachment, { + $hash: { value: JSON.stringify(attachment) }, + $index: { value: i }, + $jointIndex: { value: jointIndex }, + $path: { value: path }, + toString: { value: toString }, + }); + }); + }, + // compare before / after attachment sets to determine which ones need to be (re)created + refreshAttachments: function() { + var before = this.attachments || [], + beforeIndex = before.reduce(function(out, att, index) { + out[att.$hash] = index; return out; + }, {}); + var after = this._getResolvedAttachments(), + afterIndex = after.reduce(function(out, att, index) { + out[att.$hash] = index; return out; + }, {}); + + Object.keys(beforeIndex).concat(Object.keys(afterIndex)).forEach(function(hash) { + if (hash in beforeIndex && hash in afterIndex) { + // print('reusing previous attachment', hash); + after[afterIndex[hash]] = before[beforeIndex[hash]]; + } else if (!(hash in afterIndex)) { + var attachment = before[beforeIndex[hash]]; + attachment.properties && attachment.properties.objectID && + modelHelper.deleteObject(attachment.properties.objectID); + delete attachment.properties; + } + }); + this.attachments = after; + this._createAttachmentObjects(); + }, + _createAttachmentObjects: function() { + try { + var attachments = this.attachments, + parentID = this.doppleganger.objectID, + jointNames = this.doppleganger.jointNames, + properties = modelHelper.getProperties(this.doppleganger.objectID); + + log('DopplegangerAttachments..._createAttachmentObjects', JSON.stringify({ + type: properties.type, + attachments: attachments.length, + parentID: parentID, + jointNames: jointNames.join(' | '), + },0,2)); + return attachments.map(utils.bind(this, function(attachment, i) { + var type = modelHelper.type(attachment.properties && attachment.properties.objectID); + if (type === 'overlay') { + log('skipping already-provisioned attachment object', type, attachment.properties && attachment.properties.name); + return attachment; + } + var jointIndex = attachment.$jointIndex, // jointNames.indexOf(attachment.jointName), + scale = this.doppleganger.avatar.scale * (attachment.scale||1.0); + + attachment.properties = utils.assign({ + name: attachment.toString(), + type: properties.type, + modelURL: attachment.modelUrl, + scale: scale, + dimensions: modelHelper.type(parentID) === 'entity' ? + Vec3.multiply(attachment.scale||1.0, Vec3.ONE) : undefined, + visible: false, + collisionless: true, + dynamic: false, + shapeType: 'none', + lifetime: 60, + grabbable: true, + }, !this.manualJointSync && { + parentID: parentID, + parentJointIndex: jointIndex, + }); + var objectID = attachment.properties.objectID = modelHelper.addObject(attachment.properties); + attachment._resource = ModelCache.prefetch(attachment.properties.modelURL); + attachment._modelReadier = new ModelReadyWatcher( { + resource: attachment._resource, + objectID: objectID, + }); + this.doppleganger.activeChanged.connect(attachment._modelReadier, '_stop'); + + attachment._modelReadier.modelReady.connect(this, function(err, result) { + if (err) { + log('>>>>> modelReady ERROR <<<<<: ' + err, attachment.modelUrl); + modelHelper.deleteObject(objectID); + return objectID = null; + } + log('attachment model ('+modelHelper.type(result.objectID)+') is ready; # joints ==', + result.jointNames && result.jointNames.length, result.naturalDimensions, result.objectID); + var properties = modelHelper.getProperties(result.objectID), + naturalDimensions = attachment.properties.naturalDimensions = properties.naturalDimensions; + modelHelper.editObject(result.objectID, { + dimensions: naturalDimensions ? Vec3.multiply(attachment.scale, naturalDimensions) : undefined, + }); + this.onJointsUpdated(parentID); // trigger once to initialize position/rotation + // give time for model overlay to "settle", then make it visible + Script.setTimeout(utils.bind(this, function() { + modelHelper.editObject(result.objectID, { + visible: true, + }); + attachment._loaded = true; + }), 100); + }); + return attachment; + })); + } catch (e) { + log('_createAttachmentObjects ERROR:', e.stack || e, e.fileName, e.lineNumber); + } + }, + + _removeAttachmentObjects: function() { + if (this.attachments) { + this.attachments.forEach(function(attachment) { + if (attachment.properties) { + if (attachment.properties.objectID) { + modelHelper.deleteObject(attachment.properties.objectID); + } + delete attachment.properties.objectID; + } + }); + delete this.attachments; + } + }, + + onJointsUpdated: function onJointsUpdated(objectID) { + var jointOrientations = modelHelper.getJointOrientations(objectID), + jointPositions = modelHelper.getJointPositions(objectID), + parentID = objectID, + avatarScale = this.doppleganger.scale, + manualJointSync = this.manualJointSync; + + if (!this.attachments) { + this.attachments = this._getResolvedAttachments(); + this._createAttachmentObjects(); + log('created attachment objects #' + this.attachments.length); + } + var updatedObjects = this.attachments.reduce(function(updates, attachment, i) { + if (!attachment.properties || !attachment._loaded) { + return updates; + } + var objectID = attachment.properties.objectID, + jointIndex = attachment.$jointIndex, + jointOrientation = jointOrientations[jointIndex], + jointPosition = jointPositions[jointIndex]; + + var translation = Vec3.multiply(avatarScale, attachment.translation), + rotation = Quat.fromVec3Degrees(attachment.rotation), + localPosition = Vec3.multiplyQbyV(jointOrientation, translation), + localRotation = rotation; + + updates[objectID] = manualJointSync ? { + visible: true, + position: Vec3.sum(jointPosition, localPosition), + rotation: Quat.multiply(jointOrientation, localRotation), + scale: avatarScale * attachment.scale, + } : { + visible: true, + parentID: parentID, + parentJointIndex: jointIndex, + localRotation: localRotation, + localPosition: localPosition, + scale: attachment.scale, + }; + onJointsUpdated[objectID] = updates[objectID]; + return updates; + }, {}); + modelHelper.editObjects(updatedObjects); + }, + + _initialize: function() { + var doppleganger = this.doppleganger; + if ('$attachmentControls' in doppleganger) { + throw new Error('only one set of debug controls can be added per doppleganger'); + } + doppleganger.$attachmentControls = this; + doppleganger.activeChanged.connect(this, function(active) { + if (active) { + doppleganger.jointsUpdated.connect(this, 'onJointsUpdated'); + } else { + doppleganger.jointsUpdated.disconnect(this, 'onJointsUpdated'); + this._removeAttachmentObjects(); + } + }); + + Script.scriptEnding.connect(this, '_removeAttachmentObjects'); + }, +}; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js new file mode 100644 index 0000000000..f7001f5a88 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -0,0 +1,515 @@ +"use strict"; + +// doppleganger.js +// +// Created by Timothy Dedischew on 04/21/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global module */ +// @module doppleganger +// +// This module contains the `Doppleganger` class implementation for creating an inspectable replica of +// an Avatar (as a model directly in front of and facing them). Joint positions and rotations are copied +// over in an update thread, so that the model automatically mirrors the Avatar's joint movements. +// An Avatar can then for example walk around "themselves" and examine from the back, etc. +// +// This should be helpful for inspecting your own look and debugging avatars, etc. +// +// The doppleganger is created as an overlay so that others do not see it -- and this also allows for the +// highest possible update rate when keeping joint data in sync. + +module.exports = Doppleganger; + +var require = function DEBUG_REQUIRE(id) { + return Script.require(id + '?'+new Date().getTime().toString(36)); +}; + +var _modelHelper = require('./model-helper.js'), + modelHelper = _modelHelper.modelHelper, + ModelReadyWatcher = _modelHelper.ModelReadyWatcher; + +// @property {bool} - when set true, Script.update will be used instead of setInterval for syncing joint data +Doppleganger.USE_SCRIPT_UPDATE = false; + +// @property {int} - the frame rate to target when using setInterval for joint updates +Doppleganger.TARGET_FPS = 24;/// 60; + +// @property {int} - the maximum time in seconds to wait for the model overlay to finish loading +Doppleganger.MAX_WAIT_SECS = 10; + +// @class Doppleganger - Creates a new instance of a Doppleganger. +// @param {Avatar} [options.avatar=MyAvatar] - Avatar used to retrieve position and joint data. +// @param {bool} [options.mirrored=true] - Apply "symmetric mirroring" of Left/Right joints. +// @param {bool} [options.autoUpdate=true] - Automatically sync joint data. +function Doppleganger(options) { + this.options = options = options || {}; + this.avatar = options.avatar || MyAvatar; + this.mirrored = 'mirrored' in options ? options.mirrored : true; + this.autoUpdate = 'autoUpdate' in options ? options.autoUpdate : true; + + // @public + this.active = false; // whether doppleganger is currently being displayed/updated + this.objectID = null; // current doppleganger's Overlay or Entity id + this.frame = 0; // current joint update frame + + // @signal - emitted when .active state changes + this.activeChanged = signal(function(active, reason) {}); + // @signal - emitted once model is either loaded or errors out + this.modelLoaded = signal(function(error, result){}); + // @signal - emitted each time the model's joint data has been synchronized + this.jointsUpdated = signal(function(objectID){}); +} + +Doppleganger.prototype = { + // @public @method - toggles doppleganger on/off + toggle: function() { + if (this.active) { + log('toggling off'); + this.stop(); + } else { + log('toggling on'); + this.start(); + } + return this.active; + }, + + // @public @method - synchronize the joint data between Avatar / doppleganger + update: function() { + this.frame++; + try { + if (!this.objectID) { + throw new Error('!this.objectID'); + } + + if (this.avatar.skeletonModelURL !== this.skeletonModelURL) { + return this.stop('avatar_changed'); + } + + var rotations = this.avatar.getJointRotations(); + var translations = this.avatar.getJointTranslations(); + var size = rotations.length; + + // note: this mismatch can happen when the avatar's model is actively changing + if (size !== translations.length || + (this.jointStateCount && size !== this.jointStateCount)) { + log('mismatched joint counts (avatar model likely changed)', size, translations.length, this.jointStateCount); + this.stop('avatar_changed_joints'); + return; + } + this.jointStateCount = size; + + if (this.mirrored) { + var mirroredIndexes = this.mirroredIndexes; + var outRotations = new Array(size); + var outTranslations = new Array(size); + for (var i=0; i < size; i++) { + var index = mirroredIndexes[i]; + if (index < 0 || index === false) { + index = i; + } + var rot = rotations[index]; + var trans = translations[index]; + trans.x *= -1; + rot.y *= -1; + rot.z *= -1; + outRotations[i] = rot; + outTranslations[i] = trans; + } + rotations = outRotations; + translations = outTranslations; + } + modelHelper.editObject(this.objectID, { + jointRotations: rotations, + jointTranslations: translations + }); + this.jointsUpdated(this.objectID); + } catch (e) { + log('.update error: '+ e, index, e.stack); + this.stop('update_error'); + throw e; + } + }, + + // @public @method - show the doppleganger (and start the update thread, if options.autoUpdate was specified). + // @param {vec3} [options.position=(in front of avatar)] - starting position + // @param {quat} [options.orientation=avatar.orientation] - starting orientation + start: function(options) { + options = assign(this.options, options); + if (this.objectID) { + log('start() called but object model already exists', this.objectID); + return; + } + var avatar = this.avatar; + if (!avatar.jointNames.length) { + return this.stop('joints_unavailable'); + } + + this.frame = 0; + var localPosition = Vec3.multiply(2, Quat.getForward(avatar.orientation)); + this.position = options.position || Vec3.sum(avatar.position, localPosition); + this.orientation = options.orientation || avatar.orientation; + this.skeletonModelURL = avatar.skeletonModelURL; + this.scale = avatar.scale || 1.0; + this.jointStateCount = 0; + this.jointNames = avatar.jointNames; + this.type = options.type || 'overlay'; + this.mirroredNames = modelHelper.deriveMirroredJointNames(this.jointNames); + this.mirroredIndexes = this.mirroredNames.map(function(name) { + return name ? avatar.getJointIndex(name) : false; + }); + + this.objectID = modelHelper.addObject({ + type: this.type === 'overlay' ? 'model' : 'Model', + modelURL: this.skeletonModelURL, + position: this.position, + rotation: this.orientation, + scale: this.scale + }); + Script.scriptEnding.connect(this, function() { + modelHelper.deleteObject(this.objectID); + }); + log('doppleganger created; objectID =', this.objectID); + + // trigger clean up (and stop updates) if the object gets deleted + this.onObjectDeleted = function(uuid) { + if (uuid === this.objectID) { + log('onObjectDeleted', uuid); + this.stop('object_deleted'); + } + }; + modelHelper.objectDeleted.connect(this, 'onObjectDeleted'); + + if ('onLoadComplete' in avatar) { + // stop the current doppleganger if Avatar loads a different model URL + this.onLoadComplete = function() { + if (avatar.skeletonModelURL !== this.skeletonModelURL) { + this.stop('avatar_changed_load'); + } + }; + avatar.onLoadComplete.connect(this, 'onLoadComplete'); + } + + this.onModelLoaded = function(error, result) { + if (error) { + return this.stop(error); + } + log('model ('+modelHelper.type(this.objectID)+')' + ' is ready; # joints == ' + result.jointNames.length); + var naturalDimensions = modelHelper.getProperties(this.objectID, ['naturalDimensions']).naturalDimensions; + log('naturalDimensions:', JSON.stringify(naturalDimensions)); + var props = { visible: true }; + if (naturalDimensions) { + props.dimensions = Vec3.multiply(this.scale, naturalDimensions); + } + log('scaledDimensions:', this.scale, JSON.stringify(props.dimensions)); + modelHelper.editObject(this.objectID, props); + if (!options.position) { + this.syncVerticalPosition(); + } + if (this.autoUpdate) { + this._createUpdateThread(); + } + }; + + this._resource = ModelCache.prefetch(this.skeletonModelURL); + this._modelReadier = new ModelReadyWatcher({ + resource: this._resource, + objectID: this.objectID + }); + this._modelReadier.modelReady.connect(this, 'onModelLoaded'); + this.activeChanged(this.active = true, 'start'); + }, + + // @public @method - hide the doppleganger + // @param {String} [reason=stop] - the reason stop was called + stop: function(reason) { + reason = reason || 'stop'; + if (this.onUpdate) { + Script.update.disconnect(this, 'onUpdate'); + delete this.onUpdate; + } + if (this._interval) { + Script.clearInterval(this._interval); + this._interval = undefined; + } + if (this.onObjectDeleted) { + modelHelper.objectDeleted.disconnect(this, 'onObjectDeleted'); + delete this.onObjectDeleted; + } + if (this.onLoadComplete) { + this.avatar.onLoadComplete.disconnect(this, 'onLoadComplete'); + delete this.onLoadComplete; + } + if (this.onModelLoaded) { + this._modelReadier && this._modelReadier.modelReady.disconnect(this, 'onModelLoaded'); + this._modelReadier = this.onModelLoaded = undefined; + } + if (this.objectID) { + modelHelper.deleteObject(this.objectID); + this.objectID = undefined; + } + if (this.active) { + this.activeChanged(this.active = false, reason); + } else if (reason) { + log('already stopped so not triggering another activeChanged; latest reason was:', reason); + } + }, + // @public @method - Reposition the doppleganger so it sees "eye to eye" with the Avatar. + // @param {String} [byJointName=Hips] - the reference joint used to align the Doppleganger and Avatar + syncVerticalPosition: function(byJointName) { + byJointName = byJointName || 'Hips'; + var positions = modelHelper.getJointPositions(this.objectID), + properties = modelHelper.getProperties(this.objectID), + dopplePosition = properties.position, + doppleJointIndex = modelHelper.getJointIndex(this.objectID, byJointName),// names.indexOf(byJointName), + doppleJointPosition = positions[doppleJointIndex]; + + print('........... doppleJointPosition', JSON.stringify({ + byJointName: byJointName, + dopplePosition: dopplePosition, + doppleJointIndex: doppleJointIndex, + doppleJointPosition: doppleJointPosition, + properties: properties.type, + positions: positions[0] + },0,2)); + var avatarPosition = this.avatar.position, + avatarJointIndex = this.avatar.getJointIndex(byJointName), + avatarJointPosition = this.avatar.getJointPosition(avatarJointIndex); + + var offset = (avatarJointPosition.y - doppleJointPosition.y); + log('adjusting for offset', offset); + if (properties.type === 'model') { + dopplePosition.y = avatarPosition.y + offset; + } else { + dopplePosition.y = avatarPosition.y - offset; + } + + this.position = dopplePosition; + modelHelper.editObject(this.objectID, { position: this.position }); + }, + + // @private @method - creates the update thread to synchronize joint data + _createUpdateThread: function() { + if (Doppleganger.USE_SCRIPT_UPDATE) { + log('creating Script.update thread'); + this.onUpdate = this.update; + Script.update.connect(this, 'onUpdate'); + } else { + log('creating Script.setInterval thread @ ~', Doppleganger.TARGET_FPS +'fps'); + var timeout = 1000 / Doppleganger.TARGET_FPS; + this._interval = Script.setInterval(bind(this, 'update'), timeout); + } + } + +}; + +// @function - bind a function to a `this` context +// @param {Object} - the `this` context +// @param {Function|String} - function or method name +function bind(thiz, method) { + method = thiz[method] || method; + return function() { + return method.apply(thiz, arguments); + }; +} + +// @function - Qt signal polyfill +function signal(template) { + var callbacks = []; + return Object.defineProperties(function() { + var args = [].slice.call(arguments); + callbacks.forEach(function(obj) { + obj.handler.apply(obj.scope, args); + }); + }, { + connect: { value: function(scope, handler) { + var callback = {scope: scope, handler: scope[handler] || handler || scope}; + if (!callback.handler || !callback.handler.apply) { + throw new Error('invalid arguments to connect:' + [template, scope, handler]); + } + callbacks.push({scope: scope, handler: scope[handler] || handler || scope}); + }}, + disconnect: { value: function(scope, handler) { + var match = {scope: scope, handler: scope[handler] || handler || scope}; + callbacks = callbacks.filter(function(obj) { + return !(obj.scope === match.scope && obj.handler === match.handler); + }); + }} + }); +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill +/* eslint-disable */ +function assign(target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; +} +/* eslint-enable */ +// //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill + +// @function - debug logging +function log() { + print('doppleganger | ' + [].slice.call(arguments).join(' ')); +} + +// -- ADVANCED DEBUGGING -- +// @function - Add debug joint indicators / extra debugging info. +// @param {Doppleganger} - existing Doppleganger instance to add controls to +// +// @note: +// * rightclick toggles mirror mode on/off +// * shift-rightclick toggles the debug indicators on/off +// * clicking on an indicator displays the joint name and mirrored joint name in the debug log. +// +// Example use: +// var doppleganger = new Doppleganger(); +// Doppleganger.addDebugControls(doppleganger); +Doppleganger.addDebugControls = function(doppleganger) { + DebugControls.COLOR_DEFAULT = { red: 255, blue: 255, green: 255 }; + DebugControls.COLOR_SELECTED = { red: 0, blue: 255, green: 0 }; + + function DebugControls() { + this.enableIndicators = true; + this.selectedJointName = null; + this.debugOverlayIDs = undefined; + this.jointSelected = signal(function(result) {}); + } + DebugControls.prototype = { + start: function() { + if (!this.onMousePressEvent) { + this.onMousePressEvent = this._onMousePressEvent; + Controller.mousePressEvent.connect(this, 'onMousePressEvent'); + } + }, + + stop: function() { + this.removeIndicators(); + if (this.onMousePressEvent) { + Controller.mousePressEvent.disconnect(this, 'onMousePressEvent'); + delete this.onMousePressEvent; + } + }, + + createIndicators: function(jointNames) { + this.jointNames = jointNames; + return jointNames.map(function(name, i) { + return Overlays.addOverlay('shape', { + shape: 'Icosahedron', + scale: 0.1, + solid: false, + alpha: 0.5 + }); + }); + }, + + removeIndicators: function() { + if (this.debugOverlayIDs) { + this.debugOverlayIDs.forEach(Overlays.deleteOverlay); + this.debugOverlayIDs = undefined; + } + }, + + onJointsUpdated: function(overlayID) { + if (!this.enableIndicators) { + return; + } + var jointNames = Overlays.getProperty(overlayID, 'jointNames'), + jointOrientations = Overlays.getProperty(overlayID, 'jointOrientations'), + jointPositions = Overlays.getProperty(overlayID, 'jointPositions'), + selectedIndex = jointNames.indexOf(this.selectedJointName); + + if (!this.debugOverlayIDs) { + this.debugOverlayIDs = this.createIndicators(jointNames); + } + + // batch all updates into a single call (using the editOverlays({ id: {props...}, ... }) API) + var updatedOverlays = this.debugOverlayIDs.reduce(function(updates, id, i) { + updates[id] = { + position: jointPositions[i], + rotation: jointOrientations[i], + color: i === selectedIndex ? DebugControls.COLOR_SELECTED : DebugControls.COLOR_DEFAULT, + solid: i === selectedIndex + }; + return updates; + }, {}); + Overlays.editOverlays(updatedOverlays); + }, + + _onMousePressEvent: function(evt) { + if (!evt.isLeftButton || !this.enableIndicators || !this.debugOverlayIDs) { + return; + } + var ray = Camera.computePickRay(evt.x, evt.y), + hit = Overlays.findRayIntersection(ray, true, this.debugOverlayIDs); + + hit.jointIndex = this.debugOverlayIDs.indexOf(hit.overlayID); + hit.jointName = this.jointNames[hit.jointIndex]; + this.jointSelected(hit); + } + }; + + if ('$debugControls' in doppleganger) { + throw new Error('only one set of debug controls can be added per doppleganger'); + } + var debugControls = new DebugControls(); + doppleganger.$debugControls = debugControls; + + function onMousePressEvent(evt) { + if (evt.isRightButton) { + if (evt.isShifted) { + debugControls.enableIndicators = !debugControls.enableIndicators; + if (!debugControls.enableIndicators) { + debugControls.removeIndicators(); + } + } else { + doppleganger.mirrored = !doppleganger.mirrored; + } + } + } + + doppleganger.activeChanged.connect(function(active) { + if (active) { + debugControls.start(); + doppleganger.jointsUpdated.connect(debugControls, 'onJointsUpdated'); + Controller.mousePressEvent.connect(onMousePressEvent); + } else { + Controller.mousePressEvent.disconnect(onMousePressEvent); + doppleganger.jointsUpdated.disconnect(debugControls, 'onJointsUpdated'); + debugControls.stop(); + } + }); + + debugControls.jointSelected.connect(function(hit) { + debugControls.selectedJointName = hit.jointName; + if (hit.jointIndex < 0) { + return; + } + hit.mirroredJointName = Doppleganger.getMirroredJointNames([hit.jointName])[0]; + log('selected joint:', JSON.stringify(hit, 0, 2)); + }); + + Script.scriptEnding.connect(debugControls, 'removeIndicators'); + + return doppleganger; +}; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js new file mode 100644 index 0000000000..8987695473 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js @@ -0,0 +1,333 @@ +// model-helper.js +// +// Created by Timothy Dedischew on 06/01/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global module */ +// @module model-helper +// +// This module provides ModelReadyWatcher (a helper class for knowing when a model becomes usable inworld) and +// also initial plumbing helpers to eliminate unnecessary API differences when working with Model Overlays and +// Model Entities at a high-level from scripting. + +function log() { + print('model-helper | ' + [].slice.call(arguments).join(' ')); +} + +var require = function DEBUG_REQUIRE(id) { + return Script.require(id + '?'+new Date().getTime().toString(36)); +}; + +var utils = require('./utils.js'), + assert = utils.assert; + +module.exports = { + version: '0.0.0', + ModelReadyWatcher: ModelReadyWatcher +}; + +var _objectDeleted = utils.signal(function objectDeleted(objectID){}); +// proxy for _objectDeleted that only binds deletion tracking if script actually connects to the unified signal +var objectDeleted = utils.assign(function objectDeleted(objectID){}, { + connect: function() { + Overlays.overlayDeleted.connect(_objectDeleted); + // Entities.deletingEntity.connect(objectDeleted); + Script.scriptEnding.connect(function() { + Overlays.overlayDeleted.disconnect(_objectDeleted); + // Entities.deletingEntity.disconnect(objectDeleted); + }); + // hereafter _objectDeleted.connect will be used instead + this.connect = utils.bind(_objectDeleted, 'connect'); + return this.connect.apply(this, arguments); + }, + disconnect: utils.bind(_objectDeleted, 'disconnect') +}); + +var modelHelper = module.exports.modelHelper = { + // Entity <-> Overlay property translations + _entityFromOverlay: { + modelURL: function url() { + return this.url; + }, + dimensions: function dimensions() { + return Vec3.multiply(this.scale, this.naturalDimensions); + } + }, + _overlayFromEntity: { + url: function modelURL() { + return this.modelURL; + }, + scale: function scale() { + return this.dimensions && this.naturalDimensions && { + x: this.dimensions.x / this.naturalDimensions.x, + y: this.dimensions.y / this.naturalDimensions.y, + z: this.dimensions.z / this.naturalDimensions.z + }; + } + }, + objectDeleted: objectDeleted, + type: function(objectID) { + // TODO: support Model Entities (by detecting type from objectID, which is already possible) + return !Uuid.isNull(objectID) ? 'overlay' : null; + }, + addObject: function(properties) { + var type = 'overlay'; // this.resolveType(properties) + switch (type) { + case 'overlay': return Overlays.addOverlay(properties.type, this.toOverlayProps(properties)); + } + return false; + }, + editObject: function(objectID, properties) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.editOverlay(objectID, this.toOverlayProps(properties)); + } + return false; + }, + deleteObject: function(objectID) { + return this.type(objectID) === 'overlay' && Overlays.deleteOverlay(objectID); + }, + getProperty: function(objectID, propertyName) { + return this.type(objectID) === 'overlay' && Overlays.getProperty(objectID, propertyName); + }, + getProperties: function(objectID, filter) { + switch (this.type(objectID)) { + case 'overlay': + filter = Array.isArray(filter) ? filter : [ + 'position', 'rotation', 'localPosition', 'localRotation', 'parentID', + 'parentJointIndex', 'scale', 'name', 'visible', 'type', 'url', + 'dimensions', 'naturalDimensions', 'grabbable' + ]; + var properties = filter.reduce(function(out, propertyName) { + out[propertyName] = Overlays.getProperty(objectID, propertyName); + return out; + }, {}); + return this.toEntityProps(properties); + } + return null; + }, + // adapt Entity conventions to Overlay (eg: { modelURL: ... } -> { url: ... }) + toOverlayProps: function(properties) { + var result = {}; + for (var from in this._overlayFromEntity) { + var adapter = this._overlayFromEntity[from]; + result[from] = adapter.call(properties, from, adapter.name); + } + return utils.assign(result, properties); + }, + // adapt Overlay conventions to Entity (eg: { url: ... } -> { modelURL: ... }) + toEntityProps: function(properties) { + var result = {}; + for (var from in this._entityToOverlay) { + var adapter = this._entityToOverlay[from]; + result[from] = adapter.call(properties, from, adapter.name); + } + return utils.assign(result, properties); + }, + editObjects: function(updatedObjects) { + var objectIDs = Object.keys(updatedObjects), + type = this.type(objectIDs[0]); + switch (type) { + case 'overlay': + var translated = {}; + for (var objectID in updatedObjects) { + translated[objectID] = this.toOverlayProps(updatedObjects[objectID]); + } + return Overlays.editOverlays(translated); + } + return false; + }, + getJointIndex: function(objectID, name) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointNames').indexOf(name); + } + return -1; + }, + getJointNames: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointNames'); + } + return []; + }, + // @function - derives mirrored joint names from a list of regular joint names + // @param {Array} - list of joint names to mirror + // @return {Array} - list of mirrored joint names (note: entries for non-mirrored joints will be `undefined`) + deriveMirroredJointNames: function(jointNames) { + return jointNames.map(function(name, i) { + if (/Left/.test(name)) { + return name.replace('Left', 'Right'); + } + if (/Right/.test(name)) { + return name.replace('Right', 'Left'); + } + return undefined; + }); + }, + getJointPosition: function(objectID, index) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointPositions')[index]; + } + return Vec3.ZERO; + }, + getJointPositions: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointPositions'); + } + return []; + }, + getJointOrientation: function(objectID, index) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointOrientations')[index]; + } + return Quat.normalize({}); + }, + getJointOrientations: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointOrientations'); + } + }, + getJointTranslation: function(objectID, index) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointTranslations')[index]; + } + return Vec3.ZERO; + }, + getJointTranslations: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointTranslations'); + } + return []; + }, + getJointRotation: function(objectID, index) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointRotations')[index]; + } + return Quat.normalize({}); + }, + getJointRotations: function(objectID) { + switch (this.type(objectID)) { + case 'overlay': return Overlays.getProperty(objectID, 'jointRotations'); + } + return []; + } +}; // modelHelper + + +// @property {PreconditionFunction} - indicates when the model's jointNames have become available +ModelReadyWatcher.JOINTS = function(state) { + return Array.isArray(state.jointNames); +}; +// @property {PreconditionFunction} - indicates when a model's naturalDimensions have become available +ModelReadyWatcher.DIMENSIONS = function(state) { + return Vec3.length(state.naturalDimensions) > 0; +}; +// @property {PreconditionFunction} - indicates when both a model's naturalDimensions and jointNames have become available +ModelReadyWatcher.JOINTS_AND_DIMENSIONS = function(state) { + // eslint-disable-next-line new-cap + return ModelReadyWatcher.JOINTS(state) && ModelReadyWatcher.DIMENSIONS(state); +}; +// @property {int} - interval used for continually rechecking model readiness, until ready or a timeout occurs +ModelReadyWatcher.RECHECK_MS = 50; +// @property {int} - default wait time before considering a model unready-able. +ModelReadyWatcher.DEFAULT_TIMEOUT_SECS = 10; + +// @private @class - waits for model to become usable inworld and tracks errors/timeouts +// @param [Object} options -- key/value config options: +// @param {ModelResource} options.resource - a ModelCache prefetched resource to observe for determining load state +// @param {Uuid} options.objectID - an inworld object to observe for determining readiness states +// @param {Function} [options.precondition=ModelReadyWatcher.JOINTS] - the precondition used to determine if the model is usable +// @param {int} [options.maxWaitSeconds=10] - max seconds to wait for the model to become usable, after which a timeout error is emitted +// @return {ModelReadyWatcher} +function ModelReadyWatcher(options) { + options = utils.assign({ + precondition: ModelReadyWatcher.JOINTS, + maxWaitSeconds: ModelReadyWatcher.DEFAULT_TIMEOUT_SECS + }, options); + + assert(!Uuid.isNull(options.objectID), 'Error: invalid options.objectID'); + assert(options.resource && 'state' in options.resource, 'Error: invalid options.resource'); + assert(typeof options.precondition === 'function', 'Error: invalid options.precondition'); + + utils.assign(this, { + resource: options.resource, + objectID: options.objectID, + precondition: options.precondition, + + // @signal - emitted when the model becomes ready, or with the error that prevented it + modelReady: utils.signal(function modelReady(error, result){}), + + // @public + ready: false, // tracks readiness state + jointNames: null, // populated with detected jointNames + naturalDimensions: null, // populated with detected naturalDimensions + + _startTime: new Date, + _watchdogTimer: Script.setTimeout(utils.bind(this, function() { + this._watchdogTimer = null; + }), options.maxWaitSeconds * 1000), + _interval: Script.setInterval(utils.bind(this, '_waitUntilReady'), ModelReadyWatcher.RECHECK_MS) + }); + + this.modelReady.connect(this, function(error, result) { + this.ready = !error && result; + }); +} + +ModelReadyWatcher.prototype = { + contructor: ModelReadyWatcher, + // @public method -- cancels monitoring and (if model was not yet ready) emits an error + cancel: function() { + return this._stop() && !this.ready && this.modelReady('cancelled', null); + }, + // stop pending timers + _stop: function() { + var stopped = 0; + if (this._watchdogTimer) { + Script.clearTimeout(this._watchdogTimer); + this._watchdogTimer = null; + stopped++; + } + if (this._interval) { + Script.clearInterval(this._interval); + this._interval = null; + stopped++; + } + return stopped; + }, + // the monitoring thread func + _waitUntilReady: function() { + var error = null, result = null; + if (!this._watchdogTimer) { + error = this.precondition.name || 'timeout'; + } else if (this.resource.state === Resource.State.FAILED) { + error = 'prefetch_failed'; + } else if (this.resource.state === Resource.State.FINISHED) { + // in theory there will always be at least one "joint name" that represents the main submesh + var names = modelHelper.getJointNames(this.objectID); + if (Array.isArray(names) && names.length) { + this.jointNames = names; + } + var props = modelHelper.getProperties(this.objectID, ['naturalDimensions']); + if (props && props.naturalDimensions && Vec3.length(props.naturalDimensions)) { + this.naturalDimensions = props.naturalDimensions; + } + var state = { + resource: this.resource, + objectID: this.objectID, + waitTime: (new Date - this._startTime) / 1000, + jointNames: this.jointNames, + naturalDimensions: this.naturalDimensions + }; + if (this.precondition(state)) { + result = state; + } + } + if (error || result !== null) { + this._stop(); + this.modelReady(error, result); + } + } +}; // ModelReadyWatcher.prototype diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/utils.js b/unpublishedScripts/marketplace/doppleganger-attachments/utils.js new file mode 100644 index 0000000000..53140bf342 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/utils.js @@ -0,0 +1,100 @@ +/* eslint-env commonjs */ +/* eslint-disable comma-dangle, no-empty */ + +module.exports = { + bind: bind, + signal: signal, + assign: assign, + assert: assert, +}; + +// @function - bind a function to a `this` context +// @param {Object} - the `this` context +// @param {Function|String} - function or method name +// @param {value} varargs... - optional curry-right arguments (passed to method after any explicit arguments) +function bind(thiz, method, varargs) { + method = thiz[method] || method; + varargs = [].slice.call(arguments, 2); + return function() { + var args = [].slice.call(arguments).concat(varargs); + return method.apply(thiz, args); + }; +} + +// @function - Qt signal polyfill +function signal(template) { + var callbacks = []; + return Object.defineProperties(function() { + var args = [].slice.call(arguments); + callbacks.forEach(function(obj) { + obj.handler.apply(obj.scope, args); + }); + }, { + connect: { value: function(scope, handler) { + var callback = {scope: scope, handler: scope[handler] || handler || scope}; + if (!callback.handler || !callback.handler.apply) { + throw new Error('invalid arguments to connect:' + [template, scope, handler]); + } + callbacks.push({scope: scope, handler: scope[handler] || handler || scope}); + }}, + disconnect: { value: function(scope, handler) { + var match = {scope: scope, handler: scope[handler] || handler || scope}; + callbacks = callbacks.filter(function(obj) { + return !(obj.scope === match.scope && obj.handler === match.handler); + }); + }} + }); +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill +/* eslint-disable */ +function assign(target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; +} +/* eslint-enable */ +// //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill + +// examples: +// assert(function assertion() { return (conditions === true) }, 'assertion failed!') +// var neededValue = assert(idString, 'idString not specified!'); +// assert(false, 'unexpected state'); +function assert(truthy, message) { + message = message || 'Assertion Failed:'; + + if (typeof truthy === 'function' && truthy.name === 'assertion') { + // extract function body to display with the assertion message + var assertion = (truthy+'').replace(/[\r\n]/g, ' ') + .replace(/^[^{]+\{|\}$|^\s*|\s*$/g, '').trim() + .replace(/^return /,'').replace(/\s[\r\n\t\s]+/g, ' '); + message += ' ' + JSON.stringify(assertion); + try { + truthy = truthy(); + } catch (e) { + message += '(exception: ' + e +')'; + } + } + if (!truthy) { + message += ' ('+truthy+')'; + throw new Error(message); + } + return truthy; +} From e25b567ff136f1d22e788a49addf9647bf2eb9bf Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 16:09:20 -0400 Subject: [PATCH 03/16] comment-out debug requires, bump back to 60fps --- .../app-doppleganger-attachments.js | 6 ++++-- .../doppleganger-attachments/doppleganger-attachments.js | 4 ++-- .../marketplace/doppleganger-attachments/doppleganger.js | 6 +++--- .../marketplace/doppleganger-attachments/model-helper.js | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index c487232aa5..b7702d9348 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -10,9 +10,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var require = function DEBUG_REQUIRE(id) { +/*var require = function DEBUG_REQUIRE(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); -}; +};*/ + +var require = Script.require; var DopplegangerClass = require('./doppleganger.js'), DopplegangerAttachments = require('./doppleganger-attachments.js'), diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js index 85c3d3589e..7c6f846fa1 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -6,9 +6,9 @@ module.exports = DopplegangerAttachments; DopplegangerAttachments.version = '0.0.0'; -var require = function DEBUG_REQUIRE(id) { +/*var require = function DEBUG_REQUIRE(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); -}; +};*/ var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js index f7001f5a88..58280a09f2 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -24,9 +24,9 @@ module.exports = Doppleganger; -var require = function DEBUG_REQUIRE(id) { +/*var require = function DEBUG_REQUIRE(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); -}; +};*/ var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, @@ -36,7 +36,7 @@ var _modelHelper = require('./model-helper.js'), Doppleganger.USE_SCRIPT_UPDATE = false; // @property {int} - the frame rate to target when using setInterval for joint updates -Doppleganger.TARGET_FPS = 24;/// 60; +Doppleganger.TARGET_FPS = 60; // @property {int} - the maximum time in seconds to wait for the model overlay to finish loading Doppleganger.MAX_WAIT_SECS = 10; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js index 8987695473..e3dcc0de1f 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js @@ -18,9 +18,9 @@ function log() { print('model-helper | ' + [].slice.call(arguments).join(' ')); } -var require = function DEBUG_REQUIRE(id) { +/*var require = function DEBUG_REQUIRE(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); -}; +};*/ var utils = require('./utils.js'), assert = utils.assert; From 46e9ada424163d2bb0d86f14820fad3de510b61f Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 16:58:06 -0400 Subject: [PATCH 04/16] cleanup pass --- .../app-doppleganger-attachments.js | 4 ---- .../doppleganger-attachments.js | 10 +++------- .../doppleganger-attachments/doppleganger.js | 6 +----- .../doppleganger-attachments/model-helper.js | 10 +--------- .../marketplace/doppleganger-attachments/utils.js | 3 +-- 5 files changed, 6 insertions(+), 27 deletions(-) diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index b7702d9348..e0ba47162e 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -10,10 +10,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/*var require = function DEBUG_REQUIRE(id) { - return Script.require(id + '?'+new Date().getTime().toString(36)); -};*/ - var require = Script.require; var DopplegangerClass = require('./doppleganger.js'), diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js index 7c6f846fa1..894d627fc5 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -1,15 +1,11 @@ -/* eslint-env commonjs */ -/* eslint-disable comma-dangle, no-empty */ - "use strict"; +/* eslint-env commonjs */ +/* eslint-disable comma-dangle */ + module.exports = DopplegangerAttachments; DopplegangerAttachments.version = '0.0.0'; -/*var require = function DEBUG_REQUIRE(id) { - return Script.require(id + '?'+new Date().getTime().toString(36)); -};*/ - var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, ModelReadyWatcher = _modelHelper.ModelReadyWatcher, diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js index 58280a09f2..c743357e0c 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global module */ +/* eslint-env commonjs */ // @module doppleganger // // This module contains the `Doppleganger` class implementation for creating an inspectable replica of @@ -24,10 +24,6 @@ module.exports = Doppleganger; -/*var require = function DEBUG_REQUIRE(id) { - return Script.require(id + '?'+new Date().getTime().toString(36)); -};*/ - var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, ModelReadyWatcher = _modelHelper.ModelReadyWatcher; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js index e3dcc0de1f..2dda2c12ec 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/model-helper.js @@ -7,21 +7,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global module */ +/* eslint-env commonjs */ // @module model-helper // // This module provides ModelReadyWatcher (a helper class for knowing when a model becomes usable inworld) and // also initial plumbing helpers to eliminate unnecessary API differences when working with Model Overlays and // Model Entities at a high-level from scripting. -function log() { - print('model-helper | ' + [].slice.call(arguments).join(' ')); -} - -/*var require = function DEBUG_REQUIRE(id) { - return Script.require(id + '?'+new Date().getTime().toString(36)); -};*/ - var utils = require('./utils.js'), assert = utils.assert; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/utils.js b/unpublishedScripts/marketplace/doppleganger-attachments/utils.js index 53140bf342..76c6e1ef7f 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/utils.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/utils.js @@ -1,11 +1,10 @@ /* eslint-env commonjs */ -/* eslint-disable comma-dangle, no-empty */ module.exports = { bind: bind, signal: signal, assign: assign, - assert: assert, + assert: assert }; // @function - bind a function to a `this` context From e6e1ae16db76ef9485aa0be94db24081e39271b9 Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 19 Jun 2017 17:21:52 -0400 Subject: [PATCH 05/16] swap-in different icon for now --- .../doppleganger-attachments/Hat4.svg | 328 --------- .../Pullover-lineart-inverted.svg | 647 ++++++++++++++++++ .../Pullover-lineart-normal.svg | 646 +++++++++++++++++ .../app-doppleganger-attachments.js | 4 +- 4 files changed, 1295 insertions(+), 330 deletions(-) delete mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg deleted file mode 100644 index 8d628cf1cd..0000000000 --- a/unpublishedScripts/marketplace/doppleganger-attachments/Hat4.svg +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg new file mode 100644 index 0000000000..52aed00285 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg @@ -0,0 +1,647 @@ + + + + + Pullover + + + + + + image/svg+xml + + Pullover + + 2014-02-03 + + + Frank Tremmel + + + + + pulli + pullover + sweater + shirt + clothes + clothings + kleidung + + + + outline pullover + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg new file mode 100644 index 0000000000..9cab6592aa --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg @@ -0,0 +1,646 @@ + + + + + Pullover + + + + + + image/svg+xml + + Pullover + + 2014-02-03 + + + Frank Tremmel + + + + + pulli + pullover + sweater + shirt + clothes + clothings + kleidung + + + + outline pullover + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index e0ba47162e..71be8d72af 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -18,8 +18,8 @@ var DopplegangerClass = require('./doppleganger.js'), var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'), button = tablet.addButton({ - icon: Script.resolvePath("./Hat4.svg"), - // activeIcon: Script.resolvePath("./app-a.svg"), + icon: Script.resolvePath('./Pullover-lineart-normal.svg'), + activeIcon: Script.resolvePath('./Pullover-lineart-inverted.svg'), text: 'Mannequin' }); From 8ce1474d9addf058ea2fbbd65abb52414aa5a1d6 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 21 Jun 2017 09:48:34 -0700 Subject: [PATCH 06/16] Add isReplicated to avatar identity data --- assignment-client/src/avatars/AvatarMixer.cpp | 4 ++-- assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 9 ++++++++- libraries/avatars/src/AvatarData.h | 9 ++++++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 136d5f2e8e..10edd21258 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -236,7 +236,7 @@ void AvatarMixer::start() { auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { - if (node->getType() == NodeType::Agent && !node->isUpstream()) { + if (node->getType() == NodeType::Agent) { manageIdentityData(node); } @@ -332,7 +332,7 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { sendIdentity = true; } } - if (sendIdentity) { + if (sendIdentity && !node->isUpstream()) { sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar. } } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 4d5e507923..1392344376 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -81,7 +81,7 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, int AvatarMixerSlave::sendReplicatedIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { if (destinationNode->getType() == NodeType::DownstreamAvatarMixer) { - QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true); + QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true, true); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious auto identityPacket = NLPacket::create(PacketType::ReplicatedAvatarIdentity); identityPacket->write(individualData); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6ec2b45c89..36fc991958 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1509,6 +1509,7 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide >> identity.attachmentData >> identity.displayName >> identity.sessionDisplayName + >> identity.isReplicated >> identity.avatarEntityData; // set the store identity sequence number to match the incoming identity @@ -1531,6 +1532,11 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide } maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName); + if (identity.isReplicated != _isReplicated) { + _isReplicated = identity.isReplicated; + identityChanged = true; + } + if (identity.attachmentData != _attachmentData) { setAttachmentData(identity.attachmentData); identityChanged = true; @@ -1563,7 +1569,7 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide } } -QByteArray AvatarData::identityByteArray(bool shouldForwardIncomingSequenceNumber) const { +QByteArray AvatarData::identityByteArray(bool shouldForwardIncomingSequenceNumber, bool setIsReplicated) const { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL @@ -1584,6 +1590,7 @@ QByteArray AvatarData::identityByteArray(bool shouldForwardIncomingSequenceNumbe << _attachmentData << _displayName << getSessionDisplayNameForTransport() // depends on _sessionDisplayName + << (_isReplicated || setIsReplicated) << _avatarEntityData; }); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 44d910b571..27b9cd7194 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -531,6 +531,7 @@ public: QVector attachmentData; QString displayName; QString sessionDisplayName; + bool isReplicated; AvatarEntityMap avatarEntityData; }; @@ -539,7 +540,7 @@ public: void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged, bool& skeletonModelUrlChanged); - QByteArray identityByteArray(bool shouldForwardIncomingSequenceNumber = false) const; + QByteArray identityByteArray(bool shouldForwardIncomingSequenceNumber = false, bool setIsReplicated = false) const; const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } @@ -627,6 +628,8 @@ public: float getDensity() const { return _density; } + bool getIsReplicated() const { return _isReplicated; } + signals: void displayNameChanged(); @@ -663,6 +666,10 @@ protected: bool hasParent() const { return !getParentID().isNull(); } bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; } + // isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master" + // Audio Mixer that the replicated avatar is connected to. + bool _isReplicated{ false }; + glm::vec3 _handPosition; virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; } virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) { } // No-op in AvatarMixer From 0e7ddfd29f478ad8f39c14cf2dc59ad5584643a4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 21 Jun 2017 09:49:16 -0700 Subject: [PATCH 07/16] Disable silence/ban buttons in PAL if avatar is replicated --- interface/resources/qml/hifi/NameCard.qml | 1 + interface/resources/qml/hifi/Pal.qml | 2 ++ libraries/avatars/src/ScriptAvatarData.cpp | 9 +++++++++ libraries/avatars/src/ScriptAvatarData.h | 2 ++ scripts/system/pal.js | 3 ++- 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index a86defdfd7..6e881a7fbf 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -43,6 +43,7 @@ Item { property bool selected: false property bool isAdmin: false property bool isPresent: true + property bool isReplicated: false property string placeName: "" property string profilePicBorderColor: (connectionStatus == "connection" ? hifi.colors.indigoAccent : (connectionStatus == "friend" ? hifi.colors.greenHighlight : "transparent")) property alias avImage: avatarImage diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index bbb42e61ac..8db04a0f5b 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -473,6 +473,7 @@ Rectangle { visible: !isCheckBox && !isButton && !isAvgAudio; uuid: model ? model.sessionId : ""; selected: styleData.selected; + isReplicated: model.isReplicated; isAdmin: model && model.admin; isPresent: model && model.isPresent; // Size @@ -553,6 +554,7 @@ Rectangle { id: actionButton; color: 2; // Red visible: isButton; + enabled: !nameCard.isReplicated; anchors.centerIn: parent; width: 32; height: 32; diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 01d7f293d8..90ec7ec309 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -152,6 +152,15 @@ QString ScriptAvatarData::getSessionDisplayName() const { return QString(); } } + +bool ScriptAvatarData::getIsReplicated() const { + if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { + return sharedAvatarData->getIsReplicated(); + } else { + return false; + } +} + // // IDENTIFIER PROPERTIES // END diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index d763b6e97a..1b6944e01d 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -45,6 +45,7 @@ class ScriptAvatarData : public QObject { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged) Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName) + Q_PROPERTY(bool isReplicated READ getIsReplicated) // // ATTACHMENT AND JOINT PROPERTIES @@ -95,6 +96,7 @@ public: QUuid getSessionUUID() const; QString getDisplayName() const; QString getSessionDisplayName() const; + bool getIsReplicated() const; // // ATTACHMENT AND JOINT PROPERTIES diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 0500c13f9b..6c1652c700 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -479,7 +479,8 @@ function populateNearbyUserList(selectData, oldAudioData) { admin: false, personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null ignore: !!id && Users.getIgnoreStatus(id), // ditto - isPresent: true + isPresent: true, + isReplicated: avatar.isReplicated }; if (id) { addAvatarNode(id); // No overlay for ourselves From 30714d5d254cdb924ad35302a81355dd296a2e58 Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Thu, 22 Jun 2017 16:47:40 +0200 Subject: [PATCH 08/16] Make background with default color. Fix few warnings --- interface/resources/qml/controls-uit/ComboBox.qml | 2 +- .../qml/hifi/tablet/tabletWindows/TabletFileDialog.qml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml index 3ce297ef80..d672fa6387 100644 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -217,7 +217,7 @@ FocusScope { anchors.leftMargin: hifi.dimensions.textPadding anchors.verticalCenter: parent.verticalCenter id: popupText - text: listView.model[index] ? listView.model[index] : (listView.model.get(index).text ? listView.model.get(index).text : "") + text: listView.model[index] ? listView.model[index] : (listView.model.get && listView.model.get(index).text ? listView.model.get(index).text : "") size: hifi.fontSizes.textFieldInput color: hifi.colors.baseGray } diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index 26e35c4dcf..7b91cfeba9 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -23,11 +23,13 @@ import "../../../windows" import "../../../dialogs/fileDialog" //FIXME implement shortcuts for favorite location -Item { +Rectangle { id: root - anchors.top: parent.top + anchors.top: parent ? parent.top : undefined HifiConstants { id: hifi } + color: hifi.colors.baseGray; + Settings { category: "FileDialog" property alias width: root.width From 0ef623ba069818da60463a47188488c9fe58392c Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 22 Jun 2017 12:45:53 -0400 Subject: [PATCH 09/16] * revert button icons and name * add new attachmentsUpdated signal and connect to 'doppleganger_attachments' logAction * fix typo in debug mode --- .../Pullover-lineart-inverted.svg | 647 ------------------ .../Pullover-lineart-normal.svg | 646 ----------------- .../app-doppleganger-attachments.js | 14 +- .../doppleganger-a.svg | 94 +++ .../doppleganger-attachments.js | 6 +- .../doppleganger-i.svg | 94 +++ .../doppleganger-attachments/doppleganger.js | 2 +- 7 files changed, 203 insertions(+), 1300 deletions(-) delete mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg delete mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-a.svg create mode 100644 unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-i.svg diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg deleted file mode 100644 index 52aed00285..0000000000 --- a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-inverted.svg +++ /dev/null @@ -1,647 +0,0 @@ - - - - - Pullover - - - - - - image/svg+xml - - Pullover - - 2014-02-03 - - - Frank Tremmel - - - - - pulli - pullover - sweater - shirt - clothes - clothings - kleidung - - - - outline pullover - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg b/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg deleted file mode 100644 index 9cab6592aa..0000000000 --- a/unpublishedScripts/marketplace/doppleganger-attachments/Pullover-lineart-normal.svg +++ /dev/null @@ -1,646 +0,0 @@ - - - - - Pullover - - - - - - image/svg+xml - - Pullover - - 2014-02-03 - - - Frank Tremmel - - - - - pulli - pullover - sweater - shirt - clothes - clothings - kleidung - - - - outline pullover - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index 71be8d72af..4142ff332e 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -18,9 +18,9 @@ var DopplegangerClass = require('./doppleganger.js'), var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'), button = tablet.addButton({ - icon: Script.resolvePath('./Pullover-lineart-normal.svg'), - activeIcon: Script.resolvePath('./Pullover-lineart-inverted.svg'), - text: 'Mannequin' + icon: Script.resolvePath('./doppleganger-i.svg'), + activeIcon: Script.resolvePath('./doppleganger-a.svg'), + text: 'MIRROR' }); Script.scriptEnding.connect(function() { @@ -97,11 +97,15 @@ doppleganger.modelLoaded.connect(function(error, result) { } }); +// ---------------------------------------------------------------------------- + // add debug indicators, but only if the user has configured the settings value if (Settings.getValue('debug.doppleganger', false)) { DopplegangerClass.addDebugControls(doppleganger); } +// ---------------------------------------------------------------------------- + UserActivityLogger.logAction('doppleganger_app_load'); doppleganger.activeChanged.connect(function(active, reason) { if (active) { @@ -116,3 +120,7 @@ doppleganger.activeChanged.connect(function(active, reason) { } } }); +dopplegangerAttachments.attachmentsUpdated.connect(function(attachments) { + UserActivityLogger.logAction('doppleganger_attachments', { count: attachments.length }); +}); + diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-a.svg b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-a.svg new file mode 100644 index 0000000000..100986647e --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-a.svg @@ -0,0 +1,94 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js index 894d627fc5..1d3847cad2 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -21,6 +21,7 @@ function DopplegangerAttachments(doppleganger, options) { doppleganger: doppleganger, attachments: undefined, manualJointSync: true, + attachmentsUpdated: utils.signal(function attachmentsUpdated(currentAttachments, previousAttachments){}), }); this._initialize(); log('DopplegangerAttachments...', JSON.stringify(options)); @@ -77,6 +78,7 @@ DopplegangerAttachments.prototype = { }); this.attachments = after; this._createAttachmentObjects(); + this.attachmentsUpdated(after, before); }, _createAttachmentObjects: function() { try { @@ -176,9 +178,7 @@ DopplegangerAttachments.prototype = { manualJointSync = this.manualJointSync; if (!this.attachments) { - this.attachments = this._getResolvedAttachments(); - this._createAttachmentObjects(); - log('created attachment objects #' + this.attachments.length); + this.refreshAttachments(); } var updatedObjects = this.attachments.reduce(function(updates, attachment, i) { if (!attachment.properties || !attachment._loaded) { diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-i.svg b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-i.svg new file mode 100644 index 0000000000..0c55e0e0c7 --- /dev/null +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-i.svg @@ -0,0 +1,94 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js index c743357e0c..375105e722 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -501,7 +501,7 @@ Doppleganger.addDebugControls = function(doppleganger) { if (hit.jointIndex < 0) { return; } - hit.mirroredJointName = Doppleganger.getMirroredJointNames([hit.jointName])[0]; + hit.mirroredJointName = modelHelper.deriveMirroredJointNames([hit.jointName])[0]; log('selected joint:', JSON.stringify(hit, 0, 2)); }); From b48efc431789fe3ec55dfe8544c60930fc07f800 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 22 Jun 2017 22:48:32 +0100 Subject: [PATCH 10/16] fixed wide stance issue --- plugins/openvr/src/ViveControllerManager.cpp | 34 +++++++++++++++----- plugins/openvr/src/ViveControllerManager.h | 1 + 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index d914cdcfad..648373ccc2 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -959,15 +959,33 @@ void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToRefer controller::Pose& secondFootPose = secondFoot.second; if (determineLimbOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) { - _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose); + calibrateFoot(defaultToReferenceMat, inputCalibration, firstFoot, true); + calibrateFoot(defaultToReferenceMat, inputCalibration, secondFoot, false); } else { - _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose); + calibrateFoot(defaultToReferenceMat, inputCalibration, secondFoot, true); + calibrateFoot(defaultToReferenceMat, inputCalibration, firstFoot, false); + } +} + +void ViveControllerManager::InputDevice::calibrateFoot(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& footPair, bool isLeftFoot){ + controller::Pose footPose = footPair.second; + glm::mat4 puckPoseAvatarMat = createMatFromQuatAndPos(footPose.getRotation(), footPose.getTranslation()); + glm::mat4 defaultFoot = isLeftFoot ? inputCalibration.defaultLeftFoot : inputCalibration.defaultRightFoot; + glm::mat4 footOffset = computeOffset(defaultToReferenceMat, defaultFoot, footPose); + + glm::quat rotationOffset = glmExtractRotation(footOffset); + glm::vec3 translationOffset = extractTranslation(footOffset); + glm::vec3 avatarXAxisInPuckFrame = glm::normalize(transformVectorFast(glm::inverse(puckPoseAvatarMat), glm::vec3(-1.0f, 0.0f, 0.0f))); + float distance = glm::dot(translationOffset, avatarXAxisInPuckFrame); + glm::vec3 finalTranslation = translationOffset - (distance * avatarXAxisInPuckFrame); + glm::mat4 finalOffset = createMatFromQuatAndPos(rotationOffset, finalTranslation); + + if (isLeftFoot) { + _jointToPuckMap[controller::LEFT_FOOT] = footPair.first; + _pucksOffset[footPair.first] = finalOffset; + } else { + _jointToPuckMap[controller::RIGHT_FOOT] = footPair.first; + _pucksOffset[footPair.first] = finalOffset; } } diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 67a9ff46fd..0b0406bb60 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -98,6 +98,7 @@ private: void calibrateLeftHand(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair); void calibrateRightHand(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair); void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); + void calibrateFoot(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& footPair, bool isLeftFoot); void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, From d6eb77e815147b818fe3129fb6ea5cb2f827d98f Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 23 Jun 2017 13:09:22 -0400 Subject: [PATCH 11/16] changes per feedback to quiet print debugs throughout --- .../app-doppleganger-attachments.js | 14 ++++++-- .../doppleganger-attachments.js | 13 ++++--- .../doppleganger-attachments/doppleganger.js | 34 +++++++++++-------- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js index 4142ff332e..4617cf47b6 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/app-doppleganger-attachments.js @@ -12,6 +12,8 @@ var require = Script.require; +var WANT_DEBUG = false; + var DopplegangerClass = require('./doppleganger.js'), DopplegangerAttachments = require('./doppleganger-attachments.js'), modelHelper = require('./model-helper.js').modelHelper; @@ -57,7 +59,7 @@ var doppleganger = new DopplegangerClass({ var currentHash = dopplegangerAttachments.getAttachmentsHash(); if (currentHash !== lastHash) { lastHash = currentHash; - print('app-doppleganger | detect attachment change'); + debugPrint('app-doppleganger | detect attachment change'); dopplegangerAttachments.refreshAttachments(); } } @@ -86,7 +88,7 @@ button.clicked.connect(doppleganger, 'toggle'); doppleganger.activeChanged.connect(function(active, reason) { if (button) { button.editProperties({ isActive: active }); - print('doppleganger.activeChanged', active, reason); + debugPrint('doppleganger.activeChanged', active, reason); } }); @@ -101,9 +103,15 @@ doppleganger.modelLoaded.connect(function(error, result) { // add debug indicators, but only if the user has configured the settings value if (Settings.getValue('debug.doppleganger', false)) { + WANT_DEBUG = true; DopplegangerClass.addDebugControls(doppleganger); } +function debugPrint() { + if (WANT_DEBUG) { + print('app-doppleganger | ' + [].slice.call(arguments).join(' ')); + } +} // ---------------------------------------------------------------------------- UserActivityLogger.logAction('doppleganger_app_load'); @@ -115,7 +123,7 @@ doppleganger.activeChanged.connect(function(active, reason) { // user intentionally toggled the doppleganger UserActivityLogger.logAction('doppleganger_disable'); } else { - print('doppleganger stopped:', reason); + debugPrint('doppleganger stopped:', reason); UserActivityLogger.logAction('doppleganger_autodisable', { reason: reason }); } } diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js index 1d3847cad2..a3b3873c2d 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger-attachments.js @@ -5,6 +5,7 @@ module.exports = DopplegangerAttachments; DopplegangerAttachments.version = '0.0.0'; +DopplegangerAttachments.WANT_DEBUG = false; var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, @@ -15,6 +16,10 @@ function log() { print('doppleganger-attachments | ' + [].slice.call(arguments).join(' ')); } +function debugPrint() { + DopplegangerAttachments.WANT_DEBUG && log.apply(this, arguments); +} + function DopplegangerAttachments(doppleganger, options) { utils.assign(this, { _options: options, @@ -24,7 +29,7 @@ function DopplegangerAttachments(doppleganger, options) { attachmentsUpdated: utils.signal(function attachmentsUpdated(currentAttachments, previousAttachments){}), }); this._initialize(); - log('DopplegangerAttachments...', JSON.stringify(options)); + debugPrint('DopplegangerAttachments...', JSON.stringify(options)); } DopplegangerAttachments.prototype = { // "hash" the current attachments (so that changes can be detected) @@ -87,7 +92,7 @@ DopplegangerAttachments.prototype = { jointNames = this.doppleganger.jointNames, properties = modelHelper.getProperties(this.doppleganger.objectID); - log('DopplegangerAttachments..._createAttachmentObjects', JSON.stringify({ + debugPrint('DopplegangerAttachments..._createAttachmentObjects', JSON.stringify({ type: properties.type, attachments: attachments.length, parentID: parentID, @@ -96,7 +101,7 @@ DopplegangerAttachments.prototype = { return attachments.map(utils.bind(this, function(attachment, i) { var type = modelHelper.type(attachment.properties && attachment.properties.objectID); if (type === 'overlay') { - log('skipping already-provisioned attachment object', type, attachment.properties && attachment.properties.name); + debugPrint('skipping already-provisioned attachment object', type, attachment.properties && attachment.properties.name); return attachment; } var jointIndex = attachment.$jointIndex, // jointNames.indexOf(attachment.jointName), @@ -133,7 +138,7 @@ DopplegangerAttachments.prototype = { modelHelper.deleteObject(objectID); return objectID = null; } - log('attachment model ('+modelHelper.type(result.objectID)+') is ready; # joints ==', + debugPrint('attachment model ('+modelHelper.type(result.objectID)+') is ready; # joints ==', result.jointNames && result.jointNames.length, result.naturalDimensions, result.objectID); var properties = modelHelper.getProperties(result.objectID), naturalDimensions = attachment.properties.naturalDimensions = properties.naturalDimensions; diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js index 375105e722..bebd36df45 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -28,15 +28,15 @@ var _modelHelper = require('./model-helper.js'), modelHelper = _modelHelper.modelHelper, ModelReadyWatcher = _modelHelper.ModelReadyWatcher; +// @property {bool} - toggle verbose debug logging on/off +Doppleganger.WANT_DEBUG = false; + // @property {bool} - when set true, Script.update will be used instead of setInterval for syncing joint data Doppleganger.USE_SCRIPT_UPDATE = false; // @property {int} - the frame rate to target when using setInterval for joint updates Doppleganger.TARGET_FPS = 60; -// @property {int} - the maximum time in seconds to wait for the model overlay to finish loading -Doppleganger.MAX_WAIT_SECS = 10; - // @class Doppleganger - Creates a new instance of a Doppleganger. // @param {Avatar} [options.avatar=MyAvatar] - Avatar used to retrieve position and joint data. // @param {bool} [options.mirrored=true] - Apply "symmetric mirroring" of Left/Right joints. @@ -64,10 +64,10 @@ Doppleganger.prototype = { // @public @method - toggles doppleganger on/off toggle: function() { if (this.active) { - log('toggling off'); + debugPrint('toggling off'); this.stop(); } else { - log('toggling on'); + debugPrint('toggling on'); this.start(); } return this.active; @@ -92,7 +92,7 @@ Doppleganger.prototype = { // note: this mismatch can happen when the avatar's model is actively changing if (size !== translations.length || (this.jointStateCount && size !== this.jointStateCount)) { - log('mismatched joint counts (avatar model likely changed)', size, translations.length, this.jointStateCount); + debugPrint('mismatched joint counts (avatar model likely changed)', size, translations.length, this.jointStateCount); this.stop('avatar_changed_joints'); return; } @@ -168,7 +168,7 @@ Doppleganger.prototype = { Script.scriptEnding.connect(this, function() { modelHelper.deleteObject(this.objectID); }); - log('doppleganger created; objectID =', this.objectID); + debugPrint('doppleganger created; objectID =', this.objectID); // trigger clean up (and stop updates) if the object gets deleted this.onObjectDeleted = function(uuid) { @@ -193,14 +193,14 @@ Doppleganger.prototype = { if (error) { return this.stop(error); } - log('model ('+modelHelper.type(this.objectID)+')' + ' is ready; # joints == ' + result.jointNames.length); + debugPrint('model ('+modelHelper.type(this.objectID)+')' + ' is ready; # joints == ' + result.jointNames.length); var naturalDimensions = modelHelper.getProperties(this.objectID, ['naturalDimensions']).naturalDimensions; - log('naturalDimensions:', JSON.stringify(naturalDimensions)); + debugPrint('naturalDimensions:', JSON.stringify(naturalDimensions)); var props = { visible: true }; if (naturalDimensions) { props.dimensions = Vec3.multiply(this.scale, naturalDimensions); } - log('scaledDimensions:', this.scale, JSON.stringify(props.dimensions)); + debugPrint('scaledDimensions:', this.scale, JSON.stringify(props.dimensions)); modelHelper.editObject(this.objectID, props); if (!options.position) { this.syncVerticalPosition(); @@ -250,7 +250,7 @@ Doppleganger.prototype = { if (this.active) { this.activeChanged(this.active = false, reason); } else if (reason) { - log('already stopped so not triggering another activeChanged; latest reason was:', reason); + debugPrint('already stopped so not triggering another activeChanged; latest reason was:', reason); } }, // @public @method - Reposition the doppleganger so it sees "eye to eye" with the Avatar. @@ -263,7 +263,7 @@ Doppleganger.prototype = { doppleJointIndex = modelHelper.getJointIndex(this.objectID, byJointName),// names.indexOf(byJointName), doppleJointPosition = positions[doppleJointIndex]; - print('........... doppleJointPosition', JSON.stringify({ + debugPrint('........... doppleJointPosition', JSON.stringify({ byJointName: byJointName, dopplePosition: dopplePosition, doppleJointIndex: doppleJointIndex, @@ -276,7 +276,7 @@ Doppleganger.prototype = { avatarJointPosition = this.avatar.getJointPosition(avatarJointIndex); var offset = (avatarJointPosition.y - doppleJointPosition.y); - log('adjusting for offset', offset); + debugPrint('adjusting for offset', offset); if (properties.type === 'model') { dopplePosition.y = avatarPosition.y + offset; } else { @@ -290,11 +290,11 @@ Doppleganger.prototype = { // @private @method - creates the update thread to synchronize joint data _createUpdateThread: function() { if (Doppleganger.USE_SCRIPT_UPDATE) { - log('creating Script.update thread'); + debugPrint('creating Script.update thread'); this.onUpdate = this.update; Script.update.connect(this, 'onUpdate'); } else { - log('creating Script.setInterval thread @ ~', Doppleganger.TARGET_FPS +'fps'); + debugPrint('creating Script.setInterval thread @ ~', Doppleganger.TARGET_FPS +'fps'); var timeout = 1000 / Doppleganger.TARGET_FPS; this._interval = Script.setInterval(bind(this, 'update'), timeout); } @@ -369,6 +369,10 @@ function log() { print('doppleganger | ' + [].slice.call(arguments).join(' ')); } +function debugPrint() { + Doppleganger.WANT_DEBUG && log.apply(this, arguments); +} + // -- ADVANCED DEBUGGING -- // @function - Add debug joint indicators / extra debugging info. // @param {Doppleganger} - existing Doppleganger instance to add controls to From 416852c0cd216a772558f4079efaba9dfd8b2142 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 23 Jun 2017 10:19:16 -0700 Subject: [PATCH 12/16] Fix warnings about creating QObjects with parents in different thread --- interface/src/Application.cpp | 6 +++++- libraries/gpu-gl/src/gpu/gl/GLBackend.cpp | 5 +++++ libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 5 ----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a7bed9fc9c..1ce21f9ec7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -713,9 +713,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo updateHeartbeat(); // setup a timer for domain-server check ins - QTimer* domainCheckInTimer = new QTimer(nodeList.data()); + QTimer* domainCheckInTimer = new QTimer(this); connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); + connect(this, &QCoreApplication::aboutToQuit, [domainCheckInTimer] { + domainCheckInTimer->stop(); + domainCheckInTimer->deleteLater(); + }); auto audioIO = DependencyManager::get(); diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 791130ef6e..3441407f62 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -178,6 +178,11 @@ void GLBackend::init() { int swapInterval = wglGetSwapIntervalEXT(); qCDebug(gpugllogging, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); }*/ +#endif +#if THREADED_TEXTURE_BUFFERING + // This has to happen on the main thread in order to give the thread + // pool a reasonable parent object + GLVariableAllocationSupport::TransferJob::startBufferingThread(); #endif }); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 4161242a24..7758ddaf49 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -461,11 +461,6 @@ void GLVariableAllocationSupport::updateMemoryPressure() { if (newState != _memoryPressureState) { _memoryPressureState = newState; -#if THREADED_TEXTURE_BUFFERING - if (MemoryPressureState::Transfer == _memoryPressureState) { - TransferJob::startBufferingThread(); - } -#endif // Clear the existing queue _transferQueue = WorkQueue(); _promoteQueue = WorkQueue(); From 86ed61a15dccda8fd29196b72e8ad347c9ad6786 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 23 Jun 2017 14:26:13 -0700 Subject: [PATCH 13/16] Push avatar packet version for isReplicated --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index ad15d04db1..240697d890 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -69,7 +69,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::AvatarIdentitySequenceFront); + return static_cast(AvatarMixerPacketVersion::IsReplicatedInAvatarIdentity); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 2944c1ce93..6c42193e11 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -247,7 +247,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { IdentityPacketsIncludeUpdateTime, AvatarIdentitySequenceId, MannequinDefaultAvatar, - AvatarIdentitySequenceFront + AvatarIdentitySequenceFront, + IsReplicatedInAvatarIdentity }; enum class DomainConnectRequestVersion : PacketVersion { From db2b702221fbf0b02f8eab9f7b707f1e6398c143 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Mon, 26 Jun 2017 08:08:52 -0700 Subject: [PATCH 14/16] Fix reverbTest script --- scripts/developer/utilities/tools/reverbTest.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/developer/utilities/tools/reverbTest.js b/scripts/developer/utilities/tools/reverbTest.js index a7a6bad9d7..8d83140ecd 100644 --- a/scripts/developer/utilities/tools/reverbTest.js +++ b/scripts/developer/utilities/tools/reverbTest.js @@ -35,8 +35,8 @@ var audioOptions = new AudioEffectOptions({ wetDryMix: 50, }); -AudioDevice.setReverbOptions(audioOptions); -AudioDevice.setReverb(true); +Audio.setReverbOptions(audioOptions); +Audio.setReverb(true); print("Reverb is ON."); var panel = new Panel(10, 160); @@ -66,7 +66,7 @@ var parameters = [ ] function setter(name) { - return function(value) { audioOptions[name] = value; AudioDevice.setReverbOptions(audioOptions); } + return function(value) { audioOptions[name] = value; Audio.setReverbOptions(audioOptions); } } function getter(name) { @@ -89,7 +89,7 @@ Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseRelease function scriptEnding() { panel.destroy(); - AudioDevice.setReverb(false); + Audio.setReverb(false); print("Reverb is OFF."); } Script.scriptEnding.connect(scriptEnding); From fba1a8ddaa284bb2be4ca791bdd5f6be8a553f76 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Mon, 26 Jun 2017 08:28:29 -0700 Subject: [PATCH 15/16] Fix halfDuplex script --- scripts/tutorials/halfDuplex.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/scripts/tutorials/halfDuplex.js b/scripts/tutorials/halfDuplex.js index e1ed132233..d4a993ae06 100644 --- a/scripts/tutorials/halfDuplex.js +++ b/scripts/tutorials/halfDuplex.js @@ -20,7 +20,7 @@ var averageLoudness = 0.0; var AVERAGING_TIME = 0.9; var LOUDNESS_THRESHOLD = 100; -var HYSTERESIS_GAP = 1.41; // 3db gap +var HYSTERESIS_GAP = 1.41; // 3dB gap var MICROPHONE_DISPLAY_NAME = "Microphone"; var debug = false; @@ -54,17 +54,13 @@ Script.update.connect(function () { print("Muted!"); } isMuted = true; - if (!AudioDevice.getMuted()) { - AudioDevice.toggleMute(); - } + Audio.muted = true; } else if (isMuted && (averageLoudness < LOUDNESS_THRESHOLD)) { if (debug) { print("UnMuted!"); } isMuted = false; - if (AudioDevice.getMuted()) { - AudioDevice.toggleMute(); - } + Audio.muted = false; } }); From de88a34e977b1cd33913216a911ab813129e195d Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 26 Jun 2017 23:18:21 -0700 Subject: [PATCH 16/16] Fix toolbar button activation states --- scripts/system/audio.js | 12 ++++-------- scripts/system/help.js | 3 ++- scripts/system/marketplaces/marketplaces.js | 12 ++---------- scripts/system/menu.js | 9 ++------- scripts/system/pal.js | 16 ++++++---------- scripts/system/snapshot.js | 10 +++------- 6 files changed, 19 insertions(+), 43 deletions(-) diff --git a/scripts/system/audio.js b/scripts/system/audio.js index cb9589ae9e..0a3471fa81 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -15,6 +15,7 @@ var TABLET_BUTTON_NAME = "AUDIO"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +var AUDIO_QML_SOURCE = "../audio/Audio.qml"; var MUTE_ICONS = { icon: "icons/tablet-icons/mic-mute-i.svg", @@ -34,7 +35,6 @@ function onMuteToggled() { } } -var shouldActivateButton = false; var onAudioScreen = false; function onClicked() { @@ -44,18 +44,14 @@ function onClicked() { } else { var entity = HMD.tabletID; Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); - shouldActivateButton = true; - shouldActivateButton = true; - tablet.loadQMLSource("../audio/Audio.qml"); - onAudioScreen = true; + tablet.loadQMLSource(AUDIO_QML_SOURCE); } } function onScreenChanged(type, url) { + onAudioScreen = (type === "QML" && url === AUDIO_QML_SOURCE); // for toolbar mode: change button to active when window is first openend, false otherwise. - button.editProperties({isActive: shouldActivateButton}); - shouldActivateButton = false; - onAudioScreen = false; + button.editProperties({isActive: onAudioScreen}); } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); diff --git a/scripts/system/help.js b/scripts/system/help.js index a335b2ef9c..1265a5597b 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -40,7 +40,8 @@ } function onScreenChanged(type, url) { - onHelpScreen = false; + onHelpScreen = type === "Web" && url.startsWith("../../../html/tabletHelp.html"); + button.editProperties({ isActive: onHelpScreen }); } button.clicked.connect(onClicked); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 4d26bcadb6..3be8143830 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -52,17 +52,11 @@ function onMessageBoxClosed(id, button) { Window.messageBoxClosed.connect(onMessageBoxClosed); -var shouldActivateButton = false; var onMarketplaceScreen = false; function showMarketplace() { UserActivityLogger.openedMarketplace(); - - shouldActivateButton = true; - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - onMarketplaceScreen = true; - tablet.webEventReceived.connect(function (message) { if (message === GOTO_DIRECTORY) { @@ -122,7 +116,6 @@ function onClick() { if (onMarketplaceScreen) { // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); - onMarketplaceScreen = false; } else { var entity = HMD.tabletID; Entities.editEntity(entity, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})}); @@ -131,10 +124,9 @@ function onClick() { } function onScreenChanged(type, url) { + onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL // for toolbar mode: change button to active when window is first openend, false otherwise. - marketplaceButton.editProperties({isActive: shouldActivateButton}); - shouldActivateButton = false; - onMarketplaceScreen = false; + marketplaceButton.editProperties({isActive: onMarketplaceScreen}); } marketplaceButton.clicked.connect(onClick); diff --git a/scripts/system/menu.js b/scripts/system/menu.js index 4ad5958144..c7a44d3e48 100644 --- a/scripts/system/menu.js +++ b/scripts/system/menu.js @@ -21,7 +21,6 @@ var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet- sortOrder: 3 }); - var shouldActivateButton = false; var onMenuScreen = false; function onClicked() { @@ -31,17 +30,13 @@ var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet- } else { var entity = HMD.tabletID; Entities.editEntity(entity, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})}); - shouldActivateButton = true; tablet.gotoMenuScreen(); - onMenuScreen = true; } } function onScreenChanged(type, url) { - // for toolbar mode: change button to active when window is first openend, false otherwise. - button.editProperties({isActive: shouldActivateButton}); - shouldActivateButton = false; - onMenuScreen = false; + onMenuScreen = type === "Menu"; + button.editProperties({isActive: onMenuScreen}); } button.clicked.connect(onClicked); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 6c1652c700..2c81622668 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -40,7 +40,7 @@ var HOVER_TEXTURES = { var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now - +var PAL_QML_SOURCE = "../Pal.qml"; var conserveResources = true; Script.include("/~/system/libraries/controllers.js"); @@ -727,17 +727,14 @@ function tabletVisibilityChanged() { } var onPalScreen = false; -var shouldActivateButton = false; function onTabletButtonClicked() { if (onPalScreen) { // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { - shouldActivateButton = true; - tablet.loadQMLSource("../Pal.qml"); + tablet.loadQMLSource(PAL_QML_SOURCE); tablet.tabletShownChanged.connect(tabletVisibilityChanged); - onPalScreen = true; Users.requestsDomainListData = true; populateNearbyUserList(); isWired = true; @@ -765,14 +762,13 @@ function wireEventBridge(on) { } function onTabletScreenChanged(type, url) { - wireEventBridge(shouldActivateButton); + onPalScreen = (type === "QML" && url === PAL_QML_SOURCE); + wireEventBridge(onPalScreen); // for toolbar mode: change button to active when window is first openend, false otherwise. - button.editProperties({isActive: shouldActivateButton}); - shouldActivateButton = false; - onPalScreen = false; + button.editProperties({isActive: onPalScreen}); // disable sphere overlays when not on pal screen. - if (type !== "QML" || url !== "../Pal.qml") { + if (!onPalScreen) { off(); } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 6321c17ded..c60e3a67f7 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -377,18 +377,15 @@ function fillImageDataFromPrevious() { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; -var shouldActivateButton = false; function onButtonClicked() { if (isInSnapshotReview){ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { - shouldActivateButton = true; fillImageDataFromPrevious(); tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); tablet.webEventReceived.connect(onMessage); HMD.openTablet(); - isInSnapshotReview = true; } } @@ -662,11 +659,10 @@ function maybeDeleteSnapshotStories() { storyIDsToMaybeDelete = []; } function onTabletScreenChanged(type, url) { - button.editProperties({ isActive: shouldActivateButton }); - shouldActivateButton = false; - if (isInSnapshotReview) { + isInSnapshotReview = (type === "Web" && url === SNAPSHOT_REVIEW_URL); + button.editProperties({ isActive: isInSnapshotReview }); + if (!isInSnapshotReview) { tablet.webEventReceived.disconnect(onMessage); - isInSnapshotReview = false; } } function onUsernameChanged() {