diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 161be954ff..a78939256d 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -73,24 +73,20 @@ void ScriptableAvatar::update(float deltatime) { const FBXAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(currentFrame) % frameCount); const float frameFraction = glm::fract(currentFrame); - for (int i = 0; i < modelJoints.size(); i++) { - int mapping = animationJoints.indexOf(modelJoints[i]); - if (mapping != -1 && !_maskedJoints.contains(modelJoints[i])) { - JointData& data = _jointData[i]; + for (int i = 0; i < animationJoints.size(); i++) { + const QString& name = animationJoints[i]; + int mapping = getJointIndex(name); + if (mapping != -1 && !_maskedJoints.contains(name)) { + JointData& data = _jointData[mapping]; auto newRotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); - auto newTranslation = floorFrame.translations.at(i) * (1.0f - frameFraction) + - ceilFrame.translations.at(i) * frameFraction; - + // We could probably do translations as in interpolation in model space (rather than the parent space that each frame is in), + // but we don't do so for MyAvatar yet, so let's not be different here. if (data.rotation != newRotation) { data.rotation = newRotation; data.rotationSet = true; } - if (data.translation != newTranslation) { - data.translation = newTranslation; - data.translationSet = true; - } - } + } } } else { _animation.clear(); diff --git a/cmake/macros/CopyDllsBesideWindowsExecutable.cmake b/cmake/macros/CopyDllsBesideWindowsExecutable.cmake index b57c781eff..69fd20a57b 100644 --- a/cmake/macros/CopyDllsBesideWindowsExecutable.cmake +++ b/cmake/macros/CopyDllsBesideWindowsExecutable.cmake @@ -18,12 +18,19 @@ macro(COPY_DLLS_BESIDE_WINDOWS_EXECUTABLE) @ONLY ) + if (APPLE) + set(PLUGIN_PATH "interface.app/Contents/MacOS/plugins") + else() + set(PLUGIN_PATH "plugins") + endif() + # add a post-build command to copy DLLs beside the executable add_custom_command( TARGET ${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -DBUNDLE_EXECUTABLE=$ + -DBUNDLE_PLUGIN_DIR=$/${PLUGIN_PATH} -P ${CMAKE_CURRENT_BINARY_DIR}/FixupBundlePostBuild.cmake ) diff --git a/cmake/templates/FixupBundlePostBuild.cmake.in b/cmake/templates/FixupBundlePostBuild.cmake.in index 4afe4de403..ee686a6967 100644 --- a/cmake/templates/FixupBundlePostBuild.cmake.in +++ b/cmake/templates/FixupBundlePostBuild.cmake.in @@ -41,4 +41,16 @@ function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) endfunction() message(STATUS "FIXUP_LIBS for fixup_bundle called for bundle ${BUNDLE_EXECUTABLE} are @FIXUP_LIBS@") -fixup_bundle("${BUNDLE_EXECUTABLE}" "" "@FIXUP_LIBS@") \ No newline at end of file + +message(STATUS "Scanning for plugins from ${BUNDLE_PLUGIN_DIR}") + +if (APPLE) + set(PLUGIN_EXTENSION "dylib") +elseif (WIN32) + set(PLUGIN_EXTENSION "dll") +else() + set(PLUGIN_EXTENSION "so") +endif() + +file(GLOB RUNTIME_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}") +fixup_bundle("${BUNDLE_EXECUTABLE}" "${RUNTIME_PLUGINS}" "@FIXUP_LIBS@") \ No newline at end of file diff --git a/examples/acScripts/animatedAvatarAgent.js b/examples/acScripts/animatedAvatarAgent.js new file mode 100644 index 0000000000..4e550e9789 --- /dev/null +++ b/examples/acScripts/animatedAvatarAgent.js @@ -0,0 +1,29 @@ +"use strict"; +/*jslint vars: true, plusplus: true*/ +/*global Agent, Avatar, Script, Entities, Vec3, print*/ +// +// animatedAvatar.js +// examples/acScripts +// +// Created by Howard Stearns 11/6/15 +// Copyright 2015 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 +// +// An assignment client script that animates one avatar at random location within 'spread' meters of 'origin'. +// In Domain Server Settings, go to scripts and give the url of this script. Press '+', and then 'Save and restart'. + +var origin = {x: 500, y: 502, z: 500}; +var spread = 10; // meters +var animationData = {url: "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", lastFrame: 35}; +Avatar.skeletonModelURL = "https://hifi-public.s3.amazonaws.com/marketplace/contents/dd03b8e3-52fb-4ab3-9ac9-3b17e00cd85d/98baa90b3b66803c5d7bd4537fca6993.fst"; //lovejoy +Avatar.displayName = "'Bot"; +var millisecondsToWaitBeforeStarting = 10 * 1000; // To give the various servers a chance to start. + +Agent.isAvatar = true; +Script.setTimeout(function () { + Avatar.position = Vec3.sum(origin, {x: Math.random() * spread, y: 0, z: Math.random() * spread}); + print("Starting at", JSON.stringify(Avatar.position)); + Avatar.startAnimation(animationData.url, animationData.fps || 30, 1, true, false, animationData.firstFrame || 0, animationData.lastFrame); +}, millisecondsToWaitBeforeStarting); diff --git a/examples/avatarMover/avatarMover.js b/examples/avatarMover/avatarMover.js new file mode 100644 index 0000000000..a47bd01ed5 --- /dev/null +++ b/examples/avatarMover/avatarMover.js @@ -0,0 +1,188 @@ +(function() { + this.defaultRange = 5; + this.acceleration = { + x: 0, + y: 0, + z: 0 + }; + this.onColor = { + red: 77, + green: 11, + blue: 111 + }; + this.offColor = { + red: 200, + green: 0, + blue: 0 + }; + var self = this; + //Default forward direction of mover object + this.forward = { + x: 0, + y: 0, + z: -1 + }; + this.isMoving = false; + this.velocity = { + x: 0, + y: 0, + z: 0 + }; + this.defaultThrust = 500; + this.maxRotMixVal = 0.01; + this.minRotMixVal = this.maxRotMixVal * 0.5; + this.minThrustPercentage = 0.2; + this.userData = {}; + + + this.getUserData = function() { + if (this.properties.userData) { + this.userData = JSON.parse(this.properties.userData); + } + } + + this.updateUserData = function() { + Entities.editEntity(this.entityId, { + userData: JSON.stringify(this.userData) + }); + } + + + this.toggleMover = function() { + if (!this.userData.active) { + this.activate(); + } else if (this.userData.active) { + this.deactivate(); + } + + } + + this.clickReleaseOnEntity = function(entityId, mouseEvent) { + this.entityId = entityId + if (mouseEvent.isLeftButton) { + this.toggleMover(); + } + } + + this.activate = function() { + //activate a light at the movers position + this.properties = Entities.getEntityProperties(this.entityId); + this.getUserData(); + this.userData.active = true; + this.initUserData(); + var lightPos = this.properties.position; + lightPos.y += .1; + this.light = Entities.addEntity({ + type: "Light", + position: lightPos, + isSpotlight: false, + dimensions: { + x: 2, + y: 2, + z: 2 + }, + color: this.onColor, + intensity: 10 + // rotation: {x : 0, y: Math.PI/2, z: 0} + }); + + this.field = Overlays.addOverlay("sphere", { + position: this.properties.position, + size: this.userData.range, + solid: false, + color: { + red: 250, + green: 10, + blue: 10 + }, + }) + + //change color + Entities.editEntity(this.entityId, { + color: this.onColor, + }); + } + + this.initUserData = function() { + this.userData.range = this.userData.range || this.defaultRange; + this.userData.thrust = this.userData.thrust || this.defaultThrust; + this.updateUserData(); + } + + this.updateOverlays = function() { + if (this.field) { + Overlays.editOverlay(this.field, { + size: this.userData.range + }); + } + } + + + this.deactivate = function() { + this.userData.active = false; + this.updateUserData(); + Entities.editEntity(this.entityId, { + color: this.offColor + }); + this.cleanUp(); + } + + this.scriptEnding = function() { + this.cleanUp(); + } + + this.update = function(deltaTime) { + self.properties = Entities.getEntityProperties(self.entityId); + self.getUserData(); + self.updateOverlays(); + if (!self.userData.active) { + return; + } + self.distance = Vec3.distance(MyAvatar.position, self.properties.position); + if (self.distance < self.userData.range) { + self.rotationMixVal = map(self.distance, 0, self.userData.range, self.maxRotMixVal, self.minRotMixVal); + + //We want to extract yaw from rotated object so avatars do not pith or roll, as they will be stuck that way. + self.sanitizedRotation = Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(self.properties.rotation).y, 0); + self.newOrientation = Quat.mix(MyAvatar.orientation, self.sanitizedRotation, self.rotationMixVal); + MyAvatar.orientation = self.newOrientation; + + self.rotatedDir = { + x: self.forward.x, + y: self.forward.y, + z: self.forward.z + }; + self.rotatedDir = Vec3.multiplyQbyV(self.properties.rotation, self.rotatedDir); + + self.thrust = map(self.distance, 0, self.userData.range, self.userData.thrust, self.userData.thrust * self.minThrustPercentage); + self.direction = Vec3.normalize(self.rotatedDir); + self.velocity = Vec3.multiply(self.direction, self.thrust); + MyAvatar.addThrust(Vec3.multiply(self.velocity, deltaTime)); + } + + } + + + this.preload = function(entityId) { + this.entityId = entityId; + } + + this.unload = function() { + Script.update.disconnect(this.update); + this.cleanUp(); + } + + + this.cleanUp = function() { + Entities.deleteEntity(this.light); + Overlays.deleteOverlay(this.field); + } + + function map(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); + } + + Script.scriptEnding.connect(this.scriptEnding); + Script.update.connect(this.update); + +}); \ No newline at end of file diff --git a/examples/avatarMover/avatarMoverSpawner.js b/examples/avatarMover/avatarMoverSpawner.js new file mode 100644 index 0000000000..526446ecdb --- /dev/null +++ b/examples/avatarMover/avatarMoverSpawner.js @@ -0,0 +1,18 @@ +var modelURL = "https://s3.amazonaws.com/hifi-public/eric/models/arrow.fbx"; +var scriptURL = Script.resolvePath('avatarMover.js'); +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation()))); + +var avatarMover = Entities.addEntity({ + type: "Model", + modelURL: modelURL, + position: center, + userData: JSON.stringify({range: 5}), + script: scriptURL +}); + + +function cleanup() { + Entities.deleteEntity(avatarMover); +} + +Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/controllers/rightClickExample.js b/examples/controllers/rightClickExample.js new file mode 100644 index 0000000000..c3e6ea8f3d --- /dev/null +++ b/examples/controllers/rightClickExample.js @@ -0,0 +1,10 @@ +var MAPPING_NAME = "com.highfidelity.rightClickExample"; +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from(Controller.Hardware.Keyboard.RightMouseClicked).to(function (value) { + print("Keyboard.RightMouseClicked"); +}); +Controller.enableMapping(MAPPING_NAME); + +Script.scriptEnding.connect(function () { + Controller.disableMapping(MAPPING_NAME); +}); \ No newline at end of file diff --git a/examples/entityScripts/createParamsEntity.js b/examples/entityScripts/createParamsEntity.js new file mode 100644 index 0000000000..991bb0d667 --- /dev/null +++ b/examples/entityScripts/createParamsEntity.js @@ -0,0 +1,42 @@ +// +// createParamsEntity.js +// +// Created by James B. Pollack @imgntn on 11/6/2015 +// Copyright 2015 High Fidelity, Inc. +// +// This script demonstrates creating an entity and sending it a method call with parameters. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var PARAMS_SCRIPT_URL = Script.resolvePath('paramsEntity.js'); + +var testEntity = Entities.addEntity({ + name: 'paramsTestEntity', + dimensions: { + x: 1, + y: 1, + z: 1 + }, + type: 'Box', + position: { + x: 0, + y: 0, + z: 0 + }, + visible: false, + script: PARAMS_SCRIPT_URL +}); + + +var subData1 = ['apple', 'banana', 'orange']; +var subData2 = { + thing: 1, + otherThing: 2 +}; +var data = [subData1, JSON.stringify(subData2), 'third']; +Script.setTimeout(function() { + print('sending data to entity') + Entities.callEntityMethod(testEntity, 'testParams', data); +}, 1500) \ No newline at end of file diff --git a/examples/entityScripts/paramsEntity.js b/examples/entityScripts/paramsEntity.js new file mode 100644 index 0000000000..6cb5cb5833 --- /dev/null +++ b/examples/entityScripts/paramsEntity.js @@ -0,0 +1,45 @@ +// +// paramsEntity.js +// +// Script Type: Entity +// +// Created by James B. Pollack @imgntn on 11/6/2015 +// Copyright 2015 High Fidelity, Inc. +// +// This script demonstrates how to recieve parameters from a Entities.callEntityMethod call +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function() { + + function ParamsEntity() { + return; + } + + ParamsEntity.prototype = { + preload: function(entityID) { + print('entity loaded') + this.entityID = entityID; + }, + testParams: function(myID, paramsArray) { + + paramsArray.forEach(function(param) { + var p; + try { + p = JSON.parse(param); + print("it's a json param") + print('json param property:' + p.thing); + } catch (err) { + print('not a json param') + p = param; + print('param is:' + p); + } + + }); + + } + + } + + return new ParamsEntity(); +}); \ No newline at end of file diff --git a/examples/libraries/promise.js b/examples/libraries/promise.js new file mode 100644 index 0000000000..cffa294715 --- /dev/null +++ b/examples/libraries/promise.js @@ -0,0 +1,222 @@ +// Copyright (c) 2014 Taylor Hakes +// Copyright (c) 2014 Forbes Lindesay + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + + +function promiseMaker() { + + // Use polyfill for setImmediate for performance gains + var asap = (typeof setImmediate === 'function' && setImmediate) || + function(fn) { + Script.setTimeout(fn, 1); + }; + + // Polyfill for Function.prototype.bind + function bind(fn, thisArg) { + return function() { + fn.apply(thisArg, arguments); + } + } + + var isArray = Array.isArray || function(value) { + return Object.prototype.toString.call(value) === "[object Array]" + }; + + function Promise(fn) { + if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); + if (typeof fn !== 'function') throw new TypeError('not a function'); + this._state = null; + this._value = null; + this._deferreds = [] + + doResolve(fn, bind(resolve, this), bind(reject, this)) + } + + function handle(deferred) { + var me = this; + if (this._state === null) { + this._deferreds.push(deferred); + return + } + asap(function() { + var cb = me._state ? deferred.onFulfilled : deferred.onRejected + if (cb === null) { + (me._state ? deferred.resolve : deferred.reject)(me._value); + return; + } + var ret; + try { + ret = cb(me._value); + } catch (e) { + deferred.reject(e); + return; + } + deferred.resolve(ret); + }) + } + + function resolve(newValue) { + try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure + if (newValue === this) throw new TypeError('A promise cannot be resolved with itself.'); + if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { + var then = newValue.then; + if (typeof then === 'function') { + doResolve(bind(then, newValue), bind(resolve, this), bind(reject, this)); + return; + } + } + this._state = true; + this._value = newValue; + finale.call(this); + } catch (e) { + reject.call(this, e); + } + } + + function reject(newValue) { + this._state = false; + this._value = newValue; + finale.call(this); + } + + function finale() { + for (var i = 0, len = this._deferreds.length; i < len; i++) { + handle.call(this, this._deferreds[i]); + } + this._deferreds = null; + } + + function Handler(onFulfilled, onRejected, resolve, reject) { + this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; + this.onRejected = typeof onRejected === 'function' ? onRejected : null; + this.resolve = resolve; + this.reject = reject; + } + + /** + * Take a potentially misbehaving resolver function and make sure + * onFulfilled and onRejected are only called once. + * + * Makes no guarantees about asynchrony. + */ + function doResolve(fn, onFulfilled, onRejected) { + var done = false; + try { + fn(function(value) { + if (done) return; + done = true; + onFulfilled(value); + }, function(reason) { + if (done) return; + done = true; + onRejected(reason); + }) + } catch (ex) { + if (done) return; + done = true; + onRejected(ex); + } + } + + Promise.prototype['catch'] = function(onRejected) { + return this.then(null, onRejected); + }; + + Promise.prototype.then = function(onFulfilled, onRejected) { + var me = this; + return new Promise(function(resolve, reject) { + handle.call(me, new Handler(onFulfilled, onRejected, resolve, reject)); + }) + }; + + Promise.all = function() { + var args = Array.prototype.slice.call(arguments.length === 1 && isArray(arguments[0]) ? arguments[0] : arguments); + + return new Promise(function(resolve, reject) { + if (args.length === 0) return resolve([]); + var remaining = args.length; + + function res(i, val) { + try { + if (val && (typeof val === 'object' || typeof val === 'function')) { + var then = val.then; + if (typeof then === 'function') { + then.call(val, function(val) { + res(i, val) + }, reject); + return; + } + } + args[i] = val; + if (--remaining === 0) { + resolve(args); + } + } catch (ex) { + reject(ex); + } + } + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; + + Promise.resolve = function(value) { + if (value && typeof value === 'object' && value.constructor === Promise) { + return value; + } + + return new Promise(function(resolve) { + resolve(value); + }); + }; + + Promise.reject = function(value) { + return new Promise(function(resolve, reject) { + reject(value); + }); + }; + + Promise.race = function(values) { + return new Promise(function(resolve, reject) { + for (var i = 0, len = values.length; i < len; i++) { + values[i].then(resolve, reject); + } + }); + }; + + /** + * Set the immediate function to execute callbacks + * @param fn {function} Function to execute + * @private + */ + Promise._setImmediateFn = function _setImmediateFn(fn) { + asap = fn; + }; + + + return Promise + +} + +loadPromise = function() { + return promiseMaker(); +} \ No newline at end of file diff --git a/examples/libraries/promiseExample.js b/examples/libraries/promiseExample.js new file mode 100644 index 0000000000..817a78d2b0 --- /dev/null +++ b/examples/libraries/promiseExample.js @@ -0,0 +1,18 @@ +Script.include('promise.js'); +var Promise = loadPromise(); +var prom = new Promise(function(resolve, reject) { + print('making a promise') + // do a thing, possibly async, then… + var thing = true; + if (thing) { + resolve("Stuff worked!"); + } else { + print('ERROR') + reject(new Error("It broke")); + } +}); + +// Do something when async done +prom.then(function(result) { + print('result ' + result); +}); \ No newline at end of file diff --git a/examples/libraries/stringHelpers.js b/examples/libraries/stringHelpers.js index 0fae9035f0..81f990ef7f 100644 --- a/examples/libraries/stringHelpers.js +++ b/examples/libraries/stringHelpers.js @@ -1,39 +1,36 @@ - - - if (typeof String.prototype.fileName !== "function") { - String.prototype.fileName = function () { + String.prototype.fileName = function() { return this.replace(/^(.*[\/\\])*/, ""); }; } if (typeof String.prototype.fileBase !== "function") { - String.prototype.fileBase = function () { + String.prototype.fileBase = function() { var filename = this.fileName(); return filename.slice(0, filename.indexOf(".")); }; } if (typeof String.prototype.fileType !== "function") { - String.prototype.fileType = function () { + String.prototype.fileType = function() { return this.slice(this.lastIndexOf(".") + 1); }; } if (typeof String.prototype.path !== "function") { - String.prototype.path = function () { + String.prototype.path = function() { return this.replace(/[\\\/][^\\\/]*$/, ""); }; } if (typeof String.prototype.regExpEscape !== "function") { - String.prototype.regExpEscape = function () { + String.prototype.regExpEscape = function() { return this.replace(/([$\^.+*?|\\\/{}()\[\]])/g, '\\$1'); }; } if (typeof String.prototype.toArrayBuffer !== "function") { - String.prototype.toArrayBuffer = function () { + String.prototype.toArrayBuffer = function() { var length, buffer, view, @@ -64,3 +61,416 @@ if (typeof String.prototype.toArrayBuffer !== "function") { return buffer; }; } +// Copyright Mathias Bynens + +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: + +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +/*! https://mths.be/includes v1.0.0 by @mathias */ +if (!String.prototype.includes) { + (function() { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var toString = {}.toString; + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch (error) {} + return result; + }()); + var indexOf = ''.indexOf; + var includes = function(search) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + if (search && toString.call(search) == '[object RegExp]') { + throw TypeError(); + } + var stringLength = string.length; + var searchString = String(search); + var searchLength = searchString.length; + var position = arguments.length > 1 ? arguments[1] : undefined; + // `ToInteger` + var pos = position ? Number(position) : 0; + if (pos != pos) { // better `isNaN` + pos = 0; + } + var start = Math.min(Math.max(pos, 0), stringLength); + // Avoid the `indexOf` call if no match is possible + if (searchLength + start > stringLength) { + return false; + } + return indexOf.call(string, searchString, pos) != -1; + }; + if (defineProperty) { + defineProperty(String.prototype, 'includes', { + 'value': includes, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.includes = includes; + } + }()); +} + +/*! https://mths.be/startswith v0.2.0 by @mathias */ +if (!String.prototype.startsWith) { + (function() { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch (error) {} + return result; + }()); + var toString = {}.toString; + var startsWith = function(search) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + if (search && toString.call(search) == '[object RegExp]') { + throw TypeError(); + } + var stringLength = string.length; + var searchString = String(search); + var searchLength = searchString.length; + var position = arguments.length > 1 ? arguments[1] : undefined; + // `ToInteger` + var pos = position ? Number(position) : 0; + if (pos != pos) { // better `isNaN` + pos = 0; + } + var start = Math.min(Math.max(pos, 0), stringLength); + // Avoid the `indexOf` call if no match is possible + if (searchLength + start > stringLength) { + return false; + } + var index = -1; + while (++index < searchLength) { + if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) { + return false; + } + } + return true; + }; + if (defineProperty) { + defineProperty(String.prototype, 'startsWith', { + 'value': startsWith, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.startsWith = startsWith; + } + }()); +} +if (!String.prototype.endsWith) { + (function() { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch (error) {} + return result; + }()); + var toString = {}.toString; + var endsWith = function(search) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + if (search && toString.call(search) == '[object RegExp]') { + throw TypeError(); + } + var stringLength = string.length; + var searchString = String(search); + var searchLength = searchString.length; + var pos = stringLength; + if (arguments.length > 1) { + var position = arguments[1]; + if (position !== undefined) { + // `ToInteger` + pos = position ? Number(position) : 0; + if (pos != pos) { // better `isNaN` + pos = 0; + } + } + } + var end = Math.min(Math.max(pos, 0), stringLength); + var start = end - searchLength; + if (start < 0) { + return false; + } + var index = -1; + while (++index < searchLength) { + if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) { + return false; + } + } + return true; + }; + if (defineProperty) { + defineProperty(String.prototype, 'endsWith', { + 'value': endsWith, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.endsWith = endsWith; + } + }()); +} + +/*! https://mths.be/repeat v0.2.0 by @mathias */ +if (!String.prototype.repeat) { + (function() { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch (error) {} + return result; + }()); + var repeat = function(count) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + // `ToInteger` + var n = count ? Number(count) : 0; + if (n != n) { // better `isNaN` + n = 0; + } + // Account for out-of-bounds indices + if (n < 0 || n == Infinity) { + throw RangeError(); + } + var result = ''; + while (n) { + if (n % 2 == 1) { + result += string; + } + if (n > 1) { + string += string; + } + n >>= 1; + } + return result; + }; + if (defineProperty) { + defineProperty(String.prototype, 'repeat', { + 'value': repeat, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.repeat = repeat; + } + }()); +} + +if (!String.prototype.at) { + (function() { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements. + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch (exception) {} + return result; + }()); + var at = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices + // The odd lower bound is because the ToInteger operation is + // going to round `n` to `0` for `-1 < n <= 0`. + if (index <= -1 || index >= size) { + return ''; + } + // Second half of `ToInteger` + index = index | 0; + // Get the first code unit and code unit value + var cuFirst = string.charCodeAt(index); + var cuSecond; + var nextIndex = index + 1; + var len = 1; + if ( // Check if it’s the start of a surrogate pair. + cuFirst >= 0xD800 && cuFirst <= 0xDBFF && // high surrogate + size > nextIndex // there is a next code unit + ) { + cuSecond = string.charCodeAt(nextIndex); + if (cuSecond >= 0xDC00 && cuSecond <= 0xDFFF) { // low surrogate + len = 2; + } + } + return string.slice(index, index + len); + }; + if (defineProperty) { + defineProperty(String.prototype, 'at', { + 'value': at, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.at = at; + } + }()); +} + +/*! https://mths.be/codepointat v0.2.0 by @mathias */ +if (!String.prototype.codePointAt) { + (function() { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch (error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); +} + +/*! https://mths.be/fromcodepoint v0.2.1 by @mathias */ +if (!String.fromCodePoint) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch (error) {} + return result; + }()); + var stringFromCharCode = String.fromCharCode; + var floor = Math.floor; + var fromCodePoint = function(_) { + var MAX_SIZE = 0x4000; + var codeUnits = []; + var highSurrogate; + var lowSurrogate; + var index = -1; + var length = arguments.length; + if (!length) { + return ''; + } + var result = ''; + while (++index < length) { + var codePoint = Number(arguments[index]); + if (!isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity` + codePoint < 0 || // not a valid Unicode code point + codePoint > 0x10FFFF || // not a valid Unicode code point + floor(codePoint) != codePoint // not an integer + ) { + throw RangeError('Invalid code point: ' + codePoint); + } + if (codePoint <= 0xFFFF) { // BMP code point + codeUnits.push(codePoint); + } else { // Astral code point; split in surrogate halves + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + codePoint -= 0x10000; + highSurrogate = (codePoint >> 10) + 0xD800; + lowSurrogate = (codePoint % 0x400) + 0xDC00; + codeUnits.push(highSurrogate, lowSurrogate); + } + if (index + 1 == length || codeUnits.length > MAX_SIZE) { + result += stringFromCharCode.apply(null, codeUnits); + codeUnits.length = 0; + } + } + return result; + }; + if (defineProperty) { + defineProperty(String, 'fromCodePoint', { + 'value': fromCodePoint, + 'configurable': true, + 'writable': true + }); + } else { + String.fromCodePoint = fromCodePoint; + } + }()); +} \ No newline at end of file diff --git a/examples/libraries/usertiming.js b/examples/libraries/usertiming.js new file mode 100644 index 0000000000..9351972e37 --- /dev/null +++ b/examples/libraries/usertiming.js @@ -0,0 +1,547 @@ +// +// usertiming.js +// +// A polyfill for UserTiming (http://www.w3.org/TR/user-timing/) +// +// Copyright 2013 Nic Jansma +// http://nicj.net +// +// https://github.com/nicjansma/usertiming.js +// +// Licensed under the MIT license +// +// Adapted for High Fidelity by James B. Pollack @imgntn on 11/6/2015 + +function userTiming() { + "use strict"; + + // allow running in Node.js environment + if (typeof window === "undefined") { + window = {}; + } + + // prepare base perf object + if (typeof window.performance === "undefined") { + window.performance = {}; + } + + // We need to keep a global reference to the window.performance object to + // prevent any added properties from being garbage-collected in Safari 8. + // https://bugs.webkit.org/show_bug.cgi?id=137407 + window._perfRefForUserTimingPolyfill = window.performance; + + // + // Note what we shimmed + // + window.performance.userTimingJsNow = false; + window.performance.userTimingJsNowPrefixed = false; + window.performance.userTimingJsUserTiming = false; + window.performance.userTimingJsUserTimingPrefixed = false; + window.performance.userTimingJsPerformanceTimeline = false; + window.performance.userTimingJsPerformanceTimelinePrefixed = false; + + // for prefixed support + var prefixes = []; + var methods = []; + var methodTest = null; + var i, j; + + // + // window.performance.now() shim + // http://www.w3.org/TR/hr-time/ + // + if (typeof window.performance.now !== "function") { + window.performance.userTimingJsNow = true; + + // copy prefixed version over if it exists + methods = ["webkitNow", "msNow", "mozNow"]; + + for (i = 0; i < methods.length; i++) { + if (typeof window.performance[methods[i]] === "function") { + window.performance.now = window.performance[methods[i]]; + + window.performance.userTimingJsNowPrefixed = true; + + break; + } + } + + // + // now() should be a DOMHighResTimeStamp, which is defined as being a time relative + // to navigationStart of the PerformanceTiming (PT) interface. If this browser supports + // PT, use that as our relative start. Otherwise, use "now" as the start and all other + // now() calls will be relative to our initialization. + // + + var nowOffset = +(new Date()); + if (window.performance.timing && window.performance.timing.navigationStart) { + nowOffset = window.performance.timing.navigationStart; + } + + if (typeof window.performance.now !== "function") { + // No browser support, fall back to Date.now + if (Date.now) { + window.performance.now = function() { + return Date.now() - nowOffset; + }; + } else { + // no Date.now support, get the time from new Date() + window.performance.now = function() { + return +(new Date()) - nowOffset; + }; + } + } + } + + // + // PerformanceTimeline (PT) shims + // http://www.w3.org/TR/performance-timeline/ + // + + /** + * Adds an object to our internal Performance Timeline array. + * + * Will be blank if the environment supports PT. + */ + var addToPerformanceTimeline = function() {}; + + /** + * Clears the specified entry types from our timeline array. + * + * Will be blank if the environment supports PT. + */ + var clearEntriesFromPerformanceTimeline = function() {}; + + // performance timeline array + var performanceTimeline = []; + + // whether or not the timeline will require sort on getEntries() + var performanceTimelineRequiresSort = false; + + // whether or not ResourceTiming is natively supported but UserTiming is + // not (eg Firefox 35) + var hasNativeGetEntriesButNotUserTiming = false; + + // + // If getEntries() and mark() aren't defined, we'll assume + // we have to shim at least some PT functions. + // + if (typeof window.performance.getEntries !== "function" || + typeof window.performance.mark !== "function") { + + if (typeof window.performance.getEntries === "function" && + typeof window.performance.mark !== "function") { + hasNativeGetEntriesButNotUserTiming = true; + } + + window.performance.userTimingJsPerformanceTimeline = true; + + // copy prefixed version over if it exists + prefixes = ["webkit", "moz"]; + methods = ["getEntries", "getEntriesByName", "getEntriesByType"]; + + for (i = 0; i < methods.length; i++) { + for (j = 0; j < prefixes.length; j++) { + // prefixed method will likely have an upper-case first letter + methodTest = prefixes[j] + methods[i].substr(0, 1).toUpperCase() + methods[i].substr(1); + + if (typeof window.performance[methodTest] === "function") { + window.performance[methods[i]] = window.performance[methodTest]; + + window.performance.userTimingJsPerformanceTimelinePrefixed = true; + } + } + } + + /** + * Adds an object to our internal Performance Timeline array. + * + * @param {Object} obj PerformanceEntry + */ + addToPerformanceTimeline = function(obj) { + performanceTimeline.push(obj); + + // + // If we insert a measure, its startTime may be out of order + // from the rest of the entries because the use can use any + // mark as the start time. If so, note we have to sort it before + // returning getEntries(); + // + if (obj.entryType === "measure") { + performanceTimelineRequiresSort = true; + } + }; + + /** + * Ensures our PT array is in the correct sorted order (by startTime) + */ + var ensurePerformanceTimelineOrder = function() { + if (!performanceTimelineRequiresSort) { + return; + } + + // + // Measures, which may be in this list, may enter the list in + // an unsorted order. For example: + // + // 1. measure("a") + // 2. mark("start_mark") + // 3. measure("b", "start_mark") + // 4. measure("c") + // 5. getEntries() + // + // When calling #5, we should return [a,c,b] because technically the start time + // of c is "0" (navigationStart), which will occur before b's start time due to the mark. + // + performanceTimeline.sort(function(a, b) { + return a.startTime - b.startTime; + }); + + performanceTimelineRequiresSort = false; + }; + + /** + * Clears the specified entry types from our timeline array. + * + * @param {string} entryType Entry type (eg "mark" or "measure") + * @param {string} [name] Entry name (optional) + */ + clearEntriesFromPerformanceTimeline = function(entryType, name) { + // clear all entries from the perf timeline + i = 0; + while (i < performanceTimeline.length) { + if (performanceTimeline[i].entryType !== entryType) { + // unmatched entry type + i++; + continue; + } + + if (typeof name !== "undefined" && performanceTimeline[i].name !== name) { + // unmatched name + i++; + continue; + } + + // this entry matches our criteria, remove just it + performanceTimeline.splice(i, 1); + } + }; + + if (typeof window.performance.getEntries !== "function" || hasNativeGetEntriesButNotUserTiming) { + var origGetEntries = window.performance.getEntries; + + /** + * Gets all entries from the Performance Timeline. + * http://www.w3.org/TR/performance-timeline/#dom-performance-getentries + * + * NOTE: This will only ever return marks and measures. + * + * @returns {PerformanceEntry[]} Array of PerformanceEntrys + */ + window.performance.getEntries = function() { + ensurePerformanceTimelineOrder(); + + // get a copy of all of our entries + var entries = performanceTimeline.slice(0); + + // if there was a native version of getEntries, add that + if (hasNativeGetEntriesButNotUserTiming && origGetEntries) { + // merge in native + Array.prototype.push.apply(entries, origGetEntries.call(window.performance)); + + // sort by startTime + entries.sort(function(a, b) { + return a.startTime - b.startTime; + }); + } + + return entries; + }; + } + + if (typeof window.performance.getEntriesByType !== "function" || hasNativeGetEntriesButNotUserTiming) { + var origGetEntriesByType = window.performance.getEntriesByType; + + /** + * Gets all entries from the Performance Timeline of the specified type. + * http://www.w3.org/TR/performance-timeline/#dom-performance-getentriesbytype + * + * NOTE: This will only work for marks and measures. + * + * @param {string} entryType Entry type (eg "mark" or "measure") + * + * @returns {PerformanceEntry[]} Array of PerformanceEntrys + */ + window.performance.getEntriesByType = function(entryType) { + // we only support marks/measures + if (typeof entryType === "undefined" || + (entryType !== "mark" && entryType !== "measure")) { + + if (hasNativeGetEntriesButNotUserTiming && origGetEntriesByType) { + // native version exists, forward + return origGetEntriesByType.call(window.performance, entryType); + } + + return []; + } + + // see note in ensurePerformanceTimelineOrder() on why this is required + if (entryType === "measure") { + ensurePerformanceTimelineOrder(); + } + + // find all entries of entryType + var entries = []; + for (i = 0; i < performanceTimeline.length; i++) { + if (performanceTimeline[i].entryType === entryType) { + entries.push(performanceTimeline[i]); + } + } + + return entries; + }; + } + + if (typeof window.performance.getEntriesByName !== "function" || hasNativeGetEntriesButNotUserTiming) { + var origGetEntriesByName = window.performance.getEntriesByName; + + /** + * Gets all entries from the Performance Timeline of the specified + * name, and optionally, type. + * http://www.w3.org/TR/performance-timeline/#dom-performance-getentriesbyname + * + * NOTE: This will only work for marks and measures. + * + * @param {string} name Entry name + * @param {string} [entryType] Entry type (eg "mark" or "measure") + * + * @returns {PerformanceEntry[]} Array of PerformanceEntrys + */ + window.performance.getEntriesByName = function(name, entryType) { + if (entryType && entryType !== "mark" && entryType !== "measure") { + if (hasNativeGetEntriesButNotUserTiming && origGetEntriesByName) { + // native version exists, forward + return origGetEntriesByName.call(window.performance, name, entryType); + } + + return []; + } + + // see note in ensurePerformanceTimelineOrder() on why this is required + if (typeof entryType !== "undefined" && entryType === "measure") { + ensurePerformanceTimelineOrder(); + } + + // find all entries of the name and (optionally) type + var entries = []; + for (i = 0; i < performanceTimeline.length; i++) { + if (typeof entryType !== "undefined" && + performanceTimeline[i].entryType !== entryType) { + continue; + } + + if (performanceTimeline[i].name === name) { + entries.push(performanceTimeline[i]); + } + } + + if (hasNativeGetEntriesButNotUserTiming && origGetEntriesByName) { + // merge in native + Array.prototype.push.apply(entries, origGetEntriesByName.call(window.performance, name, entryType)); + + // sort by startTime + entries.sort(function(a, b) { + return a.startTime - b.startTime; + }); + } + + return entries; + }; + } + } + + // + // UserTiming support + // + if (typeof window.performance.mark !== "function") { + window.performance.userTimingJsUserTiming = true; + + // copy prefixed version over if it exists + prefixes = ["webkit", "moz", "ms"]; + methods = ["mark", "measure", "clearMarks", "clearMeasures"]; + + for (i = 0; i < methods.length; i++) { + for (j = 0; j < prefixes.length; j++) { + // prefixed method will likely have an upper-case first letter + methodTest = prefixes[j] + methods[i].substr(0, 1).toUpperCase() + methods[i].substr(1); + + if (typeof window.performance[methodTest] === "function") { + window.performance[methods[i]] = window.performance[methodTest]; + + window.performance.userTimingJsUserTimingPrefixed = true; + } + } + } + + // only used for measure(), to quickly see the latest timestamp of a mark + var marks = {}; + + if (typeof window.performance.mark !== "function") { + /** + * UserTiming mark + * http://www.w3.org/TR/user-timing/#dom-performance-mark + * + * @param {string} markName Mark name + */ + window.performance.mark = function(markName) { + var now = window.performance.now(); + + // mark name is required + if (typeof markName === "undefined") { + throw new SyntaxError("Mark name must be specified"); + } + + // mark name can't be a NT timestamp + if (window.performance.timing && markName in window.performance.timing) { + throw new SyntaxError("Mark name is not allowed"); + } + + if (!marks[markName]) { + marks[markName] = []; + } + + marks[markName].push(now); + + // add to perf timeline as well + addToPerformanceTimeline({ + entryType: "mark", + name: markName, + startTime: now, + duration: 0 + }); + }; + } + + if (typeof window.performance.clearMarks !== "function") { + /** + * UserTiming clear marks + * http://www.w3.org/TR/user-timing/#dom-performance-clearmarks + * + * @param {string} markName Mark name + */ + window.performance.clearMarks = function(markName) { + if (!markName) { + // clear all marks + marks = {}; + } else { + marks[markName] = []; + } + + clearEntriesFromPerformanceTimeline("mark", markName); + }; + } + + if (typeof window.performance.measure !== "function") { + /** + * UserTiming measure + * http://www.w3.org/TR/user-timing/#dom-performance-measure + * + * @param {string} measureName Measure name + * @param {string} [startMark] Start mark name + * @param {string} [endMark] End mark name + */ + window.performance.measure = function(measureName, startMark, endMark) { + var now = window.performance.now(); + + if (typeof measureName === "undefined") { + throw new SyntaxError("Measure must be specified"); + } + + // if there isn't a startMark, we measure from navigationStart to now + if (!startMark) { + // add to perf timeline as well + addToPerformanceTimeline({ + entryType: "measure", + name: measureName, + startTime: 0, + duration: now + }); + + return; + } + + // + // If there is a startMark, check for it first in the NavigationTiming interface, + // then check our own marks. + // + var startMarkTime = 0; + if (window.performance.timing && startMark in window.performance.timing) { + // mark cannot have a timing of 0 + if (startMark !== "navigationStart" && window.performance.timing[startMark] === 0) { + throw new Error(startMark + " has a timing of 0"); + } + + // time is the offset of this mark to navigationStart's time + startMarkTime = window.performance.timing[startMark] - window.performance.timing.navigationStart; + } else if (startMark in marks) { + startMarkTime = marks[startMark][marks[startMark].length - 1]; + } else { + throw new Error(startMark + " mark not found"); + } + + // + // If there is a endMark, check for it first in the NavigationTiming interface, + // then check our own marks. + // + var endMarkTime = now; + + if (endMark) { + endMarkTime = 0; + + if (window.performance.timing && endMark in window.performance.timing) { + // mark cannot have a timing of 0 + if (endMark !== "navigationStart" && window.performance.timing[endMark] === 0) { + throw new Error(endMark + " has a timing of 0"); + } + + // time is the offset of this mark to navigationStart's time + endMarkTime = window.performance.timing[endMark] - window.performance.timing.navigationStart; + } else if (endMark in marks) { + endMarkTime = marks[endMark][marks[endMark].length - 1]; + } else { + throw new Error(endMark + " mark not found"); + } + } + + // add to our measure array + var duration = endMarkTime - startMarkTime; + + // add to perf timeline as well + addToPerformanceTimeline({ + entryType: "measure", + name: measureName, + startTime: startMarkTime, + duration: duration + }); + }; + } + + if (typeof window.performance.clearMeasures !== "function") { + /** + * UserTiming clear measures + * http://www.w3.org/TR/user-timing/#dom-performance-clearmeasures + * + * @param {string} measureName Measure name + */ + window.performance.clearMeasures = function(measureName) { + clearEntriesFromPerformanceTimeline("measure", measureName); + }; + } + } + + return window +} + +loadUserTiming = function() { + return userTiming(); +} diff --git a/examples/libraries/usertimingExample.js b/examples/libraries/usertimingExample.js new file mode 100644 index 0000000000..cceb43435a --- /dev/null +++ b/examples/libraries/usertimingExample.js @@ -0,0 +1,18 @@ +Script.include('usertiming.js'); +var timing = loadUserTiming(); +//set a mark +timing.performance.mark('firstMark'); + +//do something that takes time -- we're just going to set a timeout here as an example + +Script.setTimeout(function() { + //and set another mark + timing.performance.mark('secondMark'); + + //measure time between marks (first parameter is a name for the measurement) + timing.performance.measure('howlong', 'firstMark', 'secondMark'); + + //you can also get the marks by changing the type + var measures = timing.performance.getEntriesByType('measure'); + print('measures:::' + JSON.stringify(measures)) +}, 1000) diff --git a/examples/toybox/bubblewand/createWand.js b/examples/toybox/bubblewand/createWand.js index 25649a9aad..d62c2064cf 100644 --- a/examples/toybox/bubblewand/createWand.js +++ b/examples/toybox/bubblewand/createWand.js @@ -13,13 +13,18 @@ Script.include("../../utilities.js"); Script.include("../../libraries/utils.js"); -var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx'; -var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/collisionHull.obj'; +var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/wand.fbx'; +var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/actual_no_top_collision_hull.obj'; + var WAND_SCRIPT_URL = Script.resolvePath("wand.js"); //create the wand in front of the avatar -var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); +var center = Vec3.sum(Vec3.sum(MyAvatar.position, { + x: 0, + y: 0.5, + z: 0 +}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); var wand = Entities.addEntity({ name: 'Bubble Wand', diff --git a/examples/toybox/bubblewand/wand.js b/examples/toybox/bubblewand/wand.js index 707b0fd47f..c8ba51f51d 100644 --- a/examples/toybox/bubblewand/wand.js +++ b/examples/toybox/bubblewand/wand.js @@ -17,7 +17,7 @@ Script.include("../../utilities.js"); Script.include("../../libraries/utils.js"); - var BUBBLE_MODEL = "http://hifi-public.s3.amazonaws.com/james/bubblewand/models/bubble/bubble.fbx"; + var BUBBLE_MODEL = "http://hifi-public.s3.amazonaws.com/models/bubblewand/bubble.fbx"; var BUBBLE_INITIAL_DIMENSIONS = { x: 0.01, diff --git a/examples/toybox/ping_pong_gun/createPingPongGun.js b/examples/toybox/ping_pong_gun/createPingPongGun.js index cfeaba7f4e..9639f75320 100644 --- a/examples/toybox/ping_pong_gun/createPingPongGun.js +++ b/examples/toybox/ping_pong_gun/createPingPongGun.js @@ -14,8 +14,8 @@ Script.include("../../utilities.js"); var scriptURL = Script.resolvePath('pingPongGun.js'); var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx' -var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_collision_hull.obj'; - +var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_convex.obj'; +var COLLISION_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/plastic_impact.L.wav'; var center = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, @@ -25,9 +25,8 @@ var center = Vec3.sum(Vec3.sum(MyAvatar.position, { var pingPongGun = Entities.addEntity({ type: "Model", modelURL: MODEL_URL, - shapeType:'box', - // shapeType: 'compound', - // compoundShapeURL: COLLISION_HULL_URL, + shapeType: 'compound', + compoundShapeURL: COLLISION_HULL_URL, script: scriptURL, position: center, dimensions: { @@ -36,6 +35,7 @@ var pingPongGun = Entities.addEntity({ z: 0.47 }, collisionsWillMove: true, + collisionSoundURL: COLLISION_SOUND_URL }); function cleanUp() { diff --git a/examples/utilities/tools/renderEngineDebug.js b/examples/utilities/tools/renderEngineDebug.js index 3271741985..cca97b7184 100755 --- a/examples/utilities/tools/renderEngineDebug.js +++ b/examples/utilities/tools/renderEngineDebug.js @@ -62,10 +62,24 @@ var overlaysCounter = new CounterWidget(panel, "Overlays", ); -panel.newCheckbox("Display status", - function(value) { Scene.setEngineDisplayItemStatus(value); }, - function() { return Scene.doEngineDisplayItemStatus(); }, - function(value) { return (value); } +// see libraries/render/src/render/Engine.h +var showDisplayStatusFlag = 1; +var showNetworkStatusFlag = 2; + +panel.newCheckbox("Display status", + function(value) { Scene.setEngineDisplayItemStatus(value ? + Scene.doEngineDisplayItemStatus() | showDisplayStatusFlag : + Scene.doEngineDisplayItemStatus() & ~showDisplayStatusFlag); }, + function() { return (Scene.doEngineDisplayItemStatus() & showDisplayStatusFlag) > 0; }, + function(value) { return (value & showDisplayStatusFlag) > 0; } +); + +panel.newCheckbox("Network/Physics status", + function(value) { Scene.setEngineDisplayItemStatus(value ? + Scene.doEngineDisplayItemStatus() | showNetworkStatusFlag : + Scene.doEngineDisplayItemStatus() & ~showNetworkStatusFlag); }, + function() { return (Scene.doEngineDisplayItemStatus() & showNetworkStatusFlag) > 0; }, + function(value) { return (value & showNetworkStatusFlag) > 0; } ); var tickTackPeriod = 500; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 930bdbd7ce..98a9dad909 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -102,7 +102,7 @@ endif() # link required hifi libraries link_hifi_libraries(shared octree environment gpu gl procedural model render - fbx networking model-networking entities avatars + recording fbx networking model-networking entities avatars audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater controllers plugins display-plugins input-plugins ) diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 5de188c0b6..8a24543b74 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -14,8 +14,17 @@ ] }, { "from": "Standard.RX", "to": "Actions.Yaw" }, - { "from": "Standard.RY", "when": "!Application.InHMD", "to": "Actions.Pitch" }, - + + { "from": "Standard.RY", + "when": "Application.Grounded", + "to": "Actions.Up", + "filters": + [ + { "type": "deadZone", "min": 0.95 }, + "invert" + ] + }, + { "from": "Standard.RY", "to": "Actions.Up", "filters": "invert"}, { "from": [ "Standard.DU", "Standard.DL", "Standard.DR", "Standard.DD" ], "to": "Standard.LeftPrimaryThumb" }, { "from": "Standard.Back", "to": "Standard.LeftSecondaryThumb" }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 718d06c6e5..dd99599d96 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -646,11 +646,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _applicationStateDevice->addInputVariant(QString("ComfortMode"), controller::StateController::ReadLambda([]() -> float { return (float)Menu::getInstance()->isOptionChecked(MenuOption::ComfortMode); })); + _applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float { + return (float)qApp->getMyAvatar()->getCharacterController()->onGround(); + })); userInputMapper->registerDevice(_applicationStateDevice); // Setup the keyboardMouseDevice and the user input mapper with the default bindings - userInputMapper->registerDevice(_keyboardMouseDevice); + userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); userInputMapper->loadDefaultMapping(userInputMapper->getStandardDeviceID()); @@ -726,8 +729,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Now that menu is initalized we can sync myAvatar with it's state. getMyAvatar()->updateMotionBehaviorFromMenu(); +// FIXME spacemouse code still needs cleanup +#if 0 // the 3Dconnexion device wants to be initiliazed after a window is displayed. SpacemouseManager::getInstance().init(); +#endif auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket"); @@ -1027,10 +1033,7 @@ void Application::initializeUi() { foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { QString name = inputPlugin->getName(); if (name == KeyboardMouseDevice::NAME) { - auto kbm = static_cast(inputPlugin.data()); - // FIXME incredibly evil.... _keyboardMouseDevice is now owned by - // both a QSharedPointer and a std::shared_ptr - _keyboardMouseDevice = std::shared_ptr(kbm); + _keyboardMouseDevice = std::dynamic_pointer_cast(inputPlugin); } } updateInputModes(); @@ -1850,9 +1853,12 @@ void Application::focusOutEvent(QFocusEvent* event) { } } +// FIXME spacemouse code still needs cleanup +#if 0 //SpacemouseDevice::getInstance().focusOutEvent(); //SpacemouseManager::getInstance().getDevice()->focusOutEvent(); SpacemouseManager::getInstance().ManagerFocusOutEvent(); +#endif // synthesize events for keys currently pressed, since we may not get their release events foreach (int key, _keysPressed) { @@ -3493,10 +3499,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls)) { renderDebugFlags = (RenderArgs::DebugFlags) (renderDebugFlags | (int)RenderArgs::RENDER_DEBUG_HULLS); } - if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowOwned)) { - renderDebugFlags = - (RenderArgs::DebugFlags) (renderDebugFlags | (int)RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP); - } renderArgs->_debugFlags = renderDebugFlags; //ViveControllerManager::getInstance().updateRendering(renderArgs, _main3DScene, pendingChanges); } @@ -3556,6 +3558,9 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se renderContext._maxDrawnOverlay3DItems = sceneInterface->getEngineMaxDrawnOverlay3DItems(); renderContext._drawItemStatus = sceneInterface->doEngineDisplayItemStatus(); + if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowOwned)) { + renderContext._drawItemStatus |= render::showNetworkStatusFlag; + } renderContext._drawHitEffect = sceneInterface->doEngineDisplayHitEffect(); renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion); @@ -4645,7 +4650,7 @@ DisplayPlugin* Application::getActiveDisplayPlugin() { updateDisplayMode(); Q_ASSERT(_displayPlugin); } - return _displayPlugin.data(); + return _displayPlugin.get(); } const DisplayPlugin* Application::getActiveDisplayPlugin() const { @@ -4685,10 +4690,10 @@ void Application::updateDisplayMode() { bool first = true; foreach(auto displayPlugin, displayPlugins) { addDisplayPluginToMenu(displayPlugin, first); - QObject::connect(displayPlugin.data(), &DisplayPlugin::requestRender, [this] { + QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, [this] { paintGL(); }); - QObject::connect(displayPlugin.data(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) { + QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) { resizeGL(); }); @@ -4814,12 +4819,14 @@ void Application::updateInputModes() { foreach(auto inputPlugin, inputPlugins) { QString name = inputPlugin->getName(); QAction* action = menu->getActionForOption(name); - if (action->isChecked() && !_activeInputPlugins.contains(inputPlugin)) { - _activeInputPlugins.append(inputPlugin); - newInputPlugins.append(inputPlugin); - } else if (!action->isChecked() && _activeInputPlugins.contains(inputPlugin)) { - _activeInputPlugins.removeOne(inputPlugin); - removedInputPlugins.append(inputPlugin); + + auto it = std::find(std::begin(_activeInputPlugins), std::end(_activeInputPlugins), inputPlugin); + if (action->isChecked() && it == std::end(_activeInputPlugins)) { + _activeInputPlugins.push_back(inputPlugin); + newInputPlugins.push_back(inputPlugin); + } else if (!action->isChecked() && it != std::end(_activeInputPlugins)) { + _activeInputPlugins.erase(it); + removedInputPlugins.push_back(inputPlugin); } } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 162b713948..24033325f6 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -44,22 +44,11 @@ #include "Menu.h" -Menu* Menu::_instance = NULL; +static const char* const MENU_PROPERTY_NAME = "com.highfidelity.Menu"; Menu* Menu::getInstance() { - static QMutex menuInstanceMutex; - - // lock the menu instance mutex to make sure we don't race and create two menus and crash - menuInstanceMutex.lock(); - - if (!_instance) { - qCDebug(interfaceapp, "First call to Menu::getInstance() - initing menu."); - _instance = new Menu(); - } - - menuInstanceMutex.unlock(); - - return _instance; + static Menu* instance = globalInstance(MENU_PROPERTY_NAME); + return instance; } Menu::Menu() { @@ -465,8 +454,6 @@ Menu::Menu() { avatar, SLOT(setEnableMeshVisible(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Connexion, 0, false, &SpacemouseManager::getInstance(), SLOT(toggleSpacemouse(bool))); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ComfortMode, 0, true); MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 162fad1b9f..dfa2cfa41b 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -57,6 +57,7 @@ private: class Menu : public QMenuBar { Q_OBJECT public: + Menu(); static Menu* getInstance(); void loadSettings(); @@ -103,9 +104,6 @@ public slots: void setIsOptionChecked(const QString& menuOption, bool isChecked); private: - static Menu* _instance; - Menu(); - typedef void(*settingsAction)(Settings&, QAction&); static void loadAction(Settings& settings, QAction& action); static void saveAction(Settings& settings, QAction& action); diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 70216eca11..866f444b32 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -107,8 +107,12 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { withWriteLock([&]{ if (_kinematicSetVelocity) { if (_previousSet) { - glm::vec3 positionalVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep; + // smooth velocity over 2 frames + glm::vec3 positionalDelta = _positionalTarget - _previousPositionalTarget; + glm::vec3 positionalVelocity = (positionalDelta + _previousPositionalDelta) / (deltaTimeStep + _previousDeltaTimeStep); rigidBody->setLinearVelocity(glmToBullet(positionalVelocity)); + _previousPositionalDelta = positionalDelta; + _previousDeltaTimeStep = deltaTimeStep; } } diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index 6badf97e9e..15a096d1ce 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -30,7 +30,7 @@ public: QByteArray serialize() const; virtual void deserialize(QByteArray serializedArguments); - virtual bool shouldSuppressLocationEdits() { return true; } + virtual bool shouldSuppressLocationEdits() { return _active && !_ownerEntity.expired(); } private: static const uint16_t holdVersion; @@ -46,6 +46,9 @@ private: bool _previousSet { false }; glm::vec3 _previousPositionalTarget; glm::quat _previousRotationalTarget; + + float _previousDeltaTimeStep = 0.0f; + glm::vec3 _previousPositionalDelta; }; #endif // hifi_AvatarActionHold_h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 57af9e732d..8595fa850e 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1608,7 +1608,6 @@ void MyAvatar::updateOrientation(float deltaTime) { // Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll // get an instantaneous 15 degree turn. If you keep holding the key down you'll get another // snap turn every half second. - quint64 now = usecTimestampNow(); if (_driveKeys[STEP_YAW] != 0.0f) { totalBodyYaw += _driveKeys[STEP_YAW]; } @@ -1676,8 +1675,6 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f); glm::vec3 newLocalVelocity = localVelocity; - float stepControllerInput = fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]); - quint64 now = usecTimestampNow(); // FIXME how do I implement step translation as well? diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 2a2a45b67b..5b7bbc2aba 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -473,18 +473,7 @@ void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) { continue; } - int controllerButtons = 0; - - //Check for if we should toggle or drag the magnification window - if (controllerButtons & BUTTON_3) { - if (isPressed[index] == false) { - //We are now dragging the window - isPressed[index] = true; - //set the pressed time in us - pressedTime[index] = usecTimestampNow(); - stateWhenPressed[index] = _magActive[index]; - } - } else if (isPressed[index]) { + if (isPressed[index]) { isPressed[index] = false; //If the button was only pressed for < 250 ms //then disable it. diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 42e9472819..d5728b79b4 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -156,7 +156,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vectorgetParentIndex(tipIndex); - if (pivotIndex == -1) { + if (pivotIndex == -1 || pivotIndex == _hipsIndex) { continue; } int pivotsParentIndex = _skeleton->getParentIndex(pivotIndex); @@ -173,7 +173,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector= _jointStates.size()) { return false; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 3abd63bf63..9079f15f53 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -240,7 +240,8 @@ public: Q_INVOKABLE void setHandState(char s) { _handState = s; } Q_INVOKABLE char getHandState() const { return _handState; } - const QVector& getJointData() const { return _jointData; } + const QVector& getRawJointData() const { return _jointData; } + void setRawJointData(QVector data) { _jointData = data; } Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation); Q_INVOKABLE virtual void setJointRotation(int index, const glm::quat& rotation); diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index a62172a730..bc4b0469f5 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -206,7 +206,7 @@ namespace controller { void ScriptingInterface::updateMaps() { QVariantMap newHardware; auto userInputMapper = DependencyManager::get(); - auto devices = userInputMapper->getDevices(); + const auto& devices = userInputMapper->getDevices(); for (const auto& deviceMapping : devices) { auto deviceID = deviceMapping.first; if (deviceID != userInputMapper->getStandardDeviceID()) { diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index c1dfcf5d33..d93a93016c 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -100,7 +100,7 @@ namespace controller { void setSensorToWorldMat(glm::mat4 sensorToWorldMat) { _sensorToWorldMat = sensorToWorldMat; } glm::mat4 getSensorToWorldMat() { return _sensorToWorldMat; } - DevicesMap getDevices() { return _registeredDevices; } + const DevicesMap& getDevices() { return _registeredDevices; } uint16 getStandardDeviceID() const { return STANDARD_DEVICE; } InputDevice::Pointer getStandardDevice() { return _registeredDevices[getStandardDeviceID()]; } diff --git a/libraries/display-plugins/src/display-plugins/Logging.cpp b/libraries/display-plugins/src/display-plugins/Logging.cpp new file mode 100644 index 0000000000..00cd4cf0f9 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/Logging.cpp @@ -0,0 +1,11 @@ +// +// Created by Bradley Austin Davis 2015/10/11 +// Copyright 2015 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 +// + +#include "Logging.h" + +Q_LOGGING_CATEGORY(displayPlugins, "hifi.plugins.display") diff --git a/libraries/display-plugins/src/display-plugins/Logging.h b/libraries/display-plugins/src/display-plugins/Logging.h new file mode 100644 index 0000000000..79b20b5dcc --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/Logging.h @@ -0,0 +1,16 @@ +// +// Created by Bradley Austin Davis 2015/10/11 +// Copyright 2015 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 +// + +#ifndef hifi_DisplayPlugins_Logging_h +#define hifi_DisplayPlugins_Logging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(displayPlugins) + +#endif diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp index 174bf1bf36..bb39c7bb7a 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp @@ -37,8 +37,6 @@ const QString & OpenVrDisplayPlugin::getName() const { return NAME; } -vr::IVRSystem* _hmd{ nullptr }; -int hmdRefCount = 0; static vr::IVRCompositor* _compositor{ nullptr }; vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount]; @@ -78,24 +76,17 @@ mat4 toGlm(const vr::HmdMatrix34_t& m) { } bool OpenVrDisplayPlugin::isSupported() const { - bool success = vr::VR_IsHmdPresent(); - if (success) { - vr::HmdError eError = vr::HmdError_None; - auto hmd = vr::VR_Init(&eError); - success = (hmd != nullptr); - vr::VR_Shutdown(); - } + auto hmd = acquireOpenVrSystem(); + bool success = nullptr != hmd; + releaseOpenVrSystem(); return success; } void OpenVrDisplayPlugin::activate() { _container->setIsOptionChecked(StandingHMDSensorMode, true); - hmdRefCount++; - vr::HmdError eError = vr::HmdError_None; if (!_hmd) { - _hmd = vr::VR_Init(&eError); - Q_ASSERT(eError == vr::HmdError_None); + _hmd = acquireOpenVrSystem(); } Q_ASSERT(_hmd); @@ -114,6 +105,7 @@ void OpenVrDisplayPlugin::activate() { }); + vr::HmdError eError = vr::HmdError_None; _compositor = (vr::IVRCompositor*)vr::VR_GetGenericInterface(vr::IVRCompositor_Version, &eError); Q_ASSERT(eError == vr::HmdError_None); Q_ASSERT(_compositor); @@ -133,11 +125,8 @@ void OpenVrDisplayPlugin::activate() { void OpenVrDisplayPlugin::deactivate() { _container->setIsOptionChecked(StandingHMDSensorMode, false); - - hmdRefCount--; - - if (hmdRefCount == 0 && _hmd) { - vr::VR_Shutdown(); + if (_hmd) { + releaseOpenVrSystem(); _hmd = nullptr; } _compositor = nullptr; diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h index 7849623552..15d37d9de8 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h @@ -10,6 +10,7 @@ #include #if defined(Q_OS_WIN) +#include #include "../WindowOpenGLDisplayPlugin.h" @@ -39,6 +40,7 @@ protected: virtual void finishFrame() override; private: + vr::IVRSystem* _hmd { nullptr }; static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.cpp b/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.cpp new file mode 100644 index 0000000000..f8e810beaf --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.cpp @@ -0,0 +1,75 @@ +// +// Created by Bradley Austin Davis on 2015/11/01 +// Copyright 2015 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 +// +#include "OpenVrHelpers.h" + +#if defined(Q_OS_WIN) + +#include + +#include +#include + +#include "../Logging.h" + +using Mutex = std::mutex; +using Lock = std::unique_lock; + +static int refCount { 0 }; +static Mutex mutex; +static vr::IVRSystem* activeHmd { nullptr }; +static bool hmdPresent = vr::VR_IsHmdPresent(); + +static const uint32_t RELEASE_OPENVR_HMD_DELAY_MS = 5000; + +vr::IVRSystem* acquireOpenVrSystem() { + if (hmdPresent) { + Lock lock(mutex); + if (!activeHmd) { + qCDebug(displayPlugins) << "openvr: No vr::IVRSystem instance active, building"; + vr::HmdError eError = vr::HmdError_None; + activeHmd = vr::VR_Init(&eError); + qCDebug(displayPlugins) << "openvr display: HMD is " << activeHmd << " error is " << eError; + } + if (activeHmd) { + qCDebug(displayPlugins) << "openvr: incrementing refcount"; + ++refCount; + } + } + return activeHmd; +} + +void releaseOpenVrSystem() { + if (activeHmd) { + Lock lock(mutex); + qDebug() << "openvr: decrementing refcount"; + --refCount; + if (0 == refCount) { + qDebug() << "openvr: zero refcount, deallocate VR system"; + // Avoid spamming the VR system with activate/deactivate calls at system startup by + // putting in a delay before we destory the shutdown the VR subsystem + + // FIXME releasing the VR system at all seems to trigger an exception deep inside the Oculus DLL. + // disabling for now. + //QTimer* releaseTimer = new QTimer(); + //releaseTimer->singleShot(RELEASE_OPENVR_HMD_DELAY_MS, [releaseTimer] { + // Lock lock(mutex); + // qDebug() << "Delayed openvr destroy activated"; + // if (0 == refCount && nullptr != activeHmd) { + // qDebug() << "Delayed openvr destroy: releasing resources"; + // activeHmd = nullptr; + // vr::VR_Shutdown(); + // } else { + // qDebug() << "Delayed openvr destroy: HMD still in use"; + // } + // releaseTimer->deleteLater(); + //}); + } + } +} + +#endif diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.h b/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.h index 761bef8cfc..3e445d90ba 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.h +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.h @@ -7,10 +7,16 @@ // #pragma once +#include + #if defined(Q_OS_WIN) #include #include #include #include + +vr::IVRSystem* acquireOpenVrSystem(); +void releaseOpenVrSystem(); + #endif diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 8543c00eec..d33e52fa6b 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -227,7 +227,7 @@ bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_p // note: we don't care if the model fails to add items, we always added our meta item and therefore we return // true so that the system knows our meta item is in the scene! - _model->addToScene(scene, pendingChanges, statusGetters); + _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); } return true; @@ -259,14 +259,16 @@ void RenderableModelEntityItem::render(RenderArgs* args) { // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene - if (_model->needsFixupInScene()) { + bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0; + if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) { + _showCollisionHull = shouldShowCollisionHull; render::PendingChanges pendingChanges; _model->removeFromScene(scene, pendingChanges); render::Item::Status::Getters statusGetters; makeEntityItemStatusGetters(this, statusGetters); - _model->addToScene(scene, pendingChanges, statusGetters); + _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); scene->enqueuePendingChanges(pendingChanges); } @@ -288,7 +290,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { EntityTreeRenderer* renderer = static_cast(args->_renderer); getModel(renderer); } - + if (_model) { // handle animations.. if (hasAnimation()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 04a1694dd3..c4e36c240a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -81,6 +81,8 @@ private: bool _dimensionsInitialized = true; render::ItemID _myMetaItem; + + bool _showCollisionHull = false; }; #endif // hifi_RenderableModelEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index a7bdffc020..41cf3b9bbf 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -23,6 +23,7 @@ #include "untextured_particle_frag.h" #include "textured_particle_vert.h" #include "textured_particle_frag.h" +#include "textured_particle_alpha_discard_frag.h" class ParticlePayload { public: @@ -114,8 +115,7 @@ namespace render { } } -gpu::PipelinePointer RenderableParticleEffectEntityItem::_texturedPipeline; -gpu::PipelinePointer RenderableParticleEffectEntityItem::_untexturedPipeline; + EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return std::make_shared(entityID, properties); @@ -203,19 +203,25 @@ void RenderableParticleEffectEntityItem::updateRenderItem() { // sort particles back to front // NOTE: this is view frustum might be one frame out of date. + auto frustum = AbstractViewStateInterface::instance()->getCurrentViewFrustum(); - ::zSortAxis = frustum->getDirection(); - qSort(particleDetails.begin(), particleDetails.end(), zSort); + + // No need to sort if we're doing additive blending + if (_additiveBlending != true) { + ::zSortAxis = frustum->getDirection(); + qSort(particleDetails.begin(), particleDetails.end(), zSort); + } + + // allocate vertices _vertices.clear(); // build vertices from particle positions and radiuses - glm::vec3 frustumPosition = frustum->getPosition(); + glm::vec3 dir = frustum->getDirection(); for (auto&& particle : particleDetails) { - glm::vec3 particleDirection = particle.position - frustumPosition; - glm::vec3 right = glm::normalize(glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), particleDirection)); - glm::vec3 up = glm::normalize(glm::cross(right, particleDirection)); + glm::vec3 right = glm::normalize(glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), dir)); + glm::vec3 up = glm::normalize(glm::cross(right, dir)); glm::vec3 upOffset = up * particle.radius; glm::vec3 rightOffset = right * particle.radius; @@ -309,12 +315,21 @@ void RenderableParticleEffectEntityItem::updateRenderItem() { } void RenderableParticleEffectEntityItem::createPipelines() { + bool writeToDepthBuffer = false; + gpu::State::BlendArg destinationColorBlendArg; + if (_additiveBlending) { + destinationColorBlendArg = gpu::State::ONE; + } + else { + destinationColorBlendArg = gpu::State::INV_SRC_ALPHA; + writeToDepthBuffer = true; + } if (!_untexturedPipeline) { auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setDepthTest(true, writeToDepthBuffer, gpu::LESS_EQUAL); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, - gpu::State::ONE, gpu::State::FACTOR_ALPHA, + destinationColorBlendArg, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(untextured_particle_vert))); auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(untextured_particle_frag))); @@ -324,13 +339,24 @@ void RenderableParticleEffectEntityItem::createPipelines() { if (!_texturedPipeline) { auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, true, gpu::LESS_EQUAL); + + + bool writeToDepthBuffer = !_additiveBlending; + state->setDepthTest(true, writeToDepthBuffer, gpu::LESS_EQUAL); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, - gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, + destinationColorBlendArg, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(textured_particle_vert))); - auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(textured_particle_frag))); + gpu::ShaderPointer fragShader; + if (_additiveBlending) { + fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(textured_particle_frag))); + } + else { + //If we are sorting and have no additive blending, we want to discard pixels with low alpha to avoid inter-particle entity artifacts + fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(textured_particle_alpha_discard_frag))); + } auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader)); _texturedPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + } } diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 5d69d19026..678f7eb904 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -38,11 +38,11 @@ protected: uint32_t rgba; }; - static void createPipelines(); + void createPipelines(); std::vector _vertices; - static gpu::PipelinePointer _untexturedPipeline; - static gpu::PipelinePointer _texturedPipeline; + gpu::PipelinePointer _untexturedPipeline; + gpu::PipelinePointer _texturedPipeline; render::ScenePointer _scene; NetworkTexturePointer _texture; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index ff56bef46b..62d98f3322 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -112,7 +112,7 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); render::PendingChanges pendingChanges; _model->removeFromScene(scene, pendingChanges); - _model->addToScene(scene, pendingChanges); + _model->addToScene(scene, pendingChanges, false); scene->enqueuePendingChanges(pendingChanges); diff --git a/libraries/entities-renderer/src/textured_particle_alpha_discard.slf b/libraries/entities-renderer/src/textured_particle_alpha_discard.slf new file mode 100644 index 0000000000..389744449a --- /dev/null +++ b/libraries/entities-renderer/src/textured_particle_alpha_discard.slf @@ -0,0 +1,25 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// fragment shader +// +// Copyright 2015 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 +// + +uniform sampler2D colorMap; + +in vec4 _color; +in vec2 _texCoord0; + +out vec4 outFragColor; + +void main(void) { + vec4 color = texture(colorMap, _texCoord0); + if (color.a < 0.1) { + discard; + } + outFragColor = color * _color; +} diff --git a/libraries/entities/src/EntitiesScriptEngineProvider.h b/libraries/entities/src/EntitiesScriptEngineProvider.h index d112a6c0f9..69bf73e688 100644 --- a/libraries/entities/src/EntitiesScriptEngineProvider.h +++ b/libraries/entities/src/EntitiesScriptEngineProvider.h @@ -19,7 +19,7 @@ class EntitiesScriptEngineProvider { public: - virtual void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) = 0; + virtual void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList()) = 0; }; #endif // hifi_EntitiesScriptEngineProvider_h \ No newline at end of file diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 002f1bb527..4f5d256969 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -195,6 +195,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_ALPHA_SPREAD, alphaSpread); CHECK_PROPERTY_CHANGE(PROP_ALPHA_START, alphaStart); CHECK_PROPERTY_CHANGE(PROP_ALPHA_FINISH, alphaFinish); + CHECK_PROPERTY_CHANGE(PROP_ADDITIVE_BLENDING, additiveBlending); CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL); CHECK_PROPERTY_CHANGE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible); @@ -351,6 +352,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_SPREAD, alphaSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ADDITIVE_BLENDING, additiveBlending); + } // Models only @@ -502,6 +505,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaSpread, float, setAlphaSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaStart, float, setAlphaStart); COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaFinish, float, setAlphaFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(additiveBlending, bool, setAdditiveBlending); COPY_PROPERTY_FROM_QSCRIPTVALUE(modelURL, QString, setModelURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(compoundShapeURL, QString, setCompoundShapeURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(glowLevel, float, setGlowLevel); @@ -650,6 +654,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_ALPHA_SPREAD, AlphaSpread, alphaSpread, float); ADD_PROPERTY_TO_MAP(PROP_ALPHA_START, AlphaStart, alphaStart, float); ADD_PROPERTY_TO_MAP(PROP_ALPHA_FINISH, AlphaFinish, alphaFinish, float); + ADD_PROPERTY_TO_MAP(PROP_ADDITIVE_BLENDING, AdditiveBlending, additiveBlending, bool); ADD_PROPERTY_TO_MAP(PROP_MODEL_URL, ModelURL, modelURL, QString); ADD_PROPERTY_TO_MAP(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString); ADD_PROPERTY_TO_MAP(PROP_REGISTRATION_POINT, RegistrationPoint, registrationPoint, glm::vec3); @@ -959,6 +964,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_ALPHA_SPREAD, properties.getAlphaSpread()); APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, properties.getAlphaStart()); APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, properties.getAlphaFinish()); + APPEND_ENTITY_PROPERTY(PROP_ADDITIVE_BLENDING, properties.getAdditiveBlending()); } if (properties.getType() == EntityTypes::Zone) { @@ -1241,6 +1247,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_SPREAD, float, setAlphaSpread); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_START, float, setAlphaStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_FINISH, float, setAlphaFinish); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ADDITIVE_BLENDING, bool, setAdditiveBlending); } if (properties.getType() == EntityTypes::Zone) { @@ -1579,6 +1586,9 @@ QList EntityItemProperties::listChangedProperties() { if (alphaFinishChanged()) { out += "alphaFinish"; } + if (additiveBlendingChanged()) { + out += "additiveBlending"; + } if (modelURLChanged()) { out += "modelURL"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index b95f4d35f4..84a5aeca5d 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -159,6 +159,7 @@ public: DEFINE_PROPERTY(PROP_RADIUS_SPREAD, RadiusSpread, radiusSpread, float, ParticleEffectEntityItem::DEFAULT_RADIUS_SPREAD); DEFINE_PROPERTY(PROP_RADIUS_START, RadiusStart, radiusStart, float, ParticleEffectEntityItem::DEFAULT_RADIUS_START); DEFINE_PROPERTY(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float, ParticleEffectEntityItem::DEFAULT_RADIUS_FINISH); + DEFINE_PROPERTY(PROP_ADDITIVE_BLENDING, AdditiveBlending, additiveBlending, bool, ParticleEffectEntityItem::DEFAULT_ADDITIVE_BLENDING); DEFINE_PROPERTY_REF(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString, ENTITY_ITEM_DEFAULT_MARKETPLACE_ID); DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup); DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 65060c8d45..e5fa2983e2 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -149,6 +149,8 @@ enum EntityPropertyList { PROP_ANIMATION_HOLD, PROP_ANIMATION_START_AUTOMATICALLY, + PROP_ADDITIVE_BLENDING, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 01d46e0a91..8ca0e9b5fa 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -217,14 +217,13 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { } } -void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method) { +void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method, const QStringList& params) { if (_entitiesScriptEngine) { EntityItemID entityID{ id }; - _entitiesScriptEngine->callEntityScriptMethod(entityID, method); + _entitiesScriptEngine->callEntityScriptMethod(entityID, method, params); } } - QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { EntityItemID result; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 156f16cf46..8a4414a596 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -15,6 +15,7 @@ #define hifi_EntityScriptingInterface_h #include +#include #include #include @@ -93,7 +94,7 @@ public slots: /// Allows a script to call a method on an entity's script. The method will execute in the entity script /// engine. If the entity does not have an entity script or the method does not exist, this call will have /// no effect. - Q_INVOKABLE void callEntityMethod(QUuid entityID, const QString& method); + Q_INVOKABLE void callEntityMethod(QUuid entityID, const QString& method, const QStringList& params = QStringList()); /// finds the closest model to the center point, within the radius /// will return a EntityItemID.isKnownID = false if no models are in the radius diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 24c13ae28e..8e32158362 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -611,6 +611,16 @@ EntityItemPointer EntityTree::findEntityByEntityItemID(const EntityItemID& entit return foundEntity; } +void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties) { + if (properties.simulationOwnerChanged()) { + int simIndex = changedProperties.indexOf("simulationOwner"); + if (simIndex >= 0) { + SimulationOwner simOwner = properties.getSimulationOwner(); + changedProperties[simIndex] = QString("simulationOwner:") + QString::number((int)simOwner.getPriority()); + } + } +} + int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) { @@ -661,7 +671,9 @@ int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* edi qCDebug(entities) << " properties:" << properties; } if (wantTerseEditLogging()) { - qCDebug(entities) << "edit" << entityItemID.toString() << properties.listChangedProperties(); + QList changedProperties = properties.listChangedProperties(); + fixupTerseEditLogging(properties, changedProperties); + qCDebug(entities) << "edit" << entityItemID.toString() << changedProperties; } endLogging = usecTimestampNow(); @@ -689,7 +701,9 @@ int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* edi qCDebug(entities) << " properties:" << properties; } if (wantTerseEditLogging()) { - qCDebug(entities) << "add" << entityItemID.toString() << properties.listChangedProperties(); + QList changedProperties = properties.listChangedProperties(); + fixupTerseEditLogging(properties, changedProperties); + qCDebug(entities) << "add" << entityItemID.toString() << changedProperties; } endLogging = usecTimestampNow(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 1957787a60..c177840199 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -77,6 +77,7 @@ public: virtual bool canProcessVersion(PacketVersion thisVersion) const { return thisVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS; } virtual bool handlesEditPacketType(PacketType packetType) const; + void fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties); virtual int processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 57f49f2354..7ada138d02 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -18,6 +18,7 @@ #include "EntityItemProperties.h" #include "EntityTree.h" #include "EntityTreeElement.h" +#include "EntityTypes.h" EntityTreeElement::EntityTreeElement(unsigned char* octalCode) : OctreeElement() { init(octalCode); @@ -591,7 +592,8 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con } } else { // if the entity type doesn't support a detailed intersection, then just return the non-AABox results - if (localDistance < distance) { + // Never intersect with particle effect entities + if (localDistance < distance && EntityTypes::getEntityTypeName(entity->getType()) != "ParticleEffect") { distance = localDistance; face = localFace; surfaceNormal = localSurfaceNormal; diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index ddd79375b3..263d7dce0c 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -94,6 +94,7 @@ const float ParticleEffectEntityItem::DEFAULT_RADIUS_SPREAD = 0.0f; const float ParticleEffectEntityItem::DEFAULT_RADIUS_START = DEFAULT_PARTICLE_RADIUS; const float ParticleEffectEntityItem::DEFAULT_RADIUS_FINISH = DEFAULT_PARTICLE_RADIUS; const QString ParticleEffectEntityItem::DEFAULT_TEXTURES = ""; +const bool ParticleEffectEntityItem::DEFAULT_ADDITIVE_BLENDING = false; EntityItemPointer ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { @@ -121,7 +122,8 @@ ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityIte _alphaMiddles(DEFAULT_MAX_PARTICLES, DEFAULT_ALPHA), _alphaFinishes(DEFAULT_MAX_PARTICLES, DEFAULT_ALPHA), _particleMaxBound(glm::vec3(1.0f, 1.0f, 1.0f)), - _particleMinBound(glm::vec3(-1.0f, -1.0f, -1.0f)) + _particleMinBound(glm::vec3(-1.0f, -1.0f, -1.0f)) , + _additiveBlending(DEFAULT_ADDITIVE_BLENDING) { _type = EntityTypes::ParticleEffect; @@ -355,6 +357,8 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(EntityPropertyFlags COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaStart, getAlphaStart); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaFinish, getAlphaFinish); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(additiveBlending, getAdditiveBlending); + return properties; } @@ -392,6 +396,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaStart, setAlphaStart); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaFinish, setAlphaFinish); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(additiveBlending, setAdditiveBlending); if (somethingChanged) { bool wantDebug = false; @@ -483,6 +488,10 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, float, setAzimuthFinish); } + if (args.bitstreamVersion >= VERSION_ENTITIES_PARTICLES_ADDITIVE_BLENDING) { + READ_ENTITY_PROPERTY(PROP_ADDITIVE_BLENDING, bool, setAdditiveBlending); + } + return bytesRead; } @@ -520,6 +529,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea requestedProperties += PROP_POLAR_FINISH; requestedProperties += PROP_AZIMUTH_START; requestedProperties += PROP_AZIMUTH_FINISH; + requestedProperties += PROP_ADDITIVE_BLENDING; return requestedProperties; } @@ -562,6 +572,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_POLAR_FINISH, getPolarFinish()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, getAzimuthStart()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, getAzimuthFinish()); + APPEND_ENTITY_PROPERTY(PROP_ADDITIVE_BLENDING, getAdditiveBlending()); } bool ParticleEffectEntityItem::isEmittingParticles() const { diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 029d65cfc0..e3c5cd895a 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -209,6 +209,14 @@ public: } } + static const bool DEFAULT_ADDITIVE_BLENDING; + bool getAdditiveBlending() const { return _additiveBlending; } + void setAdditiveBlending(bool additiveBlending) { + _additiveBlending = additiveBlending; + } + + virtual bool supportsDetailedRayIntersection() const { return false; } + protected: bool isAnimatingSomething() const; @@ -219,7 +227,6 @@ protected: void extendBounds(const glm::vec3& point); void integrateParticle(quint32 index, float deltaTime); quint32 getLivingParticleCount() const; - // the properties of this entity rgbColor _color; xColor _colorStart = DEFAULT_COLOR; @@ -284,6 +291,8 @@ protected: // bounding volume glm::vec3 _particleMaxBound; glm::vec3 _particleMinBound; + + bool _additiveBlending; }; #endif // hifi_ParticleEffectEntityItem_h diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp index 227bd12e1b..6db35572b5 100644 --- a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -29,7 +29,7 @@ InputPluginList getInputPlugins() { InputPluginList result; for (int i = 0; PLUGIN_POOL[i]; ++i) { - InputPlugin * plugin = PLUGIN_POOL[i]; + InputPlugin* plugin = PLUGIN_POOL[i]; if (plugin->isSupported()) { plugin->init(); result.push_back(InputPluginPointer(plugin)); diff --git a/libraries/input-plugins/src/input-plugins/InputPluginsLogging.cpp b/libraries/input-plugins/src/input-plugins/InputPluginsLogging.cpp new file mode 100644 index 0000000000..43a708e5c7 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/InputPluginsLogging.cpp @@ -0,0 +1,14 @@ +// +// InputPluginsLogging.cpp +// libraries/input-plugins/src/input-plugins +// +// Created by Clement on 11/6/15. +// Copyright 2015 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 +// + +#include "InputPluginsLogging.h" + +Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") diff --git a/libraries/input-plugins/src/input-plugins/InputPluginsLogging.h b/libraries/input-plugins/src/input-plugins/InputPluginsLogging.h new file mode 100644 index 0000000000..f82ffdbe2e --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/InputPluginsLogging.h @@ -0,0 +1,18 @@ +// +// InputPluginsLogging.h +// libraries/input-plugins/src/input-plugins +// +// Created by Clement on 11/6/15. +// Copyright 2015 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 +// + +#ifndef hifi_InputPluginsLogging_h +#define hifi_InputPluginsLogging_h + +#include +Q_DECLARE_LOGGING_CATEGORY(inputplugins) + +#endif // hifi_InputPluginsLogging_h diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 2157a3a010..e91ea90aaf 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -19,8 +19,8 @@ const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; -void KeyboardMouseDevice::update(float deltaTime, bool jointsCaptured) { - _axisStateMap.clear(); +void KeyboardMouseDevice::pluginUpdate(float deltaTime, bool jointsCaptured) { + _inputDevice->update(deltaTime, jointsCaptured); // For touch event, we need to check that the last event is not too long ago // Maybe it's a Qt issue, but the touch event sequence (begin, update, end) is not always called properly @@ -35,26 +35,30 @@ void KeyboardMouseDevice::update(float deltaTime, bool jointsCaptured) { } } -void KeyboardMouseDevice::focusOutEvent() { +void KeyboardMouseDevice::InputDevice::update(float deltaTime, bool jointsCaptured) { + _axisStateMap.clear(); +} + +void KeyboardMouseDevice::InputDevice::focusOutEvent() { _buttonPressedMap.clear(); -}; +} void KeyboardMouseDevice::keyPressEvent(QKeyEvent* event) { - auto input = makeInput((Qt::Key) event->key()); - auto result = _buttonPressedMap.insert(input.getChannel()); + auto input = _inputDevice->makeInput((Qt::Key) event->key()); + auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); if (!result.second) { // key pressed again ? without catching the release event ? } } void KeyboardMouseDevice::keyReleaseEvent(QKeyEvent* event) { - auto input = makeInput((Qt::Key) event->key()); - _buttonPressedMap.erase(input.getChannel()); + auto input = _inputDevice->makeInput((Qt::Key) event->key()); + _inputDevice->_buttonPressedMap.erase(input.getChannel()); } void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { - auto input = makeInput((Qt::MouseButton) event->button()); - auto result = _buttonPressedMap.insert(input.getChannel()); + auto input = _inputDevice->makeInput((Qt::MouseButton) event->button()); + auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); if (!result.second) { // key pressed again ? without catching the release event ? } @@ -65,32 +69,32 @@ void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event, unsigned int devic } void KeyboardMouseDevice::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { - auto input = makeInput((Qt::MouseButton) event->button()); - _buttonPressedMap.erase(input.getChannel()); + auto input = _inputDevice->makeInput((Qt::MouseButton) event->button()); + _inputDevice->_buttonPressedMap.erase(input.getChannel()); // if we pressed and released at the same location, then create a "_CLICKED" input for this button // we might want to add some small tolerance to this so if you do a small drag it still counts as // a clicked. if (_mousePressAt == event->pos()) { - _buttonPressedMap.insert(makeInput((Qt::MouseButton) event->button(), true).getChannel()); + _inputDevice->_buttonPressedMap.insert(_inputDevice->makeInput((Qt::MouseButton) event->button(), true).getChannel()); } } void KeyboardMouseDevice::eraseMouseClicked() { - _buttonPressedMap.erase(makeInput(Qt::LeftButton, true).getChannel()); - _buttonPressedMap.erase(makeInput(Qt::MiddleButton, true).getChannel()); - _buttonPressedMap.erase(makeInput(Qt::RightButton, true).getChannel()); + _inputDevice->_buttonPressedMap.erase(_inputDevice->makeInput(Qt::LeftButton, true).getChannel()); + _inputDevice->_buttonPressedMap.erase(_inputDevice->makeInput(Qt::MiddleButton, true).getChannel()); + _inputDevice->_buttonPressedMap.erase(_inputDevice->makeInput(Qt::RightButton, true).getChannel()); } void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { QPoint currentPos = event->pos(); QPoint currentMove = currentPos - _lastCursor; - _axisStateMap[makeInput(MOUSE_AXIS_X_POS).getChannel()] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); - _axisStateMap[makeInput(MOUSE_AXIS_X_NEG).getChannel()] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); + _inputDevice->_axisStateMap[MOUSE_AXIS_X_POS] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); + _inputDevice->_axisStateMap[MOUSE_AXIS_X_NEG] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); // Y mouse is inverted positive is pointing up the screen - _axisStateMap[makeInput(MOUSE_AXIS_Y_POS).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); - _axisStateMap[makeInput(MOUSE_AXIS_Y_NEG).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); + _inputDevice->_axisStateMap[MOUSE_AXIS_Y_POS] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); + _inputDevice->_axisStateMap[MOUSE_AXIS_Y_NEG] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); _lastCursor = currentPos; @@ -100,10 +104,10 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event, unsigned int device void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { auto currentMove = event->angleDelta() / 120.0f; - _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); - _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); - _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); - _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); } glm::vec2 evalAverageTouchPoints(const QList& points) { @@ -138,17 +142,17 @@ void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) { } else { auto currentMove = currentPos - _lastTouch; - _axisStateMap[makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f); - _axisStateMap[makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f); // Y mouse is inverted positive is pointing up the screen - _axisStateMap[makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f); - _axisStateMap[makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f); } _lastTouch = currentPos; } -controller::Input KeyboardMouseDevice::makeInput(Qt::Key code) const { +controller::Input KeyboardMouseDevice::InputDevice::makeInput(Qt::Key code) const { auto shortCode = (uint16_t)(code & KEYBOARD_MASK); if (shortCode != code) { shortCode |= 0x0800; // add this bit instead of the way Qt::Key add a bit on the 3rd byte for some keys @@ -156,7 +160,7 @@ controller::Input KeyboardMouseDevice::makeInput(Qt::Key code) const { return controller::Input(_deviceID, shortCode, controller::ChannelType::BUTTON); } -controller::Input KeyboardMouseDevice::makeInput(Qt::MouseButton code, bool clicked) const { +controller::Input KeyboardMouseDevice::InputDevice::makeInput(Qt::MouseButton code, bool clicked) const { switch (code) { case Qt::LeftButton: return controller::Input(_deviceID, clicked ? MOUSE_BUTTON_LEFT_CLICKED : @@ -172,19 +176,19 @@ controller::Input KeyboardMouseDevice::makeInput(Qt::MouseButton code, bool clic }; } -controller::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::MouseAxisChannel axis) const { +controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevice::MouseAxisChannel axis) const { return controller::Input(_deviceID, axis, controller::ChannelType::AXIS); } -controller::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::TouchAxisChannel axis) const { +controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevice::TouchAxisChannel axis) const { return controller::Input(_deviceID, axis, controller::ChannelType::AXIS); } -controller::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::TouchButtonChannel button) const { +controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevice::TouchButtonChannel button) const { return controller::Input(_deviceID, button, controller::ChannelType::BUTTON); } -controller::Input::NamedVector KeyboardMouseDevice::getAvailableInputs() const { +controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInputs() const { using namespace controller; static QVector availableInputs; static std::once_flag once; @@ -229,7 +233,7 @@ controller::Input::NamedVector KeyboardMouseDevice::getAvailableInputs() const { return availableInputs; } -QString KeyboardMouseDevice::getDefaultMappingConfig() const { +QString KeyboardMouseDevice::InputDevice::getDefaultMappingConfig() const { static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/keyboardMouse.json"; return MAPPING_JSON; } diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index 5d86821db1..4abdc44478 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -24,7 +24,7 @@ class QKeyEvent; class QMouseEvent; class QWheelEvent; -class KeyboardMouseDevice : public InputPlugin, public controller::InputDevice { +class KeyboardMouseDevice : public InputPlugin { Q_OBJECT public: enum KeyboardChannel { @@ -64,22 +64,14 @@ public: TOUCH_BUTTON_PRESS = TOUCH_AXIS_Y_NEG + 1, }; - KeyboardMouseDevice() : InputDevice("Keyboard") {} - // Plugin functions virtual bool isSupported() const override { return true; } virtual bool isJointController() const override { return false; } const QString& getName() const override { return NAME; } - virtual void pluginFocusOutEvent() override { focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override { update(deltaTime, jointsCaptured); } + virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override; - // Device functions - virtual controller::Input::NamedVector getAvailableInputs() const override; - virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, bool jointsCaptured) override; - virtual void focusOutEvent() override; - void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); @@ -94,21 +86,40 @@ public: void wheelEvent(QWheelEvent* event); - // Let's make it easy for Qt because we assume we love Qt forever - controller::Input makeInput(Qt::Key code) const; - controller::Input makeInput(Qt::MouseButton code, bool clicked = false) const; - controller::Input makeInput(MouseAxisChannel axis) const; - controller::Input makeInput(TouchAxisChannel axis) const; - controller::Input makeInput(TouchButtonChannel button) const; - static const QString NAME; +protected: + + class InputDevice : public controller::InputDevice { + public: + InputDevice() : controller::InputDevice("Keyboard") {} + private: + // Device functions + virtual controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; + + // Let's make it easy for Qt because we assume we love Qt forever + controller::Input makeInput(Qt::Key code) const; + controller::Input makeInput(Qt::MouseButton code, bool clicked = false) const; + controller::Input makeInput(MouseAxisChannel axis) const; + controller::Input makeInput(TouchAxisChannel axis) const; + controller::Input makeInput(TouchButtonChannel button) const; + + friend class KeyboardMouseDevice; + }; + +public: + const std::shared_ptr& getInputDevice() const { return _inputDevice; } + protected: QPoint _lastCursor; QPoint _mousePressAt; glm::vec2 _lastTouch; + std::shared_ptr _inputDevice { std::make_shared() }; + bool _isTouching = false; - std::chrono::high_resolution_clock _clock; std::chrono::high_resolution_clock::time_point _lastTouchTime; }; diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp index 024eb86182..18fdc9ddad 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -9,53 +9,37 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "SixenseManager.h" + +#ifdef HAVE_SIXENSE +#include +#endif #include #include #include +#include #include #include -#include -#include -#include #include -#include +#include +#include +#include #include -#include -#include "SixenseManager.h" +#include "InputPluginsLogging.h" +static const unsigned int BUTTON_0 = 1U << 0; // the skinny button between 1 and 2 +static const unsigned int BUTTON_1 = 1U << 5; +static const unsigned int BUTTON_2 = 1U << 6; +static const unsigned int BUTTON_3 = 1U << 3; +static const unsigned int BUTTON_4 = 1U << 4; +static const unsigned int BUTTON_FWD = 1U << 7; +static const unsigned int BUTTON_TRIGGER = 1U << 8; -#ifdef HAVE_SIXENSE - #include "sixense.h" -#endif - -// TODO: This should not be here -#include -Q_DECLARE_LOGGING_CATEGORY(inputplugins) -Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") - -#ifdef HAVE_SIXENSE - -const int CALIBRATION_STATE_IDLE = 0; -const int CALIBRATION_STATE_IN_PROGRESS = 1; -const int CALIBRATION_STATE_COMPLETE = 2; - -const glm::vec3 DEFAULT_AVATAR_POSITION(-0.25f, -0.35f, -0.3f); // in hydra frame - -const float CONTROLLER_THRESHOLD = 0.35f; - -#endif - -#ifdef __APPLE__ -typedef int (*SixenseBaseFunction)(); -typedef int (*SixenseTakeIntFunction)(int); -#ifdef HAVE_SIXENSE -typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*); -#endif -#endif +const glm::vec3 SixenseManager::DEFAULT_AVATAR_POSITION { -0.25f, -0.35f, -0.3f }; // in hydra frame +const float SixenseManager::CONTROLLER_THRESHOLD { 0.35f }; const QString SixenseManager::NAME = "Sixense"; const QString SixenseManager::HYDRA_ID_STRING = "Razer Hydra"; @@ -64,15 +48,6 @@ const QString MENU_PARENT = "Avatar"; const QString MENU_NAME = "Sixense"; const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; const QString TOGGLE_SMOOTH = "Smooth Sixense Movement"; -const float DEFAULT_REACH_LENGTH = 1.5f; - -static std::shared_ptr instance; -SixenseManager::SixenseManager() : - InputDevice("Hydra"), - _reachLength(DEFAULT_REACH_LENGTH) -{ - instance = std::shared_ptr(this); -} bool SixenseManager::isSupported() const { #ifdef HAVE_SIXENSE @@ -90,43 +65,16 @@ bool SixenseManager::isSupported() const { void SixenseManager::activate() { InputPlugin::activate(); + #ifdef HAVE_SIXENSE - _calibrationState = CALIBRATION_STATE_IDLE; - _avatarPosition = DEFAULT_AVATAR_POSITION; - _container->addMenu(MENU_PATH); _container->addMenuItem(MENU_PATH, TOGGLE_SMOOTH, - [this] (bool clicked) { this->setSixenseFilter(clicked); }, + [this] (bool clicked) { setSixenseFilter(clicked); }, true, true); auto userInputMapper = DependencyManager::get(); - userInputMapper->registerDevice(instance); + userInputMapper->registerDevice(_inputDevice); -#ifdef __APPLE__ - - if (!_sixenseLibrary) { - -#ifdef SIXENSE_LIB_FILENAME - _sixenseLibrary = new QLibrary(SIXENSE_LIB_FILENAME); -#else - const QString SIXENSE_LIBRARY_NAME = "libsixense_x64"; - QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "/../Frameworks/" - + SIXENSE_LIBRARY_NAME; - - _sixenseLibrary = new QLibrary(frameworkSixenseLibrary); -#endif - } - - if (_sixenseLibrary->load()){ - qCDebug(inputplugins) << "Loaded sixense library for hydra support -" << _sixenseLibrary->fileName(); - } else { - qCDebug(inputplugins) << "Sixense library at" << _sixenseLibrary->fileName() << "failed to load." - << "Continuing without hydra support."; - return; - } - - SixenseBaseFunction sixenseInit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseInit"); -#endif loadSettings(); sixenseInit(); #endif @@ -139,54 +87,37 @@ void SixenseManager::deactivate() { _container->removeMenuItem(MENU_NAME, TOGGLE_SMOOTH); _container->removeMenu(MENU_PATH); - _poseStateMap.clear(); - _collectedSamples.clear(); + _inputDevice->_poseStateMap.clear(); + _inputDevice->_collectedSamples.clear(); - if (_deviceID != controller::Input::INVALID_DEVICE) { + if (_inputDevice->_deviceID != controller::Input::INVALID_DEVICE) { auto userInputMapper = DependencyManager::get(); - userInputMapper->removeDevice(_deviceID); + userInputMapper->removeDevice(_inputDevice->_deviceID); } -#ifdef __APPLE__ - SixenseBaseFunction sixenseExit = (SixenseBaseFunction)_sixenseLibrary->resolve("sixenseExit"); -#endif - sixenseExit(); - -#ifdef __APPLE__ - delete _sixenseLibrary; -#endif - + saveSettings(); #endif } void SixenseManager::setSixenseFilter(bool filter) { #ifdef HAVE_SIXENSE -#ifdef __APPLE__ - SixenseTakeIntFunction sixenseSetFilterEnabled = (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseSetFilterEnabled"); -#endif - int newFilter = filter ? 1 : 0; - sixenseSetFilterEnabled(newFilter); + sixenseSetFilterEnabled(filter ? 1 : 0); #endif } -void SixenseManager::update(float deltaTime, bool jointsCaptured) { - // FIXME - Some of the code in update() will crash if you haven't actually activated the - // plugin. But we want register with the UserInputMapper if we don't call this. - // We need to clean this up. - //if (!_activated) { - // return; - //} +void SixenseManager::pluginUpdate(float deltaTime, bool jointsCaptured) { + _inputDevice->update(deltaTime, jointsCaptured); + if (_inputDevice->_requestReset) { + _container->requestReset(); + _inputDevice->_requestReset = false; + } +} + +void SixenseManager::InputDevice::update(float deltaTime, bool jointsCaptured) { #ifdef HAVE_SIXENSE _buttonPressedMap.clear(); -#ifdef __APPLE__ - SixenseBaseFunction sixenseGetNumActiveControllers = - (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetNumActiveControllers"); -#endif - - auto userInputMapper = DependencyManager::get(); - static const float MAX_DISCONNECTED_TIME = 2.0f; static bool disconnected { false }; static float disconnectedInterval { 0.0f }; @@ -213,30 +144,17 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) { // FIXME send this message once when we've positively identified hydra hardware //UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); -#ifdef __APPLE__ - SixenseBaseFunction sixenseGetMaxControllers = - (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetMaxControllers"); -#endif - int maxControllers = sixenseGetMaxControllers(); // we only support two controllers - sixenseControllerData controllers[2]; - -#ifdef __APPLE__ - SixenseTakeIntFunction sixenseIsControllerEnabled = - (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseIsControllerEnabled"); - - SixenseTakeIntAndSixenseControllerData sixenseGetNewestData = - (SixenseTakeIntAndSixenseControllerData) _sixenseLibrary->resolve("sixenseGetNewestData"); -#endif + SixenseControllerData controllers[2]; int numActiveControllers = 0; for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) { if (!sixenseIsControllerEnabled(i)) { continue; } - sixenseControllerData* data = controllers + numActiveControllers; + SixenseControllerData* data = controllers + numActiveControllers; ++numActiveControllers; sixenseGetNewestData(i, data); @@ -293,36 +211,37 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) { // (4) assume that the orb is on a flat surface (yAxis is UP) // (5) compute the forward direction (zAxis = xAxis cross yAxis) -const float MINIMUM_ARM_REACH = 0.3f; // meters -const float MAXIMUM_NOISE_LEVEL = 0.05f; // meters -const quint64 LOCK_DURATION = USECS_PER_SECOND / 4; // time for lock to be acquired +static const float MINIMUM_ARM_REACH = 0.3f; // meters +static const float MAXIMUM_NOISE_LEVEL = 0.05f; // meters +static const quint64 LOCK_DURATION = USECS_PER_SECOND / 4; // time for lock to be acquired -void SixenseManager::updateCalibration(void* controllersX) { - auto controllers = reinterpret_cast(controllersX); - const sixenseControllerData* dataLeft = controllers; - const sixenseControllerData* dataRight = controllers + 1; +static bool calibrationRequested(SixenseControllerData* controllers) { + return (controllers[0].buttons == BUTTON_FWD && controllers[1].buttons == BUTTON_FWD); +} - // calibration only happpens while both hands are holding BUTTON_FORWARD - if (dataLeft->buttons != BUTTON_FWD || dataRight->buttons != BUTTON_FWD) { - if (_calibrationState == CALIBRATION_STATE_IDLE) { - return; - } +void SixenseManager::InputDevice::updateCalibration(SixenseControllerData* controllers) { + const SixenseControllerData* dataLeft = controllers; + const SixenseControllerData* dataRight = controllers + 1; + + // Calibration buttons aren't set, so check the state, and request a reset if necessary. + if (!calibrationRequested(controllers)) { switch (_calibrationState) { - case CALIBRATION_STATE_COMPLETE: - { - // compute calibration results - _avatarPosition = - 0.5f * (_reachLeft + _reachRight); // neck is midway between right and left hands - glm::vec3 xAxis = glm::normalize(_reachRight - _reachLeft); - glm::vec3 zAxis = glm::normalize(glm::cross(xAxis, Vectors::UNIT_Y)); - xAxis = glm::normalize(glm::cross(Vectors::UNIT_Y, zAxis)); - _reachLength = glm::dot(xAxis, _reachRight - _reachLeft); - _avatarRotation = glm::inverse(glm::quat_cast(glm::mat3(xAxis, Vectors::UNIT_Y, zAxis))); - const float Y_OFFSET_CALIBRATED_HANDS_TO_AVATAR = -0.3f; - _avatarPosition.y += Y_OFFSET_CALIBRATED_HANDS_TO_AVATAR; - _container->requestReset(); - qCDebug(inputplugins, "succeess: sixense calibration"); - } - break; + case CALIBRATION_STATE_IDLE: + return; + + case CALIBRATION_STATE_COMPLETE: { + // compute calibration results + _avatarPosition = -0.5f * (_reachLeft + _reachRight); // neck is midway between right and left hands + glm::vec3 xAxis = glm::normalize(_reachRight - _reachLeft); + glm::vec3 zAxis = glm::normalize(glm::cross(xAxis, Vectors::UNIT_Y)); + xAxis = glm::normalize(glm::cross(Vectors::UNIT_Y, zAxis)); + _avatarRotation = glm::inverse(glm::quat_cast(glm::mat3(xAxis, Vectors::UNIT_Y, zAxis))); + const float Y_OFFSET_CALIBRATED_HANDS_TO_AVATAR = -0.3f; + _avatarPosition.y += Y_OFFSET_CALIBRATED_HANDS_TO_AVATAR; + qCDebug(inputplugins, "succeess: sixense calibration"); + _requestReset = true; + } + break; default: qCDebug(inputplugins, "failed: sixense calibration"); break; @@ -332,6 +251,7 @@ void SixenseManager::updateCalibration(void* controllersX) { return; } + // Calibration buttons are set, continue calibration work // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters. const float* pos = dataLeft->pos; glm::vec3 positionLeft(pos[0], pos[1], pos[2]); @@ -340,6 +260,7 @@ void SixenseManager::updateCalibration(void* controllersX) { glm::vec3 positionRight(pos[0], pos[1], pos[2]); positionRight *= METERS_PER_MILLIMETER; + // Gather initial calibration data if (_calibrationState == CALIBRATION_STATE_IDLE) { float reach = glm::distance(positionLeft, positionRight); if (reach > 2.0f * MINIMUM_ARM_REACH) { @@ -382,15 +303,12 @@ void SixenseManager::updateCalibration(void* controllersX) { #endif // HAVE_SIXENSE -void SixenseManager::focusOutEvent() { +void SixenseManager::InputDevice::focusOutEvent() { _axisStateMap.clear(); _buttonPressedMap.clear(); }; -void SixenseManager::handleAxisEvent(float stickX, float stickY, float trigger, bool left) { -} - -void SixenseManager::handleButtonEvent(unsigned int buttons, bool left) { +void SixenseManager::InputDevice::handleButtonEvent(unsigned int buttons, bool left) { using namespace controller; if (buttons & BUTTON_0) { _buttonPressedMap.insert(left ? BACK : START); @@ -415,7 +333,7 @@ void SixenseManager::handleButtonEvent(unsigned int buttons, bool left) { } } -void SixenseManager::handlePoseEvent(float deltaTime, glm::vec3 position, glm::quat rotation, bool left) { +void SixenseManager::InputDevice::handlePoseEvent(float deltaTime, glm::vec3 position, glm::quat rotation, bool left) { #ifdef HAVE_SIXENSE auto hand = left ? controller::StandardPoseChannel::LEFT_HAND : controller::StandardPoseChannel::RIGHT_HAND; @@ -480,8 +398,6 @@ void SixenseManager::handlePoseEvent(float deltaTime, glm::vec3 position, glm::q glm::vec3 velocity(0.0f); glm::quat angularVelocity; - - if (prevPose.isValid() && deltaTime > std::numeric_limits::epsilon()) { velocity = (position - prevPose.getTranslation()) / deltaTime; @@ -519,9 +435,7 @@ static const auto R2 = controller::A; static const auto R3 = controller::B; static const auto R4 = controller::Y; -using namespace controller; - -controller::Input::NamedVector SixenseManager::getAvailableInputs() const { +controller::Input::NamedVector SixenseManager::InputDevice::getAvailableInputs() const { using namespace controller; static const Input::NamedVector availableInputs { makePair(L0, "L0"), @@ -551,7 +465,7 @@ controller::Input::NamedVector SixenseManager::getAvailableInputs() const { }; -QString SixenseManager::getDefaultMappingConfig() const { +QString SixenseManager::InputDevice::getDefaultMappingConfig() const { static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/hydra.json"; return MAPPING_JSON; } @@ -562,9 +476,8 @@ void SixenseManager::saveSettings() const { QString idString = getID(); settings.beginGroup(idString); { - settings.setVec3Value(QString("avatarPosition"), _avatarPosition); - settings.setQuatValue(QString("avatarRotation"), _avatarRotation); - settings.setValue(QString("reachLength"), QVariant(_reachLength)); + settings.setVec3Value(QString("avatarPosition"), _inputDevice->_avatarPosition); + settings.setQuatValue(QString("avatarRotation"), _inputDevice->_avatarRotation); } settings.endGroup(); } @@ -574,9 +487,8 @@ void SixenseManager::loadSettings() { QString idString = getID(); settings.beginGroup(idString); { - settings.getVec3ValueIfValid(QString("avatarPosition"), _avatarPosition); - settings.getQuatValueIfValid(QString("avatarRotation"), _avatarRotation); - settings.getFloatValueIfValid(QString("reachLength"), _reachLength); + settings.getVec3ValueIfValid(QString("avatarPosition"), _inputDevice->_avatarPosition); + settings.getQuatValueIfValid(QString("avatarRotation"), _inputDevice->_avatarRotation); } settings.endGroup(); } diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.h b/libraries/input-plugins/src/input-plugins/SixenseManager.h index 5b5cb7ccfa..348a7a4590 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.h +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.h @@ -12,18 +12,6 @@ #ifndef hifi_SixenseManager_h #define hifi_SixenseManager_h -#ifdef HAVE_SIXENSE - #include - #include - #include "sixense.h" - -#ifdef __APPLE__ - #include - #include -#endif - -#endif - #include #include @@ -31,24 +19,13 @@ #include "InputPlugin.h" -class QLibrary; - -const unsigned int BUTTON_0 = 1U << 0; // the skinny button between 1 and 2 -const unsigned int BUTTON_1 = 1U << 5; -const unsigned int BUTTON_2 = 1U << 6; -const unsigned int BUTTON_3 = 1U << 3; -const unsigned int BUTTON_4 = 1U << 4; -const unsigned int BUTTON_FWD = 1U << 7; -const unsigned int BUTTON_TRIGGER = 1U << 8; - -const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false; +struct _sixenseControllerData; +using SixenseControllerData = _sixenseControllerData; // Handles interaction with the Sixense SDK (e.g., Razer Hydra). -class SixenseManager : public InputPlugin, public controller::InputDevice { +class SixenseManager : public InputPlugin { Q_OBJECT public: - SixenseManager(); - // Plugin functions virtual bool isSupported() const override; virtual bool isJointController() const override { return true; } @@ -58,15 +35,8 @@ public: virtual void activate() override; virtual void deactivate() override; - virtual void pluginFocusOutEvent() override { focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override { update(deltaTime, jointsCaptured); } - - // Device functions - virtual controller::Input::NamedVector getAvailableInputs() const override; - virtual QString getDefaultMappingConfig() const override; - - virtual void update(float deltaTime, bool jointsCaptured) override; - virtual void focusOutEvent() override; + virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override; virtual void saveSettings() const override; virtual void loadSettings() override; @@ -74,39 +44,54 @@ public: public slots: void setSixenseFilter(bool filter); -private: - void handleButtonEvent(unsigned int buttons, bool left); - void handleAxisEvent(float x, float y, float trigger, bool left); - void handlePoseEvent(float deltaTime, glm::vec3 position, glm::quat rotation, bool left); - - void updateCalibration(void* controllers); - - int _calibrationState; - - // these are calibration results - glm::vec3 _avatarPosition; // in hydra-frame - glm::quat _avatarRotation; // in hydra-frame - float _reachLength; - - // these are measured values used to compute the calibration results - quint64 _lockExpiry; - glm::vec3 _averageLeft; - glm::vec3 _averageRight; - glm::vec3 _reachLeft; - glm::vec3 _reachRight; - float _lastDistance; - bool _useSixenseFilter = true; - - +private: static const int MAX_NUM_AVERAGING_SAMPLES = 50; // At ~100 updates per seconds this means averaging over ~.5s - using Samples = std::pair< MovingAverage< glm::vec3, MAX_NUM_AVERAGING_SAMPLES>, MovingAverage< glm::vec4, MAX_NUM_AVERAGING_SAMPLES> >; - using MovingAverageMap = std::map< int, Samples >; - MovingAverageMap _collectedSamples; + static const int CALIBRATION_STATE_IDLE = 0; + static const int CALIBRATION_STATE_IN_PROGRESS = 1; + static const int CALIBRATION_STATE_COMPLETE = 2; + static const glm::vec3 DEFAULT_AVATAR_POSITION; + static const float CONTROLLER_THRESHOLD; -#ifdef __APPLE__ - QLibrary* _sixenseLibrary { nullptr }; -#endif + template + using SampleAverage = MovingAverage; + using Samples = std::pair, SampleAverage>; + using MovingAverageMap = std::map; + + class InputDevice : public controller::InputDevice { + public: + InputDevice() : controller::InputDevice("Hydra") {} + private: + // Device functions + virtual controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; + + void handleButtonEvent(unsigned int buttons, bool left); + void handlePoseEvent(float deltaTime, glm::vec3 position, glm::quat rotation, bool left); + void updateCalibration(SixenseControllerData* controllers); + + friend class SixenseManager; + + MovingAverageMap _collectedSamples; + + int _calibrationState { CALIBRATION_STATE_IDLE }; + // these are calibration results + glm::vec3 _avatarPosition { DEFAULT_AVATAR_POSITION }; // in hydra-frame + glm::quat _avatarRotation; // in hydra-frame + float _lastDistance; + bool _requestReset { false }; + // these are measured values used to compute the calibration results + quint64 _lockExpiry; + glm::vec3 _averageLeft; + glm::vec3 _averageRight; + glm::vec3 _reachLeft; + glm::vec3 _reachRight; + }; + + std::shared_ptr _inputDevice { std::make_shared() }; + static const QString NAME; static const QString HYDRA_ID_STRING; }; diff --git a/libraries/input-plugins/src/input-plugins/SixenseSupportOSX.cpp b/libraries/input-plugins/src/input-plugins/SixenseSupportOSX.cpp new file mode 100644 index 0000000000..f6cec5d67f --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/SixenseSupportOSX.cpp @@ -0,0 +1,153 @@ +// +// SixenseSupportOSX.cpp +// libraries/input-plugins/src/input-plugins +// +// Created by Clement on 10/20/15. +// Copyright 2015 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 +// + +// Mock implementation of sixense.h to hide dynamic linking on OS X +#if defined(__APPLE__) && defined(HAVE_SIXENSE) +#include + +#include + +#include +#include + +#include "InputPluginsLogging.h" + +#ifndef SIXENSE_LIB_FILENAME +#define SIXENSE_LIB_FILENAME QCoreApplication::applicationDirPath() + "/../Frameworks/libsixense_x64" +#endif + +using Library = std::unique_ptr; +static Library SIXENSE; + +struct Callable { + template + int operator() (Args&&... args){ + return reinterpret_cast(function)(std::forward(args)...); + } + QFunctionPointer function; +}; + +Callable resolve(const Library& library, const char* name) { + Q_ASSERT_X(library && library->isLoaded(), __FUNCTION__, "Sixense library not loaded"); + auto function = library->resolve(name); + Q_ASSERT_X(function, __FUNCTION__, std::string("Could not resolve ").append(name).c_str()); + return Callable { function }; +} +#define FORWARD resolve(SIXENSE, __FUNCTION__) + + +void loadSixense() { + Q_ASSERT_X(!(SIXENSE && SIXENSE->isLoaded()), __FUNCTION__, "Sixense library already loaded"); + SIXENSE.reset(new QLibrary(SIXENSE_LIB_FILENAME)); + Q_CHECK_PTR(SIXENSE); + + if (SIXENSE->load()){ + qDebug() << "Loaded sixense library for hydra support -" << SIXENSE->fileName(); + } else { + qDebug() << "Sixense library at" << SIXENSE->fileName() << "failed to load:" << SIXENSE->errorString(); + qDebug() << "Continuing without hydra support."; + } +} +void unloadSixense() { + SIXENSE->unload(); +} + + +// sixense.h wrapper for OSX dynamic linking +int sixenseInit() { + loadSixense(); + return FORWARD(); +} +int sixenseExit() { + auto returnCode = FORWARD(); + unloadSixense(); + return returnCode; +} + +int sixenseGetMaxBases() { + return FORWARD(); +} +int sixenseSetActiveBase(int i) { + return FORWARD(i); +} +int sixenseIsBaseConnected(int i) { + return FORWARD(i); +} + +int sixenseGetMaxControllers() { + return FORWARD(); +} +int sixenseIsControllerEnabled(int which) { + return FORWARD(which); +} +int sixenseGetNumActiveControllers() { + return FORWARD(); +} + +int sixenseGetHistorySize() { + return FORWARD(); +} + +int sixenseGetData(int which, int index_back, sixenseControllerData* data) { + return FORWARD(which, index_back, data); +} +int sixenseGetAllData(int index_back, sixenseAllControllerData* data) { + return FORWARD(index_back, data); +} +int sixenseGetNewestData(int which, sixenseControllerData* data) { + return FORWARD(which, data); +} +int sixenseGetAllNewestData(sixenseAllControllerData* data) { + return FORWARD(data); +} + +int sixenseSetHemisphereTrackingMode(int which_controller, int state) { + return FORWARD(which_controller, state); +} +int sixenseGetHemisphereTrackingMode(int which_controller, int* state) { + return FORWARD(which_controller, state); +} +int sixenseAutoEnableHemisphereTracking(int which_controller) { + return FORWARD(which_controller); +} + +int sixenseSetHighPriorityBindingEnabled(int on_or_off) { + return FORWARD(on_or_off); +} +int sixenseGetHighPriorityBindingEnabled(int* on_or_off) { + return FORWARD(on_or_off); +} + +int sixenseTriggerVibration(int controller_id, int duration_100ms, int pattern_id) { + return FORWARD(controller_id, duration_100ms, pattern_id); +} + +int sixenseSetFilterEnabled(int on_or_off) { + return FORWARD(on_or_off); +} +int sixenseGetFilterEnabled(int* on_or_off) { + return FORWARD(on_or_off); +} + +int sixenseSetFilterParams(float near_range, float near_val, float far_range, float far_val) { + return FORWARD(near_range, near_val, far_range, far_val); +} +int sixenseGetFilterParams(float* near_range, float* near_val, float* far_range, float* far_val) { + return FORWARD(near_range, near_val, far_range, far_val); +} + +int sixenseSetBaseColor(unsigned char red, unsigned char green, unsigned char blue) { + return FORWARD(red, green, blue); +} +int sixenseGetBaseColor(unsigned char* red, unsigned char* green, unsigned char* blue) { + return FORWARD(red, green, blue); +} +#endif diff --git a/libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp b/libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp index 43e6ee48a8..fe90470cb4 100644 --- a/libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp @@ -21,11 +21,10 @@ const float MAX_AXIS = 75.0f; // max forward = 2x speed -static std::shared_ptr instance; -SpacemouseDevice::SpacemouseDevice() : -InputDevice("Spacemouse") +static std::shared_ptr instance = std::make_shared(); + +SpacemouseDevice::SpacemouseDevice() : InputDevice("Spacemouse") { - instance = std::shared_ptr(this); } void SpacemouseDevice::focusOutEvent() { @@ -118,14 +117,6 @@ void SpacemouseDevice::update(float deltaTime, bool jointsCaptured) { // for osx the api will call DeviceAddedHandler or DeviceRemoveHandler when a 3Dconnexion device is attached or detached } -SpacemouseManager& SpacemouseManager::getInstance() { - static SpacemouseManager sharedInstance; - if (instance == nullptr) { - new SpacemouseDevice(); - } - return sharedInstance; -} - void SpacemouseManager::ManagerFocusOutEvent() { instance->focusOutEvent(); } diff --git a/libraries/input-plugins/src/input-plugins/SpacemouseManager.h b/libraries/input-plugins/src/input-plugins/SpacemouseManager.h index 08ac954c94..6253fa7f9d 100644 --- a/libraries/input-plugins/src/input-plugins/SpacemouseManager.h +++ b/libraries/input-plugins/src/input-plugins/SpacemouseManager.h @@ -23,7 +23,6 @@ class SpacemouseManager : public QObject { Q_OBJECT public: - static SpacemouseManager& getInstance(); void ManagerFocusOutEvent(); void init(); void destroy() {}; @@ -92,7 +91,6 @@ class SpacemouseManager : public QObject, public QAbstractNativeEventFilter { public: SpacemouseManager() {}; - static SpacemouseManager& getInstance(); void init(); void destroy(); bool Is3dmouseAttached(); @@ -169,7 +167,6 @@ private: class SpacemouseManager : public QObject { Q_OBJECT public: - static SpacemouseManager& getInstance(); void init(); void destroy(); bool Is3dmouseAttached(); diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp index 69b2b5b2c6..ec0c35cc96 100644 --- a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp @@ -27,12 +27,13 @@ #include #ifdef Q_OS_WIN -extern vr::IVRSystem* _hmd; -extern int hmdRefCount; extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; extern mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount]; #endif +vr::IVRSystem* acquireOpenVrSystem(); +void releaseOpenVrSystem(); + const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches const QString CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; @@ -44,28 +45,11 @@ const QString MENU_NAME = "Vive Controllers"; const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; const QString RENDER_CONTROLLERS = "Render Hand Controllers"; -static std::shared_ptr instance; - -ViveControllerManager::ViveControllerManager() : - InputDevice("Vive"), - _trackedControllers(0), - _modelLoaded(false), - _leftHandRenderID(0), - _rightHandRenderID(0), - _renderControllers(false) -{ - instance = std::shared_ptr(this); -} - bool ViveControllerManager::isSupported() const { #ifdef Q_OS_WIN - bool success = vr::VR_IsHmdPresent(); - if (success) { - vr::HmdError eError = vr::HmdError_None; - auto hmd = vr::VR_Init(&eError); - success = (hmd != nullptr); - vr::VR_Shutdown(); - } + auto hmd = acquireOpenVrSystem(); + bool success = hmd != nullptr; + releaseOpenVrSystem(); return success; #else return false; @@ -80,11 +64,8 @@ void ViveControllerManager::activate() { [this] (bool clicked) { this->setRenderControllers(clicked); }, true, true); - hmdRefCount++; if (!_hmd) { - vr::HmdError eError = vr::HmdError_None; - _hmd = vr::VR_Init(&eError); - Q_ASSERT(eError == vr::HmdError_None); + _hmd = acquireOpenVrSystem(); } Q_ASSERT(_hmd); @@ -138,7 +119,7 @@ void ViveControllerManager::activate() { // unregister with UserInputMapper auto userInputMapper = DependencyManager::get(); - userInputMapper->registerDevice(instance); + userInputMapper->registerDevice(_inputDevice); _registeredWithInputMapper = true; } @@ -149,18 +130,17 @@ void ViveControllerManager::deactivate() { _container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); _container->removeMenu(MENU_PATH); - hmdRefCount--; - - if (hmdRefCount == 0 && _hmd) { - vr::VR_Shutdown(); + if (_hmd) { + releaseOpenVrSystem(); _hmd = nullptr; } - _poseStateMap.clear(); + + _inputDevice->_poseStateMap.clear(); #endif // unregister with UserInputMapper auto userInputMapper = DependencyManager::get(); - userInputMapper->removeDevice(_deviceID); + userInputMapper->removeDevice(_inputDevice->_deviceID); _registeredWithInputMapper = false; } @@ -177,8 +157,8 @@ void ViveControllerManager::updateRendering(RenderArgs* args, render::ScenePoint //pendingChanges.updateItem(_leftHandRenderID, ); - controller::Pose leftHand = _poseStateMap[controller::StandardPoseChannel::LEFT_HAND]; - controller::Pose rightHand = _poseStateMap[controller::StandardPoseChannel::RIGHT_HAND]; + controller::Pose leftHand = _inputDevice->_poseStateMap[controller::StandardPoseChannel::LEFT_HAND]; + controller::Pose rightHand = _inputDevice->_poseStateMap[controller::StandardPoseChannel::RIGHT_HAND]; gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { auto geometryCache = DependencyManager::get(); @@ -223,15 +203,28 @@ void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch& batch.drawIndexed(gpu::TRIANGLES, mesh->getNumIndices(), 0); } -void ViveControllerManager::update(float deltaTime, bool jointsCaptured) { + +void ViveControllerManager::pluginUpdate(float deltaTime, bool jointsCaptured) { + _inputDevice->update(deltaTime, jointsCaptured); + auto userInputMapper = DependencyManager::get(); + + if (_inputDevice->_trackedControllers == 0 && _registeredWithInputMapper) { + userInputMapper->removeDevice(_inputDevice->_deviceID); + _registeredWithInputMapper = false; + _inputDevice->_poseStateMap.clear(); + } + + if (!_registeredWithInputMapper && _inputDevice->_trackedControllers > 0) { + userInputMapper->registerDevice(_inputDevice); + _registeredWithInputMapper = true; + UserActivityLogger::getInstance().connectedDevice("spatial_controller", "steamVR"); + } +} + +void ViveControllerManager::InputDevice::update(float deltaTime, bool jointsCaptured) { #ifdef Q_OS_WIN _poseStateMap.clear(); - // TODO: This shouldn't be necessary - if (!_hmd) { - return; - } - _buttonPressedMap.clear(); PerformanceTimer perfTimer("ViveControllerManager::update"); @@ -279,33 +272,17 @@ void ViveControllerManager::update(float deltaTime, bool jointsCaptured) { } } - auto userInputMapper = DependencyManager::get(); - - if (numTrackedControllers == 0) { - if (_registeredWithInputMapper) { - userInputMapper->removeDevice(_deviceID); - _registeredWithInputMapper = false; - _poseStateMap.clear(); - } - } - - if (_trackedControllers == 0 && numTrackedControllers > 0) { - userInputMapper->registerDevice(instance); - _registeredWithInputMapper = true; - UserActivityLogger::getInstance().connectedDevice("spatial_controller", "steamVR"); - } - _trackedControllers = numTrackedControllers; #endif } -void ViveControllerManager::focusOutEvent() { +void ViveControllerManager::InputDevice::focusOutEvent() { _axisStateMap.clear(); _buttonPressedMap.clear(); }; // These functions do translation from the Steam IDs to the standard controller IDs -void ViveControllerManager::handleAxisEvent(uint32_t axis, float x, float y, bool left) { +void ViveControllerManager::InputDevice::handleAxisEvent(uint32_t axis, float x, float y, bool left) { #ifdef Q_OS_WIN //FIX ME? It enters here every frame: probably we want to enter only if an event occurs axis += vr::k_EButton_Axis0; @@ -320,7 +297,7 @@ void ViveControllerManager::handleAxisEvent(uint32_t axis, float x, float y, boo } // These functions do translation from the Steam IDs to the standard controller IDs -void ViveControllerManager::handleButtonEvent(uint32_t button, bool pressed, bool left) { +void ViveControllerManager::InputDevice::handleButtonEvent(uint32_t button, bool pressed, bool left) { #ifdef Q_OS_WIN if (!pressed) { return; @@ -342,7 +319,7 @@ void ViveControllerManager::handleButtonEvent(uint32_t button, bool pressed, boo #endif } -void ViveControllerManager::handlePoseEvent(const mat4& mat, bool left) { +void ViveControllerManager::InputDevice::handlePoseEvent(const mat4& mat, bool left) { glm::vec3 position = extractTranslation(mat); glm::quat rotation = glm::quat_cast(mat); @@ -409,7 +386,7 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, bool left) { _poseStateMap[left ? controller::LEFT_HAND : controller::RIGHT_HAND] = controller::Pose(position, rotation); } -controller::Input::NamedVector ViveControllerManager::getAvailableInputs() const { +controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableInputs() const { using namespace controller; QVector availableInputs{ // Trackpad analogs @@ -450,7 +427,7 @@ controller::Input::NamedVector ViveControllerManager::getAvailableInputs() const return availableInputs; } -QString ViveControllerManager::getDefaultMappingConfig() const { +QString ViveControllerManager::InputDevice::getDefaultMappingConfig() const { static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/vive.json"; return MAPPING_JSON; } diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.h b/libraries/input-plugins/src/input-plugins/ViveControllerManager.h index 938f2f9ba9..02bdecb10a 100644 --- a/libraries/input-plugins/src/input-plugins/ViveControllerManager.h +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.h @@ -24,10 +24,13 @@ #include #include -class ViveControllerManager : public InputPlugin, public controller::InputDevice { +namespace vr { + class IVRSystem; +} + +class ViveControllerManager : public InputPlugin { Q_OBJECT public: - ViveControllerManager(); // Plugin functions virtual bool isSupported() const override; @@ -37,40 +40,51 @@ public: virtual void activate() override; virtual void deactivate() override; - virtual void pluginFocusOutEvent() override { focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override { update(deltaTime, jointsCaptured); } - - // Device functions - virtual controller::Input::NamedVector getAvailableInputs() const override; - virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, bool jointsCaptured) override; - virtual void focusOutEvent() override; + virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override; void updateRendering(RenderArgs* args, render::ScenePointer scene, render::PendingChanges pendingChanges); void setRenderControllers(bool renderControllers) { _renderControllers = renderControllers; } private: + class InputDevice : public controller::InputDevice { + public: + InputDevice(vr::IVRSystem*& hmd) : controller::InputDevice("Vive"), _hmd(hmd) {} + private: + // Device functions + virtual controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; + + void handleButtonEvent(uint32_t button, bool pressed, bool left); + void handleAxisEvent(uint32_t axis, float x, float y, bool left); + void handlePoseEvent(const mat4& mat, bool left); + + int _trackedControllers { 0 }; + vr::IVRSystem*& _hmd; + friend class ViveControllerManager; + }; + void renderHand(const controller::Pose& pose, gpu::Batch& batch, int sign); - void handleButtonEvent(uint32_t button, bool pressed, bool left); - void handleAxisEvent(uint32_t axis, float x, float y, bool left); - void handlePoseEvent(const mat4& mat, bool left); - int _trackedControllers; - bool _modelLoaded; + bool _registeredWithInputMapper { false }; + bool _modelLoaded { false }; model::Geometry _modelGeometry; gpu::TexturePointer _texture; - int _leftHandRenderID; - int _rightHandRenderID; + int _leftHandRenderID { 0 }; + int _rightHandRenderID { 0 }; - bool _renderControllers; + bool _renderControllers { false }; + vr::IVRSystem* _hmd { nullptr }; + std::shared_ptr _inputDevice { std::make_shared(_hmd) }; static const QString NAME; - bool _registeredWithInputMapper { false }; }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index a46a9693ac..24034ff9b3 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -38,7 +38,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP_BIS; + return VERSION_ENTITIES_PARTICLES_ADDITIVE_BLENDING; case PacketType::AvatarData: case PacketType::BulkAvatarData: default: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 553c12f8e3..82d905bf28 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -145,5 +145,6 @@ const PacketVersion VERSION_ENTITIES_PROTOCOL_CHANNELS = 45; const PacketVersion VERSION_ENTITIES_ANIMATION_PROPERTIES_GROUP = 46; const PacketVersion VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP = 47; const PacketVersion VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP_BIS = 48; +const PacketVersion VERSION_ENTITIES_PARTICLES_ADDITIVE_BLENDING = 49; #endif // hifi_PacketHeaders_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 42aaea33c2..c8a0f87b6d 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -79,8 +79,13 @@ EntityMotionState::~EntityMotionState() { assert(!_entity); } -void EntityMotionState::updateServerPhysicsVariables() { +void EntityMotionState::updateServerPhysicsVariables(const QUuid& sessionID) { assert(entityTreeIsLocked()); + if (_entity->getSimulatorID() == sessionID) { + // don't slam these values if we are the simulation owner + return; + } + _serverPosition = _entity->getPosition(); _serverRotation = _entity->getRotation(); _serverVelocity = _entity->getVelocity(); @@ -92,7 +97,7 @@ void EntityMotionState::updateServerPhysicsVariables() { // virtual bool EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { assert(entityTreeIsLocked()); - updateServerPhysicsVariables(); + updateServerPhysicsVariables(engine->getSessionID()); ObjectMotionState::handleEasyChanges(flags, engine); if (flags & Simulation::DIRTY_SIMULATOR_ID) { @@ -129,7 +134,7 @@ bool EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) // virtual bool EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { - updateServerPhysicsVariables(); + updateServerPhysicsVariables(engine->getSessionID()); return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index f3a2e80070..188e7096b9 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -28,7 +28,7 @@ public: EntityMotionState(btCollisionShape* shape, EntityItemPointer item); virtual ~EntityMotionState(); - void updateServerPhysicsVariables(); + void updateServerPhysicsVariables(const QUuid& sessionID); virtual bool handleEasyChanges(uint32_t flags, PhysicsEngine* engine); virtual bool handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h index 78ec8fdcb3..8d8259ba4f 100644 --- a/libraries/plugins/src/plugins/Forward.h +++ b/libraries/plugins/src/plugins/Forward.h @@ -7,9 +7,8 @@ // #pragma once -#include -#include -#include +#include +#include class DisplayPlugin; class InputPlugin; @@ -17,8 +16,8 @@ class Plugin; class PluginContainer; class PluginManager; -using DisplayPluginPointer = QSharedPointer; -using DisplayPluginList = QVector; -using InputPluginPointer = QSharedPointer; -using InputPluginList = QVector; +using DisplayPluginPointer = std::shared_ptr; +using DisplayPluginList = std::vector; +using InputPluginPointer = std::shared_ptr; +using InputPluginList = std::vector; diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 17619a93c0..2e056414ec 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -7,6 +7,8 @@ // #pragma once +#include + #include "Forward.h" class PluginManager : public QObject { diff --git a/libraries/recording/src/recording/Clip.cpp b/libraries/recording/src/recording/Clip.cpp new file mode 100644 index 0000000000..09acf0579f --- /dev/null +++ b/libraries/recording/src/recording/Clip.cpp @@ -0,0 +1,49 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 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 +// + +#include "Clip.h" + +#include "Frame.h" + +#include "impl/FileClip.h" +#include "impl/BufferClip.h" + +using namespace recording; + +Clip::Pointer Clip::fromFile(const QString& filePath) { + auto result = std::make_shared(filePath); + if (result->frameCount() == 0) { + return Clip::Pointer(); + } + return result; +} + +void Clip::toFile(const QString& filePath, Clip::Pointer clip) { + FileClip::write(filePath, clip->duplicate()); +} + +Clip::Pointer Clip::newClip() { + return std::make_shared(); +} + +Clip::Pointer Clip::duplicate() { + Clip::Pointer result = std::make_shared(); + + Locker lock(_mutex); + float currentPosition = position(); + seek(0); + + Frame::Pointer frame = nextFrame(); + while (frame) { + result->addFrame(frame); + frame = nextFrame(); + } + + seek(currentPosition); + return result; +} diff --git a/libraries/recording/src/recording/Clip.h b/libraries/recording/src/recording/Clip.h new file mode 100644 index 0000000000..e7034ef077 --- /dev/null +++ b/libraries/recording/src/recording/Clip.h @@ -0,0 +1,57 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 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 +// + +#pragma once +#ifndef hifi_Recording_Clip_h +#define hifi_Recording_Clip_h + +#include "Forward.h" + +#include + +#include + +class QIODevice; + +namespace recording { + +class Clip { +public: + using Pointer = std::shared_ptr; + + virtual ~Clip() {} + + Pointer duplicate(); + + virtual float duration() const = 0; + virtual size_t frameCount() const = 0; + + virtual void seek(float offset) = 0; + virtual float position() const = 0; + + virtual FramePointer peekFrame() const = 0; + virtual FramePointer nextFrame() = 0; + virtual void skipFrame() = 0; + virtual void addFrame(FramePointer) = 0; + + static Pointer fromFile(const QString& filePath); + static void toFile(const QString& filePath, Pointer clip); + static Pointer newClip(); + +protected: + using Mutex = std::recursive_mutex; + using Locker = std::unique_lock; + + virtual void reset() = 0; + + mutable Mutex _mutex; +}; + +} + +#endif diff --git a/libraries/recording/src/recording/Deck.cpp b/libraries/recording/src/recording/Deck.cpp new file mode 100644 index 0000000000..f0db37078b --- /dev/null +++ b/libraries/recording/src/recording/Deck.cpp @@ -0,0 +1,12 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 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 +// + + +// FIXME -- DO NOT include headers in empty CPP files, it produces warnings. Once we define new symbols +// and some actual code here, we can uncomment this include. +//#include "Deck.h" diff --git a/libraries/recording/src/recording/Deck.h b/libraries/recording/src/recording/Deck.h new file mode 100644 index 0000000000..2ae8d6a7be --- /dev/null +++ b/libraries/recording/src/recording/Deck.h @@ -0,0 +1,37 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 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 +// + +#pragma once +#ifndef hifi_Recording_Deck_h +#define hifi_Recording_Deck_h + +#include "Forward.h" + +#include + +class QIODevice; + +namespace recording { + +class Deck : public QObject { +public: + using Pointer = std::shared_ptr; + + Deck(QObject* parent = nullptr) : QObject(parent) {} + virtual ~Deck(); + + // Place a clip on the deck for recording or playback + void queueClip(ClipPointer clip, float timeOffset = 0.0f); + void play(float timeOffset = 0.0f); + void reposition(float timeOffsetDelta); + void setPlaybackSpeed(float rate); +}; + +} + +#endif diff --git a/libraries/recording/src/recording/Forward.h b/libraries/recording/src/recording/Forward.h index 83a89da847..5bd6dd917f 100644 --- a/libraries/recording/src/recording/Forward.h +++ b/libraries/recording/src/recording/Forward.h @@ -15,14 +15,28 @@ namespace recording { +using FrameType = uint16_t; + +struct Frame; + +using FramePointer = std::shared_ptr; + // A recording of some set of state from the application, usually avatar // data + audio for a single person class Clip; -// An interface for interacting with clips, creating them by recording or -// playing them back. Also serialization to and from files / network sources +using ClipPointer = std::shared_ptr; + +// An interface for playing back clips class Deck; +using DeckPointer = std::shared_ptr; + +// An interface for recording a single clip +class Recorder; + +using RecorderPointer = std::shared_ptr; + } #endif diff --git a/libraries/recording/src/recording/Frame.cpp b/libraries/recording/src/recording/Frame.cpp new file mode 100644 index 0000000000..aac8a4d9c3 --- /dev/null +++ b/libraries/recording/src/recording/Frame.cpp @@ -0,0 +1,104 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 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 +// + +#include "Frame.h" + +#include + +#include + +using namespace recording; + +// FIXME move to shared +template +class Registry { +public: + using ForwardMap = QMap; + using BackMap = QMap; + static const Key INVALID_KEY = static_cast(-1); + + Key registerValue(const Value& value) { + Locker lock(_mutex); + Key result = INVALID_KEY; + if (_forwardMap.contains(value)) { + result = _forwardMap[value]; + } else { + _forwardMap[value] = result = _nextKey++; + _backMap[result] = value; + } + return result; + } + + Key getKey(const Value& value) { + Locker lock(_mutex); + Key result = INVALID_KEY; + if (_forwardMap.contains(value)) { + result = _forwardMap[value]; + } + return result; + } + + ForwardMap getKeysByValue() { + Locker lock(_mutex); + ForwardMap result = _forwardMap; + return result; + } + + BackMap getValuesByKey() { + Locker lock(_mutex); + BackMap result = _backMap; + return result; + } + +private: + using Mutex = std::mutex; + using Locker = std::unique_lock; + + Mutex _mutex; + + ForwardMap _forwardMap; + BackMap _backMap; + Key _nextKey { 0 }; +}; + +static Registry frameTypes; +static QMap handlerMap; +using Mutex = std::mutex; +using Locker = std::unique_lock; +static Mutex mutex; +static std::once_flag once; + + + +FrameType Frame::registerFrameType(const QString& frameTypeName) { + Locker lock(mutex); + std::call_once(once, [&] { + auto headerType = frameTypes.registerValue("com.highfidelity.recording.Header"); + Q_ASSERT(headerType == Frame::TYPE_HEADER); + Q_UNUSED(headerType); // FIXME - build system on unix still not upgraded to Qt 5.5.1 so Q_ASSERT still produces warnings + }); + return frameTypes.registerValue(frameTypeName); +} + +QMap Frame::getFrameTypes() { + return frameTypes.getKeysByValue(); +} + +QMap Frame::getFrameTypeNames() { + return frameTypes.getValuesByKey(); +} + +Frame::Handler Frame::registerFrameHandler(FrameType type, Handler handler) { + Locker lock(mutex); + Handler result; + if (handlerMap.contains(type)) { + result = handlerMap[type]; + } + handlerMap[type] = handler; + return result; +} diff --git a/libraries/recording/src/recording/Frame.h b/libraries/recording/src/recording/Frame.h new file mode 100644 index 0000000000..0fb95c4b2e --- /dev/null +++ b/libraries/recording/src/recording/Frame.h @@ -0,0 +1,44 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 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 +// + +#pragma once +#ifndef hifi_Recording_Frame_h +#define hifi_Recording_Frame_h + +#include "Forward.h" + +#include + +#include + +namespace recording { + +struct Frame { +public: + using Pointer = std::shared_ptr; + using Handler = std::function; + + static const FrameType TYPE_INVALID = 0xFFFF; + static const FrameType TYPE_HEADER = 0x0; + FrameType type { TYPE_INVALID }; + float timeOffset { 0 }; + QByteArray data; + + Frame() {} + Frame(FrameType type, float timeOffset, const QByteArray& data) + : type(type), timeOffset(timeOffset), data(data) {} + + static FrameType registerFrameType(const QString& frameTypeName); + static QMap getFrameTypes(); + static QMap getFrameTypeNames(); + static Handler registerFrameHandler(FrameType type, Handler handler); +}; + +} + +#endif diff --git a/libraries/recording/src/recording/Logging.cpp b/libraries/recording/src/recording/Logging.cpp new file mode 100644 index 0000000000..5673e6e175 --- /dev/null +++ b/libraries/recording/src/recording/Logging.cpp @@ -0,0 +1,11 @@ +// +// Created by Bradley Austin Davis 2015/10/11 +// Copyright 2015 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 +// + +#include "Logging.h" + +Q_LOGGING_CATEGORY(recordingLog, "hifi.recording") diff --git a/libraries/recording/src/recording/Logging.h b/libraries/recording/src/recording/Logging.h new file mode 100644 index 0000000000..a1b28329d7 --- /dev/null +++ b/libraries/recording/src/recording/Logging.h @@ -0,0 +1,16 @@ +// +// Created by Bradley Austin Davis 2015/10/11 +// Copyright 2015 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 +// + +#ifndef hifi_Controllers_Logging_h +#define hifi_Controllers_Logging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(recordingLog) + +#endif diff --git a/libraries/recording/src/recording/Recorder.cpp b/libraries/recording/src/recording/Recorder.cpp new file mode 100644 index 0000000000..b2e7399cd4 --- /dev/null +++ b/libraries/recording/src/recording/Recorder.cpp @@ -0,0 +1,62 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 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 +// + +#include "Recorder.h" + +#include + +#include "impl/BufferClip.h" +#include "Frame.h" + +using namespace recording; + +void Recorder::start() { + if (!_recording) { + _recording = true; + if (!_clip) { + _clip = std::make_shared(); + } + _timer.start(); + emit recordingStateChanged(); + } +} + +void Recorder::stop() { + if (!_recording) { + _recording = false; + _elapsed = _timer.elapsed(); + emit recordingStateChanged(); + } +} + +bool Recorder::isRecording() { + return _recording; +} + +void Recorder::clear() { + _clip.reset(); +} + +void Recorder::recordFrame(FrameType type, QByteArray frameData) { + if (!_recording || !_clip) { + return; + } + + Frame::Pointer frame = std::make_shared(); + frame->type = type; + frame->data = frameData; + frame->timeOffset = (float)(_elapsed + _timer.elapsed()) / MSECS_PER_SECOND; + _clip->addFrame(frame); +} + +ClipPointer Recorder::getClip() { + auto result = _clip; + _clip.reset(); + return result; +} + diff --git a/libraries/recording/src/recording/Recorder.h b/libraries/recording/src/recording/Recorder.h new file mode 100644 index 0000000000..deae543bb0 --- /dev/null +++ b/libraries/recording/src/recording/Recorder.h @@ -0,0 +1,55 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 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 +// + +#pragma once +#ifndef hifi_Recording_Recorder_h +#define hifi_Recording_Recorder_h + +#include "Forward.h" + +#include +#include + +namespace recording { + +// An interface for interacting with clips, creating them by recording or +// playing them back. Also serialization to and from files / network sources +class Recorder : public QObject { +public: + using Pointer = std::shared_ptr; + + Recorder(QObject* parent = nullptr) : QObject(parent) {} + virtual ~Recorder(); + + // Start recording frames + void start(); + // Stop recording + void stop(); + // Test if recording is active + bool isRecording(); + // Erase the currently recorded content + void clear(); + + void recordFrame(FrameType type, QByteArray frameData); + + // Return the currently recorded content + ClipPointer getClip(); + +signals: + void recordingStateChanged(); + +private: + QElapsedTimer _timer; + ClipPointer _clip; + quint64 _elapsed; + bool _recording { false }; +}; + +} + +#endif diff --git a/libraries/recording/src/recording/impl/BufferClip.cpp b/libraries/recording/src/recording/impl/BufferClip.cpp new file mode 100644 index 0000000000..4d5a910d42 --- /dev/null +++ b/libraries/recording/src/recording/impl/BufferClip.cpp @@ -0,0 +1,90 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 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 +// + +#include "BufferClip.h" + +#include "../Frame.h" + +using namespace recording; + + +void BufferClip::seek(float offset) { + Locker lock(_mutex); + auto itr = std::lower_bound(_frames.begin(), _frames.end(), offset, + [](Frame::Pointer a, float b)->bool{ + return a->timeOffset < b; + } + ); + _frameIndex = itr - _frames.begin(); +} + +float BufferClip::position() const { + Locker lock(_mutex); + float result = std::numeric_limits::max(); + if (_frameIndex < _frames.size()) { + result = _frames[_frameIndex]->timeOffset; + } + return result; +} + +FramePointer BufferClip::peekFrame() const { + Locker lock(_mutex); + FramePointer result; + if (_frameIndex < _frames.size()) { + result = _frames[_frameIndex]; + } + return result; +} + +FramePointer BufferClip::nextFrame() { + Locker lock(_mutex); + FramePointer result; + if (_frameIndex < _frames.size()) { + result = _frames[_frameIndex]; + ++_frameIndex; + } + return result; +} + +void BufferClip::addFrame(FramePointer newFrame) { + if (newFrame->timeOffset < 0.0f) { + throw std::runtime_error("Frames may not have negative time offsets"); + } + auto currentPosition = position(); + seek(newFrame->timeOffset); + { + Locker lock(_mutex); + + _frames.insert(_frames.begin() + _frameIndex, newFrame); + } + seek(currentPosition); +} + +void BufferClip::skipFrame() { + Locker lock(_mutex); + if (_frameIndex < _frames.size()) { + ++_frameIndex; + } +} + +void BufferClip::reset() { + Locker lock(_mutex); + _frameIndex = 0; +} + +float BufferClip::duration() const { + if (_frames.empty()) { + return 0; + } + return (*_frames.rbegin())->timeOffset; +} + +size_t BufferClip::frameCount() const { + return _frames.size(); +} + diff --git a/libraries/recording/src/recording/impl/BufferClip.h b/libraries/recording/src/recording/impl/BufferClip.h new file mode 100644 index 0000000000..b40687a4ec --- /dev/null +++ b/libraries/recording/src/recording/impl/BufferClip.h @@ -0,0 +1,45 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 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 +// + +#pragma once +#ifndef hifi_Recording_Impl_BufferClip_h +#define hifi_Recording_Impl_BufferClip_h + +#include "../Clip.h" + +#include + +namespace recording { + +class BufferClip : public Clip { +public: + using Pointer = std::shared_ptr; + + virtual ~BufferClip() {} + + virtual float duration() const override; + virtual size_t frameCount() const override; + + virtual void seek(float offset) override; + virtual float position() const override; + + virtual FramePointer peekFrame() const override; + virtual FramePointer nextFrame() override; + virtual void skipFrame() override; + virtual void addFrame(FramePointer) override; + +private: + virtual void reset() override; + + std::vector _frames; + mutable size_t _frameIndex { 0 }; +}; + +} + +#endif diff --git a/libraries/recording/src/recording/impl/FileClip.cpp b/libraries/recording/src/recording/impl/FileClip.cpp new file mode 100644 index 0000000000..be7230e3f8 --- /dev/null +++ b/libraries/recording/src/recording/impl/FileClip.cpp @@ -0,0 +1,273 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 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 +// + +#include "FileClip.h" + +#include + +#include +#include +#include + +#include + +#include "../Frame.h" +#include "../Logging.h" + + +using namespace recording; + +static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(float) + sizeof(uint16_t); + +static const QString FRAME_TYPE_MAP = QStringLiteral("frameTypes"); + +using FrameHeaderList = std::list; +using FrameTranslationMap = QMap; + +FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) { + FrameTranslationMap results; + auto headerObj = doc.object(); + if (headerObj.contains(FRAME_TYPE_MAP)) { + auto frameTypeObj = headerObj[FRAME_TYPE_MAP].toObject(); + auto currentFrameTypes = Frame::getFrameTypes(); + for (auto frameTypeName : frameTypeObj.keys()) { + qDebug() << frameTypeName; + if (!currentFrameTypes.contains(frameTypeName)) { + continue; + } + FrameType currentTypeEnum = currentFrameTypes[frameTypeName]; + FrameType storedTypeEnum = static_cast(frameTypeObj[frameTypeName].toInt()); + results[storedTypeEnum] = currentTypeEnum; + } + } + return results; +} + + +FrameHeaderList parseFrameHeaders(uchar* const start, const qint64& size) { + using FrameHeader = FileClip::FrameHeader; + FrameHeaderList results; + auto current = start; + auto end = current + size; + // Read all the frame headers + // FIXME move to Frame::readHeader? + while (end - current >= MINIMUM_FRAME_SIZE) { + FrameHeader header; + memcpy(&(header.type), current, sizeof(FrameType)); + current += sizeof(FrameType); + memcpy(&(header.timeOffset), current, sizeof(float)); + current += sizeof(float); + memcpy(&(header.size), current, sizeof(uint16_t)); + current += sizeof(uint16_t); + header.fileOffset = current - start; + if (end - current < header.size) { + current = end; + break; + } + current += header.size; + results.push_back(header); + } + return results; +} + + +FileClip::FileClip(const QString& fileName) : _file(fileName) { + auto size = _file.size(); + bool opened = _file.open(QIODevice::ReadOnly); + if (!opened) { + qCWarning(recordingLog) << "Unable to open file " << fileName; + return; + } + _map = _file.map(0, size, QFile::MapPrivateOption); + if (!_map) { + qCWarning(recordingLog) << "Unable to map file " << fileName; + return; + } + + FrameHeaderList parsedFrameHeaders = parseFrameHeaders(_map, size); + + // Verify that at least one frame exists and that the first frame is a header + if (0 == parsedFrameHeaders.size()) { + qWarning() << "No frames found, invalid file"; + return; + } + + // Grab the file header + { + auto fileHeaderFrameHeader = *parsedFrameHeaders.begin(); + parsedFrameHeaders.pop_front(); + if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) { + qWarning() << "Missing header frame, invalid file"; + return; + } + + QByteArray fileHeaderData((char*)_map + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size); + _fileHeader = QJsonDocument::fromBinaryData(fileHeaderData); + } + + // Find the type enum translation map and fix up the frame headers + { + FrameTranslationMap translationMap = parseTranslationMap(_fileHeader); + if (translationMap.empty()) { + qWarning() << "Header missing frame type map, invalid file"; + return; + } + + // Update the loaded headers with the frame data + _frameHeaders.reserve(parsedFrameHeaders.size()); + for (auto& frameHeader : parsedFrameHeaders) { + if (!translationMap.contains(frameHeader.type)) { + continue; + } + frameHeader.type = translationMap[frameHeader.type]; + _frameHeaders.push_back(frameHeader); + } + } +} + +// FIXME move to frame? +bool writeFrame(QIODevice& output, const Frame& frame) { + auto written = output.write((char*)&(frame.type), sizeof(FrameType)); + if (written != sizeof(FrameType)) { + return false; + } + written = output.write((char*)&(frame.timeOffset), sizeof(float)); + if (written != sizeof(float)) { + return false; + } + uint16_t dataSize = frame.data.size(); + written = output.write((char*)&dataSize, sizeof(uint16_t)); + if (written != sizeof(uint16_t)) { + return false; + } + if (dataSize != 0) { + written = output.write(frame.data); + if (written != dataSize) { + return false; + } + } + return true; +} + +bool FileClip::write(const QString& fileName, Clip::Pointer clip) { + qCDebug(recordingLog) << "Writing clip to file " << fileName; + + if (0 == clip->frameCount()) { + return false; + } + + QFile outputFile(fileName); + if (!outputFile.open(QFile::Truncate | QFile::WriteOnly)) { + return false; + } + + Finally closer([&] { outputFile.close(); }); + { + auto frameTypes = Frame::getFrameTypes(); + QJsonObject frameTypeObj; + for (const auto& frameTypeName : frameTypes.keys()) { + frameTypeObj[frameTypeName] = frameTypes[frameTypeName]; + } + + QJsonObject rootObject; + rootObject.insert(FRAME_TYPE_MAP, frameTypeObj); + QByteArray headerFrameData = QJsonDocument(rootObject).toBinaryData(); + if (!writeFrame(outputFile, Frame({ Frame::TYPE_HEADER, 0, headerFrameData }))) { + return false; + } + } + + clip->seek(0); + for (auto frame = clip->nextFrame(); frame; frame = clip->nextFrame()) { + if (!writeFrame(outputFile, *frame)) { + return false; + } + } + outputFile.close(); + return true; +} + +FileClip::~FileClip() { + Locker lock(_mutex); + _file.unmap(_map); + _map = nullptr; + if (_file.isOpen()) { + _file.close(); + } +} + +void FileClip::seek(float offset) { + Locker lock(_mutex); + auto itr = std::lower_bound(_frameHeaders.begin(), _frameHeaders.end(), offset, + [](const FrameHeader& a, float b)->bool { + return a.timeOffset < b; + } + ); + _frameIndex = itr - _frameHeaders.begin(); +} + +float FileClip::position() const { + Locker lock(_mutex); + float result = std::numeric_limits::max(); + if (_frameIndex < _frameHeaders.size()) { + result = _frameHeaders[_frameIndex].timeOffset; + } + return result; +} + +FramePointer FileClip::readFrame(uint32_t frameIndex) const { + FramePointer result; + if (frameIndex < _frameHeaders.size()) { + result = std::make_shared(); + const FrameHeader& header = _frameHeaders[frameIndex]; + result->type = header.type; + result->timeOffset = header.timeOffset; + if (header.size) { + result->data.insert(0, reinterpret_cast(_map)+header.fileOffset, header.size); + } + } + return result; +} + +FramePointer FileClip::peekFrame() const { + Locker lock(_mutex); + return readFrame(_frameIndex); +} + +FramePointer FileClip::nextFrame() { + Locker lock(_mutex); + auto result = readFrame(_frameIndex); + if (_frameIndex < _frameHeaders.size()) { + ++_frameIndex; + } + return result; +} + +void FileClip::skipFrame() { + ++_frameIndex; +} + +void FileClip::reset() { + _frameIndex = 0; +} + +void FileClip::addFrame(FramePointer) { + throw std::runtime_error("File clips are read only"); +} + +float FileClip::duration() const { + if (_frameHeaders.empty()) { + return 0; + } + return _frameHeaders.rbegin()->timeOffset; +} + +size_t FileClip::frameCount() const { + return _frameHeaders.size(); +} + diff --git a/libraries/recording/src/recording/impl/FileClip.h b/libraries/recording/src/recording/impl/FileClip.h new file mode 100644 index 0000000000..08eacd8337 --- /dev/null +++ b/libraries/recording/src/recording/impl/FileClip.h @@ -0,0 +1,71 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 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 +// + +#pragma once +#ifndef hifi_Recording_Impl_FileClip_h +#define hifi_Recording_Impl_FileClip_h + +#include "../Clip.h" + +#include +#include + +#include + +namespace recording { + +class FileClip : public Clip { +public: + using Pointer = std::shared_ptr; + + FileClip(const QString& file); + virtual ~FileClip(); + + virtual float duration() const override; + virtual size_t frameCount() const override; + + virtual void seek(float offset) override; + virtual float position() const override; + + virtual FramePointer peekFrame() const override; + virtual FramePointer nextFrame() override; + virtual void skipFrame() override; + virtual void addFrame(FramePointer) override; + + const QJsonDocument& getHeader() { + return _fileHeader; + } + + static bool write(const QString& filePath, Clip::Pointer clip); + + struct FrameHeader { + FrameType type; + float timeOffset; + uint16_t size; + quint64 fileOffset; + }; + +private: + + virtual void reset() override; + + + using FrameHeaderVector = std::vector; + + FramePointer readFrame(uint32_t frameIndex) const; + + QJsonDocument _fileHeader; + QFile _file; + uint32_t _frameIndex { 0 }; + uchar* _map { nullptr }; + FrameHeaderVector _frameHeaders; +}; + +} + +#endif diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 6aae7ad1cb..0c00192ce6 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -135,9 +135,6 @@ void Model::reset() { const FBXGeometry& geometry = _geometry->getFBXGeometry(); _rig->reset(geometry.joints); } - _meshGroupsKnown = false; - _readyWhenAdded = false; // in case any of our users are using scenes - invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid } bool Model::updateGeometry() { @@ -508,8 +505,10 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr scen } -bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges) { - if (!_meshGroupsKnown && isLoaded()) { +bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, bool showCollisionHull) { + + if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) { + _showCollisionHull = showCollisionHull; segregateMeshGroups(); } @@ -532,8 +531,12 @@ bool Model::addToScene(std::shared_ptr scene, render::PendingChan return somethingAdded; } -bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters) { - if (!_meshGroupsKnown && isLoaded()) { +bool Model::addToScene(std::shared_ptr scene, + render::PendingChanges& pendingChanges, + render::Item::Status::Getters& statusGetters, + bool showCollisionHull) { + if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) { + _showCollisionHull = showCollisionHull; segregateMeshGroups(); } @@ -1144,8 +1147,14 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { } void Model::segregateMeshGroups() { - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const std::vector>& networkMeshes = _geometry->getMeshes(); + QSharedPointer networkGeometry; + if (_showCollisionHull && _collisionGeometry && _collisionGeometry->isLoaded()) { + networkGeometry = _collisionGeometry; + } else { + networkGeometry = _geometry; + } + const FBXGeometry& geometry = networkGeometry->getFBXGeometry(); + const std::vector>& networkMeshes = networkGeometry->getMeshes(); _rig->makeAnimSkeleton(geometry); @@ -1156,6 +1165,9 @@ void Model::segregateMeshGroups() { return; } + Q_ASSERT(_renderItems.isEmpty()); // We should not have any existing renderItems if we enter this section of code + Q_ASSERT(_renderItemsSet.isEmpty()); // We should not have any existing renderItemsSet if we enter this section of code + _renderItemsSet.clear(); // Run through all of the meshes, and place them into their segregated, but unsorted buckets diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index f5d5f40363..b154ae2b52 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -76,10 +76,13 @@ public: return !_needsReload && isRenderable() && isActive() && isLoaded(); } bool initWhenReady(render::ScenePointer scene); - bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges); bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, - render::Item::Status::Getters& statusGetters); + bool showCollisionHull = false); + bool addToScene(std::shared_ptr scene, + render::PendingChanges& pendingChanges, + render::Item::Status::Getters& statusGetters, + bool showCollisionHull = false); void removeFromScene(std::shared_ptr scene, render::PendingChanges& pendingChanges); void renderSetup(RenderArgs* args); bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().empty()); } @@ -368,6 +371,7 @@ private: bool _readyWhenAdded = false; bool _needsReload = true; bool _needsUpdateClusterMatrices = true; + bool _showCollisionHull = false; friend class MeshPartPayload; protected: diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 04483fc037..6daa90b1ed 100755 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -97,7 +97,12 @@ public: int _drawStatusJobIndex = -1; int _drawHitEffectJobIndex = -1; - void setDrawItemStatus(bool draw) { if (_drawStatusJobIndex >= 0) { _jobs[_drawStatusJobIndex].setEnabled(draw); } } + void setDrawItemStatus(int draw) { + if (_drawStatusJobIndex >= 0) { + _jobs[_drawStatusJobIndex].setEnabled(draw > 0); + } + } + bool doDrawItemStatus() const { if (_drawStatusJobIndex >= 0) { return _jobs[_drawStatusJobIndex].isEnabled(); } else { return false; } } void setDrawHitEffect(bool draw) { if (_drawHitEffectJobIndex >= 0) { _jobs[_drawHitEffectJobIndex].setEnabled(draw); } } diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 190370fd48..7b344ae63e 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -153,22 +153,27 @@ void DrawStatus::run(const SceneContextPointer& sceneContext, const RenderContex const unsigned int VEC3_ADRESS_OFFSET = 3; - for (int i = 0; i < nbItems; i++) { - batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*) (itemAABox + i)); - batch._glUniform3fv(_drawItemBoundDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET); + if ((renderContext->_drawItemStatus & showDisplayStatusFlag) > 0) { + for (int i = 0; i < nbItems; i++) { + batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*) (itemAABox + i)); + batch._glUniform3fv(_drawItemBoundDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET); - batch.draw(gpu::LINES, 24, 0); + batch.draw(gpu::LINES, 24, 0); + } } batch.setResourceTexture(0, gpu::TextureView(getStatusIconMap(), 0)); batch.setPipeline(getDrawItemStatusPipeline()); - for (int i = 0; i < nbItems; i++) { - batch._glUniform3fv(_drawItemStatusPosLoc, 1, (const float*) (itemAABox + i)); - batch._glUniform3fv(_drawItemStatusDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET); - batch._glUniform4iv(_drawItemStatusValueLoc, 1, (const int*) (itemStatus + i)); - batch.draw(gpu::TRIANGLES, 24, 0); + if ((renderContext->_drawItemStatus & showNetworkStatusFlag) > 0) { + for (int i = 0; i < nbItems; i++) { + batch._glUniform3fv(_drawItemStatusPosLoc, 1, (const float*) (itemAABox + i)); + batch._glUniform3fv(_drawItemStatusDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET); + batch._glUniform4iv(_drawItemStatusValueLoc, 1, (const int*) (itemStatus + i)); + + batch.draw(gpu::TRIANGLES, 24, 0); + } } batch.setResourceTexture(0, 0); }); diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index 5da7956b22..7c11246cff 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -25,6 +25,10 @@ public: }; typedef std::shared_ptr SceneContextPointer; +// see examples/utilities/tools/renderEngineDebug.js +const int showDisplayStatusFlag = 1; +const int showNetworkStatusFlag = 2; + class RenderContext { public: @@ -49,7 +53,7 @@ public: int _numDrawnOverlay3DItems = 0; int _maxDrawnOverlay3DItems = -1; - bool _drawItemStatus = false; + int _drawItemStatus = 0; bool _drawHitEffect = false; bool _occlusionStatus = false; diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index 95919d6c0c..6be0ce44a8 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -107,8 +107,8 @@ public: Q_INVOKABLE void setEngineMaxDrawnOverlay3DItems(int count) { _maxDrawnOverlay3DItems = count; } Q_INVOKABLE int getEngineMaxDrawnOverlay3DItems() { return _maxDrawnOverlay3DItems; } - Q_INVOKABLE void setEngineDisplayItemStatus(bool display) { _drawItemStatus = display; } - Q_INVOKABLE bool doEngineDisplayItemStatus() { return _drawItemStatus; } + Q_INVOKABLE void setEngineDisplayItemStatus(int display) { _drawItemStatus = display; } + Q_INVOKABLE int doEngineDisplayItemStatus() { return _drawItemStatus; } Q_INVOKABLE void setEngineDisplayHitEffect(bool display) { _drawHitEffect = display; } Q_INVOKABLE bool doEngineDisplayHitEffect() { return _drawHitEffect; } @@ -143,7 +143,7 @@ protected: int _maxDrawnTransparentItems = -1; int _maxDrawnOverlay3DItems = -1; - bool _drawItemStatus = false; + int _drawItemStatus = 0; bool _drawHitEffect = false; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 381eef63db..405815aa5b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -1170,8 +1171,7 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { recurseGuard = false; } - -void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) { +void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " @@ -1180,7 +1180,8 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, methodName)); + Q_ARG(const QString&, methodName), + Q_ARG(const QStringList&, params)); return; } #ifdef THREAD_DEBUGGING @@ -1195,6 +1196,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS if (entityScript.property(methodName).isFunction()) { QScriptValueList args; args << entityID.toScriptValue(this); + args << qScriptValueFromSequence(this, params); entityScript.property(methodName).call(entityScript, args); } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 43fbdc1b0e..c957b0c3b4 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -115,7 +116,7 @@ public: Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload = false); // will call the preload method once loaded Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method Q_INVOKABLE void unloadAllEntityScripts(); - Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName); + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList()); Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event); Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); diff --git a/libraries/shared/src/DependencyManager.cpp b/libraries/shared/src/DependencyManager.cpp index 5f78f6bcd5..a870feab98 100644 --- a/libraries/shared/src/DependencyManager.cpp +++ b/libraries/shared/src/DependencyManager.cpp @@ -11,8 +11,16 @@ #include "DependencyManager.h" -DependencyManager DependencyManager::_manager; +#include "SharedUtil.h" +#include "Finally.h" + +static const char* const DEPENDENCY_PROPERTY_NAME = "com.highfidelity.DependencyMananger"; + +DependencyManager& DependencyManager::manager() { + static DependencyManager* instance = globalInstance(DEPENDENCY_PROPERTY_NAME); + return *instance; +} QSharedPointer& DependencyManager::safeGet(size_t hashCode) { return _instanceHash[hashCode]; -} \ No newline at end of file +} diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h index db41e72e1e..c0568bc752 100644 --- a/libraries/shared/src/DependencyManager.h +++ b/libraries/shared/src/DependencyManager.h @@ -62,8 +62,8 @@ public: static void registerInheritance(); private: - static DependencyManager _manager; - + static DependencyManager& manager(); + template size_t getHashCode(); @@ -75,11 +75,11 @@ private: template QSharedPointer DependencyManager::get() { - static size_t hashCode = _manager.getHashCode(); + static size_t hashCode = manager().getHashCode(); static QWeakPointer instance; if (instance.isNull()) { - instance = qSharedPointerCast(_manager.safeGet(hashCode)); + instance = qSharedPointerCast(manager().safeGet(hashCode)); if (instance.isNull()) { qWarning() << "DependencyManager::get(): No instance available for" << typeid(T).name(); @@ -91,9 +91,9 @@ QSharedPointer DependencyManager::get() { template QSharedPointer DependencyManager::set(Args&&... args) { - static size_t hashCode = _manager.getHashCode(); + static size_t hashCode = manager().getHashCode(); - QSharedPointer& instance = _manager.safeGet(hashCode); + QSharedPointer& instance = manager().safeGet(hashCode); instance.clear(); // Clear instance before creation of new one to avoid edge cases QSharedPointer newInstance(new T(args...), &T::customDeleter); QSharedPointer storedInstance = qSharedPointerCast(newInstance); @@ -104,9 +104,9 @@ QSharedPointer DependencyManager::set(Args&&... args) { template QSharedPointer DependencyManager::set(Args&&... args) { - static size_t hashCode = _manager.getHashCode(); + static size_t hashCode = manager().getHashCode(); - QSharedPointer& instance = _manager.safeGet(hashCode); + QSharedPointer& instance = manager().safeGet(hashCode); instance.clear(); // Clear instance before creation of new one to avoid edge cases QSharedPointer newInstance(new I(args...), &I::customDeleter); QSharedPointer storedInstance = qSharedPointerCast(newInstance); @@ -117,15 +117,15 @@ QSharedPointer DependencyManager::set(Args&&... args) { template void DependencyManager::destroy() { - static size_t hashCode = _manager.getHashCode(); - _manager.safeGet(hashCode).clear(); + static size_t hashCode = manager().getHashCode(); + manager().safeGet(hashCode).clear(); } template void DependencyManager::registerInheritance() { size_t baseHashCode = typeid(Base).hash_code(); size_t derivedHashCode = typeid(Derived).hash_code(); - _manager._inheritanceHash.insert(baseHashCode, derivedHashCode); + manager()._inheritanceHash.insert(baseHashCode, derivedHashCode); } template diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h index 25eed96490..fcacf7aaed 100644 --- a/libraries/shared/src/RenderArgs.h +++ b/libraries/shared/src/RenderArgs.h @@ -68,17 +68,16 @@ public: class RenderArgs { public: typedef std::function ShoudRenderFunctor; - + enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE }; enum RenderSide { MONO, STEREO_LEFT, STEREO_RIGHT }; enum DebugFlags { RENDER_DEBUG_NONE = 0, - RENDER_DEBUG_HULLS = 1, - RENDER_DEBUG_SIMULATION_OWNERSHIP = 2, + RENDER_DEBUG_HULLS = 1 }; - + RenderArgs(std::shared_ptr context = nullptr, OctreeRenderer* renderer = nullptr, ViewFrustum* viewFrustum = nullptr, diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index cd4f734d40..89ccba1479 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -13,6 +13,7 @@ #define hifi_SharedUtil_h #include +#include #include #include @@ -20,7 +21,36 @@ #include // not on windows, not needed for mac or windows #endif -#include +#include +#include + +// Provides efficient access to a named global type. By storing the value +// in the QApplication by name we can implement the singleton pattern and +// have the single instance function across DLL boundaries. +template +T* globalInstance(const char* propertyName, Args&&... args) { + static std::unique_ptr instancePtr; + static T* resultInstance { nullptr }; + static std::mutex mutex; + if (!resultInstance) { + std::unique_lock lock(mutex); + if (!resultInstance) { + auto variant = qApp->property(propertyName); + if (variant.isNull()) { + // Since we're building the object, store it in a shared_ptr so it's + // destroyed by the destructor of the static instancePtr + instancePtr = std::unique_ptr(new T(std::forward(args)...)); + + void* voidInstance = &(*instancePtr); + variant = QVariant::fromValue(voidInstance); + qApp->setProperty(propertyName, variant); + } + void* returnedVoidInstance = variant.value(); + resultInstance = static_cast(returnedVoidInstance); + } + } + return resultInstance; +} const int BYTES_PER_COLOR = 3; const int BYTES_PER_FLAGS = 1; diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 139d9b282c..664a894e44 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -132,8 +132,7 @@ int main(int argc, char** argv) { inputPlugin->activate(); auto userInputMapper = DependencyManager::get(); if (name == KeyboardMouseDevice::NAME) { - auto keyboardMouseDevice = static_cast(inputPlugin.data()); // TODO: this seems super hacky - userInputMapper->registerDevice(std::shared_ptr(keyboardMouseDevice)); + userInputMapper->registerDevice(std::dynamic_pointer_cast(inputPlugin)->getInputDevice()); } inputPlugin->pluginUpdate(0, false); } diff --git a/tests/recording/CMakeLists.txt b/tests/recording/CMakeLists.txt new file mode 100644 index 0000000000..a523947f52 --- /dev/null +++ b/tests/recording/CMakeLists.txt @@ -0,0 +1,16 @@ +set(TARGET_NAME recording-test) +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Test) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") +link_hifi_libraries(shared recording) +copy_dlls_beside_windows_executable() + +# FIXME convert to unit tests +# Declare dependencies +#macro (setup_testcase_dependencies) +# # link in the shared libraries +# link_hifi_libraries(shared recording) +# +# copy_dlls_beside_windows_executable() +#endmacro () +#setup_hifi_testcase() diff --git a/tests/recording/src/Constants.h b/tests/recording/src/Constants.h new file mode 100644 index 0000000000..2aa19fe786 --- /dev/null +++ b/tests/recording/src/Constants.h @@ -0,0 +1,21 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 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 +// + +#pragma once + +#ifndef hifi_Constants_h +#define hifi_Constants_h + +#include + +static const QString HEADER_NAME = "com.highfidelity.recording.Header"; +static const QString TEST_NAME = "com.highfidelity.recording.Test"; + +#endif // hifi_FrameTests_h + + diff --git a/tests/recording/src/FrameTests.cpp b/tests/recording/src/FrameTests.cpp new file mode 100644 index 0000000000..ebd7f90e31 --- /dev/null +++ b/tests/recording/src/FrameTests.cpp @@ -0,0 +1,33 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 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 +// + +#include "FrameTests.h" +#include "Constants.h" + +#if 0 + +#include "../QTestExtensions.h" + +#include + +QTEST_MAIN(FrameTests) + +void FrameTests::registerFrameTypeTest() { + auto result = recording::Frame::registerFrameType(TEST_NAME); + QCOMPARE(result, (recording::FrameType)1); + auto forwardMap = recording::Frame::getFrameTypes(); + QCOMPARE(forwardMap.count(TEST_NAME), 1); + QCOMPARE(forwardMap[TEST_NAME], result); + QCOMPARE(forwardMap[HEADER_NAME], recording::Frame::TYPE_HEADER); + auto backMap = recording::Frame::getFrameTypeNames(); + QCOMPARE(backMap.count(result), 1); + QCOMPARE(backMap[result], TEST_NAME); + QCOMPARE(backMap[recording::Frame::TYPE_HEADER], HEADER_NAME); +} + +#endif diff --git a/tests/recording/src/FrameTests.h b/tests/recording/src/FrameTests.h new file mode 100644 index 0000000000..bdf4542846 --- /dev/null +++ b/tests/recording/src/FrameTests.h @@ -0,0 +1,24 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 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 +// + +#pragma once +#ifndef hifi_FrameTests_h +#define hifi_FrameTests_h + +#if 0 +#include + +class FrameTests : public QObject { + Q_OBJECT +private slots: + void registerFrameTypeTest(); +}; + +#endif + +#endif // hifi_FrameTests_h diff --git a/tests/recording/src/RecorderTests.cpp b/tests/recording/src/RecorderTests.cpp new file mode 100644 index 0000000000..b102a3c931 --- /dev/null +++ b/tests/recording/src/RecorderTests.cpp @@ -0,0 +1,29 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 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 +// + +#include "RecorderTests.h" +#include "Constants.h" + +#if 0 + +#include "../QTestExtensions.h" + +#include + +QTEST_MAIN(RecorderTests) + +void RecorderTests::recorderTest() { + //auto recorder = std::make_shared(); + //QCOMPARE(recoreder.isRecording(), false); + //recorder.start(); + //QCOMPARE(recoreder.isRecording(), true); + //recorder.stop(); + //QCOMPARE(recoreder.isRecording(), false); +} + +#endif diff --git a/tests/recording/src/RecorderTests.h b/tests/recording/src/RecorderTests.h new file mode 100644 index 0000000000..9bfd8e2d10 --- /dev/null +++ b/tests/recording/src/RecorderTests.h @@ -0,0 +1,25 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 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 +// + +#pragma once +#ifndef hifi_RecorderTests_h +#define hifi_RecorderTests_h + +#if 0 + +#include + +class RecorderTests : public QObject { + Q_OBJECT +private slots: + void recorderTest(); +}; + +#endif + +#endif diff --git a/tests/recording/src/main.cpp b/tests/recording/src/main.cpp new file mode 100644 index 0000000000..f4049b04b7 --- /dev/null +++ b/tests/recording/src/main.cpp @@ -0,0 +1,114 @@ +#include +#include +#include +#include + +#ifdef Q_OS_WIN32 +#include +#endif + +#include +#include + +#include "Constants.h" + +using namespace recording; +FrameType TEST_FRAME_TYPE { Frame::TYPE_INVALID }; + +void testFrameTypeRegistration() { + TEST_FRAME_TYPE = Frame::registerFrameType(TEST_NAME); + QVERIFY(TEST_FRAME_TYPE != Frame::TYPE_INVALID); + QVERIFY(TEST_FRAME_TYPE != Frame::TYPE_HEADER); + + auto forwardMap = recording::Frame::getFrameTypes(); + QVERIFY(forwardMap.count(TEST_NAME) == 1); + QVERIFY(forwardMap[TEST_NAME] == TEST_FRAME_TYPE); + QVERIFY(forwardMap[HEADER_NAME] == recording::Frame::TYPE_HEADER); + + auto backMap = recording::Frame::getFrameTypeNames(); + QVERIFY(backMap.count(TEST_FRAME_TYPE) == 1); + QVERIFY(backMap[TEST_FRAME_TYPE] == TEST_NAME); + auto typeHeader = recording::Frame::TYPE_HEADER; + QVERIFY(backMap[typeHeader] == HEADER_NAME); +} + +void testFilePersist() { + QTemporaryFile file; + QString fileName; + if (file.open()) { + fileName = file.fileName(); + file.close(); + } + auto readClip = Clip::fromFile(fileName); + QVERIFY(Clip::Pointer() == readClip); + auto writeClip = Clip::newClip(); + writeClip->addFrame(std::make_shared(TEST_FRAME_TYPE, 5.0f, QByteArray())); + QVERIFY(writeClip->frameCount() == 1); + QVERIFY(writeClip->duration() == 5.0f); + + Clip::toFile(fileName, writeClip); + readClip = Clip::fromFile(fileName); + QVERIFY(readClip != Clip::Pointer()); + QVERIFY(readClip->frameCount() == 1); + QVERIFY(readClip->duration() == 5.0f); + readClip->seek(0); + writeClip->seek(0); + + size_t count = 0; + for (auto readFrame = readClip->nextFrame(), writeFrame = writeClip->nextFrame(); readFrame && writeFrame; + readFrame = readClip->nextFrame(), writeFrame = writeClip->nextFrame(), ++count) { + QVERIFY(readFrame->type == writeFrame->type); + QVERIFY(readFrame->timeOffset == writeFrame->timeOffset); + QVERIFY(readFrame->data == writeFrame->data); + } + QVERIFY(readClip->frameCount() == count); + + + writeClip = Clip::newClip(); + writeClip->addFrame(std::make_shared(TEST_FRAME_TYPE, 5.0f, QByteArray())); + // Simulate an unknown frametype + writeClip->addFrame(std::make_shared(Frame::TYPE_INVALID - 1, 10.0f, QByteArray())); + QVERIFY(writeClip->frameCount() == 2); + QVERIFY(writeClip->duration() == 10.0f); + Clip::toFile(fileName, writeClip); + + // Verify that the read version of the clip ignores the unknown frame type + readClip = Clip::fromFile(fileName); + QVERIFY(readClip != Clip::Pointer()); + QVERIFY(readClip->frameCount() == 1); + QVERIFY(readClip->duration() == 5.0f); +} + +void testClipOrdering() { + auto writeClip = Clip::newClip(); + // simulate our of order addition of frames + writeClip->addFrame(std::make_shared(TEST_FRAME_TYPE, 10.0f, QByteArray())); + writeClip->addFrame(std::make_shared(TEST_FRAME_TYPE, 5.0f, QByteArray())); + QVERIFY(writeClip->frameCount() == 2); + QVERIFY(writeClip->duration() == 10.0f); + + QVERIFY(std::numeric_limits::max() == writeClip->position()); + writeClip->seek(0); + QVERIFY(5.0f == writeClip->position()); + float lastFrameTimeOffset { 0 }; + for (auto writeFrame = writeClip->nextFrame(); writeFrame; writeFrame = writeClip->nextFrame()) { + QVERIFY(writeClip->position() >= lastFrameTimeOffset); + } + Q_UNUSED(lastFrameTimeOffset); // FIXME - Unix build not yet upgraded to Qt 5.5.1 we can remove this once it is +} + +#ifdef Q_OS_WIN32 +void myMessageHandler(QtMsgType type, const QMessageLogContext & context, const QString & msg) { + OutputDebugStringA(msg.toLocal8Bit().toStdString().c_str()); + OutputDebugStringA("\n"); +} +#endif + +int main(int, const char**) { +#ifdef Q_OS_WIN32 + qInstallMessageHandler(myMessageHandler); +#endif + testFrameTypeRegistration(); + testFilePersist(); + testClipOrdering(); +} \ No newline at end of file diff --git a/unpublishedScripts/basketballsResetter.js b/unpublishedScripts/basketballsResetter.js index 4d02296e8e..791ac1c0cf 100644 --- a/unpublishedScripts/basketballsResetter.js +++ b/unpublishedScripts/basketballsResetter.js @@ -82,7 +82,7 @@ var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; z: 0 }, collisionsWillMove: true, - collisionsSoundURL: basketballCollisionSoundURL, + collisionSoundURL: basketballCollisionSoundURL, ignoreForCollisions: false, modelURL: basketballURL, userData: JSON.stringify({ diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index d343c52497..a8b4383b04 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -308,6 +308,7 @@ z: 0 }, collisionsWillMove: true, + collisionSoundURL: 'http://hifi-public.s3.amazonaws.com/sounds/basketball/basketball.wav', ignoreForCollisions: false, modelURL: basketballURL, userData: JSON.stringify({ @@ -886,8 +887,8 @@ function createPingPongBallGun() { var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx'; - var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_collision_hull.obj'; - + var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_convex.obj'; + var COLLISION_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/plastic_impact.L.wav'; var position = { x: 548.6, y: 495.4, @@ -899,7 +900,8 @@ var pingPongGun = Entities.addEntity({ type: "Model", modelURL: MODEL_URL, - shapeType: 'box', + shapeType: 'compound', + compoundShapeURL: COLLISION_HULL_URL, script: pingPongScriptURL, position: position, rotation: rotation, @@ -914,6 +916,7 @@ z: 0.47 }, collisionsWillMove: true, + collisionSoundURL: COLLISION_SOUND_URL, userData: JSON.stringify({ resetMe: { resetMe: true @@ -927,9 +930,8 @@ } function createWand(position) { - var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx'; - var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/actual_no_top_collision_hull.obj'; - + var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/wand.fbx'; + var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/actual_no_top_collision_hull.obj'; var entity = Entities.addEntity({ name: 'Bubble Wand', type: "Model", @@ -1259,4 +1261,4 @@ }; // entity scripts always need to return a newly constructed object of our type return new ResetSwitch(); -}); +}); \ No newline at end of file diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js index 364807f42f..1b4c83a1d5 100644 --- a/unpublishedScripts/masterReset.js +++ b/unpublishedScripts/masterReset.js @@ -248,7 +248,7 @@ MasterReset = function() { }, grabbableKey: { grabbable: false, - wantsTrigger:true + wantsTrigger: true } }) }); @@ -289,6 +289,7 @@ MasterReset = function() { z: 0 }, collisionsWillMove: true, + collisionSoundURL: 'http://hifi-public.s3.amazonaws.com/sounds/basketball/basketball.wav', ignoreForCollisions: false, modelURL: basketballURL, userData: JSON.stringify({ @@ -334,7 +335,7 @@ MasterReset = function() { name: "Basketball Resetter", script: basketballResetterScriptURL, dimensions: dimensions, - visible:false, + visible: false, userData: JSON.stringify({ resetMe: { resetMe: true @@ -367,7 +368,7 @@ MasterReset = function() { name: "Target Resetter", script: targetsResetterScriptURL, dimensions: dimensions, - visible:false, + visible: false, userData: JSON.stringify({ resetMe: { resetMe: true @@ -868,8 +869,8 @@ MasterReset = function() { function createPingPongBallGun() { var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx'; - var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_collision_hull.obj'; - + var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_convex.obj'; + var COLLISION_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/plastic_impact.L.wav'; var position = { x: 548.6, y: 495.4, @@ -881,7 +882,8 @@ MasterReset = function() { var pingPongGun = Entities.addEntity({ type: "Model", modelURL: MODEL_URL, - shapeType: 'box', + shapeType: 'compound', + compoundShapeURL:COLLISION_HULL_URL, script: pingPongScriptURL, position: position, rotation: rotation, @@ -896,6 +898,7 @@ MasterReset = function() { z: 0.47 }, collisionsWillMove: true, + collisionSoundURL: COLLISION_SOUND_URL, userData: JSON.stringify({ resetMe: { resetMe: true @@ -909,8 +912,8 @@ MasterReset = function() { } function createWand(position) { - var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx'; - var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/actual_no_top_collision_hull.obj'; + var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/wand.fbx'; + var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/actual_no_top_collision_hull.obj'; var entity = Entities.addEntity({ name: 'Bubble Wand', @@ -1238,4 +1241,4 @@ MasterReset = function() { Script.scriptEnding.connect(cleanup); } -}; +}; \ No newline at end of file