diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index e6c14d06da..fcc2288356 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -213,6 +213,8 @@ void Agent::run() { loop.exec(); + + // let the AvatarData and ResourceCache classes use our QNetworkAccessManager AvatarData::setNetworkAccessManager(networkManager); ResourceCache::setNetworkAccessManager(networkManager); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f979de64c0..8efdeed8a7 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -735,7 +735,7 @@ void DomainServer::setupPendingAssignmentCredits() { const float CREDITS_PER_HOUR = 0.10f; const float CREDITS_PER_MSEC = CREDITS_PER_HOUR / (60 * 60 * 1000); - const int SATOSHIS_PER_MSEC = CREDITS_PER_MSEC * powf(10.0f, 8.0f); + const int SATOSHIS_PER_MSEC = CREDITS_PER_MSEC * SATOSHIS_PER_CREDIT; float pendingCredits = elapsedMsecsSinceLastPayment * SATOSHIS_PER_MSEC; @@ -884,10 +884,10 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { if (!nodeData->getWalletUUID().isNull()) { TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID()); - double pendingCreditAmount = 0; + float pendingCreditAmount = 0; while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) { - pendingCreditAmount += i.value()->getAmount(); + pendingCreditAmount += i.value()->getAmount() / SATOSHIS_PER_CREDIT; ++i; } @@ -1004,6 +1004,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else { + // check if this is for json stats for a node const QString NODE_JSON_REGEX_STRING = QString("\\%1\\/(%2).json\\/?$").arg(URI_NODES).arg(UUID_REGEX_STRING); QRegExp nodeShowRegex(NODE_JSON_REGEX_STRING); @@ -1028,6 +1029,40 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // tell the caller we processed the request return true; } + + return false; + } + + // check if this is a request for a scripted assignment (with a temp unique UUID) + const QString ASSIGNMENT_REGEX_STRING = QString("\\%1\\/(%2)\\/?$").arg(URI_ASSIGNMENT).arg(UUID_REGEX_STRING); + QRegExp assignmentRegex(ASSIGNMENT_REGEX_STRING); + + if (assignmentRegex.indexIn(url.path()) != -1) { + QUuid matchingUUID = QUuid(assignmentRegex.cap(1)); + + SharedAssignmentPointer matchingAssignment = _allAssignments.value(matchingUUID); + if (!matchingAssignment) { + // check if we have a pending assignment that matches this temp UUID, and it is a scripted assignment + PendingAssignedNodeData* pendingData = _pendingAssignedNodes.value(matchingUUID); + if (pendingData) { + matchingAssignment = _allAssignments.value(pendingData->getAssignmentUUID()); + + if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) { + // we have a matching assignment and it is for the right type, have the HTTP manager handle it + // via correct URL for the script so the client can download + + QUrl scriptURL = url; + scriptURL.setPath(URI_ASSIGNMENT + "/" + + uuidStringWithoutCurlyBraces(pendingData->getAssignmentUUID())); + + // have the HTTPManager serve the appropriate script file + return _httpManager.handleHTTPRequest(connection, scriptURL); + } + } + } + + // request not handled + return false; } } } else if (connection->requestOperation() == QNetworkAccessManager::PostOperation) { diff --git a/domain-server/src/WalletTransaction.cpp b/domain-server/src/WalletTransaction.cpp index 6ff57f063c..9c8226a908 100644 --- a/domain-server/src/WalletTransaction.cpp +++ b/domain-server/src/WalletTransaction.cpp @@ -18,13 +18,13 @@ WalletTransaction::WalletTransaction() : _uuid(), _destinationUUID(), - _amount(), + _amount(0), _isFinalized(false) { } -WalletTransaction::WalletTransaction(const QUuid& destinationUUID, double amount) : +WalletTransaction::WalletTransaction(const QUuid& destinationUUID, qint64 amount) : _uuid(QUuid::createUuid()), _destinationUUID(destinationUUID), _amount(amount), @@ -63,5 +63,5 @@ void WalletTransaction::loadFromJson(const QJsonObject& jsonObject) { _uuid = QUuid(transactionObject.value(TRANSACTION_ID_KEY).toString()); _destinationUUID = QUuid(transactionObject.value(TRANSACTION_DESTINATION_WALLET_ID_KEY).toString()); - _amount = transactionObject.value(TRANSACTION_AMOUNT_KEY).toDouble(); + _amount = transactionObject.value(TRANSACTION_AMOUNT_KEY).toInt(); } \ No newline at end of file diff --git a/domain-server/src/WalletTransaction.h b/domain-server/src/WalletTransaction.h index 8f36d10302..5e05f9f549 100644 --- a/domain-server/src/WalletTransaction.h +++ b/domain-server/src/WalletTransaction.h @@ -19,16 +19,16 @@ class WalletTransaction : public QObject { public: WalletTransaction(); - WalletTransaction(const QUuid& destinationUUID, double amount); + WalletTransaction(const QUuid& destinationUUID, qint64 amount); const QUuid& getUUID() const { return _uuid; } void setDestinationUUID(const QUuid& destinationUUID) { _destinationUUID = destinationUUID; } const QUuid& getDestinationUUID() const { return _destinationUUID; } - double getAmount() const { return _amount; } - void setAmount(double amount) { _amount = amount; } - void incrementAmount(double increment) { _amount += increment; } + qint64 getAmount() const { return _amount; } + void setAmount(qint64 amount) { _amount = amount; } + void incrementAmount(qint64 increment) { _amount += increment; } bool isFinalized() const { return _isFinalized; } void setIsFinalized(bool isFinalized) { _isFinalized = isFinalized; } @@ -39,7 +39,7 @@ public: private: QUuid _uuid; QUuid _destinationUUID; - double _amount; + qint64 _amount; bool _isFinalized; }; diff --git a/examples/editModels.js b/examples/editModels.js index 55c79cbc1d..d53b854003 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -479,6 +479,7 @@ function mousePressEvent(event) { var pickRay = Camera.computePickRay(event.x, event.y); Vec3.print("[Mouse] Looking at: ", pickRay.origin); var foundModels = Models.findModels(pickRay.origin, LASER_LENGTH_FACTOR); + var closest = -1.0; for (var i = 0; i < foundModels.length; i++) { if (!foundModels[i].isKnownID) { var identify = Models.identifyModel(foundModels[i]); @@ -514,35 +515,51 @@ function mousePressEvent(event) { var d = Vec3.length(Vec3.subtract(P, X)); if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) { - modelSelected = true; - selectedModelID = foundModels[i]; - selectedModelProperties = properties; - - selectedModelProperties.oldRadius = selectedModelProperties.radius; - selectedModelProperties.oldPosition = { - x: selectedModelProperties.position.x, - y: selectedModelProperties.position.y, - z: selectedModelProperties.position.z, - }; - selectedModelProperties.oldRotation = { - x: selectedModelProperties.modelRotation.x, - y: selectedModelProperties.modelRotation.y, - z: selectedModelProperties.modelRotation.z, - w: selectedModelProperties.modelRotation.w, - }; - - - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); - - print("Clicked on " + selectedModelID.id + " " + modelSelected); - return; + if (closest < 0.0) { + closest = x; + } + + if (x <= closest) { + modelSelected = true; + selectedModelID = foundModels[i]; + selectedModelProperties = properties; + + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); + } } } } + + if (modelSelected) { + selectedModelProperties.oldRadius = selectedModelProperties.radius; + selectedModelProperties.oldPosition = { + x: selectedModelProperties.position.x, + y: selectedModelProperties.position.y, + z: selectedModelProperties.position.z, + }; + selectedModelProperties.oldRotation = { + x: selectedModelProperties.modelRotation.x, + y: selectedModelProperties.modelRotation.y, + z: selectedModelProperties.modelRotation.z, + w: selectedModelProperties.modelRotation.w, + }; + + selectedModelProperties.glowLevel = 0.1; + Models.editModel(selectedModelID, { glowLevel: selectedModelProperties.glowLevel}); + + print("Clicked on " + selectedModelID.id + " " + modelSelected); + } } } +Controller.mouseReleaseEvent.connect(function() { + if (modelSelected) { + Models.editModel(selectedModelID, { glowLevel: 0.0 }); + modelSelected = false; + } + }); + var oldModifier = 0; var modifier = 0; var wasShifted = false; diff --git a/examples/hydraMove.js b/examples/hydraMove.js index ed6a5a4f44..f211a450a3 100644 --- a/examples/hydraMove.js +++ b/examples/hydraMove.js @@ -78,7 +78,7 @@ function createDebugOverlay() { position: defaultPosition, size: RADIUS, color: GRAY_COLOR, - alpha: 1, + alpha: 0.75, visible: true, solid: true, anchor: "MyAvatar" @@ -87,7 +87,7 @@ function createDebugOverlay() { position: defaultPosition, size: RADIUS, color: RED_COLOR, - alpha: 1, + alpha: 0.5, visible: true, solid: true, anchor: "MyAvatar" @@ -111,7 +111,7 @@ function displayDebug() { } } else { // update debug indicator - if (greenSphere == -1) { + if (greenSphere == -1) { createDebugOverlay(); } @@ -149,8 +149,8 @@ function getGrabRotation() { // When move button is pressed, process results function handleGrabBehavior(deltaTime) { // check for and handle grab behaviors - grabbingWithRightHand = Controller.isButtonPressed(RIGHT_BUTTON_FWD) || Controller.isButtonPressed(RIGHT_BUTTON_4); - grabbingWithLeftHand = Controller.isButtonPressed(LEFT_BUTTON_FWD) || Controller.isButtonPressed(LEFT_BUTTON_4); + grabbingWithRightHand = Controller.isButtonPressed(RIGHT_BUTTON_4); + grabbingWithLeftHand = Controller.isButtonPressed(LEFT_BUTTON_4); stoppedGrabbingWithLeftHand = false; stoppedGrabbingWithRightHand = false; @@ -201,20 +201,22 @@ function handleGrabBehavior(deltaTime) { printVector("grabDelta: ", grabDelta, 3); } - var THRUST_GRAB_SCALING = 300000.0; + var thrust = Vec3.multiply(grabDelta, Math.abs(Vec3.length(grabDelta))); + + var THRUST_GRAB_SCALING = 100000.0; - var thrustFront = Vec3.multiply(front, MyAvatar.scale * -grabDelta.z * THRUST_GRAB_SCALING * deltaTime); + var thrustFront = Vec3.multiply(front, MyAvatar.scale * -thrust.z * THRUST_GRAB_SCALING * deltaTime); MyAvatar.addThrust(thrustFront); - var thrustRight = Vec3.multiply(right, MyAvatar.scale * grabDelta.x * THRUST_GRAB_SCALING * deltaTime); + var thrustRight = Vec3.multiply(right, MyAvatar.scale * thrust.x * THRUST_GRAB_SCALING * deltaTime); MyAvatar.addThrust(thrustRight); - var thrustUp = Vec3.multiply(up, MyAvatar.scale * grabDelta.y * THRUST_GRAB_SCALING * deltaTime); + var thrustUp = Vec3.multiply(up, MyAvatar.scale * thrust.y * THRUST_GRAB_SCALING * deltaTime); MyAvatar.addThrust(thrustUp); // add some rotation... var deltaRotation = getGrabRotation(); - var PITCH_SCALING = 2.0; + var PITCH_SCALING = 2.5; var PITCH_DEAD_ZONE = 2.0; - var YAW_SCALING = 2.0; + var YAW_SCALING = 2.5; var ROLL_SCALING = 2.0; var euler = Quat.safeEulerAngles(deltaRotation); diff --git a/examples/playSound.js b/examples/playSound.js index 317581ba36..189c24e86f 100644 --- a/examples/playSound.js +++ b/examples/playSound.js @@ -3,22 +3,22 @@ // examples // // Copyright 2014 High Fidelity, Inc. -// This sample script loads a sound file and plays it at the 'fingertip' of the +// Plays a sample audio file at the avatar's current location // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// First, load the clap sound from a URL -var clap = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); +// First, load a sample sound from a URL +var bird = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); function maybePlaySound(deltaTime) { if (Math.random() < 0.01) { // Set the location and other info for the sound to play var options = new AudioInjectionOptions(); - var palmPosition = Controller.getSpatialControlPosition(0); - options.position = palmPosition; + var position = MyAvatar.position; + options.position = position; options.volume = 0.5; - Audio.playSound(clap, options); + Audio.playSound(bird, options); } } diff --git a/interface/resources/shaders/model.vert b/interface/resources/shaders/model.vert index f78ed5045b..da7e9640d9 100644 --- a/interface/resources/shaders/model.vert +++ b/interface/resources/shaders/model.vert @@ -31,6 +31,9 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = gl_MultiTexCoord0; + // and the shadow texture coordinates + gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0); + // use standard pipeline transform gl_Position = ftransform(); } diff --git a/interface/resources/shaders/model_normal_map.vert b/interface/resources/shaders/model_normal_map.vert index b013a0a736..2a2f4156f0 100644 --- a/interface/resources/shaders/model_normal_map.vert +++ b/interface/resources/shaders/model_normal_map.vert @@ -36,6 +36,10 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = gl_MultiTexCoord0; + // and the shadow texture coordinates + gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], interpolatedPosition), dot(gl_EyePlaneT[0], interpolatedPosition), + dot(gl_EyePlaneR[0], interpolatedPosition), 1.0); + // use standard pipeline transform gl_Position = ftransform(); } diff --git a/interface/resources/shaders/model_shadow_map.frag b/interface/resources/shaders/model_shadow_map.frag new file mode 100644 index 0000000000..aa1df03b95 --- /dev/null +++ b/interface/resources/shaders/model_shadow_map.frag @@ -0,0 +1,48 @@ +#version 120 + +// +// model_shadow_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/23/14. +// Copyright 2014 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 +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position +varying vec4 position; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the base color based on OpenGL lighting model + vec4 normalizedNormal = normalize(normal); + float diffuse = dot(normalizedNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + + // compute the specular component (sans exponent) + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(vec4(position.xyz, 0.0))), + normalizedNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + + vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0); +} diff --git a/interface/resources/shaders/model_shadow_normal_map.frag b/interface/resources/shaders/model_shadow_normal_map.frag new file mode 100644 index 0000000000..3461c1b5f3 --- /dev/null +++ b/interface/resources/shaders/model_shadow_normal_map.frag @@ -0,0 +1,60 @@ +#version 120 + +// +// model_shadow_normal_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/23/14. +// Copyright 2014 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 +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the normal map texture +uniform sampler2D normalMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position +varying vec4 interpolatedPosition; + +// the interpolated normal +varying vec4 interpolatedNormal; + +// the interpolated tangent +varying vec4 interpolatedTangent; + +void main(void) { + vec3 normalizedNormal = normalize(vec3(interpolatedNormal)); + vec3 normalizedTangent = normalize(vec3(interpolatedTangent)); + vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); + vec3 localNormal = vec3(texture2D(normalMap, gl_TexCoord[0].st)) * 2.0 - vec3(1.0, 1.0, 1.0); + + // compute the base color based on OpenGL lighting model + vec4 viewNormal = vec4(normalizedTangent * localNormal.x + + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); + float diffuse = dot(viewNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + + // compute the specular component (sans exponent) + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - + normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + + vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0); +} diff --git a/interface/resources/shaders/model_shadow_normal_specular_map.frag b/interface/resources/shaders/model_shadow_normal_specular_map.frag new file mode 100644 index 0000000000..273d197fca --- /dev/null +++ b/interface/resources/shaders/model_shadow_normal_specular_map.frag @@ -0,0 +1,63 @@ +#version 120 + +// +// model_shadow_normal_specular_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/23/14. +// Copyright 2014 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 +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the normal map texture +uniform sampler2D normalMap; + +// the specular map texture +uniform sampler2D specularMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position +varying vec4 interpolatedPosition; + +// the interpolated normal +varying vec4 interpolatedNormal; + +// the interpolated tangent +varying vec4 interpolatedTangent; + +void main(void) { + vec3 normalizedNormal = normalize(vec3(interpolatedNormal)); + vec3 normalizedTangent = normalize(vec3(interpolatedTangent)); + vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); + vec3 localNormal = vec3(texture2D(normalMap, gl_TexCoord[0].st)) * 2.0 - vec3(1.0, 1.0, 1.0); + + // compute the base color based on OpenGL lighting model + vec4 viewNormal = vec4(normalizedTangent * localNormal.x + + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); + float diffuse = dot(viewNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + + // compute the specular component (sans exponent) + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - + normalize(vec4(interpolatedPosition.xyz, 0.0))), viewNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * + gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0); +} diff --git a/interface/resources/shaders/model_shadow_specular_map.frag b/interface/resources/shaders/model_shadow_specular_map.frag new file mode 100644 index 0000000000..77cff1e04e --- /dev/null +++ b/interface/resources/shaders/model_shadow_specular_map.frag @@ -0,0 +1,51 @@ +#version 120 + +// +// model_shadow_specular_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/23/14. +// Copyright 2014 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 +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the specular texture +uniform sampler2D specularMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position in view space +varying vec4 position; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the base color based on OpenGL lighting model + vec4 normalizedNormal = normalize(normal); + float diffuse = dot(normalizedNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + + // compute the specular component (sans exponent) + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(vec4(position.xyz, 0.0))), + normalizedNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * + gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0); +} diff --git a/interface/resources/shaders/skin_model.vert b/interface/resources/shaders/skin_model.vert index f743609dc3..d68347d33d 100644 --- a/interface/resources/shaders/skin_model.vert +++ b/interface/resources/shaders/skin_model.vert @@ -43,5 +43,8 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = gl_MultiTexCoord0; + // and the shadow texture coordinates + gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0); + gl_Position = gl_ProjectionMatrix * position; } diff --git a/interface/resources/shaders/skin_model_normal_map.vert b/interface/resources/shaders/skin_model_normal_map.vert index 5dbc32626a..66cc7c0f58 100644 --- a/interface/resources/shaders/skin_model_normal_map.vert +++ b/interface/resources/shaders/skin_model_normal_map.vert @@ -52,5 +52,9 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = gl_MultiTexCoord0; + // and the shadow texture coordinates + gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], interpolatedPosition), dot(gl_EyePlaneT[0], interpolatedPosition), + dot(gl_EyePlaneR[0], interpolatedPosition), 1.0); + gl_Position = gl_ProjectionMatrix * interpolatedPosition; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1455520b84..5b43159122 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -172,7 +172,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _nodeBoundsDisplay(this), _previousScriptLocation(), _runningScriptsWidget(new RunningScriptsWidget(_window)), - _runningScriptsWidgetWasVisible(false) + _runningScriptsWidgetWasVisible(false), + _trayIcon(new QSystemTrayIcon(_window)) { // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -242,7 +243,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QTimer* locationUpdateTimer = new QTimer(this); connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer); locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); - + connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); @@ -251,16 +252,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); connect(nodeList, SIGNAL(uuidChanged(const QUuid&)), _myAvatar, SLOT(setSessionUUID(const QUuid&))); connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); - + // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); - + const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * 1000; - + QTimer* balanceUpdateTimer = new QTimer(this); connect(balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance); balanceUpdateTimer->start(BALANCE_UPDATE_INTERVAL_MSECS); - + connect(&accountManager, &AccountManager::balanceChanged, this, &Application::updateWindowTitle); connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain); @@ -391,6 +392,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : OAuthWebViewHandler::getInstance(); // make sure the High Fidelity root CA is in our list of trusted certs OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig(); + + _trayIcon->show(); } Application::~Application() { @@ -562,7 +565,7 @@ void Application::paintGL() { PerformanceWarning warn(showWarnings, "Application::paintGL()"); glEnable(GL_LINE_SMOOTH); - + if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { _myCamera.setTightness(0.0f); // In first person, camera follows (untweaked) head exactly without delay _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); @@ -2325,10 +2328,15 @@ void Application::updateShadowMap() { // store view matrix without translation, which we'll use for precision-sensitive objects updateUntranslatedViewMatrix(); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.1f, 4.0f); // magic numbers courtesy http://www.eecs.berkeley.edu/~ravir/6160/papers/shadowmaps.ppt + _avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE); _particles.render(OctreeRenderer::SHADOW_RENDER_MODE); _models.render(OctreeRenderer::SHADOW_RENDER_MODE); + glDisable(GL_POLYGON_OFFSET_FILL); + glPopMatrix(); glMatrixMode(GL_PROJECTION); @@ -2534,6 +2542,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { if (_voxelFades.size() > 0) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... voxel fades..."); + _voxelFadesLock.lockForWrite(); for(std::vector::iterator fade = _voxelFades.begin(); fade != _voxelFades.end();) { fade->render(); if(fade->isDone()) { @@ -2542,6 +2551,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { ++fade; } } + _voxelFadesLock.unlock(); } // give external parties a change to hook in @@ -3103,18 +3113,18 @@ void Application::updateWindowTitle(){ QString username = AccountManager::getInstance().getAccountInfo().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) + nodeList->getDomainHandler().getHostname() + buildVersion; - + AccountManager& accountManager = AccountManager::getInstance(); if (accountManager.getAccountInfo().hasBalance()) { - float creditBalance = accountManager.getAccountInfo().getBalance() * pow(10.0f, -8.0f); - + float creditBalance = accountManager.getAccountInfo().getBalance() / SATOSHIS_PER_CREDIT; + QString creditBalanceString; creditBalanceString.sprintf("%.8f", creditBalance); - + title += " - ₵" + creditBalanceString; } - - qDebug("Application title set to: %s", title.toStdString().c_str()); + + qDebug("Application title set to: %s", title.toStdString().c_str()); _window->setWindowTitle(title); } @@ -3208,7 +3218,9 @@ void Application::nodeKilled(SharedNodePointer node) { fade.voxelDetails = rootDetails; const float slightly_smaller = 0.99f; fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller; + _voxelFadesLock.lockForWrite(); _voxelFades.push_back(fade); + _voxelFadesLock.unlock(); } // If the voxel server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server @@ -3239,7 +3251,9 @@ void Application::nodeKilled(SharedNodePointer node) { fade.voxelDetails = rootDetails; const float slightly_smaller = 0.99f; fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller; + _voxelFadesLock.lockForWrite(); _voxelFades.push_back(fade); + _voxelFadesLock.unlock(); } // If the particle server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server @@ -3271,7 +3285,9 @@ void Application::nodeKilled(SharedNodePointer node) { fade.voxelDetails = rootDetails; const float slightly_smaller = 0.99f; fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller; + _voxelFadesLock.lockForWrite(); _voxelFades.push_back(fade); + _voxelFadesLock.unlock(); } // If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server @@ -3356,7 +3372,9 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin fade.voxelDetails = rootDetails; const float slightly_smaller = 0.99f; fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller; + _voxelFadesLock.lockForWrite(); _voxelFades.push_back(fade); + _voxelFadesLock.unlock(); } } // store jurisdiction details for later use diff --git a/interface/src/Application.h b/interface/src/Application.h index 1968ef4fee..2e3880b7fd 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -203,6 +204,7 @@ public: JoystickManager* getJoystickManager() { return &_joystickManager; } BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } QUndoStack* getUndoStack() { return &_undoStack; } + QSystemTrayIcon* getTrayIcon() { return _trayIcon; } /// if you need to access the application settings, use lockSettings()/unlockSettings() QSettings* lockSettings() { _settingsMutex.lock(); return _settings; } @@ -467,7 +469,7 @@ private: glm::mat4 _untranslatedViewMatrix; glm::vec3 _viewMatrixTranslation; glm::mat4 _projectionMatrix; - + float _scaleMirror; float _rotateMirror; float _raiseMirror; @@ -533,6 +535,7 @@ private: NodeBounds _nodeBoundsDisplay; std::vector _voxelFades; + QReadWriteLock _voxelFadesLock; ControllerScriptingInterface _controllerScriptingInterface; QPointer _logDialog; QPointer _snapshotShareDialog; @@ -554,6 +557,8 @@ private: RunningScriptsWidget* _runningScriptsWidget; QHash _scriptEnginesHash; bool _runningScriptsWidgetWasVisible; + + QSystemTrayIcon* _trayIcon; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 96bfa106f4..92121e719a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -34,6 +34,7 @@ #include #include "Application.h" +#include "AccountManager.h" #include "Menu.h" #include "scripting/MenuScriptingInterface.h" #include "Util.h" @@ -195,12 +196,13 @@ Menu::Menu() : addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments())); addActionToQMenuAndActionHash(editMenu, MenuOption::Animations, 0, this, SLOT(editAnimations())); - + addDisabledActionAndSeparator(editMenu, "Physics"); QObject* avatar = appInstance->getAvatar(); - addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, + addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, + avatar, SLOT(updateMotionBehaviorsFromMenu())); + addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::StandOnNearbyFloors, 0, true, avatar, SLOT(updateMotionBehaviorsFromMenu())); - addAvatarCollisionSubMenu(editMenu); @@ -331,6 +333,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::AllowOculusCameraModeChange, 0, false); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::AvatarsReceiveShadows, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes); @@ -1025,24 +1028,24 @@ void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJson void Menu::muteEnvironment() { int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment); int packetSize = headerSize + sizeof(glm::vec3) + sizeof(float); - + glm::vec3 position = Application::getInstance()->getAvatar()->getPosition(); - + char* packet = (char*)malloc(packetSize); populatePacketHeader(packet, PacketTypeMuteEnvironment); memcpy(packet + headerSize, &position, sizeof(glm::vec3)); memcpy(packet + headerSize + sizeof(glm::vec3), &MUTE_RADIUS, sizeof(float)); - + QByteArray mutePacket(packet, packetSize); - + // grab our audio mixer from the NodeList, if it exists SharedNodePointer audioMixer = NodeList::getInstance()->soloNodeOfType(NodeType::AudioMixer); - + if (audioMixer) { // send off this mute packet NodeList::getInstance()->writeDatagram(mutePacket, audioMixer); } - + free(packet); } @@ -1200,19 +1203,24 @@ void Menu::showScriptEditor() { } void Menu::showChat() { - QMainWindow* mainWindow = Application::getInstance()->getWindow(); - if (!_chatWindow) { - _chatWindow = new ChatWindow(mainWindow); - } - if (_chatWindow->isHidden()) { - _chatWindow->show(); + if (AccountManager::getInstance().isLoggedIn()) { + QMainWindow* mainWindow = Application::getInstance()->getWindow(); + if (!_chatWindow) { + _chatWindow = new ChatWindow(mainWindow); + } + + if (_chatWindow->isHidden()) { + _chatWindow->show(); + } + } else { + Application::getInstance()->getTrayIcon()->showMessage("Interface", "You need to login to be able to chat with others on this domain."); } } void Menu::toggleChat() { #ifdef HAVE_QXMPP _chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected()); - if (!_chatAction->isEnabled() && _chatWindow) { + if (!_chatAction->isEnabled() && _chatWindow && AccountManager::getInstance().isLoggedIn()) { if (_chatWindow->isHidden()) { _chatWindow->show(); } else { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b12f989ed6..70f4f62ce4 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -293,6 +293,7 @@ namespace MenuOption { const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation"; const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation"; const QString Avatars = "Avatars"; + const QString AvatarsReceiveShadows = "Avatars Receive Shadows"; const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; const QString BuckyBalls = "Bucky Balls"; @@ -376,6 +377,7 @@ namespace MenuOption { const QString ShowBordersModelNodes = "Show Model Nodes"; const QString ShowBordersParticleNodes = "Show Particle Nodes"; const QString ShowIKConstraints = "Show IK Constraints"; + const QString StandOnNearbyFloors = "Stand on nearby floors"; const QString Stars = "Stars"; const QString Stats = "Stats"; const QString StopAllScripts = "Stop All Scripts"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index aff15f4e0b..306dc0194e 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -347,7 +347,6 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { void Avatar::renderBody(RenderMode renderMode, float glowLevel) { Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; - { Glower glower(glowLevel); @@ -356,7 +355,8 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { renderBillboard(); return; } - _skeletonModel.render(1.0f, modelRenderMode); + + _skeletonModel.render(1.0f, modelRenderMode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)); renderAttachments(renderMode); getHand()->render(false, modelRenderMode); } @@ -390,8 +390,9 @@ void Avatar::simulateAttachments(float deltaTime) { void Avatar::renderAttachments(RenderMode renderMode) { Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + bool receiveShadows = Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows); foreach (Model* model, _attachmentModels) { - model->render(1.0f, modelRenderMode); + model->render(1.0f, modelRenderMode, receiveShadows); } } diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 2d0599b31f..8382843c1e 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -176,7 +176,8 @@ void Head::relaxLean(float deltaTime) { } void Head::render(float alpha, Model::RenderMode mode) { - if (_faceModel.render(alpha, mode) && _renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) { + if (_faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)) && + _renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) { renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 042d2cdc7c..f47880056c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -65,6 +65,7 @@ MyAvatar::MyAvatar() : _distanceToNearestAvatar(std::numeric_limits::max()), _wasPushing(false), _isPushing(false), + _isBraking(false), _trapDuration(0.0f), _thrust(0.0f), _motorVelocity(0.0f), @@ -134,45 +135,7 @@ void MyAvatar::simulate(float deltaTime) { _handState = HAND_STATE_NULL; updateOrientation(deltaTime); - - float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + - fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) + - fabsf(_driveKeys[UP] - _driveKeys[DOWN]); - - bool walkingOnFloor = false; - float gravityLength = glm::length(_gravity); - if (gravityLength > EPSILON) { - const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); - glm::vec3 startCap; - boundingShape.getStartPoint(startCap); - glm::vec3 bottomOfBoundingCapsule = startCap + (boundingShape.getRadius() / gravityLength) * _gravity; - - float fallThreshold = 2.0f * deltaTime * gravityLength; - walkingOnFloor = (glm::distance(bottomOfBoundingCapsule, _lastFloorContactPoint) < fallThreshold); - } - - if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f || - ! walkingOnFloor) { - // apply gravity - _velocity += _scale * _gravity * (GRAVITY_EARTH * deltaTime); - - // update motor and thrust - updateMotorFromKeyboard(deltaTime, walkingOnFloor); - applyMotor(deltaTime); - applyThrust(deltaTime); - - // update position - if (glm::length2(_velocity) < EPSILON) { - _velocity = glm::vec3(0.0f); - } else { - _position += _velocity * deltaTime; - } - } - - // update moving flag based on speed - const float MOVING_SPEED_THRESHOLD = 0.01f; - _moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD; - updateChatCircle(deltaTime); + updatePosition(deltaTime); // update avatar skeleton and simulate hand and head getHand()->collideAgainstOurself(); @@ -206,19 +169,17 @@ void MyAvatar::simulate(float deltaTime) { radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f)); radius *= COLLISION_RADIUS_SCALAR; } - if (_collisionGroups) { - updateShapePositions(); - if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) { - updateCollisionWithEnvironment(deltaTime, radius); - } - if (_collisionGroups & COLLISION_GROUP_VOXELS) { - updateCollisionWithVoxels(deltaTime, radius); - } else { - _trapDuration = 0.0f; - } - if (_collisionGroups & COLLISION_GROUP_AVATARS) { - updateCollisionWithAvatars(deltaTime); - } + updateShapePositions(); + if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) { + updateCollisionWithEnvironment(deltaTime, radius); + } + if (_collisionGroups & COLLISION_GROUP_VOXELS) { + updateCollisionWithVoxels(deltaTime, radius); + } else { + _trapDuration = 0.0f; + } + if (_collisionGroups & COLLISION_GROUP_AVATARS) { + updateCollisionWithAvatars(deltaTime); } } @@ -423,9 +384,9 @@ void MyAvatar::setGravity(const glm::vec3& gravity) { float gravityLength = glm::length(gravity); if (gravityLength > EPSILON) { _worldUpDirection = _gravity / -gravityLength; - } else { - _worldUpDirection = DEFAULT_UP_DIRECTION; } + // NOTE: the else case here it to leave _worldUpDirection unchanged + // so it continues to point opposite to the previous gravity setting. } AnimationHandlePointer MyAvatar::addAnimationHandle() { @@ -812,7 +773,7 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { // Render the body's voxels and head Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; - _skeletonModel.render(1.0f, modelRenderMode); + _skeletonModel.render(1.0f, modelRenderMode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)); renderAttachments(renderMode); // Render head so long as the camera isn't inside it @@ -830,6 +791,15 @@ bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode rend (glm::length(cameraPosition - head->calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale); } +float MyAvatar::computeDistanceToFloor(const glm::vec3& startPoint) { + glm::vec3 direction = -_worldUpDirection; + OctreeElement* elementHit; // output from findRayIntersection + float distance = FLT_MAX; // output from findRayIntersection + BoxFace face; // output from findRayIntersection + Application::getInstance()->getVoxelTree()->findRayIntersection(startPoint, direction, elementHit, distance, face); + return distance; +} + void MyAvatar::updateOrientation(float deltaTime) { // Gather rotation information from keyboard _bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime; @@ -890,8 +860,7 @@ void MyAvatar::updateOrientation(float deltaTime) { // We must adjust the body orientation using a delta rotation (rather than // doing yaw math) because the body's yaw ranges are not the same // as what the Oculus API provides. - glm::vec3 UP_AXIS = glm::vec3(0.0f, 1.0f, 0.0f); - glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), UP_AXIS); + glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), _worldUpDirection); orientation = orientation * bodyCorrection; } Head* head = getHead(); @@ -905,6 +874,96 @@ void MyAvatar::updateOrientation(float deltaTime) { setOrientation(orientation); } +const float NEARBY_FLOOR_THRESHOLD = 5.0f; + +void MyAvatar::updatePosition(float deltaTime) { + float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + + fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) + + fabsf(_driveKeys[UP] - _driveKeys[DOWN]); + + bool walkingOnFloor = false; + float gravityLength = glm::length(_gravity) * GRAVITY_EARTH; + + const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); + glm::vec3 startCap; + boundingShape.getStartPoint(startCap); + glm::vec3 bottom = startCap - boundingShape.getRadius() * _worldUpDirection; + + if (gravityLength > EPSILON) { + float speedFromGravity = _scale * deltaTime * gravityLength; + float distanceToFall = glm::distance(bottom, _lastFloorContactPoint); + walkingOnFloor = (distanceToFall < 2.0f * deltaTime * speedFromGravity); + + if (walkingOnFloor) { + // BEGIN HACK: to prevent the avatar from bouncing on a floor surface + if (distanceToFall < deltaTime * speedFromGravity) { + float verticalSpeed = glm::dot(_velocity, _worldUpDirection); + if (fabs(verticalSpeed) < speedFromGravity) { + // we're standing on a floor, and nearly at rest so we zero the vertical velocity component + _velocity -= verticalSpeed * _worldUpDirection; + } + } else { + // fall with gravity against floor + _velocity -= speedFromGravity * _worldUpDirection; + } + // END HACK + } else { + if (!_isBraking) { + // fall with gravity toward floor + _velocity -= speedFromGravity * _worldUpDirection; + } + + if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { + const float MAX_VERTICAL_FLOOR_DETECTION_SPEED = _scale * MAX_WALKING_SPEED; + if (keyboardInput && glm::dot(_motorVelocity, _worldUpDirection) > 0.0f && + glm::dot(_velocity, _worldUpDirection) > MAX_VERTICAL_FLOOR_DETECTION_SPEED) { + // disable local gravity when flying up + setLocalGravity(glm::vec3(0.0f)); + } else { + const float maxFloorDistance = _scale * NEARBY_FLOOR_THRESHOLD; + if (computeDistanceToFloor(bottom) > maxFloorDistance) { + // disable local gravity when floor is too far + setLocalGravity(glm::vec3(0.0f)); + } + } + } + } + } else { + if ((_collisionGroups & COLLISION_GROUP_VOXELS) && + _motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { + const float MIN_FLOOR_DETECTION_SPEED = _scale * 1.0f; + if (glm::length(_velocity) < MIN_FLOOR_DETECTION_SPEED ) { + // scan for floor under avatar + const float maxFloorDistance = _scale * NEARBY_FLOOR_THRESHOLD; + if (computeDistanceToFloor(bottom) < maxFloorDistance) { + // enable local gravity + setLocalGravity(-_worldUpDirection); + } + } + } + } + + if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f || ! walkingOnFloor) { + // update motor and thrust + updateMotorFromKeyboard(deltaTime, walkingOnFloor); + applyMotor(deltaTime); + applyThrust(deltaTime); + + // update position + if (glm::length2(_velocity) < EPSILON) { + _velocity = glm::vec3(0.0f); + } else { + _position += _velocity * deltaTime; + } + } + + // update moving flag based on speed + const float MOVING_SPEED_THRESHOLD = 0.01f; + _moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD; + + updateChatCircle(deltaTime); +} + void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) { // Increase motor velocity until its length is equal to _maxMotorSpeed. if (!(_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED)) { @@ -930,12 +989,12 @@ void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) { if (directionLength > EPSILON) { direction /= directionLength; // the finalMotorSpeed depends on whether we are walking or not - float finalMaxMotorSpeed = walking ? MAX_WALKING_SPEED : _maxMotorSpeed; + float finalMaxMotorSpeed = walking ? _scale * MAX_WALKING_SPEED : _scale * _maxMotorSpeed; float motorLength = glm::length(_motorVelocity); - if (motorLength < MIN_KEYBOARD_CONTROL_SPEED) { + if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) { // an active keyboard motor should never be slower than this - _motorVelocity = MIN_KEYBOARD_CONTROL_SPEED * direction; + _motorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction; } else { float MOTOR_LENGTH_TIMESCALE = 1.5f; float tau = glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f); @@ -963,32 +1022,30 @@ float MyAvatar::computeMotorTimescale() { // (1) braking --> short timescale (aggressive motor assertion) // (2) pushing --> medium timescale (mild motor assertion) // (3) inactive --> long timescale (gentle friction for low speeds) - // - // TODO: recover extra braking behavior when flying close to nearest avatar float MIN_MOTOR_TIMESCALE = 0.125f; float MAX_MOTOR_TIMESCALE = 0.5f; float MIN_BRAKE_SPEED = 0.4f; float timescale = MAX_MOTOR_TIMESCALE; - float speed = glm::length(_velocity); - bool areThrusting = (glm::length2(_thrust) > EPSILON); - - if (_wasPushing && !(_isPushing || areThrusting) && speed > MIN_BRAKE_SPEED) { - // we don't change _wasPushing for this case --> - // keeps the brakes on until we go below MIN_BRAKE_SPEED - timescale = MIN_MOTOR_TIMESCALE; + bool isThrust = (glm::length2(_thrust) > EPSILON); + if (_isPushing || isThrust) { + timescale = _motorTimescale; + _isBraking = false; } else { - if (_isPushing) { - timescale = _motorTimescale; - } - _wasPushing = _isPushing || areThrusting; + float speed = glm::length(_velocity); + _isBraking = _wasPushing || (_isBraking && speed > MIN_BRAKE_SPEED); + if (_isBraking) { + timescale = MIN_MOTOR_TIMESCALE; + } } + _wasPushing = _isPushing || isThrust; _isPushing = false; return timescale; } void MyAvatar::applyMotor(float deltaTime) { + // TODO: recover extra braking behavior when flying close to nearest avatar if (!( _motionBehaviors & AVATAR_MOTION_MOTOR_ENABLED)) { // nothing to do --> early exit return; @@ -1178,6 +1235,8 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { const float MIN_STEP_HEIGHT = 0.0f; glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection; float highestStep = 0.0f; + float lowestStep = MAX_STEP_HEIGHT; + glm::vec3 floorPoint; glm::vec3 stepPenetration(0.0f); glm::vec3 totalPenetration(0.0f); @@ -1211,8 +1270,15 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { highestStep = stepHeight; stepPenetration = collision->_penetration; } + if (stepHeight < lowestStep) { + lowestStep = stepHeight; + floorPoint = collision->_contactPoint - collision->_penetration; + } } } + if (lowestStep < MAX_STEP_HEIGHT) { + _lastFloorContactPoint = floorPoint; + } float penetrationLength = glm::length(totalPenetration); if (penetrationLength < EPSILON) { @@ -1251,8 +1317,9 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING); } - const float VOXEL_COLLISION_FREQUENCY = 0.5f; - updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); + // Don't make a collision sound against voxlels by default -- too annoying when walking + //const float VOXEL_COLLISION_FREQUENCY = 0.5f; + //updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); } _trapDuration = isTrapped ? _trapDuration + deltaTime : 0.0f; } @@ -1598,7 +1665,8 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { } void MyAvatar::updateMotionBehaviorsFromMenu() { - if (Menu::getInstance()->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) { + Menu* menu = Menu::getInstance(); + if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) { _motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; // Environmental and Local gravities are incompatible. Environmental setting trumps local. _motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY; @@ -1608,6 +1676,14 @@ void MyAvatar::updateMotionBehaviorsFromMenu() { if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) { setGravity(glm::vec3(0.0f)); } + if (menu->isOptionChecked(MenuOption::StandOnNearbyFloors)) { + _motionBehaviors |= AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; + // standing on floors requires collision with voxels + _collisionGroups |= COLLISION_GROUP_VOXELS; + menu->setIsOptionChecked(MenuOption::CollideWithVoxels, true); + } else { + _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; + } } void MyAvatar::renderAttachments(RenderMode renderMode) { @@ -1619,10 +1695,11 @@ void MyAvatar::renderAttachments(RenderMode renderMode) { QString headJointName = (geometry.headJointIndex == -1) ? QString() : geometry.joints.at(geometry.headJointIndex).name; Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + bool receiveShadows = Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows); for (int i = 0; i < _attachmentData.size(); i++) { const QString& jointName = _attachmentData.at(i).jointName; if (jointName != headJointName && jointName != "Head") { - _attachmentModels.at(i)->render(1.0f, modelRenderMode); + _attachmentModels.at(i)->render(1.0f, modelRenderMode, receiveShadows); } } } @@ -1634,6 +1711,11 @@ void MyAvatar::setCollisionGroups(quint32 collisionGroups) { menu->setIsOptionChecked(MenuOption::CollideWithAvatars, (bool)(_collisionGroups & COLLISION_GROUP_AVATARS)); menu->setIsOptionChecked(MenuOption::CollideWithVoxels, (bool)(_collisionGroups & COLLISION_GROUP_VOXELS)); menu->setIsOptionChecked(MenuOption::CollideWithParticles, (bool)(_collisionGroups & COLLISION_GROUP_PARTICLES)); + if (! (_collisionGroups & COLLISION_GROUP_VOXELS)) { + // no collision with voxels --> disable standing on floors + _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; + menu->setIsOptionChecked(MenuOption::StandOnNearbyFloors, false); + } } void MyAvatar::setMotionBehaviorsByScript(quint32 flags) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a8eb3babd0..c0bc12b6b0 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -150,11 +150,11 @@ private: bool _shouldJump; float _driveKeys[MAX_DRIVE_KEYS]; glm::vec3 _gravity; - glm::vec3 _environmentGravity; float _distanceToNearestAvatar; // How close is the nearest avatar? bool _wasPushing; bool _isPushing; + bool _isBraking; float _trapDuration; // seconds that avatar has been trapped by collisions glm::vec3 _thrust; // final acceleration from outside sources for the current frame @@ -174,7 +174,9 @@ private: QList _animationHandles; // private methods + float computeDistanceToFloor(const glm::vec3& startPoint); void updateOrientation(float deltaTime); + void updatePosition(float deltaTime); void updateMotorFromKeyboard(float deltaTime, bool walking); float computeMotorTimescale(); void applyMotor(float deltaTime); diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp index 8ffd61043a..68bebd7b99 100644 --- a/interface/src/models/ModelTreeRenderer.cpp +++ b/interface/src/models/ModelTreeRenderer.cpp @@ -220,7 +220,13 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) // TODO: should we allow modelItems to have alpha on their models? Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; - model->render(alpha, modelRenderMode); + + if (modelItem.getGlowLevel() > 0.0f) { + Glower glower(modelItem.getGlowLevel()); + model->render(alpha, modelRenderMode); + } else { + model->render(alpha, modelRenderMode); + } if (!isShadowMode && displayModelBounds) { diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 7b3a7eba60..db2f6e0664 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -59,18 +59,39 @@ ProgramObject Model::_program; ProgramObject Model::_normalMapProgram; ProgramObject Model::_specularMapProgram; ProgramObject Model::_normalSpecularMapProgram; + +ProgramObject Model::_shadowMapProgram; +ProgramObject Model::_shadowNormalMapProgram; +ProgramObject Model::_shadowSpecularMapProgram; +ProgramObject Model::_shadowNormalSpecularMapProgram; + ProgramObject Model::_shadowProgram; + ProgramObject Model::_skinProgram; ProgramObject Model::_skinNormalMapProgram; ProgramObject Model::_skinSpecularMapProgram; ProgramObject Model::_skinNormalSpecularMapProgram; + +ProgramObject Model::_skinShadowMapProgram; +ProgramObject Model::_skinShadowNormalMapProgram; +ProgramObject Model::_skinShadowSpecularMapProgram; +ProgramObject Model::_skinShadowNormalSpecularMapProgram; + ProgramObject Model::_skinShadowProgram; + int Model::_normalMapTangentLocation; int Model::_normalSpecularMapTangentLocation; +int Model::_shadowNormalMapTangentLocation; +int Model::_shadowNormalSpecularMapTangentLocation; + Model::SkinLocations Model::_skinLocations; Model::SkinLocations Model::_skinNormalMapLocations; Model::SkinLocations Model::_skinSpecularMapLocations; Model::SkinLocations Model::_skinNormalSpecularMapLocations; +Model::SkinLocations Model::_skinShadowMapLocations; +Model::SkinLocations Model::_skinShadowNormalMapLocations; +Model::SkinLocations Model::_skinShadowSpecularMapLocations; +Model::SkinLocations Model::_skinShadowNormalSpecularMapLocations; Model::SkinLocations Model::_skinShadowLocations; void Model::setScale(const glm::vec3& scale) { @@ -100,7 +121,8 @@ void Model::setOffset(const glm::vec3& offset) { } -void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations, int specularTextureUnit) { +void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations, + int specularTextureUnit, int shadowTextureUnit) { program.bind(); locations.clusterMatrices = program.uniformLocation("clusterMatrices"); locations.clusterIndices = program.attributeLocation("clusterIndices"); @@ -109,6 +131,7 @@ void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locati program.setUniformValue("diffuseMap", 0); program.setUniformValue("normalMap", 1); program.setUniformValue("specularMap", specularTextureUnit); + program.setUniformValue("shadowMap", shadowTextureUnit); program.release(); } @@ -170,7 +193,7 @@ void Model::init() { _program.link(); _program.bind(); - _program.setUniformValue("texture", 0); + _program.setUniformValue("diffuseMap", 0); _program.release(); _normalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, @@ -209,11 +232,63 @@ void Model::init() { _normalSpecularMapTangentLocation = _normalMapProgram.attributeLocation("tangent"); _normalSpecularMapProgram.release(); + + _shadowMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model.vert"); + _shadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/model_shadow_map.frag"); + _shadowMapProgram.link(); + + _shadowMapProgram.bind(); + _shadowMapProgram.setUniformValue("diffuseMap", 0); + _shadowMapProgram.setUniformValue("shadowMap", 1); + _shadowMapProgram.release(); + + _shadowNormalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/model_normal_map.vert"); + _shadowNormalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_shadow_normal_map.frag"); + _shadowNormalMapProgram.link(); + + _shadowNormalMapProgram.bind(); + _shadowNormalMapProgram.setUniformValue("diffuseMap", 0); + _shadowNormalMapProgram.setUniformValue("normalMap", 1); + _shadowNormalMapProgram.setUniformValue("shadowMap", 2); + _shadowNormalMapTangentLocation = _shadowNormalMapProgram.attributeLocation("tangent"); + _shadowNormalMapProgram.release(); + + _shadowSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/model.vert"); + _shadowSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_shadow_specular_map.frag"); + _shadowSpecularMapProgram.link(); + + _shadowSpecularMapProgram.bind(); + _shadowSpecularMapProgram.setUniformValue("diffuseMap", 0); + _shadowSpecularMapProgram.setUniformValue("specularMap", 1); + _shadowSpecularMapProgram.setUniformValue("shadowMap", 2); + _shadowSpecularMapProgram.release(); + + _shadowNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/model_normal_map.vert"); + _shadowNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_shadow_normal_specular_map.frag"); + _shadowNormalSpecularMapProgram.link(); + + _shadowNormalSpecularMapProgram.bind(); + _shadowNormalSpecularMapProgram.setUniformValue("diffuseMap", 0); + _shadowNormalSpecularMapProgram.setUniformValue("normalMap", 1); + _shadowNormalSpecularMapProgram.setUniformValue("specularMap", 2); + _shadowNormalSpecularMapProgram.setUniformValue("shadowMap", 3); + _shadowNormalSpecularMapTangentLocation = _normalMapProgram.attributeLocation("tangent"); + _shadowNormalSpecularMapProgram.release(); + + _shadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model_shadow.vert"); _shadowProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model_shadow.frag"); _shadowProgram.link(); + _skinProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model.vert"); _skinProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model.frag"); _skinProgram.link(); @@ -244,6 +319,40 @@ void Model::init() { initSkinProgram(_skinNormalSpecularMapProgram, _skinNormalSpecularMapLocations, 2); + + _skinShadowMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/skin_model.vert"); + _skinShadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/model_shadow_map.frag"); + _skinShadowMapProgram.link(); + + initSkinProgram(_skinShadowMapProgram, _skinShadowMapLocations); + + _skinShadowNormalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model_normal_map.vert"); + _skinShadowNormalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_shadow_normal_map.frag"); + _skinShadowNormalMapProgram.link(); + + initSkinProgram(_skinShadowNormalMapProgram, _skinShadowNormalMapLocations, 1, 2); + + _skinShadowSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model.vert"); + _skinShadowSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_shadow_specular_map.frag"); + _skinShadowSpecularMapProgram.link(); + + initSkinProgram(_skinShadowSpecularMapProgram, _skinShadowSpecularMapLocations, 1, 2); + + _skinShadowNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model_normal_map.vert"); + _skinShadowNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_shadow_normal_specular_map.frag"); + _skinShadowNormalSpecularMapProgram.link(); + + initSkinProgram(_skinShadowNormalSpecularMapProgram, _skinShadowNormalSpecularMapLocations, 2, 3); + + _skinShadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model_shadow.vert"); _skinShadowProgram.addShaderFromSourceFile(QGLShader::Fragment, @@ -361,7 +470,7 @@ bool Model::updateGeometry() { return needFullUpdate; } -bool Model::render(float alpha, RenderMode mode) { +bool Model::render(float alpha, RenderMode mode, bool receiveShadows) { // render the attachments foreach (Model* attachment, _attachments) { attachment->render(alpha, mode); @@ -389,6 +498,9 @@ bool Model::render(float alpha, RenderMode mode) { glDisable(GL_CULL_FACE); } else { glEnable(GL_CULL_FACE); + if (mode == SHADOW_RENDER_MODE) { + glCullFace(GL_FRONT); + } } // render opaque meshes with alpha testing @@ -396,16 +508,21 @@ bool Model::render(float alpha, RenderMode mode) { glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.5f * alpha); - renderMeshes(alpha, mode, false); + receiveShadows &= Menu::getInstance()->isOptionChecked(MenuOption::Shadows); + renderMeshes(alpha, mode, false, receiveShadows); glDisable(GL_ALPHA_TEST); // render translucent meshes afterwards - renderMeshes(alpha, mode, true); + renderMeshes(alpha, mode, true, receiveShadows); glDisable(GL_CULL_FACE); + if (mode == SHADOW_RENDER_MODE) { + glCullFace(GL_BACK); + } + // deactivate vertex arrays after drawing glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); @@ -1480,10 +1597,15 @@ void Model::deleteGeometry() { } } -void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { +void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool receiveShadows) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); + if (receiveShadows) { + glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[0]); + glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[1]); + glTexGenfv(GL_R, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[2]); + } for (int i = 0; i < networkMeshes.size(); i++) { // exit early if the translucency doesn't match what we're drawing const NetworkMesh& networkMesh = networkMeshes.at(i); @@ -1506,6 +1628,7 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { ProgramObject* skinProgram = &_skinProgram; SkinLocations* skinLocations = &_skinLocations; GLenum specularTextureUnit = 0; + GLenum shadowTextureUnit = 0; if (mode == SHADOW_RENDER_MODE) { program = &_shadowProgram; skinProgram = &_skinShadowProgram; @@ -1513,21 +1636,46 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { } else if (!mesh.tangents.isEmpty()) { if (mesh.hasSpecularTexture()) { - program = &_normalSpecularMapProgram; - skinProgram = &_skinNormalSpecularMapProgram; - skinLocations = &_skinNormalSpecularMapLocations; + if (receiveShadows) { + program = &_shadowNormalSpecularMapProgram; + skinProgram = &_skinShadowNormalSpecularMapProgram; + skinLocations = &_skinShadowNormalSpecularMapLocations; + shadowTextureUnit = GL_TEXTURE3; + } else { + program = &_normalSpecularMapProgram; + skinProgram = &_skinNormalSpecularMapProgram; + skinLocations = &_skinNormalSpecularMapLocations; + } specularTextureUnit = GL_TEXTURE2; + } else if (receiveShadows) { + program = &_shadowNormalMapProgram; + skinProgram = &_skinShadowNormalMapProgram; + skinLocations = &_skinShadowNormalMapLocations; + shadowTextureUnit = GL_TEXTURE2; } else { program = &_normalMapProgram; skinProgram = &_skinNormalMapProgram; skinLocations = &_skinNormalMapLocations; } } else if (mesh.hasSpecularTexture()) { - program = &_specularMapProgram; - skinProgram = &_skinSpecularMapProgram; - skinLocations = &_skinSpecularMapLocations; + if (receiveShadows) { + program = &_shadowSpecularMapProgram; + skinProgram = &_skinShadowSpecularMapProgram; + skinLocations = &_skinShadowSpecularMapLocations; + shadowTextureUnit = GL_TEXTURE2; + } else { + program = &_specularMapProgram; + skinProgram = &_skinSpecularMapProgram; + skinLocations = &_skinSpecularMapLocations; + } specularTextureUnit = GL_TEXTURE1; + + } else if (receiveShadows) { + program = &_shadowMapProgram; + skinProgram = &_skinShadowMapProgram; + skinLocations = &_skinShadowMapLocations; + shadowTextureUnit = GL_TEXTURE1; } const MeshState& state = _meshStates.at(i); @@ -1616,8 +1764,7 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID()); - if (!mesh.tangents.isEmpty()) { - specularTextureUnit = GL_TEXTURE2; + if (!mesh.tangents.isEmpty()) { glActiveTexture(GL_TEXTURE1); Texture* normalMap = networkPart.normalTexture.data(); glBindTexture(GL_TEXTURE_2D, !normalMap ? @@ -1632,6 +1779,12 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { Application::getInstance()->getTextureCache()->getWhiteTextureID() : specularMap->getID()); glActiveTexture(GL_TEXTURE0); } + + if (shadowTextureUnit) { + glActiveTexture(shadowTextureUnit); + glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getShadowDepthTextureID()); + glActiveTexture(GL_TEXTURE0); + } } glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset); offset += part.quadIndices.size() * sizeof(int); @@ -1661,6 +1814,12 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { glActiveTexture(GL_TEXTURE0); } + if (shadowTextureUnit) { + glActiveTexture(shadowTextureUnit); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + } + if (state.clusterMatrices.size() > 1) { skinProgram->disableAttributeArray(skinLocations->clusterIndices); skinProgram->disableAttributeArray(skinLocations->clusterWeights); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 1a6642dfc6..69ea700b49 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -79,7 +79,7 @@ public: enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE }; - bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE); + bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE, bool receiveShadows = true); /// Sets the URL of the model to render. /// \param fallback the URL of a fallback model to render if the requested model fails to load @@ -315,7 +315,7 @@ private: void applyNextGeometry(); void deleteGeometry(); - void renderMeshes(float alpha, RenderMode mode, bool translucent); + void renderMeshes(float alpha, RenderMode mode, bool translucent, bool receiveShadows); QVector createJointStates(const FBXGeometry& geometry); QSharedPointer _baseGeometry; ///< reference required to prevent collection of base @@ -344,15 +344,30 @@ private: static ProgramObject _normalMapProgram; static ProgramObject _specularMapProgram; static ProgramObject _normalSpecularMapProgram; + + static ProgramObject _shadowMapProgram; + static ProgramObject _shadowNormalMapProgram; + static ProgramObject _shadowSpecularMapProgram; + static ProgramObject _shadowNormalSpecularMapProgram; + static ProgramObject _shadowProgram; + static ProgramObject _skinProgram; static ProgramObject _skinNormalMapProgram; static ProgramObject _skinSpecularMapProgram; static ProgramObject _skinNormalSpecularMapProgram; + + static ProgramObject _skinShadowMapProgram; + static ProgramObject _skinShadowNormalMapProgram; + static ProgramObject _skinShadowSpecularMapProgram; + static ProgramObject _skinShadowNormalSpecularMapProgram; + static ProgramObject _skinShadowProgram; static int _normalMapTangentLocation; static int _normalSpecularMapTangentLocation; + static int _shadowNormalMapTangentLocation; + static int _shadowNormalSpecularMapTangentLocation; class SkinLocations { public: @@ -366,9 +381,14 @@ private: static SkinLocations _skinNormalMapLocations; static SkinLocations _skinSpecularMapLocations; static SkinLocations _skinNormalSpecularMapLocations; + static SkinLocations _skinShadowMapLocations; + static SkinLocations _skinShadowNormalMapLocations; + static SkinLocations _skinShadowSpecularMapLocations; + static SkinLocations _skinShadowNormalSpecularMapLocations; static SkinLocations _skinShadowLocations; - static void initSkinProgram(ProgramObject& program, SkinLocations& locations, int specularTextureUnit = 1); + static void initSkinProgram(ProgramObject& program, SkinLocations& locations, + int specularTextureUnit = 1, int shadowTextureUnit = 1); }; Q_DECLARE_METATYPE(QPointer) diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 611f955031..fde77334f4 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -80,7 +80,7 @@ ChatWindow::ChatWindow(QWidget* parent) : } else { ui->numOnlineLabel->hide(); ui->closeButton->hide(); - ui->usersWidget->hide(); + ui->usersArea->hide(); ui->messagesScrollArea->hide(); ui->messagePlainTextEdit->hide(); connect(&XmppClient::getInstance(), SIGNAL(joinedPublicChatRoom()), this, SLOT(connected())); @@ -208,7 +208,7 @@ void ChatWindow::connected() { ui->connectingToXMPPLabel->hide(); ui->numOnlineLabel->show(); ui->closeButton->show(); - ui->usersWidget->show(); + ui->usersArea->show(); ui->messagesScrollArea->show(); ui->messagePlainTextEdit->show(); ui->messagePlainTextEdit->setFocus(); @@ -248,6 +248,7 @@ void ChatWindow::notificationClicked() { return; } } + Application::processEvents(); scrollToBottom(); } @@ -262,6 +263,8 @@ void ChatWindow::error(QXmppClient::Error error) { } void ChatWindow::participantsChanged() { + bool atBottom = isNearBottom(); + QStringList participants = XmppClient::getInstance().getPublicChatRoom()->participants(); ui->numOnlineLabel->setText(tr("%1 online now:").arg(participants.count())); @@ -288,6 +291,11 @@ void ChatWindow::participantsChanged() { userLabel->installEventFilter(this); ui->usersWidget->layout()->addWidget(userLabel); } + Application::processEvents(); + + if (atBottom) { + scrollToBottom(); + } } void ChatWindow::messageReceived(const QXmppMessage& message) { @@ -306,7 +314,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { messageArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); messageArea->setReadOnly(true); - messageArea->setStyleSheet("padding-bottom: 2px;" + messageArea->setStyleSheet("QTextBrowser{ padding-bottom: 2px;" "padding-left: 2px;" "padding-top: 2px;" "padding-right: 20px;" @@ -314,7 +322,8 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { "color: #333333;" "font-size: 14pt;" "background-color: rgba(0, 0, 0, 0%);" - "border: 0;"); + "border: 0; }" + "QMenu{ border: 2px outset gray; }"); QString userLabel = getParticipantName(message.from()); if (fromSelf) { diff --git a/interface/src/ui/SnapshotShareDialog.cpp b/interface/src/ui/SnapshotShareDialog.cpp index b5694b3e48..617d5e7101 100644 --- a/interface/src/ui/SnapshotShareDialog.cpp +++ b/interface/src/ui/SnapshotShareDialog.cpp @@ -31,6 +31,10 @@ const QString FORUM_REPLY_TO_TOPIC = "244"; const QString FORUM_POST_TEMPLATE = "

%2

"; const QString SHARE_DEFAULT_ERROR = "The server isn't responding. Please try again in a few minutes."; const QString SUCCESS_LABEL_TEMPLATE = "Success!!! Go check out your image ...
%1"; +const QString SHARE_BUTTON_STYLE = "border-width:0;border-radius:9px;border-radius:9px;font-family:Arial;font-size:18px;" + "font-weight:100;color:#FFFFFF;width: 120px;height: 50px;"; +const QString SHARE_BUTTON_ENABLED_STYLE = "background-color: #333;"; +const QString SHARE_BUTTON_DISABLED_STYLE = "background-color: #999;"; Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) @@ -73,6 +77,10 @@ SnapshotShareDialog::SnapshotShareDialog(QString fileName, QWidget* parent) : } void SnapshotShareDialog::accept() { + // prevent multiple clicks on share button + _ui.shareButton->setEnabled(false); + // gray out share button + _ui.shareButton->setStyleSheet(SHARE_BUTTON_STYLE + SHARE_BUTTON_DISABLED_STYLE); uploadSnapshot(); } @@ -179,6 +187,8 @@ void SnapshotShareDialog::postRequestFinished() { } } QMessageBox::warning(this, "", errorMessage); + _ui.shareButton->setEnabled(true); + _ui.shareButton->setStyleSheet(SHARE_BUTTON_STYLE + SHARE_BUTTON_ENABLED_STYLE); } } @@ -192,6 +202,8 @@ void SnapshotShareDialog::uploadRequestFinished() { sendForumPost(responseObject["url"].toString()); } else { QMessageBox::warning(this, "", SHARE_DEFAULT_ERROR); + _ui.shareButton->setEnabled(true); + _ui.shareButton->setStyleSheet(SHARE_BUTTON_STYLE + SHARE_BUTTON_ENABLED_STYLE); } delete requestReply; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index da7b05ead7..95f4f2b2fe 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -22,16 +22,26 @@ Overlays::Overlays() : _nextOverlayID(1) { } Overlays::~Overlays() { - QMap::iterator it; - for (it = _overlays2D.begin(); it != _overlays2D.end(); ++it) { - delete _overlays2D.take(it.key()); + + { + QWriteLocker lock(&_lock); + foreach(Overlay* thisOverlay, _overlays2D) { + delete thisOverlay; + } + _overlays2D.clear(); + foreach(Overlay* thisOverlay, _overlays3D) { + delete thisOverlay; + } + _overlays3D.clear(); } - for (it = _overlays3D.begin(); it != _overlays3D.end(); ++it) { - delete _overlays3D.take(it.key()); - } - while (!_overlaysToDelete.isEmpty()) { - delete _overlaysToDelete.takeLast(); + + if (!_overlaysToDelete.isEmpty()) { + QWriteLocker lock(&_deleteLock); + do { + delete _overlaysToDelete.takeLast(); + } while (!_overlaysToDelete.isEmpty()); } + } void Overlays::init(QGLWidget* parent) { diff --git a/interface/ui/chatWindow.ui b/interface/ui/chatWindow.ui index 4d223b2665..46ccafd5f8 100644 --- a/interface/ui/chatWindow.ui +++ b/interface/ui/chatWindow.ui @@ -128,19 +128,43 @@ QPushButton:pressed { - - - #usersWidget { - margin-right: 20px; -} + + + + 0 + 0 + + + + 0 + 0 + + + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 12 + + + + + - - margin-top: 12px; - Qt::ScrollBarAlwaysOff diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 072070e98c..8f658678b5 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -53,23 +53,27 @@ typedef unsigned long long quint64; #include "HandData.h" // avatar motion behaviors -const quint32 AVATAR_MOTION_MOTOR_ENABLED = 1U << 0; -const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 1; -const quint32 AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME = 1U << 2; -const quint32 AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY = 1U << 3; +const quint32 AVATAR_MOTION_MOTOR_ENABLED = 1U << 0; +const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 1; +const quint32 AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME = 1U << 2; +const quint32 AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY = 1U << 3; -const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 4; -const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 5; +const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 4; +const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 5; + +const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 6; const quint32 AVATAR_MOTION_DEFAULTS = AVATAR_MOTION_MOTOR_ENABLED | AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED | - AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME; + AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME | + AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; // these bits will be expanded as features are exposed const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | - AVATAR_MOTION_OBEY_LOCAL_GRAVITY; + AVATAR_MOTION_OBEY_LOCAL_GRAVITY | + AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; // First bitset diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index a6eb391138..beb107c4cf 100755 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -19,6 +19,7 @@ const char* HTTPConnection::StatusCode200 = "200 OK"; const char* HTTPConnection::StatusCode301 = "301 Moved Permanently"; +const char* HTTPConnection::StatusCode302 = "302 Found"; const char* HTTPConnection::StatusCode400 = "400 Bad Request"; const char* HTTPConnection::StatusCode404 = "404 Not Found"; const char* HTTPConnection::DefaultContentType = "text/plain; charset=ISO-8859-1"; diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index d5214ee3a8..e2352ed250 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -44,6 +44,7 @@ class HTTPConnection : public QObject { public: static const char* StatusCode200; static const char* StatusCode301; + static const char* StatusCode302; static const char* StatusCode400; static const char* StatusCode404; static const char* DefaultContentType; diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp index 3dd1c99e60..b6f4fe6c1d 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -85,12 +85,13 @@ ModelItem::ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& _shouldDie = false; _modelURL = MODEL_DEFAULT_MODEL_URL; _modelRotation = MODEL_DEFAULT_MODEL_ROTATION; - + // animation related _animationURL = MODEL_DEFAULT_ANIMATION_URL; _animationIsPlaying = false; _animationFrameIndex = 0.0f; _animationFPS = MODEL_DEFAULT_ANIMATION_FPS; + _glowLevel = 0.0f; _jointMappingCompleted = false; _lastAnimated = now; @@ -125,6 +126,7 @@ void ModelItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t _animationIsPlaying = false; _animationFrameIndex = 0.0f; _animationFPS = MODEL_DEFAULT_ANIMATION_FPS; + _glowLevel = 0.0f; _jointMappingCompleted = false; _lastAnimated = now; } @@ -802,6 +804,7 @@ ModelItemProperties::ModelItemProperties() : _animationIsPlaying(false), _animationFrameIndex(0.0), _animationFPS(MODEL_DEFAULT_ANIMATION_FPS), + _glowLevel(0.0f), _id(UNKNOWN_MODEL_ID), _idSet(false), @@ -817,6 +820,7 @@ ModelItemProperties::ModelItemProperties() : _animationIsPlayingChanged(false), _animationFrameIndexChanged(false), _animationFPSChanged(false), + _glowLevelChanged(false), _defaultSettings(true) { } @@ -890,6 +894,7 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const properties.setProperty("animationIsPlaying", _animationIsPlaying); properties.setProperty("animationFrameIndex", _animationFrameIndex); properties.setProperty("animationFPS", _animationFPS); + properties.setProperty("glowLevel", _glowLevel); if (_idSet) { properties.setProperty("id", _id); @@ -1015,7 +1020,7 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) { _animationFrameIndexChanged = true; } } - + QScriptValue animationFPS = object.property("animationFPS"); if (animationFPS.isValid()) { float newFPS; @@ -1025,6 +1030,16 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) { _animationFPSChanged = true; } } + + QScriptValue glowLevel = object.property("glowLevel"); + if (glowLevel.isValid()) { + float newGlowLevel; + newGlowLevel = glowLevel.toVariant().toFloat(); + if (_defaultSettings || newGlowLevel != _glowLevel) { + _glowLevel = newGlowLevel; + _glowLevelChanged = true; + } + } _lastEdited = usecTimestampNow(); } @@ -1075,11 +1090,16 @@ void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const { modelItem.setAnimationFrameIndex(_animationFrameIndex); somethingChanged = true; } - + if (_animationFPSChanged) { modelItem.setAnimationFPS(_animationFPS); somethingChanged = true; } + + if (_glowLevelChanged) { + modelItem.setGlowLevel(_glowLevel); + somethingChanged = true; + } if (somethingChanged) { bool wantDebug = false; @@ -1104,6 +1124,7 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _animationIsPlaying = modelItem.getAnimationIsPlaying(); _animationFrameIndex = modelItem.getAnimationFrameIndex(); _animationFPS = modelItem.getAnimationFPS(); + _glowLevel = modelItem.getGlowLevel(); _id = modelItem.getID(); _idSet = true; @@ -1119,6 +1140,7 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _animationIsPlayingChanged = false; _animationFrameIndexChanged = false; _animationFPSChanged = false; + _glowLevelChanged = false; _defaultSettings = false; } diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index 284203555d..9a558f2ef4 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -83,6 +83,7 @@ public: float getAnimationFrameIndex() const { return _animationFrameIndex; } bool getAnimationIsPlaying() const { return _animationIsPlaying; } float getAnimationFPS() const { return _animationFPS; } + float getGlowLevel() const { return _glowLevel; } quint64 getLastEdited() const { return _lastEdited; } uint16_t getChangedBits() const; @@ -100,6 +101,7 @@ public: void setAnimationFrameIndex(float value) { _animationFrameIndex = value; _animationFrameIndexChanged = true; } void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; } void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; } + void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; } /// used by ModelScriptingInterface to return ModelItemProperties for unknown models void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; } @@ -119,6 +121,7 @@ private: bool _animationIsPlaying; float _animationFrameIndex; float _animationFPS; + float _glowLevel; uint32_t _id; bool _idSet; @@ -135,6 +138,7 @@ private: bool _animationIsPlayingChanged; bool _animationFrameIndexChanged; bool _animationFPSChanged; + bool _glowLevelChanged; bool _defaultSettings; }; Q_DECLARE_METATYPE(ModelItemProperties); @@ -206,6 +210,7 @@ public: const glm::quat& getModelRotation() const { return _modelRotation; } bool hasAnimation() const { return !_animationURL.isEmpty(); } const QString& getAnimationURL() const { return _animationURL; } + float getGlowLevel() const { return _glowLevel; } ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); } ModelItemProperties getProperties() const; @@ -248,6 +253,7 @@ public: void setAnimationFrameIndex(float value) { _animationFrameIndex = value; } void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; } void setAnimationFPS(float value) { _animationFPS = value; } + void setGlowLevel(float glowLevel) { _glowLevel = glowLevel; } void setProperties(const ModelItemProperties& properties); @@ -293,6 +299,8 @@ protected: // model related items QString _modelURL; glm::quat _modelRotation; + + float _glowLevel; uint32_t _creatorTokenID; bool _newlyCreated; diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index b3607200fe..809f083e35 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -83,7 +83,7 @@ void DataServerAccountInfo::setDiscourseApiKey(const QString& discourseApiKey) { } } -void DataServerAccountInfo::setBalance(quint64 balance) { +void DataServerAccountInfo::setBalance(qint64 balance) { if (!_hasBalance || _balance != balance) { _balance = balance; _hasBalance = true; diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index fd135f922b..e0209326f9 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -16,6 +16,8 @@ #include "OAuthAccessToken.h" +const float SATOSHIS_PER_CREDIT = 100000000.0f; + class DataServerAccountInfo : public QObject { Q_OBJECT public: @@ -35,8 +37,8 @@ public: const QString& getDiscourseApiKey() const { return _discourseApiKey; } void setDiscourseApiKey(const QString& discourseApiKey); - quint64 getBalance() const { return _balance; } - void setBalance(quint64 balance); + qint64 getBalance() const { return _balance; } + void setBalance(qint64 balance); bool hasBalance() const { return _hasBalance; } void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; } Q_INVOKABLE void setBalanceFromJSON(const QJsonObject& jsonObject); @@ -52,7 +54,7 @@ private: QString _username; QString _xmppPassword; QString _discourseApiKey; - quint64 _balance; + qint64 _balance; bool _hasBalance; };