diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index b30cd355d1..e7ac7577b9 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include @@ -51,7 +51,7 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : connect(&_shutdownEventListener, SIGNAL(receivedCloseEvent()), SLOT(quit())); // set the logging target to the the CHILD_TARGET_NAME - Logging::setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); + LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); const QVariantMap argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments()); @@ -218,7 +218,7 @@ void AssignmentClient::handleAuthenticationRequest() { void AssignmentClient::assignmentCompleted() { // reset the logging target to the the CHILD_TARGET_NAME - Logging::setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); + LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); qDebug("Assignment finished or never started - waiting for new assignment."); diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index af09ff1535..02a4ff04f0 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include #include "AssignmentClientMonitor.h" @@ -21,7 +21,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(int &argc, char **argv, int num QCoreApplication(argc, argv) { // start the Logging class with the parent's target name - Logging::setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME); + LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME); _childArguments = arguments(); diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 8d87638434..6ca93a7b11 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -41,7 +41,7 @@ #include #include -#include +#include #include #include #include @@ -640,6 +640,7 @@ void AudioMixer::run() { timer.start(); char clientMixBuffer[MAX_PACKET_SIZE]; + char clientEnvBuffer[MAX_PACKET_SIZE]; int usecToSleep = BUFFER_SEND_INTERVAL_USECS; @@ -719,65 +720,90 @@ void AudioMixer::run() { int streamsMixed = prepareMixForListeningNode(node.data()); - char* dataAt; + char* mixDataAt; if (streamsMixed > 0) { // pack header - int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio); - dataAt = clientMixBuffer + numBytesPacketHeader; + int numBytesMixPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio); + mixDataAt = clientMixBuffer + numBytesMixPacketHeader; // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); - memcpy(dataAt, &sequence, sizeof(quint16)); - dataAt += sizeof(quint16); + memcpy(mixDataAt, &sequence, sizeof(quint16)); + mixDataAt += sizeof(quint16); + + // pack mixed audio samples + memcpy(mixDataAt, _mixSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO); + mixDataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO; - // Pack stream properties - bool inAZone = false; + // Send stream properties + bool hasReverb = false; + float reverbTime, wetLevel; + // find reverb properties for (int i = 0; i < _zoneReverbSettings.size(); ++i) { AudioMixerClientData* data = static_cast(node->getLinkedData()); glm::vec3 streamPosition = data->getAvatarAudioStream()->getPosition(); if (_audioZones[_zoneReverbSettings[i].zone].contains(streamPosition)) { - bool hasReverb = true; - float reverbTime = _zoneReverbSettings[i].reverbTime; - float wetLevel = _zoneReverbSettings[i].wetLevel; - - memcpy(dataAt, &hasReverb, sizeof(bool)); - dataAt += sizeof(bool); - memcpy(dataAt, &reverbTime, sizeof(float)); - dataAt += sizeof(float); - memcpy(dataAt, &wetLevel, sizeof(float)); - dataAt += sizeof(float); - - inAZone = true; + hasReverb = true; + reverbTime = _zoneReverbSettings[i].reverbTime; + wetLevel = _zoneReverbSettings[i].wetLevel; break; } } - if (!inAZone) { - bool hasReverb = false; - memcpy(dataAt, &hasReverb, sizeof(bool)); - dataAt += sizeof(bool); + AvatarAudioStream* stream = nodeData->getAvatarAudioStream(); + bool dataChanged = (stream->hasReverb() != hasReverb) || + (stream->hasReverb() && (stream->getRevebTime() != reverbTime || + stream->getWetLevel() != wetLevel)); + if (dataChanged) { + // Update stream + if (hasReverb) { + stream->setReverb(reverbTime, wetLevel); + } else { + stream->clearReverb(); + } } - // pack mixed audio samples - memcpy(dataAt, _mixSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO); - dataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO; + // Send at change or every so often + float CHANCE_OF_SEND = 0.01f; + bool sendData = dataChanged || (randFloat() < CHANCE_OF_SEND); + + if (sendData) { + int numBytesEnvPacketHeader = populatePacketHeader(clientEnvBuffer, PacketTypeAudioEnvironment); + char* envDataAt = clientEnvBuffer + numBytesEnvPacketHeader; + + unsigned char bitset = 0; + if (hasReverb) { + setAtBit(bitset, HAS_REVERB_BIT); + } + + memcpy(envDataAt, &bitset, sizeof(unsigned char)); + envDataAt += sizeof(unsigned char); + + if (hasReverb) { + memcpy(envDataAt, &reverbTime, sizeof(float)); + envDataAt += sizeof(float); + memcpy(envDataAt, &wetLevel, sizeof(float)); + envDataAt += sizeof(float); + } + nodeList->writeDatagram(clientEnvBuffer, envDataAt - clientEnvBuffer, node); + } } else { // pack header int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeSilentAudioFrame); - dataAt = clientMixBuffer + numBytesPacketHeader; + mixDataAt = clientMixBuffer + numBytesPacketHeader; // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); - memcpy(dataAt, &sequence, sizeof(quint16)); - dataAt += sizeof(quint16); + memcpy(mixDataAt, &sequence, sizeof(quint16)); + mixDataAt += sizeof(quint16); // pack number of silent audio samples quint16 numSilentSamples = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO; - memcpy(dataAt, &numSilentSamples, sizeof(quint16)); - dataAt += sizeof(quint16); + memcpy(mixDataAt, &numSilentSamples, sizeof(quint16)); + mixDataAt += sizeof(quint16); } // send mixed audio packet - nodeList->writeDatagram(clientMixBuffer, dataAt - clientMixBuffer, node); + nodeList->writeDatagram(clientMixBuffer, mixDataAt - clientMixBuffer, node); nodeData->incrementOutgoingMixedAudioSequenceNumber(); // send an audio stream stats packet if it's time diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 93f38e3608..3ec7c5cfbf 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include #include diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index 5e103cf767..3bf6990a74 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include #include #include "Assignment.h" @@ -22,7 +22,7 @@ int main(int argc, char* argv[]) { #endif // use the verbose message handler in Logging - qInstallMessageHandler(Logging::verboseMessageHandler); + qInstallMessageHandler(LogHandler::verboseMessageHandler); const char* numForksString = getCmdOption(argc, (const char**)argv, NUM_FORKS_PARAMETER); diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index d0a17287cb..121cac0273 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include "../AssignmentClient.h" @@ -923,7 +923,7 @@ void OctreeServer::run() { beforeRun(); // after payload has been processed - qInstallMessageHandler(Logging::verboseMessageHandler); + qInstallMessageHandler(LogHandler::verboseMessageHandler); const char* STATUS_PORT = "--statusPort"; const char* statusPort = getCmdOption(_argc, _argv, STATUS_PORT); diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 82e4bf7cab..ba80e6fce0 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -17,7 +17,7 @@ #include -#include +#include #include #include "DomainServer.h" @@ -27,7 +27,7 @@ int main(int argc, char* argv[]) { setvbuf(stdout, NULL, _IOLBF, 0); #endif - qInstallMessageHandler(Logging::verboseMessageHandler); + qInstallMessageHandler(LogHandler::verboseMessageHandler); int currentExitCode = 0; diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index becc8c86b2..c23f8b0dcc 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -143,6 +143,41 @@ EntityPropertyDialogBox = (function () { array.push({ label: "Blue:", value: properties.color.blue }); index++; } + + if (properties.type == "Light") { + array.push({ label: "Light Properties:", type: "header" }); + index++; + array.push({ label: "Is Spot Light:", value: properties.isSpotlight }); + index++; + array.push({ label: "Diffuse Red:", value: properties.diffuseColor.red }); + index++; + array.push({ label: "Diffuse Green:", value: properties.diffuseColor.green }); + index++; + array.push({ label: "Diffuse Blue:", value: properties.diffuseColor.blue }); + index++; + array.push({ label: "Ambient Red:", value: properties.ambientColor.red }); + index++; + array.push({ label: "Ambient Green:", value: properties.ambientColor.green }); + index++; + array.push({ label: "Ambient Blue:", value: properties.ambientColor.blue }); + index++; + array.push({ label: "Specular Red:", value: properties.specularColor.red }); + index++; + array.push({ label: "Specular Green:", value: properties.specularColor.green }); + index++; + array.push({ label: "Specular Blue:", value: properties.specularColor.blue }); + index++; + array.push({ label: "Constant Attenuation:", value: properties.constantAttenuation }); + index++; + array.push({ label: "Linear Attenuation:", value: properties.linearAttenuation }); + index++; + array.push({ label: "Quadratic Attenuation:", value: properties.quadraticAttenuation }); + index++; + array.push({ label: "Exponent:", value: properties.exponent }); + index++; + array.push({ label: "Cutoff (in degrees):", value: properties.cutoff }); + index++; + } array.push({ button: "Cancel" }); index++; @@ -243,6 +278,25 @@ EntityPropertyDialogBox = (function () { properties.color.green = array[index++].value; properties.color.blue = array[index++].value; } + if (properties.type == "Light") { + index++; // skip header + properties.isSpotlight = array[index++].value; + properties.diffuseColor.red = array[index++].value; + properties.diffuseColor.green = array[index++].value; + properties.diffuseColor.blue = array[index++].value; + properties.ambientColor.red = array[index++].value; + properties.ambientColor.green = array[index++].value; + properties.ambientColor.blue = array[index++].value; + properties.specularColor.red = array[index++].value; + properties.specularColor.green = array[index++].value; + properties.specularColor.blue = array[index++].value; + properties.constantAttenuation = array[index++].value; + properties.linearAttenuation = array[index++].value; + properties.quadraticAttenuation = array[index++].value; + properties.exponent = array[index++].value; + properties.cutoff = array[index++].value; + } + Entities.editEntity(editModelID, properties); selectionDisplay.select(editModelID, false); } diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index c503ab82a4..a325491dd3 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -19,10 +19,15 @@ SPACE_WORLD = "world"; SelectionManager = (function() { var that = {}; + var PENDING_SELECTION_CHECK_INTERVAL = 50; + that.savedProperties = {}; that.eventListener = null; that.selections = []; + // These are selections that don't have a known ID yet + that.pendingSelections = []; + var pendingSelectionTimer = null; that.localRotation = Quat.fromPitchYawRollDegrees(0, 0, 0); that.localPosition = { x: 0, y: 0, z: 0 }; @@ -45,11 +50,58 @@ SelectionManager = (function() { that.eventListener = func; }; + that.hasSelection = function() { + return that.selections.length > 0; + }; + + that.setSelections = function(entityIDs) { + that.selections = []; + for (var i = 0; i < entityIDs.length; i++) { + var entityID = entityIDs[i]; + if (entityID.isKnownID) { + that.selections.push(entityID); + } else { + that.pendingSelections.push(entityID); + if (pendingSelectionTimer == null) { + pendingSelectionTimer = Script.setInterval(that._checkPendingSelections, PENDING_SELECTION_CHECK_INTERVAL); + } + } + } + + that._update(); + }; + + that._checkPendingSelections = function() { + for (var i = 0; i < that.pendingSelections.length; i++) { + var entityID = that.pendingSelections[i]; + var newEntityID = Entities.identifyEntity(entityID); + if (newEntityID.isKnownID) { + that.pendingSelections.splice(i, 1); + that.addEntity(newEntityID); + i--; + } + } + + if (that.pendingSelections.length == 0) { + Script.clearInterval(pendingSelectionTimer); + pendingSelectionTimer = null; + } + } + that.addEntity = function(entityID) { - print("Adding: " + entityID.id); - var idx = that.selections.indexOf(entityID); - if (idx == -1) { - that.selections.push(entityID); + if (entityID.isKnownID) { + var idx = that.selections.indexOf(entityID); + if (idx == -1) { + that.selections.push(entityID); + } + } else { + var idx = that.pendingSelections.indexOf(entityID); + if (idx == -1) { + that.pendingSelections.push(entityID); + if (pendingSelectionTimer == null) { + pendingSelectionTimer = Script.setInterval(that._checkPendingSelections, PENDING_SELECTION_CHECK_INTERVAL); + } + } } that._update(); @@ -61,11 +113,22 @@ SelectionManager = (function() { that.selections.splice(idx, 1); } + var idx = that.pendingSelections.indexOf(entityID); + if (idx >= 0) { + that.pendingSelections.splice(idx, 1); + } + that._update(); }; that.clearSelections = function() { that.selections = []; + that.pendingSelections = []; + + if (pendingSelectionTimer !== null) { + Script.clearInterval(pendingSelectionTimer); + pendingSelectionTimer = null; + } that._update(); }; @@ -77,8 +140,6 @@ SelectionManager = (function() { that.worldDimensions = null; that.worldPosition = null; } else if (that.selections.length == 1) { - SelectionDisplay.setSpaceMode(SPACE_LOCAL); - var properties = Entities.getEntityProperties(that.selections[0]); that.localDimensions = properties.dimensions; that.localPosition = properties.position; @@ -86,10 +147,9 @@ SelectionManager = (function() { that.worldDimensions = properties.boundingBox.dimensions; that.worldPosition = properties.boundingBox.center; - } else { - // For 1+ selections we can only modify selections in world space - SelectionDisplay.setSpaceMode(SPACE_WORLD); + SelectionDisplay.setSpaceMode(SPACE_LOCAL); + } else { that.localRotation = null; that.localDimensions = null; that.localPosition = null; @@ -122,6 +182,9 @@ SelectionManager = (function() { y: brn.y + (that.worldDimensions.y / 2), z: brn.z + (that.worldDimensions.z / 2), }; + + // For 1+ selections we can only modify selections in world space + SelectionDisplay.setSpaceMode(SPACE_WORLD); } if (that.eventListener) { @@ -600,7 +663,7 @@ SelectionDisplay = (function () { currentSelection = entityID; entitySelected = true; - lastCameraPosition = Camera.getPosition(); + // lastCameraPosition = Camera.getPosition(); lastCameraOrientation = Camera.getOrientation(); if (event !== false) { @@ -623,10 +686,15 @@ SelectionDisplay = (function () { } + Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); + Overlays.editOverlay(highlightBox, { visible: false }); + + that.updateHandles(); + } + + that.updateRotationHandles = function() { var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; var innerActive = false; var innerAlpha = 0.2; var outerAlpha = 0.2; @@ -637,22 +705,18 @@ SelectionDisplay = (function () { } var rotateHandleOffset = 0.05; - var grabberMoveUpOffset = 0.1; var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; - // objectCenter = { x: properties.position.x, y: properties.position.y, z: properties.position.z }; - - var dimensions; + var dimensions, rotation; if (spaceMode == SPACE_LOCAL) { - objectCenter = SelectionManager.localPosition; - dimensions = SelectionManager.localDimensions; + rotation = SelectionManager.localRotation; } else { - objectCenter = SelectionManager.worldPosition; - dimensions = SelectionManager.worldDimensions; + rotation = SelectionManager.worldRotation; } objectCenter = SelectionManager.worldPosition; dimensions = SelectionManager.worldDimensions; + var position = objectCenter; top = objectCenter.y + (dimensions.y / 2); far = objectCenter.z + (dimensions.z / 2); @@ -662,7 +726,6 @@ SelectionDisplay = (function () { near = objectCenter.z - (dimensions.z / 2); right = objectCenter.x - (dimensions.x / 2); - // boundsCenter = { x: properties.boundingBox.center.x, y: properties.boundingBox.center.y, z: properties.boundingBox.center.z }; boundsCenter = objectCenter; var yawCorner; @@ -812,10 +875,12 @@ SelectionDisplay = (function () { } var rotateHandlesVisible = true; + var rotationOverlaysVisible = false; var translateHandlesVisible = true; var stretchHandlesVisible = true; var selectionBoxVisible = true; if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_X in case they Z") { + rotationOverlaysVisible = true; rotateHandlesVisible = false; translateHandlesVisible = false; stretchHandlesVisible = false; @@ -828,77 +893,33 @@ SelectionDisplay = (function () { rotateHandlesVisible = false; translateHandlesVisible = false; } - - Overlays.editOverlay(highlightBox, { visible: false }); - var dimensions, rotation, position; - if (spaceMode == SPACE_LOCAL) { - rotation = SelectionManager.localRotation; - dimensions = SelectionManager.localDimensions; - position = SelectionManager.localPosition; - } else { - rotation = SelectionManager.worldRotation; - dimensions = SelectionManager.worldDimensions; - position = SelectionManager.worldPosition; - } - - Overlays.editOverlay(grabberMoveUp, { visible: translateHandlesVisible, position: { x: boundsCenter.x, y: top + grabberMoveUpOffset, z: boundsCenter.z } }); - - that.updateHandles(); - + var rotation = SelectionManager.worldRotation; + var dimensions = SelectionManager.worldDimensions; + var position = SelectionManager.worldPosition; Overlays.editOverlay(baseOfEntityProjectionOverlay, { visible: true, solid:true, lineWidth: 2.0, - position: { x: properties.position.x, + position: { x: position.x, y: 0, - z: properties.position.z }, + z: position.z }, - dimensions: { x: properties.dimensions.x, y: properties.dimensions.z }, - rotation: properties.rotation, + dimensions: { x: dimensions.x, y: 0, z: dimensions.z }, + rotation: rotation, }); - Overlays.editOverlay(rotateOverlayTarget, { visible: false }); - - Overlays.editOverlay(rotateOverlayInner, - { - visible: false, - size: innerRadius, - innerRadius: 0.9, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, - { - visible: false, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, - { - visible: false, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotateZeroOverlay, { visible: false }); - Overlays.editOverlay(rotateCurrentOverlay, { visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { visible: rotationOverlaysVisible }); + Overlays.editOverlay(rotateZeroOverlay, { visible: rotationOverlaysVisible }); + Overlays.editOverlay(rotateCurrentOverlay, { visible: rotationOverlaysVisible }); // TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden Overlays.editOverlay(yawHandle, { visible: rotateHandlesVisible, position: yawCorner, rotation: yawHandleRotation}); Overlays.editOverlay(pitchHandle, { visible: rotateHandlesVisible, position: pitchCorner, rotation: pitchHandleRotation}); Overlays.editOverlay(rollHandle, { visible: rotateHandlesVisible, position: rollCorner, rotation: rollHandleRotation}); - - Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); }; that.setSpaceMode = function(newSpaceMode) { @@ -931,6 +952,8 @@ SelectionDisplay = (function () { return; } + that.updateRotationHandles(); + var rotation, dimensions, position; if (spaceMode == SPACE_LOCAL) { @@ -945,12 +968,14 @@ SelectionDisplay = (function () { var halfDimensions = Vec3.multiply(0.5, dimensions); - left = -halfDimensions.x; - right = halfDimensions.x; - top = halfDimensions.y; - bottom = -halfDimensions.y; + var left = -halfDimensions.x; + var right = halfDimensions.x; + var top = halfDimensions.y; + var bottom = -halfDimensions.y; var front = far = halfDimensions.z; - near = -halfDimensions.z; + var near = -halfDimensions.z; + + var worldTop = SelectionManager.worldDimensions.y / 2; var LBN = { x: left, y: bottom, z: near }; var RBN = { x: right, y: bottom, z: near }; @@ -1060,7 +1085,7 @@ SelectionDisplay = (function () { position: position, dimensions: dimensions, rotation: rotation, - visible: true, + visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"), }); Overlays.editOverlay(grabberEdgeTR, { visible: stretchHandlesVisible, rotation: rotation, position: EdgeTR }); @@ -1076,6 +1101,8 @@ SelectionDisplay = (function () { Overlays.editOverlay(grabberEdgeFR, { visible: stretchHandlesVisible, rotation: rotation, position: EdgeFR }); Overlays.editOverlay(grabberEdgeFL, { visible: stretchHandlesVisible, rotation: rotation, position: EdgeFL }); + var grabberMoveUpOffset = 0.1; + Overlays.editOverlay(grabberMoveUp, { visible: activeTool == null || mode == "TRANSLATE_UP_DOWN", position: { x: position.x, y: position.y + worldTop + grabberMoveUpOffset, z: position.z } }); }; that.setOverlaysVisible = function(isVisible) { @@ -1094,47 +1121,10 @@ SelectionDisplay = (function () { entitySelected = false; }; - function applyEntityProperties(data) { - for (var i = 0; i < data.length; i++) { - var entityID = data[i].entityID; - var properties = data[i].properties; - Entities.editEntity(entityID, properties); - } - selectionManager._update(); - }; - - // For currently selected entities, push a command to the UndoStack that uses the current entity properties for the - // redo command, and the saved properties for the undo command. - function pushCommandForSelections() { - var undoData = []; - var redoData = []; - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID.id]; - var currentProperties = Entities.getEntityProperties(entityID); - undoData.push({ - entityID: entityID, - properties: { - position: initialProperties.position, - rotation: initialProperties.rotation, - dimensions: initialProperties.dimensions, - }, - }); - redoData.push({ - entityID: entityID, - properties: { - position: currentProperties.position, - rotation: currentProperties.rotation, - dimensions: currentProperties.dimensions, - }, - }); - } - UndoStack.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); - } - var initialXZPick = null; var isConstrained = false; var startPosition = null; + var duplicatedEntityIDs = null; var translateXZTool = { mode: 'TRANSLATE_XZ', onBegin: function(event) { @@ -1149,43 +1139,32 @@ SelectionDisplay = (function () { // copy of the selected entities and move the _original_ entities, not // the new ones. if (event.isAlt) { + duplicatedEntityIDs = []; for (var otherEntityID in SelectionManager.savedProperties) { var properties = SelectionManager.savedProperties[otherEntityID]; var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties, + }); } + } else { + duplicatedEntityIDs = null; } isConstrained = false; }, onEnd: function(event, reason) { - if (reason == 'cancel') { - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID.id]; - Entities.editEntity(entityID, initialProperties); - } - } else { - pushCommandForSelections(); - } + pushCommandForSelections(duplicatedEntityIDs); + Overlays.editOverlay(xRailOverlay, { visible: false }); Overlays.editOverlay(zRailOverlay, { visible: false }); }, onMove: function(event) { - if (!entitySelected || mode !== "TRANSLATE_XZ") { - return; // not allowed - } - pickRay = Camera.computePickRay(event.x, event.y); - // translate mode left/right based on view toward entity - var newIntersection = rayPlaneIntersection(pickRay, - selectedEntityPropertiesOriginalPosition, - Quat.getFront(lastCameraOrientation)); - - var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); var pick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, { x: 0, y: 1, z: 0 }); var vector = Vec3.subtract(pick, initialXZPick); - // initialXZPick = pick; // If shifted, constrain to one axis if (event.isShifted) { @@ -1222,7 +1201,6 @@ SelectionDisplay = (function () { if (wantDebug) { print("translateXZ... "); Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); - Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); Vec3.print(" newPosition:", properties.position); Vec3.print(" newPosition:", newPosition); @@ -1239,22 +1217,28 @@ SelectionDisplay = (function () { mode: "TRANSLATE_UP_DOWN", onBegin: function(event) { SelectionManager.saveProperties(); - }, - onEnd: function(event, reason) { - if (reason == 'cancel') { - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID.id]; - Entities.editEntity(entityID, initialProperties); + + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt) { + duplicatedEntityIDs = []; + for (var otherEntityID in SelectionManager.savedProperties) { + var properties = SelectionManager.savedProperties[otherEntityID]; + var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties, + }); } } else { - pushCommandForSelections(); + duplicatedEntityIDs = null; } }, + onEnd: function(event, reason) { + pushCommandForSelections(duplicatedEntityIDs); + }, onMove: function(event) { - if (!entitySelected || mode !== "TRANSLATE_UP_DOWN") { - return; // not allowed - } pickRay = Camera.computePickRay(event.x, event.y); // translate mode left/right based on view toward entity @@ -1403,21 +1387,10 @@ SelectionDisplay = (function () { Overlays.editOverlay(yRailOverlay, { visible: false }); Overlays.editOverlay(zRailOverlay, { visible: false }); - if (reason == 'cancel') { - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID.id]; - Entities.editEntity(entityID, initialProperties); - } - } else { - pushCommandForSelections(); - } + pushCommandForSelections(); }; var onMove = function(event) { - if (!entitySelected || mode !== stretchMode) { - return; // not allowed - } var proportional = spaceMode == SPACE_WORLD || event.isShifted; var position, dimensions, rotation; @@ -1510,14 +1483,6 @@ SelectionDisplay = (function () { }; }; - that.cancelTool = function() { - if (activeTool) { - activeTool.onEnd(null, 'cancel'); - activeTool = null; - SelectionManager._update(); - } - }; - function addStretchTool(overlay, mode, pivot, direction) { if (!pivot) { pivot = Vec3.multiply(-1, direction); @@ -1563,23 +1528,49 @@ SelectionDisplay = (function () { onBegin: function(event) { SelectionManager.saveProperties(); initialPosition = SelectionManager.worldPosition; + + // Size the overlays to the current selection size + var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); + innerRadius = diagonal; + outerRadius = diagonal * 1.15; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + Overlays.editOverlay(rotateOverlayInner, + { + visible: true, + size: innerRadius, + innerRadius: 0.9, + alpha: innerAlpha + }); + + Overlays.editOverlay(rotateOverlayOuter, + { + visible: true, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha, + }); + + Overlays.editOverlay(rotateOverlayCurrent, + { + visible: true, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9, + }); }, onEnd: function(event, reason) { - if (reason == 'cancel') { - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID.id]; - Entities.editEntity(entityID, initialProperties); - } - } else { - pushCommandForSelections(); - } + Overlays.editOverlay(rotateOverlayInner, { visible: false }); + Overlays.editOverlay(rotateOverlayOuter, { visible: false }); + Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); + + pushCommandForSelections(); }, onMove: function(event) { - if (!entitySelected || mode !== "ROTATE_YAW") { - return; // not allowed - } - var debug = Menu.isOptionChecked("Debug Ryans Rotation Problems"); if (debug) { @@ -1591,9 +1582,6 @@ SelectionDisplay = (function () { Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); - Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true }); - Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true }); - Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true }); var result = Overlays.findRayIntersection(pickRay); @@ -1611,6 +1599,7 @@ SelectionDisplay = (function () { var distanceFromCenter = Vec3.distance(center, result.intersection); var snapToInner = false; + // var innerRadius = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; if (distanceFromCenter < innerRadius) { angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; snapToInner = true; @@ -1671,22 +1660,49 @@ SelectionDisplay = (function () { onBegin: function(event) { SelectionManager.saveProperties(); initialPosition = SelectionManager.worldPosition; + + // Size the overlays to the current selection size + var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); + var innerRadius = diagonal; + var outerRadius = diagonal * 1.15; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + Overlays.editOverlay(rotateOverlayInner, + { + visible: true, + size: innerRadius, + innerRadius: 0.9, + alpha: innerAlpha + }); + + Overlays.editOverlay(rotateOverlayOuter, + { + visible: true, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha, + }); + + Overlays.editOverlay(rotateOverlayCurrent, + { + visible: true, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9, + }); }, onEnd: function(event, reason) { - if (reason == 'cancel') { - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID.id]; - Entities.editEntity(entityID, initialProperties); - } - } else { - pushCommandForSelections(); - } + Overlays.editOverlay(rotateOverlayInner, { visible: false }); + Overlays.editOverlay(rotateOverlayOuter, { visible: false }); + Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); + + pushCommandForSelections(); }, onMove: function(event) { - if (!entitySelected || mode !== "ROTATE_PITCH") { - return; // not allowed - } var debug = Menu.isOptionChecked("Debug Ryans Rotation Problems"); if (debug) { @@ -1698,9 +1714,6 @@ SelectionDisplay = (function () { Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); - Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true }); - Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true }); - Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true }); var result = Overlays.findRayIntersection(pickRay); if (debug) { @@ -1777,22 +1790,49 @@ SelectionDisplay = (function () { onBegin: function(event) { SelectionManager.saveProperties(); initialPosition = SelectionManager.worldPosition; + + // Size the overlays to the current selection size + var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); + var innerRadius = diagonal; + var outerRadius = diagonal * 1.15; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + Overlays.editOverlay(rotateOverlayInner, + { + visible: true, + size: innerRadius, + innerRadius: 0.9, + alpha: innerAlpha + }); + + Overlays.editOverlay(rotateOverlayOuter, + { + visible: true, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha, + }); + + Overlays.editOverlay(rotateOverlayCurrent, + { + visible: true, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9, + }); }, onEnd: function(event, reason) { - if (reason == 'cancel') { - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID.id]; - Entities.editEntity(entityID, initialProperties); - } - } else { - pushCommandForSelections(); - } + Overlays.editOverlay(rotateOverlayInner, { visible: false }); + Overlays.editOverlay(rotateOverlayOuter, { visible: false }); + Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); + + pushCommandForSelections(); }, onMove: function(event) { - if (!entitySelected || mode !== "ROTATE_ROLL") { - return; // not allowed - } var debug = Menu.isOptionChecked("Debug Ryans Rotation Problems"); if (debug) { @@ -1804,9 +1844,6 @@ SelectionDisplay = (function () { Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); - Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true }); - Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true }); - Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true }); var result = Overlays.findRayIntersection(pickRay); if (debug) { diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index aa38baf9e2..a34f3d3985 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -1,4 +1,4 @@ -// + // newEditEntities.js // examples // @@ -73,6 +73,7 @@ var toolBar = (function () { newModelButton, newCubeButton, newSphereButton, + newLightButton, browseModelsButton, loadURLMenuItem, loadFileMenuItem, @@ -157,6 +158,15 @@ var toolBar = (function () { visible: true }); + newLightButton = toolBar.addTool({ + imageURL: toolIconUrl + "light.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: true + }); + } function toggleNewModelButton(active) { @@ -272,7 +282,7 @@ var toolBar = (function () { toggleNewModelButton(false); file = Window.browse("Select your model file ...", - Settings.getValue("LastModelUploadLocation").path(), + Settings.getValue("LastModelUploadLocation").path(), "Model files (*.fst *.fbx)"); //"Model files (*.fst *.fbx *.svo)"); if (file !== null) { @@ -295,7 +305,7 @@ var toolBar = (function () { var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); if (position.x > 0 && position.y > 0 && position.z > 0) { - Entities.addEntity({ + Entities.addEntity({ type: "Box", position: position, dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, @@ -312,14 +322,39 @@ var toolBar = (function () { var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); if (position.x > 0 && position.y > 0 && position.z > 0) { - Entities.addEntity({ + Entities.addEntity({ type: "Sphere", position: position, dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, color: { red: 255, green: 0, blue: 0 } }); } else { - print("Can't create box: Box would be out of bounds."); + print("Can't create sphere: Sphere would be out of bounds."); + } + return true; + } + + if (newLightButton === toolBar.clicked(clickedOverlay)) { + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + + if (position.x > 0 && position.y > 0 && position.z > 0) { + Entities.addEntity({ + type: "Light", + position: position, + dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, + isSpotlight: false, + diffuseColor: { red: 255, green: 255, blue: 255 }, + ambientColor: { red: 255, green: 255, blue: 255 }, + specularColor: { red: 0, green: 0, blue: 0 }, + + constantAttenuation: 1, + linearAttenuation: 0, + quadraticAttenuation: 0, + exponent: 0, + cutoff: 180, // in degrees + }); + } else { + print("Can't create Light: Light would be out of bounds."); } return true; } @@ -433,7 +468,7 @@ function mousePressEvent(event) { var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); if (0 < x && sizeOK) { @@ -467,7 +502,7 @@ function mousePressEvent(event) { w: selectedEntityProperties.rotation.w, }; selectedEntityProperties.glowLevel = 0.0; - + print("Clicked on " + selectedEntityID.id + " " + entitySelected); tooltip.updateText(selectedEntityProperties); tooltip.show(true); @@ -481,7 +516,7 @@ function mouseMoveEvent(event) { if (!isActive) { return; } - + // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing if (selectionDisplay.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { return; @@ -496,11 +531,11 @@ function mouseMoveEvent(event) { } var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), entityIntersection.properties.position)) * 180 / 3.14; - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); if (entityIntersection.entityID.isKnownID && sizeOK) { @@ -510,7 +545,7 @@ function mouseMoveEvent(event) { highlightedEntityID = entityIntersection.entityID; selectionDisplay.highlightSelectable(entityIntersection.entityID); } - + } } @@ -550,9 +585,9 @@ function setupModelMenus() { } Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L", + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L", afterItem: "Paste Models", isCheckable: true }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S", + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S", afterItem: "Allow Select Large Models", isCheckable: true }); Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" }); @@ -606,14 +641,24 @@ function handeMenuEvent(menuItem) { } else if (menuItem == "Allow Select Large Models") { allowLargeModels = Menu.isOptionChecked("Allow Select Large Models"); } else if (menuItem == "Delete") { - if (entitySelected) { + if (SelectionManager.hasSelection()) { print(" Delete Entity.... selectedEntityID="+ selectedEntityID); + SelectionManager.saveProperties(); + var savedProperties = []; for (var i = 0; i < selectionManager.selections.length; i++) { - Entities.deleteEntity(selectionManager.selections[i]); + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID.id]; + SelectionManager.savedProperties[entityID.id]; + savedProperties.push({ + entityID: entityID, + properties: initialProperties + }); + Entities.deleteEntity(entityID); } + SelectionManager.clearSelections(); + pushCommandForSelections([], savedProperties); selectionDisplay.unselect(selectedEntityID); entitySelected = false; - selectionManager.clearSelections(); } else { print(" Delete Entity.... not holding..."); } @@ -623,15 +668,16 @@ function handeMenuEvent(menuItem) { editModelID = -1; if (selectionManager.selections.length == 1) { print(" Edit Properties.... selectedEntityID="+ selectedEntityID); - editModelID = selectedEntityID; + editModelID = selectionManager.selections[0]; } else { print(" Edit Properties.... not holding..."); } if (editModelID != -1) { print(" Edit Properties.... about to edit properties..."); entityPropertyDialogBox.openDialog(editModelID); + selectionManager._update(); } - + } else if (menuItem == "Paste Models") { modelImporter.paste(); } else if (menuItem == "Export Models") { @@ -659,8 +705,6 @@ Controller.keyReleaseEvent.connect(function (event) { handeMenuEvent("Delete"); } else if (event.text == "TAB") { selectionDisplay.toggleSpaceMode(); - } else if (event.text == "ESC") { - selectionDisplay.cancelTool(); } else if (event.text == "f") { if (entitySelected) { // Get latest properties @@ -671,8 +715,130 @@ Controller.keyReleaseEvent.connect(function (event) { if (isActive) { cameraManager.enable(); } + } else { + var delta = null; + + if (event.text == 'UP') { + if (event.isControl || event.isAlt) { + delta = { x: 0, y: 1, z: 0 }; + } else { + delta = { x: 0, y: 0, z: -1 }; + } + } else if (event.text == 'DOWN') { + if (event.isControl || event.isAlt) { + delta = { x: 0, y: -1, z: 0 }; + } else { + delta = { x: 0, y: 0, z: 1 }; + } + } else if (event.text == 'LEFT') { + delta = { x: -1, y: 0, z: 0 }; + } else if (event.text == 'RIGHT') { + delta = { x: 1, y: 0, z: 0 }; + } + + if (delta != null) { + // Adjust delta so that movements are relative to the current camera orientation + var lookDirection = Quat.getFront(Camera.getOrientation()); + lookDirection.z *= -1; + + var angle = Math.atan2(lookDirection.z, lookDirection.x); + angle -= (Math.PI / 4); + + var rotation = Math.floor(angle / (Math.PI / 2)) * (Math.PI / 2); + var rotator = Quat.fromPitchYawRollRadians(0, rotation, 0); + + delta = Vec3.multiplyQbyV(rotator, delta); + + SelectionManager.saveProperties(); + + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = selectionManager.selections[i]; + var properties = Entities.getEntityProperties(entityID); + Entities.editEntity(entityID, { + position: Vec3.sum(properties.position, delta) + }); + } + + pushCommandForSelections(); + + selectionManager._update(); + } } }); +// When an entity has been deleted we need a way to "undo" this deletion. Because it's not currently +// possible to create an entity with a specific id, earlier undo commands to the deleted entity +// will fail if there isn't a way to find the new entity id. +DELETED_ENTITY_MAP = { +} +function applyEntityProperties(data) { + var properties = data.setProperties; + var selectedEntityIDs = []; + for (var i = 0; i < properties.length; i++) { + var entityID = properties[i].entityID; + if (DELETED_ENTITY_MAP[entityID.id] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID.id]; + } + Entities.editEntity(entityID, properties[i].properties); + selectedEntityIDs.push(entityID); + } + for (var i = 0; i < data.createEntities.length; i++) { + var entityID = data.createEntities[i].entityID; + var properties = data.createEntities[i].properties; + var newEntityID = Entities.addEntity(properties); + DELETED_ENTITY_MAP[entityID.id] = newEntityID; + print(newEntityID.isKnownID); + if (data.selectCreated) { + selectedEntityIDs.push(newEntityID); + } + } + for (var i = 0; i < data.deleteEntities.length; i++) { + var entityID = data.deleteEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID.id] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID.id]; + } + Entities.deleteEntity(entityID); + } + selectionManager.setSelections(selectedEntityIDs); +}; + +// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the +// redo command, and the saved properties for the undo command. Also, include create and delete entity data. +function pushCommandForSelections(createdEntityData, deletedEntityData) { + var undoData = { + setProperties: [], + createEntities: deletedEntityData || [], + deleteEntities: createdEntityData || [], + selectCreated: true, + }; + var redoData = { + setProperties: [], + createEntities: createdEntityData || [], + deleteEntities: deletedEntityData || [], + selectCreated: false, + }; + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID.id]; + var currentProperties = Entities.getEntityProperties(entityID); + undoData.setProperties.push({ + entityID: entityID, + properties: { + position: initialProperties.position, + rotation: initialProperties.rotation, + dimensions: initialProperties.dimensions, + }, + }); + redoData.setProperties.push({ + entityID: entityID, + properties: { + position: currentProperties.position, + rotation: currentProperties.rotation, + dimensions: currentProperties.dimensions, + }, + }); + } + UndoStack.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); +} diff --git a/examples/spotlightExample.js b/examples/spotlightExample.js new file mode 100644 index 0000000000..5eb5432f3f --- /dev/null +++ b/examples/spotlightExample.js @@ -0,0 +1,37 @@ +// +// spotlightExample.js +// examples +// +// Created by Brad Hefta-Gaub on 10/28/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that demonstrates creating and editing a particle +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var lightProperties = { + type: "Light", + position: { x: 0, y: 0, z: 0 }, + dimensions: { x: 1000, y: 1000, z: 1000 }, + angularVelocity: { x: 0, y: 10, z: 0 }, + angularDamping: 0, + + isSpotlight: true, + diffuseColor: { red: 255, green: 255, blue: 255 }, + ambientColor: { red: 255, green: 255, blue: 255 }, + specularColor: { red: 255, green: 255, blue: 255 }, + + constantAttenuation: 1, + linearAttenuation: 0, + quadraticAttenuation: 0, + exponent: 0, + cutoff: 180, // in degrees +}; + +var spotlightID = Entities.addEntity(lightProperties); + +Script.scriptEnding.connect(function() { + Entities.deleteEntity(spotlightID); +}); diff --git a/ice-server/src/main.cpp b/ice-server/src/main.cpp index 21c8b563b1..2326984668 100644 --- a/ice-server/src/main.cpp +++ b/ice-server/src/main.cpp @@ -11,7 +11,7 @@ #include -#include +#include #include "IceServer.h" @@ -20,7 +20,7 @@ int main(int argc, char* argv[]) { setvbuf(stdout, NULL, _IOLBF, 0); #endif - qInstallMessageHandler(Logging::verboseMessageHandler); + qInstallMessageHandler(LogHandler::verboseMessageHandler); IceServer iceServer(argc, argv); return iceServer.exec(); diff --git a/interface/external/gverb/readme.txt b/interface/external/gverb/readme.txt index aa2fe8a602..1a85659b91 100644 --- a/interface/external/gverb/readme.txt +++ b/interface/external/gverb/readme.txt @@ -1,7 +1,7 @@ Instructions for adding the Gverb library to Interface (This is a required library) -Clément Brisset, Octobre 22nd, 2014 +Clément Brisset, October 22nd, 2014 1. Go to https://github.com/highfidelity/gverb Or download the sources directly via this link: diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 438713e03d..d62b92bd49 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -58,7 +58,7 @@ #include #include #include -#include +#include #include #include #include @@ -116,12 +116,10 @@ const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::D const QString DEFAULT_SCRIPTS_JS_URL = "http://public.highfidelity.io/scripts/defaultScripts.js"; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (message.size() > 0) { - QString dateString = QDateTime::currentDateTime().toTimeSpec(Qt::LocalTime).toString(Qt::ISODate); - QString formattedMessage = QString("[%1] %2\n").arg(dateString).arg(message); - - fprintf(stdout, "%s", qPrintable(formattedMessage)); - Application::getInstance()->getLogger()->addMessage(qPrintable(formattedMessage)); + QString logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message); + + if (!logMessage.isEmpty()) { + Application::getInstance()->getLogger()->addMessage(qPrintable(logMessage)); } } @@ -581,10 +579,6 @@ void Application::initializeGL() { float startupTime = (float)_applicationStartupTime.elapsed() / 1000.0; _justStarted = false; qDebug("Startup time: %4.2f seconds.", startupTime); - const char LOGSTASH_INTERFACE_START_TIME_KEY[] = "interface-start-time"; - - // ask the Logstash class to record the startup time - Logging::stashValue(STAT_TYPE_TIMER, LOGSTASH_INTERFACE_START_TIME_KEY, startupTime); } // update before the first render @@ -3175,7 +3169,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { } else { // if not rendering the billboard, the region is in device independent coordinates; must convert to device QSize size = getTextureCache()->getFrameBufferSize(); - float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio(); + float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * _renderResolutionScale; int x = region.x() * ratio, y = region.y() * ratio, width = region.width() * ratio, height = region.height() * ratio; glViewport(x, size.height() - y - height, width, height); glScissor(x, size.height() - y - height, width, height); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index dd84eb3211..7d039387bb 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -1018,6 +1018,27 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { } } +void Audio::parseAudioEnvironmentData(const QByteArray &packet) { + int numBytesPacketHeader = numBytesForPacketHeader(packet); + const char* dataAt = packet.constData() + numBytesPacketHeader; + + char bitset; + memcpy(&bitset, dataAt, sizeof(char)); + dataAt += sizeof(char); + + bool hasReverb = oneAtBit(bitset, HAS_REVERB_BIT);; + if (hasReverb) { + float reverbTime, wetLevel; + memcpy(&reverbTime, dataAt, sizeof(float)); + dataAt += sizeof(float); + memcpy(&wetLevel, dataAt, sizeof(float)); + dataAt += sizeof(float); + _receivedAudioStream.setReverb(reverbTime, wetLevel); + } else { + _receivedAudioStream.clearReverb(); + } +} + void Audio::sendDownstreamAudioStatsPacket() { // since this function is called every second, we'll sample for some of our stats here diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 900b6ce0d6..fcbfb12761 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -125,6 +125,7 @@ public slots: void stop(); void addReceivedAudioToStream(const QByteArray& audioByteArray); void parseAudioStreamStatsPacket(const QByteArray& packet); + void parseAudioEnvironmentData(const QByteArray& packet); void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples); void handleAudioInput(); void reset(); diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 868654b9da..6f9f4cae68 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -49,14 +49,18 @@ void DatagramProcessor::processDatagrams() { PacketType incomingType = packetTypeForPacket(incomingPacket); // only process this packet if we have a match on the packet version switch (incomingType) { + case PacketTypeAudioEnvironment: + case PacketTypeAudioStreamStats: case PacketTypeMixedAudio: - case PacketTypeSilentAudioFrame: - case PacketTypeAudioStreamStats: { - if (incomingType != PacketTypeAudioStreamStats) { - QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToStream", Qt::QueuedConnection, + case PacketTypeSilentAudioFrame: { + if (incomingType == PacketTypeAudioStreamStats) { + QMetaObject::invokeMethod(&application->_audio, "parseAudioStreamStatsPacket", Qt::QueuedConnection, + Q_ARG(QByteArray, incomingPacket)); + } else if (incomingType == PacketTypeAudioEnvironment) { + QMetaObject::invokeMethod(&application->_audio, "parseAudioEnvironmentData", Qt::QueuedConnection, Q_ARG(QByteArray, incomingPacket)); } else { - QMetaObject::invokeMethod(&application->_audio, "parseAudioStreamStatsPacket", Qt::QueuedConnection, + QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToStream", Qt::QueuedConnection, Q_ARG(QByteArray, incomingPacket)); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 274afd4bca..4e453bfde2 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1266,7 +1266,6 @@ void Menu::changeVSync() { } void Menu::changeRenderTargetFramerate(QAction* action) { bool vsynOn = Application::getInstance()->isVSyncOn(); - unsigned int framerate = Application::getInstance()->getRenderTargetFramerate(); QString text = action->text(); if (text == MenuOption::RenderTargetFramerateUnlimited) { @@ -1315,7 +1314,7 @@ void Menu::displayNameLocationResponse(const QString& errorString) { void Menu::toggleLocationList() { if (!_userLocationsDialog) { JavascriptObjectMap locationObjectMap; - locationObjectMap.insert("InterfaceLocation", LocationScriptingInterface::getInstance()); + locationObjectMap.insert("InterfaceLocation", &AddressManager::getInstance()); _userLocationsDialog = DataWebDialog::dialogForPath("/user/locations", locationObjectMap); } @@ -1359,7 +1358,7 @@ void Menu::nameLocation() { if (!_newLocationDialog) { JavascriptObjectMap locationObjectMap; - locationObjectMap.insert("InterfaceLocation", LocationScriptingInterface::getInstance()); + locationObjectMap.insert("InterfaceLocation", &AddressManager::getInstance()); _newLocationDialog = DataWebDialog::dialogForPath("/user/locations/new", locationObjectMap); } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 3fcc05d7c7..59c8c90d1c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -871,7 +871,7 @@ const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f; void Avatar::setJointModelPositionAndOrientation(int index, glm::vec3 position, const glm::quat& rotation) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation", - Qt::BlockingQueuedConnection, Q_ARG(const int, index), Q_ARG(const glm::vec3, position), + Qt::AutoConnection, Q_ARG(const int, index), Q_ARG(const glm::vec3, position), Q_ARG(const glm::quat&, rotation)); } else { _skeletonModel.inverseKinematics(index, position, rotation, SCRIPT_PRIORITY); @@ -881,7 +881,7 @@ void Avatar::setJointModelPositionAndOrientation(int index, glm::vec3 position, void Avatar::setJointModelPositionAndOrientation(const QString& name, glm::vec3 position, const glm::quat& rotation) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation", - Qt::BlockingQueuedConnection, Q_ARG(const QString&, name), Q_ARG(const glm::vec3, position), + Qt::AutoConnection, Q_ARG(const QString&, name), Q_ARG(const glm::vec3, position), Q_ARG(const glm::quat&, rotation)); } else { _skeletonModel.inverseKinematics(getJointIndex(name), position, rotation, SCRIPT_PRIORITY); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 178113efc1..623631d5e5 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -49,8 +49,9 @@ const float PITCH_SPEED = 100.0f; // degrees/sec const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions const float COLLISION_RADIUS_SCALE = 0.125f; -const float MIN_KEYBOARD_CONTROL_SPEED = 0.50f; const float MAX_WALKING_SPEED = 4.5f; +const float MAX_BOOST_SPEED = 0.5f * MAX_WALKING_SPEED; // keyboard motor gets additive boost below this speed +const float MIN_AVATAR_SPEED = 0.05f; // speed is set to zero below this // TODO: normalize avatar speed for standard avatar size, then scale all motion logic // to properly follow avatar size. @@ -1286,19 +1287,21 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe // Compute the target keyboard velocity (which ramps up slowly, and damps very quickly) // the max magnitude of which depends on what we're doing: - float motorLength = glm::length(_keyboardMotorVelocity); + float motorSpeed = glm::length(_keyboardMotorVelocity); float finalMaxMotorSpeed = hasFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED; float speedGrowthTimescale = 2.0f; float speedIncreaseFactor = 1.8f; - motorLength *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor; - if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) { + motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor; + const float maxBoostSpeed = _scale * MAX_BOOST_SPEED; + if (motorSpeed < maxBoostSpeed) { // an active keyboard motor should never be slower than this - motorLength = _scale * MIN_KEYBOARD_CONTROL_SPEED; - motorEfficiency = 1.0f; - } else if (motorLength > finalMaxMotorSpeed) { - motorLength = finalMaxMotorSpeed; + float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; + motorSpeed += MIN_AVATAR_SPEED * boostCoefficient; + motorEfficiency += (1.0f - motorEfficiency) * boostCoefficient; + } else if (motorSpeed > finalMaxMotorSpeed) { + motorSpeed = finalMaxMotorSpeed; } - _keyboardMotorVelocity = motorLength * direction; + _keyboardMotorVelocity = motorSpeed * direction; _isPushing = true; } } else { @@ -1430,7 +1433,6 @@ void MyAvatar::updatePosition(float deltaTime) { } // update position - const float MIN_AVATAR_SPEED = 0.075f; if (speed > MIN_AVATAR_SPEED) { applyPositionDelta(deltaTime * velocity); } diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 2c7c970376..7eb28c2d3e 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -24,6 +24,7 @@ #include "EntityTreeRenderer.h" #include "RenderableBoxEntityItem.h" +#include "RenderableLightEntityItem.h" #include "RenderableModelEntityItem.h" #include "RenderableSphereEntityItem.h" @@ -39,6 +40,7 @@ EntityTreeRenderer::EntityTreeRenderer() : REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) } EntityTreeRenderer::~EntityTreeRenderer() { diff --git a/interface/src/entities/RenderableLightEntityItem.cpp b/interface/src/entities/RenderableLightEntityItem.cpp new file mode 100644 index 0000000000..bf9939e164 --- /dev/null +++ b/interface/src/entities/RenderableLightEntityItem.cpp @@ -0,0 +1,89 @@ +// +// RenderableLightEntityItem.cpp +// interface/src +// +// Created by Brad Hefta-Gaub on 8/6/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 +// + +#include + +#include + +#include "InterfaceConfig.h" + +#include +#include + + +#include "Application.h" +#include "Menu.h" +#include "EntityTreeRenderer.h" +#include "RenderableLightEntityItem.h" + + +EntityItem* RenderableLightEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return new RenderableLightEntityItem(entityID, properties); +} + +void RenderableLightEntityItem::render(RenderArgs* args) { + PerformanceTimer perfTimer("RenderableLightEntityItem::render"); + assert(getType() == EntityTypes::Light); + glm::vec3 position = getPositionInMeters(); + glm::vec3 center = getCenterInMeters(); + glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; + glm::quat rotation = getRotation(); + float largestDiameter = glm::max(dimensions.x, dimensions.y, dimensions.z); + + const float MAX_COLOR = 255.0f; + float diffuseR = getDiffuseColor()[RED_INDEX] / MAX_COLOR; + float diffuseG = getDiffuseColor()[GREEN_INDEX] / MAX_COLOR; + float diffuseB = getDiffuseColor()[BLUE_INDEX] / MAX_COLOR; + + float ambientR = getAmbientColor()[RED_INDEX] / MAX_COLOR; + float ambientG = getAmbientColor()[GREEN_INDEX] / MAX_COLOR; + float ambientB = getAmbientColor()[BLUE_INDEX] / MAX_COLOR; + + float specularR = getSpecularColor()[RED_INDEX] / MAX_COLOR; + float specularG = getSpecularColor()[GREEN_INDEX] / MAX_COLOR; + float specularB = getSpecularColor()[BLUE_INDEX] / MAX_COLOR; + + glm::vec3 ambient = glm::vec3(ambientR, ambientG, ambientB); + glm::vec3 diffuse = glm::vec3(diffuseR, diffuseG, diffuseB); + glm::vec3 specular = glm::vec3(specularR, specularG, specularB); + glm::vec3 direction = IDENTITY_FRONT * rotation; + float constantAttenuation = getConstantAttenuation(); + float linearAttenuation = getLinearAttenuation(); + float quadraticAttenuation = getQuadraticAttenuation(); + float exponent = getExponent(); + float cutoff = glm::radians(getCutoff()); + + if (_isSpotlight) { + Application::getInstance()->getDeferredLightingEffect()->addSpotLight(position, largestDiameter / 2.0f, + ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation, + direction, exponent, cutoff); + } else { + Application::getInstance()->getDeferredLightingEffect()->addPointLight(position, largestDiameter / 2.0f, + ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation); + } + + bool wantDebug = false; + if (wantDebug) { + glColor4f(diffuseR, diffuseG, diffuseB, 1.0f); + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + + glScalef(dimensions.x, dimensions.y, dimensions.z); + Application::getInstance()->getDeferredLightingEffect()->renderWireSphere(0.5f, 15, 15); + glPopMatrix(); + glPopMatrix(); + } +}; diff --git a/interface/src/entities/RenderableLightEntityItem.h b/interface/src/entities/RenderableLightEntityItem.h new file mode 100644 index 0000000000..cecd9b761e --- /dev/null +++ b/interface/src/entities/RenderableLightEntityItem.h @@ -0,0 +1,40 @@ +// +// RenderableLightEntityItem.h +// interface/src/entities +// +// Created by Brad Hefta-Gaub on 8/6/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 +// + +#ifndef hifi_RenderableLightEntityItem_h +#define hifi_RenderableLightEntityItem_h + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +class RenderableLightEntityItem : public LightEntityItem { +public: + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + RenderableLightEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + LightEntityItem(entityItemID, properties) + { } + + virtual void render(RenderArgs* args); +}; + + +#endif // hifi_RenderableLightEntityItem_h diff --git a/interface/src/gpu/Batch.cpp b/interface/src/gpu/Batch.cpp index 054bc09846..b7db58ea30 100644 --- a/interface/src/gpu/Batch.cpp +++ b/interface/src/gpu/Batch.cpp @@ -111,6 +111,8 @@ void Batch::runCommand(Command com, uint32 offset) { CASE_COMMAND(glColor4f); CASE_COMMAND(glMaterialf); CASE_COMMAND(glMaterialfv); + default: + break; } } diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index e949e9a811..5682f37a04 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -122,21 +122,36 @@ void Model::setOffset(const glm::vec3& offset) { void Model::initProgram(ProgramObject& program, Model::Locations& locations, int specularTextureUnit) { program.bind(); - locations.tangent = program.attributeLocation("tangent"); - locations.alphaThreshold = program.uniformLocation("alphaThreshold"); - program.setUniformValue("diffuseMap", 0); - program.setUniformValue("normalMap", 1); - program.setUniformValue("specularMap", specularTextureUnit); - program.release(); -} -void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations, int specularTextureUnit) { - initProgram(program, locations, specularTextureUnit); - - program.bind(); - locations.clusterMatrices = program.uniformLocation("clusterMatrices"); - locations.clusterIndices = program.attributeLocation("clusterIndices"); - locations.clusterWeights = program.attributeLocation("clusterWeights"); +#ifdef Q_OS_MAC + // HACK: Assign explicitely the attribute channel to avoid a bug on Yosemite + glBindAttribLocation(program.programId(), 4, "tangent"); + glLinkProgram(program.programId()); +#endif + + locations.tangent = program.attributeLocation("tangent"); + locations.alphaThreshold = program.uniformLocation("alphaThreshold"); + program.setUniformValue("diffuseMap", 0); + program.setUniformValue("normalMap", 1); + program.setUniformValue("specularMap", specularTextureUnit); + program.release(); +} + +void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations, int specularTextureUnit) { + initProgram(program, locations, specularTextureUnit); + +#ifdef Q_OS_MAC + // HACK: Assign explicitely the attribute channel to avoid a bug on Yosemite + glBindAttribLocation(program.programId(), 5, "clusterIndices"); + glBindAttribLocation(program.programId(), 6, "clusterWeights"); + glLinkProgram(program.programId()); +#endif + + program.bind(); + locations.clusterMatrices = program.uniformLocation("clusterMatrices"); + + locations.clusterIndices = program.attributeLocation("clusterIndices"); + locations.clusterWeights = program.attributeLocation("clusterWeights"); program.release(); } @@ -1737,7 +1752,8 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl mesh.texCoords.size() * sizeof(glm::vec2) + (mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0); //skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4); - GLBATCH(glVertexAttribPointer)(skinLocations->clusterIndices, 4, GL_FLOAT, GL_TRUE, 0, (const void*) offset); + GLBATCH(glVertexAttribPointer)(skinLocations->clusterIndices, 4, GL_FLOAT, GL_TRUE, 0, + reinterpret_cast(offset)); //skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT, // offset + vertexCount * sizeof(glm::vec4), 4); GLBATCH(glVertexAttribPointer)(skinLocations->clusterWeights, 4, GL_FLOAT, GL_TRUE, 0, (const void*) (offset + vertexCount * sizeof(glm::vec4))); diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 59578951f8..366659b633 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -83,6 +83,12 @@ void InboundAudioStream::clearBuffer() { _currentJitterBufferFrames = 0; } +void InboundAudioStream::setReverb(float reverbTime, float wetLevel) { + _hasReverb = true; + _reverbTime = reverbTime; + _wetLevel = wetLevel; +} + void InboundAudioStream::perSecondCallbackForUpdatingStats() { _incomingSequenceNumberStats.pushStatsToHistory(); _timeGapStatsForDesiredCalcOnTooManyStarves.currentIntervalComplete(); @@ -163,22 +169,9 @@ int InboundAudioStream::parseData(const QByteArray& packet) { } int InboundAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { - int read = 0; - if (type == PacketTypeMixedAudio) { - memcpy(&_hasReverb, packetAfterSeqNum.data() + read, sizeof(bool)); - read += sizeof(bool); - - if (_hasReverb) { - memcpy(&_reverbTime, packetAfterSeqNum.data() + read, sizeof(float)); - read += sizeof(float); - memcpy(&_wetLevel, packetAfterSeqNum.data() + read, sizeof(float)); - read += sizeof(float); - } - } - // mixed audio packets do not have any info between the seq num and the audio data. - numAudioSamples = (packetAfterSeqNum.size() - read) / sizeof(int16_t); - return read; + numAudioSamples = packetAfterSeqNum.size() / sizeof(int16_t); + return 0; } int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 3e69db0afb..ad2b7266ed 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -45,6 +45,9 @@ const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES = 50; const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION = 10; const bool DEFAULT_REPETITION_WITH_FADE = true; +// Audio Env bitset +const int HAS_REVERB_BIT = 0; // 1st bit + class InboundAudioStream : public NodeData { Q_OBJECT public: @@ -158,6 +161,8 @@ public: bool hasReverb() const { return _hasReverb; } float getRevebTime() const { return _reverbTime; } float getWetLevel() const { return _wetLevel; } + void setReverb(float reverbTime, float wetLevel); + void clearReverb() { _hasReverb = false; } public slots: /// This function should be called every second for all the stats to function properly. If dynamic jitter buffers diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 90f56e36b9..83d13e369d 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -68,8 +68,8 @@ EntityItemProperties::EntityItemProperties() : _animationFPS(ModelEntityItem::DEFAULT_ANIMATION_FPS), _glowLevel(0.0f), _localRenderAlpha(1.0f), + _isSpotlight(false), - _naturalDimensions(1.0f, 1.0f, 1.0f), _colorChanged(false), _modelURLChanged(false), _animationURLChanged(false), @@ -78,11 +78,51 @@ EntityItemProperties::EntityItemProperties() : _animationFPSChanged(false), _glowLevelChanged(false), _localRenderAlphaChanged(false), + _isSpotlightChanged(false), - _defaultSettings(true) + _diffuseColor(), + _ambientColor(), + _specularColor(), + _constantAttenuation(1.0f), + _linearAttenuation(0.0f), + _quadraticAttenuation(0.0f), + _exponent(0.0f), + _cutoff(PI), + + _diffuseColorChanged(false), + _ambientColorChanged(false), + _specularColorChanged(false), + _constantAttenuationChanged(false), + _linearAttenuationChanged(false), + _quadraticAttenuationChanged(false), + _exponentChanged(false), + _cutoffChanged(false), + + _defaultSettings(true), + _sittingPoints(NULL), + _naturalDimensions(1.0f, 1.0f, 1.0f) { } +EntityItemProperties::~EntityItemProperties() { + if (_sittingPoints) { + delete _sittingPoints; + _sittingPoints = NULL; + } +} + +void EntityItemProperties::setSittingPoints(const QVector& sittingPoints) { + if (!_sittingPoints) { + _sittingPoints = new QVector; + } + _sittingPoints->clear(); + + foreach (SittingPoint sitPoint, sittingPoints) { + _sittingPoints->append(sitPoint); + } +} + + void EntityItemProperties::debugDump() const { qDebug() << "EntityItemProperties..."; qDebug() << " _type=" << EntityTypes::getEntityTypeName(_type); @@ -120,6 +160,15 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping); CHECK_PROPERTY_CHANGE(PROP_IGNORE_FOR_COLLISIONS, ignoreForCollisions); CHECK_PROPERTY_CHANGE(PROP_COLLISIONS_WILL_MOVE, collisionsWillMove); + CHECK_PROPERTY_CHANGE(PROP_IS_SPOTLIGHT, isSpotlight); + CHECK_PROPERTY_CHANGE(PROP_DIFFUSE_COLOR, diffuseColor); + CHECK_PROPERTY_CHANGE(PROP_AMBIENT_COLOR, ambientColor); + CHECK_PROPERTY_CHANGE(PROP_SPECULAR_COLOR, specularColor); + CHECK_PROPERTY_CHANGE(PROP_CONSTANT_ATTENUATION, constantAttenuation); + CHECK_PROPERTY_CHANGE(PROP_LINEAR_ATTENUATION, linearAttenuation); + CHECK_PROPERTY_CHANGE(PROP_QUADRATIC_ATTENUATION, quadraticAttenuation); + CHECK_PROPERTY_CHANGE(PROP_EXPONENT, exponent); + CHECK_PROPERTY_CHANGE(PROP_CUTOFF, cutoff); return changedProperties; } @@ -161,17 +210,30 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions); COPY_PROPERTY_TO_QSCRIPTVALUE(collisionsWillMove); + COPY_PROPERTY_TO_QSCRIPTVALUE(isSpotlight); + COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(diffuseColor, getDiffuseColor()); + COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(ambientColor, getAmbientColor()); + COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(specularColor, getSpecularColor()); + COPY_PROPERTY_TO_QSCRIPTVALUE(constantAttenuation); + COPY_PROPERTY_TO_QSCRIPTVALUE(linearAttenuation); + COPY_PROPERTY_TO_QSCRIPTVALUE(quadraticAttenuation); + COPY_PROPERTY_TO_QSCRIPTVALUE(exponent); + COPY_PROPERTY_TO_QSCRIPTVALUE(cutoff); // Sitting properties support QScriptValue sittingPoints = engine->newObject(); - for (int i = 0; i < _sittingPoints.size(); ++i) { - QScriptValue sittingPoint = engine->newObject(); - sittingPoint.setProperty("name", _sittingPoints[i].name); - sittingPoint.setProperty("position", vec3toScriptValue(engine, _sittingPoints[i].position)); - sittingPoint.setProperty("rotation", quatToScriptValue(engine, _sittingPoints[i].rotation)); - sittingPoints.setProperty(i, sittingPoint); + if (_sittingPoints) { + for (int i = 0; i < _sittingPoints->size(); ++i) { + QScriptValue sittingPoint = engine->newObject(); + sittingPoint.setProperty("name", _sittingPoints->at(i).name); + sittingPoint.setProperty("position", vec3toScriptValue(engine, _sittingPoints->at(i).position)); + sittingPoint.setProperty("rotation", quatToScriptValue(engine, _sittingPoints->at(i).rotation)); + sittingPoints.setProperty(i, sittingPoint); + } + sittingPoints.setProperty("length", _sittingPoints->size()); + } else { + sittingPoints.setProperty("length", 0); } - sittingPoints.setProperty("length", _sittingPoints.size()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable AABox aaBox = getAABoxInMeters(); @@ -220,6 +282,15 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localRenderAlpha, setLocalRenderAlpha); COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(ignoreForCollisions, setIgnoreForCollisions); COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(collisionsWillMove, setCollisionsWillMove); + COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(isSpotlight, setIsSpotlight); + COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(diffuseColor, setDiffuseColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(ambientColor, setAmbientColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(specularColor, setSpecularColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(constantAttenuation, setConstantAttenuation); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(linearAttenuation, setLinearAttenuation); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(quadraticAttenuation, setQuadraticAttenuation); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(exponent, setExponent); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(cutoff, setCutoff); _lastEdited = usecTimestampNow(); } @@ -370,6 +441,15 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, properties.getVisible()); APPEND_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, appendValue, properties.getIgnoreForCollisions()); APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, appendValue, properties.getCollisionsWillMove()); + APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, appendValue, properties.getIsSpotlight()); + APPEND_ENTITY_PROPERTY(PROP_DIFFUSE_COLOR, appendColor, properties.getDiffuseColor()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_COLOR, appendColor, properties.getAmbientColor()); + APPEND_ENTITY_PROPERTY(PROP_SPECULAR_COLOR, appendColor, properties.getSpecularColor()); + APPEND_ENTITY_PROPERTY(PROP_CONSTANT_ATTENUATION, appendValue, properties.getConstantAttenuation()); + APPEND_ENTITY_PROPERTY(PROP_LINEAR_ATTENUATION, appendValue, properties.getLinearAttenuation()); + APPEND_ENTITY_PROPERTY(PROP_QUADRATIC_ATTENUATION, appendValue, properties.getQuadraticAttenuation()); + APPEND_ENTITY_PROPERTY(PROP_EXPONENT, appendValue, properties.getExponent()); + APPEND_ENTITY_PROPERTY(PROP_CUTOFF, appendValue, properties.getCutoff()); } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -568,6 +648,15 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IGNORE_FOR_COLLISIONS, bool, setIgnoreForCollisions); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONS_WILL_MOVE, bool, setCollisionsWillMove); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IS_SPOTLIGHT, bool, setIsSpotlight); + READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(PROP_DIFFUSE_COLOR, setDiffuseColor); + READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(PROP_AMBIENT_COLOR, setAmbientColor); + READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(PROP_SPECULAR_COLOR, setSpecularColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CONSTANT_ATTENUATION, float, setConstantAttenuation); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINEAR_ATTENUATION, float, setLinearAttenuation); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_QUADRATIC_ATTENUATION, float, setQuadraticAttenuation); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff); return valid; } @@ -622,6 +711,16 @@ void EntityItemProperties::markAllChanged() { _animationFPSChanged = true; _glowLevelChanged = true; _localRenderAlphaChanged = true; + _isSpotlightChanged = true; + + _diffuseColorChanged = true; + _ambientColorChanged = true; + _specularColorChanged = true; + _constantAttenuationChanged = true; + _linearAttenuationChanged = true; + _quadraticAttenuationChanged = true; + _exponentChanged = true; + _cutoffChanged = true; } AACube EntityItemProperties::getMaximumAACubeInTreeUnits() const { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 6e1594fb9b..f977b346cc 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -32,7 +32,6 @@ #include "EntityItemPropertiesMacros.h" #include "EntityTypes.h" -// PropertyFlags support enum EntityPropertyList { PROP_PAGED_PROPERTY, PROP_CUSTOM_PROPERTIES_INCLUDED, @@ -65,7 +64,18 @@ enum EntityPropertyList { PROP_IGNORE_FOR_COLLISIONS, PROP_COLLISIONS_WILL_MOVE, - PROP_LAST_ITEM = PROP_COLLISIONS_WILL_MOVE + // property used by Light entity + PROP_IS_SPOTLIGHT, + PROP_DIFFUSE_COLOR, + PROP_AMBIENT_COLOR, + PROP_SPECULAR_COLOR, + PROP_CONSTANT_ATTENUATION, + PROP_LINEAR_ATTENUATION, + PROP_QUADRATIC_ATTENUATION, + PROP_EXPONENT, + PROP_CUTOFF, + + PROP_LAST_ITEM = PROP_CUTOFF }; typedef PropertyFlags EntityPropertyFlags; @@ -83,9 +93,10 @@ class EntityItemProperties { friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods friend class BoxEntityItem; // TODO: consider removing this friend relationship and use public methods friend class SphereEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class LightEntityItem; // TODO: consider removing this friend relationship and use public methods public: EntityItemProperties(); - virtual ~EntityItemProperties() { }; + virtual ~EntityItemProperties(); virtual QScriptValue copyToScriptValue(QScriptEngine* engine) const; virtual void copyFromScriptValue(const QScriptValue& object); @@ -209,8 +220,7 @@ public: void clearID() { _id = UNKNOWN_ENTITY_ID; _idSet = false; } void markAllChanged(); - QVector getSittingPoints() const { return _sittingPoints; } - void setSittingPoints(QVector sittingPoints) { _sittingPoints = sittingPoints; } + void setSittingPoints(const QVector& sittingPoints); const glm::vec3& getNaturalDimensions() const { return _naturalDimensions; } void setNaturalDimensions(const glm::vec3& value) { _naturalDimensions = value; } @@ -233,6 +243,36 @@ public: bool getCollisionsWillMove() const { return _collisionsWillMove; } void setCollisionsWillMove(bool value) { _collisionsWillMove = value; _collisionsWillMoveChanged = true; } + bool getIsSpotlight() const { return _isSpotlight; } + void setIsSpotlight(bool value) { _isSpotlight = value; _isSpotlightChanged = true; } + + xColor getDiffuseColor() const { return _diffuseColor; } + xColor getAmbientColor() const { return _ambientColor; } + xColor getSpecularColor() const { return _specularColor; } + + void setDiffuseColor(const xColor& value) { _diffuseColor = value; _diffuseColorChanged = true; } + void setAmbientColor(const xColor& value) { _ambientColor = value; _ambientColorChanged = true; } + void setSpecularColor(const xColor& value) { _specularColor = value; _specularColorChanged = true; } + + bool diffuseColorChanged() const { return _colorChanged; } + bool ambientColorChanged() const { return _ambientColorChanged; } + bool specularColorChanged() const { return _specularColorChanged; } + + bool getConstantAttenuation() const { return _constantAttenuation; } + void setConstantAttenuation(float value) { _constantAttenuation = value; _constantAttenuationChanged = true; } + + bool getLinearAttenuation() const { return _linearAttenuation; } + void setLinearAttenuation(float value) { _linearAttenuation = value; _linearAttenuationChanged = true; } + + bool getQuadraticAttenuation() const { return _quadraticAttenuation; } + void setQuadraticAttenuation(float value) { _quadraticAttenuation = value; _quadraticAttenuationChanged = true; } + + bool getExponent() const { return _exponent; } + void setExponent(bool value) { _exponent = value; _exponentChanged = true; } + + bool getCutoff() const { return _cutoff; } + void setCutoff(bool value) { _cutoff = value; _cutoffChanged = true; } + void setLastEdited(quint64 usecTime) { _lastEdited = usecTime; } private: @@ -288,8 +328,7 @@ private: float _animationFPS; float _glowLevel; float _localRenderAlpha; - QVector _sittingPoints; - glm::vec3 _naturalDimensions; + bool _isSpotlight; bool _colorChanged; bool _modelURLChanged; @@ -299,8 +338,32 @@ private: bool _animationFPSChanged; bool _glowLevelChanged; bool _localRenderAlphaChanged; + bool _isSpotlightChanged; + + xColor _diffuseColor; + xColor _ambientColor; + xColor _specularColor; + float _constantAttenuation; + float _linearAttenuation; + float _quadraticAttenuation; + float _exponent; + float _cutoff; + + bool _diffuseColorChanged; + bool _ambientColorChanged; + bool _specularColorChanged; + bool _constantAttenuationChanged; + bool _linearAttenuationChanged; + bool _quadraticAttenuationChanged; + bool _exponentChanged; + bool _cutoffChanged; bool _defaultSettings; + + // NOTE: The following are pseudo client only properties. They are only used in clients which can access + // properties of model geometry. But these properties are not serialized like other properties. + QVector* _sittingPoints; + glm::vec3 _naturalDimensions; }; Q_DECLARE_METATYPE(EntityItemProperties); QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 16f883a36a..b5a489f88c 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -147,6 +147,10 @@ QScriptValue P = xColorToScriptValue(engine, _##P); \ properties.setProperty(#P, P); +#define COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(P,G) \ + QScriptValue P = xColorToScriptValue(engine, G); \ + properties.setProperty(#P, P); + #define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(P, G) \ properties.setProperty(#P, G); diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 7505c3f768..aaa297f4fd 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -19,6 +19,7 @@ #include "EntityTypes.h" #include "BoxEntityItem.h" +#include "LightEntityItem.h" #include "ModelEntityItem.h" #include "SphereEntityItem.h" @@ -33,6 +34,7 @@ const QString ENTITY_TYPE_NAME_UNKNOWN = "Unknown"; REGISTER_ENTITY_TYPE(Model) REGISTER_ENTITY_TYPE(Box) REGISTER_ENTITY_TYPE(Sphere) +REGISTER_ENTITY_TYPE(Light) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 8851b04b7e..85bbff99ef 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -33,7 +33,8 @@ public: Model, Box, Sphere, - LAST = Sphere + Light, + LAST = Light } EntityType; static const QString& getEntityTypeName(EntityType entityType); diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp new file mode 100644 index 0000000000..20f28cd98c --- /dev/null +++ b/libraries/entities/src/LightEntityItem.cpp @@ -0,0 +1,148 @@ +// +// LightEntityItem.cpp +// libraries/entities/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include + +#include + +#include "EntityTree.h" +#include "EntityTreeElement.h" +#include "LightEntityItem.h" + + +EntityItem* LightEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return new LightEntityItem(entityID, properties); +} + +// our non-pure virtual subclass for now... +LightEntityItem::LightEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + EntityItem(entityItemID, properties) +{ + _type = EntityTypes::Light; + + // default property values + const quint8 MAX_COLOR = 255; + _ambientColor[RED_INDEX] = _ambientColor[GREEN_INDEX] = _ambientColor[BLUE_INDEX] = 0; + _diffuseColor[RED_INDEX] = _diffuseColor[GREEN_INDEX] = _diffuseColor[BLUE_INDEX] = MAX_COLOR; + _specularColor[RED_INDEX] = _specularColor[GREEN_INDEX] = _specularColor[BLUE_INDEX] = MAX_COLOR; + _constantAttenuation = 1.0f; + _linearAttenuation = 0.0f; + _quadraticAttenuation = 0.0f; + _exponent = 0.0f; + _cutoff = PI; + + setProperties(properties, true); + + // a light is not collide-able so we make it's shape be a tiny sphere at origin + _emptyShape.setTranslation(glm::vec3(0.0f, 0.0f, 0.0f)); + _emptyShape.setRadius(0.0f); +} + +EntityItemProperties LightEntityItem::getProperties() const { + EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(isSpotlight, getIsSpotlight); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(diffuseColor, getDiffuseXColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(ambientColor, getAmbientXColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(specularColor, getSpecularXColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(constantAttenuation, getConstantAttenuation); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(linearAttenuation, getLinearAttenuation); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(quadraticAttenuation, getQuadraticAttenuation); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(exponent, getExponent); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(cutoff, getCutoff); + + return properties; +} + +bool LightEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) { + bool somethingChanged = EntityItem::setProperties(properties, forceCopy); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(isSpotlight, setIsSpotlight); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(diffuseColor, setDiffuseColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientColor, setAmbientColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(specularColor, setSpecularColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(isSpotlight, setIsSpotlight); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(constantAttenuation, setConstantAttenuation); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(linearAttenuation, setLinearAttenuation); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(quadraticAttenuation, setQuadraticAttenuation); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(exponent, setExponent); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(cutoff, setCutoff); + + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qDebug() << "LightEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, bool, _isSpotlight); + READ_ENTITY_PROPERTY_COLOR(PROP_DIFFUSE_COLOR, _diffuseColor); + READ_ENTITY_PROPERTY_COLOR(PROP_AMBIENT_COLOR, _ambientColor); + READ_ENTITY_PROPERTY_COLOR(PROP_SPECULAR_COLOR, _specularColor); + READ_ENTITY_PROPERTY(PROP_CONSTANT_ATTENUATION, float, _constantAttenuation); + READ_ENTITY_PROPERTY(PROP_LINEAR_ATTENUATION, float, _linearAttenuation); + READ_ENTITY_PROPERTY(PROP_QUADRATIC_ATTENUATION, float, _quadraticAttenuation); + READ_ENTITY_PROPERTY(PROP_EXPONENT, float, _exponent); + READ_ENTITY_PROPERTY(PROP_CUTOFF, float, _cutoff); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.lastViewFrustumSent time +EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_IS_SPOTLIGHT; + requestedProperties += PROP_DIFFUSE_COLOR; + requestedProperties += PROP_AMBIENT_COLOR; + requestedProperties += PROP_SPECULAR_COLOR; + requestedProperties += PROP_CONSTANT_ATTENUATION; + requestedProperties += PROP_LINEAR_ATTENUATION; + requestedProperties += PROP_QUADRATIC_ATTENUATION; + requestedProperties += PROP_EXPONENT; + requestedProperties += PROP_CUTOFF; + return requestedProperties; +} + +void LightEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, appendValue, getIsSpotlight()); + APPEND_ENTITY_PROPERTY(PROP_DIFFUSE_COLOR, appendColor, getDiffuseColor()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_COLOR, appendColor, getAmbientColor()); + APPEND_ENTITY_PROPERTY(PROP_SPECULAR_COLOR, appendColor, getSpecularColor()); + APPEND_ENTITY_PROPERTY(PROP_CONSTANT_ATTENUATION, appendValue, getConstantAttenuation()); + APPEND_ENTITY_PROPERTY(PROP_LINEAR_ATTENUATION, appendValue, getLinearAttenuation()); + APPEND_ENTITY_PROPERTY(PROP_QUADRATIC_ATTENUATION, appendValue, getQuadraticAttenuation()); + APPEND_ENTITY_PROPERTY(PROP_EXPONENT, appendValue, getExponent()); + APPEND_ENTITY_PROPERTY(PROP_CUTOFF, appendValue, getCutoff()); +} diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h new file mode 100644 index 0000000000..2006efb896 --- /dev/null +++ b/libraries/entities/src/LightEntityItem.h @@ -0,0 +1,118 @@ +// +// LightEntityItem.h +// libraries/entities/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_LightEntityItem_h +#define hifi_LightEntityItem_h + +#include +#include "EntityItem.h" + +class LightEntityItem : public EntityItem { +public: + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + LightEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + + ALLOW_INSTANTIATION // This class can be instantiated + + // methods for getting/setting all properties of an entity + virtual EntityItemProperties getProperties() const; + virtual bool setProperties(const EntityItemProperties& properties, bool forceCopy = false); + + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + + const rgbColor& getAmbientColor() const { return _ambientColor; } + xColor getAmbientXColor() const { + xColor color = { _ambientColor[RED_INDEX], _ambientColor[GREEN_INDEX], _ambientColor[BLUE_INDEX] }; return color; + } + + void setAmbientColor(const rgbColor& value) { memcpy(_ambientColor, value, sizeof(_ambientColor)); } + void setAmbientColor(const xColor& value) { + _ambientColor[RED_INDEX] = value.red; + _ambientColor[GREEN_INDEX] = value.green; + _ambientColor[BLUE_INDEX] = value.blue; + } + + const rgbColor& getDiffuseColor() const { return _diffuseColor; } + xColor getDiffuseXColor() const { + xColor color = { _diffuseColor[RED_INDEX], _diffuseColor[GREEN_INDEX], _diffuseColor[BLUE_INDEX] }; return color; + } + + void setDiffuseColor(const rgbColor& value) { memcpy(_diffuseColor, value, sizeof(_diffuseColor)); } + void setDiffuseColor(const xColor& value) { + _diffuseColor[RED_INDEX] = value.red; + _diffuseColor[GREEN_INDEX] = value.green; + _diffuseColor[BLUE_INDEX] = value.blue; + } + + const rgbColor& getSpecularColor() const { return _specularColor; } + xColor getSpecularXColor() const { + xColor color = { _specularColor[RED_INDEX], _specularColor[GREEN_INDEX], _specularColor[BLUE_INDEX] }; return color; + } + + void setSpecularColor(const rgbColor& value) { memcpy(_specularColor, value, sizeof(_specularColor)); } + void setSpecularColor(const xColor& value) { + _specularColor[RED_INDEX] = value.red; + _specularColor[GREEN_INDEX] = value.green; + _specularColor[BLUE_INDEX] = value.blue; + } + + bool getIsSpotlight() const { return _isSpotlight; } + void setIsSpotlight(bool value) { _isSpotlight = value; } + + float getConstantAttenuation() const { return _constantAttenuation; } + void setConstantAttenuation(float value) { _constantAttenuation = value; } + + float getLinearAttenuation() const { return _linearAttenuation; } + void setLinearAttenuation(float value) { _linearAttenuation = value; } + + float getQuadraticAttenuation() const { return _quadraticAttenuation; } + void setQuadraticAttenuation(float value) { _quadraticAttenuation = value; } + + float getExponent() const { return _exponent; } + void setExponent(float value) { _exponent = value; } + + float getCutoff() const { return _cutoff; } + void setCutoff(float value) { _cutoff = value; } + + virtual const Shape& getCollisionShapeInMeters() const { return _emptyShape; } + +protected: + virtual void recalculateCollisionShape() { /* nothing to do */ } + + // properties of a light + rgbColor _ambientColor; + rgbColor _diffuseColor; + rgbColor _specularColor; + bool _isSpotlight; + float _constantAttenuation; + float _linearAttenuation; + float _quadraticAttenuation; + float _exponent; + float _cutoff; + + // used for collision detection + SphereShape _emptyShape; +}; + +#endif // hifi_LightEntityItem_h diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 5da218c11a..f5b8eb27e9 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -33,10 +33,7 @@ SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID, const Entit EntityItemProperties SphereEntityItem::getProperties() const { EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class - properties.setColor(getXColor()); - properties.setGlowLevel(getGlowLevel()); - return properties; } diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 1201a8973f..6113cadb06 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -15,6 +15,7 @@ #include #include +#include #include "NodeList.h" @@ -71,6 +72,11 @@ const QString AddressManager::currentPath(bool withOrientation) const { } } +QString AddressManager::getDomainID() const { + const QUuid& domainID = NodeList::getInstance()->getDomainHandler().getUUID(); + return domainID.isNull() ? "" : uuidStringWithoutCurlyBraces(domainID); +} + const JSONCallbackParameters& AddressManager::apiCallbackParameters() { static bool hasSetupParameters = false; static JSONCallbackParameters callbackParams; diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 8a25464223..d6ecdc9fc6 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -31,6 +31,7 @@ class AddressManager : public QObject { Q_PROPERTY(QString protocol READ getProtocol) Q_PROPERTY(QString hostname READ getCurrentDomain) Q_PROPERTY(QString pathname READ currentPath) + Q_PROPERTY(QString domainID READ getDomainID) public: static AddressManager& getInstance(); @@ -41,6 +42,7 @@ public: const QString currentPath(bool withOrientation = true) const; const QString& getCurrentDomain() const { return _currentDomain; } + QString getDomainID() const; void attemptPlaceNameLookup(const QString& lookupString); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 919dc75c23..5c4dc6cea2 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -19,10 +19,11 @@ #include #include +#include + #include "AccountManager.h" #include "Assignment.h" #include "HifiSockAddr.h" -#include "Logging.h" #include "LimitedNodeList.h" #include "PacketHeaders.h" #include "SharedUtil.h" @@ -211,8 +212,11 @@ bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) { << uuidFromPacketHeader(packet); } } else { + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex("Packet of type \\d+ received from unknown node with UUID"); + qDebug() << "Packet of type" << checkType << "received from unknown node with UUID" - << uuidFromPacketHeader(packet); + << qPrintable(uuidStringWithoutCurlyBraces(uuidFromPacketHeader(packet))); } } else { return true; diff --git a/libraries/networking/src/Logging.cpp b/libraries/networking/src/Logging.cpp deleted file mode 100644 index f42f1bda58..0000000000 --- a/libraries/networking/src/Logging.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// -// Logging.cpp -// libraries/networking/src -// -// Created by Stephen Birarda on 6/11/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include -#include -//#include // not available on windows, apparently not needed on mac - -#ifdef _WIN32 -#include -#define getpid _getpid -#define getppid _getpid // hack to build -#define pid_t int // hack to build -#endif - -#include - -#include "HifiSockAddr.h" -#include "SharedUtil.h" -#include "NodeList.h" - -#include "Logging.h" - -HifiSockAddr Logging::_logstashSocket = HifiSockAddr(); -QString Logging::_targetName = QString(); - -const HifiSockAddr& Logging::socket() { - - if (_logstashSocket.getAddress().isNull()) { - // we need to construct the socket object - // use the constant port - _logstashSocket.setPort(htons(LOGSTASH_UDP_PORT)); - - // lookup the IP address for the constant hostname - QHostInfo hostInfo = QHostInfo::fromName(LOGSTASH_HOSTNAME); - if (!hostInfo.addresses().isEmpty()) { - // use the first IP address - _logstashSocket.setAddress(hostInfo.addresses().first()); - } else { - qDebug("Failed to lookup logstash IP - will try again on next log attempt."); - } - } - - return _logstashSocket; -} - -bool Logging::shouldSendStats() { - static bool shouldSendStats = isInEnvironment("production"); - return shouldSendStats; -} - -void Logging::stashValue(char statType, const char* key, float value) { - static char logstashPacket[MAX_PACKET_SIZE]; - - // load up the logstash packet with the key and the passed float value - // send it to 4 decimal places - int numPacketBytes = sprintf(logstashPacket, "%c %s %.4f", statType, key, value); - - NodeList *nodeList = NodeList::getInstance(); - - if (nodeList) { - nodeList->getNodeSocket().writeDatagram(logstashPacket, numPacketBytes, - _logstashSocket.getAddress(), _logstashSocket.getPort()); - } -} - -const char* stringForLogType(QtMsgType msgType) { - switch (msgType) { - case QtDebugMsg: - return "DEBUG"; - case QtCriticalMsg: - return "CRITICAL"; - case QtFatalMsg: - return "FATAL"; - case QtWarningMsg: - return "WARNING"; - default: - return "UNKNOWN"; - } -} - -// the following will produce 2000-10-02 13:55:36 -0700 -const char DATE_STRING_FORMAT[] = "%Y-%m-%d %H:%M:%S %z"; - -void Logging::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (message.isEmpty()) { - return; - } - // log prefix is in the following format - // [DEBUG] [TIMESTAMP] [PID:PARENT_PID] [TARGET] logged string - - QString prefixString = QString("[%1]").arg(stringForLogType(type)); - - time_t rawTime; - time(&rawTime); - struct tm* localTime = localtime(&rawTime); - - char dateString[100]; - strftime(dateString, sizeof(dateString), DATE_STRING_FORMAT, localTime); - - prefixString.append(QString(" [%1]").arg(dateString)); - - prefixString.append(QString(" [%1").arg(getpid())); - - pid_t parentProcessID = getppid(); - if (parentProcessID != 0) { - prefixString.append(QString(":%1]").arg(parentProcessID)); - } else { - prefixString.append("]"); - } - - if (!_targetName.isEmpty()) { - prefixString.append(QString(" [%1]").arg(_targetName)); - } - - fprintf(stdout, "%s %s\n", prefixString.toLocal8Bit().constData(), message.toLocal8Bit().constData()); -} diff --git a/libraries/networking/src/Logging.h b/libraries/networking/src/Logging.h deleted file mode 100644 index c52812bd33..0000000000 --- a/libraries/networking/src/Logging.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// Logging.h -// libraries/networking/src -// -// Created by Stephen Birarda on 6/11/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_Logging_h -#define hifi_Logging_h - -#include - -const int LOGSTASH_UDP_PORT = 9500; -const char LOGSTASH_HOSTNAME[] = "graphite.highfidelity.io"; - -const char STAT_TYPE_TIMER = 't'; -const char STAT_TYPE_COUNTER = 'c'; -const char STAT_TYPE_GAUGE = 'g'; - -class HifiSockAddr; - -/// Handles custom message handling and sending of stats/logs to Logstash instance -class Logging { -public: - /// \return the socket used to send stats to logstash - static const HifiSockAddr& socket(); - - /// checks if this target should send stats to logstash, given its current environment - /// \return true if the caller should send stats to logstash - static bool shouldSendStats(); - - /// stashes a float value to Logstash instance - /// \param statType a stat type from the constants in this file - /// \param key the key at which to store the stat - /// \param value the value to store - static void stashValue(char statType, const char* key, float value); - - /// sets the target name to output via the verboseMessageHandler, called once before logging begins - /// \param targetName the desired target name to output in logs - static void setTargetName(const QString& targetName) { _targetName = targetName; } - - /// a qtMessageHandler that can be hooked up to a target that links to Qt - /// prints various process, message type, and time information - static void verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message); -private: - static HifiSockAddr _logstashSocket; - static QString _targetName; -}; - -#endif // hifi_Logging_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index feeb13500a..ff3b86880d 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -15,10 +15,11 @@ #include #include +#include + #include "AccountManager.h" #include "Assignment.h" #include "HifiSockAddr.h" -#include "Logging.h" #include "NodeList.h" #include "PacketHeaders.h" #include "SharedUtil.h" diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 8ce379b203..73671295df 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -53,7 +53,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeSilentAudioFrame: return 4; case PacketTypeMixedAudio: - return 2; + return 1; case PacketTypeAvatarData: return 3; case PacketTypeAvatarIdentity: @@ -71,11 +71,9 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeOctreeStats: return 1; - case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: return VERSION_ENTITIES_SUPPORT_DIMENSIONS; - case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: @@ -135,8 +133,13 @@ QString nameForPacketType(PacketType type) { PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityAddResponse); PACKET_TYPE_NAME_LOOKUP(PacketTypeOctreeDataNack); PACKET_TYPE_NAME_LOOKUP(PacketTypeVoxelEditNack); + PACKET_TYPE_NAME_LOOKUP(PacketTypeAudioEnvironment); PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityEditNack); PACKET_TYPE_NAME_LOOKUP(PacketTypeSignedTransactionPayment); + PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerHeartbeat); + PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerHeartbeatResponse); + PACKET_TYPE_NAME_LOOKUP(PacketTypeUnverifiedPing); + PACKET_TYPE_NAME_LOOKUP(PacketTypeUnverifiedPingReply); default: return QString("Type: ") + QString::number((int)type); } diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 7632f47686..2e9ce697f0 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -68,7 +68,7 @@ enum PacketType { PacketTypeEntityAddResponse, PacketTypeOctreeDataNack, // 45 PacketTypeVoxelEditNack, - UNUSED_6, + PacketTypeAudioEnvironment, PacketTypeEntityEditNack, // 48 PacketTypeSignedTransactionPayment, PacketTypeIceServerHeartbeat, diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 6d2e366499..52644a9a4e 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -14,7 +14,8 @@ #include #include -#include "Logging.h" +#include + #include "ThreadedAssignment.h" ThreadedAssignment::ThreadedAssignment(const QByteArray& packet) : @@ -54,7 +55,7 @@ void ThreadedAssignment::setFinished(bool isFinished) { void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeType, bool shouldSendStats) { // change the logging target name while the assignment is running - Logging::setTargetName(targetName); + LogHandler::getInstance().setTargetName(targetName); NodeList* nodeList = NodeList::getInstance(); nodeList->setOwnerType(nodeType); diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index 4b2b57fab2..f1894c7cab 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -24,7 +24,7 @@ #include "OctreeProjectedPolygon.h" const float DEFAULT_KEYHOLE_RADIUS = 3.0f; -const float DEFAULT_FIELD_OF_VIEW_DEGREES = 90.0f; +const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f; const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.f; const float DEFAULT_ASPECT_RATIO = 16.f/9.f; const float DEFAULT_NEAR_CLIP = 0.08f; diff --git a/libraries/script-engine/src/UndoStackScriptingInterface.cpp b/libraries/script-engine/src/UndoStackScriptingInterface.cpp index ed0f4d563d..17bf8b1aa6 100644 --- a/libraries/script-engine/src/UndoStackScriptingInterface.cpp +++ b/libraries/script-engine/src/UndoStackScriptingInterface.cpp @@ -30,6 +30,7 @@ void UndoStackScriptingInterface::pushCommand(QScriptValue undoFunction, QScript ScriptUndoCommand::ScriptUndoCommand(QScriptValue undoFunction, QScriptValue undoData, QScriptValue redoFunction, QScriptValue redoData) : + _hasRedone(false), _undoFunction(undoFunction), _undoData(undoData), _redoFunction(redoFunction), @@ -41,7 +42,15 @@ void ScriptUndoCommand::undo() { } void ScriptUndoCommand::redo() { - QMetaObject::invokeMethod(this, "doRedo"); + // QUndoStack will call `redo()` when adding a command to the stack. This + // makes it difficult to work with commands that span a period of time - for instance, + // the entity duplicate + move command that duplicates an entity and then moves it. + // A better implementation might be to properly implement `mergeWith()` and `id()` + // so that the two actions in the example would be merged. + if (_hasRedone) { + QMetaObject::invokeMethod(this, "doRedo"); + } + _hasRedone = true; } void ScriptUndoCommand::doUndo() { @@ -50,7 +59,6 @@ void ScriptUndoCommand::doUndo() { _undoFunction.call(QScriptValue(), args); } - void ScriptUndoCommand::doRedo() { QScriptValueList args; args << _redoData; diff --git a/libraries/script-engine/src/UndoStackScriptingInterface.h b/libraries/script-engine/src/UndoStackScriptingInterface.h index 835e5dfff4..479648fc92 100644 --- a/libraries/script-engine/src/UndoStackScriptingInterface.h +++ b/libraries/script-engine/src/UndoStackScriptingInterface.h @@ -43,6 +43,7 @@ public slots: void doRedo(); private: + bool _hasRedone; QScriptValue _undoFunction; QScriptValue _undoData; QScriptValue _redoFunction; diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp new file mode 100644 index 0000000000..1d8e55f432 --- /dev/null +++ b/libraries/shared/src/LogHandler.cpp @@ -0,0 +1,146 @@ +// +// LogHandler.cpp +// libraries/shared/src +// +// Created by Stephen Birarda on 2014-10-28. +// Migrated from Logging.cpp created on 6/11/13 +// 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 +// + +#include + +#ifdef _WIN32 +#include +#define getpid _getpid +#define getppid _getpid // hack to build +#define pid_t int // hack to build +#else +#include // for getpid() on linux +#endif + +#include +#include + +#include "LogHandler.h" + +LogHandler& LogHandler::getInstance() { + static LogHandler staticInstance; + return staticInstance; +} + +LogHandler::LogHandler() : + _shouldOutputPID(false) +{ + // setup our timer to flush the verbose logs every 5 seconds + QTimer* logFlushTimer = new QTimer(this); + connect(logFlushTimer, &QTimer::timeout, this, &LogHandler::flushRepeatedMessages); + logFlushTimer->start(VERBOSE_LOG_INTERVAL_SECONDS * 1000); +} + +const char* stringForLogType(LogMsgType msgType) { + switch (msgType) { + case QtDebugMsg: + return "DEBUG"; + case QtCriticalMsg: + return "CRITICAL"; + case QtFatalMsg: + return "FATAL"; + case QtWarningMsg: + return "WARNING"; + case LogSuppressed: + return "SUPPRESS"; + default: + return "UNKNOWN"; + } +} + +// the following will produce 2000-10-02 13:55:36 -0700 +const char DATE_STRING_FORMAT[] = "%Y-%m-%d %H:%M:%S %z"; + +void LogHandler::flushRepeatedMessages() { + QHash::iterator message = _repeatMessageCountHash.begin(); + while (message != _repeatMessageCountHash.end()) { + + if (message.value() > 0) { + QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"") + .arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key())); + + QMessageLogContext emptyContext; + printMessage(LogSuppressed, emptyContext, repeatMessage); + } + + _lastRepeatedMessage.remove(message.key()); + message = _repeatMessageCountHash.erase(message); + } +} + +QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& context, const QString& message) { + + if (message.isEmpty()) { + return QString(); + } + + if (type == LogDebug) { + // for debug messages, check if this matches any of our regexes for repeated log messages + foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) { + QRegExp repeatRegex(regexString); + if (repeatRegex.indexIn(message) != -1) { + + if (!_repeatMessageCountHash.contains(regexString)) { + // we have a match but didn't have this yet - output the first one + _repeatMessageCountHash[regexString] = 0; + + // break the foreach so we output the first match + break; + } else { + // we have a match - add 1 to the count of repeats for this message and set this as the last repeated message + _repeatMessageCountHash[regexString] += 1; + _lastRepeatedMessage[regexString] = message; + + // return out, we're not printing this one + return QString(); + } + } + } + } + + // log prefix is in the following format + // [DEBUG] [TIMESTAMP] [PID:PARENT_PID] [TARGET] logged string + + QString prefixString = QString("[%1]").arg(stringForLogType(type)); + + time_t rawTime; + time(&rawTime); + struct tm* localTime = localtime(&rawTime); + + char dateString[100]; + strftime(dateString, sizeof(dateString), DATE_STRING_FORMAT, localTime); + + prefixString.append(QString(" [%1]").arg(dateString)); + + if (_shouldOutputPID) { + prefixString.append(QString(" [%1").arg(getpid())); + + pid_t parentProcessID = getppid(); + if (parentProcessID != 0) { + prefixString.append(QString(":%1]").arg(parentProcessID)); + } else { + prefixString.append("]"); + } + } + + if (!_targetName.isEmpty()) { + prefixString.append(QString(" [%1]").arg(_targetName)); + } + + QString logMessage = QString("%1 %2").arg(prefixString, message); + fprintf(stdout, "%s\n", qPrintable(logMessage)); + return logMessage; +} + +void LogHandler::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + getInstance().printMessage((LogMsgType) type, context, message); +} diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h new file mode 100644 index 0000000000..f8f06b0033 --- /dev/null +++ b/libraries/shared/src/LogHandler.h @@ -0,0 +1,63 @@ +// +// LogHandler.cpp +// libraries/shared/src +// +// Created by Stephen Birarda on 2014-10-28. +// Migrated from Logging.cpp created on 6/11/13 +// 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 +// + +#ifndef hifi_LogHandler_h +#define hifi_LogHandler_h + +#include +#include +#include +#include +#include + +const int VERBOSE_LOG_INTERVAL_SECONDS = 5; + +enum LogMsgType { + LogDebug, + LogWarning, + LogCritical, + LogFatal, + LogSuppressed +}; + +/// Handles custom message handling and sending of stats/logs to Logstash instance +class LogHandler : public QObject { + Q_OBJECT +public: + static LogHandler& getInstance(); + + /// sets the target name to output via the verboseMessageHandler, called once before logging begins + /// \param targetName the desired target name to output in logs + void setTargetName(const QString& targetName) { _targetName = targetName; } + + void setShouldOutputPID(bool shouldOutputPID) { _shouldOutputPID = shouldOutputPID; } + + QString printMessage(LogMsgType type, const QMessageLogContext& context, const QString &message); + + /// a qtMessageHandler that can be hooked up to a target that links to Qt + /// prints various process, message type, and time information + static void verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message); + + const QString& addRepeatedMessageRegex(const QString& regexString) { return *_repeatedMessageRegexes.insert(regexString); } +private: + LogHandler(); + + void flushRepeatedMessages(); + + QString _targetName; + bool _shouldOutputPID; + QSet _repeatedMessageRegexes; + QHash _repeatMessageCountHash; + QHash _lastRepeatedMessage; +}; + +#endif // hifi_LogHandler_h \ No newline at end of file