diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 0b7af01c94..36a527009e 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -77,7 +77,19 @@ void ScriptableAvatar::update(float deltatime) { int mapping = animationJoints.indexOf(modelJoints[i]); if (mapping != -1 && !_maskedJoints.contains(modelJoints[i])) { JointData& data = _jointData[i]; - data.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + + auto newRotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + auto newTranslation = floorFrame.translations.at(i) * (1.0f - frameFraction) + + ceilFrame.translations.at(i) * frameFraction; + + if (data.rotation != newRotation) { + data.rotation = newRotation; + data.rotationSet = true; + } + if (data.translation != newTranslation) { + data.translation = newTranslation; + data.translationSet = true; + } } } } else { diff --git a/examples/acScripts/rain.js b/examples/acScripts/rain.js index 3f38b8d36a..fc02b7163f 100644 --- a/examples/acScripts/rain.js +++ b/examples/acScripts/rain.js @@ -18,7 +18,7 @@ var RainSquall = function (properties) { dropFallSpeed = 1, // m/s dropLifetime = 60, // Seconds dropSpinMax = 0, // Maximum angular velocity per axis; deg/s - debug = false, // Display origin circle; don't use running on Stack Manager + debug = true, // Display origin circle; don't use running on Stack Manager // Other squallCircle, SQUALL_CIRCLE_COLOR = { red: 255, green: 0, blue: 0 }, @@ -151,8 +151,10 @@ var RainSquall = function (properties) { }; }; +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation()))); +center.y += 10; var rainSquall1 = new RainSquall({ - origin: { x: 1195, y: 1223, z: 1020 }, + origin:center, radius: 25, dropsPerMinute: 120, dropSize: { x: 0.1, y: 0.1, z: 0.1 }, diff --git a/examples/closePaint.js b/examples/closePaint.js new file mode 100644 index 0000000000..d9f70aab3c --- /dev/null +++ b/examples/closePaint.js @@ -0,0 +1,289 @@ +// +// closePaint.js +// examples +// +// Created by Eric Levina on 9/30/15. +// Copyright 2015 High Fidelity, Inc. +// +// Run this script to be able to paint on entities you are close to, with hydras. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/utils.js"); + + +var RIGHT_HAND = 1; +var LEFT_HAND = 0; + +var MIN_POINT_DISTANCE = 0.02; +var MAX_POINT_DISTANCE = 0.5; + +var SPATIAL_CONTROLLERS_PER_PALM = 2; +var TIP_CONTROLLER_OFFSET = 1; + +var TRIGGER_ON_VALUE = 0.3; + +var MAX_DISTANCE = 10; + +var MAX_POINTS_PER_LINE = 40; + +var RIGHT_4_ACTION = 18; +var RIGHT_2_ACTION = 16; + +var LEFT_4_ACTION = 17; +var LEFT_2_ACTION = 16; + +var HUE_INCREMENT = 0.02; + +var MIN_STROKE_WIDTH = 0.002; +var MAX_STROKE_WIDTH = 0.04; + + +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(Camera.getOrientation()))); + + + +function MyController(hand, triggerAction) { + this.hand = hand; + this.strokes = []; + this.painting = false; + this.currentStrokeWidth = MIN_STROKE_WIDTH; + + if (this.hand === RIGHT_HAND) { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; + } else { + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + } + + this.triggerAction = triggerAction; + this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; + this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; + + + this.strokeColor = { + h: 0.8, + s: 0.8, + l: 0.4 + }; + + + this.laserPointer = Overlays.addOverlay("circle3d", { + color: hslToRgb(this.strokeColor), + solid: true, + position: center + }); + this.triggerValue = 0; + this.prevTriggerValue = 0; + var _this = this; + + this.update = function() { + this.updateControllerState(); + this.search(); + if (this.canPaint === true) { + this.paint(this.intersection.intersection, this.intersection.surfaceNormal); + } + }; + + this.paint = function(position, normal) { + if (this.painting === false) { + if (this.oldPosition) { + this.newStroke(this.oldPosition); + } else { + this.newStroke(position); + } + this.painting = true; + } + + + + var localPoint = Vec3.subtract(position, this.strokeBasePosition); + //Move stroke a bit forward along normal so it doesnt zfight with mesh its drawing on + localPoint = Vec3.sum(localPoint, Vec3.multiply(normal, 0.001 + Math.random() * .001)); //rand avoid z fighting + + var distance = Vec3.distance(localPoint, this.strokePoints[this.strokePoints.length - 1]); + if (this.strokePoints.length > 0 && distance < MIN_POINT_DISTANCE) { + //need a minimum distance to avoid binormal NANs + return; + } + if (this.strokePoints.length > 0 && distance > MAX_POINT_DISTANCE) { + //Prevents drawing lines accross models + this.painting = false; + return; + } + if (this.strokePoints.length === 0) { + localPoint = { + x: 0, + y: 0, + z: 0 + }; + } + + this.strokePoints.push(localPoint); + this.strokeNormals.push(normal); + this.strokeWidths.push(this.currentStrokeWidth); + Entities.editEntity(this.currentStroke, { + linePoints: this.strokePoints, + normals: this.strokeNormals, + strokeWidths: this.strokeWidths + }); + if (this.strokePoints.length === MAX_POINTS_PER_LINE) { + this.painting = false; + return; + } + this.oldPosition = position; + } + + this.newStroke = function(position) { + this.strokeBasePosition = position; + this.currentStroke = Entities.addEntity({ + position: position, + type: "PolyLine", + color: hslToRgb(this.strokeColor), + dimensions: { + x: 50, + y: 50, + z: 50 + }, + lifetime: 200 + }); + this.strokePoints = []; + this.strokeNormals = []; + this.strokeWidths = []; + + this.strokes.push(this.currentStroke); + + } + + this.updateControllerState = function() { + this.triggerValue = Controller.getActionValue(this.triggerAction); + if (this.triggerValue > TRIGGER_ON_VALUE && this.prevTriggerValue <= TRIGGER_ON_VALUE) { + this.squeeze(); + } else if (this.triggerValue < TRIGGER_ON_VALUE && this.prevTriggerValue >= TRIGGER_ON_VALUE) { + this.release(); + } + + this.prevTriggerValue = this.triggerValue; + } + + this.squeeze = function() { + this.tryPainting = true; + + } + this.release = function() { + this.painting = false; + this.tryPainting = false; + this.canPaint = false; + this.oldPosition = null; + } + this.search = function() { + + // the trigger is being pressed, do a ray test + var handPosition = this.getHandPosition(); + var pickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()) + }; + + + this.intersection = Entities.findRayIntersection(pickRay, true); + if (this.intersection.intersects) { + var distance = Vec3.distance(handPosition, this.intersection.intersection); + if (distance < MAX_DISTANCE) { + var displayPoint = this.intersection.intersection; + displayPoint = Vec3.sum(displayPoint, Vec3.multiply(this.intersection.surfaceNormal, .01)); + if (this.tryPainting) { + this.canPaint = true; + } + this.currentStrokeWidth = map(this.triggerValue, TRIGGER_ON_VALUE, 1, MIN_STROKE_WIDTH, MAX_STROKE_WIDTH); + var laserSize = map(distance, 1, MAX_DISTANCE, 0.01, 0.1); + laserSize += this.currentStrokeWidth / 2; + Overlays.editOverlay(this.laserPointer, { + visible: true, + position: displayPoint, + rotation: orientationOf(this.intersection.surfaceNormal), + size: { + x: laserSize, + y: laserSize + } + }); + + } else { + this.hitFail(); + } + } else { + this.hitFail(); + } + }; + + this.hitFail = function() { + this.canPaint = false; + + Overlays.editOverlay(this.laserPointer, { + visible: false + }); + + } + + this.cleanup = function() { + Overlays.deleteOverlay(this.laserPointer); + this.strokes.forEach(function(stroke) { + Entities.deleteEntity(stroke); + }); + } + + this.cycleColorDown = function() { + this.strokeColor.h -= HUE_INCREMENT; + if (this.strokeColor.h < 0) { + this.strokeColor = 1; + } + Overlays.editOverlay(this.laserPointer, { + color: hslToRgb(this.strokeColor) + }); + } + + this.cycleColorUp = function() { + this.strokeColor.h += HUE_INCREMENT; + if (this.strokeColor.h > 1) { + this.strokeColor.h = 0; + } + Overlays.editOverlay(this.laserPointer, { + color: hslToRgb(this.strokeColor) + }); + } +} + +var rightController = new MyController(RIGHT_HAND, Controller.findAction("RIGHT_HAND_CLICK")); +var leftController = new MyController(LEFT_HAND, Controller.findAction("LEFT_HAND_CLICK")); + +Controller.actionEvent.connect(function(action, state) { + if (state === 0) { + return; + } + if (action === RIGHT_4_ACTION) { + rightController.cycleColorUp(); + } else if (action === RIGHT_2_ACTION) { + rightController.cycleColorDown(); + } + if (action === LEFT_4_ACTION) { + leftController.cycleColorUp(); + } else if (action === LEFT_2_ACTION) { + leftController.cycleColorDown(); + } +}); + +function update() { + rightController.update(); + leftController.update(); +} + +function cleanup() { + rightController.cleanup(); + leftController.cleanup(); +} + +Script.scriptEnding.connect(cleanup); +Script.update.connect(update); diff --git a/examples/edit.js b/examples/edit.js index f508de3cec..0a1f31f0d7 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -264,7 +264,6 @@ var toolBar = (function () { cameraManager.disable(); } else { hasShownPropertiesTool = false; - cameraManager.enable(); entityListTool.setVisible(true); gridTool.setVisible(true); grid.setEnabled(true); @@ -675,15 +674,11 @@ function mouseMove(event) { lastMousePosition = { x: event.x, y: event.y }; - highlightEntityUnderCursor(lastMousePosition, false); idleMouseTimerId = Script.setTimeout(handleIdleMouse, IDLE_MOUSE_TIMEOUT); } function handleIdleMouse() { idleMouseTimerId = null; - if (isActive) { - highlightEntityUnderCursor(lastMousePosition, true); - } } function highlightEntityUnderCursor(position, accurateRay) { @@ -807,6 +802,7 @@ function mouseClickEvent(event) { selectionDisplay.select(selectedEntityID, event); if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { + cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); @@ -1147,6 +1143,7 @@ Controller.keyReleaseEvent.connect(function (event) { } else if (event.text == "f") { if (isActive) { if (selectionManager.hasSelection()) { + cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); diff --git a/examples/entityScripts/sprayPaintCan.js b/examples/entityScripts/sprayPaintCan.js deleted file mode 100644 index 21613bdeb5..0000000000 --- a/examples/entityScripts/sprayPaintCan.js +++ /dev/null @@ -1,252 +0,0 @@ -(function() { - // Script.include("../libraries/utils.js"); - //Need absolute path for now, for testing before PR merge and s3 cloning. Will change post-merge - - Script.include("../libraries/utils.js"); - GRAB_FRAME_USER_DATA_KEY = "grabFrame"; - this.userData = {}; - - var TIP_OFFSET_Z = 0.14; - var TIP_OFFSET_Y = 0.04; - - var ZERO_VEC = { - x: 0, - y: 0, - z: 0 - } - - var MAX_POINTS_PER_LINE = 40; - var MIN_POINT_DISTANCE = 0.01; - var STROKE_WIDTH = 0.02; - - var self = this; - - var timeSinceLastMoved = 0; - var RESET_TIME_THRESHOLD = 5; - var DISTANCE_FROM_HOME_THRESHOLD = 0.5; - var HOME_POSITION = { - x: 549.12, - y: 495.555, - z: 503.77 - }; - this.getUserData = function() { - - - if (this.properties.userData) { - this.userData = JSON.parse(this.properties.userData); - } - } - - this.updateUserData = function() { - Entities.editEntity(this.entityId, { - userData: JSON.stringify(this.userData) - }); - } - - this.update = function(deltaTime) { - self.getUserData(); - self.properties = Entities.getEntityProperties(self.entityId); - - if (Vec3.length(self.properties.velocity) < 0.1 && Vec3.distance(HOME_POSITION, self.properties.position) > DISTANCE_FROM_HOME_THRESHOLD) { - timeSinceLastMoved += deltaTime; - if (timeSinceLastMoved > RESET_TIME_THRESHOLD) { - self.reset(); - timeSinceLastMoved = 0; - } - } else { - timeSinceLastMoved = 0; - } - - //Only activate for the user who grabbed the object - if (self.userData.grabKey && self.userData.grabKey.activated === true && self.userData.grabKey.avatarId == MyAvatar.sessionUUID) { - if (self.activated !== true) { - //We were just grabbed, so create a particle system - self.grab(); - } - //Move emitter to where entity is always when its activated - self.sprayStream(); - } else if (self.userData.grabKey && self.userData.grabKey.activated === false && self.activated) { - self.letGo(); - } - } - - this.grab = function() { - this.activated = true; - var animationSettings = JSON.stringify({ - fps: 30, - loop: true, - firstFrame: 1, - lastFrame: 10000, - running: true - }); - var PI = 3.141593; - var DEG_TO_RAD = PI / 180.0; - - this.paintStream = Entities.addEntity({ - type: "ParticleEffect", - animationSettings: animationSettings, - position: this.properties.position, - textures: "https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png", - emitSpeed: 0, - speedSpread: 0.02, - polarFinish: 2 * DEG_TO_RAD, - emitAcceleration: ZERO_VEC, - emitRate: 100, - particleRadius: 0.01, - color: { - red: 170, - green: 20, - blue: 150 - }, - lifetime: 50, //probably wont be holding longer than this straight - }); - } - - this.letGo = function() { - this.activated = false; - Entities.deleteEntity(this.paintStream); - this.paintStream = null; - } - - this.reset = function() { - Entities.editEntity(self.entityId, { - position: HOME_POSITION, - rotation: Quat.fromPitchYawRollDegrees(0, 0, 0), - angularVelocity: ZERO_VEC, - velocity: ZERO_VEC - }); - } - - this.sprayStream = function() { - var forwardVec = Quat.getFront(Quat.multiply(self.properties.rotation , Quat.fromPitchYawRollDegrees(0, 90, 0))); - forwardVec = Vec3.normalize(forwardVec); - - var upVec = Quat.getUp(self.properties.rotation); - var position = Vec3.sum(self.properties.position, Vec3.multiply(forwardVec, TIP_OFFSET_Z)); - position = Vec3.sum(position, Vec3.multiply(upVec, TIP_OFFSET_Y)) - Entities.editEntity(self.paintStream, { - position: position, - emitOrientation: forwardVec, - emitSpeed: 5 - }); - - //Now check for an intersection with an entity - //move forward so ray doesnt intersect with gun - var origin = Vec3.sum(position, forwardVec); - var pickRay = { - origin: origin, - direction: Vec3.multiply(forwardVec, 2) - } - - var intersection = Entities.findRayIntersection(pickRay, true); - if (intersection.intersects) { - var normal = Vec3.multiply(-1, Quat.getFront(intersection.properties.rotation)); - this.paint(intersection.intersection, normal); - } - - - } - - this.paint = function(position, normal) { - if (!this.painting) { - - this.newStroke(position); - this.painting = true; - } - - if (this.strokePoints.length > MAX_POINTS_PER_LINE) { - this.painting = false; - return; - } - - var localPoint = Vec3.subtract(position, this.strokeBasePosition); - //Move stroke a bit forward along normal so it doesnt zfight with mesh its drawing on - localPoint = Vec3.sum(localPoint, Vec3.multiply(normal, .1)); - - if (this.strokePoints.length > 0 && Vec3.distance(localPoint, this.strokePoints[this.strokePoints.length - 1]) < MIN_POINT_DISTANCE) { - //need a minimum distance to avoid binormal NANs - return; - } - - this.strokePoints.push(localPoint); - this.strokeNormals.push(normal); - this.strokeWidths.push(STROKE_WIDTH); - Entities.editEntity(this.currentStroke, { - linePoints: this.strokePoints, - normals: this.strokeNormals, - strokeWidths: this.strokeWidths - }); - - - } - - this.newStroke = function(position) { - this.strokeBasePosition = position; - this.currentStroke = Entities.addEntity({ - position: position, - type: "PolyLine", - color: { - red: randInt(160, 250), - green: randInt(10, 20), - blue: randInt(190, 250) - }, - dimensions: { - x: 50, - y: 50, - z: 50 - }, - lifetime: 100 - }); - this.strokePoints = []; - this.strokeNormals = []; - this.strokeWidths = []; - - this.strokes.push(this.currentStroke); - } - - this.preload = function(entityId) { - this.strokes = []; - this.activated = false; - this.entityId = entityId; - this.properties = Entities.getEntityProperties(self.entityId); - this.getUserData(); - - //Only activate for the avatar who is grabbing the can! - if (this.userData.grabKey && this.userData.grabKey.activated) { - this.activated = true; - } - if (!this.userData.grabFrame) { - var data = { - relativePosition: { - x: 0, - y: 0, - z: 0 - }, - relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, 0) - } - setEntityCustomData(GRAB_FRAME_USER_DATA_KEY, this.entityId, data); - } - } - - - this.unload = function() { - Script.update.disconnect(this.update); - if(this.paintStream) { - Entities.deleteEntity(this.paintStream); - } - this.strokes.forEach(function(stroke) { - Entities.deleteEntity(stroke); - }); - } - Script.update.connect(this.update); -}); - - - -function randFloat(min, max) { - return Math.random() * (max - min) + min; -} - -function randInt(min, max) { - return Math.floor(Math.random() * (max - min)) + min; -} \ No newline at end of file diff --git a/examples/libraries/entityList.js b/examples/libraries/entityList.js index bab256dd5b..bb84ce27b4 100644 --- a/examples/libraries/entityList.js +++ b/examples/libraries/entityList.js @@ -56,7 +56,7 @@ EntityListTool = function(opts) { var selectedIDs = []; for (var i = 0; i < selectionManager.selections.length; i++) { - selectedIDs.push(selectionManager.selections[i].id); // ? + selectedIDs.push(selectionManager.selections[i].id); } var data = { @@ -77,6 +77,7 @@ EntityListTool = function(opts) { } selectionManager.setSelections(entityIDs); if (data.focus) { + cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js index f6f635c73a..fa0f36cbb1 100644 --- a/examples/libraries/utils.js +++ b/examples/libraries/utils.js @@ -179,3 +179,69 @@ pointInExtents = function(point, minPoint, maxPoint) { (point.y >= minPoint.y && point.y <= maxPoint.y) && (point.z >= minPoint.z && point.z <= maxPoint.z); } + +/** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param Number h The hue + * @param Number s The saturation + * @param Number l The lightness + * @return Array The RGB representation + */ +hslToRgb = function(hsl, hueOffset) { + var r, g, b; + if (hsl.s == 0) { + r = g = b = hsl.l; // achromatic + } else { + var hue2rgb = function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + var q = hsl.l < 0.5 ? hsl.l * (1 + hsl.s) : hsl.l + hsl.s - hsl.l * hsl.s; + var p = 2 * hsl.l - q; + r = hue2rgb(p, q, hsl.h + 1 / 3); + g = hue2rgb(p, q, hsl.h); + b = hue2rgb(p, q, hsl.h - 1 / 3); + } + + return { + red: Math.round(r * 255), + green: Math.round(g * 255), + blue: Math.round(b * 255) + }; +} + +map = function(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); +} + +orientationOf = function(vector) { + var Y_AXIS = { + x: 0, + y: 1, + z: 0 + }; + var X_AXIS = { + x: 1, + y: 0, + z: 0 + }; + + var theta = 0.0; + + var RAD_TO_DEG = 180.0 / Math.PI; + var direction, yaw, pitch; + direction = Vec3.normalize(vector); + yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS); + pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS); + return Quat.multiply(yaw, pitch); +} + diff --git a/examples/particle_explorer/dat.gui.min.js b/examples/particle_explorer/dat.gui.min.js new file mode 100644 index 0000000000..8ea141a966 --- /dev/null +++ b/examples/particle_explorer/dat.gui.min.js @@ -0,0 +1,95 @@ +/** + * dat-gui JavaScript Controller Library + * http://code.google.com/p/dat-gui + * + * Copyright 2011 Data Arts Team, Google Creative Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(f,a){a=a||document;var d=a.createElement("link");d.type="text/css";d.rel="stylesheet";d.href=f;a.getElementsByTagName("head")[0].appendChild(d)},inject:function(f,a){a=a||document;var d=document.createElement("style");d.type="text/css";d.innerHTML=f;a.getElementsByTagName("head")[0].appendChild(d)}}}(); +dat.utils.common=function(){var f=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(a[c])||(d[c]=a[c])},this);return d},defaults:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(d[c])&&(d[c]=a[c])},this);return d},compose:function(){var d=a.call(arguments);return function(){for(var e=a.call(arguments),c=d.length-1;0<=c;c--)e=[d[c].apply(this,e)];return e[0]}}, +each:function(a,e,c){if(a)if(f&&a.forEach&&a.forEach===f)a.forEach(e,c);else if(a.length===a.length+0)for(var b=0,p=a.length;bthis.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return e.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__impliedStep=this.__step=a;this.__precision=d(a);return this}});return e}(dat.controllers.Controller,dat.utils.common); +dat.controllers.NumberControllerBox=function(f,a,d){var e=function(c,b,f){function q(){var a=parseFloat(n.__input.value);d.isNaN(a)||n.setValue(a)}function l(a){var b=u-a.clientY;n.setValue(n.getValue()+b*n.__impliedStep);u=a.clientY}function r(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",r)}this.__truncationSuspended=!1;e.superclass.call(this,c,b,f);var n=this,u;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",q);a.bind(this.__input, +"blur",function(){q();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",l);a.bind(window,"mouseup",r);u=b.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,f.prototype,{updateDisplay:function(){var a=this.__input,b;if(this.__truncationSuspended)b= +this.getValue();else{b=this.getValue();var d=Math.pow(10,this.__precision);b=Math.round(b*d)/d}a.value=b;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); +dat.controllers.NumberControllerSlider=function(f,a,d,e,c){function b(a,b,c,e,d){return e+(a-b)/(c-b)*(d-e)}var p=function(c,e,d,f,u){function A(c){c.preventDefault();var e=a.getOffset(k.__background),d=a.getWidth(k.__background);k.setValue(b(c.clientX,e.left,e.left+d,k.__min,k.__max));return!1}function g(){a.unbind(window,"mousemove",A);a.unbind(window,"mouseup",g);k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())}p.superclass.call(this,c,e,{min:d,max:f,step:u});var k=this;this.__background= +document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",A);a.bind(window,"mouseup",g);A(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=f;p.useDefaultStyles=function(){d.inject(c)};e.extend(p.prototype,f.prototype,{updateDisplay:function(){var a= +(this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); +dat.controllers.FunctionController=function(f,a,d){var e=function(c,b,d){e.superclass.call(this,c,b);var f=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===d?"Fire":d;a.bind(this.__button,"click",function(a){a.preventDefault();f.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};e.superclass=f;d.extend(e.prototype,f.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.getValue().call(this.object); +this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); +dat.controllers.BooleanController=function(f,a,d){var e=function(c,b){e.superclass.call(this,c,b);var d=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){d.setValue(!d.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& +this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); +dat.color.toString=function(f){return function(a){if(1==a.a||f.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); +dat.color.interpret=function(f,a){var d,e,c=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:f},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:f},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); +return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:f},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:f}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!= +a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& +a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){e=!1; +var b=1\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n', +".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", +dat.controllers.factory=function(f,a,d,e,c,b,p){return function(q,l,r,n){var u=q[l];if(p.isArray(r)||p.isObject(r))return new f(q,l,r);if(p.isNumber(u))return p.isNumber(r)&&p.isNumber(n)?new d(q,l,r,n):new a(q,l,{min:r,max:n});if(p.isString(u))return new e(q,l);if(p.isFunction(u))return new c(q,l,"");if(p.isBoolean(u))return new b(q,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(f,a,d){var e= +function(c,b){function d(){f.setValue(f.__input.value)}e.superclass.call(this,c,b);var f=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",d);a.bind(this.__input,"change",d);a.bind(this.__input,"blur",function(){f.__onFinishChange&&f.__onFinishChange.call(f,f.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype, +f.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, +dat.controllers.ColorController=function(f,a,d,e,c){function b(a,b,d,e){a.style.background="";c.each(l,function(c){a.style.cssText+="background: "+c+"linear-gradient("+b+", "+d+" 0%, "+e+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; +a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var q=function(f,n){function u(b){v(b);a.bind(window,"mousemove",v);a.bind(window, +"mouseup",l)}function l(){a.unbind(window,"mousemove",v);a.unbind(window,"mouseup",l)}function g(){var a=e(this.value);!1!==a?(t.__color.__state=a,t.setValue(t.__color.toOriginal())):this.value=t.__color.toString()}function k(){a.unbind(window,"mousemove",w);a.unbind(window,"mouseup",k)}function v(b){b.preventDefault();var c=a.getWidth(t.__saturation_field),d=a.getOffset(t.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c;b=1-(b.clientY-d.top+document.body.scrollTop)/c;1 +b&&(b=0);1e&&(e=0);t.__color.v=b;t.__color.s=e;t.setValue(t.__color.toOriginal());return!1}function w(b){b.preventDefault();var c=a.getHeight(t.__hue_field),d=a.getOffset(t.__hue_field);b=1-(b.clientY-d.top+document.body.scrollTop)/c;1b&&(b=0);t.__color.h=360*b;t.setValue(t.__color.toOriginal());return!1}q.superclass.call(this,f,n);this.__color=new d(this.getValue());this.__temp=new d(0);var t=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); +this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input"); +this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(t.__selector,"drag")})});var y=document.createElement("div");c.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});c.extend(this.__field_knob.style, +{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});c.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});c.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});c.extend(y.style,{width:"100%",height:"100%", +background:"none"});b(y,"top","rgba(0,0,0,0)","#000");c.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);c.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",u);a.bind(this.__field_knob,"mousedown",u);a.bind(this.__hue_field,"mousedown",function(b){w(b);a.bind(window, +"mousemove",w);a.bind(window,"mouseup",k)});this.__saturation_field.appendChild(y);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};q.superclass=f;c.extend(q.prototype,f.prototype,{updateDisplay:function(){var a=e(this.getValue());if(!1!==a){var f=!1; +c.each(d.COMPONENTS,function(b){if(!c.isUndefined(a[b])&&!c.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return f=!0,{}},this);f&&c.extend(this.__color.__state,a)}c.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var l=.5>this.__color.v||.5a&&(a+=1);return{h:360*a,s:c/b,v:b/255}},rgb_to_hex:function(a,d,e){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,d);return a=this.hex_with_component(a,0,e)},component_from_hex:function(a,d){return a>>8*d&255},hex_with_component:function(a,d,e){return e<<(f=8*d)|a&~(255< + + + + + + + + + +
    + +
    +
    +
    + + + \ No newline at end of file diff --git a/examples/particle_explorer/main.js b/examples/particle_explorer/main.js new file mode 100644 index 0000000000..4be8168407 --- /dev/null +++ b/examples/particle_explorer/main.js @@ -0,0 +1,513 @@ +// +// main.js +// +// Created by James B. Pollack @imgntn on 9/26/2015 +// Copyright 2015 High Fidelity, Inc. +// Web app side of the App - contains GUI. +// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/*global window, alert, EventBridge, dat, convertBinaryToBoolean, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/ + +var Settings = function() { + this.exportSettings = function() { + //copyExportSettingsToClipboard(); + showPreselectedPrompt(); + }; + this.importSettings = function() { + importSettings(); + }; +}; + +//2-way bindings-aren't quite ready yet. see bottom of file. +var AUTO_UPDATE = false; +var UPDATE_ALL_FREQUENCY = 100; + +var controllers = []; +var colorControllers = []; +var folders = []; +var gui = null; +var settings = new Settings(); +var updateInterval; + +var currentInputField; +var storedController; +var keysToIgnore = [ + 'importSettings', + 'exportSettings', + 'script', + 'visible', + 'locked', + 'userData', + 'position', + 'dimensions', + 'rotation', + 'id', + 'description', + 'type', + 'created', + 'age', + 'ageAsText', + 'boundingBox', + 'naturalDimensions', + 'naturalPosition', + 'velocity', + 'gravity', + 'acceleration', + 'damping', + 'restitution', + 'friction', + 'density', + 'lifetime', + 'scriptTimestamp', + 'registrationPoint', + 'angularVelocity', + 'angularDamping', + 'ignoreForCollisions', + 'collisionsWillMove', + 'href', + 'actionData', + 'marketplaceID', + 'collisionSoundURL', + 'shapeType', + 'animationSettings', + 'animationFrameIndex', + 'animationIsPlaying', + 'animationFPS', + 'sittingPoints', + 'originalTextures' +]; + +var individualKeys = []; +var vec3Keys = []; +var quatKeys = []; +var colorKeys = []; + +window.onload = function() { + if (typeof EventBridge !== 'undefined') { + + var stringifiedData = JSON.stringify({ + messageType: 'page_loaded' + }); + + EventBridge.emitWebEvent( + stringifiedData + ); + + listenForSettingsUpdates(); + window.onresize = setGUIWidthToWindowWidth; + } else { + console.log('No event bridge, probably not in interface.'); + } +}; + +function loadGUI() { + //whether or not to autoplace + gui = new dat.GUI({ + autoPlace: false + }); + + //if not autoplacing, put gui in a custom container + if (gui.autoPlace === false) { + var customContainer = document.getElementById('my-gui-container'); + customContainer.appendChild(gui.domElement); + } + + // presets for the GUI itself. a little confusing and import/export is mostly what we want to do at the moment. + // gui.remember(settings); + + var keys = _.keys(settings); + + _.each(keys, function(key) { + var shouldIgnore = _.contains(keysToIgnore, key); + + if (shouldIgnore) { + return; + } + + var subKeys = _.keys(settings[key]); + var hasX = _.contains(subKeys, 'x'); + var hasY = _.contains(subKeys, 'y'); + var hasZ = _.contains(subKeys, 'z'); + var hasW = _.contains(subKeys, 'w'); + var hasRed = _.contains(subKeys, 'red'); + var hasGreen = _.contains(subKeys, 'green'); + var hasBlue = _.contains(subKeys, 'blue'); + + if ((hasX && hasY && hasZ) && hasW === false) { + vec3Keys.push(key); + } else if (hasX && hasY && hasZ && hasW) { + quatKeys.push(key); + } else if (hasRed || hasGreen || hasBlue) { + colorKeys.push(key); + + } else { + individualKeys.push(key); + } + + }); + + //alphabetize our keys + individualKeys.sort(); + vec3Keys.sort(); + quatKeys.sort(); + colorKeys.sort(); + + //add to gui in the order they should appear + gui.add(settings, 'importSettings'); + gui.add(settings, 'exportSettings'); + addIndividualKeys(); + addFolders(); + + //set the gui width to match the web window width + gui.width = window.innerWidth; + + //2-way binding stuff + // if (AUTO_UPDATE) { + // setInterval(manuallyUpdateDisplay, UPDATE_ALL_FREQUENCY); + // registerDOMElementsForListenerBlocking(); + // } + +} + +function addIndividualKeys() { + _.each(individualKeys, function(key) { + //temporary patch for not crashing when this goes below 0 + var controller; + + if (key.indexOf('emitRate') > -1) { + controller = gui.add(settings, key).min(0); + } else { + controller = gui.add(settings, key); + + } + + //2-way - need to fix not being able to input exact values if constantly listening + //controller.listen(); + + //keep track of our controller + controllers.push(controller); + + //hook into change events for this gui controller + controller.onChange(function(value) { + // Fires on every change, drag, keypress, etc. + writeDataToInterface(this.property, value); + }); + + }); +} + +function addFolders() { + _.each(colorKeys, function(key) { + createColorPicker(key); + }); + _.each(vec3Keys, function(key) { + createVec3Folder(key); + }); + _.each(quatKeys, function(key) { + createQuatFolder(key); + }); +} + +function createColorPicker(key) { + var colorObject = settings[key]; + var colorArray = convertColorObjectToArray(colorObject); + settings[key] = colorArray; + var controller = gui.addColor(settings, key); + controller.onChange(function(value) { + var obj = {}; + obj[key] = convertColorArrayToObject(value); + writeVec3ToInterface(obj); + }); + + return; +} + +function createVec3Folder(category) { + var folder = gui.addFolder(category); + + folder.add(settings[category], 'x').step(0.1).onChange(function(value) { + // Fires when a controller loses focus. + var obj = {}; + obj[category] = {}; + obj[category][this.property] = value; + obj[category].y = settings[category].y; + obj[category].z = settings[category].z; + writeVec3ToInterface(obj); + }); + + folder.add(settings[category], 'y').step(0.1).onChange(function(value) { + // Fires when a controller loses focus. + var obj = {}; + obj[category] = {}; + obj[category].x = settings[category].x; + obj[category][this.property] = value; + obj[category].z = settings[category].z; + writeVec3ToInterface(obj); + }); + + folder.add(settings[category], 'z').step(0.1).onChange(function(value) { + // Fires when a controller loses focus. + var obj = {}; + obj[category] = {}; + obj[category].y = settings[category].y; + obj[category].x = settings[category].x; + obj[category][this.property] = value; + writeVec3ToInterface(obj); + }); + + folders.push(folder); + folder.open(); +} + +function createQuatFolder(category) { + var folder = gui.addFolder(category); + + folder.add(settings[category], 'x').step(0.1).onChange(function(value) { + // Fires when a controller loses focus. + var obj = {}; + obj[category] = {}; + obj[category][this.property] = value; + obj[category].y = settings[category].y; + obj[category].z = settings[category].z; + obj[category].w = settings[category].w; + writeVec3ToInterface(obj); + }); + + folder.add(settings[category], 'y').step(0.1).onChange(function(value) { + // Fires when a controller loses focus. + var obj = {}; + obj[category] = {}; + obj[category].x = settings[category].x; + obj[category][this.property] = value; + obj[category].z = settings[category].z; + obj[category].w = settings[category].w; + writeVec3ToInterface(obj); + }); + + folder.add(settings[category], 'z').step(0.1).onChange(function(value) { + // Fires when a controller loses focus. + var obj = {}; + obj[category] = {}; + obj[category].x = settings[category].x; + obj[category].y = settings[category].y; + obj[category][this.property] = value; + obj[category].w = settings[category].w; + writeVec3ToInterface(obj); + }); + + folder.add(settings[category], 'w').step(0.1).onChange(function(value) { + // Fires when a controller loses focus. + var obj = {}; + obj[category] = {}; + obj[category].x = settings[category].x; + obj[category].y = settings[category].y; + obj[category].z = settings[category].z; + obj[category][this.property] = value; + writeVec3ToInterface(obj); + }); + + folders.push(folder); + folder.open(); +} + +function convertColorObjectToArray(colorObject) { + var colorArray = []; + + _.each(colorObject, function(singleColor) { + colorArray.push(singleColor); + }); + + return colorArray; +} + +function convertColorArrayToObject(colorArray) { + var colorObject = { + red: colorArray[0], + green: colorArray[1], + blue: colorArray[2] + }; + + return colorObject; +} + +function writeDataToInterface(property, value) { + var data = {}; + data[property] = value; + + var sendData = { + messageType: "settings_update", + updatedSettings: data + }; + + var stringifiedData = JSON.stringify(sendData); + + EventBridge.emitWebEvent(stringifiedData); +} + +function writeVec3ToInterface(obj) { + var sendData = { + messageType: "settings_update", + updatedSettings: obj + }; + + var stringifiedData = JSON.stringify(sendData); + + EventBridge.emitWebEvent(stringifiedData); +} + +function listenForSettingsUpdates() { + EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); + + //2-way + // if (data.messageType === 'object_update') { + // _.each(data.objectSettings, function(value, key) { + // settings[key] = value; + // }); + // } + + if (data.messageType === 'initial_settings') { + _.each(data.initialSettings, function(value, key) { + settings[key] = {}; + settings[key] = value; + }); + + loadGUI(); + } + }); +} + +function manuallyUpdateDisplay() { + // Iterate over all controllers + // this is expensive, write a method for indiviudal controllers and use it when the value is different than a cached value, perhaps. + var i; + for (i in gui.__controllers) { + gui.__controllers[i].updateDisplay(); + } +} + +function setGUIWidthToWindowWidth() { + if (gui !== null) { + gui.width = window.innerWidth; + } +} + +function handleInputKeyPress(e) { + if (e.keyCode === 13) { + importSettings(); + } + return false; +} + +function importSettings() { + var importInput = document.getElementById('importer-input'); + + try { + var importedSettings = JSON.parse(importInput.value); + + var keys = _.keys(importedSettings); + + _.each(keys, function(key) { + var shouldIgnore = _.contains(keysToIgnore, key); + + if (shouldIgnore) { + return; + } + + settings[key] = importedSettings[key]; + }); + + writeVec3ToInterface(settings); + + manuallyUpdateDisplay(); + } catch (e) { + alert('Not properly formatted JSON'); + } +} + +function prepareSettingsForExport() { + var keys = _.keys(settings); + + var exportSettings = {}; + + _.each(keys, function(key) { + var shouldIgnore = _.contains(keysToIgnore, key); + + if (shouldIgnore) { + return; + } + + if (key.indexOf('color') > -1) { + var colorObject = convertColorArrayToObject(settings[key]); + settings[key] = colorObject; + } + + exportSettings[key] = settings[key]; + }); + + return JSON.stringify(exportSettings); +} + +function showPreselectedPrompt() { + window.prompt("Ctrl-C to copy, then Enter.", prepareSettingsForExport()); +} + +function removeContainerDomElement() { + var elem = document.getElementById("my-gui-container"); + elem.parentNode.removeChild(elem); +} + +function removeListenerFromGUI(key) { + _.each(gui.__listening, function(controller, index) { + if (controller.property === key) { + storedController = controller; + gui.__listening.splice(index, 1); + } + }); +} + +//the section below is to try to work at achieving two way bindings; +function addListenersBackToGUI() { + gui.__listening.push(storedController); + storedController = null; +} + +function registerDOMElementsForListenerBlocking() { + _.each(gui.__controllers, function(controller) { + var input = controller.domElement.childNodes[0]; + input.addEventListener('focus', function() { + console.log('INPUT ELEMENT GOT FOCUS!' + controller.property); + removeListenerFromGUI(controller.property); + }); + }); + + _.each(gui.__controllers, function(controller) { + var input = controller.domElement.childNodes[0]; + input.addEventListener('blur', function() { + console.log('INPUT ELEMENT GOT BLUR!' + controller.property); + addListenersBackToGUI(); + }); + }); + + // also listen to inputs inside of folders + _.each(gui.__folders, function(folder) { + _.each(folder.__controllers, function(controller) { + var input = controller.__input; + input.addEventListener('focus', function() { + console.log('FOLDER ELEMENT GOT FOCUS!' + controller.property); + }); + }); + }); +} + +///utility method for converting weird collisionWillMove type propertyies from binary to new Boolean() +// +// function convertBinaryToBoolean(value) { +// if (value === 0) { +// return false; +// } +// return true; +// } \ No newline at end of file diff --git a/examples/particle_explorer/particleExplorer.js b/examples/particle_explorer/particleExplorer.js new file mode 100644 index 0000000000..6f03eb3c61 --- /dev/null +++ b/examples/particle_explorer/particleExplorer.js @@ -0,0 +1,226 @@ +// +// particleExplorer.js +// +// Created by James B. Pollack @imgntn on 9/26/2015 +// includes setup from @ctrlaltdavid's particlesTest.js +// Copyright 2015 High Fidelity, Inc. +// +// Interface side of the App. +// Quickly edit the aesthetics of a particle system. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// next version: 2 way bindings, integrate with edit.js +// +/*global print, WebWindow, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ + +var box, + sphere, + sphereDimensions = { + x: 0.4, + y: 0.8, + z: 0.2 + }, + pointDimensions = { + x: 0.0, + y: 0.0, + z: 0.0 + }, + sphereOrientation = Quat.fromPitchYawRollDegrees(-60.0, 30.0, 0.0), + verticalOrientation = Quat.fromPitchYawRollDegrees(-90.0, 0.0, 0.0), + particles, + particleExample = -1, + PARTICLE_RADIUS = 0.04, + SLOW_EMIT_RATE = 2.0, + HALF_EMIT_RATE = 50.0, + FAST_EMIT_RATE = 100.0, + SLOW_EMIT_SPEED = 0.025, + FAST_EMIT_SPEED = 1.0, + GRAVITY_EMIT_ACCELERATON = { + x: 0.0, + y: -0.3, + z: 0.0 + }, + ZERO_EMIT_ACCELERATON = { + x: 0.0, + y: 0.0, + z: 0.0 + }, + PI = 3.141593, + DEG_TO_RAD = PI / 180.0, + NUM_PARTICLE_EXAMPLES = 18; + +var particleProperties; + +function setUp() { + var boxPoint, + spawnPoint, + animation = { + fps: 30, + frameIndex: 0, + running: true, + firstFrame: 0, + lastFrame: 30, + loop: true + }; + + boxPoint = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation()))); + boxPoint = Vec3.sum(boxPoint, { + x: 0.0, + y: -0.5, + z: 0.0 + }); + spawnPoint = Vec3.sum(boxPoint, { + x: 0.0, + y: 1.0, + z: 0.0 + }); + + box = Entities.addEntity({ + type: "Box", + name: "ParticlesTest Box", + position: boxPoint, + rotation: verticalOrientation, + dimensions: { + x: 0.3, + y: 0.3, + z: 0.3 + }, + color: { + red: 128, + green: 128, + blue: 128 + }, + lifetime: 3600, // 1 hour; just in case + visible: true + }); + + // Same size and orientation as emitter when ellipsoid. + sphere = Entities.addEntity({ + type: "Sphere", + name: "ParticlesTest Sphere", + position: boxPoint, + rotation: sphereOrientation, + dimensions: sphereDimensions, + color: { + red: 128, + green: 128, + blue: 128 + }, + lifetime: 3600, // 1 hour; just in case + visible: false + }); + + // 1.0m above the box or ellipsoid. + particles = Entities.addEntity({ + type: "ParticleEffect", + name: "ParticlesTest Emitter", + position: spawnPoint, + emitOrientation: verticalOrientation, + particleRadius: PARTICLE_RADIUS, + radiusSpread: 0.0, + emitRate: SLOW_EMIT_RATE, + emitSpeed: FAST_EMIT_SPEED, + speedSpread: 0.0, + emitAcceleration: GRAVITY_EMIT_ACCELERATON, + accelerationSpread: { + x: 0.0, + y: 0.0, + z: 0.0 + }, + textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", + color: { + red: 255, + green: 255, + blue: 255 + }, + lifespan: 5.0, + visible: false, + locked: false, + animationSettings: animation, + animationIsPlaying: true, + lifetime: 3600 // 1 hour; just in case + }); + +} + +SettingsWindow = function() { + var _this = this; + + this.webWindow = null; + + this.init = function() { + Script.update.connect(waitForObjectAuthorization); + _this.webWindow = new WebWindow('Particle Explorer', Script.resolvePath('index.html'), 400, 600, false); + _this.webWindow.eventBridge.webEventReceived.connect(_this.onWebEventReceived); + }; + + this.sendData = function(data) { + _this.webWindow.eventBridge.emitScriptEvent(JSON.stringify(data)); + }; + + this.onWebEventReceived = function(data) { + var _data = JSON.parse(data); + if (_data.messageType === 'page_loaded') { + _this.webWindow.setVisible(true); + _this.pageLoaded = true; + sendInitialSettings(particleProperties); + } + if (_data.messageType === 'settings_update') { + editEntity(_data.updatedSettings); + return; + } + + }; +}; + +function waitForObjectAuthorization() { + var properties = Entities.getEntityProperties(particles, "isKnownID"); + var isKnownID = properties.isKnownID; + if (isKnownID === false || SettingsWindow.pageLoaded === false) { + return; + } + var currentProperties = Entities.getEntityProperties(particles); + particleProperties = currentProperties; + Script.update.connect(sendObjectUpdates); + Script.update.disconnect(waitForObjectAuthorization); +} + +function sendObjectUpdates() { + var currentProperties = Entities.getEntityProperties(particles); + sendUpdatedObject(currentProperties); +} + +function sendInitialSettings(properties) { + var settings = { + messageType: 'initial_settings', + initialSettings: properties + }; + + settingsWindow.sendData(settings); +} + +function sendUpdatedObject(properties) { + var settings = { + messageType: 'object_update', + objectSettings: properties + }; + settingsWindow.sendData(settings); +} + +function editEntity(properties) { + Entities.editEntity(particles, properties); +} + +function tearDown() { + Entities.deleteEntity(particles); + Entities.deleteEntity(box); + Entities.deleteEntity(sphere); + Script.update.disconnect(sendObjectUpdates); +} + +var settingsWindow = new SettingsWindow(); +settingsWindow.init(); +setUp(); +Script.scriptEnding.connect(tearDown); \ No newline at end of file diff --git a/examples/particle_explorer/underscore-min.js b/examples/particle_explorer/underscore-min.js new file mode 100644 index 0000000000..f01025b7bc --- /dev/null +++ b/examples/particle_explorer/underscore-min.js @@ -0,0 +1,6 @@ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. +(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); +//# sourceMappingURL=underscore-min.map \ No newline at end of file diff --git a/examples/sprayPaintSpawner.js b/examples/sprayPaintSpawner.js deleted file mode 100644 index 3b9cee6ef4..0000000000 --- a/examples/sprayPaintSpawner.js +++ /dev/null @@ -1,41 +0,0 @@ -// sprayPaintSpawner.js -// -// Created by Eric Levin on 9/3/15 -// Copyright 2015 High Fidelity, Inc. -// -// This is script spwans a spreay paint can model with the sprayPaintCan.js entity script attached -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -//Just temporarily using my own bucket here so others can test the entity. Once PR is tested and merged, then the entity script will appear in its proper place in S3, and I wil switch it -// var scriptURL = "https://hifi-public.s3.amazonaws.com/eric/scripts/sprayPaintCan.js?=v6 "; -var scriptURL = Script.resolvePath("entityScripts/sprayPaintCan.js?v2"); -var modelURL = "https://hifi-public.s3.amazonaws.com/eric/models/paintcan.fbx"; - -var sprayCan = Entities.addEntity({ - type: "Model", - name: "spraycan", - modelURL: modelURL, - position: {x: 549.12, y:495.55, z:503.77}, - rotation: {x: 0, y: 0, z: 0, w: 1}, - dimensions: { - x: 0.07, - y: 0.17, - z: 0.07 - }, - collisionsWillMove: true, - shapeType: 'box', - script: scriptURL, - gravity: {x: 0, y: -0.5, z: 0}, - velocity: {x: 0, y: -1, z: 0} -}); - -function cleanup() { - - // Uncomment the below line to delete sprayCan on script reload- for faster iteration during development - // Entities.deleteEntity(sprayCan); -} - -Script.scriptEnding.connect(cleanup); - diff --git a/examples/toys/cat.js b/examples/toys/cat.js new file mode 100644 index 0000000000..103752bb1b --- /dev/null +++ b/examples/toys/cat.js @@ -0,0 +1,52 @@ +// +// cat.js +// examples/toybox/entityScripts +// +// Created by Eric Levin on 9/21/15. +// Copyright 2015 High Fidelity, Inc. +// + +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/*global Cat, print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ + + +(function() { + + var _this; + Cat = function() { + _this = this; + this.meowSound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Animals/cat_meow.wav"); + }; + + Cat.prototype = { + isMeowing: false, + injector: null, + startTouch: function() { + if (this.isMeowing !== true) { + this.meow(); + this.isMeowing = true; + } + + }, + meow: function() { + this.injector = Audio.playSound(this.meowSound, { + position: this.position, + volume: 0.1 + }); + Script.setTimeout(function() { + _this.isMeowing = false; + }, this.soundClipTime); + }, + + preload: function(entityID) { + this.entityID = entityID; + this.position = Entities.getEntityProperties(this.entityID, "position").position; + this.soundClipTime = 700; + }, + + }; + + // entity scripts always need to return a newly constructed object of our type + return new Cat(); +}); \ No newline at end of file diff --git a/examples/toys/doll/doll.js b/examples/toys/doll/doll.js index f97b6de378..19a7dacda9 100644 --- a/examples/toys/doll/doll.js +++ b/examples/toys/doll/doll.js @@ -15,21 +15,14 @@ (function() { Script.include("../../utilities.js"); Script.include("../../libraries/utils.js"); - + var _this; // this is the "constructor" for the entity as a JS object we don't do much here var Doll = function() { + _this = this; this.screamSounds = [SoundCache.getSound("https://hifi-public.s3.amazonaws.com/sounds/KenDoll_1%2303.wav")]; }; Doll.prototype = { - startAnimationSetting: JSON.stringify({ - running: true, - fps: 30, - startFrame: 0, - lastFrame: 128, - startAutomatically: true - }), - stopAnimationSetting: JSON.stringify({running: false}), audioInjector: null, isGrabbed: false, setLeftHand: function() { @@ -41,26 +34,27 @@ }, startNearGrab: function() { - if (this.isGrabbed === false) { + Entities.editEntity(this.entityID, { + animationURL: "https://hifi-public.s3.amazonaws.com/models/Bboys/zombie_scream.fbx", + animationFrameIndex: 0 + }); - Entities.editEntity(this.entityID, { - animationURL: "https://hifi-public.s3.amazonaws.com/models/Bboys/zombie_scream.fbx", - animationSettings: this.startAnimationSetting - }); + Entities.editEntity(_this.entityID, { + animationIsPlaying: true + }); - var position = Entities.getEntityProperties(this.entityID, "position").position; - this.audioInjector = Audio.playSound(this.screamSounds[randInt(0, this.screamSounds.length)], { - position: position, - volume: 0.1 - }); + var position = Entities.getEntityProperties(this.entityID, "position").position; + this.audioInjector = Audio.playSound(this.screamSounds[randInt(0, this.screamSounds.length)], { + position: position, + volume: 0.1 + }); - this.isGrabbed = true; - this.initialHand = this.hand; - } + this.isGrabbed = true; + this.initialHand = this.hand; }, continueNearGrab: function() { - var props = Entities.getEntityProperties(this.entityID, "position"); + var props = Entities.getEntityProperties(this.entityID, ["position"]); var audioOptions = { position: props.position }; @@ -69,24 +63,23 @@ releaseGrab: function() { if (this.isGrabbed === true && this.hand === this.initialHand) { - this.audioInjector.stop(); + Entities.editEntity(this.entityID, { + animationFrameIndex: 0 + }); Entities.editEntity(this.entityID, { - animationSettings: this.stopAnimationSetting, - animationURL: "http://hifi-public.s3.amazonaws.com/models/Bboys/bboy2/bboy2.fbx", + animationURL: "http://hifi-public.s3.amazonaws.com/models/Bboys/bboy2/bboy2.fbx" }); + this.isGrabbed = false; } }, preload: function(entityID) { - // preload() will be called when the entity has become visible (or known) to the interface - // it gives us a chance to set our local JavaScript object up. In this case it means: - // * remembering our entityID, so we can access it in cases where we're called without an entityID this.entityID = entityID; }, }; // entity scripts always need to return a newly constructed object of our type return new Doll(); -}); \ No newline at end of file +}); diff --git a/examples/toys/lightSwitchGarage.js b/examples/toys/lightSwitchGarage.js new file mode 100644 index 0000000000..19d33117df --- /dev/null +++ b/examples/toys/lightSwitchGarage.js @@ -0,0 +1,202 @@ +// +// lightSwitchGarage.js.js +// examples/entityScripts +// +// Created by Eric Levin on 9/21/15. +// Copyright 2015 High Fidelity, Inc. +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + var _this; + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + LightSwitchGarage = function() { + _this = this; + + this.lightStateKey = "lightStateKey"; + this.resetKey = "resetMe"; + + this.switchSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_2.wav"); + + }; + + LightSwitchGarage.prototype = { + + clickReleaseOnEntity: function(entityID, mouseEvent) { + if (!mouseEvent.isLeftButton) { + return; + } + this.toggleLights(); + }, + + startNearGrabNonColliding: function() { + this.toggleLights(); + }, + + toggleLights: function() { + var defaultLightData = { + on: false + }; + var lightState = getEntityCustomData(this.lightStateKey, this.entityID, defaultLightData); + if (lightState.on === true) { + this.clearLights(); + } else if (lightState.on === false) { + this.createLights(); + } + + this.flipLights(); + + Audio.playSound(this.switchSound, { + volume: 0.5, + position: this.position + }); + + }, + + clearLights: function() { + var entities = Entities.findEntities(MyAvatar.position, 100); + var self = this; + entities.forEach(function(entity) { + var resetData = getEntityCustomData(self.resetKey, entity, {}) + if (resetData.resetMe === true && resetData.lightType === "Sconce Light Garage") { + Entities.deleteEntity(entity); + } + }); + + setEntityCustomData(this.lightStateKey, this.entityID, { + on: false + }); + }, + + createLights: function() { + + var sconceLight3 = Entities.addEntity({ + type: "Light", + position: { + x: 545.49468994140625, + y: 496.24026489257812, + z: 500.63516235351562 + }, + + name: "Sconce 3 Light", + dimensions: { + x: 2.545, + y: 2.545, + z: 2.545 + }, + cutoff: 90, + color: { + red: 217, + green: 146, + blue: 24 + } + }); + + setEntityCustomData(this.resetKey, sconceLight3, { + resetMe: true, + lightType: "Sconce Light Garage" + }); + + var sconceLight4 = Entities.addEntity({ + type: "Light", + position: { + x: 550.90399169921875, + y: 496.24026489257812, + z: 507.90237426757812 + }, + name: "Sconce 4 Light", + dimensions: { + x: 2.545, + y: 2.545, + z: 2.545 + }, + cutoff: 90, + color: { + red: 217, + green: 146, + blue: 24 + } + }); + + setEntityCustomData(this.resetKey, sconceLight4, { + resetMe: true, + lightType: "Sconce Light Garage" + }); + + var sconceLight5 = Entities.addEntity({ + type: "Light", + position: { + x: 548.407958984375, + y: 496.24026489257812, + z: 509.5504150390625 + }, + name: "Sconce 5 Light", + dimensions: { + x: 2.545, + y: 2.545, + z: 2.545 + }, + cutoff: 90, + color: { + red: 217, + green: 146, + blue: 24 + } + }); + + setEntityCustomData(this.resetKey, sconceLight5, { + resetMe: true, + lightType: "Sconce Light Garage" + }); + + setEntityCustomData(this.lightStateKey, this.entityID, { + on: true + }); + }, + + flipLights: function() { + // flip model to give illusion of light switch being flicked + var rotation = Entities.getEntityProperties(this.entityID, "rotation").rotation; + var axis = { + x: 0, + y: 1, + z: 0 + }; + var dQ = Quat.angleAxis(180, axis); + rotation = Quat.multiply(rotation, dQ); + + + Entities.editEntity(this.entityID, { + rotation: rotation + }); + + }, + + preload: function(entityID) { + this.entityID = entityID; + + //The light switch is static, so just cache its position once + this.position = Entities.getEntityProperties(this.entityID, "position").position; + var defaultLightData = { + on: false + }; + var lightState = getEntityCustomData(this.lightStateKey, this.entityID, defaultLightData); + + //If light is off, then we create two new lights- at the position of the sconces + if (lightState.on === false) { + this.createLights(); + this.flipLights(); + } + //If lights are on, do nothing! + }, + }; + + // entity scripts always need to return a newly constructed object of our type + return new LightSwitchGarage(); +}) diff --git a/examples/toys/lightSwitchHall.js b/examples/toys/lightSwitchHall.js new file mode 100644 index 0000000000..e1093311f9 --- /dev/null +++ b/examples/toys/lightSwitchHall.js @@ -0,0 +1,179 @@ +// +// lightSwitchHall.js +// examples/entityScripts +// +// Created by Eric Levin on 9/21/15. +// Copyright 2015 High Fidelity, Inc. +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + var _this; + + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + LightSwitchHall = function() { + _this = this; + + this.lightStateKey = "lightStateKey"; + this.resetKey = "resetMe"; + + this.switchSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_2.wav"); + }; + + LightSwitchHall.prototype = { + + clickReleaseOnEntity: function(entityId, mouseEvent) { + if (!mouseEvent.isLeftButton) { + return; + } + this.toggleLights(); + }, + + startNearGrabNonColliding: function() { + this.toggleLights(); + }, + + toggleLights: function() { + var defaultLightData = { + on: false + }; + var lightState = getEntityCustomData(this.lightStateKey, this.entityID, defaultLightData); + if (lightState.on === true) { + this.clearLights(); + } else if (lightState.on === false) { + this.createLights(); + } + + // flip model to give illusion of light switch being flicked + this.flipLights(); + + Audio.playSound(this.switchSound, { + volume: 0.5, + position: this.position + }); + + }, + + clearLights: function() { + var entities = Entities.findEntities(MyAvatar.position, 100); + var self = this; + entities.forEach(function(entity) { + var resetData = getEntityCustomData(self.resetKey, entity, {}) + if (resetData.resetMe === true && resetData.lightType === "Sconce Light Hall") { + Entities.deleteEntity(entity); + } + }); + + setEntityCustomData(this.lightStateKey, this.entityID, { + on: false + }); + }, + + createLights: function() { + var sconceLight1 = Entities.addEntity({ + type: "Light", + position: { + x: 543.75, + y: 496.24, + z: 511.13 + }, + name: "Sconce 1 Light", + dimensions: { + x: 2.545, + y: 2.545, + z: 2.545 + }, + cutoff: 90, + color: { + red: 217, + green: 146, + blue: 24 + } + }); + + setEntityCustomData(this.resetKey, sconceLight1, { + resetMe: true, + lightType: "Sconce Light Hall" + }); + + var sconceLight2 = Entities.addEntity({ + type: "Light", + position: { + x: 540.1, + y: 496.24, + z: 505.57 + }, + name: "Sconce 2 Light", + dimensions: { + x: 2.545, + y: 2.545, + z: 2.545 + }, + cutoff: 90, + color: { + red: 217, + green: 146, + blue: 24 + } + }); + + setEntityCustomData(this.resetKey, sconceLight2, { + resetMe: true, + lightType: "Sconce Light Hall" + }); + + setEntityCustomData(this.lightStateKey, this.entityID, { + on: true + }); + + }, + + flipLights: function() { + // flip model to give illusion of light switch being flicked + var rotation = Entities.getEntityProperties(this.entityID, "rotation").rotation; + var axis = { + x: 0, + y: 1, + z: 0 + }; + var dQ = Quat.angleAxis(180, axis); + rotation = Quat.multiply(rotation, dQ); + + + Entities.editEntity(this.entityID, { + rotation: rotation + }); + + }, + + // preload() will be called when the entity has become visible (or known) to the interface + // it gives us a chance to set our local JavaScript object up. In this case it means: + preload: function(entityID) { + this.entityID = entityID; + + //The light switch is static, so just cache its position once + this.position = Entities.getEntityProperties(this.entityID, "position").position; + var defaultLightData = { + on: false + }; + var lightState = getEntityCustomData(this.lightStateKey, this.entityID, defaultLightData); + + //If light is off, then we create two new lights- at the position of the sconces + if (lightState.on === false) { + this.createLights(); + this.flipLights(); + + } + //If lights are on, do nothing! + }, + }; + + // entity scripts always need to return a newly constructed object of our type + return new LightSwitchHall(); +}) diff --git a/examples/toys/sprayPaintCan.js b/examples/toys/sprayPaintCan.js new file mode 100644 index 0000000000..e0aeb19995 --- /dev/null +++ b/examples/toys/sprayPaintCan.js @@ -0,0 +1,162 @@ +// +// sprayPaintCan.js +// examples/entityScripts +// +// Created by Eric Levin on 9/21/15. +// Copyright 2015 High Fidelity, Inc. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +(function() { + // Script.include("../libraries/utils.js"); + //Need absolute path for now, for testing before PR merge and s3 cloning. Will change post-merge + + Script.include("../libraries/utils.js"); + + this.spraySound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/sprayPaintSound.wav"); + + var TIP_OFFSET_Z = 0.02; + var TIP_OFFSET_Y = 0.08; + + var ZERO_VEC = { + x: 0, + y: 0, + z: 0 + }; + + // if the trigger value goes below this while held, the can will stop spraying. if it goes above, it will spray + var DISABLE_SPRAY_THRESHOLD = 0.5; + + var MAX_POINTS_PER_LINE = 40; + var MIN_POINT_DISTANCE = 0.01; + var STROKE_WIDTH = 0.02; + + this.setRightHand = function() { + this.hand = 'RIGHT'; + } + + this.setLeftHand = function() { + this.hand = 'LEFT'; + } + + this.startNearGrab = function() { + this.whichHand = this.hand; + } + + this.toggleWithTriggerPressure = function() { + var handClickString = this.whichHand + "_HAND_CLICK"; + + var handClick = Controller.findAction(handClickString); + + this.triggerValue = Controller.getActionValue(handClick); + if (this.triggerValue < DISABLE_SPRAY_THRESHOLD && this.spraying === true) { + this.spraying = false; + this.disableStream(); + } else if (this.triggerValue >= DISABLE_SPRAY_THRESHOLD && this.spraying === false) { + this.spraying = true; + this.enableStream(); + } + } + + this.enableStream = function() { + var position = Entities.getEntityProperties(this.entityId, "position").position; + var animationSettings = JSON.stringify({ + fps: 30, + loop: true, + firstFrame: 1, + lastFrame: 10000, + running: true + }); + var PI = 3.141593; + var DEG_TO_RAD = PI / 180.0; + + this.paintStream = Entities.addEntity({ + type: "ParticleEffect", + name: "streamEffect", + animationSettings: animationSettings, + position: position, + textures: "https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png", + emitSpeed: 3, + speedSpread: 0.02, + emitAcceleration: ZERO_VEC, + emitRate: 100, + particleRadius: 0.01, + radiusSpread: 0.005, + polarFinish: 0.05, + color: { + red: 170, + green: 20, + blue: 150 + }, + lifetime: 50, //probably wont be holding longer than this straight + }); + + setEntityCustomData(this.resetKey, this.paintStream, { + resetMe: true + }); + + this.sprayInjector = Audio.playSound(this.spraySound, { + position: position, + volume: this.sprayVolume, + loop: true + }); + + } + + this.releaseGrab = function() { + this.disableStream(); + } + + this.disableStream = function() { + Entities.deleteEntity(this.paintStream); + this.paintStream = null; + this.spraying = false; + this.sprayInjector.stop(); + } + + + this.continueNearGrab = function() { + + this.toggleWithTriggerPressure(); + + if (this.spraying === false) { + return; + } + + var props = Entities.getEntityProperties(this.entityId, ["position, rotation"]); + var forwardVec = Quat.getFront(Quat.multiply(props.rotation, Quat.fromPitchYawRollDegrees(0, 90, 0))); + forwardVec = Vec3.normalize(forwardVec); + var forwardQuat = orientationOf(forwardVec); + var upVec = Quat.getUp(props.rotation); + var position = Vec3.sum(props.position, Vec3.multiply(forwardVec, TIP_OFFSET_Z)); + position = Vec3.sum(position, Vec3.multiply(upVec, TIP_OFFSET_Y)) + Entities.editEntity(this.paintStream, { + position: position, + emitOrientation: forwardQuat, + }); + + this.sprayInjector.setOptions({ + position: position, + volume: this.sprayVolume + }); + } + + this.preload = function(entityId) { + this.sprayVolume = 0.1; + this.spraying = false; + this.entityId = entityId; + this.resetKey = "resetMe"; + } + + + this.unload = function() { + if (this.paintStream) { + Entities.deleteEntity(this.paintStream); + } + this.strokes.forEach(function(stroke) { + Entities.deleteEntity(stroke); + }); + } +}); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index e3118c0048..63d1445496 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -1,6 +1,8 @@ set(TARGET_NAME interface) project(${TARGET_NAME}) +add_definitions(-DGLEW_STATIC) + # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "Faceshift" "LeapMotion" "RtMidi" "RSSDK" "3DConnexionClient" "iViewHMD") foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5f127988e9..b25f3aa6a5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2764,12 +2764,19 @@ void Application::cameraMenuChanged() { } void Application::reloadResourceCaches() { + // Clear entities out of view frustum + _viewFrustum.setPosition(glm::vec3(0.0f, 0.0f, TREE_SCALE)); + _viewFrustum.setOrientation(glm::quat()); + queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); + emptyLocalCache(); - + DependencyManager::get()->refreshAll(); DependencyManager::get()->refreshAll(); DependencyManager::get()->refreshAll(); DependencyManager::get()->refreshAll(); + + DependencyManager::get()->reset(); // Force redownload of .fst models } void Application::rotationModeChanged() { diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index 4af1a26612..6145529b52 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -141,6 +141,7 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { 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.reset(gpu::Pipeline::create(program, state)); } @@ -152,6 +153,7 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { 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.reset(gpu::Pipeline::create(program, state)); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 0d1c3b8a20..faf4bffeba 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -202,11 +202,14 @@ void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("skeleton"); for (int i = 0; i < _jointData.size(); i++) { const JointData& data = _jointData.at(i); - _skeletonModel.setJointState(i, true, data.rotation); + _skeletonModel.setJointRotation(i, data.rotationSet, data.rotation, 1.0f); + _skeletonModel.setJointTranslation(i, data.translationSet, data.translation, 1.0f); } - _skeletonModel.simulate(deltaTime, _hasNewJointRotations); + + _skeletonModel.simulate(deltaTime, _hasNewJointRotations || _hasNewJointTranslations); simulateAttachments(deltaTime); _hasNewJointRotations = false; + _hasNewJointTranslations = false; } { PerformanceTimer perfTimer("head"); @@ -879,6 +882,16 @@ glm::quat Avatar::getJointRotation(int index) const { return rotation; } +glm::vec3 Avatar::getJointTranslation(int index) const { + if (QThread::currentThread() != thread()) { + return AvatarData::getJointTranslation(index); + } + glm::vec3 translation; + _skeletonModel.getJointTranslation(index, translation); + return translation; +} + + int Avatar::getJointIndex(const QString& name) const { if (QThread::currentThread() != thread()) { int result; diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 6cfbf939a2..5ff3f37ef5 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -115,6 +115,7 @@ public: virtual QVector getJointRotations() const; virtual glm::quat getJointRotation(int index) const; + virtual glm::vec3 getJointTranslation(int index) const; virtual int getJointIndex(const QString& name) const; virtual QStringList getJointNames() const; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a57740a691..9654305d70 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -240,9 +240,11 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("joints"); // copy out the skeleton joints from the model _jointData.resize(_rig->getJointStateCount()); + for (int i = 0; i < _jointData.size(); i++) { JointData& data = _jointData[i]; - _rig->getJointStateRotation(i, data.rotation); + data.rotationSet |= _rig->getJointStateRotation(i, data.rotation); + data.translationSet |= _rig->getJointStateTranslation(i, data.translation); } } @@ -1115,21 +1117,43 @@ void MyAvatar::setJointRotations(QVector jointRotations) { int numStates = glm::min(_skeletonModel.getJointStateCount(), jointRotations.size()); for (int i = 0; i < numStates; ++i) { // HACK: ATM only Recorder calls setJointRotations() so we hardcode its priority here - _skeletonModel.setJointState(i, true, jointRotations[i], RECORDER_PRIORITY); + _skeletonModel.setJointRotation(i, true, jointRotations[i], RECORDER_PRIORITY); } } -void MyAvatar::setJointData(int index, const glm::quat& rotation) { +void MyAvatar::setJointTranslations(QVector jointTranslations) { + int numStates = glm::min(_skeletonModel.getJointStateCount(), jointTranslations.size()); + for (int i = 0; i < numStates; ++i) { + // HACK: ATM only Recorder calls setJointTranslations() so we hardcode its priority here + _skeletonModel.setJointTranslation(i, true, jointTranslations[i], RECORDER_PRIORITY); + } +} + +void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) { if (QThread::currentThread() == thread()) { // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, true, rotation, SCRIPT_PRIORITY); + _rig->setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + } +} + +void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { + if (QThread::currentThread() == thread()) { + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _rig->setJointRotation(index, true, rotation, SCRIPT_PRIORITY); + } +} + +void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { + if (QThread::currentThread() == thread()) { + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _rig->setJointTranslation(index, true, translation, SCRIPT_PRIORITY); } } void MyAvatar::clearJointData(int index) { if (QThread::currentThread() == thread()) { // HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, false, glm::quat(), 0.0f); + _rig->setJointState(index, false, glm::quat(), glm::vec3(), 0.0f); _rig->clearJointAnimationPriority(index); } } @@ -1428,7 +1452,10 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { AnimPose pose = _debugDrawSkeleton->getRelativeBindPose(i); glm::quat jointRot; _rig->getJointRotationInConstrainedFrame(i, jointRot); + glm::vec3 jointTrans; + _rig->getJointTranslation(i, jointTrans); pose.rot = pose.rot * jointRot; + pose.trans = jointTrans; poses.push_back(pose); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6efec3be13..96c9f37c39 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -140,7 +140,10 @@ public: void clearLookAtTargetAvatar(); virtual void setJointRotations(QVector jointRotations); - virtual void setJointData(int index, const glm::quat& rotation); + virtual void setJointTranslations(QVector jointTranslations); + virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation); + virtual void setJointRotation(int index, const glm::quat& rotation); + virtual void setJointTranslation(int index, const glm::vec3& translation); virtual void clearJointData(int index); virtual void clearJointsData(); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 7c2cf43c07..a892447961 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -246,6 +246,12 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { Hand* hand = _owningAvatar->getHand(); hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); + // let rig compute the model offset + glm::vec3 modelOffset; + if (_rig->getModelOffset(modelOffset)) { + setOffset(modelOffset); + } + // Don't Relax toward hand positions when in animGraph mode. if (!_rig->getEnableAnimGraph()) { const float HAND_RESTORATION_RATE = 0.25f; @@ -651,6 +657,7 @@ void SkeletonModel::computeBoundingShape() { // RECOVER FROM BOUNINDG SHAPE HACK: now that we're all done, restore the default pose for (int i = 0; i < numStates; i++) { _rig->restoreJointRotation(i, 1.0f, 1.0f); + _rig->restoreJointTranslation(i, 1.0f, 1.0f); } } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 23aa884933..c432b3b9ac 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -135,23 +135,46 @@ void AnimClip::copyFromNetworkAnim() { const int frameCount = geom.animationFrames.size(); const int skeletonJointCount = _skeleton->getNumJoints(); _anim.resize(frameCount); - for (int i = 0; i < frameCount; i++) { + + const glm::vec3 offsetScale = extractScale(geom.offset); + + for (int frame = 0; frame < frameCount; frame++) { // init all joints in animation to bind pose - _anim[i].reserve(skeletonJointCount); - for (int j = 0; j < skeletonJointCount; j++) { - _anim[i].push_back(_skeleton->getRelativeBindPose(j)); + // this will give us a resonable result for bones in the skeleton but not in the animation. + _anim[frame].reserve(skeletonJointCount); + for (int skeletonJoint = 0; skeletonJoint < skeletonJointCount; skeletonJoint++) { + _anim[frame].push_back(_skeleton->getRelativeBindPose(skeletonJoint)); } - // init over all joint animations - for (int j = 0; j < animJointCount; j++) { - int k = jointMap[j]; - if (k >= 0 && k < skeletonJointCount) { - // currently FBX animations only have rotation. - _anim[i][k].rot = _skeleton->getRelativeBindPose(k).rot * geom.animationFrames[i].rotations[j]; + for (int animJoint = 0; animJoint < animJointCount; animJoint++) { + + int skeletonJoint = jointMap[animJoint]; + + // skip joints that are in the animation but not in the skeleton. + if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) { + + const glm::vec3& fbxZeroTrans = geom.animationFrames[0].translations[animJoint] * offsetScale; + const AnimPose& relBindPose = _skeleton->getRelativeBindPose(skeletonJoint); + + // used to adjust translation offsets, so large translation animatons on the reference skeleton + // will be adjusted when played on a skeleton with short limbs. + float limbLengthScale = fabs(glm::length(fbxZeroTrans)) <= 0.0001f ? 1.0f : (glm::length(relBindPose.trans) / glm::length(fbxZeroTrans)); + + AnimPose& pose = _anim[frame][skeletonJoint]; + const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame]; + + // rotation in fbxAnimationFrame is a delta from a reference skeleton bind pose. + pose.rot = relBindPose.rot * fbxAnimFrame.rotations[animJoint]; + + // translation in fbxAnimationFrame is not a delta. + // convert it into a delta by subtracting from the first frame. + const glm::vec3& fbxTrans = fbxAnimFrame.translations[animJoint] * offsetScale; + pose.trans = relBindPose.trans + limbLengthScale * (fbxTrans - fbxZeroTrans); } } } + _poses.resize(skeletonJointCount); } diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index c0adba0ad6..4285fd72cc 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -348,6 +348,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } else { _relativePoses[i].rot = underPoses[i].rot; } + _relativePoses[i].trans = underPoses[i].trans; } } diff --git a/libraries/animation/src/AnimationHandle.cpp b/libraries/animation/src/AnimationHandle.cpp index 93dd965e02..cee182afc8 100644 --- a/libraries/animation/src/AnimationHandle.cpp +++ b/libraries/animation/src/AnimationHandle.cpp @@ -173,6 +173,7 @@ void AnimationHandle::applyFrame(float frameIndex) { const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount); const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount); float frameFraction = glm::fract(frameIndex); + for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { // allow missing bones @@ -182,6 +183,15 @@ void AnimationHandle::applyFrame(float frameIndex) { frameFraction), _priority, _mix); + + // This isn't working. + // glm::vec3 floorTranslationPart = floorFrame.translations.at(i) * (1.0f - frameFraction); + // glm::vec3 ceilTranslationPart = ceilFrame.translations.at(i) * frameFraction; + // glm::vec3 floorCeilFraction = floorTranslationPart + ceilTranslationPart; + // glm::vec3 defaultTrans = _rig->getJointDefaultTranslationInConstrainedFrame(i); + // glm::vec3 mixedTranslation = floorCeilFraction * (1.0f - _mix) + defaultTrans * _mix; + // _rig->setJointTranslation(mapping, true, mixedTranslation, _priority); + } } } @@ -202,6 +212,7 @@ void AnimationHandle::restoreJoints() { int mapping = _jointMappings.at(i); if (mapping != -1) { _rig->restoreJointRotation(mapping, 1.0f, _rig->getJointAnimatinoPriority(mapping)); + _rig->restoreJointTranslation(mapping, 1.0f, _rig->getJointAnimatinoPriority(mapping)); } } } diff --git a/libraries/animation/src/AvatarRig.cpp b/libraries/animation/src/AvatarRig.cpp index 0727d0956d..4dbf5207b1 100644 --- a/libraries/animation/src/AvatarRig.cpp +++ b/libraries/animation/src/AvatarRig.cpp @@ -20,9 +20,7 @@ void AvatarRig::updateJointState(int index, glm::mat4 rootTransform) { // compute model transforms if (index == _rootJointIndex) { - // we always zero-out the translation part of an avatar's root join-transform. state.computeTransform(rootTransform); - clearJointTransformTranslation(index); } else { // guard against out-of-bounds access to _jointStates int parentIndex = state.getParentIndex(); @@ -97,3 +95,28 @@ void AvatarRig::setHandPosition(int jointIndex, shoulderRotation, priority); setJointRotationInBindFrame(jointIndex, rotation, priority); } + +void AvatarRig::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { + if (index != -1 && index < _jointStates.size()) { + JointState& state = _jointStates[index]; + if (valid) { + state.setTranslation(translation, priority); + } else { + state.restoreTranslation(1.0f, priority); + } + } +} + + +void AvatarRig::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { + if (index != -1 && index < _jointStates.size()) { + JointState& state = _jointStates[index]; + if (valid) { + state.setRotationInConstrainedFrame(rotation, priority); + state.setTranslation(translation, priority); + } else { + state.restoreRotation(1.0f, priority); + state.restoreTranslation(1.0f, priority); + } + } +} diff --git a/libraries/animation/src/AvatarRig.h b/libraries/animation/src/AvatarRig.h index f137e89939..f45c2951e9 100644 --- a/libraries/animation/src/AvatarRig.h +++ b/libraries/animation/src/AvatarRig.h @@ -24,6 +24,10 @@ class AvatarRig : public Rig { virtual void updateJointState(int index, glm::mat4 rootTransform); virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, float scale, float priority); + virtual void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); + virtual void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority); }; + + #endif // hifi_AvatarRig_h diff --git a/libraries/animation/src/EntityRig.cpp b/libraries/animation/src/EntityRig.cpp index e65407116b..8748214734 100644 --- a/libraries/animation/src/EntityRig.cpp +++ b/libraries/animation/src/EntityRig.cpp @@ -27,3 +27,17 @@ void EntityRig::updateJointState(int index, glm::mat4 rootTransform) { } } } + + +void EntityRig::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { + if (index != -1 && index < _jointStates.size()) { + JointState& state = _jointStates[index]; + if (valid) { + state.setRotationInConstrainedFrame(rotation, priority); + // state.setTranslation(translation, priority); + } else { + state.restoreRotation(1.0f, priority); + // state.restoreTranslation(1.0f, priority); + } + } +} diff --git a/libraries/animation/src/EntityRig.h b/libraries/animation/src/EntityRig.h index b36ffb9874..17486bad78 100644 --- a/libraries/animation/src/EntityRig.h +++ b/libraries/animation/src/EntityRig.h @@ -24,6 +24,7 @@ class EntityRig : public Rig { virtual void updateJointState(int index, glm::mat4 rootTransform); virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, float scale, float priority) {} + virtual void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority); }; #endif // hifi_EntityRig_h diff --git a/libraries/animation/src/JointState.cpp b/libraries/animation/src/JointState.cpp index 0746f65e84..28416b315d 100644 --- a/libraries/animation/src/JointState.cpp +++ b/libraries/animation/src/JointState.cpp @@ -34,6 +34,7 @@ void JointState::copyState(const JointState& other) { _isFree = other._isFree; _parentIndex = other._parentIndex; _defaultRotation = other._defaultRotation; + _defaultTranslation = other._defaultTranslation; _inverseDefaultRotation = other._inverseDefaultRotation; _translation = other._translation; _preRotation = other._preRotation; @@ -49,6 +50,7 @@ JointState::JointState(const FBXJoint& joint) { _parentIndex = joint.parentIndex; _translation = joint.translation; _defaultRotation = joint.rotation; + _defaultTranslation = _translation; _inverseDefaultRotation = joint.inverseDefaultRotation; _preRotation = joint.preRotation; _postRotation = joint.postRotation; @@ -70,6 +72,9 @@ glm::quat JointState::getRotation() const { } void JointState::initTransform(const glm::mat4& parentTransform) { + + _unitsScale = extractScale(parentTransform); + computeTransform(parentTransform); _positionInParentFrame = glm::inverse(extractRotation(parentTransform)) * (extractTranslation(_transform) - extractTranslation(parentTransform)); _distanceToParent = glm::length(_positionInParentFrame); @@ -79,11 +84,11 @@ void JointState::computeTransform(const glm::mat4& parentTransform, bool parentT if (!parentTransformChanged && !_transformChanged) { return; } - + glm::quat rotationInParentFrame = _preRotation * _rotationInConstrainedFrame * _postRotation; glm::mat4 transformInParentFrame = _preTransform * glm::mat4_cast(rotationInParentFrame) * _postTransform; glm::mat4 newTransform = parentTransform * glm::translate(_translation) * transformInParentFrame; - + if (newTransform != _transform) { _transform = newTransform; _transformChanged = true; @@ -106,6 +111,13 @@ void JointState::restoreRotation(float fraction, float priority) { } } +void JointState::restoreTranslation(float fraction, float priority) { + if (priority == _animationPriority || _animationPriority == 0.0f) { + _translation = _translation * (1.0f - fraction) + _defaultTranslation * fraction; + _animationPriority = 0.0f; + } +} + void JointState::setRotationInBindFrame(const glm::quat& rotation, float priority) { // rotation is from bind- to model-frame if (priority >= _animationPriority) { @@ -176,6 +188,14 @@ void JointState::setRotationInConstrainedFrame(glm::quat targetRotation, float p } } +void JointState::setTranslation(const glm::vec3& translation, float priority) { + if (priority >= _animationPriority || _animationPriority == 0.0f) { + _translation = translation / _unitsScale; + _transformChanged = true; + _animationPriority = priority; + } +} + void JointState::setRotationInConstrainedFrameInternal(const glm::quat& targetRotation) { if (_rotationInConstrainedFrame != targetRotation) { glm::quat parentRotation = computeParentRotation(); @@ -194,11 +214,15 @@ bool JointState::rotationIsDefault(const glm::quat& rotation, float tolerance) c glm::abs(rotation.w - defaultRotation.w) < tolerance; } +bool JointState::translationIsDefault(const glm::vec3& translation, float tolerance) const { + return glm::distance(_defaultTranslation * _unitsScale, translation * _unitsScale) < tolerance; +} + glm::quat JointState::getDefaultRotationInParentFrame() const { // NOTE: the result is constant and could be cached return _preRotation * _defaultRotation * _postRotation; } -const glm::vec3& JointState::getDefaultTranslationInConstrainedFrame() const { - return _translation; +glm::vec3 JointState::getDefaultTranslationInConstrainedFrame() const { + return _defaultTranslation * _unitsScale; } diff --git a/libraries/animation/src/JointState.h b/libraries/animation/src/JointState.h index eee8863e6f..5dec7138b6 100644 --- a/libraries/animation/src/JointState.h +++ b/libraries/animation/src/JointState.h @@ -68,6 +68,8 @@ public: /// \param priority priority level of this animation blend void restoreRotation(float fraction, float priority); + void restoreTranslation(float fraction, float priority); + /// \param rotation is from bind- to model-frame /// computes and sets new _rotationInConstrainedFrame /// NOTE: the JointState's model-frame transform/rotation are NOT updated! @@ -78,13 +80,17 @@ public: /// NOTE: the JointState's model-frame transform/rotation are NOT updated! void setRotationInModelFrame(const glm::quat& rotationInModelFrame, float priority); + void setTranslation(const glm::vec3& translation, float priority); + void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, float mix = 1.0f); + const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; } bool rotationIsDefault(const glm::quat& rotation, float tolerance = EPSILON) const; + bool translationIsDefault(const glm::vec3& translation, float tolerance = EPSILON) const; glm::quat getDefaultRotationInParentFrame() const; - const glm::vec3& getDefaultTranslationInConstrainedFrame() const; + glm::vec3 getDefaultTranslationInConstrainedFrame() const; void clearTransformTranslation(); @@ -95,12 +101,13 @@ public: void setTransform(const glm::mat4& transform) { _transform = transform; } - const glm::vec3& getTranslation() const { return _translation; } + glm::vec3 getTranslation() const { return _translation * _unitsScale; } const glm::mat4& getPreTransform() const { return _preTransform; } const glm::mat4& getPostTransform() const { return _postTransform; } const glm::quat& getPreRotation() const { return _preRotation; } const glm::quat& getPostRotation() const { return _postRotation; } const glm::quat& getDefaultRotation() const { return _defaultRotation; } + glm::vec3 getDefaultTranslation() const { return _defaultTranslation * _unitsScale; } const glm::quat& getInverseDefaultRotation() const { return _inverseDefaultRotation; } const QString& getName() const { return _name; } bool getIsFree() const { return _isFree; } @@ -124,6 +131,7 @@ private: glm::quat _defaultRotation; // Not necessarilly bind rotation. See FBXJoint transform/bindTransform glm::quat _inverseDefaultRotation; + glm::vec3 _defaultTranslation; glm::vec3 _translation; QString _name; int _parentIndex; @@ -133,6 +141,8 @@ private: glm::mat4 _preTransform; glm::mat4 _postTransform; glm::quat _inverseBindRotation; + + glm::vec3 _unitsScale{1.0f, 1.0f, 1.0f}; }; #endif // hifi_JointState_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index ac80f46836..ee3ac0a05a 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -266,6 +266,7 @@ void Rig::reset(const QVector& fbxJoints) { } for (int i = 0; i < _jointStates.size(); i++) { _jointStates[i].setRotationInConstrainedFrame(fbxJoints.at(i).rotation, 0.0f); + _jointStates[i].setTranslation(fbxJoints.at(i).translation, 0.0f); } } @@ -285,10 +286,20 @@ bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { return !state.rotationIsDefault(rotation); } +bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const { + if (index == -1 || index >= _jointStates.size()) { + return false; + } + const JointState& state = _jointStates.at(index); + translation = state.getTranslation(); + return !state.translationIsDefault(translation); +} + void Rig::clearJointState(int index) { if (index != -1 && index < _jointStates.size()) { JointState& state = _jointStates[index]; state.setRotationInConstrainedFrame(glm::quat(), 0.0f); + state.setTranslation(state.getDefaultTranslationInConstrainedFrame(), 0.0f); } } @@ -315,7 +326,7 @@ void Rig::setJointAnimatinoPriority(int index, float newPriority) { } } -void Rig::setJointState(int index, bool valid, const glm::quat& rotation, float priority) { +void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { if (index != -1 && index < _jointStates.size()) { JointState& state = _jointStates[index]; if (valid) { @@ -332,6 +343,12 @@ void Rig::restoreJointRotation(int index, float fraction, float priority) { } } +void Rig::restoreJointTranslation(int index, float fraction, float priority) { + if (index != -1 && index < _jointStates.size()) { + _jointStates[index].restoreTranslation(fraction, priority); + } +} + bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { if (jointIndex == -1 || jointIndex >= _jointStates.size()) { @@ -367,6 +384,14 @@ bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const { return true; } +bool Rig::getJointTranslation(int jointIndex, glm::vec3& translation) const { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { + return false; + } + translation = _jointStates[jointIndex].getTranslation(); + return true; +} + bool Rig::getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const { if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return false; @@ -554,6 +579,7 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { const float PRIORITY = 1.0f; for (size_t i = 0; i < poses.size(); i++) { setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, 1.0f); + setJointTranslation((int)i, true, poses[i].trans, PRIORITY); } } else { @@ -827,6 +853,7 @@ bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, c foreach (int index, freeLineage) { JointState& state = _jointStates[index]; state.restoreRotation(fraction, priority); + state.restoreTranslation(fraction, priority); } return true; } @@ -1175,3 +1202,12 @@ void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) { qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; }); } + +bool Rig::getModelOffset(glm::vec3& modelOffsetOut) const { + if (_animSkeleton && _rootJointIndex >= 0) { + modelOffsetOut = -_animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans; + return true; + } else { + return false; + } +} diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 8ddc0c9f6d..ef802e925d 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -123,12 +123,13 @@ public: int rightShoulderJointIndex); bool jointStatesEmpty() { return _jointStates.isEmpty(); }; int getJointStateCount() const { return _jointStates.size(); } - int indexOfJoint(const QString& jointName) ; + int indexOfJoint(const QString& jointName); void initJointTransforms(glm::mat4 rootTransform); void clearJointTransformTranslation(int jointIndex); void reset(const QVector& fbxJoints); bool getJointStateRotation(int index, glm::quat& rotation) const; + bool getJointStateTranslation(int index, glm::vec3& translation) const; void applyJointRotationDelta(int jointIndex, const glm::quat& delta, float priority); JointState getJointState(int jointIndex) const; // XXX bool getVisibleJointState(int index, glm::quat& rotation) const; @@ -137,14 +138,21 @@ public: void clearJointAnimationPriority(int index); float getJointAnimatinoPriority(int index); void setJointAnimatinoPriority(int index, float newPriority); - void setJointState(int index, bool valid, const glm::quat& rotation, float priority); + + virtual void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, + float priority) = 0; + virtual void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) {} + void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); + void restoreJointRotation(int index, float fraction, float priority); + void restoreJointTranslation(int index, float fraction, float priority); bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const; bool getJointPosition(int jointIndex, glm::vec3& position) const; bool getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const; bool getJointRotation(int jointIndex, glm::quat& rotation) const; + bool getJointTranslation(int jointIndex, glm::vec3& translation) const; bool getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const; bool getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const; @@ -196,6 +204,8 @@ public: AnimSkeleton::ConstPointer getAnimSkeleton() const { return _animSkeleton; } bool disableHands {false}; // should go away with rig animation (and Rig::inverseKinematics) + bool getModelOffset(glm::vec3& modelOffsetOut) const; + protected: void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); diff --git a/libraries/audio/src/AudioSRC.cpp b/libraries/audio/src/AudioSRC.cpp index 736b098def..59fe29df36 100644 --- a/libraries/audio/src/AudioSRC.cpp +++ b/libraries/audio/src/AudioSRC.cpp @@ -558,30 +558,8 @@ static const float Q32_TO_FLOAT = 1.0f / (1ULL << 32); // blocking size in frames, chosen so block processing fits in L1 cache static const int SRC_BLOCK = 1024; -//#define SRC_DITHER -#define RAND16(r) (((r) = (r) * 69069u + 1u) >> 16) - -// these are performance sensitive -#define lo32(a) (((uint32_t* )&(a))[0]) -#define hi32(a) (((int32_t* )&(a))[1]) - -//#define lo32(a) ((uint32_t)(a)) -//#define hi32(a) ((int32_t)((a) >> 32)) - -//static inline uint32_t lo32(int64_t a) { -// union { -// int64_t val; -// struct { uint32_t lo; int32_t hi; } reg; -// } b = { a }; -// return b.reg.lo; -//} -//static inline int32_t hi32(int64_t a) { -// union { -// int64_t val; -// struct { uint32_t lo; int32_t hi; } reg; -// } b = { a }; -// return b.reg.hi; -//} +#define lo32(a) ((uint32_t)(a)) +#define hi32(a) ((int32_t)((a) >> 32)) // // Portable aligned malloc/free @@ -671,6 +649,7 @@ int AudioSRC::createRationalFilter(int upFactor, int downFactor, float gain) { numTaps = (numCoefs + upFactor - 1) / upFactor; gain *= (float)oldCoefs / numCoefs; } + numTaps = (numTaps + 3) & ~3; // SIMD4 // interpolate the coefficients of the prototype filter float* tempFilter = new float[numTaps * numPhases]; @@ -679,7 +658,7 @@ int AudioSRC::createRationalFilter(int upFactor, int downFactor, float gain) { cubicInterpolation(prototypeFilter, tempFilter, prototypeCoefs, numCoefs, gain); // create the polyphase filter - _polyphaseFilter = (float*)aligned_malloc(numTaps * numPhases * sizeof(float), 32); + _polyphaseFilter = (float*)aligned_malloc(numTaps * numPhases * sizeof(float), 16); // SIMD4 // rearrange into polyphase form, ordered by use for (int i = 0; i < numPhases; i++) { @@ -720,6 +699,7 @@ int AudioSRC::createIrrationalFilter(int upFactor, int downFactor, float gain) { numTaps = (numCoefs + upFactor - 1) / upFactor; gain *= (float)oldCoefs / numCoefs; } + numTaps = (numTaps + 3) & ~3; // SIMD4 // interpolate the coefficients of the prototype filter float* tempFilter = new float[numTaps * numPhases]; @@ -728,7 +708,7 @@ int AudioSRC::createIrrationalFilter(int upFactor, int downFactor, float gain) { cubicInterpolation(prototypeFilter, tempFilter, prototypeCoefs, numCoefs, gain); // create the polyphase filter, with extra phase at the end to simplify coef interpolation - _polyphaseFilter = (float*)aligned_malloc(numTaps * (numPhases + 1) * sizeof(float), 32); + _polyphaseFilter = (float*)aligned_malloc(numTaps * (numPhases + 1) * sizeof(float), 16); // SIMD4 // rearrange into polyphase form, ordered by fractional delay for (int phase = 0; phase < numPhases; phase++) { @@ -754,6 +734,336 @@ int AudioSRC::createIrrationalFilter(int upFactor, int downFactor, float gain) { return numTaps; } +// +// on x86 architecture, assume that SSE2 is present +// +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) + +#include + +int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFrames) { + int outputFrames = 0; + + assert((_numTaps & 0x3) == 0); // SIMD4 + + if (_step == 0) { // rational + + int32_t i = hi32(_offset); + + while (i < inputFrames) { + + const float* c0 = &_polyphaseFilter[_numTaps * _phase]; + + __m128 acc0 = _mm_setzero_ps(); + + for (int j = 0; j < _numTaps; j += 4) { + + //float coef = c0[j]; + __m128 coef0 = _mm_loadu_ps(&c0[j]); + + //acc0 += input0[i + j] * coef; + acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); + } + + // horizontal sum + acc0 = _mm_add_ps(acc0, _mm_movehl_ps(acc0, acc0)); + acc0 = _mm_add_ss(acc0, _mm_shuffle_ps(acc0, acc0, _MM_SHUFFLE(0,0,0,1))); + + _mm_store_ss(&output0[outputFrames], acc0); + outputFrames += 1; + + i += _stepTable[_phase]; + if (++_phase == _upFactor) { + _phase = 0; + } + } + _offset = (int64_t)(i - inputFrames) << 32; + + } else { // irrational + + while (hi32(_offset) < inputFrames) { + + int32_t i = hi32(_offset); + uint32_t f = lo32(_offset); + + uint32_t phase = f >> SRC_FRACBITS; + __m128 frac = _mm_set1_ps((f & SRC_FRACMASK) * QFRAC_TO_FLOAT); + + const float* c0 = &_polyphaseFilter[_numTaps * (phase + 0)]; + const float* c1 = &_polyphaseFilter[_numTaps * (phase + 1)]; + + __m128 acc0 = _mm_setzero_ps(); + + for (int j = 0; j < _numTaps; j += 4) { + + //float coef = c0[j] + frac * (c1[j] - c0[j]); + __m128 coef0 = _mm_loadu_ps(&c0[j]); + __m128 coef1 = _mm_loadu_ps(&c1[j]); + coef1 = _mm_sub_ps(coef1, coef0); + coef0 = _mm_add_ps(_mm_mul_ps(coef1, frac), coef0); + + //acc0 += input0[i + j] * coef; + acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); + } + + // horizontal sum + acc0 = _mm_add_ps(acc0, _mm_movehl_ps(acc0, acc0)); + acc0 = _mm_add_ss(acc0, _mm_shuffle_ps(acc0, acc0, _MM_SHUFFLE(0,0,0,1))); + + _mm_store_ss(&output0[outputFrames], acc0); + outputFrames += 1; + + _offset += _step; + } + _offset -= (int64_t)inputFrames << 32; + } + + return outputFrames; +} + +int AudioSRC::multirateFilter2(const float* input0, const float* input1, float* output0, float* output1, int inputFrames) { + int outputFrames = 0; + + assert((_numTaps & 0x3) == 0); // SIMD4 + + if (_step == 0) { // rational + + int32_t i = hi32(_offset); + + while (i < inputFrames) { + + const float* c0 = &_polyphaseFilter[_numTaps * _phase]; + + __m128 acc0 = _mm_setzero_ps(); + __m128 acc1 = _mm_setzero_ps(); + + for (int j = 0; j < _numTaps; j += 4) { + + //float coef = c0[j]; + __m128 coef0 = _mm_loadu_ps(&c0[j]); + + //acc0 += input0[i + j] * coef; + acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); + acc1 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input1[i + j]), coef0), acc1); + } + + // horizontal sum + acc0 = _mm_add_ps(acc0, _mm_movehl_ps(acc0, acc0)); + acc1 = _mm_add_ps(acc1, _mm_movehl_ps(acc1, acc1)); + acc0 = _mm_add_ss(acc0, _mm_shuffle_ps(acc0, acc0, _MM_SHUFFLE(0,0,0,1))); + acc1 = _mm_add_ss(acc1, _mm_shuffle_ps(acc1, acc1, _MM_SHUFFLE(0,0,0,1))); + + _mm_store_ss(&output0[outputFrames], acc0); + _mm_store_ss(&output1[outputFrames], acc1); + outputFrames += 1; + + i += _stepTable[_phase]; + if (++_phase == _upFactor) { + _phase = 0; + } + } + _offset = (int64_t)(i - inputFrames) << 32; + + } else { // irrational + + while (hi32(_offset) < inputFrames) { + + int32_t i = hi32(_offset); + uint32_t f = lo32(_offset); + + uint32_t phase = f >> SRC_FRACBITS; + __m128 frac = _mm_set1_ps((f & SRC_FRACMASK) * QFRAC_TO_FLOAT); + + const float* c0 = &_polyphaseFilter[_numTaps * (phase + 0)]; + const float* c1 = &_polyphaseFilter[_numTaps * (phase + 1)]; + + __m128 acc0 = _mm_setzero_ps(); + __m128 acc1 = _mm_setzero_ps(); + + for (int j = 0; j < _numTaps; j += 4) { + + //float coef = c0[j] + frac * (c1[j] - c0[j]); + __m128 coef0 = _mm_loadu_ps(&c0[j]); + __m128 coef1 = _mm_loadu_ps(&c1[j]); + coef1 = _mm_sub_ps(coef1, coef0); + coef0 = _mm_add_ps(_mm_mul_ps(coef1, frac), coef0); + + //acc0 += input0[i + j] * coef; + acc0 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input0[i + j]), coef0), acc0); + acc1 = _mm_add_ps(_mm_mul_ps(_mm_loadu_ps(&input1[i + j]), coef0), acc1); + } + + // horizontal sum + acc0 = _mm_add_ps(acc0, _mm_movehl_ps(acc0, acc0)); + acc1 = _mm_add_ps(acc1, _mm_movehl_ps(acc1, acc1)); + acc0 = _mm_add_ss(acc0, _mm_shuffle_ps(acc0, acc0, _MM_SHUFFLE(0,0,0,1))); + acc1 = _mm_add_ss(acc1, _mm_shuffle_ps(acc1, acc1, _MM_SHUFFLE(0,0,0,1))); + + _mm_store_ss(&output0[outputFrames], acc0); + _mm_store_ss(&output1[outputFrames], acc1); + outputFrames += 1; + + _offset += _step; + } + _offset -= (int64_t)inputFrames << 32; + } + + return outputFrames; +} + +// convert int16_t to float, deinterleave stereo +void AudioSRC::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { + __m128 scale = _mm_set1_ps(1/32768.0f); + + if (_numChannels == 1) { + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128i a0 = _mm_loadl_epi64((__m128i*)&input[i]); + + // sign-extend + a0 = _mm_srai_epi32(_mm_unpacklo_epi16(a0, a0), 16); + + __m128 f0 = _mm_mul_ps(_mm_cvtepi32_ps(a0), scale); + + _mm_storeu_ps(&outputs[0][i], f0); + } + for (; i < numFrames; i++) { + __m128i a0 = _mm_insert_epi16(_mm_setzero_si128(), input[i], 0); + + // sign-extend + a0 = _mm_srai_epi32(_mm_unpacklo_epi16(a0, a0), 16); + + __m128 f0 = _mm_mul_ps(_mm_cvtepi32_ps(a0), scale); + + _mm_store_ss(&outputs[0][i], f0); + } + + } else if (_numChannels == 2) { + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128i a0 = _mm_loadu_si128((__m128i*)&input[2*i]); + __m128i a1 = a0; + + // deinterleave and sign-extend + a0 = _mm_madd_epi16(a0, _mm_set1_epi32(0x00000001)); + a1 = _mm_madd_epi16(a1, _mm_set1_epi32(0x00010000)); + + __m128 f0 = _mm_mul_ps(_mm_cvtepi32_ps(a0), scale); + __m128 f1 = _mm_mul_ps(_mm_cvtepi32_ps(a1), scale); + + _mm_storeu_ps(&outputs[0][i], f0); + _mm_storeu_ps(&outputs[1][i], f1); + } + for (; i < numFrames; i++) { + __m128i a0 = _mm_cvtsi32_si128(*(int32_t*)&input[2*i]); + __m128i a1 = a0; + + // deinterleave and sign-extend + a0 = _mm_madd_epi16(a0, _mm_set1_epi32(0x00000001)); + a1 = _mm_madd_epi16(a1, _mm_set1_epi32(0x00010000)); + + __m128 f0 = _mm_mul_ps(_mm_cvtepi32_ps(a0), scale); + __m128 f1 = _mm_mul_ps(_mm_cvtepi32_ps(a1), scale); + + _mm_store_ss(&outputs[0][i], f0); + _mm_store_ss(&outputs[1][i], f1); + } + } +} + +// fast TPDF dither in [-1.0f, 1.0f] +static inline __m128 dither4() { + static __m128i rz; + + // update the 8 different maximum-length LCGs + rz = _mm_mullo_epi16(rz, _mm_set_epi16(25173, -25511, -5975, -23279, 19445, -27591, 30185, -3495)); + rz = _mm_add_epi16(rz, _mm_set_epi16(13849, -32767, 105, -19675, -7701, -32679, -13225, 28013)); + + // promote to 32-bit + __m128i r0 = _mm_unpacklo_epi16(rz, _mm_setzero_si128()); + __m128i r1 = _mm_unpackhi_epi16(rz, _mm_setzero_si128()); + + // return (r0 - r1) * (1/65536.0f); + __m128 d0 = _mm_cvtepi32_ps(_mm_sub_epi32(r0, r1)); + return _mm_mul_ps(d0, _mm_set1_ps(1/65536.0f)); +} + +// convert float to int16_t, interleave stereo +void AudioSRC::convertOutputToInt16(float** inputs, int16_t* output, int numFrames) { + __m128 scale = _mm_set1_ps(32768.0f); + + if (_numChannels == 1) { + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128 f0 = _mm_mul_ps(_mm_loadu_ps(&inputs[0][i]), scale); + + f0 = _mm_add_ps(f0, dither4()); + + // round and saturate + __m128i a0 = _mm_cvtps_epi32(f0); + a0 = _mm_packs_epi32(a0, a0); + + _mm_storel_epi64((__m128i*)&output[i], a0); + } + for (; i < numFrames; i++) { + __m128 f0 = _mm_mul_ps(_mm_load_ss(&inputs[0][i]), scale); + + f0 = _mm_add_ps(f0, dither4()); + + // round and saturate + __m128i a0 = _mm_cvtps_epi32(f0); + a0 = _mm_packs_epi32(a0, a0); + + output[i] = (int16_t)_mm_extract_epi16(a0, 0); + } + + } else if (_numChannels == 2) { + + int i = 0; + for (; i < numFrames - 3; i += 4) { + __m128 f0 = _mm_mul_ps(_mm_loadu_ps(&inputs[0][i]), scale); + __m128 f1 = _mm_mul_ps(_mm_loadu_ps(&inputs[1][i]), scale); + + __m128 d0 = dither4(); + f0 = _mm_add_ps(f0, d0); + f1 = _mm_add_ps(f1, d0); + + // round and saturate + __m128i a0 = _mm_cvtps_epi32(f0); + __m128i a1 = _mm_cvtps_epi32(f1); + a0 = _mm_packs_epi32(a0, a0); + a1 = _mm_packs_epi32(a1, a1); + + // interleave + a0 = _mm_unpacklo_epi16(a0, a1); + _mm_storeu_si128((__m128i*)&output[2*i], a0); + } + for (; i < numFrames; i++) { + __m128 f0 = _mm_mul_ps(_mm_load_ss(&inputs[0][i]), scale); + __m128 f1 = _mm_mul_ps(_mm_load_ss(&inputs[1][i]), scale); + + __m128 d0 = dither4(); + f0 = _mm_add_ps(f0, d0); + f1 = _mm_add_ps(f1, d0); + + // round and saturate + __m128i a0 = _mm_cvtps_epi32(f0); + __m128i a1 = _mm_cvtps_epi32(f1); + a0 = _mm_packs_epi32(a0, a0); + a1 = _mm_packs_epi32(a1, a1); + + // interleave + a0 = _mm_unpacklo_epi16(a0, a1); + *(int32_t*)&output[2*i] = _mm_cvtsi128_si32(a0); + } + } +} + +#else + int AudioSRC::multirateFilter1(const float* input0, float* output0, int inputFrames) { int outputFrames = 0; @@ -886,45 +1196,73 @@ int AudioSRC::multirateFilter2(const float* input0, const float* input1, float* return outputFrames; } -// convert int16_t to float -// deinterleave stereo samples +// convert int16_t to float, deinterleave stereo void AudioSRC::convertInputFromInt16(const int16_t* input, float** outputs, int numFrames) { - for (int i = 0; i < numFrames; i++) { - for (int j = 0; j < _numChannels; j++) { + const float scale = 1/32768.0f; - float f = (float)input[_numChannels*i + j]; - outputs[j][i] = f * (1.0f/32768.0f); + if (_numChannels == 1) { + for (int i = 0; i < numFrames; i++) { + outputs[0][i] = (float)input[i] * scale; + } + } else if (_numChannels == 2) { + for (int i = 0; i < numFrames; i++) { + outputs[0][i] = (float)input[2*i + 0] * scale; + outputs[1][i] = (float)input[2*i + 1] * scale; } } } -// convert float to int16_t -// interleave stereo samples +// fast TPDF dither in [-1.0f, 1.0f] +static inline float dither() { + static uint32_t rz = 0; + rz = rz * 69069 + 1; + int32_t r0 = rz & 0xffff; + int32_t r1 = rz >> 16; + return (r0 - r1) * (1/65536.0f); +} + +// convert float to int16_t, interleave stereo void AudioSRC::convertOutputToInt16(float** inputs, int16_t* output, int numFrames) { - for (int i = 0; i < numFrames; i++) { - for (int j = 0; j < _numChannels; j++) { + const float scale = 32768.0f; - float f = inputs[j][i] * 32768.0f; + if (_numChannels == 1) { + for (int i = 0; i < numFrames; i++) { -#ifdef SRC_DITHER - // TPDF dither in [-1.0f, 1.0f] - static uint32_t rz = 1; - int r0 = RAND16(rz); - int r1 = RAND16(rz); - f += (r0 - r1) * (1.0f/65536.0f); + float f = inputs[0][i] * scale; - // round + f += dither(); + + // round and saturate f += (f < 0.0f ? -0.5f : +0.5f); -#endif - // saturate - f = std::min(f, 32767.0f); - f = std::max(f, -32768.0f); + f = std::max(std::min(f, 32767.0f), -32768.0f); - output[_numChannels * i + j] = (int16_t)f; + output[i] = (int16_t)f; + } + } else if (_numChannels == 2) { + for (int i = 0; i < numFrames; i++) { + + float f0 = inputs[0][i] * scale; + float f1 = inputs[1][i] * scale; + + float d = dither(); + f0 += d; + f1 += d; + + // round and saturate + f0 += (f0 < 0.0f ? -0.5f : +0.5f); + f1 += (f1 < 0.0f ? -0.5f : +0.5f); + f0 = std::max(std::min(f0, 32767.0f), -32768.0f); + f1 = std::max(std::min(f1, 32767.0f), -32768.0f); + + // interleave + output[2*i + 0] = (int16_t)f0; + output[2*i + 1] = (int16_t)f1; } } } +#endif + int AudioSRC::processFloat(float** inputs, float** outputs, int inputFrames) { int outputFrames = 0; @@ -1019,10 +1357,10 @@ AudioSRC::AudioSRC(int inputSampleRate, int outputSampleRate, int numChannels) { _history[1] = new float[2 * _numHistory]; // format conversion buffers - _inputs[0] = new float[SRC_BLOCK]; - _inputs[1] = new float[SRC_BLOCK]; - _outputs[0] = new float[SRC_BLOCK]; - _outputs[1] = new float[SRC_BLOCK]; + _inputs[0] = (float*)aligned_malloc(SRC_BLOCK * sizeof(float), 16); // SIMD4 + _inputs[1] = (float*)aligned_malloc(SRC_BLOCK * sizeof(float), 16); + _outputs[0] = (float*)aligned_malloc(SRC_BLOCK * sizeof(float), 16); + _outputs[1] = (float*)aligned_malloc(SRC_BLOCK * sizeof(float), 16); // input blocking size, such that input and output are both guaranteed not to exceed SRC_BLOCK frames _inputBlock = std::min(SRC_BLOCK, getMaxInput(SRC_BLOCK)); @@ -1041,10 +1379,10 @@ AudioSRC::~AudioSRC() { delete[] _history[0]; delete[] _history[1]; - delete[] _inputs[0]; - delete[] _inputs[1]; - delete[] _outputs[0]; - delete[] _outputs[1]; + aligned_free(_inputs[0]); + aligned_free(_inputs[1]); + aligned_free(_outputs[0]); + aligned_free(_outputs[1]); } // diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f64504d662..84bf4bec7b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -49,6 +49,7 @@ AvatarData::AvatarData() : _keyState(NO_KEY_DOWN), _forceFaceTrackerConnected(false), _hasNewJointRotations(true), + _hasNewJointTranslations(true), _headData(NULL), _handData(NULL), _faceModelURL("http://invalid.com"), @@ -278,12 +279,16 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { // pupil dilation destinationBuffer += packFloatToByte(destinationBuffer, _headData->_pupilDilation, 1.0f); - // joint data + // joint rotation data *destinationBuffer++ = _jointData.size(); unsigned char* validityPosition = destinationBuffer; unsigned char validity = 0; int validityBit = 0; + #ifdef WANT_DEBUG + int rotationSentCount = 0; + #endif + _lastSentJointData.resize(_jointData.size()); for (int i=0; i < _jointData.size(); i++) { @@ -292,7 +297,12 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { if (sendAll || !cullSmallChanges || fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) { - validity |= (1 << validityBit); + if (data.rotationSet) { + validity |= (1 << validityBit); + #ifdef WANT_DEBUG + rotationSentCount++; + #endif + } } } if (++validityBit == BITS_IN_BYTE) { @@ -317,6 +327,73 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } } + + // joint translation data + validityPosition = destinationBuffer; + validity = 0; + validityBit = 0; + + #ifdef WANT_DEBUG + int translationSentCount = 0; + #endif + + float maxTranslationDimension = 0.0; + for (int i=0; i < _jointData.size(); i++) { + const JointData& data = _jointData.at(i); + if (sendAll || _lastSentJointData[i].translation != data.translation) { + if (sendAll || + !cullSmallChanges || + glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) { + if (data.translationSet) { + validity |= (1 << validityBit); + #ifdef WANT_DEBUG + translationSentCount++; + #endif + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + } + } + } + if (++validityBit == BITS_IN_BYTE) { + *destinationBuffer++ = validity; + validityBit = validity = 0; + } + } + + + if (validityBit != 0) { + *destinationBuffer++ = validity; + } + + // TODO -- automatically pick translationCompressionRadix + int translationCompressionRadix = 12; + + *destinationBuffer++ = translationCompressionRadix; + + validityBit = 0; + validity = *validityPosition++; + for (int i = 0; i < _jointData.size(); i ++) { + const JointData& data = _jointData[ i ]; + if (validity & (1 << validityBit)) { + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, translationCompressionRadix); + } + if (++validityBit == BITS_IN_BYTE) { + validityBit = 0; + validity = *validityPosition++; + } + } + + #ifdef WANT_DEBUG + if (sendAll) { + qDebug() << "SENDING -- rotations:" << rotationSentCount << "translations:" << translationSentCount + << "largest:" << maxTranslationDimension + << "radix:" << translationCompressionRadix + << "size:" << (int)(destinationBuffer - startPosition); + } + #endif + return avatarDataByteArray.left(destinationBuffer - startPosition); } @@ -328,7 +405,16 @@ void AvatarData::doneEncoding(bool cullSmallChanges) { if (_lastSentJointData[i].rotation != data.rotation) { if (!cullSmallChanges || fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) { - _lastSentJointData[i].rotation = data.rotation; + if (data.rotationSet) { + _lastSentJointData[i].rotation = data.rotation; + } + } + + if (!cullSmallChanges || + glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) { + if (data.translationSet) { + _lastSentJointData[i].translation = data.translation; + } } } } @@ -374,7 +460,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // + 1 byte for numJoints (0) // = 39 bytes int minPossibleSize = 39; - + int maxAvailableSize = buffer.size(); if (minPossibleSize > maxAvailableSize) { if (shouldLogError(now)) { @@ -556,7 +642,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f); } // 1 byte - // joint data + // joint rotations int numJoints = *sourceBuffer++; int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); minPossibleSize += bytesOfValidity; @@ -569,13 +655,13 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } return maxAvailableSize; } - int numValidJoints = 0; + int numValidJointRotations = 0; _jointData.resize(numJoints); - QVector valids; - valids.resize(numJoints); + QVector validRotations; + validRotations.resize(numJoints); - { // validity bits + { // rotation validity bits unsigned char validity = 0; int validityBit = 0; for (int i = 0; i < numJoints; i++) { @@ -584,20 +670,19 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } bool valid = (bool)(validity & (1 << validityBit)); if (valid) { - ++numValidJoints; + ++numValidJointRotations; } - valids[i] = valid; + validRotations[i] = valid; validityBit = (validityBit + 1) % BITS_IN_BYTE; } - } - // 1 + bytesOfValidity bytes + } // 1 + bytesOfValidity bytes // each joint rotation component is stored in two bytes (sizeof(uint16_t)) int COMPONENTS_PER_QUATERNION = 4; - minPossibleSize += numValidJoints * COMPONENTS_PER_QUATERNION * sizeof(uint16_t); + minPossibleSize += numValidJointRotations * COMPONENTS_PER_QUATERNION * sizeof(uint16_t); if (minPossibleSize > maxAvailableSize) { if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointData;" + qCDebug(avatars) << "Malformed AvatarData packet after JointData rotation validity;" << " displayName = '" << _displayName << "'" << " minPossibleSize = " << minPossibleSize << " maxAvailableSize = " << maxAvailableSize; @@ -608,13 +693,71 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { { // joint data for (int i = 0; i < numJoints; i++) { JointData& data = _jointData[i]; - if (valids[i]) { + if (validRotations[i]) { _hasNewJointRotations = true; + data.rotationSet = true; sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation); } } } // numJoints * 8 bytes + // joint translations + // get translation validity bits -- these indicate which translations were packed + int numValidJointTranslations = 0; + QVector validTranslations; + validTranslations.resize(numJoints); + + { // translation validity bits + unsigned char validity = 0; + int validityBit = 0; + for (int i = 0; i < numJoints; i++) { + if (validityBit == 0) { + validity = *sourceBuffer++; + } + bool valid = (bool)(validity & (1 << validityBit)); + if (valid) { + ++numValidJointTranslations; + } + validTranslations[i] = valid; + validityBit = (validityBit + 1) % BITS_IN_BYTE; + } + } // 1 + bytesOfValidity bytes + + // each joint translation component is stored in 6 bytes. 1 byte for translationCompressionRadix + minPossibleSize += numValidJointTranslations * 6 + 1; + if (minPossibleSize > maxAvailableSize) { + if (shouldLogError(now)) { + qCDebug(avatars) << "Malformed AvatarData packet after JointData translation validity;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; + } + return maxAvailableSize; + } + + int translationCompressionRadix = *sourceBuffer++; + + { // joint data + for (int i = 0; i < numJoints; i++) { + JointData& data = _jointData[i]; + if (validTranslations[i]) { + sourceBuffer += + unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, translationCompressionRadix); + _hasNewJointTranslations = true; + data.translationSet = true; + } + } + } // numJoints * 12 bytes + + #ifdef WANT_DEBUG + if (numValidJointRotations > 15) { + qDebug() << "RECEIVING -- rotations:" << numValidJointRotations + << "translations:" << numValidJointTranslations + << "radix:" << translationCompressionRadix + << "size:" << (int)(sourceBuffer - startPosition); + } + #endif + int numBytesRead = sourceBuffer - startPosition; _averageBytesReceived.updateAverage(numBytesRead); return numBytesRead; @@ -800,7 +943,7 @@ void AvatarData::changeReferential(Referential* ref) { _referential = ref; } -void AvatarData::setJointData(int index, const glm::quat& rotation) { +void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) { if (index == -1) { return; } @@ -813,6 +956,7 @@ void AvatarData::setJointData(int index, const glm::quat& rotation) { } JointData& data = _jointData[index]; data.rotation = rotation; + data.translation = translation; } void AvatarData::clearJointData(int index) { @@ -854,13 +998,67 @@ glm::quat AvatarData::getJointRotation(int index) const { return index < _jointData.size() ? _jointData.at(index).rotation : glm::quat(); } -void AvatarData::setJointData(const QString& name, const glm::quat& rotation) { + +glm::vec3 AvatarData::getJointTranslation(int index) const { + if (index == -1) { + return glm::vec3(); + } + if (QThread::currentThread() != thread()) { + glm::vec3 result; + QMetaObject::invokeMethod(const_cast(this), "getJointTranslation", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(glm::vec3, result), Q_ARG(int, index)); + return result; + } + return index < _jointData.size() ? _jointData.at(index).translation : glm::vec3(); +} + +glm::vec3 AvatarData::getJointTranslation(const QString& name) const { + if (QThread::currentThread() != thread()) { + glm::vec3 result; + QMetaObject::invokeMethod(const_cast(this), "getJointTranslation", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(glm::vec3, result), Q_ARG(const QString&, name)); + return result; + } + return getJointTranslation(getJointIndex(name)); +} + +void AvatarData::setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setJointData", Q_ARG(const QString&, name), Q_ARG(const glm::quat&, rotation)); return; } - setJointData(getJointIndex(name), rotation); + setJointData(getJointIndex(name), rotation, translation); +} + +void AvatarData::setJointRotation(int index, const glm::quat& rotation) { + if (index == -1) { + return; + } + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation)); + return; + } + if (_jointData.size() <= index) { + _jointData.resize(index + 1); + } + JointData& data = _jointData[index]; + data.rotation = rotation; +} + +void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { + if (index == -1) { + return; + } + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(int, index), Q_ARG(const glm::vec3&, translation)); + return; + } + if (_jointData.size() <= index) { + _jointData.resize(index + 1); + } + JointData& data = _jointData[index]; + data.translation = translation; } void AvatarData::clearJointData(const QString& name) { @@ -918,7 +1116,25 @@ void AvatarData::setJointRotations(QVector jointRotations) { } for (int i = 0; i < jointRotations.size(); ++i) { if (i < _jointData.size()) { - setJointData(i, jointRotations[i]); + setJointRotation(i, jointRotations[i]); + } + } +} + +void AvatarData::setJointTranslations(QVector jointTranslations) { + if (QThread::currentThread() != thread()) { + QVector result; + QMetaObject::invokeMethod(const_cast(this), + "setJointTranslations", Qt::BlockingQueuedConnection, + Q_ARG(QVector, jointTranslations)); + } + + if (_jointData.size() < jointTranslations.size()) { + _jointData.resize(jointTranslations.size()); + } + for (int i = 0; i < jointTranslations.size(); ++i) { + if (i < _jointData.size()) { + setJointTranslation(i, jointTranslations[i]); } } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a934b98037..e4022fd474 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -117,6 +117,7 @@ const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default"); const float AVATAR_SEND_FULL_UPDATE_RATIO = 0.02f; // this controls how large a change in joint-rotation must be before the interface sends it to the avatar mixer const float AVATAR_MIN_ROTATION_DOT = 0.9999999f; +const float AVATAR_MIN_TRANSLATION = 0.0001f; // Where one's own Avatar begins in the world (will be overwritten if avatar data file is found). @@ -240,20 +241,24 @@ public: Q_INVOKABLE char getHandState() const { return _handState; } const QVector& getJointData() const { return _jointData; } - void setJointData(const QVector& jointData) { _jointData = jointData; } - Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation); + Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation); + Q_INVOKABLE virtual void setJointRotation(int index, const glm::quat& rotation); + Q_INVOKABLE virtual void setJointTranslation(int index, const glm::vec3& translation); Q_INVOKABLE virtual void clearJointData(int index); Q_INVOKABLE bool isJointDataValid(int index) const; Q_INVOKABLE virtual glm::quat getJointRotation(int index) const; + Q_INVOKABLE virtual glm::vec3 getJointTranslation(int index) const; - Q_INVOKABLE void setJointData(const QString& name, const glm::quat& rotation); + Q_INVOKABLE void setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation); Q_INVOKABLE void clearJointData(const QString& name); Q_INVOKABLE bool isJointDataValid(const QString& name) const; Q_INVOKABLE glm::quat getJointRotation(const QString& name) const; + Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const; Q_INVOKABLE virtual QVector getJointRotations() const; Q_INVOKABLE virtual void setJointRotations(QVector jointRotations); + Q_INVOKABLE virtual void setJointTranslations(QVector jointTranslations); Q_INVOKABLE virtual void clearJointsData(); @@ -387,6 +392,7 @@ protected: bool _forceFaceTrackerConnected; bool _hasNewJointRotations; // set in AvatarData, cleared in Avatar + bool _hasNewJointTranslations; // set in AvatarData, cleared in Avatar HeadData* _headData; HandData* _handData; @@ -435,6 +441,9 @@ Q_DECLARE_METATYPE(AvatarData*) class JointData { public: glm::quat rotation; + bool rotationSet = false; + glm::vec3 translation; + bool translationSet = false; }; class AttachmentData { diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp index 29544924b2..a425323a41 100644 --- a/libraries/avatars/src/Player.cpp +++ b/libraries/avatars/src/Player.cpp @@ -254,8 +254,17 @@ void Player::play() { nextFrame.getJointRotations()[i], _frameInterpolationFactor); } + + QVector jointTranslations(currentFrame.getJointTranslations().size()); + for (int i = 0; i < currentFrame.getJointTranslations().size(); ++i) { + jointTranslations[i] = + currentFrame.getJointTranslations()[i] * (1.0f - _frameInterpolationFactor) + + nextFrame.getJointTranslations()[i] * _frameInterpolationFactor; + } + _avatar->setJointRotations(jointRotations); - + _avatar->setJointTranslations(jointTranslations); + HeadData* head = const_cast(_avatar->getHeadData()); if (head) { // Make sure fake face tracker connection doesn't get turned off diff --git a/libraries/avatars/src/Recording.cpp b/libraries/avatars/src/Recording.cpp index 76e1d68050..5514b97b6f 100644 --- a/libraries/avatars/src/Recording.cpp +++ b/libraries/avatars/src/Recording.cpp @@ -239,6 +239,7 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename) { if (i == 0 || frame._jointRotations[j] != previousFrame._jointRotations[j]) { writeQuat(stream, frame._jointRotations[j]); + // TODO -- handle translations mask.setBit(maskIndex); } maskIndex++; @@ -561,7 +562,9 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString frame._jointRotations[j] = previousFrame._jointRotations[j]; } } - + + // TODO -- handle translations + if (!mask[maskIndex++] || !readVec3(stream, frame._translation)) { frame._translation = previousFrame._translation; } @@ -670,7 +673,9 @@ RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QStr for (int i = 0; i < jointRotationSize; ++i) { fileStream >> baseFrame._jointRotations[i].x >> baseFrame._jointRotations[i].y >> baseFrame._jointRotations[i].z >> baseFrame._jointRotations[i].w; } - + + // TODO -- handle translations + fileStream >> baseFrame._translation.x >> baseFrame._translation.y >> baseFrame._translation.z; fileStream >> baseFrame._rotation.x >> baseFrame._rotation.y >> baseFrame._rotation.z >> baseFrame._rotation.w; fileStream >> baseFrame._scale; @@ -736,7 +741,9 @@ RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QStr frame._jointRotations[i] = previousFrame._jointRotations[i]; } } - + + // TODO -- handle translations + if (mask[maskIndex++]) { stream >> frame._translation.x >> frame._translation.y >> frame._translation.z; frame._translation = context.orientationInv * frame._translation; diff --git a/libraries/avatars/src/Recording.h b/libraries/avatars/src/Recording.h index 49d12ec5b5..3533af6535 100644 --- a/libraries/avatars/src/Recording.h +++ b/libraries/avatars/src/Recording.h @@ -83,6 +83,7 @@ class RecordingFrame { public: QVector getBlendshapeCoefficients() const { return _blendshapeCoefficients; } QVector getJointRotations() const { return _jointRotations; } + QVector getJointTranslations() const { return _jointTranslations; } glm::vec3 getTranslation() const { return _translation; } glm::quat getRotation() const { return _rotation; } float getScale() const { return _scale; } @@ -94,6 +95,7 @@ public: protected: void setBlendshapeCoefficients(QVector blendshapeCoefficients); void setJointRotations(QVector jointRotations) { _jointRotations = jointRotations; } + void setJointTranslations(QVector jointTranslations) { _jointTranslations = jointTranslations; } void setTranslation(const glm::vec3& translation) { _translation = translation; } void setRotation(const glm::quat& rotation) { _rotation = rotation; } void setScale(float scale) { _scale = scale; } @@ -105,6 +107,7 @@ protected: private: QVector _blendshapeCoefficients; QVector _jointRotations; + QVector _jointTranslations; glm::vec3 _translation; glm::quat _rotation; float _scale; @@ -124,4 +127,4 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename); RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString& filename); RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QString& filename, const QByteArray& byteArray); -#endif // hifi_Recording_h \ No newline at end of file +#endif // hifi_Recording_h diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index 384fa57b62..b602327f4c 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME display-plugins) +add_definitions(-DGLEW_STATIC) + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(OpenGL) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c978fab012..ed088bc9b5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -276,10 +276,13 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (jointsMapped()) { bool newFrame; - auto frameData = getAnimationFrame(newFrame); + QVector frameDataRotations; + QVector frameDataTranslations; + getAnimationFrame(newFrame, frameDataRotations, frameDataTranslations); + assert(frameDataRotations.size() == frameDataTranslations.size()); if (newFrame) { - for (int i = 0; i < frameData.size(); i++) { - _model->setJointState(i, true, frameData[i]); + for (int i = 0; i < frameDataRotations.size(); i++) { + _model->setJointState(i, true, frameDataRotations[i], frameDataTranslations[i], 1.0f); } } } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index e1e042f3d0..ef44ba5ab0 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -79,6 +79,7 @@ public: glm::mat4 localToVoxelMatrix() const; virtual ShapeType getShapeType() const; + virtual bool shouldBePhysical() const { return true; } virtual bool isReadyToComputeShape(); virtual void computeShapeInfo(ShapeInfo& info); diff --git a/libraries/entities/src/BoxEntityItem.h b/libraries/entities/src/BoxEntityItem.h index fdc66dd3e5..cc3bba4823 100644 --- a/libraries/entities/src/BoxEntityItem.h +++ b/libraries/entities/src/BoxEntityItem.h @@ -52,6 +52,7 @@ public: } virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } + virtual bool shouldBePhysical() const { return true; } virtual void debugDump() const; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 62b436b498..f070e2c1e4 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -334,7 +334,7 @@ public: bool getCollisionsWillMove() const { return _collisionsWillMove; } void setCollisionsWillMove(bool value) { _collisionsWillMove = value; } - virtual bool shouldBePhysical() const { return !_ignoreForCollisions; } + virtual bool shouldBePhysical() const { return false; } bool getLocked() const { return _locked; } void setLocked(bool value) { _locked = value; } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index b92ea34827..61e369cf96 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1573,14 +1573,17 @@ void EntityItemProperties::markAllChanged() { _accelerationSpreadChanged = true; _particleRadiusChanged = true; _radiusSpreadChanged = true; - _radiusStartChanged = true; - _radiusFinishChanged = true; _colorSpreadChanged = true; - _colorStartChanged = true; - _colorFinishChanged = true; _alphaSpreadChanged = true; - _alphaStartChanged = true; - _alphaFinishChanged = true; + + // Only mark the following as changed if their values are specified in the properties when the particle is created. If their + // values are specified then they are marked as changed in getChangedProperties(). + //_radiusStartChanged = true; + //_radiusFinishChanged = true; + //_colorStartChanged = true; + //_colorFinishChanged = true; + //_alphaStartChanged = true; + //_alphaFinishChanged = true; _marketplaceIDChanged = true; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 70747937d8..774441e567 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -218,18 +218,20 @@ void ModelEntityItem::mapJoints(const QStringList& modelJointNames) { } } -const QVector& ModelEntityItem::getAnimationFrame(bool& newFrame) { +void ModelEntityItem::getAnimationFrame(bool& newFrame, + QVector& rotationsResult, QVector& translationsResult) { newFrame = false; if (!hasAnimation() || !_jointMappingCompleted) { - return _lastKnownFrameData; + rotationsResult = _lastKnownFrameDataRotations; + translationsResult = _lastKnownFrameDataTranslations; } - + AnimationPointer myAnimation = getAnimation(_animationURL); // FIXME: this could be optimized if (myAnimation && myAnimation->isLoaded()) { - + const QVector& frames = myAnimation->getFramesReference(); // NOTE: getFrames() is too heavy - + int frameCount = frames.size(); if (frameCount > 0) { int animationFrameIndex = (int)(glm::floor(getAnimationFrameIndex())) % frameCount; @@ -240,20 +242,27 @@ const QVector& ModelEntityItem::getAnimationFrame(bool& newFrame) { if (animationFrameIndex != _lastKnownFrameIndex) { _lastKnownFrameIndex = animationFrameIndex; newFrame = true; - - const QVector& rotations = frames[animationFrameIndex].rotations; - _lastKnownFrameData.resize(_jointMapping.size()); + const QVector& rotations = frames[animationFrameIndex].rotations; + const QVector& translations = frames[animationFrameIndex].translations; + + _lastKnownFrameDataRotations.resize(_jointMapping.size()); + _lastKnownFrameDataTranslations.resize(_jointMapping.size()); for (int j = 0; j < _jointMapping.size(); j++) { - int rotationIndex = _jointMapping[j]; - if (rotationIndex != -1 && rotationIndex < rotations.size()) { - _lastKnownFrameData[j] = rotations[rotationIndex]; + int index = _jointMapping[j]; + if (index != -1 && index < rotations.size()) { + _lastKnownFrameDataRotations[j] = rotations[index]; + } + if (index != -1 && index < translations.size()) { + _lastKnownFrameDataTranslations[j] = translations[index]; } } } } } - return _lastKnownFrameData; + + rotationsResult = _lastKnownFrameDataRotations; + translationsResult = _lastKnownFrameDataTranslations; } bool ModelEntityItem::isAnimatingSomething() const { @@ -457,5 +466,5 @@ QString ModelEntityItem::getAnimationSettings() const { // virtual bool ModelEntityItem::shouldBePhysical() const { - return EntityItem::shouldBePhysical() && getShapeType() != SHAPE_TYPE_NONE; + return getShapeType() != SHAPE_TYPE_NONE; } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index bf6d7a9785..8bf6658bf3 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -106,7 +106,7 @@ public: float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } void mapJoints(const QStringList& modelJointNames); - const QVector& getAnimationFrame(bool& newFrame); + void getAnimationFrame(bool& newFrame, QVector& rotationsResult, QVector& translationsResult); bool jointsMapped() const { return _jointMappingCompleted; } bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); } @@ -123,7 +123,8 @@ public: static void cleanupLoadedAnimations(); protected: - QVector _lastKnownFrameData; + QVector _lastKnownFrameDataRotations; + QVector _lastKnownFrameDataTranslations; int _lastKnownFrameIndex; diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 74d39aa4af..6d5b8761b0 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -177,12 +177,12 @@ public: float getParticleRadius() const { return _particleRadius; } static const float DEFAULT_RADIUS_START; - bool _isRadiusStartInitialized; + bool _isRadiusStartInitialized = false; void setRadiusStart(float radiusStart) { _radiusStart = radiusStart; _isRadiusStartInitialized = true; } float getRadiusStart() const { return _isRadiusStartInitialized ? _radiusStart : _particleRadius; } static const float DEFAULT_RADIUS_FINISH; - bool _isRadiusFinishInitialized; + bool _isRadiusFinishInitialized = false; void setRadiusFinish(float radiusFinish) { _radiusFinish = radiusFinish; _isRadiusFinishInitialized = true; } float getRadiusFinish() const { return _isRadiusFinishInitialized ? _radiusFinish : _particleRadius; } diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index 19ea5d06f9..81a6cf704c 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -51,6 +51,7 @@ public: } virtual ShapeType getShapeType() const { return SHAPE_TYPE_SPHERE; } + virtual bool shouldBePhysical() const { return true; } virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index d6ec59499d..0c738c5a5b 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -530,6 +530,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QHash typeFlags; QHash localRotations; + QHash localTranslations; QHash xComponents; QHash yComponents; QHash zComponents; @@ -1112,16 +1113,16 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS normalTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type.contains("specular") || type.contains("reflection")) { specularTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - + } else if (type == "lcl rotation") { localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - + } else if (type == "lcl translation") { + localTranslations.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type == "d|x") { xComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type == "d|y") { yComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type == "d|z") { zComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); @@ -1232,6 +1233,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS for (int i = 0; i < frameCount; i++) { FBXAnimationFrame frame; frame.rotations.resize(modelIDs.size()); + frame.translations.resize(modelIDs.size()); geometry.animationFrames.append(frame); } @@ -1255,7 +1257,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS joint.freeLineage.append(index); } joint.freeLineage.remove(lastFreeIndex + 1, joint.freeLineage.size() - lastFreeIndex - 1); - joint.translation = model.translation; + joint.translation = model.translation; // these are usually in centimeters joint.preTransform = model.preTransform; joint.preRotation = model.preRotation; joint.rotation = model.rotation; @@ -1280,7 +1282,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = model.name; - + foreach (const QString& childID, _connectionChildMap.values(modelID)) { QString type = typeFlags.value(childID); if (!type.isEmpty()) { @@ -1293,17 +1295,29 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS geometry.joints.append(joint); geometry.jointIndices.insert(model.name, geometry.joints.size()); - + QString rotationID = localRotations.value(modelID); - AnimationCurve xCurve = animationCurves.value(xComponents.value(rotationID)); - AnimationCurve yCurve = animationCurves.value(yComponents.value(rotationID)); - AnimationCurve zCurve = animationCurves.value(zComponents.value(rotationID)); - glm::vec3 defaultValues = glm::degrees(safeEulerAngles(joint.rotation)); + AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID)); + AnimationCurve yRotCurve = animationCurves.value(yComponents.value(rotationID)); + AnimationCurve zRotCurve = animationCurves.value(zComponents.value(rotationID)); + + QString translationID = localTranslations.value(modelID); + AnimationCurve xPosCurve = animationCurves.value(xComponents.value(translationID)); + AnimationCurve yPosCurve = animationCurves.value(yComponents.value(translationID)); + AnimationCurve zPosCurve = animationCurves.value(zComponents.value(translationID)); + + glm::vec3 defaultRotValues = glm::degrees(safeEulerAngles(joint.rotation)); + glm::vec3 defaultPosValues = joint.translation; + for (int i = 0; i < frameCount; i++) { geometry.animationFrames[i].rotations[jointIndex] = glm::quat(glm::radians(glm::vec3( - xCurve.values.isEmpty() ? defaultValues.x : xCurve.values.at(i % xCurve.values.size()), - yCurve.values.isEmpty() ? defaultValues.y : yCurve.values.at(i % yCurve.values.size()), - zCurve.values.isEmpty() ? defaultValues.z : zCurve.values.at(i % zCurve.values.size())))); + xRotCurve.values.isEmpty() ? defaultRotValues.x : xRotCurve.values.at(i % xRotCurve.values.size()), + yRotCurve.values.isEmpty() ? defaultRotValues.y : yRotCurve.values.at(i % yRotCurve.values.size()), + zRotCurve.values.isEmpty() ? defaultRotValues.z : zRotCurve.values.at(i % zRotCurve.values.size())))); + geometry.animationFrames[i].translations[jointIndex] = glm::vec3( + xPosCurve.values.isEmpty() ? defaultPosValues.x : xPosCurve.values.at(i % xPosCurve.values.size()), + yPosCurve.values.isEmpty() ? defaultPosValues.y : yPosCurve.values.at(i % yPosCurve.values.size()), + zPosCurve.values.isEmpty() ? defaultPosValues.z : zPosCurve.values.at(i % zPosCurve.values.size())); } } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index da6df60f6d..2277d84d9c 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -211,8 +211,8 @@ public: /// A single animation frame extracted from an FBX document. class FBXAnimationFrame { public: - QVector rotations; + QVector translations; }; /// A light in an FBX document. diff --git a/libraries/gpu/src/gpu/DrawUnitQuadTexcoord.slv b/libraries/gpu/src/gpu/DrawUnitQuadTexcoord.slv new file mode 100644 index 0000000000..60ab0bd7dd --- /dev/null +++ b/libraries/gpu/src/gpu/DrawUnitQuadTexcoord.slv @@ -0,0 +1,28 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Draw the unit quad [-1,-1 -> 1,1] amd pass along the unit texcoords [0, 0 -> 1, 1]. Not transform used. +// Simply draw a Triangle_strip of 2 triangles, no input buffers or index buffer needed +// +// Created by Sam Gateau on 6/22/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +out vec2 varTexCoord0; + +void main(void) { + const vec4 UNIT_QUAD[4] = vec4[4]( + vec4(-1.0, -1.0, 0.0, 1.0), + vec4(1.0, -1.0, 0.0, 1.0), + vec4(-1.0, 1.0, 0.0, 1.0), + vec4(1.0, 1.0, 0.0, 1.0) + ); + vec4 pos = UNIT_QUAD[gl_VertexID]; + + varTexCoord0 = (pos.xy + 1) * 0.5; + + gl_Position = pos; +} diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index 96bd3d3002..d570007b3e 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -247,7 +247,13 @@ bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const For _bufferMask = ( _bufferMask & ~BUFFER_DEPTHSTENCIL); if (texture) { - _bufferMask |= BUFFER_DEPTHSTENCIL; + if (format.getSemantic() == gpu::DEPTH) { + _bufferMask |= BUFFER_DEPTH; + } else if (format.getSemantic() == gpu::STENCIL) { + _bufferMask |= BUFFER_STENCIL; + } else if (format.getSemantic() == gpu::DEPTH_STENCIL) { + _bufferMask |= BUFFER_DEPTHSTENCIL; + } } return true; diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index 310255af9f..83ff8fbb23 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -116,6 +116,8 @@ public: bool isEmpty() const { return (_bufferMask == 0); } bool hasColor() const { return (getBufferMask() & BUFFER_COLORS); } bool hasDepthStencil() const { return (getBufferMask() & BUFFER_DEPTHSTENCIL); } + bool hasDepth() const { return (getBufferMask() & BUFFER_DEPTH); } + bool hasStencil() const { return (getBufferMask() & BUFFER_STENCIL); } bool validateTargetCompatibility(const Texture& texture, uint32 subresource = 0) const; diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index 250fc4aaac..b1e63a18bd 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -132,20 +132,26 @@ void GLBackend::renderPassTransfer(Batch& batch) { const size_t numCommands = batch.getCommands().size(); const Batch::Commands::value_type* command = batch.getCommands().data(); const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data(); - - for (auto& cached : batch._buffers._items) { - if (cached._data) { - syncGPUObject(*cached._data); + + { // Sync all the buffers + PROFILE_RANGE("syncGPUBuffer"); + + for (auto& cached : batch._buffers._items) { + if (cached._data) { + syncGPUObject(*cached._data); + } } } - // Reset the transform buffers - _transform._cameras.resize(0); - _transform._cameraOffsets.clear(); - _transform._objects.resize(0); - _transform._objectOffsets.clear(); - for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) { - switch (*command) { + { // Sync all the buffers + PROFILE_RANGE("syncCPUTransform"); + _transform._cameras.resize(0); + _transform._cameraOffsets.clear(); + _transform._objects.resize(0); + _transform._objectOffsets.clear(); + + for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) { + switch (*command) { case Batch::COMMAND_draw: case Batch::COMMAND_drawIndexed: case Batch::COMMAND_drawInstanced: @@ -164,11 +170,16 @@ void GLBackend::renderPassTransfer(Batch& batch) { default: break; + } + command++; + offset++; } - command++; - offset++; } - _transform.transfer(); + + { // Sync the transform buffers + PROFILE_RANGE("syncGPUTransform"); + _transform.transfer(); + } } void GLBackend::renderPassDraw(Batch& batch) { diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp index c3f61a67c3..70e4b18b0f 100755 --- a/libraries/gpu/src/gpu/GLBackendOutput.cpp +++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp @@ -100,7 +100,18 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe if (surface) { auto gltexture = GLBackend::syncGPUObject(*surface); if (gltexture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, gltexture->_texture, 0); + GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT; + if (!framebuffer.hasStencil()) { + attachement = GL_DEPTH_ATTACHMENT; + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + } else if (!framebuffer.hasDepth()) { + attachement = GL_STENCIL_ATTACHMENT; + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + } else { + attachement = GL_DEPTH_STENCIL_ATTACHMENT; + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachement, GL_RENDERBUFFER, gltexture->_texture); + } + (void) CHECK_GL_ERROR(); } } } diff --git a/libraries/gpu/src/gpu/GLBackendState.cpp b/libraries/gpu/src/gpu/GLBackendState.cpp index 9fdcbc0870..895d0a0027 100644 --- a/libraries/gpu/src/gpu/GLBackendState.cpp +++ b/libraries/gpu/src/gpu/GLBackendState.cpp @@ -642,8 +642,13 @@ void GLBackend::do_setStateStencil(State::StencilActivation activation, State::S if (activation.isEnabled()) { glEnable(GL_STENCIL_TEST); - glStencilMaskSeparate(GL_FRONT, activation.getWriteMaskFront()); - glStencilMaskSeparate(GL_BACK, activation.getWriteMaskBack()); + + if (activation.getWriteMaskFront() != activation.getWriteMaskBack()) { + glStencilMaskSeparate(GL_FRONT, activation.getWriteMaskFront()); + glStencilMaskSeparate(GL_BACK, activation.getWriteMaskBack()); + } else { + glStencilMask(activation.getWriteMaskFront()); + } static GLenum STENCIL_OPS[] = { GL_KEEP, @@ -655,11 +660,16 @@ void GLBackend::do_setStateStencil(State::StencilActivation activation, State::S GL_INCR, GL_DECR }; - glStencilFuncSeparate(GL_FRONT, STENCIL_OPS[frontTest.getFailOp()], STENCIL_OPS[frontTest.getPassOp()], STENCIL_OPS[frontTest.getDepthFailOp()]); - glStencilFuncSeparate(GL_FRONT, GL_COMPARISON_FUNCTIONS[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); + if (frontTest != backTest) { + glStencilOpSeparate(GL_FRONT, STENCIL_OPS[frontTest.getFailOp()], STENCIL_OPS[frontTest.getPassOp()], STENCIL_OPS[frontTest.getDepthFailOp()]); + glStencilFuncSeparate(GL_FRONT, GL_COMPARISON_FUNCTIONS[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); - glStencilFuncSeparate(GL_BACK, STENCIL_OPS[backTest.getFailOp()], STENCIL_OPS[backTest.getPassOp()], STENCIL_OPS[backTest.getDepthFailOp()]); - glStencilFuncSeparate(GL_BACK, GL_COMPARISON_FUNCTIONS[backTest.getFunction()], backTest.getReference(), backTest.getReadMask()); + glStencilOpSeparate(GL_BACK, STENCIL_OPS[backTest.getFailOp()], STENCIL_OPS[backTest.getPassOp()], STENCIL_OPS[backTest.getDepthFailOp()]); + glStencilFuncSeparate(GL_BACK, GL_COMPARISON_FUNCTIONS[backTest.getFunction()], backTest.getReference(), backTest.getReadMask()); + } else { + glStencilOp(STENCIL_OPS[frontTest.getFailOp()], STENCIL_OPS[frontTest.getPassOp()], STENCIL_OPS[frontTest.getDepthFailOp()]); + glStencilFunc(GL_COMPARISON_FUNCTIONS[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); + } } else { glDisable(GL_STENCIL_TEST); } diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index dce5236868..ee5e1d3bc6 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -66,7 +66,9 @@ public: texel.internalFormat = GL_RG; break; case gpu::DEPTH_STENCIL: - texel.internalFormat = GL_DEPTH_STENCIL; + texel.type = GL_UNSIGNED_BYTE; + texel.format = GL_DEPTH_STENCIL; + texel.internalFormat = GL_DEPTH24_STENCIL8; break; default: qCDebug(gpulogging) << "Unknown combination of texel format"; @@ -197,7 +199,9 @@ public: texel.internalFormat = GL_RG; break; case gpu::DEPTH_STENCIL: - texel.internalFormat = GL_DEPTH_STENCIL; + texel.type = GL_UNSIGNED_BYTE; + texel.format = GL_DEPTH_STENCIL; + texel.internalFormat = GL_DEPTH24_STENCIL8; break; default: qCDebug(gpulogging) << "Unknown combination of texel format"; @@ -334,22 +338,34 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { } GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); - - glTexImage2D(GL_TEXTURE_2D, 0, - texelFormat.internalFormat, texture.getWidth(), texture.getHeight(), 0, - texelFormat.format, texelFormat.type, bytes); - if (bytes && texture.isAutogenerateMips()) { - glGenerateMipmap(GL_TEXTURE_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - }/* else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - }*/ - - object->_target = GL_TEXTURE_2D; + auto semantic = texture.getTexelFormat().getSemantic(); - syncSampler(texture.getSampler(), texture.getType(), object); + if (semantic == gpu::DEPTH_STENCIL) { + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &object->_texture); + + glGenRenderbuffers(1, &object->_texture); + glBindRenderbuffer(GL_RENDERBUFFER, object->_texture); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, texture.getWidth(), texture.getHeight()); + // At this point the mip pixels have been loaded, we can notify + texture.notifyMipFaceGPULoaded(0, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + } else { + glTexImage2D(GL_TEXTURE_2D, 0, + texelFormat.internalFormat, texture.getWidth(), texture.getHeight(), 0, + texelFormat.format, texelFormat.type, bytes); + + if (bytes && texture.isAutogenerateMips()) { + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + + object->_target = GL_TEXTURE_2D; + + syncSampler(texture.getSampler(), texture.getType(), object); + } // At this point the mip pixels have been loaded, we can notify texture.notifyMipFaceGPULoaded(0, 0); diff --git a/libraries/gpu/src/gpu/GLBackendTransform.cpp b/libraries/gpu/src/gpu/GLBackendTransform.cpp index 4a55155a86..5e16421c6a 100755 --- a/libraries/gpu/src/gpu/GLBackendTransform.cpp +++ b/libraries/gpu/src/gpu/GLBackendTransform.cpp @@ -130,19 +130,27 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo void GLBackend::TransformStageState::transfer() const { static QByteArray bufferData; - glBindBuffer(GL_UNIFORM_BUFFER, _cameraBuffer); - bufferData.resize(_cameraUboSize * _cameras.size()); - for (size_t i = 0; i < _cameras.size(); ++i) { - memcpy(bufferData.data() + (_cameraUboSize * i), &_cameras[i], sizeof(TransformCamera)); + if (!_cameras.empty()) { + glBindBuffer(GL_UNIFORM_BUFFER, _cameraBuffer); + bufferData.resize(_cameraUboSize * _cameras.size()); + for (size_t i = 0; i < _cameras.size(); ++i) { + memcpy(bufferData.data() + (_cameraUboSize * i), &_cameras[i], sizeof(TransformCamera)); + } + glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); } - glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, _objectBuffer); - bufferData.resize(_objectUboSize * _objects.size()); - for (size_t i = 0; i < _objects.size(); ++i) { - memcpy(bufferData.data() + (_objectUboSize * i), &_objects[i], sizeof(TransformObject)); + + if (!_objects.empty()) { + glBindBuffer(GL_UNIFORM_BUFFER, _objectBuffer); + bufferData.resize(_objectUboSize * _objects.size()); + for (size_t i = 0; i < _objects.size(); ++i) { + memcpy(bufferData.data() + (_objectUboSize * i), &_objects[i], sizeof(TransformObject)); + } + glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + } + + if (!_cameras.empty() || !_objects.empty()) { + glBindBuffer(GL_UNIFORM_BUFFER, 0); } - glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/StandardShaderLib.cpp b/libraries/gpu/src/gpu/StandardShaderLib.cpp index 3f27a7fc35..864bff08c9 100755 --- a/libraries/gpu/src/gpu/StandardShaderLib.cpp +++ b/libraries/gpu/src/gpu/StandardShaderLib.cpp @@ -12,6 +12,7 @@ // #include "StandardShaderLib.h" +#include "DrawUnitQuadTexcoord_vert.h" #include "DrawTransformUnitQuad_vert.h" #include "DrawTexcoordRectTransformUnitQuad_vert.h" #include "DrawViewportQuadTransformTexcoord_vert.h" @@ -21,6 +22,7 @@ using namespace gpu; +ShaderPointer StandardShaderLib::_drawUnitQuadTexcoordVS; ShaderPointer StandardShaderLib::_drawTransformUnitQuadVS; ShaderPointer StandardShaderLib::_drawTexcoordRectTransformUnitQuadVS; ShaderPointer StandardShaderLib::_drawViewportQuadTransformTexcoordVS; @@ -55,6 +57,12 @@ ShaderPointer StandardShaderLib::getProgram(GetShader getVS, GetShader getPS) { } +ShaderPointer StandardShaderLib::getDrawUnitQuadTexcoordVS() { + if (!_drawUnitQuadTexcoordVS) { + _drawUnitQuadTexcoordVS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(DrawUnitQuadTexcoord_vert))); + } + return _drawUnitQuadTexcoordVS; +} ShaderPointer StandardShaderLib::getDrawTransformUnitQuadVS() { if (!_drawTransformUnitQuadVS) { diff --git a/libraries/gpu/src/gpu/StandardShaderLib.h b/libraries/gpu/src/gpu/StandardShaderLib.h index 2d9c168473..12ea9045c2 100755 --- a/libraries/gpu/src/gpu/StandardShaderLib.h +++ b/libraries/gpu/src/gpu/StandardShaderLib.h @@ -23,6 +23,9 @@ namespace gpu { class StandardShaderLib { public: + // Shader draws the unit quad in the full viewport clipPos = ([(-1,-1),(1,1)]) and the unit texcoord = [(0,0),(1,1)]. + static ShaderPointer getDrawUnitQuadTexcoordVS(); + // Shader draw the unit quad objectPos = ([(-1,-1),(1,1)]) and transform it by the full model transform stack (Model, View, Proj). // A texcoord attribute is also generated texcoord = [(0,0),(1,1)] static ShaderPointer getDrawTransformUnitQuadVS(); @@ -44,6 +47,7 @@ public: protected: + static ShaderPointer _drawUnitQuadTexcoordVS; static ShaderPointer _drawTransformUnitQuadVS; static ShaderPointer _drawTexcoordRectTransformUnitQuadVS; static ShaderPointer _drawViewportQuadTransformTexcoordVS; diff --git a/libraries/gpu/src/gpu/State.h b/libraries/gpu/src/gpu/State.h index 5500f20e06..7740506bce 100755 --- a/libraries/gpu/src/gpu/State.h +++ b/libraries/gpu/src/gpu/State.h @@ -143,11 +143,11 @@ public: static const int PASS_OP_OFFSET = 12; uint16 _functionAndOperations; - uint8 _reference = 0; + int8 _reference = 0; uint8 _readMask = 0xff; public: - StencilTest(uint8 reference = 0, uint8 readMask =0xFF, ComparisonFunction func = ALWAYS, StencilOp failOp = STENCIL_OP_KEEP, StencilOp depthFailOp = STENCIL_OP_KEEP, StencilOp passOp = STENCIL_OP_KEEP) : + StencilTest(int8 reference = 0, uint8 readMask =0xFF, ComparisonFunction func = ALWAYS, StencilOp failOp = STENCIL_OP_KEEP, StencilOp depthFailOp = STENCIL_OP_KEEP, StencilOp passOp = STENCIL_OP_KEEP) : _functionAndOperations(func | (failOp << FAIL_OP_OFFSET) | (depthFailOp << DEPTH_FAIL_OP_OFFSET) | (passOp << PASS_OP_OFFSET)), _reference(reference), _readMask(readMask) {} @@ -157,7 +157,7 @@ public: StencilOp getDepthFailOp() const { return StencilOp((_functionAndOperations & DEPTH_FAIL_OP_MASK) >> DEPTH_FAIL_OP_OFFSET); } StencilOp getPassOp() const { return StencilOp((_functionAndOperations & PASS_OP_MASK) >> PASS_OP_OFFSET); } - uint8 getReference() const { return _reference; } + int8 getReference() const { return _reference; } uint8 getReadMask() const { return _readMask; } int32 getRaw() const { return *(reinterpret_cast(this)); } diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index e27a0d25ce..21b40a54c8 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -21,6 +21,8 @@ using namespace model; Skybox::Skybox() { + Data data; + _dataBuffer = gpu::BufferView(std::make_shared(sizeof(Data), (const gpu::Byte*) &data)); /* // PLease create a default engineer skybox _cubemap.reset( gpu::Texture::createCube(gpu::Element::COLOR_RGBA_32, 1)); @@ -36,7 +38,7 @@ Skybox::Skybox() { } void Skybox::setColor(const Color& color) { - _color = color; + _dataBuffer.edit()._color = color; } void Skybox::setCubemap(const gpu::TexturePointer& cubemap) { @@ -44,12 +46,40 @@ void Skybox::setCubemap(const gpu::TexturePointer& cubemap) { } +void Skybox::updateDataBuffer() const { + auto blend = 0.0f; + if (getCubemap() && getCubemap()->isDefined()) { + blend = 1.0f; + // If pitch black neutralize the color + if (glm::all(glm::equal(getColor(), glm::vec3(0.0f)))) { + blend = 2.0f; + } + } + + if (blend != _dataBuffer.get()._blend) { + _dataBuffer.edit()._blend = blend; + } +} + + + +void Skybox::render(gpu::Batch& batch, const ViewFrustum& frustum) const { + updateDataBuffer(); + Skybox::render(batch, frustum, (*this)); +} + + void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox) { + // Create the static shared elements used to render the skybox static gpu::BufferPointer theBuffer; static gpu::Stream::FormatPointer theFormat; - - if (skybox.getCubemap()) { - if (!theBuffer) { + static gpu::BufferPointer theConstants; + static gpu::PipelinePointer thePipeline; + const int SKYBOX_SKYMAP_SLOT = 0; + const int SKYBOX_CONSTANTS_SLOT = 0; + static std::once_flag once; + std::call_once(once, [&] { + { const float CLIP = 1.0f; const glm::vec2 vertices[4] = { { -CLIP, -CLIP }, { CLIP, -CLIP }, { -CLIP, CLIP }, { CLIP, CLIP } }; theBuffer = std::make_shared(sizeof(vertices), (const gpu::Byte*) vertices); @@ -57,62 +87,49 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky theFormat->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ)); } - glm::mat4 projMat; - viewFrustum.evalProjectionMatrix(projMat); + { + auto skyVS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(Skybox_vert))); + auto skyFS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(Skybox_frag))); + auto skyShader = gpu::ShaderPointer(gpu::Shader::createProgram(skyVS, skyFS)); - Transform viewTransform; - viewFrustum.evalViewTransform(viewTransform); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewTransform); - batch.setModelTransform(Transform()); // only for Mac - batch.setInputBuffer(gpu::Stream::POSITION, theBuffer, 0, 8); - batch.setInputFormat(theFormat); + gpu::Shader::BindingSet bindings; + bindings.insert(gpu::Shader::Binding(std::string("cubeMap"), SKYBOX_SKYMAP_SLOT)); + bindings.insert(gpu::Shader::Binding(std::string("skyboxBuffer"), SKYBOX_CONSTANTS_SLOT)); + if (!gpu::Shader::makeProgram(*skyShader, bindings)) { - if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { - static gpu::BufferPointer theConstants; - static gpu::PipelinePointer thePipeline; - static int SKYBOX_CONSTANTS_SLOT = 0; // need to be defined by the compilation of the shader - if (!thePipeline) { - auto skyVS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(Skybox_vert))); - auto skyFS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(Skybox_frag))); - auto skyShader = gpu::ShaderPointer(gpu::Shader::createProgram(skyVS, skyFS)); - - gpu::Shader::BindingSet bindings; - bindings.insert(gpu::Shader::Binding(std::string("cubeMap"), 0)); - if (!gpu::Shader::makeProgram(*skyShader, bindings)) { - - } - - SKYBOX_CONSTANTS_SLOT = skyShader->getBuffers().findLocation("skyboxBuffer"); - if (SKYBOX_CONSTANTS_SLOT == gpu::Shader::INVALID_LOCATION) { - SKYBOX_CONSTANTS_SLOT = skyShader->getUniforms().findLocation("skyboxBuffer"); - } - - auto skyState = std::make_shared(); - - thePipeline = gpu::PipelinePointer(gpu::Pipeline::create(skyShader, skyState)); - - auto color = glm::vec4(1.0f); - theConstants = std::make_shared(sizeof(color), (const gpu::Byte*) &color); } - if (glm::all(glm::equal(skybox.getColor(), glm::vec3(0.0f)))) { - auto color = glm::vec4(1.0f); - theConstants->setSubData(0, sizeof(color), (const gpu::Byte*) &color); - } else { - theConstants->setSubData(0, sizeof(Color), (const gpu::Byte*) &skybox.getColor()); - } + auto skyState = std::make_shared(); + skyState->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)); - batch.setPipeline(thePipeline); - batch.setUniformBuffer(SKYBOX_CONSTANTS_SLOT, theConstants, 0, theConstants->getSize()); - batch.setResourceTexture(0, skybox.getCubemap()); - batch.draw(gpu::TRIANGLE_STRIP, 4); + thePipeline = gpu::PipelinePointer(gpu::Pipeline::create(skyShader, skyState)); } + }); - } else { - // skybox has no cubemap, just clear the color buffer - auto color = skybox.getColor(); - batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(color, 0.0f), 0.0f, 0, true); + // Render + glm::mat4 projMat; + viewFrustum.evalProjectionMatrix(projMat); + + Transform viewTransform; + viewFrustum.evalViewTransform(viewTransform); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewTransform); + batch.setModelTransform(Transform()); // only for Mac + batch.setInputBuffer(gpu::Stream::POSITION, theBuffer, 0, 8); + batch.setInputFormat(theFormat); + + gpu::TexturePointer skymap; + if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { + skymap = skybox.getCubemap(); } + + batch.setPipeline(thePipeline); + batch.setUniformBuffer(SKYBOX_CONSTANTS_SLOT, skybox._dataBuffer); + batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, skymap); + + batch.draw(gpu::TRIANGLE_STRIP, 4); + + batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, nullptr); + } diff --git a/libraries/model/src/model/Skybox.h b/libraries/model/src/model/Skybox.h index e9f95afa16..14ba9fa005 100755 --- a/libraries/model/src/model/Skybox.h +++ b/libraries/model/src/model/Skybox.h @@ -30,20 +30,28 @@ public: virtual ~Skybox() {}; void setColor(const Color& color); - const Color& getColor() const { return _color; } + const Color getColor() const { return _dataBuffer.get()._color; } void setCubemap(const gpu::TexturePointer& cubemap); const gpu::TexturePointer& getCubemap() const { return _cubemap; } - virtual void render(gpu::Batch& batch, const ViewFrustum& frustum) const { - render(batch, frustum, (*this)); - } + virtual void render(gpu::Batch& batch, const ViewFrustum& frustum) const; + static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox); protected: gpu::TexturePointer _cubemap; - Color _color{1.0f, 1.0f, 1.0f}; + + class Data { + public: + glm::vec3 _color{ 1.0f, 1.0f, 1.0f }; + float _blend = 1.0f; + }; + + mutable gpu::BufferView _dataBuffer; + + void updateDataBuffer() const; }; typedef std::shared_ptr< Skybox > SkyboxPointer; diff --git a/libraries/model/src/model/Skybox.slf b/libraries/model/src/model/Skybox.slf index 382801f52d..6246bbd9d3 100755 --- a/libraries/model/src/model/Skybox.slf +++ b/libraries/model/src/model/Skybox.slf @@ -40,8 +40,18 @@ void main(void) { #else vec3 coord = normalize(_normal); - vec3 texel = texture(cubeMap, coord).rgb; - vec3 color = texel * _skybox._color.rgb; + + // Skybox color or blend with skymap + vec3 color = _skybox._color.rgb; + if (_skybox._color.a > 0.0) { + vec3 texel = texture(cubeMap, coord).rgb; + if (_skybox._color.a < 2.0) { + color *= texel; + } else { + color = texel; + } + } + vec3 pixel = pow(color, vec3(1.0/2.2)); // manual Gamma correction _fragColor = vec4(pixel, 0.0); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index a8cf743686..ca75a86158 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -39,6 +39,9 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: return VERSION_ENTITIES_PARTICLE_ELLIPSOID_EMITTER; + case PacketType::AvatarData: + case PacketType::BulkAvatarData: + return 15; default: return 14; } diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index ff546dbb0b..9cbe89dda6 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -596,6 +596,12 @@ QString EntityMotionState::getName() { // virtual int16_t EntityMotionState::computeCollisionGroup() { + if (!_entity) { + return COLLISION_GROUP_STATIC; + } + if (_entity->getIgnoreForCollisions()) { + return COLLISION_GROUP_COLLISIONLESS; + } switch (computeObjectMotionType()){ case MOTION_TYPE_STATIC: return COLLISION_GROUP_STATIC; diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 1bdf8b6372..61254e49bd 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -37,11 +37,11 @@ enum MotionStateType { // The update flags trigger two varieties of updates: "hard" which require the body to be pulled // and re-added to the physics engine and "easy" which just updates the body properties. -const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE); +const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE | + EntityItem::DIRTY_COLLISION_GROUP); const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES | - EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | - EntityItem::DIRTY_MATERIAL | EntityItem::DIRTY_SIMULATOR_ID | - EntityItem::DIRTY_SIMULATOR_OWNERSHIP); + EntityItem::DIRTY_MASS | EntityItem::DIRTY_MATERIAL | + EntityItem::DIRTY_SIMULATOR_ID | EntityItem::DIRTY_SIMULATOR_OWNERSHIP); // These are the set of incoming flags that the PhysicsEngine needs to hear about: const uint32_t DIRTY_PHYSICS_FLAGS = (uint32_t)(HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS | diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 8d34f0e7e5..1c7e7e457c 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -32,7 +32,8 @@ void ProceduralSkybox::setProcedural(const ProceduralPointer& procedural) { if (_procedural) { _procedural->_vertexSource = ProceduralSkybox_vert; _procedural->_fragmentSource = ProceduralSkybox_frag; - // No pipeline state customization + // Adjust the pipeline state for background using the stencil test + _procedural->_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)); } } @@ -42,6 +43,7 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& frustum) con void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const ProceduralSkybox& skybox) { if (!(skybox._procedural)) { + skybox.updateDataBuffer(); Skybox::render(batch, viewFrustum, skybox); } diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 6a0f69dd8d..fa6cc6fe77 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -20,6 +20,8 @@ add_dependency_external_projects(oglplus) find_package(OGLPLUS REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS}) +add_definitions(-DGLEW_STATIC) + if (WIN32) if (USE_NSIGHT) # try to find the Nsight package and add it to the build if we find it diff --git a/libraries/render-utils/src/Environment.cpp b/libraries/render-utils/src/Environment.cpp index 7fbd89acc1..b26d402fa3 100644 --- a/libraries/render-utils/src/Environment.cpp +++ b/libraries/render-utils/src/Environment.cpp @@ -62,7 +62,7 @@ void Environment::setupAtmosphereProgram(const char* vertSource, const char* fra auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_NONE); - state->setDepthTest(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, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); diff --git a/libraries/render-utils/src/FramebufferCache.cpp b/libraries/render-utils/src/FramebufferCache.cpp index d6ebd001d2..cd81a21f9a 100644 --- a/libraries/render-utils/src/FramebufferCache.cpp +++ b/libraries/render-utils/src/FramebufferCache.cpp @@ -35,7 +35,9 @@ void FramebufferCache::setFrameBufferSize(QSize frameBufferSize) { _frameBufferSize = frameBufferSize; _primaryFramebufferFull.reset(); _primaryFramebufferDepthColor.reset(); + _primaryFramebufferStencilColor.reset(); _primaryDepthTexture.reset(); + _primaryStencilTexture.reset(); _primaryColorTexture.reset(); _primaryNormalTexture.reset(); _primarySpecularTexture.reset(); @@ -47,6 +49,7 @@ void FramebufferCache::setFrameBufferSize(QSize frameBufferSize) { void FramebufferCache::createPrimaryFramebuffer() { _primaryFramebufferFull = gpu::FramebufferPointer(gpu::Framebuffer::create()); _primaryFramebufferDepthColor = gpu::FramebufferPointer(gpu::Framebuffer::create()); + _primaryFramebufferStencilColor = gpu::FramebufferPointer(gpu::Framebuffer::create()); auto colorFormat = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); auto width = _frameBufferSize.width(); @@ -63,12 +66,19 @@ void FramebufferCache::createPrimaryFramebuffer() { _primaryFramebufferDepthColor->setRenderBuffer(0, _primaryColorTexture); + _primaryFramebufferStencilColor->setRenderBuffer(0, _primaryColorTexture); + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); _primaryDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, width, height, defaultSampler)); + auto stencilFormat = gpu::Element(gpu::VEC2, gpu::UINT32, gpu::DEPTH_STENCIL); + _primaryStencilTexture = gpu::TexturePointer(gpu::Texture::create2D(stencilFormat, width, height, defaultSampler)); + _primaryFramebufferFull->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); _primaryFramebufferDepthColor->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + + _primaryFramebufferStencilColor->setDepthStencilBuffer(_primaryStencilTexture, stencilFormat); _selfieFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); auto tex = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width * 0.5, height * 0.5, defaultSampler)); @@ -89,6 +99,12 @@ gpu::FramebufferPointer FramebufferCache::getPrimaryFramebufferDepthColor() { return _primaryFramebufferDepthColor; } +gpu::FramebufferPointer FramebufferCache::getPrimaryFramebufferStencilColor() { + if (!_primaryFramebufferStencilColor) { + createPrimaryFramebuffer(); + } + return _primaryFramebufferStencilColor; +} gpu::TexturePointer FramebufferCache::getPrimaryDepthTexture() { if (!_primaryDepthTexture) { @@ -97,6 +113,13 @@ gpu::TexturePointer FramebufferCache::getPrimaryDepthTexture() { return _primaryDepthTexture; } +gpu::TexturePointer FramebufferCache::getPrimaryStencilTexture() { + if (!_primaryStencilTexture) { + createPrimaryFramebuffer(); + } + return _primaryStencilTexture; +} + gpu::TexturePointer FramebufferCache::getPrimaryColorTexture() { if (!_primaryColorTexture) { createPrimaryFramebuffer(); diff --git a/libraries/render-utils/src/FramebufferCache.h b/libraries/render-utils/src/FramebufferCache.h index c2274a77e8..8951ceee80 100644 --- a/libraries/render-utils/src/FramebufferCache.h +++ b/libraries/render-utils/src/FramebufferCache.h @@ -31,8 +31,10 @@ public: /// used for scene rendering. gpu::FramebufferPointer getPrimaryFramebuffer(); gpu::FramebufferPointer getPrimaryFramebufferDepthColor(); + gpu::FramebufferPointer getPrimaryFramebufferStencilColor(); gpu::TexturePointer getPrimaryDepthTexture(); + gpu::TexturePointer getPrimaryStencilTexture(); gpu::TexturePointer getPrimaryColorTexture(); gpu::TexturePointer getPrimaryNormalTexture(); gpu::TexturePointer getPrimarySpecularTexture(); @@ -58,7 +60,9 @@ private: gpu::FramebufferPointer _primaryFramebufferFull; gpu::FramebufferPointer _primaryFramebufferDepthColor; + gpu::FramebufferPointer _primaryFramebufferStencilColor; gpu::TexturePointer _primaryDepthTexture; + gpu::TexturePointer _primaryStencilTexture; gpu::TexturePointer _primaryColorTexture; gpu::TexturePointer _primaryNormalTexture; gpu::TexturePointer _primarySpecularTexture; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 90dbe6524c..e05ac15ca2 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1001,8 +1001,16 @@ void Model::clearJointState(int index) { _rig->clearJointState(index); } -void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) { - _rig->setJointState(index, valid, rotation, priority); +void Model::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { + _rig->setJointState(index, valid, rotation, translation, priority); +} + +void Model::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { + _rig->setJointRotation(index, valid, rotation, priority); +} + +void Model::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { + _rig->setJointTranslation(index, valid, translation, priority); } int Model::getParentJointIndex(int jointIndex) const { @@ -1074,6 +1082,10 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const { return _rig->getJointRotation(jointIndex, rotation); } +bool Model::getJointTranslation(int jointIndex, glm::vec3& translation) const { + return _rig->getJointTranslation(jointIndex, translation); +} + bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const { return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 2620310857..093c22938e 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -126,7 +126,9 @@ public: QStringList getJointNames() const; /// Sets the joint state at the specified index. - void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f); + void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority); + void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); + void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, @@ -161,6 +163,7 @@ public: /// \param rotation[out] rotation of joint in model-frame /// \return true if joint exists bool getJointRotation(int jointIndex, glm::quat& rotation) const; + bool getJointTranslation(int jointIndex, glm::vec3& translation) const; /// Returns the index of the parent of the indexed joint, or -1 if not found. int getParentJointIndex(int jointIndex) const; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 65f77689c3..0f79dc8b8d 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "FramebufferCache.h" #include "DeferredLightingEffect.h" @@ -28,21 +29,27 @@ #include "overlay3D_vert.h" #include "overlay3D_frag.h" +#include "drawOpaqueStencil_frag.h" + using namespace render; void SetupDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { RenderArgs* args = renderContext->args; gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + auto primaryFboStencil = DependencyManager::get()->getPrimaryFramebufferStencilColor(); auto primaryFbo = DependencyManager::get()->getPrimaryFramebufferDepthColor(); batch.enableStereo(false); - batch.setFramebuffer(nullptr); - batch.setFramebuffer(primaryFbo); - batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); + batch.setFramebuffer(primaryFboStencil); + batch.clearFramebuffer( + gpu::Framebuffer::BUFFER_STENCIL, + vec4(vec3(0), 1), 1.0, 0.0, true); + + batch.setFramebuffer(primaryFbo); batch.clearFramebuffer( gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH, @@ -65,7 +72,6 @@ void ResolveDeferred::run(const SceneContextPointer& sceneContext, const RenderC RenderDeferredTask::RenderDeferredTask() : Task() { _jobs.push_back(Job(new SetupDeferred::JobModel("SetupFramebuffer"))); - _jobs.push_back(Job(new DrawBackground::JobModel("DrawBackground"))); _jobs.push_back(Job(new PrepareDeferred::JobModel("PrepareDeferred"))); _jobs.push_back(Job(new FetchItems::JobModel("FetchOpaque", @@ -79,7 +85,10 @@ RenderDeferredTask::RenderDeferredTask() : Task() { _jobs.push_back(Job(new DepthSortItems::JobModel("DepthSortOpaque", _jobs.back().getOutput()))); auto& renderedOpaques = _jobs.back().getOutput(); _jobs.push_back(Job(new DrawOpaqueDeferred::JobModel("DrawOpaqueDeferred", _jobs.back().getOutput()))); - + + _jobs.push_back(Job(new DrawStencilDeferred::JobModel("DrawOpaqueStencil"))); + _jobs.push_back(Job(new DrawBackgroundDeferred::JobModel("DrawBackgroundDeferred"))); + _jobs.push_back(Job(new DrawLight::JobModel("DrawLight"))); _jobs.push_back(Job(new RenderDeferred::JobModel("RenderDeferred"))); _jobs.push_back(Job(new ResolveDeferred::JobModel("ResolveDeferred"))); @@ -226,7 +235,9 @@ const gpu::PipelinePointer& DrawOverlay3D::getOpaquePipeline() { auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); auto state = std::make_shared(); - state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setDepthTest(false); + // additive blending + state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); _opaquePipeline.reset(gpu::Pipeline::create(program, state)); } @@ -289,3 +300,95 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon args->_whiteTexture.reset(); } } + +gpu::PipelinePointer DrawStencilDeferred::_opaquePipeline; +const gpu::PipelinePointer& DrawStencilDeferred::getOpaquePipeline() { + if (!_opaquePipeline) { + const gpu::int8 STENCIL_OPAQUE = 1; + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(drawOpaqueStencil_frag))); + auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + + + gpu::Shader::makeProgram((*program)); + + auto state = std::make_shared(); + state->setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_OPAQUE, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE)); + state->setColorWriteMask(0); + + _opaquePipeline.reset(gpu::Pipeline::create(program, state)); + } + return _opaquePipeline; +} + +void DrawStencilDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { + assert(renderContext->args); + assert(renderContext->args->_viewFrustum); + + // from the touched pixel generate the stencil buffer + RenderArgs* args = renderContext->args; + doInBatch(args->_context, [=](gpu::Batch& batch) { + args->_batch = &batch; + + auto primaryFboColorDepthStencil = DependencyManager::get()->getPrimaryFramebufferStencilColor(); + auto primaryDepth = DependencyManager::get()->getPrimaryDepthTexture(); + + batch.enableStereo(false); + + batch.setFramebuffer(primaryFboColorDepthStencil); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + batch.setPipeline(getOpaquePipeline()); + batch.setResourceTexture(0, primaryDepth); + + batch.draw(gpu::TRIANGLE_STRIP, 4); + batch.setResourceTexture(0, nullptr); + + }); + args->_batch = nullptr; +} + +void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { + assert(renderContext->args); + assert(renderContext->args->_viewFrustum); + + // render backgrounds + auto& scene = sceneContext->_scene; + auto& items = scene->getMasterBucket().at(ItemFilter::Builder::background()); + + + ItemIDsBounds inItems; + inItems.reserve(items.size()); + for (auto id : items) { + inItems.emplace_back(id); + } + RenderArgs* args = renderContext->args; + doInBatch(args->_context, [=](gpu::Batch& batch) { + args->_batch = &batch; + + auto primaryFboColorStencil = DependencyManager::get()->getPrimaryFramebufferStencilColor(); + auto primaryFboFull = DependencyManager::get()->getPrimaryFramebuffer(); + + batch.enableSkybox(true); + + batch.setFramebuffer(primaryFboColorStencil); + + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + glm::mat4 projMat; + Transform viewMat; + args->_viewFrustum->evalProjectionMatrix(projMat); + args->_viewFrustum->evalViewTransform(viewMat); + + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + + renderItems(sceneContext, renderContext, inItems); + + batch.setFramebuffer(primaryFboFull); + + }); + args->_batch = nullptr; +} \ No newline at end of file diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 8366a2665d..04483fc037 100755 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -59,6 +59,23 @@ public: typedef render::Job::ModelI JobModel; }; +class DrawStencilDeferred { + static gpu::PipelinePointer _opaquePipeline; //lazy evaluation hence mutable +public: + static const gpu::PipelinePointer& getOpaquePipeline(); + + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); + + typedef render::Job::Model JobModel; +}; + +class DrawBackgroundDeferred { +public: + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); + + typedef render::Job::Model JobModel; +}; + class DrawOverlay3D { static gpu::PipelinePointer _opaquePipeline; //lazy evaluation hence mutable public: diff --git a/libraries/render-utils/src/drawOpaqueStencil.slf b/libraries/render-utils/src/drawOpaqueStencil.slf new file mode 100644 index 0000000000..14feda21e9 --- /dev/null +++ b/libraries/render-utils/src/drawOpaqueStencil.slf @@ -0,0 +1,24 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// drawOpaqueStencil.frag +// fragment shader +// +// Created by Sam Gateau on 9/29/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +in vec2 varTexCoord0; + +uniform sampler2D depthTexture; + +void main(void) { + float depth = texture(depthTexture, varTexCoord0.xy).r; + if (depth >= 1.0) { + discard; + } +} diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index ec6656c0dc..b7a03b81e2 100755 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -257,6 +257,7 @@ class DrawBackground { public: void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext); + typedef Job::Model JobModel; }; diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 7d56157e53..dc7887c9f8 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -33,6 +33,8 @@ const vec3& Vectors::RIGHT = Vectors::UNIT_X; const vec3& Vectors::UP = Vectors::UNIT_Y; const vec3& Vectors::FRONT = Vectors::UNIT_NEG_Z; +const quat Quaternions::IDENTITY{ 1.0f, 0.0f, 0.0f, 0.0f }; + // Safe version of glm::mix; based on the code in Nick Bobick's article, // http://www.gamasutra.com/features/19980703/quaternions_01.htm (via Clyde, // https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 6683088306..7bdd3bf2de 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -53,6 +53,12 @@ const glm::vec3 IDENTITY_FRONT = glm::vec3( 0.0f, 0.0f,-1.0f); glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha); + +class Quaternions { + public: + static const quat IDENTITY; +}; + class Vectors { public: static const vec3 UNIT_X; diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js new file mode 100644 index 0000000000..d6759e2b48 --- /dev/null +++ b/unpublishedScripts/masterReset.js @@ -0,0 +1,834 @@ +// masterReset.js +// Created by Eric Levin on 9/23/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ +//per script +/*global deleteAllToys, createAllToys, createGates, createPingPongBallGun, createFire, createPottedPlant, createCombinedArmChair, createBasketballHoop, createBasketBall, createSprayCan, createDoll, createWand, createDice, createCat, deleteAllToys, createFlashlight, createBlocks, createMagballs, createLightSwitches */ +var utilitiesScript = Script.resolvePath("../examples/libraries/utils.js"); +Script.include(utilitiesScript); + +var resetKey = "resetMe"; +var GRABBABLE_DATA_KEY = "grabbableKey"; + +var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + +var shouldDeleteOnEndScript = false; + +//Before creating anything, first search a radius and delete all the things that should be deleted +deleteAllToys(); +createAllToys(); + +function createAllToys() { + createBlocks({ + x: 548.3, + y: 495.55, + z: 504.4 + }); + + createSprayCan({ + x: 549.7, + y: 495.6, + z: 503.91 + }); + + createBasketBall({ + x: 547.73, + y: 495.5, + z: 505.47 + }); + + createDoll({ + x: 546.67, + y: 495.41, + z: 505.09 + }); + + createWand({ + x: 546.71, + y: 495.55, + z: 506.15 + }); + + createDice(); + + createFlashlight({ + x: 545.72, + y: 495.41, + z: 505.78 + }); + + createCat({ + x: 551.09, + y: 494.98, + z: 503.49 + }); + + + createCombinedArmChair({ + x: 549.29, + y: 495.05, + z: 508.22 + }); + + createPottedPlant({ + x: 554.26, + y: 495.23, + z: 504.53 + }); + + createPingPongBallGun(); + + createBasketballHoop(); + + createGates(); + + createFire(); + // //Handles toggling of all sconce lights + createLightSwitches(); + + + +} + +function deleteAllToys() { + var entities = Entities.findEntities(MyAvatar.position, 100); + + entities.forEach(function (entity) { + //params: customKey, id, defaultValue + var shouldReset = getEntityCustomData(resetKey, entity, {}).resetMe; + if (shouldReset === true) { + Entities.deleteEntity(entity); + } + }); +} + +function createFire() { + + + var myOrientation = Quat.fromPitchYawRollDegrees(-90, 0, 0.0); + + var animationSettings = JSON.stringify({ + fps: 30, + running: true, + loop: true, + firstFrame: 1, + lastFrame: 10000 + }); + + + var fire = Entities.addEntity({ + type: "ParticleEffect", + name: "fire", + animationSettings: animationSettings, + textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", + position: { + x: 551.45, + y: 494.82, + z: 502.05 + }, + emitRate: 100, + colorStart: { + red: 70, + green: 70, + blue: 137 + }, + color: { + red: 200, + green: 99, + blue: 42 + }, + colorFinish: { + red: 255, + green: 99, + blue: 32 + }, + radiusSpread: 0.01, + radiusStart: 0.02, + radiusEnd: 0.001, + particleRadius: 0.05, + radiusFinish: 0.0, + emitOrientation: myOrientation, + emitSpeed: 0.3, + speedSpread: 0.1, + alphaStart: 0.05, + alpha: 0.1, + alphaFinish: 0.05, + emitDimensions: { + x: 1, + y: 1, + z: 0.1 + }, + polarFinish: 0.1, + emitAcceleration: { + x: 0.0, + y: 0.0, + z: 0.0 + }, + accelerationSpread: { + x: 0.1, + y: 0.01, + z: 0.1 + }, + lifespan: 1 + }); + + + setEntityCustomData(resetKey, fire, { + resetMe: true + }); +} + + +function createCat(position) { + var scriptURL = Script.resolvePath("../examples/toys/cat.js"); + var modelURL = "http://hifi-public.s3.amazonaws.com/ryan/Dark_Cat.fbx"; + var animationURL = "http://hifi-public.s3.amazonaws.com/ryan/sleeping.fbx"; + var animationSettings = JSON.stringify({ + running: true, + }); + var cat = Entities.addEntity({ + type: "Model", + modelURL: modelURL, + name: "cat", + script: scriptURL, + animationURL: animationURL, + animationSettings: animationSettings, + position: position, + rotation: { + w: 0.35020983219146729, + x: -4.57763671875e-05, + y: 0.93664455413818359, + z: -1.52587890625e-05 + }, + dimensions: { + x: 0.15723302960395813, + y: 0.50762706995010376, + z: 0.90716040134429932 + }, + }); + + setEntityCustomData(resetKey, cat, { + resetMe: true + }); +} + +function createFlashlight(position) { + var scriptURL = Script.resolvePath('../examples/toys/flashlight/flashlight.js'); + var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"; + + var flashlight = Entities.addEntity({ + type: "Model", + modelURL: modelURL, + name: "flashlight", + script: scriptURL, + position: position, + dimensions: { + x: 0.08, + y: 0.30, + z: 0.08 + }, + collisionsWillMove: true, + gravity: { + x: 0, + y: -3.5, + z: 0 + }, + velocity: { + x: 0, + y: -0.01, + z: 0 + }, + shapeType: 'box', + }); + + setEntityCustomData(resetKey, flashlight, { + resetMe: true + }); + +} + +function createLightSwitches() { + var modelURL = "http://hifi-public.s3.amazonaws.com/ryan/lightswitch.fbx?v1"; + var scriptURL = Script.resolvePath("../examples/toys/lightSwitchHall.js"); + + var lightSwitchHall = Entities.addEntity({ + type: "Model", + modelURL: modelURL, + name: "Light Switch Hall", + script: scriptURL, + position: { + x: 543.27764892578125, + y: 495.67999267578125, + z: 511.00564575195312 + }, + rotation: { + w: 0.63280689716339111, + x: 0.63280689716339111, + y: -0.31551080942153931, + z: 0.31548023223876953 + }, + dimensions: { + x: 0.10546875, + y: 0.032372996211051941, + z: 0.16242524981498718 + } + }); + + setEntityCustomData(resetKey, lightSwitchHall, { + resetMe: true + }); + + scriptURL = Script.resolvePath("../examples/toys/lightSwitchGarage.js"); + + var lightSwitchGarage = Entities.addEntity({ + type: "Model", + modelURL: modelURL, + name: "Light Switch Garage", + script: scriptURL, + position: { + x: 545.62, + y: 495.68, + z: 500.21 + }, + rotation: { + w: 0.20082402229309082, + x: 0.20082402229309082, + y: -0.67800414562225342, + z: 0.67797362804412842 + }, + dimensions: { + x: 0.10546875, + y: 0.032372996211051941, + z: 0.16242524981498718 + } + }); + + setEntityCustomData(resetKey, lightSwitchGarage, { + resetMe: true + }); + +} + +function createDice() { + var diceProps = { + type: "Model", + modelURL: "http://s3.amazonaws.com/hifi-public/models/props/Dice/goldDie.fbx", + collisionSoundURL: "http://s3.amazonaws.com/hifi-public/sounds/dice/diceCollide.wav", + name: "dice", + position: { + x: 541, + y: 494.96, + z: 509.1 + }, + dimensions: { + x: 0.09, + y: 0.09, + z: 0.09 + }, + gravity: { + x: 0, + y: -3.5, + z: 0 + }, + velocity: { + x: 0, + y: -0.01, + z: 0 + }, + shapeType: "box", + collisionsWillMove: true + }; + var dice1 = Entities.addEntity(diceProps); + + diceProps.position = { + x: 541.05, + y: 494.96, + z: 509.0 + }; + + var dice2 = Entities.addEntity(diceProps); + + setEntityCustomData(resetKey, dice1, { + resetMe: true + }); + + setEntityCustomData(resetKey, dice2, { + resetMe: true + }); +} + + +function createGates() { + var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/ryan/fence.fbx'; + + var rotation1 = Quat.fromPitchYawRollDegrees(0, 36, 0); + + var gate1 = Entities.addEntity({ + name: 'Back Door Gate', + type: 'Model', + shapeType: 'box', + modelURL: MODEL_URL, + position: { + x: 546.52, + y: 494.76, + z: 498.87 + }, + dimensions: { + x: 1.42, + y: 1.13, + z: 0.25 + }, + rotation: rotation1, + collisionsWillMove: true, + gravity: { + x: 0, + y: -50, + z: 0 + }, + linearDamping: 1, + angularDamping: 10, + mass: 10, + + }); + + setEntityCustomData(resetKey, gate1, { + resetMe: true + }); + + setEntityCustomData(GRABBABLE_DATA_KEY, gate1, { + grabbable: false + }); + + var rotation2 = Quat.fromPitchYawRollDegrees(0, -16, 0); + var gate2 = Entities.addEntity({ + name: 'Front Door Fence', + type: 'Model', + modelURL: MODEL_URL, + shapeType: 'box', + position: { + x: 531.15, + y: 495.11, + z: 520.20 + }, + dimensions: { + x: 1.42, + y: 1.13, + z: 0.2 + }, + rotation: rotation2, + collisionsWillMove: true, + gravity: { + x: 0, + y: -100, + z: 0 + }, + linearDamping: 1, + angularDamping: 10, + mass: 10, + }); + + setEntityCustomData(resetKey, gate2, { + resetMe: true + }); + + setEntityCustomData(GRABBABLE_DATA_KEY, gate2, { + grabbable: false + }); +} + +function createPingPongBallGun() { + var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx'; + var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_collision_hull.obj'; + var scriptURL = Script.resolvePath('../examples/toys/ping_pong_gun/pingPongGun.js'); + + var position = { + x: 548.6, + y: 495.4, + z: 503.39 + }; + + var rotation = Quat.fromPitchYawRollDegrees(0, 36, 0); + + var pingPongGun = Entities.addEntity({ + type: "Model", + modelURL: MODEL_URL, + shapeType: 'compound', + compoundShapeURL: COLLISION_HULL_URL, + script: scriptURL, + position: position, + rotation: rotation, + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + dimensions: { + x: 0.67, + y: 0.14, + z: 0.09 + }, + collisionsWillMove: true, + }); + + setEntityCustomData(resetKey, pingPongGun, { + resetMe: true + }); + + +} + +function createBasketballHoop() { + var position = { + x: 539.23, + y: 496.13, + z: 475.89 + }; + var rotation = Quat.fromPitchYawRollDegrees(0, 58.49, 0); + + var hoopURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop.fbx"; + var hoopCollisionHullURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop_collision_hull.obj"; + + var hoop = Entities.addEntity({ + type: "Model", + modelURL: hoopURL, + position: position, + rotation: rotation, + shapeType: 'compound', + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + dimensions: { + x: 1.89, + y: 3.99, + z: 3.79 + }, + compoundShapeURL: hoopCollisionHullURL + }); + + setEntityCustomData(resetKey, hoop, { + resetMe: true + }); + + setEntityCustomData(GRABBABLE_DATA_KEY, hoop, { + grabbable: false + }); +} + +function createWand(position) { + var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx'; + var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/actual_no_top_collision_hull.obj'; + var scriptURL = Script.resolvePath("../examples/toys/bubblewand/wand.js"); + + var entity = Entities.addEntity({ + name: 'Bubble Wand', + type: "Model", + modelURL: WAND_MODEL, + position: position, + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + dimensions: { + x: 0.05, + y: 0.25, + z: 0.05 + }, + //must be enabled to be grabbable in the physics engine + shapeType: 'compound', + collisionsWillMove: true, + compoundShapeURL: WAND_COLLISION_SHAPE, + //Look into why bubble wand is going through table when gravity is enabled + // gravity: {x: 0, y: -3.5, z: 0}, + // velocity: {x: 0, y: -0.01, z:0}, + script: scriptURL + }); + + setEntityCustomData(resetKey, entity, { + resetMe: true + }); +} + +function createBasketBall(position) { + + var modelURL = "http://s3.amazonaws.com/hifi-public/models/content/basketball2.fbx"; + + var entity = Entities.addEntity({ + type: "Model", + modelURL: modelURL, + position: position, + collisionsWillMove: true, + shapeType: "sphere", + name: "basketball", + dimensions: { + x: 0.25, + y: 0.26, + z: 0.25 + }, + gravity: { + x: 0, + y: -7, + z: 0 + }, + restitution: 10, + linearDamping: 0.0, + velocity: { + x: 0, + y: -0.01, + z: 0 + }, + collisionSoundURL: "http://s3.amazonaws.com/hifi-public/sounds/basketball/basketball.wav" + }); + + setEntityCustomData(resetKey, entity, { + resetMe: true + }); +} + +function createDoll(position) { + var modelURL = "http://hifi-public.s3.amazonaws.com/models/Bboys/bboy2/bboy2.fbx"; + var scriptURL = Script.resolvePath("../examples/toys/doll/doll.js"); + + var naturalDimensions = { + x: 1.63, + y: 1.67, + z: 0.26 + }; + var desiredDimensions = Vec3.multiply(naturalDimensions, 0.15); + var entity = Entities.addEntity({ + type: "Model", + name: "doll", + modelURL: modelURL, + script: scriptURL, + position: position, + shapeType: 'box', + dimensions: desiredDimensions, + gravity: { + x: 0, + y: -5, + z: 0 + }, + velocity: { + x: 0, + y: -0.1, + z: 0 + }, + collisionsWillMove: true + }); + + setEntityCustomData(resetKey, entity, { + resetMe: true + }); +} + +function createSprayCan(position) { + var scriptURL = Script.resolvePath("../examples/toys/sprayPaintCan.js"); + var modelURL = "https://hifi-public.s3.amazonaws.com/eric/models/paintcan.fbx"; + + var entity = Entities.addEntity({ + type: "Model", + name: "spraycan", + modelURL: modelURL, + position: position, + dimensions: { + x: 0.07, + y: 0.17, + z: 0.07 + }, + collisionsWillMove: true, + shapeType: 'box', + script: scriptURL, + gravity: { + x: 0, + y: -0.5, + z: 0 + }, + velocity: { + x: 0, + y: -1, + z: 0 + } + }); + + setEntityCustomData(resetKey, entity, { + resetMe: true + }); + +} + +function createPottedPlant(position) { + var modelURL = "http://hifi-public.s3.amazonaws.com/models/potted_plant/potted_plant.fbx"; + + var entity = Entities.addEntity({ + type: "Model", + name: "Potted Plant", + modelURL: modelURL, + position: position, + dimensions: { + x: 1.10, + y: 2.18, + z: 1.07 + }, + collisionsWillMove: true, + shapeType: 'box', + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + velocity: { + x: 0, + y: 0, + z: 0 + }, + linearDamping: 0.4 + }); + + setEntityCustomData(resetKey, entity, { + resetMe: true + }); + + + setEntityCustomData(GRABBABLE_DATA_KEY, entity, { + grabbable: false + }); +} + + +function createCombinedArmChair(position) { + var modelURL = "http://hifi-public.s3.amazonaws.com/models/red_arm_chair/combined_chair.fbx"; + var RED_ARM_CHAIR_COLLISION_HULL = "http://hifi-public.s3.amazonaws.com/models/red_arm_chair/red_arm_chair_collision_hull.obj"; + + var rotation = Quat.fromPitchYawRollDegrees(0, -143, 0); + + var entity = Entities.addEntity({ + type: "Model", + name: "Red Arm Chair", + modelURL: modelURL, + shapeType: 'compound', + compoundShapeURL: RED_ARM_CHAIR_COLLISION_HULL, + position: position, + rotation: rotation, + dimensions: { + x: 1.26, + y: 1.56, + z: 1.35 + }, + collisionsWillMove: true, + gravity: { + x: 0, + y: -0.8, + z: 0 + }, + velocity: { + x: 0, + y: 0, + z: 0 + }, + linearDamping: 0.2 + }); + + setEntityCustomData(resetKey, entity, { + resetMe: true + }); + + setEntityCustomData(GRABBABLE_DATA_KEY, entity, { + grabbable: false + }); +} + +function createBlocks(position) { + var baseURL = HIFI_PUBLIC_BUCKET + "models/content/planky/"; + var collisionSoundURL = "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/ToyWoodBlock.L.wav"; + var NUM_BLOCKS_PER_COLOR = 4; + var i, j; + + var blockTypes = [{ + url: "planky_blue.fbx", + dimensions: { + x: 0.05, + y: 0.05, + z: 0.25 + } + }, { + url: "planky_green.fbx", + dimensions: { + x: 0.1, + y: 0.1, + z: 0.25 + } + }, { + url: "planky_natural.fbx", + dimensions: { + x: 0.05, + y: 0.05, + z: 0.05 + } + }, { + url: "planky_yellow.fbx", + dimensions: { + x: 0.03, + y: 0.05, + z: 0.25 + } + }, { + url: "planky_red.fbx", + dimensions: { + x: 0.1, + y: 0.05, + z: 0.25 + } + }, ]; + + var modelURL, entity; + for (i = 0; i < blockTypes.length; i++) { + for (j = 0; j < NUM_BLOCKS_PER_COLOR; j++) { + modelURL = baseURL + blockTypes[i].url; + entity = Entities.addEntity({ + type: "Model", + modelURL: modelURL, + position: Vec3.sum(position, { + x: j / 10, + y: i / 10, + z: 0 + }), + shapeType: 'box', + name: "block", + dimensions: blockTypes[i].dimensions, + collisionsWillMove: true, + collisionSoundURL: collisionSoundURL, + gravity: { + x: 0, + y: -2.5, + z: 0 + }, + velocity: { + x: 0, + y: -0.01, + z: 0 + } + }); + + //customKey, id, data + setEntityCustomData(resetKey, entity, { + resetMe: true + }); + } + } +} + +function cleanup() { + deleteAllToys(); +} + +if (shouldDeleteOnEndScript) { + + Script.scriptEnding.connect(cleanup); +}