diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 2c3ee65c92..a4c8c36169 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -14,11 +14,9 @@ Script.load("edit.js"); Script.load("examples.js"); Script.load("selectAudioDevice.js"); Script.load("notifications.js"); -Script.load("users.js"); Script.load("controllers/handControllerGrab.js"); Script.load("controllers/squeezeHands.js"); Script.load("grab.js"); Script.load("directory.js"); Script.load("dialTone.js"); -// Script.load("attachedEntitiesManager.js"); Script.load("depthReticle.js"); diff --git a/examples/libraries/jasmine/hifi-boot.js b/examples/libraries/jasmine/hifi-boot.js new file mode 100644 index 0000000000..0c66a925af --- /dev/null +++ b/examples/libraries/jasmine/hifi-boot.js @@ -0,0 +1,53 @@ + +(function() { + function ConsoleReporter(options) { + this.jasmineStarted = function (obj) { + print("jasmineStarted: numSpecs = " + obj.totalSpecsDefined); + }; + this.jasmineDone = function (obj) { + print("jasmineDone"); + }; + this.suiteStarted = function(obj) { + print("suiteStarted: \"" + obj.fullName + "\""); + }; + this.suiteDone = function(obj) { + print("suiteDone: \"" + obj.fullName + "\" " + obj.status); + }; + this.specStarted = function(obj) { + print("specStarted: \"" + obj.fullName + "\""); + }; + this.specDone = function(obj) { + print("specDone: \"" + obj.fullName + "\" " + obj.status); + + var i, l = obj.failedExpectations.length; + for (i = 0; i < l; i++) { + print(" " + obj.failedExpectations[i].message); + } + }; + return this; + } + + setTimeout = Script.setTimeout; + setInterval = Script.setInterval; + clearTimeout = Script.clearTimeout; + clearInterval = Script.clearInterval; + + var jasmine = jasmineRequire.core(jasmineRequire); + + var env = jasmine.getEnv(); + + env.addReporter(new ConsoleReporter()); + + var jasmineInterface = jasmineRequire.interface(jasmine, env); + + extend(this, jasmineInterface); + + function extend(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; + } + +}()); + diff --git a/examples/libraries/jasmine/jasmine.js b/examples/libraries/jasmine/jasmine.js new file mode 100644 index 0000000000..32667abf98 --- /dev/null +++ b/examples/libraries/jasmine/jasmine.js @@ -0,0 +1,3458 @@ +/* +Copyright (c) 2008-2015 Pivotal Labs + +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. +*/ + +var getJasmineRequireObj = (function (jasmineGlobal) { + var jasmineRequire; + + if (typeof module !== 'undefined' && module.exports) { + if (typeof global !== 'undefined') { + jasmineGlobal = global; + } else { + jasmineGlobal = {}; + } + jasmineRequire = exports; + } else { + if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') { + jasmineGlobal = window; + } + jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; + this.jasmineRequire = jasmineRequire; + } + + function getJasmineRequire() { + return jasmineRequire; + } + + getJasmineRequire().core = function(jRequire) { + var j$ = {}; + + jRequire.base(j$, jasmineGlobal); + j$.util = jRequire.util(); + j$.errors = jRequire.errors(); + j$.Any = jRequire.Any(j$); + j$.Anything = jRequire.Anything(j$); + j$.CallTracker = jRequire.CallTracker(); + j$.MockDate = jRequire.MockDate(); + j$.Clock = jRequire.Clock(); + j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); + j$.Env = jRequire.Env(j$); + j$.ExceptionFormatter = jRequire.ExceptionFormatter(); + j$.Expectation = jRequire.Expectation(); + j$.buildExpectationResult = jRequire.buildExpectationResult(); + j$.JsApiReporter = jRequire.JsApiReporter(); + j$.matchersUtil = jRequire.matchersUtil(j$); + j$.ObjectContaining = jRequire.ObjectContaining(j$); + j$.ArrayContaining = jRequire.ArrayContaining(j$); + j$.pp = jRequire.pp(j$); + j$.QueueRunner = jRequire.QueueRunner(j$); + j$.ReportDispatcher = jRequire.ReportDispatcher(); + j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); + j$.SpyStrategy = jRequire.SpyStrategy(); + j$.StringMatching = jRequire.StringMatching(j$); + j$.Suite = jRequire.Suite(j$); + j$.Timer = jRequire.Timer(); + j$.TreeProcessor = jRequire.TreeProcessor(); + j$.version = jRequire.version(); + j$.Order = jRequire.Order(); + + j$.matchers = jRequire.requireMatchers(jRequire, j$); + + return j$; + }; + + return getJasmineRequire; +})(this); + +getJasmineRequireObj().requireMatchers = function(jRequire, j$) { + var availableMatchers = [ + 'toBe', + 'toBeCloseTo', + 'toBeDefined', + 'toBeFalsy', + 'toBeGreaterThan', + 'toBeLessThan', + 'toBeNaN', + 'toBeNull', + 'toBeTruthy', + 'toBeUndefined', + 'toContain', + 'toEqual', + 'toHaveBeenCalled', + 'toHaveBeenCalledWith', + 'toHaveBeenCalledTimes', + 'toMatch', + 'toThrow', + 'toThrowError' + ], + matchers = {}; + + for (var i = 0; i < availableMatchers.length; i++) { + var name = availableMatchers[i]; + matchers[name] = jRequire[name](j$); + } + + return matchers; +}; + +getJasmineRequireObj().base = function(j$, jasmineGlobal) { + j$.unimplementedMethod_ = function() { + throw new Error('unimplemented method'); + }; + + j$.MAX_PRETTY_PRINT_DEPTH = 40; + j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; + j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + + j$.getGlobal = function() { + return jasmineGlobal; + }; + + j$.getEnv = function(options) { + var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); + //jasmine. singletons in here (setTimeout blah blah). + return env; + }; + + j$.isArray_ = function(value) { + return j$.isA_('Array', value); + }; + + j$.isString_ = function(value) { + return j$.isA_('String', value); + }; + + j$.isNumber_ = function(value) { + return j$.isA_('Number', value); + }; + + j$.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; + }; + + j$.isDomNode = function(obj) { + return obj.nodeType > 0; + }; + + j$.fnNameFor = function(func) { + return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; + }; + + j$.any = function(clazz) { + return new j$.Any(clazz); + }; + + j$.anything = function() { + return new j$.Anything(); + }; + + j$.objectContaining = function(sample) { + return new j$.ObjectContaining(sample); + }; + + j$.stringMatching = function(expected) { + return new j$.StringMatching(expected); + }; + + j$.arrayContaining = function(sample) { + return new j$.ArrayContaining(sample); + }; + + j$.createSpy = function(name, originalFn) { + + var spyStrategy = new j$.SpyStrategy({ + name: name, + fn: originalFn, + getSpy: function() { return spy; } + }), + callTracker = new j$.CallTracker(), + spy = function() { + var callData = { + object: this, + args: Array.prototype.slice.apply(arguments) + }; + + callTracker.track(callData); + var returnValue = spyStrategy.exec.apply(this, arguments); + callData.returnValue = returnValue; + + return returnValue; + }; + + for (var prop in originalFn) { + if (prop === 'and' || prop === 'calls') { + throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); + } + + spy[prop] = originalFn[prop]; + } + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + j$.createSpyObj = function(baseName, methodNames) { + if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + if (!j$.isArray_(methodNames) || methodNames.length === 0) { + throw 'createSpyObj requires a non-empty array of method names to create spies for'; + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + } + return obj; + }; +}; + +getJasmineRequireObj().util = function() { + + var util = {}; + + util.inherit = function(childClass, parentClass) { + var Subclass = function() { + }; + Subclass.prototype = parentClass.prototype; + childClass.prototype = new Subclass(); + }; + + util.htmlEscape = function(str) { + if (!str) { + return str; + } + return str.replace(/&/g, '&') + .replace(//g, '>'); + }; + + util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) { + arrayOfArgs.push(args[i]); + } + return arrayOfArgs; + }; + + util.isUndefined = function(obj) { + return obj === void 0; + }; + + util.arrayContains = function(array, search) { + var i = array.length; + while (i--) { + if (array[i] === search) { + return true; + } + } + return false; + }; + + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + }; + + return util; +}; + +getJasmineRequireObj().Spec = function(j$) { + function Spec(attrs) { + this.expectationFactory = attrs.expectationFactory; + this.resultCallback = attrs.resultCallback || function() {}; + this.id = attrs.id; + this.description = attrs.description || ''; + this.queueableFn = attrs.queueableFn; + this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; + this.userContext = attrs.userContext || function() { return {}; }; + this.onStart = attrs.onStart || function() {}; + this.getSpecName = attrs.getSpecName || function() { return ''; }; + this.expectationResultFactory = attrs.expectationResultFactory || function() { }; + this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; + this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + if (!this.queueableFn.fn) { + this.pend(); + } + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [], + passedExpectations: [], + pendingReason: '' + }; + } + + Spec.prototype.addExpectationResult = function(passed, data, isError) { + var expectationResult = this.expectationResultFactory(data); + if (passed) { + this.result.passedExpectations.push(expectationResult); + } else { + this.result.failedExpectations.push(expectationResult); + + if (this.throwOnExpectationFailure && !isError) { + throw new j$.errors.ExpectationFailed(); + } + } + }; + + Spec.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Spec.prototype.execute = function(onComplete, enabled) { + var self = this; + + this.onStart(this); + + if (!this.isExecutable() || this.markedPending || enabled === false) { + complete(enabled); + return; + } + + var fns = this.beforeAndAfterFns(); + var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); + + this.queueRunnerFactory({ + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, + onComplete: complete, + userContext: this.userContext() + }); + + function complete(enabledAgain) { + self.result.status = self.status(enabledAgain); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + }; + + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(extractCustomPendingMessage(e)); + return; + } + + if (e instanceof j$.errors.ExpectationFailed) { + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }, true); + }; + + Spec.prototype.disable = function() { + this.disabled = true; + }; + + Spec.prototype.pend = function(message) { + this.markedPending = true; + if (message) { + this.result.pendingReason = message; + } + }; + + Spec.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Spec.prototype.status = function(enabled) { + if (this.disabled || enabled === false) { + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'passed'; + } + }; + + Spec.prototype.isExecutable = function() { + return !this.disabled; + }; + + Spec.prototype.getFullName = function() { + return this.getSpecName(this); + }; + + var extractCustomPendingMessage = function(e) { + var fullMessage = e.toString(), + boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), + boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; + + return fullMessage.substr(boilerplateEnd); + }; + + Spec.pendingSpecExceptionMessage = '=> marked Pending'; + + Spec.isPendingSpecException = function(e) { + return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); + }; + + return Spec; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Spec = jasmineRequire.Spec; +} + +/*jshint bitwise: false*/ + +getJasmineRequireObj().Order = function() { + function Order(options) { + this.random = 'random' in options ? options.random : true; + var seed = this.seed = options.seed || generateSeed(); + this.sort = this.random ? randomOrder : naturalOrder; + + function naturalOrder(items) { + return items; + } + + function randomOrder(items) { + var copy = items.slice(); + copy.sort(function(a, b) { + return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id); + }); + return copy; + } + + function generateSeed() { + return String(Math.random()).slice(-5); + } + + // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function + // used to get a different output when the key changes slighly. + // We use your return to sort the children randomly in a consistent way when + // used in conjunction with a seed + + function jenkinsHash(key) { + var hash, i; + for(hash = i = 0; i < key.length; ++i) { + hash += key.charCodeAt(i); + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; + } + + } + + return Order; +}; + +getJasmineRequireObj().Env = function(j$) { + function Env(options) { + options = options || {}; + + var self = this; + var global = options.global || j$.getGlobal(); + + var totalSpecsDefined = 0; + + var catchExceptions = true; + + var realSetTimeout = j$.getGlobal().setTimeout; + var realClearTimeout = j$.getGlobal().clearTimeout; + this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global)); + + var runnableLookupTable = {}; + var runnableResources = {}; + + var currentSpec = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + var throwOnExpectationFailure = false; + var random = false; + var seed = null; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + var currentRunnable = function() { + return currentSpec || currentSuite(); + }; + + var reporter = new j$.ReportDispatcher([ + 'jasmineStarted', + 'jasmineDone', + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone' + ]); + + this.specFilter = function() { + return true; + }; + + this.addCustomEqualityTester = function(tester) { + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } + }; + + j$.Expectation.addCoreMatchers(j$.matchers); + + var nextSpecId = 0; + var getNextSpecId = function() { + return 'spec' + nextSpecId++; + }; + + var nextSuiteId = 0; + var getNextSuiteId = function() { + return 'suite' + nextSuiteId++; + }; + + var expectationFactory = function(actual, spec) { + return j$.Expectation.Factory({ + util: j$.matchersUtil, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, + actual: actual, + addExpectationResult: addExpectationResult + }); + + function addExpectationResult(passed, result) { + return spec.addExpectationResult(passed, result); + } + }; + + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; + }; + + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + var beforeAndAfterFns = function(suite) { + return function() { + var befores = [], + afters = []; + + while(suite) { + befores = befores.concat(suite.beforeFns); + afters = afters.concat(suite.afterFns); + + suite = suite.parentSuite; + } + + return { + befores: befores.reverse(), + afters: afters + }; + }; + }; + + var getSpecName = function(spec, suite) { + return suite.getFullName() + ' ' + spec.description; + }; + + // TODO: we may just be able to pass in the fn instead of wrapping here + var buildExpectationResult = j$.buildExpectationResult, + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; + + return buildExpectationResult(attrs); + }; + + // TODO: fix this naming, and here's where the value comes in + this.catchExceptions = function(value) { + catchExceptions = !!value; + return catchExceptions; + }; + + this.catchingExceptions = function() { + return catchExceptions; + }; + + var maximumSpecCallbackDepth = 20; + var currentSpecCallbackDepth = 0; + + function clearStack(fn) { + currentSpecCallbackDepth++; + if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { + currentSpecCallbackDepth = 0; + realSetTimeout(fn, 0); + } else { + fn(); + } + } + + var catchException = function(e) { + return j$.Spec.isPendingSpecException(e) || catchExceptions; + }; + + this.throwOnExpectationFailure = function(value) { + throwOnExpectationFailure = !!value; + }; + + this.throwingExpectationFailures = function() { + return throwOnExpectationFailure; + }; + + this.randomizeTests = function(value) { + random = !!value; + }; + + this.randomTests = function() { + return random; + }; + + this.seed = function(value) { + if (value) { + seed = value; + } + return seed; + }; + + var queueRunnerFactory = function(options) { + options.catchException = catchException; + options.clearStack = options.clearStack || clearStack; + options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; + options.fail = self.fail; + + new j$.QueueRunner(options).execute(); + }; + + var topSuite = new j$.Suite({ + env: this, + id: getNextSuiteId(), + description: 'Jasmine__TopLevel__Suite', + queueRunner: queueRunnerFactory + }); + runnableLookupTable[topSuite.id] = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; + + this.topSuite = function() { + return topSuite; + }; + + this.execute = function(runnablesToRun) { + if(!runnablesToRun) { + if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } + } + + var order = new j$.Order({ + random: random, + seed: seed + }); + + var processor = new j$.TreeProcessor({ + tree: topSuite, + runnableIds: runnablesToRun, + queueRunnerFactory: queueRunnerFactory, + nodeStart: function(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + }, + nodeComplete: function(suite, result) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + } + currentlyExecutingSuites.pop(); + reporter.suiteDone(result); + }, + orderChildren: function(node) { + return order.sort(node.children); + } + }); + + if(!processor.processTree().valid) { + throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times'); + } + + reporter.jasmineStarted({ + totalSpecsDefined: totalSpecsDefined + }); + + processor.execute(function() { + reporter.jasmineDone({ + order: order + }); + }); + }; + + this.addReporter = function(reporterToAdd) { + reporter.addReporter(reporterToAdd); + }; + + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } + return runnableResources[currentRunnable().id].spies; + }}); + + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); + }; + + var suiteFactory = function(description) { + var suite = new j$.Suite({ + env: self, + id: getNextSuiteId(), + description: description, + parentSuite: currentDeclarationSuite, + expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, + throwOnExpectationFailure: throwOnExpectationFailure + }); + + runnableLookupTable[suite.id] = suite; + return suite; + }; + + this.describe = function(description, specDefinitions) { + var suite = suiteFactory(description); + if (specDefinitions.length > 0) { + throw new Error('describe does not expect a done parameter'); + } + if (currentDeclarationSuite.markedPending) { + suite.pend(); + } + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + + this.xdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.pend(); + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + + var focusedRunnables = []; + + this.fdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { + var parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + self.it('encountered a declaration exception', function() { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + } + + function findFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return suite.id; + } + suite = suite.parentSuite; + } + + return null; + } + + function unfocusAncestor() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } + } + + var specFactory = function(description, fn, suite, timeout) { + totalSpecsDefined++; + var spec = new j$.Spec({ + id: getNextSpecId(), + beforeAndAfterFns: beforeAndAfterFns(suite), + expectationFactory: expectationFactory, + resultCallback: specResultCallback, + getSpecName: function(spec) { + return getSpecName(spec, suite); + }, + onStart: specStarted, + description: description, + expectationResultFactory: expectationResultFactory, + queueRunnerFactory: queueRunnerFactory, + userContext: function() { return suite.clonedSharedUserContext(); }, + queueableFn: { + fn: fn, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }, + throwOnExpectationFailure: throwOnExpectationFailure + }); + + runnableLookupTable[spec.id] = spec; + + if (!self.specFilter(spec)) { + spec.disable(); + } + + return spec; + + function specResultCallback(result) { + clearResourcesForRunnable(spec.id); + currentSpec = null; + reporter.specDone(result); + } + + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } + }; + + this.it = function(description, fn, timeout) { + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); + if (currentDeclarationSuite.markedPending) { + spec.pend(); + } + currentDeclarationSuite.addChild(spec); + return spec; + }; + + this.xit = function() { + var spec = this.it.apply(this, arguments); + spec.pend('Temporarily disabled with xit'); + return spec; + }; + + this.fit = function(description, fn, timeout){ + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); + currentDeclarationSuite.addChild(spec); + focusedRunnables.push(spec.id); + unfocusAncestor(); + return spec; + }; + + this.expect = function(actual) { + if (!currentRunnable()) { + throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); + } + + return currentRunnable().expect(actual); + }; + + this.beforeEach = function(beforeEachFunction, timeout) { + currentDeclarationSuite.beforeEach({ + fn: beforeEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.beforeAll = function(beforeAllFunction, timeout) { + currentDeclarationSuite.beforeAll({ + fn: beforeAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterEach = function(afterEachFunction, timeout) { + currentDeclarationSuite.afterEach({ + fn: afterEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterAll = function(afterAllFunction, timeout) { + currentDeclarationSuite.afterAll({ + fn: afterAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.pending = function(message) { + var fullMessage = j$.Spec.pendingSpecExceptionMessage; + if(message) { + fullMessage += message; + } + throw fullMessage; + }; + + this.fail = function(error) { + var message = 'Failed'; + if (error) { + message += ': '; + message += error.message || error; + } + + currentRunnable().addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + message: message, + error: error && error.message ? error : null + }); + }; + } + + return Env; +}; + +getJasmineRequireObj().JsApiReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function JsApiReporter(options) { + var timer = options.timer || noopTimer, + status = 'loaded'; + + this.started = false; + this.finished = false; + this.runDetails = {}; + + this.jasmineStarted = function() { + this.started = true; + status = 'started'; + timer.start(); + }; + + var executionTime; + + this.jasmineDone = function(runDetails) { + this.finished = true; + this.runDetails = runDetails; + executionTime = timer.elapsed(); + status = 'done'; + }; + + this.status = function() { + return status; + }; + + var suites = [], + suites_hash = {}; + + this.suiteStarted = function(result) { + suites_hash[result.id] = result; + }; + + this.suiteDone = function(result) { + storeSuite(result); + }; + + this.suiteResults = function(index, length) { + return suites.slice(index, index + length); + }; + + function storeSuite(result) { + suites.push(result); + suites_hash[result.id] = result; + } + + this.suites = function() { + return suites_hash; + }; + + var specs = []; + + this.specDone = function(result) { + specs.push(result); + }; + + this.specResults = function(index, length) { + return specs.slice(index, index + length); + }; + + this.specs = function() { + return specs; + }; + + this.executionTime = function() { + return executionTime; + }; + + } + + return JsApiReporter; +}; + +getJasmineRequireObj().CallTracker = function() { + + function CallTracker() { + var calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + var call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + var callArgs = []; + for(var i = 0; i < calls.length; i++){ + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } + + return CallTracker; +}; + +getJasmineRequireObj().Clock = function() { + function Clock(global, delayedFunctionSchedulerFactory, mockDate) { + var self = this, + realTimingFunctions = { + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval + }, + fakeTimingFunctions = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval + }, + installed = false, + delayedFunctionScheduler, + timer; + + + self.install = function() { + if(!originalTimingFunctionsIntact()) { + throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?'); + } + replace(global, fakeTimingFunctions); + timer = fakeTimingFunctions; + delayedFunctionScheduler = delayedFunctionSchedulerFactory(); + installed = true; + + return self; + }; + + self.uninstall = function() { + delayedFunctionScheduler = null; + mockDate.uninstall(); + replace(global, realTimingFunctions); + + timer = realTimingFunctions; + installed = false; + }; + + self.withMock = function(closure) { + this.install(); + try { + closure(); + } finally { + this.uninstall(); + } + }; + + self.mockDate = function(initialDate) { + mockDate.install(initialDate); + }; + + self.setTimeout = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); + } + return timer.setTimeout(fn, delay); + } + return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); + }; + + self.setInterval = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); + } + return timer.setInterval(fn, delay); + } + return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); + }; + + self.clearTimeout = function(id) { + return Function.prototype.call.apply(timer.clearTimeout, [global, id]); + }; + + self.clearInterval = function(id) { + return Function.prototype.call.apply(timer.clearInterval, [global, id]); + }; + + self.tick = function(millis) { + if (installed) { + mockDate.tick(millis); + delayedFunctionScheduler.tick(millis); + } else { + throw new Error('Mock clock is not installed, use jasmine.clock().install()'); + } + }; + + return self; + + function originalTimingFunctionsIntact() { + return global.setTimeout === realTimingFunctions.setTimeout && + global.clearTimeout === realTimingFunctions.clearTimeout && + global.setInterval === realTimingFunctions.setInterval && + global.clearInterval === realTimingFunctions.clearInterval; + } + + function legacyIE() { + //if these methods are polyfilled, apply will be present + return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; + } + + function replace(dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } + } + + function setTimeout(fn, delay) { + return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); + } + + function clearTimeout(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function setInterval(fn, interval) { + return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); + } + + function clearInterval(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function argSlice(argsObj, n) { + return Array.prototype.slice.call(argsObj, n); + } + } + + return Clock; +}; + +getJasmineRequireObj().DelayedFunctionScheduler = function() { + function DelayedFunctionScheduler() { + var self = this; + var scheduledLookup = []; + var scheduledFunctions = {}; + var currentTime = 0; + var delayedFnCount = 0; + + self.tick = function(millis) { + millis = millis || 0; + var endTime = currentTime + millis; + + runScheduledFunctions(endTime); + currentTime = endTime; + }; + + self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { + var f; + if (typeof(funcToCall) === 'string') { + /* jshint evil: true */ + f = function() { return eval(funcToCall); }; + /* jshint evil: false */ + } else { + f = funcToCall; + } + + millis = millis || 0; + timeoutKey = timeoutKey || ++delayedFnCount; + runAtMillis = runAtMillis || (currentTime + millis); + + var funcToSchedule = { + runAtMillis: runAtMillis, + funcToCall: f, + recurring: recurring, + params: params, + timeoutKey: timeoutKey, + millis: millis + }; + + if (runAtMillis in scheduledFunctions) { + scheduledFunctions[runAtMillis].push(funcToSchedule); + } else { + scheduledFunctions[runAtMillis] = [funcToSchedule]; + scheduledLookup.push(runAtMillis); + scheduledLookup.sort(function (a, b) { + return a - b; + }); + } + + return timeoutKey; + }; + + self.removeFunctionWithId = function(timeoutKey) { + for (var runAtMillis in scheduledFunctions) { + var funcs = scheduledFunctions[runAtMillis]; + var i = indexOfFirstToPass(funcs, function (func) { + return func.timeoutKey === timeoutKey; + }); + + if (i > -1) { + if (funcs.length === 1) { + delete scheduledFunctions[runAtMillis]; + deleteFromLookup(runAtMillis); + } else { + funcs.splice(i, 1); + } + + // intervals get rescheduled when executed, so there's never more + // than a single scheduled function with a given timeoutKey + break; + } + } + }; + + return self; + + function indexOfFirstToPass(array, testFn) { + var index = -1; + + for (var i = 0; i < array.length; ++i) { + if (testFn(array[i])) { + index = i; + break; + } + } + + return index; + } + + function deleteFromLookup(key) { + var value = Number(key); + var i = indexOfFirstToPass(scheduledLookup, function (millis) { + return millis === value; + }); + + if (i > -1) { + scheduledLookup.splice(i, 1); + } + } + + function reschedule(scheduledFn) { + self.scheduleFunction(scheduledFn.funcToCall, + scheduledFn.millis, + scheduledFn.params, + true, + scheduledFn.timeoutKey, + scheduledFn.runAtMillis + scheduledFn.millis); + } + + function forEachFunction(funcsToRun, callback) { + for (var i = 0; i < funcsToRun.length; ++i) { + callback(funcsToRun[i]); + } + } + + function runScheduledFunctions(endTime) { + if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { + return; + } + + do { + currentTime = scheduledLookup.shift(); + + var funcsToRun = scheduledFunctions[currentTime]; + delete scheduledFunctions[currentTime]; + + forEachFunction(funcsToRun, function(funcToRun) { + if (funcToRun.recurring) { + reschedule(funcToRun); + } + }); + + forEachFunction(funcsToRun, function(funcToRun) { + funcToRun.funcToCall.apply(null, funcToRun.params || []); + }); + } while (scheduledLookup.length > 0 && + // checking first if we're out of time prevents setTimeout(0) + // scheduled in a funcToRun from forcing an extra iteration + currentTime !== endTime && + scheduledLookup[0] <= endTime); + } + } + + return DelayedFunctionScheduler; +}; + +getJasmineRequireObj().ExceptionFormatter = function() { + function ExceptionFormatter() { + this.message = function(error) { + var message = ''; + + if (error.name && error.message) { + message += error.name + ': ' + error.message; + } else { + message += error.toString() + ' thrown'; + } + + if (error.fileName || error.sourceURL) { + message += ' in ' + (error.fileName || error.sourceURL); + } + + if (error.line || error.lineNumber) { + message += ' (line ' + (error.line || error.lineNumber) + ')'; + } + + return message; + }; + + this.stack = function(error) { + return error ? error.stack : null; + }; + } + + return ExceptionFormatter; +}; + +getJasmineRequireObj().Expectation = function() { + + function Expectation(options) { + this.util = options.util || { buildFailureMessage: function() {} }; + this.customEqualityTesters = options.customEqualityTesters || []; + this.actual = options.actual; + this.addExpectationResult = options.addExpectationResult || function(){}; + this.isNot = options.isNot; + + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); + } + } + + Expectation.prototype.wrapCompare = function(name, matcherFactory) { + return function() { + var args = Array.prototype.slice.call(arguments, 0), + expected = args.slice(0), + message = ''; + + args.unshift(this.actual); + + var matcher = matcherFactory(this.util, this.customEqualityTesters), + matcherCompare = matcher.compare; + + function defaultNegativeCompare() { + var result = matcher.compare.apply(null, args); + result.pass = !result.pass; + return result; + } + + if (this.isNot) { + matcherCompare = matcher.negativeCompare || defaultNegativeCompare; + } + + var result = matcherCompare.apply(null, args); + + if (!result.pass) { + if (!result.message) { + args.unshift(this.isNot); + args.unshift(name); + message = this.util.buildFailureMessage.apply(null, args); + } else { + if (Object.prototype.toString.apply(result.message) === '[object Function]') { + message = result.message(); + } else { + message = result.message; + } + } + } + + if (expected.length == 1) { + expected = expected[0]; + } + + // TODO: how many of these params are needed? + this.addExpectationResult( + result.pass, + { + matcherName: name, + passed: result.pass, + message: message, + actual: this.actual, + expected: expected // TODO: this may need to be arrayified/sliced + } + ); + }; + }; + + Expectation.addCoreMatchers = function(matchers) { + var prototype = Expectation.prototype; + for (var matcherName in matchers) { + var matcher = matchers[matcherName]; + prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); + } + }; + + Expectation.Factory = function(options) { + options = options || {}; + + var expect = new Expectation(options); + + // TODO: this would be nice as its own Object - NegativeExpectation + // TODO: copy instead of mutate options + options.isNot = true; + expect.not = new Expectation(options); + + return expect; + }; + + return Expectation; +}; + +//TODO: expectation result may make more sense as a presentation of an expectation. +getJasmineRequireObj().buildExpectationResult = function() { + function buildExpectationResult(options) { + var messageFormatter = options.messageFormatter || function() {}, + stackFormatter = options.stackFormatter || function() {}; + + var result = { + matcherName: options.matcherName, + message: message(), + stack: stack(), + passed: options.passed + }; + + if(!result.passed) { + result.expected = options.expected; + result.actual = options.actual; + } + + return result; + + function message() { + if (options.passed) { + return 'Passed.'; + } else if (options.message) { + return options.message; + } else if (options.error) { + return messageFormatter(options.error); + } + return ''; + } + + function stack() { + if (options.passed) { + return ''; + } + + var error = options.error; + if (!error) { + try { + throw new Error(message()); + } catch (e) { + error = e; + } + } + return stackFormatter(error); + } + } + + return buildExpectationResult; +}; + +getJasmineRequireObj().MockDate = function() { + function MockDate(global) { + var self = this; + var currentTime = 0; + + if (!global || !global.Date) { + self.install = function() {}; + self.tick = function() {}; + self.uninstall = function() {}; + return self; + } + + var GlobalDate = global.Date; + + self.install = function(mockDate) { + if (mockDate instanceof GlobalDate) { + currentTime = mockDate.getTime(); + } else { + currentTime = new GlobalDate().getTime(); + } + + global.Date = FakeDate; + }; + + self.tick = function(millis) { + millis = millis || 0; + currentTime = currentTime + millis; + }; + + self.uninstall = function() { + currentTime = 0; + global.Date = GlobalDate; + }; + + createDateProperties(); + + return self; + + function FakeDate() { + switch(arguments.length) { + case 0: + return new GlobalDate(currentTime); + case 1: + return new GlobalDate(arguments[0]); + case 2: + return new GlobalDate(arguments[0], arguments[1]); + case 3: + return new GlobalDate(arguments[0], arguments[1], arguments[2]); + case 4: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); + case 5: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4]); + case 6: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5]); + default: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5], arguments[6]); + } + } + + function createDateProperties() { + FakeDate.prototype = GlobalDate.prototype; + + FakeDate.now = function() { + if (GlobalDate.now) { + return currentTime; + } else { + throw new Error('Browser does not support Date.now()'); + } + }; + + FakeDate.toSource = GlobalDate.toSource; + FakeDate.toString = GlobalDate.toString; + FakeDate.parse = GlobalDate.parse; + FakeDate.UTC = GlobalDate.UTC; + } + } + + return MockDate; +}; + +getJasmineRequireObj().pp = function(j$) { + + function PrettyPrinter() { + this.ppNestLevel_ = 0; + this.seen = []; + } + + PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (j$.util.isUndefined(value)) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === 0 && 1/value === -Infinity) { + this.emitScalar('-0'); + } else if (value === j$.getGlobal()) { + this.emitScalar(''); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (j$.isSpy(value)) { + this.emitScalar('spy on ' + value.and.identity()); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.toString && typeof value === 'object' && !(value instanceof Array) && value.toString !== Object.prototype.toString) { + this.emitScalar(value.toString()); + } else if (j$.util.arrayContains(this.seen, value)) { + this.emitScalar(''); + } else if (j$.isArray_(value) || j$.isA_('Object', value)) { + this.seen.push(value); + if (j$.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + this.seen.pop(); + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } + }; + + PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } + fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && + obj.__lookupGetter__(property) !== null) : false); + } + }; + + PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; + + function StringPrettyPrinter() { + PrettyPrinter.call(this); + + this.string = ''; + } + + j$.util.inherit(StringPrettyPrinter, PrettyPrinter); + + StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); + }; + + StringPrettyPrinter.prototype.emitString = function(value) { + this.append('\'' + value + '\''); + }; + + StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + this.append('Array'); + return; + } + var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); + this.append('[ '); + for (var i = 0; i < length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + if(array.length > length){ + this.append(', ...'); + } + + var self = this; + var first = array.length === 0; + this.iterateObject(array, function(property, isGetter) { + if (property.match(/^\d+$/)) { + return; + } + + if (first) { + first = false; + } else { + self.append(', '); + } + + self.formatProperty(array, property, isGetter); + }); + + this.append(' ]'); + }; + + StringPrettyPrinter.prototype.emitObject = function(obj) { + var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null'; + this.append(constructorName); + + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + return; + } + + var self = this; + this.append('({ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.formatProperty(obj, property, isGetter); + }); + + this.append(' })'); + }; + + StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { + this.append(property); + this.append(': '); + if (isGetter) { + this.append(''); + } else { + this.format(obj[property]); + } + }; + + StringPrettyPrinter.prototype.append = function(value) { + this.string += value; + }; + + return function(value) { + var stringPrettyPrinter = new StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; + }; +}; + +getJasmineRequireObj().QueueRunner = function(j$) { + + function once(fn) { + var called = false; + return function() { + if (!called) { + called = true; + fn(); + } + }; + } + + function QueueRunner(attrs) { + this.queueableFns = attrs.queueableFns || []; + this.onComplete = attrs.onComplete || function() {}; + this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.onException = attrs.onException || function() {}; + this.catchException = attrs.catchException || function() { return true; }; + this.userContext = attrs.userContext || {}; + this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; + this.fail = attrs.fail || function() {}; + } + + QueueRunner.prototype.execute = function() { + this.run(this.queueableFns, 0); + }; + + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, + self = this, + iterativeIndex; + + + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + attemptAsync(queueableFn); + return; + } else { + attemptSync(queueableFn); + } + } + + var runnerDone = iterativeIndex >= length; + + if (runnerDone) { + this.clearStack(this.onComplete); + } + + function attemptSync(queueableFn) { + try { + queueableFn.fn.call(self.userContext); + } catch (e) { + handleException(e, queueableFn); + } + } + + function attemptAsync(queueableFn) { + var clearTimeout = function () { + if (timeoutId) { + Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); + } + }, + next = once(function () { + clearTimeout(timeoutId); + self.run(queueableFns, iterativeIndex + 1); + }), + timeoutId; + + next.fail = function() { + self.fail.apply(null, arguments); + next(); + }; + + if (queueableFn.timeout) { + timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() { + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error); + next(); + }, queueableFn.timeout()]]); + } + + try { + queueableFn.fn.call(self.userContext, next); + } catch (e) { + handleException(e, queueableFn); + next(); + } + } + + function onException(e) { + self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e); + if (!self.catchException(e)) { + //TODO: set a var when we catch an exception and + //use a finally block to close the loop in a nice way.. + throw e; + } + } + }; + + return QueueRunner; +}; + +getJasmineRequireObj().ReportDispatcher = function() { + function ReportDispatcher(methods) { + + var dispatchedMethods = methods || []; + + for (var i = 0; i < dispatchedMethods.length; i++) { + var method = dispatchedMethods[i]; + this[method] = (function(m) { + return function() { + dispatch(m, arguments); + }; + }(method)); + } + + var reporters = []; + + this.addReporter = function(reporter) { + reporters.push(reporter); + }; + + return this; + + function dispatch(method, args) { + for (var i = 0; i < reporters.length; i++) { + var reporter = reporters[i]; + if (reporter[method]) { + reporter[method].apply(reporter, args); + } + } + } + } + + return ReportDispatcher; +}; + + +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(methodName)) { + throw new Error('No method name supplied'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var descriptor; + try { + descriptor = Object.getOwnPropertyDescriptor(obj, methodName); + } catch(e) { + // IE 8 doesn't support `definePropery` on non-DOM nodes + } + + if (descriptor && !(descriptor.writable || descriptor.set)) { + throw new Error(methodName + ' is not declared writable or has no setter'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } + + return SpyRegistry; +}; + +getJasmineRequireObj().SpyStrategy = function() { + + function SpyStrategy(options) { + options = options || {}; + + var identity = options.name || 'unknown', + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; + + this.identity = function() { + return identity; + }; + + this.exec = function() { + return plan.apply(this, arguments); + }; + + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + + this.returnValue = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; + + this.returnValues = function() { + var values = Array.prototype.slice.call(arguments); + plan = function () { + return values.shift(); + }; + return getSpy(); + }; + + this.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + plan = function() { + throw error; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + plan = fn; + return getSpy(); + }; + + this.stub = function(fn) { + plan = function() {}; + return getSpy(); + }; + } + + return SpyStrategy; +}; + +getJasmineRequireObj().Suite = function(j$) { + function Suite(attrs) { + this.env = attrs.env; + this.id = attrs.id; + this.parentSuite = attrs.parentSuite; + this.description = attrs.description; + this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + this.beforeFns = []; + this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; + this.disabled = false; + + this.children = []; + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [] + }; + } + + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + if (parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + } + return fullName; + }; + + Suite.prototype.disable = function() { + this.disabled = true; + }; + + Suite.prototype.pend = function(message) { + this.markedPending = true; + }; + + Suite.prototype.beforeEach = function(fn) { + this.beforeFns.unshift(fn); + }; + + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + + Suite.prototype.afterEach = function(fn) { + this.afterFns.unshift(fn); + }; + + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + + Suite.prototype.addChild = function(child) { + this.children.push(child); + }; + + Suite.prototype.status = function() { + if (this.disabled) { + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'finished'; + } + }; + + Suite.prototype.isExecutable = function() { + return !this.disabled; + }; + + Suite.prototype.canBeReentered = function() { + return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; + }; + + Suite.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + Suite.prototype.onException = function() { + if (arguments[0] instanceof j$.errors.ExpectationFailed) { + return; + } + + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } + } + }; + + Suite.prototype.addExpectationResult = function () { + if(isAfterAll(this.children) && isFailure(arguments)){ + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + if(this.throwOnExpectationFailure) { + throw new j$.errors.ExpectationFailed(); + } + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + try { + child.addExpectationResult.apply(child, arguments); + } catch(e) { + // keep going + } + } + } + }; + + function isAfterAll(children) { + return children && children[0].result.status; + } + + function isFailure(args) { + return !args[0]; + } + + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; + } + } + + return clonedObj; + } + + return Suite; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Suite = jasmineRequire.Suite; +} + +getJasmineRequireObj().Timer = function() { + var defaultNow = (function(Date) { + return function() { return new Date().getTime(); }; + })(Date); + + function Timer(options) { + options = options || {}; + + var now = options.now || defaultNow, + startTime; + + this.start = function() { + startTime = now(); + }; + + this.elapsed = function() { + return now() - startTime; + }; + } + + return Timer; +}; + +getJasmineRequireObj().TreeProcessor = function() { + function TreeProcessor(attrs) { + var tree = attrs.tree, + runnableIds = attrs.runnableIds, + queueRunnerFactory = attrs.queueRunnerFactory, + nodeStart = attrs.nodeStart || function() {}, + nodeComplete = attrs.nodeComplete || function() {}, + orderChildren = attrs.orderChildren || function(node) { return node.children; }, + stats = { valid: true }, + processed = false, + defaultMin = Infinity, + defaultMax = 1 - Infinity; + + this.processTree = function() { + processNode(tree, false); + processed = true; + return stats; + }; + + this.execute = function(done) { + if (!processed) { + this.processTree(); + } + + if (!stats.valid) { + throw 'invalid order'; + } + + var childFns = wrapChildren(tree, 0); + + queueRunnerFactory({ + queueableFns: childFns, + userContext: tree.sharedUserContext(), + onException: function() { + tree.onException.apply(tree, arguments); + }, + onComplete: done + }); + }; + + function runnableIndex(id) { + for (var i = 0; i < runnableIds.length; i++) { + if (runnableIds[i] === id) { + return i; + } + } + } + + function processNode(node, parentEnabled) { + var executableIndex = runnableIndex(node.id); + + if (executableIndex !== undefined) { + parentEnabled = true; + } + + parentEnabled = parentEnabled && node.isExecutable(); + + if (!node.children) { + stats[node.id] = { + executable: parentEnabled && node.isExecutable(), + segments: [{ + index: 0, + owner: node, + nodes: [node], + min: startingMin(executableIndex), + max: startingMax(executableIndex) + }] + }; + } else { + var hasExecutableChild = false; + + var orderedChildren = orderChildren(node); + + for (var i = 0; i < orderedChildren.length; i++) { + var child = orderedChildren[i]; + + processNode(child, parentEnabled); + + if (!stats.valid) { + return; + } + + var childStats = stats[child.id]; + + hasExecutableChild = hasExecutableChild || childStats.executable; + } + + stats[node.id] = { + executable: hasExecutableChild + }; + + segmentChildren(node, orderedChildren, stats[node.id], executableIndex); + + if (!node.canBeReentered() && stats[node.id].segments.length > 1) { + stats = { valid: false }; + } + } + } + + function startingMin(executableIndex) { + return executableIndex === undefined ? defaultMin : executableIndex; + } + + function startingMax(executableIndex) { + return executableIndex === undefined ? defaultMax : executableIndex; + } + + function segmentChildren(node, orderedChildren, nodeStats, executableIndex) { + var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, + result = [currentSegment], + lastMax = defaultMax, + orderedChildSegments = orderChildSegments(orderedChildren); + + function isSegmentBoundary(minIndex) { + return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1; + } + + for (var i = 0; i < orderedChildSegments.length; i++) { + var childSegment = orderedChildSegments[i], + maxIndex = childSegment.max, + minIndex = childSegment.min; + + if (isSegmentBoundary(minIndex)) { + currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax}; + result.push(currentSegment); + } + + currentSegment.nodes.push(childSegment); + currentSegment.min = Math.min(currentSegment.min, minIndex); + currentSegment.max = Math.max(currentSegment.max, maxIndex); + lastMax = maxIndex; + } + + nodeStats.segments = result; + } + + function orderChildSegments(children) { + var specifiedOrder = [], + unspecifiedOrder = []; + + for (var i = 0; i < children.length; i++) { + var child = children[i], + segments = stats[child.id].segments; + + for (var j = 0; j < segments.length; j++) { + var seg = segments[j]; + + if (seg.min === defaultMin) { + unspecifiedOrder.push(seg); + } else { + specifiedOrder.push(seg); + } + } + } + + specifiedOrder.sort(function(a, b) { + return a.min - b.min; + }); + + return specifiedOrder.concat(unspecifiedOrder); + } + + function executeNode(node, segmentNumber) { + if (node.children) { + return { + fn: function(done) { + nodeStart(node); + + queueRunnerFactory({ + onComplete: function() { + nodeComplete(node, node.getResult()); + done(); + }, + queueableFns: wrapChildren(node, segmentNumber), + userContext: node.sharedUserContext(), + onException: function() { + node.onException.apply(node, arguments); + } + }); + } + }; + } else { + return { + fn: function(done) { node.execute(done, stats[node.id].executable); } + }; + } + } + + function wrapChildren(node, segmentNumber) { + var result = [], + segmentChildren = stats[node.id].segments[segmentNumber].nodes; + + for (var i = 0; i < segmentChildren.length; i++) { + result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); + } + + if (!stats[node.id].executable) { + return result; + } + + return node.beforeAllFns.concat(result).concat(node.afterAllFns); + } + } + + return TreeProcessor; +}; + +getJasmineRequireObj().Any = function(j$) { + + function Any(expectedObject) { + if (typeof expectedObject === 'undefined') { + throw new TypeError( + 'jasmine.any() expects to be passed a constructor function. ' + + 'Please pass one or use jasmine.anything() to match any object.' + ); + } + this.expectedObject = expectedObject; + } + + Any.prototype.asymmetricMatch = function(other) { + if (this.expectedObject == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedObject == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedObject == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedObject == Object) { + return typeof other == 'object'; + } + + if (this.expectedObject == Boolean) { + return typeof other == 'boolean'; + } + + return other instanceof this.expectedObject; + }; + + Any.prototype.jasmineToString = function() { + return ''; + }; + + return Any; +}; + +getJasmineRequireObj().Anything = function(j$) { + + function Anything() {} + + Anything.prototype.asymmetricMatch = function(other) { + return !j$.util.isUndefined(other) && other !== null; + }; + + Anything.prototype.jasmineToString = function() { + return ''; + }; + + return Anything; +}; + +getJasmineRequireObj().ArrayContaining = function(j$) { + function ArrayContaining(sample) { + this.sample = sample; + } + + ArrayContaining.prototype.asymmetricMatch = function(other) { + var className = Object.prototype.toString.call(this.sample); + if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); } + + for (var i = 0; i < this.sample.length; i++) { + var item = this.sample[i]; + if (!j$.matchersUtil.contains(other, item)) { + return false; + } + } + + return true; + }; + + ArrayContaining.prototype.jasmineToString = function () { + return ''; + }; + + return ArrayContaining; +}; + +getJasmineRequireObj().ObjectContaining = function(j$) { + + function ObjectContaining(sample) { + this.sample = sample; + } + + function getPrototype(obj) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(obj); + } + + if (obj.constructor.prototype == obj) { + return null; + } + + return obj.constructor.prototype; + } + + function hasProperty(obj, property) { + if (!obj) { + return false; + } + + if (Object.prototype.hasOwnProperty.call(obj, property)) { + return true; + } + + return hasProperty(getPrototype(obj), property); + } + + ObjectContaining.prototype.asymmetricMatch = function(other) { + if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } + + for (var property in this.sample) { + if (!hasProperty(other, property) || + !j$.matchersUtil.equals(this.sample[property], other[property])) { + return false; + } + } + + return true; + }; + + ObjectContaining.prototype.jasmineToString = function() { + return ''; + }; + + return ObjectContaining; +}; + +getJasmineRequireObj().StringMatching = function(j$) { + + function StringMatching(expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } + + this.regexp = new RegExp(expected); + } + + StringMatching.prototype.asymmetricMatch = function(other) { + return this.regexp.test(other); + }; + + StringMatching.prototype.jasmineToString = function() { + return ''; + }; + + return StringMatching; +}; + +getJasmineRequireObj().errors = function() { + function ExpectationFailed() {} + + ExpectationFailed.prototype = new Error(); + ExpectationFailed.prototype.constructor = ExpectationFailed; + + return { + ExpectationFailed: ExpectationFailed + }; +}; +getJasmineRequireObj().matchersUtil = function(j$) { + // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? + + return { + equals: function(a, b, customTesters) { + customTesters = customTesters || []; + + return eq(a, b, [], [], customTesters); + }, + + contains: function(haystack, needle, customTesters) { + customTesters = customTesters || []; + + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { + for (var i = 0; i < haystack.length; i++) { + if (eq(haystack[i], needle, [], [], customTesters)) { + return true; + } + } + return false; + } + + return !!haystack && haystack.indexOf(needle) >= 0; + }, + + buildFailureMessage: function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; + } + }; + + function isAsymmetric(obj) { + return obj && j$.isA_('Function', obj.asymmetricMatch); + } + + function asymmetricMatch(a, b) { + var asymmetricA = isAsymmetric(a), + asymmetricB = isAsymmetric(b); + + if (asymmetricA && asymmetricB) { + return undefined; + } + + if (asymmetricA) { + return a.asymmetricMatch(b); + } + + if (asymmetricB) { + return b.asymmetricMatch(a); + } + } + + // Equality function lovingly adapted from isEqual in + // [Underscore](http://underscorejs.org) + function eq(a, b, aStack, bStack, customTesters) { + var result = true; + + var asymmetricResult = asymmetricMatch(a, b); + if (!j$.util.isUndefined(asymmetricResult)) { + return asymmetricResult; + } + + for (var i = 0; i < customTesters.length; i++) { + var customTesterResult = customTesters[i](a, b); + if (!j$.util.isUndefined(customTesterResult)) { + return customTesterResult; + } + } + + if (a instanceof Error && b instanceof Error) { + return a.message == b.message; + } + + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) { return a !== 0 || 1 / a == 1 / b; } + // A strict comparison is necessary because `null == undefined`. + if (a === null || b === null) { return a === b; } + var className = Object.prototype.toString.call(a); + if (className != Object.prototype.toString.call(b)) { return false; } + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') { return false; } + + var aIsDomNode = j$.isDomNode(a); + var bIsDomNode = j$.isDomNode(b); + if (aIsDomNode && bIsDomNode) { + // At first try to use DOM3 method isEqualNode + if (a.isEqualNode) { + return a.isEqualNode(b); + } + // IE8 doesn't support isEqualNode, try to use outerHTML && innerText + var aIsElement = a instanceof Element; + var bIsElement = b instanceof Element; + if (aIsElement && bIsElement) { + return a.outerHTML == b.outerHTML; + } + if (aIsElement || bIsElement) { + return false; + } + return a.innerText == b.innerText && a.textContent == b.textContent; + } + if (aIsDomNode || bIsDomNode) { + return false; + } + + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) { return bStack[length] == b; } + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0; + // Recursively compare objects and arrays. + // Compare array lengths to determine if a deep comparison is necessary. + if (className == '[object Array]' && a.length !== b.length) { + result = false; + } + + if (result) { + // Objects with different constructors are not equivalent, but `Object`s + // or `Array`s from different frames are. + if (className !== '[object Array]') { + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && + isFunction(bCtor) && bCtor instanceof bCtor)) { + return false; + } + } + // Deep compare objects. + for (var key in a) { + if (has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (has(b, key) && !(size--)) { break; } + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + + return result; + + function has(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); + } + + function isFunction(obj) { + return typeof obj === 'function'; + } + } +}; + +getJasmineRequireObj().toBe = function() { + function toBe() { + return { + compare: function(actual, expected) { + return { + pass: actual === expected + }; + } + }; + } + + return toBe; +}; + +getJasmineRequireObj().toBeCloseTo = function() { + + function toBeCloseTo() { + return { + compare: function(actual, expected, precision) { + if (precision !== 0) { + precision = precision || 2; + } + + return { + pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) + }; + } + }; + } + + return toBeCloseTo; +}; + +getJasmineRequireObj().toBeDefined = function() { + function toBeDefined() { + return { + compare: function(actual) { + return { + pass: (void 0 !== actual) + }; + } + }; + } + + return toBeDefined; +}; + +getJasmineRequireObj().toBeFalsy = function() { + function toBeFalsy() { + return { + compare: function(actual) { + return { + pass: !!!actual + }; + } + }; + } + + return toBeFalsy; +}; + +getJasmineRequireObj().toBeGreaterThan = function() { + + function toBeGreaterThan() { + return { + compare: function(actual, expected) { + return { + pass: actual > expected + }; + } + }; + } + + return toBeGreaterThan; +}; + + +getJasmineRequireObj().toBeLessThan = function() { + function toBeLessThan() { + return { + + compare: function(actual, expected) { + return { + pass: actual < expected + }; + } + }; + } + + return toBeLessThan; +}; +getJasmineRequireObj().toBeNaN = function(j$) { + + function toBeNaN() { + return { + compare: function(actual) { + var result = { + pass: (actual !== actual) + }; + + if (result.pass) { + result.message = 'Expected actual not to be NaN.'; + } else { + result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; + } + + return result; + } + }; + } + + return toBeNaN; +}; + +getJasmineRequireObj().toBeNull = function() { + + function toBeNull() { + return { + compare: function(actual) { + return { + pass: actual === null + }; + } + }; + } + + return toBeNull; +}; + +getJasmineRequireObj().toBeTruthy = function() { + + function toBeTruthy() { + return { + compare: function(actual) { + return { + pass: !!actual + }; + } + }; + } + + return toBeTruthy; +}; + +getJasmineRequireObj().toBeUndefined = function() { + + function toBeUndefined() { + return { + compare: function(actual) { + return { + pass: void 0 === actual + }; + } + }; + } + + return toBeUndefined; +}; + +getJasmineRequireObj().toContain = function() { + function toContain(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + + return { + pass: util.contains(actual, expected, customEqualityTesters) + }; + } + }; + } + + return toContain; +}; + +getJasmineRequireObj().toEqual = function() { + + function toEqual(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + var result = { + pass: false + }; + + result.pass = util.equals(actual, expected, customEqualityTesters); + + return result; + } + }; + } + + return toEqual; +}; + +getJasmineRequireObj().toHaveBeenCalled = function(j$) { + + function toHaveBeenCalled() { + return { + compare: function(actual) { + var result = {}; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (arguments.length > 1) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + result.pass = actual.calls.any(); + + result.message = result.pass ? + 'Expected spy ' + actual.and.identity() + ' not to have been called.' : + 'Expected spy ' + actual.and.identity() + ' to have been called.'; + + return result; + } + }; + } + + return toHaveBeenCalled; +}; + +getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { + + function toHaveBeenCalledTimes() { + return { + compare: function(actual, expected) { + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + var args = Array.prototype.slice.call(arguments, 0), + result = { pass: false }; + + if(!expected){ + throw new Error('Expected times failed is required as an argument.'); + } + + actual = args[0]; + var calls = actual.calls.count(); + var timesMessage = expected === 1 ? 'once' : expected + ' times'; + result.pass = calls === expected; + result.message = result.pass ? + 'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : + 'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; + return result; + } + }; + } + + return toHaveBeenCalledTimes; +}; + +getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { + + function toHaveBeenCalledWith(util, customEqualityTesters) { + return { + compare: function() { + var args = Array.prototype.slice.call(arguments, 0), + actual = args[0], + expectedArgs = args.slice(1), + result = { pass: false }; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (!actual.calls.any()) { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; + return result; + } + + if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + result.pass = true; + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; + } else { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; + } + + return result; + } + }; + } + + return toHaveBeenCalledWith; +}; + +getJasmineRequireObj().toMatch = function(j$) { + + function toMatch() { + return { + compare: function(actual, expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } + + var regexp = new RegExp(expected); + + return { + pass: regexp.test(actual) + }; + } + }; + } + + return toMatch; +}; + +getJasmineRequireObj().toThrow = function(j$) { + + function toThrow(util) { + return { + compare: function(actual, expected) { + var result = { pass: false }, + threw = false, + thrown; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + result.message = 'Expected function to throw an exception.'; + return result; + } + + if (arguments.length == 1) { + result.pass = true; + result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; + + return result; + } + + if (util.equals(thrown, expected)) { + result.pass = true; + result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; + } else { + result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; + } + + return result; + } + }; + } + + return toThrow; +}; + +getJasmineRequireObj().toThrowError = function(j$) { + function toThrowError () { + return { + compare: function(actual) { + var threw = false, + pass = {pass: true}, + fail = {pass: false}, + thrown; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } + + var errorMatcher = getMatcher.apply(null, arguments); + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + fail.message = 'Expected function to throw an Error.'; + return fail; + } + + if (!(thrown instanceof Error)) { + fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; + return fail; + } + + if (errorMatcher.hasNoSpecifics()) { + pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; + return pass; + } + + if (errorMatcher.matches(thrown)) { + pass.message = function() { + return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; + }; + return pass; + } else { + fail.message = function() { + return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + + ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; + }; + return fail; + } + } + }; + + function getMatcher() { + var expected = null, + errorType = null; + + if (arguments.length == 2) { + expected = arguments[1]; + if (isAnErrorType(expected)) { + errorType = expected; + expected = null; + } + } else if (arguments.length > 2) { + errorType = arguments[1]; + expected = arguments[2]; + if (!isAnErrorType(errorType)) { + throw new Error('Expected error type is not an Error.'); + } + } + + if (expected && !isStringOrRegExp(expected)) { + if (errorType) { + throw new Error('Expected error message is not a string or RegExp.'); + } else { + throw new Error('Expected is not an Error, string, or RegExp.'); + } + } + + function messageMatch(message) { + if (typeof expected == 'string') { + return expected == message; + } else { + return expected.test(message); + } + } + + return { + errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', + thrownDescription: function(thrown) { + var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', + thrownMessage = ''; + + if (expected) { + thrownMessage = ' with message ' + j$.pp(thrown.message); + } + + return thrownName + thrownMessage; + }, + messageDescription: function() { + if (expected === null) { + return ''; + } else if (expected instanceof RegExp) { + return ' with a message matching ' + j$.pp(expected); + } else { + return ' with message ' + j$.pp(expected); + } + }, + hasNoSpecifics: function() { + return expected === null && errorType === null; + }, + matches: function(error) { + return (errorType === null || error instanceof errorType) && + (expected === null || messageMatch(error.message)); + } + }; + } + + function isStringOrRegExp(potential) { + return potential instanceof RegExp || (typeof potential == 'string'); + } + + function isAnErrorType(type) { + if (typeof type !== 'function') { + return false; + } + + var Surrogate = function() {}; + Surrogate.prototype = type.prototype; + return (new Surrogate()) instanceof Error; + } + } + + return toThrowError; +}; + +getJasmineRequireObj().interface = function(jasmine, env) { + var jasmineInterface = { + describe: function(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, + + xdescribe: function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, + + fdescribe: function(description, specDefinitions) { + return env.fdescribe(description, specDefinitions); + }, + + it: function() { + return env.it.apply(env, arguments); + }, + + xit: function() { + return env.xit.apply(env, arguments); + }, + + fit: function() { + return env.fit.apply(env, arguments); + }, + + beforeEach: function() { + return env.beforeEach.apply(env, arguments); + }, + + afterEach: function() { + return env.afterEach.apply(env, arguments); + }, + + beforeAll: function() { + return env.beforeAll.apply(env, arguments); + }, + + afterAll: function() { + return env.afterAll.apply(env, arguments); + }, + + expect: function(actual) { + return env.expect(actual); + }, + + pending: function() { + return env.pending.apply(env, arguments); + }, + + fail: function() { + return env.fail.apply(env, arguments); + }, + + spyOn: function(obj, methodName) { + return env.spyOn(obj, methodName); + }, + + jsApiReporter: new jasmine.JsApiReporter({ + timer: new jasmine.Timer() + }), + + jasmine: jasmine + }; + + jasmine.addCustomEqualityTester = function(tester) { + env.addCustomEqualityTester(tester); + }; + + jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); + }; + + jasmine.clock = function() { + return env.clock; + }; + + return jasmineInterface; +}; + +getJasmineRequireObj().version = function() { + return '2.4.1'; +}; diff --git a/examples/libraries/toolBars.js b/examples/libraries/toolBars.js index 3b712e2ea0..6e54c0276c 100644 --- a/examples/libraries/toolBars.js +++ b/examples/libraries/toolBars.js @@ -355,20 +355,36 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit }); } }; - that.windowDimensions = Controller.getViewportDimensions(); + + function clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } + + var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height }; + that.windowDimensions = recommendedDimmensions; // Controller.getViewportDimensions(); + that.origin = { x: recommendedRect.x, y: recommendedRect.y }; // Maybe fixme: Keeping the same percent of the window size isn't always the right thing. // For example, maybe we want "keep the same percentage to whatever two edges are closest to the edge of screen". // If we change that, the places to do so are onResizeViewport, save (maybe), and the initial move based on Settings, below. that.onResizeViewport = function (newSize) { // Can be overridden or extended by clients. - var fractionX = that.x / that.windowDimensions.x; - var fractionY = that.y / that.windowDimensions.y; - that.windowDimensions = newSize || Controller.getViewportDimensions(); - that.move(fractionX * that.windowDimensions.x, fractionY * that.windowDimensions.y); + var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height }; + var originRelativeX = (that.x - that.origin.x); + var originRelativeY = (that.y - that.origin.y); + var fractionX = clamp(originRelativeX / that.windowDimensions.x, 0, 1); + var fractionY = clamp(originRelativeY / that.windowDimensions.y, 0, 1); + that.windowDimensions = newSize || recommendedDimmensions; + that.origin = { x: recommendedRect.x, y: recommendedRect.y }; + var newX = (fractionX * that.windowDimensions.x) + recommendedRect.x; + var newY = (fractionY * that.windowDimensions.y) + recommendedRect.y; + that.move(newX, newY); }; if (optionalPersistenceKey) { this.fractionKey = optionalPersistenceKey + '.fraction'; this.save = function () { - var screenSize = Controller.getViewportDimensions(); + var recommendedRect = Controller.getRecommendedOverlayRect(); + var screenSize = { x: recommendedRect.width, y: recommendedRect.height }; if (screenSize.x > 0 && screenSize.y > 0) { // Guard against invalid screen size that can occur at shut-down. var fraction = {x: that.x / screenSize.x, y: that.y / screenSize.y}; @@ -411,7 +427,9 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit that.move(that.dragOffsetX + event.x, that.dragOffsetY + event.y); }; that.checkResize = function () { // Can be overriden or extended, but usually not. See onResizeViewport. - var currentWindowSize = Controller.getViewportDimensions(); + var recommendedRect = Controller.getRecommendedOverlayRect(); + var currentWindowSize = { x: recommendedRect.width, y: recommendedRect.height }; + if ((currentWindowSize.x !== that.windowDimensions.x) || (currentWindowSize.y !== that.windowDimensions.y)) { that.onResizeViewport(currentWindowSize); } @@ -434,7 +452,8 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit } if (this.fractionKey || optionalInitialPositionFunction) { var savedFraction = JSON.parse(Settings.getValue(this.fractionKey) || '0'); // getValue can answer empty string - var screenSize = Controller.getViewportDimensions(); + var recommendedRect = Controller.getRecommendedOverlayRect(); + var screenSize = { x: recommendedRect.width, y: recommendedRect.height }; if (savedFraction) { // If we have saved data, keep the toolbar at the same proportion of the screen width/height. that.move(savedFraction.x * screenSize.x, savedFraction.y * screenSize.y); diff --git a/examples/tests/avatarUnitTests.js b/examples/tests/avatarUnitTests.js new file mode 100644 index 0000000000..29e3ad0588 --- /dev/null +++ b/examples/tests/avatarUnitTests.js @@ -0,0 +1,59 @@ + +Script.include("../libraries/jasmine/jasmine.js"); +Script.include("../libraries/jasmine/hifi-boot.js"); + +// Art3mis +var DEFAULT_AVATAR_URL = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/e76946cc-c272-4adf-9bb6-02cde0a4b57d/8fd984ea6fe1495147a3303f87fa6e23.fst?1460131758"; + +var ORIGIN = {x: 0, y: 0, z: 0}; +var ONE_HUNDRED = {x: 100, y: 100, z: 100}; +var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1}; + +describe("MyAvatar", function () { + + // reload the avatar from scratch before each test. + beforeEach(function (done) { + MyAvatar.skeletonModelURL = DEFAULT_AVATAR_URL; + + // wait until we are finished loading + var id = Script.setInterval(function () { + if (MyAvatar.jointNames.length == 72) { + // assume we are finished loading. + Script.clearInterval(id); + MyAvatar.position = ORIGIN; + MyAvatar.orientation = ROT_IDENT; + // give the avatar 1/2 a second to settle on the ground in the idle pose. + Script.setTimeout(function () { + done(); + }, 500); + } + }, 500); + }); + + // makes the assumption that there is solid ground somewhat underneath the avatar. + it("position and orientation getters", function () { + var pos = MyAvatar.position; + + expect(Math.abs(pos.x)).toBeLessThan(0.1); + expect(Math.abs(pos.y)).toBeLessThan(1.0); + expect(Math.abs(pos.z)).toBeLessThan(0.1); + + var rot = MyAvatar.orientation; + expect(Math.abs(rot.x)).toBeLessThan(0.01); + expect(Math.abs(rot.y)).toBeLessThan(0.01); + expect(Math.abs(rot.z)).toBeLessThan(0.01); + expect(Math.abs(1 - rot.w)).toBeLessThan(0.01); + }); + + it("position and orientation setters", function (done) { + MyAvatar.position = ONE_HUNDRED; + Script.setTimeout(function () { + expect(Vec3.length(Vec3.subtract(MyAvatar.position, ONE_HUNDRED))).toBeLessThan(0.1); + done(); + }, 100); + }); + +}); + +jasmine.getEnv().execute(); + diff --git a/examples/tests/playaPerformanceTest.js b/examples/tests/playaPerformanceTest.js new file mode 100644 index 0000000000..4c8a728a15 --- /dev/null +++ b/examples/tests/playaPerformanceTest.js @@ -0,0 +1,11 @@ +var qml = Script.resolvePath('playaPerformanceTest.qml'); +qmlWindow = new OverlayWindow({ + title: 'Test Qml', + source: qml, + height: 320, + width: 640, + toolWindow: false, + visible: true +}); + + diff --git a/examples/tests/playaPerformanceTest.qml b/examples/tests/playaPerformanceTest.qml new file mode 100644 index 0000000000..f1382358ae --- /dev/null +++ b/examples/tests/playaPerformanceTest.qml @@ -0,0 +1,193 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Rectangle { + id: root + width: parent ? parent.width : 100 + height: parent ? parent.height : 100 + + signal sendToScript(var message); + property var values: []; + property var host: AddressManager.hostname + + + Component.onCompleted: { + Window.domainChanged.connect(function(newDomain){ + if (newDomain !== root.host) { + root.host = AddressManager.hostname; + } + }); + } + + onHostChanged: { + if (root.running) { + if (host !== "Dreaming" && host !== "Playa") { + return; + } + + console.log("PERF new domain " + host) + if (host === "Dreaming") { + AddressManager.handleLookupString("Playa"); + return; + } + + if (host === "Playa") { + console.log("PERF starting timers and frame timing"); + // If we've arrived, start running the test + FrameTimings.start(); + rotationTimer.start(); + stopTimer.start(); + } + } + } + + function startTest() { + console.log("PERF startTest()"); + root.running = true + console.log("PERF current host: " + AddressManager.hostname) + // If we're already in playa, we need to go somewhere else... + if ("Playa" === AddressManager.hostname) { + console.log("PERF Navigating to dreaming") + AddressManager.handleLookupString("Dreaming/0,0,0"); + } else { + console.log("PERF Navigating to playa") + AddressManager.handleLookupString("Playa"); + } + } + + function stopTest() { + console.log("PERF stopTest()"); + root.running = false; + stopTimer.stop(); + rotationTimer.stop(); + FrameTimings.finish(); + root.values = FrameTimings.getValues(); + AddressManager.handleLookupString("Dreaming/0,0,0"); + resultGraph.requestPaint(); + console.log("PERF Value Count: " + root.values.length); + console.log("PERF Max: " + FrameTimings.max); + console.log("PERF Min: " + FrameTimings.min); + console.log("PERF Avg: " + FrameTimings.mean); + console.log("PERF StdDev: " + FrameTimings.standardDeviation); + } + + function yaw(a) { + var y = -Math.sin( a / 2.0 ); + var w = Math.cos( a / 2.0 ); + var l = Math.sqrt((y * y) + (w * w)); + return Qt.quaternion(w / l, 0, y / l, 0); + } + + function rotate() { + MyAvatar.setOrientationVar(yaw(Date.now() / 1000)); + } + + property bool running: false + + Timer { + id: stopTimer + interval: 30 * 1000 + repeat: false + running: false + onTriggered: stopTest(); + } + + Timer { + id: rotationTimer + interval: 100 + repeat: true + running: false + onTriggered: rotate(); + } + + Row { + id: row + anchors { left: parent.left; right: parent.right; } + spacing: 8 + Button { + text: root.running ? "Stop" : "Run" + onClicked: root.running ? stopTest() : startTest(); + } + } + +// Rectangle { +// anchors { left: parent.left; right: parent.right; top: row.bottom; topMargin: 8; bottom: parent.bottom; } +// //anchors.fill: parent +// color: "#7fff0000" +// } + + // Return the maximum value from a set of values + function vv(i, max) { + var perValue = values.length / max; + var start = Math.floor(perValue * i); + var end = Math.min(values.length, Math.floor(start + perValue)); + var result = 0; + for (var j = start; j <= end; ++j) { + result = Math.max(result, values[j]); + } + return result; + } + + Canvas { + id: resultGraph + anchors { left: parent.left; right: parent.right; top: row.bottom; margins: 16; bottom: parent.bottom; } + property real maxValue: 200; + property real perFrame: 10000; + property real k1: (5 / maxValue) * height; + property real k2: (10 / maxValue) * height; + property real k3: (100 / maxValue) * height; + + onPaint: { + var ctx = getContext("2d"); + if (values.length === 0) { + ctx.fillStyle = Qt.rgba(1, 0, 0, 1); + ctx.fillRect(0, 0, width, height); + return; + } + + + //ctx.setTransform(1, 0, 0, -1, 0, 0); + ctx.fillStyle = Qt.rgba(0, 0, 0, 1); + ctx.fillRect(0, 0, width, height); + + ctx.strokeStyle= "gray"; + ctx.lineWidth="1"; + ctx.beginPath(); + for (var i = 0; i < width; ++i) { + var value = vv(i, width); //values[Math.min(i, values.length - 1)]; + value /= 10000; + value /= maxValue; + ctx.moveTo(i, height); + ctx.lineTo(i, height - (height * value)); + } + ctx.stroke(); + + ctx.strokeStyle= "green"; + ctx.lineWidth="2"; + ctx.beginPath(); + var lineHeight = height - k1; + ctx.moveTo(0, lineHeight); + ctx.lineTo(width, lineHeight); + ctx.stroke(); + + ctx.strokeStyle= "yellow"; + ctx.lineWidth="2"; + ctx.beginPath(); + lineHeight = height - k2; + ctx.moveTo(0, lineHeight); + ctx.lineTo(width, lineHeight); + ctx.stroke(); + + ctx.strokeStyle= "red"; + ctx.lineWidth="2"; + ctx.beginPath(); + lineHeight = height - k3; + ctx.moveTo(0, lineHeight); + ctx.lineTo(width, lineHeight); + ctx.stroke(); + + } + } +} + + diff --git a/interface/resources/qml/AvatarInputs.qml b/interface/resources/qml/AvatarInputs.qml index fb989dd174..75f379a425 100644 --- a/interface/resources/qml/AvatarInputs.qml +++ b/interface/resources/qml/AvatarInputs.qml @@ -23,6 +23,8 @@ Hifi.AvatarInputs { readonly property int mirrorWidth: 265 readonly property int iconSize: 24 readonly property int iconPadding: 5 + + readonly property bool shouldReposition: true Settings { category: "Overlay.AvatarInputs" diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index beea0d8c64..c0804a967d 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -21,8 +21,11 @@ FocusScope { objectName: "desktop" anchors.fill: parent - onHeightChanged: d.repositionAll(); - onWidthChanged: d.repositionAll(); + property rect recommendedRect: rect(0,0,0,0); + + onHeightChanged: d.handleSizeChanged(); + + onWidthChanged: d.handleSizeChanged(); // Controls and windows can trigger this signal to ensure the desktop becomes visible // when they're opened. @@ -50,6 +53,20 @@ FocusScope { QtObject { id: d + function handleSizeChanged() { + var oldRecommendedRect = recommendedRect; + var newRecommendedRectJS = Controller.getRecommendedOverlayRect(); + var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, + newRecommendedRectJS.width, + newRecommendedRectJS.height); + + if (oldRecommendedRect != Qt.rect(0,0,0,0) + && oldRecommendedRect != newRecommendedRect) { + d.repositionAll(); + } + recommendedRect = newRecommendedRect; + } + function findChild(item, name) { for (var i = 0; i < item.children.length; ++i) { if (item.children[i].objectName === name) { @@ -202,12 +219,42 @@ FocusScope { // } } + function getRepositionChildren(predicate) { + var currentWindows = []; + if (!desktop) { + console.log("Could not find desktop"); + return currentWindows; + } + + for (var i = 0; i < desktop.children.length; ++i) { + var child = desktop.children[i]; + if (child.shouldReposition === true && (!predicate || predicate(child))) { + currentWindows.push(child) + } + } + return currentWindows; + } function repositionAll() { + var oldRecommendedRect = recommendedRect; + var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; + var newRecommendedRect = Controller.getRecommendedOverlayRect(); + var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; var windows = d.getTopLevelWindows(); for (var i = 0; i < windows.length; ++i) { - reposition(windows[i]); + var targetWindow = windows[i]; + if (targetWindow.visible) { + repositionWindow(targetWindow, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); + } } + + // also reposition the other children that aren't top level windows but want to be repositioned + var otherChildren = d.getRepositionChildren(); + for (var i = 0; i < otherChildren.length; ++i) { + var child = otherChildren[i]; + repositionWindow(child, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); + } + } } @@ -232,38 +279,56 @@ FocusScope { targetWindow.focus = true; } - reposition(targetWindow); + var oldRecommendedRect = recommendedRect; + var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; + var newRecommendedRect = Controller.getRecommendedOverlayRect(); + var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; + repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); showDesktop(); } - function reposition(item) { + function repositionWindow(targetWindow, forceReposition, + oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions) { + if (desktop.width === 0 || desktop.height === 0) { return; } - var targetWindow = d.getDesktopWindow(item); if (!targetWindow) { console.warn("Could not find top level window for " + item); return; } + var recommended = Controller.getRecommendedOverlayRect(); + var maxX = recommended.x + recommended.width; + var maxY = recommended.y + recommended.height; var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y); - // If the window is completely offscreen, reposition it - if ((targetWindow.x > desktop.width || (targetWindow.x + targetWindow.width) < 0) || - (targetWindow.y > desktop.height || (targetWindow.y + targetWindow.height) < 0)) { + + // if we asked to force reposition, or if the window is completely outside of the recommended rectangle, reposition it + if (forceReposition || (targetWindow.x > maxX || (targetWindow.x + targetWindow.width) < recommended.x) || + (targetWindow.y > maxY || (targetWindow.y + targetWindow.height) < recommended.y)) { newPosition.x = -1 newPosition.y = -1 } + if (newPosition.x === -1 && newPosition.y === -1) { - // Set initial window position - // var minPosition = Qt.vector2d(-windowRect.x, -windowRect.y); - // var maxPosition = Qt.vector2d(desktop.width - windowRect.width, desktop.height - windowRect.height); - // newPosition = Utils.clampVector(newPosition, minPosition, maxPosition); - // newPosition = Utils.randomPosition(minPosition, maxPosition); - newPosition = Qt.vector2d(desktop.width / 2 - targetWindow.width / 2, - desktop.height / 2 - targetWindow.height / 2); + var originRelativeX = (targetWindow.x - oldRecommendedRect.x); + var originRelativeY = (targetWindow.y - oldRecommendedRect.y); + if (isNaN(originRelativeX)) { + originRelativeX = 0; + } + if (isNaN(originRelativeY)) { + originRelativeY = 0; + } + var fractionX = Utils.clamp(originRelativeX / oldRecommendedDimmensions.x, 0, 1); + var fractionY = Utils.clamp(originRelativeY / oldRecommendedDimmensions.y, 0, 1); + + var newX = (fractionX * newRecommendedDimmensions.x) + newRecommendedRect.x; + var newY = (fractionY * newRecommendedDimmensions.y) + newRecommendedRect.y; + + newPosition = Qt.vector2d(newX, newY); } targetWindow.x = newPosition.x; targetWindow.y = newPosition.y; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dbb30a4582..f3639946ad 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -147,6 +147,8 @@ #include "Util.h" #include "InterfaceParentFinder.h" +#include "FrameTimingsScriptingInterface.h" + // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. #if defined(Q_OS_WIN) @@ -193,12 +195,7 @@ static const uint32_t INVALID_FRAME = UINT32_MAX; static const float PHYSICS_READY_RANGE = 3.0f; // how far from avatar to check for entities that aren't ready for simulation -#ifndef __APPLE__ static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); -#else -// Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475 -static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).append("/script.js"); -#endif Setting::Handle maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS); @@ -1233,6 +1230,9 @@ void Application::cleanupBeforeQuit() { } Application::~Application() { + _entityClipboard->eraseAllOctreeElements(); + _entityClipboard.reset(); + EntityTreePointer tree = getEntities()->getTree(); tree->setSimulation(nullptr); @@ -1242,7 +1242,7 @@ Application::~Application() { _physicsEngine->setCharacterController(nullptr); // remove avatars from physics engine - DependencyManager::get()->clearOtherAvatars(); + DependencyManager::get()->clearAllAvatars(); VectorOfMotionStates motionStates; DependencyManager::get()->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); @@ -1337,6 +1337,8 @@ void Application::initializeGL() { InfoView::show(INFO_HELP_PATH, true); } +FrameTimingsScriptingInterface _frameTimingsScriptingInterface; + extern void setupPreferences(); void Application::initializeUi() { @@ -1381,6 +1383,8 @@ void Application::initializeUi() { rootContext->setContextProperty("Messages", DependencyManager::get().data()); rootContext->setContextProperty("Recording", DependencyManager::get().data()); rootContext->setContextProperty("Preferences", DependencyManager::get().data()); + rootContext->setContextProperty("AddressManager", DependencyManager::get().data()); + rootContext->setContextProperty("FrameTimings", &_frameTimingsScriptingInterface); rootContext->setContextProperty("TREE_SCALE", TREE_SCALE); rootContext->setContextProperty("Quat", new Quat()); @@ -1424,6 +1428,7 @@ void Application::initializeUi() { rootContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface()); rootContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor()); + _glWidget->installEventFilter(offscreenUi.data()); offscreenUi->setMouseTranslator([=](const QPointF& pt) { @@ -1466,9 +1471,9 @@ void Application::initializeUi() { }); } + void Application::paintGL() { updateHeartbeat(); - // Some plugins process message events, potentially leading to // re-entering a paint event. don't allow further processing if this // happens @@ -1486,6 +1491,7 @@ void Application::paintGL() { _frameCount++; _frameCounter.increment(); + auto lastPaintBegin = usecTimestampNow(); PROFILE_RANGE_EX(__FUNCTION__, 0xff0000ff, (uint64_t)_frameCount); PerformanceTimer perfTimer("paintGL"); @@ -1738,6 +1744,9 @@ void Application::paintGL() { batch.resetStages(); }); } + + uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin; + _frameTimingsScriptingInterface.addValue(lastPaintDuration); } void Application::runTests() { @@ -2672,8 +2681,6 @@ void Application::idle(uint64_t now) { _overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays)); } - - // If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus. auto offscreenUi = DependencyManager::get(); if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) { @@ -4885,19 +4892,44 @@ QRect Application::getRenderingGeometry() const { } glm::uvec2 Application::getUiSize() const { - return getActiveDisplayPlugin()->getRecommendedUiSize(); + static const uint MIN_SIZE = 1; + glm::uvec2 result(MIN_SIZE); + if (_displayPlugin) { + result = getActiveDisplayPlugin()->getRecommendedUiSize(); + } + return result; +} + +QRect Application::getRecommendedOverlayRect() const { + auto uiSize = getUiSize(); + QRect result(0, 0, uiSize.x, uiSize.y); + if (_displayPlugin) { + result = getActiveDisplayPlugin()->getRecommendedOverlayRect(); + } + return result; } QSize Application::getDeviceSize() const { - return fromGlm(getActiveDisplayPlugin()->getRecommendedRenderSize()); + static const int MIN_SIZE = 1; + QSize result(MIN_SIZE, MIN_SIZE); + if (_displayPlugin) { + result = fromGlm(getActiveDisplayPlugin()->getRecommendedRenderSize()); + } + return result; } bool Application::isThrottleRendering() const { - return getActiveDisplayPlugin()->isThrottled(); + if (_displayPlugin) { + return getActiveDisplayPlugin()->isThrottled(); + } + return false; } bool Application::hasFocus() const { - return getActiveDisplayPlugin()->hasFocus(); + if (_displayPlugin) { + return getActiveDisplayPlugin()->hasFocus(); + } + return (QApplication::activeWindow() != nullptr); } glm::vec2 Application::getViewportDimensions() const { diff --git a/interface/src/Application.h b/interface/src/Application.h index 6bfad21525..2911d42b65 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -117,6 +117,7 @@ public: QRect getRenderingGeometry() const; glm::uvec2 getUiSize() const; + QRect getRecommendedOverlayRect() const; QSize getDeviceSize() const; bool hasFocus() const; diff --git a/interface/src/FrameTimingsScriptingInterface.cpp b/interface/src/FrameTimingsScriptingInterface.cpp new file mode 100644 index 0000000000..59b653105c --- /dev/null +++ b/interface/src/FrameTimingsScriptingInterface.cpp @@ -0,0 +1,53 @@ +// +// Created by Bradley Austin Davis on 2016/04/04 +// Copyright 2013-2016 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 "FrameTimingsScriptingInterface.h" + +#include + +void FrameTimingsScriptingInterface::start() { + _values.clear(); + DependencyManager::get()->setUnusedResourceCacheSize(0); + _values.reserve(8192); + _active = true; +} + +void FrameTimingsScriptingInterface::addValue(uint64_t value) { + if (_active) { + _values.push_back(value); + } +} + +void FrameTimingsScriptingInterface::finish() { + _active = false; + uint64_t total = 0; + _min = std::numeric_limits::max(); + _max = std::numeric_limits::lowest(); + size_t count = _values.size(); + for (size_t i = 0; i < count; ++i) { + const uint64_t& value = _values[i]; + _max = std::max(_max, value); + _min = std::min(_min, value); + total += value; + } + _mean = (float)total / (float)count; + float deviationTotal = 0; + for (size_t i = 0; i < count; ++i) { + float deviation = _values[i] - _mean; + deviationTotal += deviation*deviation; + } + _stdDev = sqrt(deviationTotal / (float)count); +} + +QVariantList FrameTimingsScriptingInterface::getValues() const { + QVariantList result; + for (quint64 v : _values) { + result << QVariant(v); + } + return result; +} diff --git a/interface/src/FrameTimingsScriptingInterface.h b/interface/src/FrameTimingsScriptingInterface.h new file mode 100644 index 0000000000..73eb1aec67 --- /dev/null +++ b/interface/src/FrameTimingsScriptingInterface.h @@ -0,0 +1,38 @@ +// +// Created by Bradley Austin Davis on 2016/04/04 +// Copyright 2013-2016 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 +#include +#include + +class FrameTimingsScriptingInterface : public QObject { + Q_OBJECT + Q_PROPERTY(float mean READ getMean CONSTANT) + Q_PROPERTY(float max READ getMax CONSTANT) + Q_PROPERTY(float min READ getMin CONSTANT) + Q_PROPERTY(float standardDeviation READ getStandardDeviation CONSTANT) +public: + Q_INVOKABLE void start(); + Q_INVOKABLE void addValue(uint64_t value); + Q_INVOKABLE void finish(); + Q_INVOKABLE QVariantList getValues() const; + + + uint64_t getMax() const { return _max; } + uint64_t getMin() const { return _min; } + float getStandardDeviation() const { return _stdDev; } + float getMean() const { return _mean; } + +protected: + std::vector _values; + bool _active { false }; + uint64_t _max { 0 }; + uint64_t _min { 0 }; + float _stdDev { 0 }; + float _mean { 0 }; +}; diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index a0283f0efd..7e2deef494 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -42,26 +41,11 @@ static const float TAU = 6.28318530717958f; //static const float MILKY_WAY_RATIO = 0.4f; static const char* UNIFORM_TIME_NAME = "iGlobalTime"; - - -Stars::Stars() { -} - -Stars::~Stars() { -} - // Produce a random float value between 0 and 1 static float frand() { return (float)rand() / (float)RAND_MAX; } -// Produce a random radian value between 0 and 2 PI (TAU) -/* -static float rrand() { - return frand() * TAU; -} - */ - // http://mathworld.wolfram.com/SpherePointPicking.html static vec2 randPolar() { vec2 result(frand(), frand()); @@ -115,59 +99,56 @@ struct StarVertex { vec4 colorAndSize; }; -// FIXME star colors -void Stars::render(RenderArgs* renderArgs, float alpha) { - static gpu::BufferPointer vertexBuffer; - static gpu::Stream::FormatPointer streamFormat; - static gpu::Element positionElement, colorElement; - static gpu::PipelinePointer _gridPipeline; - static gpu::PipelinePointer _starsPipeline; - static int32_t _timeSlot{ -1 }; - static std::once_flag once; +static const int STARS_VERTICES_SLOT{ 0 }; +static const int STARS_COLOR_SLOT{ 1 }; - const int VERTICES_SLOT = 0; - const int COLOR_SLOT = 1; +gpu::PipelinePointer Stars::_gridPipeline{}; +gpu::PipelinePointer Stars::_starsPipeline{}; +int32_t Stars::_timeSlot{ -1 }; - std::call_once(once, [&] { - { - auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)); - auto ps = gpu::Shader::createPixel(std::string(starsGrid_frag)); - auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram((*program)); - _timeSlot = program->getBuffers().findLocation(UNIFORM_TIME_NAME); - if (_timeSlot == gpu::Shader::INVALID_LOCATION) { - _timeSlot = program->getUniforms().findLocation(UNIFORM_TIME_NAME); - } - auto state = gpu::StatePointer(new gpu::State()); - // enable decal blend - state->setDepthTest(gpu::State::DepthTest(false)); - state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); - state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - _gridPipeline = gpu::Pipeline::create(program, state); - } - { - auto vs = gpu::Shader::createVertex(std::string(stars_vert)); - auto ps = gpu::Shader::createPixel(std::string(stars_frag)); - auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram((*program)); - auto state = gpu::StatePointer(new gpu::State()); - // enable decal blend - state->setDepthTest(gpu::State::DepthTest(false)); - state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); - state->setAntialiasedLineEnable(true); // line smoothing also smooth points - state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - _starsPipeline = gpu::Pipeline::create(program, state); - +void Stars::init() { + if (!_gridPipeline) { + auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)); + auto ps = gpu::Shader::createPixel(std::string(starsGrid_frag)); + auto program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram((*program)); + _timeSlot = program->getBuffers().findLocation(UNIFORM_TIME_NAME); + if (_timeSlot == gpu::Shader::INVALID_LOCATION) { + _timeSlot = program->getUniforms().findLocation(UNIFORM_TIME_NAME); } + auto state = gpu::StatePointer(new gpu::State()); + // enable decal blend + state->setDepthTest(gpu::State::DepthTest(false)); + state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); + _gridPipeline = gpu::Pipeline::create(program, state); + } + if (!_starsPipeline) { + auto vs = gpu::Shader::createVertex(std::string(stars_vert)); + auto ps = gpu::Shader::createPixel(std::string(stars_frag)); + auto program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram((*program)); + auto state = gpu::StatePointer(new gpu::State()); + // enable decal blend + state->setDepthTest(gpu::State::DepthTest(false)); + state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + state->setAntialiasedLineEnable(true); // line smoothing also smooth points + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); + _starsPipeline = gpu::Pipeline::create(program, state); + } + + unsigned limit = STARFIELD_NUM_STARS; + std::vector points; + points.resize(limit); + + { // generate stars QElapsedTimer startTime; startTime.start(); + vertexBuffer.reset(new gpu::Buffer); srand(STARFIELD_SEED); - unsigned limit = STARFIELD_NUM_STARS; - std::vector points; - points.resize(limit); for (size_t star = 0; star < limit; ++star) { points[star].position = vec4(fromPolar(randPolar()), 1); float size = frand() * 2.5f + 0.5f; @@ -179,16 +160,32 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { points[star].colorAndSize = vec4(color, size); } } + double timeDiff = (double)startTime.nsecsElapsed() / 1000000.0; // ns to ms qDebug() << "Total time to generate stars: " << timeDiff << " msec"; + } + + gpu::Element positionElement, colorElement; + const size_t VERTEX_STRIDE = sizeof(StarVertex); + + vertexBuffer->append(VERTEX_STRIDE * limit, (const gpu::Byte*)&points[0]); + streamFormat.reset(new gpu::Stream::Format()); // 1 for everyone + streamFormat->setAttribute(gpu::Stream::POSITION, STARS_VERTICES_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), 0); + streamFormat->setAttribute(gpu::Stream::COLOR, STARS_COLOR_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA)); + positionElement = streamFormat->getAttributes().at(gpu::Stream::POSITION)._element; + colorElement = streamFormat->getAttributes().at(gpu::Stream::COLOR)._element; + + size_t offset = offsetof(StarVertex, position); + positionView = gpu::BufferView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, positionElement); + + offset = offsetof(StarVertex, colorAndSize); + colorView = gpu::BufferView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, colorElement); +} + +// FIXME star colors +void Stars::render(RenderArgs* renderArgs, float alpha) { + std::call_once(once, [&]{ init(); }); - vertexBuffer->append(sizeof(StarVertex) * limit, (const gpu::Byte*)&points[0]); - streamFormat.reset(new gpu::Stream::Format()); // 1 for everyone - streamFormat->setAttribute(gpu::Stream::POSITION, VERTICES_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), 0); - streamFormat->setAttribute(gpu::Stream::COLOR, COLOR_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA)); - positionElement = streamFormat->getAttributes().at(gpu::Stream::POSITION)._element; - colorElement = streamFormat->getAttributes().at(gpu::Stream::COLOR)._element; - }); auto modelCache = DependencyManager::get(); auto textureCache = DependencyManager::get(); @@ -210,17 +207,10 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { batch._glUniform1f(_timeSlot, secs); geometryCache->renderCube(batch); - static const size_t VERTEX_STRIDE = sizeof(StarVertex); - size_t offset = offsetof(StarVertex, position); - gpu::BufferView posView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, positionElement); - offset = offsetof(StarVertex, colorAndSize); - gpu::BufferView colView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, colorElement); - // Render the stars batch.setPipeline(_starsPipeline); - batch.setInputFormat(streamFormat); - batch.setInputBuffer(VERTICES_SLOT, posView); - batch.setInputBuffer(COLOR_SLOT, colView); + batch.setInputBuffer(STARS_VERTICES_SLOT, positionView); + batch.setInputBuffer(STARS_COLOR_SLOT, colorView); batch.draw(gpu::Primitive::POINTS, STARFIELD_NUM_STARS); } diff --git a/interface/src/Stars.h b/interface/src/Stars.h index 73f04755ab..f07caff770 100644 --- a/interface/src/Stars.h +++ b/interface/src/Stars.h @@ -12,21 +12,37 @@ #ifndef hifi_Stars_h #define hifi_Stars_h +#include + class RenderArgs; // Starfield rendering component. class Stars { public: - Stars(); - ~Stars(); + Stars() = default; + ~Stars() = default; + + Stars(Stars const&) = delete; + Stars& operator=(Stars const&) = delete; // Renders the starfield from a local viewer's perspective. // The parameters specifiy the field of view. void render(RenderArgs* args, float alpha); + private: - // don't copy/assign - Stars(Stars const&); // = delete; - Stars& operator=(Stars const&); // delete; + // Pipelines + static gpu::PipelinePointer _gridPipeline; + static gpu::PipelinePointer _starsPipeline; + static int32_t _timeSlot; + + // Buffers + gpu::BufferPointer vertexBuffer; + gpu::Stream::FormatPointer streamFormat; + gpu::BufferView positionView; + gpu::BufferView colorView; + std::once_flag once; + + void init(); }; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 9f3a0eb254..efd1bede47 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -77,7 +77,6 @@ AvatarManager::AvatarManager(QObject* parent) : } AvatarManager::~AvatarManager() { - _myAvatar->die(); } void AvatarManager::init() { @@ -250,6 +249,17 @@ void AvatarManager::clearOtherAvatars() { _myAvatar->clearLookAtTargetAvatar(); } +void AvatarManager::clearAllAvatars() { + clearOtherAvatars(); + + QWriteLocker locker(&_hashLock); + + _myAvatar->die(); + _myAvatar.reset(); + + _avatarHash.clear(); +} + void AvatarManager::setLocalLights(const QVector& localLights) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setLocalLights", Q_ARG(const QVector&, localLights)); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 57fc1022ea..bedd089c93 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -45,6 +45,7 @@ public: void updateOtherAvatars(float deltaTime); void clearOtherAvatars(); + void clearAllAvatars(); bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 906f7b4c9f..4472abe6c3 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -204,6 +204,15 @@ MyAvatar::~MyAvatar() { _lookAtTargetAvatar.reset(); } +void MyAvatar::setOrientationVar(const QVariant& newOrientationVar) { + Avatar::setOrientation(quatFromVariant(newOrientationVar)); +} + +QVariant MyAvatar::getOrientationVar() const { + return quatToVariant(Avatar::getOrientation()); +} + + // virtual void MyAvatar::simulateAttachments(float deltaTime) { // don't update attachments here, do it in harvestResultsFromPhysicsSimulation() diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 92bf9e7614..b15f812197 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -105,6 +105,10 @@ public: // thread safe Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const; + Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar); + Q_INVOKABLE QVariant getOrientationVar() const; + + // Pass a recent sample of the HMD to the avatar. // This can also update the avatar's position to follow the HMD // as it moves through the world. diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index f6e5b6364f..d28c209a52 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -80,6 +80,11 @@ glm::vec2 ControllerScriptingInterface::getViewportDimensions() const { return qApp->getUiSize(); } +QVariant ControllerScriptingInterface::getRecommendedOverlayRect() const { + auto rect = qApp->getRecommendedOverlayRect(); + return qRectToVariant(rect); +} + controller::InputController* ControllerScriptingInterface::createInputController(const QString& deviceName, const QString& tracker) { // This is where we retrieve the Device Tracker category and then the sub tracker within it auto icIt = _inputControllers.find(0); diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 43bb6987db..fc8d125839 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -96,6 +96,7 @@ public slots: virtual void releaseJoystick(int joystickIndex); virtual glm::vec2 getViewportDimensions() const; + virtual QVariant getRecommendedOverlayRect() const; /// Factory to create an InputController virtual controller::InputController* createInputController(const QString& deviceName, const QString& tracker); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index b39fd8861d..4648fc8957 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -34,6 +34,7 @@ static const float reticleSize = TWO_PI / 100.0f; static QString _tooltipId; const uvec2 CompositorHelper::VIRTUAL_SCREEN_SIZE = uvec2(3960, 1188); // ~10% more pixel density than old version, 72dx240d FOV +const QRect CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT = QRect(956, 0, 2048, 1188); // don't include entire width only center 2048 const float CompositorHelper::VIRTUAL_UI_ASPECT_RATIO = (float)VIRTUAL_SCREEN_SIZE.x / (float)VIRTUAL_SCREEN_SIZE.y; const vec2 CompositorHelper::VIRTUAL_UI_TARGET_FOV = vec2(PI * 3.0f / 2.0f, PI * 3.0f / 2.0f / VIRTUAL_UI_ASPECT_RATIO); const vec2 CompositorHelper::MOUSE_EXTENTS_ANGULAR_SIZE = vec2(PI * 2.0f, PI * 0.95f); // horizontal: full sphere, vertical: ~5deg from poles diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 062e5c1319..c0b53b329e 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -42,6 +42,7 @@ class CompositorHelper : public QObject, public Dependency { Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop) public: static const uvec2 VIRTUAL_SCREEN_SIZE; + static const QRect VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; static const float VIRTUAL_UI_ASPECT_RATIO; static const vec2 VIRTUAL_UI_TARGET_FOV; static const vec2 MOUSE_EXTENTS_ANGULAR_SIZE; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 34e484a988..79b50a7f88 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -34,6 +34,11 @@ glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const { return CompositorHelper::VIRTUAL_SCREEN_SIZE; } +QRect HmdDisplayPlugin::getRecommendedOverlayRect() const { + return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; +} + + bool HmdDisplayPlugin::internalActivate() { _monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index d16f03112f..e6ceb7e376 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -26,6 +26,8 @@ public: void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) override final; bool isDisplayVisible() const override { return isHmdMounted(); } + QRect getRecommendedOverlayRect() const override final; + virtual glm::mat4 getHeadPose() const override; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 1f5db65089..65811c5c57 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -926,7 +926,8 @@ void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) { glm::quat dQ = computeBulletRotationStep(localAngularVelocity, dt); rotation = glm::normalize(dQ * rotation); - setRotation(rotation); + bool success; + setOrientation(rotation, success, false); } setLocalAngularVelocity(localAngularVelocity); @@ -1983,10 +1984,10 @@ void EntityItem::locationChanged(bool tellPhysics) { requiresRecalcBoxes(); if (tellPhysics) { _dirtyFlags |= Simulation::DIRTY_TRANSFORM; - } - EntityTreePointer tree = getTree(); - if (tree) { - tree->entityChanged(getThisPointer()); + EntityTreePointer tree = getTree(); + if (tree) { + tree->entityChanged(getThisPointer()); + } } SpatiallyNestable::locationChanged(tellPhysics); // tell all the children, also } diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 14bfc5ac7a..7eed6d1c1a 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -35,6 +35,7 @@ void EntitySimulation::updateEntities() { callUpdateOnEntitiesThatNeedIt(now); moveSimpleKinematics(now); updateEntitiesInternal(now); + PerformanceTimer perfTimer("sortingEntities"); sortEntitiesThatMoved(); } @@ -133,10 +134,8 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { // protected void EntitySimulation::sortEntitiesThatMoved() { - QMutexLocker lock(&_mutex); // NOTE: this is only for entities that have been moved by THIS EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. - PerformanceTimer perfTimer("sortingEntities"); MovingEntitiesOperator moveOperator(_entityTree); AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE); SetOfEntities::iterator itemItr = _entitiesToSort.begin(); diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 442ff4a74b..cbedbbd868 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -92,7 +92,7 @@ protected: void expireMortalEntities(const quint64& now); void callUpdateOnEntitiesThatNeedIt(const quint64& now); - void sortEntitiesThatMoved(); + virtual void sortEntitiesThatMoved(); QMutex _mutex{ QMutex::Recursive }; diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 6bf25f767d..e406926141 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -132,3 +132,12 @@ void SimpleEntitySimulation::clearEntitiesInternal() { _entitiesThatNeedSimulationOwner.clear(); } +void SimpleEntitySimulation::sortEntitiesThatMoved() { + SetOfEntities::iterator itemItr = _entitiesToSort.begin(); + while (itemItr != _entitiesToSort.end()) { + EntityItemPointer entity = *itemItr; + entity->computePuffedQueryAACube(); + ++itemItr; + } + EntitySimulation::sortEntitiesThatMoved(); +} diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index d9c04fdcf9..12ded8a30d 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -30,6 +30,8 @@ protected: virtual void changeEntityInternal(EntityItemPointer entity) override; virtual void clearEntitiesInternal() override; + virtual void sortEntitiesThatMoved() override; + SetOfEntities _entitiesWithSimulationOwner; SetOfEntities _entitiesThatNeedSimulationOwner; quint64 _nextOwnerlessExpiry { 0 }; diff --git a/libraries/gl/src/gl/OglplusHelpers.cpp b/libraries/gl/src/gl/OglplusHelpers.cpp index 11c4f2fe3d..220ea86646 100644 --- a/libraries/gl/src/gl/OglplusHelpers.cpp +++ b/libraries/gl/src/gl/OglplusHelpers.cpp @@ -126,7 +126,7 @@ ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect) { } ShapeWrapperPtr loadSkybox(ProgramPtr program) { - return ShapeWrapperPtr(new shapes::ShapeWrapper({ { "Position" } }, shapes::SkyBox(), *program)); + return ShapeWrapperPtr(new shapes::ShapeWrapper(std::initializer_list{ "Position" }, shapes::SkyBox(), *program)); } // Return a point's cartesian coordinates on a sphere from pitch and yaw diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index aabd84fbfb..9806c17db4 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -139,7 +139,7 @@ public: GLuint _virtualSize; // theorical size as expected GLuint _numLevels{ 0 }; - void transferMip(GLenum target, const Texture::PixelsPointer& mip) const; + void transferMip(uint16_t mipLevel, uint8_t face = 0) const; // The owning texture const Texture& _gpuTexture; diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index 097d7f73cd..dfb854143b 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -93,25 +93,22 @@ void GLBackend::GLTexture::createTexture() { (void)CHECK_GL_ERROR(); // Fixme: this usage of TexStorage doesn;t work wtih compressed texture, altuogh it should. // GO through the process of allocating the correct storage - /* if (GLEW_VERSION_4_2 && !texture.getTexelFormat().isCompressed()) { - glTexStorage2D(_target, _numLevels, texelFormat.internalFormat, width, height); - (void)CHECK_GL_ERROR(); - } else*/ - { + if (GLEW_VERSION_4_2 && !_gpuTexture.getTexelFormat().isCompressed()) { + glTexStorage2D(_target, _numLevels, texelFormat.internalFormat, width, height); + (void)CHECK_GL_ERROR(); + } else { glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, _numLevels - 1); - - // for (int l = 0; l < _numLevels; l++) { - { int l = 0; - if (_gpuTexture.getType() == gpu::Texture::TEX_CUBE) { - for (size_t face = 0; face < CUBE_NUM_FACES; face++) { - glTexImage2D(CUBE_FACE_LAYOUT[face], l, texelFormat.internalFormat, width, height, 0, texelFormat.format, texelFormat.type, NULL); + for (uint16_t l = 0; l < _numLevels; l++) { + if (_gpuTexture.getType() == gpu::Texture::TEX_CUBE) { + for (size_t face = 0; face < CUBE_NUM_FACES; face++) { + glTexImage2D(CUBE_FACE_LAYOUT[face], l, texelFormat.internalFormat, width, height, 0, texelFormat.format, texelFormat.type, NULL); + } + } else { + glTexImage2D(_target, l, texelFormat.internalFormat, width, height, 0, texelFormat.format, texelFormat.type, NULL); } - } else { - glTexImage2D(_target, l, texelFormat.internalFormat, width, height, 0, texelFormat.format, texelFormat.type, NULL); - } - width = std::max(1, (width / 2)); - height = std::max(1, (height / 2)); + width = std::max(1, (width / 2)); + height = std::max(1, (height / 2)); } (void)CHECK_GL_ERROR(); } @@ -213,9 +210,13 @@ bool GLBackend::GLTexture::isReady() const { } // Move content bits from the CPU to the GPU for a given mip / face -void GLBackend::GLTexture::transferMip(GLenum target, const Texture::PixelsPointer& mip) const { +void GLBackend::GLTexture::transferMip(uint16_t mipLevel, uint8_t face) const { + auto mip = _gpuTexture.accessStoredMipFace(mipLevel, face); GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuTexture.getTexelFormat(), mip->getFormat()); - glTexSubImage2D(target, 0, 0, 0, _gpuTexture.getWidth(), _gpuTexture.getHeight(), texelFormat.format, texelFormat.type, mip->readData()); + GLenum target = _target == GL_TEXTURE_2D ? GL_TEXTURE_2D : CUBE_FACE_LAYOUT[face]; + uvec2 size = uvec2(_gpuTexture.getWidth(), _gpuTexture.getHeight()); + size >>= mipLevel; + glTexSubImage2D(target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); (void)CHECK_GL_ERROR(); } @@ -234,16 +235,20 @@ void GLBackend::GLTexture::transfer() const { // GO through the process of allocating the correct storage and/or update the content switch (_gpuTexture.getType()) { case Texture::TEX_2D: - if (_gpuTexture.isStoredMipFaceAvailable(0)) { - transferMip(GL_TEXTURE_2D, _gpuTexture.accessStoredMipFace(0)); + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuTexture.isStoredMipFaceAvailable(i)) { + transferMip(i); + } } break; case Texture::TEX_CUBE: // transfer pixels from each faces for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { - if (_gpuTexture.isStoredMipFaceAvailable(0, f)) { - transferMip(CUBE_FACE_LAYOUT[f], _gpuTexture.accessStoredMipFace(0, f)); + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuTexture.isStoredMipFaceAvailable(i, f)) { + transferMip(i, f); + } } } break; @@ -269,12 +274,21 @@ void GLBackend::GLTexture::postTransfer() { // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory switch (_gpuTexture.getType()) { case Texture::TEX_2D: - _gpuTexture.notifyMipFaceGPULoaded(0, 0); + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuTexture.isStoredMipFaceAvailable(i)) { + _gpuTexture.notifyMipFaceGPULoaded(i); + } + } break; case Texture::TEX_CUBE: - for (uint8_t f = 0; f < CUBE_NUM_FACES; ++f) { - _gpuTexture.notifyMipFaceGPULoaded(0, f); + // transfer pixels from each faces + for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuTexture.isStoredMipFaceAvailable(i, f)) { + _gpuTexture.notifyMipFaceGPULoaded(i, f); + } + } } break; @@ -345,7 +359,7 @@ GLuint GLBackend::getTextureID(const TexturePointer& texture, bool sync) { } else { object = Backend::getGPUObject(*texture); } - if (object) { + if (object && object->getSyncState() == GLTexture::Idle) { return object->_texture; } else { return 0; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 9ef98098d3..4715170cef 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -428,7 +428,7 @@ public: Stamp getSamplerStamp() const { return _samplerStamp; } // Only callable by the Backend - void notifyMipFaceGPULoaded(uint16 level, uint8 face) const { return _storage->notifyMipFaceGPULoaded(level, face); } + void notifyMipFaceGPULoaded(uint16 level, uint8 face = 0) const { return _storage->notifyMipFaceGPULoaded(level, face); } const GPUObjectPointer gpuObject {}; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index e482c20b11..0e93119fe3 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -154,21 +154,63 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, TextureType type return ResourceCache::getResource(url, QUrl(), content.isEmpty(), &extra).staticCast(); } -/// Returns a texture version of an image file -gpu::TexturePointer TextureCache::getImageTexture(const QString& path) { - QImage image = QImage(path).mirrored(false, true); - gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); - gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); - if (image.hasAlphaChannel()) { - formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); - formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::BGRA); + +TextureCache::TextureLoaderFunc getTextureLoaderForType(TextureType type) { + switch (type) { + case ALBEDO_TEXTURE: { + return model::TextureUsage::createAlbedoTextureFromImage; + break; + } + case EMISSIVE_TEXTURE: { + return model::TextureUsage::createEmissiveTextureFromImage; + break; + } + case LIGHTMAP_TEXTURE: { + return model::TextureUsage::createLightmapTextureFromImage; + break; + } + case CUBE_TEXTURE: { + return model::TextureUsage::createCubeTextureFromImage; + break; + } + case BUMP_TEXTURE: { + return model::TextureUsage::createNormalTextureFromBumpImage; + break; + } + case NORMAL_TEXTURE: { + return model::TextureUsage::createNormalTextureFromNormalImage; + break; + } + case ROUGHNESS_TEXTURE: { + return model::TextureUsage::createRoughnessTextureFromImage; + break; + } + case GLOSS_TEXTURE: { + return model::TextureUsage::createRoughnessTextureFromGlossImage; + break; + } + case SPECULAR_TEXTURE: { + return model::TextureUsage::createMetallicTextureFromImage; + break; + } + case CUSTOM_TEXTURE: { + Q_ASSERT(false); + return TextureCache::TextureLoaderFunc(); + break; + } + case DEFAULT_TEXTURE: + default: { + return model::TextureUsage::create2DTextureFromImage; + break; + } } - gpu::TexturePointer texture = gpu::TexturePointer( - gpu::Texture::create2D(formatGPU, image.width(), image.height(), - gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); - texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - texture->autoGenerateMips(-1); - return texture; +} + +/// Returns a texture version of an image file +gpu::TexturePointer TextureCache::getImageTexture(const QString& path, TextureType type) { + QImage image = QImage(path); + auto loader = getTextureLoaderForType(type); + return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString())); } QSharedPointer TextureCache::createResource(const QUrl& url, @@ -203,53 +245,10 @@ NetworkTexture::NetworkTexture(const QUrl& url, const TextureLoaderFunc& texture } NetworkTexture::TextureLoaderFunc NetworkTexture::getTextureLoader() const { - switch (_type) { - case ALBEDO_TEXTURE: { - return TextureLoaderFunc(model::TextureUsage::createAlbedoTextureFromImage); - break; - } - case EMISSIVE_TEXTURE: { - return TextureLoaderFunc(model::TextureUsage::createEmissiveTextureFromImage); - break; - } - case LIGHTMAP_TEXTURE: { - return TextureLoaderFunc(model::TextureUsage::createLightmapTextureFromImage); - break; - } - case CUBE_TEXTURE: { - return TextureLoaderFunc(model::TextureUsage::createCubeTextureFromImage); - break; - } - case BUMP_TEXTURE: { - return TextureLoaderFunc(model::TextureUsage::createNormalTextureFromBumpImage); - break; - } - case NORMAL_TEXTURE: { - return TextureLoaderFunc(model::TextureUsage::createNormalTextureFromNormalImage); - break; - } - case ROUGHNESS_TEXTURE: { - return TextureLoaderFunc(model::TextureUsage::createRoughnessTextureFromImage); - break; - } - case GLOSS_TEXTURE: { - return TextureLoaderFunc(model::TextureUsage::createRoughnessTextureFromGlossImage); - break; - } - case SPECULAR_TEXTURE: { - return TextureLoaderFunc(model::TextureUsage::createMetallicTextureFromImage); - break; - } - case CUSTOM_TEXTURE: { - return _textureLoader; - break; - } - case DEFAULT_TEXTURE: - default: { - return TextureLoaderFunc(model::TextureUsage::create2DTextureFromImage); - break; - } + if (_type == CUSTOM_TEXTURE) { + return _textureLoader; } + return getTextureLoaderForType(_type); } diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 4fe9a89460..a392117958 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -72,7 +72,7 @@ public: const gpu::TexturePointer& getNormalFittingTexture(); /// Returns a texture version of an image file - static gpu::TexturePointer getImageTexture(const QString& path); + static gpu::TexturePointer getImageTexture(const QString& path, TextureType type = DEFAULT_TEXTURE); /// Loads a texture from the specified URL. NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index 1ef8d57945..7200793bed 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -150,8 +150,8 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag QImage image = process2DImageColor(srcImage, validAlpha, alphaAsMask); gpu::Texture* theTexture = nullptr; - if ((image.width() > 0) && (image.height() > 0)) { + if ((image.width() > 0) && (image.height() > 0)) { gpu::Element formatGPU; gpu::Element formatMip; defineColorTexelFormats(formatGPU, formatMip, image, isLinear, doCompress); @@ -171,6 +171,14 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag if (generateMips) { theTexture->autoGenerateMips(-1); + auto levels = theTexture->maxMip(); + uvec2 size(image.width(), image.height()); + for (uint8_t i = 1; i <= levels; ++i) { + size >>= 1; + size = glm::max(size, uvec2(1)); + image = image.scaled(size.x, size.y, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + theTexture->assignStoredMip(i, formatMip, image.byteCount(), image.constBits()); + } } } @@ -291,7 +299,6 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); - theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); theTexture->autoGenerateMips(-1); diff --git a/libraries/networking/src/AssetUtils.cpp b/libraries/networking/src/AssetUtils.cpp index e25f357fcc..c505d108e5 100644 --- a/libraries/networking/src/AssetUtils.cpp +++ b/libraries/networking/src/AssetUtils.cpp @@ -11,6 +11,8 @@ #include "AssetUtils.h" +#include + #include #include @@ -29,12 +31,15 @@ QByteArray hashData(const QByteArray& data) { QByteArray loadFromCache(const QUrl& url) { if (auto cache = NetworkAccessManager::getInstance().cache()) { - if (auto ioDevice = cache->data(url)) { + + // caller is responsible for the deletion of the ioDevice, hence the unique_ptr + if (auto ioDevice = std::unique_ptr(cache->data(url))) { qCDebug(asset_client) << url.toDisplayString() << "loaded from disk cache."; return ioDevice->readAll(); } else { qCDebug(asset_client) << url.toDisplayString() << "not in disk cache"; } + } else { qCWarning(asset_client) << "No disk cache to load assets from."; } @@ -49,7 +54,8 @@ bool saveToCache(const QUrl& url, const QByteArray& file) { metaData.setSaveToDisk(true); metaData.setLastModified(QDateTime::currentDateTime()); metaData.setExpirationDate(QDateTime()); // Never expires - + + // ioDevice is managed by the cache and should either be passed back to insert or remove! if (auto ioDevice = cache->prepare(metaData)) { ioDevice->write(file); cache->insert(ioDevice); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 96e05c8d09..8f2bbcbb85 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -28,6 +28,97 @@ (((x) > (max)) ? (max) :\ (x))) +void ResourceCacheSharedItems::appendActiveRequest(QWeakPointer resource) { + Lock lock(_mutex); + _loadingRequests.append(resource); +} + +void ResourceCacheSharedItems::appendPendingRequest(QWeakPointer resource) { + Lock lock(_mutex); + _pendingRequests.append(resource); +} + +QList> ResourceCacheSharedItems::getPendingRequests() { + QList> result; + + { + Lock lock(_mutex); + foreach(QSharedPointer resource, _pendingRequests) { + if (resource) { + result.append(resource); + } + } + } + return result; +} + +uint32_t ResourceCacheSharedItems::getPendingRequestsCount() const { + Lock lock(_mutex); + return _pendingRequests.size(); +} + +QList> ResourceCacheSharedItems::getLoadingRequests() { + QList> result; + + { + Lock lock(_mutex); + foreach(QSharedPointer resource, _loadingRequests) { + if (resource) { + result.append(resource); + } + } + } + return result; +} + +void ResourceCacheSharedItems::removeRequest(QWeakPointer resource) { + Lock lock(_mutex); + // resource can only be removed if it still has a ref-count, as + // QWeakPointer has no operator== implementation for two weak ptrs, so + // manually loop in case resource has been freed. + for (int i = 0; i < _loadingRequests.size();) { + auto request = _loadingRequests.at(i); + // Clear our resource and any freed resources + if (!request || request.data() == resource.data()) { + _loadingRequests.removeAt(i); + continue; + } + i++; + } +} + +QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { + Lock lock(_mutex); + // look for the highest priority pending request + int highestIndex = -1; + float highestPriority = -FLT_MAX; + QSharedPointer highestResource; + + for (int i = 0; i < _pendingRequests.size();) { + // Clear any freed resources + auto resource = _pendingRequests.at(i).lock(); + if (!resource) { + _pendingRequests.removeAt(i); + continue; + } + + // Check load priority + float priority = resource->getLoadPriority(); + if (priority >= highestPriority) { + highestPriority = priority; + highestIndex = i; + highestResource = resource; + } + i++; + } + + if (highestIndex >= 0) { + _pendingRequests.takeAt(highestIndex); + } + + return highestResource; +} + ResourceCache::ResourceCache(QObject* parent) : QObject(parent) { auto& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, @@ -264,81 +355,7 @@ void ResourceCache::updateTotalSize(const qint64& oldSize, const qint64& newSize _totalResourcesSize += (newSize - oldSize); emit dirty(); } - -void ResourceCacheSharedItems::appendActiveRequest(QWeakPointer resource) { - Lock lock(_mutex); - _loadingRequests.append(resource); -} - -void ResourceCacheSharedItems::appendPendingRequest(QWeakPointer resource) { - Lock lock(_mutex); - _pendingRequests.append(resource); -} - -QList> ResourceCacheSharedItems::getPendingRequests() { - QList> result; - - { - Lock lock(_mutex); - foreach(QSharedPointer resource, _pendingRequests) { - if (resource) { - result.append(resource); - } - } - } - return result; -} - -uint32_t ResourceCacheSharedItems::getPendingRequestsCount() const { - Lock lock(_mutex); - return _pendingRequests.size(); -} - -QList> ResourceCacheSharedItems::getLoadingRequests() { - QList> result; - - { - Lock lock(_mutex); - foreach(QSharedPointer resource, _loadingRequests) { - if (resource) { - result.append(resource); - } - } - } - return result; -} - -void ResourceCacheSharedItems::removeRequest(QWeakPointer resource) { - Lock lock(_mutex); - _loadingRequests.removeAll(resource); -} - -QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { - Lock lock(_mutex); - // look for the highest priority pending request - int highestIndex = -1; - float highestPriority = -FLT_MAX; - QSharedPointer highestResource; - for (int i = 0; i < _pendingRequests.size();) { - auto resource = _pendingRequests.at(i).lock(); - if (!resource) { - _pendingRequests.removeAt(i); - continue; - } - float priority = resource->getLoadPriority(); - if (priority >= highestPriority) { - highestPriority = priority; - highestIndex = i; - highestResource = resource; - } - i++; - } - if (highestIndex >= 0) { - _pendingRequests.takeAt(highestIndex); - } - return highestResource; -} - + QList> ResourceCache::getLoadingRequests() { return DependencyManager::get()->getLoadingRequests(); } diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index de6fe0c839..64a73ab12a 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -105,6 +105,12 @@ public: return aspect(getRecommendedRenderSize()); } + // The recommended bounds for primary overlay placement + virtual QRect getRecommendedOverlayRect() const { + auto recommendedSize = getRecommendedUiSize(); + return QRect(0, 0, recommendedSize.x, recommendedSize.y); + } + // Stereo specific methods virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { return baseProjection; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 78141188ef..8312de50fb 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -22,12 +22,7 @@ #define __STR1__(x) __STR2__(x) #define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning Msg: " -#ifndef __APPLE__ static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); -#else -// Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475 -static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).append("/script.js"); -#endif ScriptsModel& getScriptsModel() { static ScriptsModel scriptsModel; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 953fdb3582..53fa8b30cf 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -128,7 +128,7 @@ void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3) { vec3.z = object.property("z").toVariant().toFloat(); } -QVariant vec3toVariant(const glm::vec3 &vec3) { +QVariant vec3toVariant(const glm::vec3& vec3) { if (vec3.x != vec3.x || vec3.y != vec3.y || vec3.z != vec3.z) { // if vec3 contains a NaN don't try to convert it return QVariant(); @@ -140,6 +140,18 @@ QVariant vec3toVariant(const glm::vec3 &vec3) { return result; } +QVariant vec4toVariant(const glm::vec4& vec4) { + if (isNaN(vec4.x) || isNaN(vec4.y) || isNaN(vec4.z) || isNaN(vec4.w)) { + // if vec4 contains a NaN don't try to convert it + return QVariant(); + } + QVariantMap result; + result["x"] = vec4.x; + result["y"] = vec4.y; + result["z"] = vec4.z; + result["w"] = vec4.w; + return result; +} QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVector& vector) { QScriptValue array = engine->newArray(); @@ -150,7 +162,7 @@ QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVector()) { + v = glm::vec4(object.toFloat()); + valid = true; + } else if (object.canConvert()) { + auto qvec4 = qvariant_cast(object); + v.x = qvec4.x(); + v.y = qvec4.y(); + v.z = qvec4.z(); + v.w = qvec4.w(); + valid = true; + } else { + auto map = object.toMap(); + auto x = map["x"]; + auto y = map["y"]; + auto z = map["z"]; + auto w = map["w"]; + if (x.canConvert() && y.canConvert() && z.canConvert() && w.canConvert()) { + v.x = x.toFloat(); + v.y = y.toFloat(); + v.z = z.toFloat(); + v.w = w.toFloat(); + valid = true; + } + } + return v; +} + +glm::vec4 vec4FromVariant(const QVariant& object) { + bool valid = false; + return vec4FromVariant(object, valid); +} + +QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat& quat) { QScriptValue obj = engine->newObject(); if (quat.x != quat.x || quat.y != quat.y || quat.z != quat.z || quat.w != quat.w) { // if quat contains a NaN don't try to convert it @@ -207,7 +256,7 @@ QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat &quat) { return obj; } -void quatFromScriptValue(const QScriptValue &object, glm::quat &quat) { +void quatFromScriptValue(const QScriptValue& object, glm::quat &quat) { quat.x = object.property("x").toVariant().toFloat(); quat.y = object.property("y").toVariant().toFloat(); quat.z = object.property("z").toVariant().toFloat(); @@ -245,12 +294,12 @@ glm::quat quatFromVariant(const QVariant &object, bool& isValid) { return q; } -glm::quat quatFromVariant(const QVariant &object) { +glm::quat quatFromVariant(const QVariant& object) { bool valid = false; return quatFromVariant(object, valid); } -QVariant quatToVariant(const glm::quat &quat) { +QVariant quatToVariant(const glm::quat& quat) { if (quat.x != quat.x || quat.y != quat.y || quat.z != quat.z) { // if vec3 contains a NaN don't try to convert it return QVariant(); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 652ec26fe7..2aefd3aa47 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -43,12 +43,15 @@ void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4); // Vec4 QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4); void vec4FromScriptValue(const QScriptValue& object, glm::vec4& vec4); +QVariant vec4toVariant(const glm::vec4& vec4); +glm::vec4 vec4FromVariant(const QVariant &object, bool& valid); +glm::vec4 vec4FromVariant(const QVariant &object); // Vec3 QScriptValue vec3toScriptValue(QScriptEngine* engine, const glm::vec3 &vec3); void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3); -QVariant vec3toVariant(const glm::vec3 &vec3); +QVariant vec3toVariant(const glm::vec3& vec3); glm::vec3 vec3FromVariant(const QVariant &object, bool& valid); glm::vec3 vec3FromVariant(const QVariant &object); @@ -71,9 +74,10 @@ glm::quat quatFromVariant(const QVariant &object); // Rect QScriptValue qRectToScriptValue(QScriptEngine* engine, const QRect& rect); void qRectFromScriptValue(const QScriptValue& object, QRect& rect); - -QVariant qRectToVariant(const QRect& rect); QRect qRectFromVariant(const QVariant& object, bool& isValid); +QRect qRectFromVariant(const QVariant& object); +QVariant qRectToVariant(const QRect& rect); + // xColor QScriptValue xColorToScriptValue(QScriptEngine* engine, const xColor& color); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 11d0668001..c4cb4f94ba 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -620,7 +620,7 @@ glm::vec3 SpatiallyNestable::getLocalPosition() const { return result; } -void SpatiallyNestable::setLocalPosition(const glm::vec3& position) { +void SpatiallyNestable::setLocalPosition(const glm::vec3& position, bool tellPhysics) { // guard against introducing NaN into the transform if (isNaN(position)) { qDebug() << "SpatiallyNestable::setLocalPosition -- position contains NaN"; @@ -629,7 +629,7 @@ void SpatiallyNestable::setLocalPosition(const glm::vec3& position) { _transformLock.withWriteLock([&] { _transform.setTranslation(position); }); - locationChanged(); + locationChanged(tellPhysics); } glm::quat SpatiallyNestable::getLocalOrientation() const { diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index c120c1010c..6485c23b87 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -102,7 +102,7 @@ public: virtual void setLocalTransform(const Transform& transform); virtual glm::vec3 getLocalPosition() const; - virtual void setLocalPosition(const glm::vec3& position); + virtual void setLocalPosition(const glm::vec3& position, bool tellPhysics = true); virtual glm::quat getLocalOrientation() const; virtual void setLocalOrientation(const glm::quat& orientation); diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index d1cd4389a7..47852104a2 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -38,9 +38,11 @@ #include #include +#include + #include #include -#include +#include #include "unlit_frag.h" #include "unlit_vert.h" @@ -83,6 +85,93 @@ public: uint32_t toCompactColor(const glm::vec4& color); + +const char* VERTEX_SHADER = R"SHADER( +#version 450 core + +layout(location = 0) in vec4 inPosition; +layout(location = 3) in vec2 inTexCoord0; + +struct TransformObject { + mat4 _model; + mat4 _modelInverse; +}; + +layout(location=15) in ivec2 _drawCallInfo; + +uniform samplerBuffer transformObjectBuffer; + +TransformObject getTransformObject() { + int offset = 8 * _drawCallInfo.x; + TransformObject object; + object._model[0] = texelFetch(transformObjectBuffer, offset); + object._model[1] = texelFetch(transformObjectBuffer, offset + 1); + object._model[2] = texelFetch(transformObjectBuffer, offset + 2); + object._model[3] = texelFetch(transformObjectBuffer, offset + 3); + + object._modelInverse[0] = texelFetch(transformObjectBuffer, offset + 4); + object._modelInverse[1] = texelFetch(transformObjectBuffer, offset + 5); + object._modelInverse[2] = texelFetch(transformObjectBuffer, offset + 6); + object._modelInverse[3] = texelFetch(transformObjectBuffer, offset + 7); + + return object; +} + +struct TransformCamera { + mat4 _view; + mat4 _viewInverse; + mat4 _projectionViewUntranslated; + mat4 _projection; + mat4 _projectionInverse; + vec4 _viewport; +}; + +layout(std140) uniform transformCameraBuffer { + TransformCamera _camera; +}; + +TransformCamera getTransformCamera() { + return _camera; +} + +// the interpolated normal +out vec2 _texCoord0; + +void main(void) { + _texCoord0 = inTexCoord0.st; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + { // transformModelToClipPos + vec4 eyeWAPos; + { // _transformModelToEyeWorldAlignedPos + highp mat4 _mv = obj._model; + _mv[3].xyz -= cam._viewInverse[3].xyz; + highp vec4 _eyeWApos = (_mv * inPosition); + eyeWAPos = _eyeWApos; + } + gl_Position = cam._projectionViewUntranslated * eyeWAPos; + } + +})SHADER"; + +const char* FRAGMENT_SHADER = R"SHADER( +#version 450 core + +uniform sampler2D originalTexture; + +in vec2 _texCoord0; + +layout(location = 0) out vec4 _fragColor0; + +void main(void) { + //_fragColor0 = vec4(_texCoord0, 0.0, 1.0); + _fragColor0 = texture(originalTexture, _texCoord0); +} +)SHADER"; + + gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { auto vs = gpu::Shader::createVertex(vertexShaderSrc); auto fs = gpu::Shader::createPixel(fragmentShaderSrc); @@ -125,6 +214,7 @@ class QTestWindow : public QWindow { glm::mat4 _projectionMatrix; RateCounter fps; QTime _time; + glm::mat4 _camera; protected: void renderText(); @@ -145,7 +235,7 @@ public: setGLFormatVersion(format); format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); format.setOption(QSurfaceFormat::DebugContext); - format.setSwapInterval(0); + //format.setSwapInterval(0); setFormat(format); @@ -158,19 +248,22 @@ public: gpu::Context::init(); _context = std::make_shared(); - + makeCurrent(); auto shader = makeShader(unlit_vert, unlit_frag, gpu::Shader::BindingSet{}); auto state = std::make_shared(); state->setMultisampleEnable(true); state->setDepthTest(gpu::State::DepthTest { true }); _pipeline = gpu::Pipeline::create(shader, state); + + // Clear screen gpu::Batch batch; batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, 0.0, 0.5, 1.0 }); _context->render(batch); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); resize(QSize(800, 600)); @@ -181,182 +274,227 @@ public: virtual ~QTestWindow() { } + void updateCamera() { + float t = _time.elapsed() * 1e-4f; + glm::vec3 unitscale { 1.0f }; + glm::vec3 up { 0.0f, 1.0f, 0.0f }; + + float distance = 3.0f; + glm::vec3 camera_position { distance * sinf(t), 0.5f, distance * cosf(t) }; + + static const vec3 camera_focus(0); + static const vec3 camera_up(0, 1, 0); + _camera = glm::inverse(glm::lookAt(camera_position, camera_focus, up)); + } + + + void drawFloorGrid(gpu::Batch& batch) { + auto geometryCache = DependencyManager::get(); + // Render grid on xz plane (not the optimal way to do things, but w/e) + // Note: GeometryCache::renderGrid will *not* work, as it is apparenly unaffected by batch rotations and renders xy only + static const std::string GRID_INSTANCE = "Grid"; + static auto compactColor1 = toCompactColor(vec4 { 0.35f, 0.25f, 0.15f, 1.0f }); + static auto compactColor2 = toCompactColor(vec4 { 0.15f, 0.25f, 0.35f, 1.0f }); + static std::vector transforms; + static gpu::BufferPointer colorBuffer; + if (!transforms.empty()) { + transforms.reserve(200); + colorBuffer = std::make_shared(); + for (int i = 0; i < 100; ++i) { + { + glm::mat4 transform = glm::translate(mat4(), vec3(0, -1, -50 + i)); + transform = glm::scale(transform, vec3(100, 1, 1)); + transforms.push_back(transform); + colorBuffer->append(compactColor1); + } + + { + glm::mat4 transform = glm::mat4_cast(quat(vec3(0, PI / 2.0f, 0))); + transform = glm::translate(transform, vec3(0, -1, -50 + i)); + transform = glm::scale(transform, vec3(100, 1, 1)); + transforms.push_back(transform); + colorBuffer->append(compactColor2); + } + } + } + auto pipeline = geometryCache->getSimplePipeline(); + for (auto& transform : transforms) { + batch.setModelTransform(transform); + batch.setupNamedCalls(GRID_INSTANCE, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + batch.setViewTransform(_camera); + batch.setPipeline(_pipeline); + geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data.count(), colorBuffer); + }); + } + } + + void drawSimpleShapes(gpu::Batch& batch) { + auto geometryCache = DependencyManager::get(); + static const size_t ITEM_COUNT = 1000; + static const float SHAPE_INTERVAL = (PI * 2.0f) / ITEM_COUNT; + static const float ITEM_INTERVAL = SHAPE_INTERVAL / TYPE_COUNT; + + static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; + static const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; + static const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA }; + + static std::vector transforms; + static std::vector colors; + static gpu::BufferPointer colorBuffer; + static gpu::BufferView colorView; + static gpu::BufferView instanceXfmView; + if (!colorBuffer) { + colorBuffer = std::make_shared(); + + static const float ITEM_RADIUS = 20; + static const vec3 ITEM_TRANSLATION { 0, 0, -ITEM_RADIUS }; + for (size_t i = 0; i < TYPE_COUNT; ++i) { + GeometryCache::Shape shape = SHAPE[i]; + GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; + //indirectCommand._count + float startingInterval = ITEM_INTERVAL * i; + for (size_t j = 0; j < ITEM_COUNT; ++j) { + float theta = j * SHAPE_INTERVAL + startingInterval; + auto transform = glm::rotate(mat4(), theta, Vectors::UP); + transform = glm::rotate(transform, (randFloat() - 0.5f) * PI / 4.0f, Vectors::UNIT_X); + transform = glm::translate(transform, ITEM_TRANSLATION); + transform = glm::scale(transform, vec3(randFloat() / 2.0f + 0.5f)); + transforms.push_back(transform); + auto color = vec4 { randomColorValue(64), randomColorValue(64), randomColorValue(64), 255 }; + color /= 255.0f; + colors.push_back(color); + colorBuffer->append(toCompactColor(color)); + } + } + colorView = gpu::BufferView(colorBuffer, COLOR_ELEMENT); + } + + batch.setViewTransform(_camera); + batch.setPipeline(_pipeline); + batch.setInputFormat(getInstancedSolidStreamFormat()); + for (size_t i = 0; i < TYPE_COUNT; ++i) { + GeometryCache::Shape shape = SHAPE[i]; + GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; + batch.setInputBuffer(gpu::Stream::COLOR, colorView); + for (size_t j = 0; j < ITEM_COUNT; ++j) { + batch.setModelTransform(transforms[j]); + shapeData.draw(batch); + } + } + } + + void drawCenterShape(gpu::Batch& batch) { + // Render unlit cube + sphere + static auto startUsecs = usecTimestampNow(); + float seconds = getSeconds(startUsecs); + seconds /= 4.0f; + batch.setModelTransform(Transform()); + batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f); + + bool wire = (seconds - floorf(seconds) > 0.5f); + auto geometryCache = DependencyManager::get(); + int shapeIndex = ((int)seconds) % TYPE_COUNT; + if (wire) { + geometryCache->renderWireShape(batch, SHAPE[shapeIndex]); + } else { + geometryCache->renderShape(batch, SHAPE[shapeIndex]); + } + + batch.setModelTransform(Transform().setScale(2.05f)); + batch._glColor4f(1, 1, 1, 1); + geometryCache->renderWireCube(batch); + } + + void drawTerrain(gpu::Batch& batch) { + auto geometryCache = DependencyManager::get(); + static std::once_flag once; + static gpu::BufferPointer vertexBuffer { std::make_shared() }; + static gpu::BufferPointer indexBuffer { std::make_shared() }; + + static gpu::BufferView positionView; + static gpu::BufferView textureView; + static gpu::Stream::FormatPointer vertexFormat { std::make_shared() }; + + static gpu::TexturePointer texture; + static gpu::PipelinePointer pipeline; + std::call_once(once, [&] { + static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec4) * 2; // position, normals, textures + static const uint SHAPE_TEXTURES_OFFSET = sizeof(glm::vec4); + static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; + static const gpu::Element TEXTURE_ELEMENT { gpu::VEC2, gpu::FLOAT, gpu::UV }; + std::vector vertices; + const int MINX = -1000; + const int MAXX = 1000; + + // top + vertices.push_back(vec4(MAXX, 0, MAXX, 1)); + vertices.push_back(vec4(MAXX, MAXX, 0, 0)); + + vertices.push_back(vec4(MAXX, 0, MINX, 1)); + vertices.push_back(vec4(MAXX, 0, 0, 0)); + + vertices.push_back(vec4(MINX, 0, MINX, 1)); + vertices.push_back(vec4(0, 0, 0, 0)); + + vertices.push_back(vec4(MINX, 0, MAXX, 1)); + vertices.push_back(vec4(0, MAXX, 0, 0)); + + vertexBuffer->append(vertices); + indexBuffer->append(std::vector({ 0, 1, 2, 2, 3, 0 })); + + positionView = gpu::BufferView(vertexBuffer, 0, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); + textureView = gpu::BufferView(vertexBuffer, SHAPE_TEXTURES_OFFSET, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, TEXTURE_ELEMENT); + texture = DependencyManager::get()->getImageTexture("C:/Users/bdavis/Git/openvr/samples/bin/cube_texture.png"); + //texture = DependencyManager::get()->getImageTexture("H:/test.png"); + //texture = DependencyManager::get()->getImageTexture("H:/crate_blue.fbm/lambert8SG_Normal_OpenGL.png"); + + auto shader = makeShader(VERTEX_SHADER, FRAGMENT_SHADER, gpu::Shader::BindingSet {}); + auto state = std::make_shared(); + state->setMultisampleEnable(false); + state->setDepthTest(gpu::State::DepthTest { true }); + pipeline = gpu::Pipeline::create(shader, state); + vertexFormat->setAttribute(gpu::Stream::POSITION); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD); + }); + batch.setPipeline(pipeline); + batch.setInputBuffer(gpu::Stream::POSITION, positionView); + batch.setInputBuffer(gpu::Stream::TEXCOORD, textureView); + batch.setIndexBuffer(gpu::UINT16, indexBuffer, 0); + batch.setInputFormat(vertexFormat); + + batch.setResourceTexture(0, texture); + batch.setModelTransform(glm::translate(glm::mat4(), vec3(0, -0.1, 0))); + batch.drawIndexed(gpu::TRIANGLES, 6, 0); + + batch.setResourceTexture(0, DependencyManager::get()->getBlueTexture()); + batch.setModelTransform(glm::translate(glm::mat4(), vec3(0, -0.2, 0))); + batch.drawIndexed(gpu::TRIANGLES, 6, 0); + } + void draw() { // Attempting to draw before we're visible and have a valid size will // produce GL errors. if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) { return; } + updateCamera(); makeCurrent(); gpu::Batch batch; - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.0f, 0.0f, 1.0f }); + batch.resetStages(); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f }); batch.clearDepthFramebuffer(1e4); batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); batch.setProjectionTransform(_projectionMatrix); - float t = _time.elapsed() * 1e-3f; - glm::vec3 unitscale { 1.0f }; - glm::vec3 up { 0.0f, 1.0f, 0.0f }; - - float distance = 3.0f; - glm::vec3 camera_position{ distance * sinf(t), 0.0f, distance * cosf(t) }; - - static const vec3 camera_focus(0); - static const vec3 camera_up(0, 1, 0); - glm::mat4 camera = glm::inverse(glm::lookAt(camera_position, camera_focus, up)); - batch.setViewTransform(camera); + batch.setViewTransform(_camera); batch.setPipeline(_pipeline); batch.setModelTransform(Transform()); - auto geometryCache = DependencyManager::get(); - - // Render grid on xz plane (not the optimal way to do things, but w/e) - // Note: GeometryCache::renderGrid will *not* work, as it is apparenly unaffected by batch rotations and renders xy only - { - static const std::string GRID_INSTANCE = "Grid"; - static auto compactColor1 = toCompactColor(vec4{ 0.35f, 0.25f, 0.15f, 1.0f }); - static auto compactColor2 = toCompactColor(vec4{ 0.15f, 0.25f, 0.35f, 1.0f }); - static std::vector transforms; - static gpu::BufferPointer colorBuffer; - if (!transforms.empty()) { - transforms.reserve(200); - colorBuffer = std::make_shared(); - for (int i = 0; i < 100; ++i) { - { - glm::mat4 transform = glm::translate(mat4(), vec3(0, -1, -50 + i)); - transform = glm::scale(transform, vec3(100, 1, 1)); - transforms.push_back(transform); - colorBuffer->append(compactColor1); - } - - { - glm::mat4 transform = glm::mat4_cast(quat(vec3(0, PI / 2.0f, 0))); - transform = glm::translate(transform, vec3(0, -1, -50 + i)); - transform = glm::scale(transform, vec3(100, 1, 1)); - transforms.push_back(transform); - colorBuffer->append(compactColor2); - } - } - } - - auto pipeline = geometryCache->getSimplePipeline(); - for (auto& transform : transforms) { - batch.setModelTransform(transform); - batch.setupNamedCalls(GRID_INSTANCE, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { - batch.setViewTransform(camera); - batch.setPipeline(_pipeline); - geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data.count(), colorBuffer); - }); - } - } - - { - static const size_t ITEM_COUNT = 1000; - static const float SHAPE_INTERVAL = (PI * 2.0f) / ITEM_COUNT; - static const float ITEM_INTERVAL = SHAPE_INTERVAL / TYPE_COUNT; - - static const gpu::Element POSITION_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ }; - static const gpu::Element NORMAL_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ }; - static const gpu::Element COLOR_ELEMENT{ gpu::VEC4, gpu::NUINT8, gpu::RGBA }; - static const gpu::Element TRANSFORM_ELEMENT{ gpu::MAT4, gpu::FLOAT, gpu::XYZW }; - - - static std::vector transforms; - static std::vector colors; - static gpu::BufferPointer indirectBuffer; - static gpu::BufferPointer transformBuffer; - static gpu::BufferPointer colorBuffer; - static gpu::BufferView colorView; - static gpu::BufferView instanceXfmView; - - if (!transformBuffer) { - transformBuffer = std::make_shared(); - colorBuffer = std::make_shared(); - indirectBuffer = std::make_shared(); - - static const float ITEM_RADIUS = 20; - static const vec3 ITEM_TRANSLATION{ 0, 0, -ITEM_RADIUS }; - for (size_t i = 0; i < TYPE_COUNT; ++i) { - GeometryCache::Shape shape = SHAPE[i]; - GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; - { - gpu::Batch::DrawIndexedIndirectCommand indirectCommand; - indirectCommand._count = (uint)shapeData._indexCount; - indirectCommand._instanceCount = ITEM_COUNT; - indirectCommand._baseInstance = (uint)(i * ITEM_COUNT); - indirectCommand._firstIndex = (uint)shapeData._indexOffset / 2; - indirectCommand._baseVertex = 0; - indirectBuffer->append(indirectCommand); - } - - //indirectCommand._count - float startingInterval = ITEM_INTERVAL * i; - for (size_t j = 0; j < ITEM_COUNT; ++j) { - float theta = j * SHAPE_INTERVAL + startingInterval; - auto transform = glm::rotate(mat4(), theta, Vectors::UP); - transform = glm::rotate(transform, (randFloat() - 0.5f) * PI / 4.0f, Vectors::UNIT_X); - transform = glm::translate(transform, ITEM_TRANSLATION); - transform = glm::scale(transform, vec3(randFloat() / 2.0f + 0.5f)); - transformBuffer->append(transform); - transforms.push_back(transform); - auto color = vec4{ randomColorValue(64), randomColorValue(64), randomColorValue(64), 255 }; - color /= 255.0f; - colors.push_back(color); - colorBuffer->append(toCompactColor(color)); - } - } - colorView = gpu::BufferView(colorBuffer, COLOR_ELEMENT); - instanceXfmView = gpu::BufferView(transformBuffer, TRANSFORM_ELEMENT); - } - -#if 1 - GeometryCache::ShapeData shapeData = geometryCache->_shapes[GeometryCache::Icosahedron]; - { - batch.setViewTransform(camera); - batch.setModelTransform(Transform()); - batch.setPipeline(_pipeline); - batch.setInputFormat(getInstancedSolidStreamFormat()); - batch.setInputBuffer(gpu::Stream::COLOR, colorView); - batch.setIndirectBuffer(indirectBuffer); - shapeData.setupBatch(batch); - batch.multiDrawIndexedIndirect(TYPE_COUNT, gpu::TRIANGLES); - } -#else - batch.setViewTransform(camera); - batch.setPipeline(_pipeline); - for (size_t i = 0; i < TYPE_COUNT; ++i) { - GeometryCache::Shape shape = SHAPE[i]; - for (size_t j = 0; j < ITEM_COUNT; ++j) { - int index = i * ITEM_COUNT + j; - batch.setModelTransform(transforms[index]); - const vec4& color = colors[index]; - batch._glColor4f(color.r, color.g, color.b, 1.0); - geometryCache->renderShape(batch, shape); - } - } -#endif - } - - // Render unlit cube + sphere - static auto startUsecs = usecTimestampNow(); - float seconds = getSeconds(startUsecs); - - seconds /= 4.0f; - int shapeIndex = ((int)seconds) % TYPE_COUNT; - bool wire = (seconds - floorf(seconds) > 0.5f); - batch.setModelTransform(Transform()); - batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f); - - if (wire) { - geometryCache->renderWireShape(batch, SHAPE[shapeIndex]); - } else { - geometryCache->renderShape(batch, SHAPE[shapeIndex]); - } - - batch.setModelTransform(Transform().setScale(2.05f)); - batch._glColor4f(1, 1, 1, 1); - geometryCache->renderWireCube(batch); + //drawFloorGrid(batch); + //drawSimpleShapes(batch); + drawCenterShape(batch); + drawTerrain(batch); _context->render(batch); _qGlContext.swapBuffers(this); @@ -387,12 +525,12 @@ protected: int main(int argc, char** argv) { QGuiApplication app(argc, argv); QTestWindow window; - QTimer timer; - timer.setInterval(0); - app.connect(&timer, &QTimer::timeout, &app, [&] { + auto timer = new QTimer(&app); + timer->setInterval(0); + app.connect(timer, &QTimer::timeout, &app, [&] { window.draw(); }); - timer.start(); + timer->start(); app.exec(); return 0; }