diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 988bcc1da7..e86ecda36e 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -62,7 +62,12 @@ void attachNewBufferToNode(Node *newNode) { } AudioMixer::AudioMixer(const QByteArray& packet) : - ThreadedAssignment(packet) + ThreadedAssignment(packet), + _trailingSleepRatio(1.0f), + _minSourceLoudnessInFrame(1.0f), + _maxSourceLoudnessInFrame(0.0f), + _loudnessCutoffRatio(0.0f), + _minRequiredLoudness(0.0f) { } @@ -301,7 +306,7 @@ void AudioMixer::prepareMixForListeningNode(Node* node) { if ((*otherNode != *node || otherNodeBuffer->shouldLoopbackForNode()) && otherNodeBuffer->willBeAddedToMix() - && otherNodeClientData->getNextOutputLoudness() > 0) { + && otherNodeBuffer->getAverageLoudness() > _minRequiredLoudness) { addBufferToMixForListeningNodeWithBuffer(otherNodeBuffer, nodeRingBuffer); } } @@ -350,14 +355,65 @@ void AudioMixer::run() { char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)]; + + int usecToSleep = BUFFER_SEND_INTERVAL_USECS; while (!_isFinished) { + _minSourceLoudnessInFrame = 1.0f; + _maxSourceLoudnessInFrame = 0.0f; + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getLinkedData()) { - ((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES); + ((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES, + _minSourceLoudnessInFrame, + _maxSourceLoudnessInFrame); } } + + const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10; + const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.30; + const float CUTOFF_EPSILON = 0.0001; + + const int TRAILING_AVERAGE_FRAMES = 100; + const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; + const float PREVIOUS_FRAMES_RATIO = 1 - CURRENT_FRAME_RATIO; + + if (usecToSleep < 0) { + usecToSleep = 0; + } + + _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) + + (usecToSleep * CURRENT_FRAME_RATIO / (float) BUFFER_SEND_INTERVAL_USECS); + + float lastCutoffRatio = _loudnessCutoffRatio; + bool hasRatioChanged = false; + + if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { + // we're struggling - change our min required loudness to reduce some load + _loudnessCutoffRatio += (1 - _loudnessCutoffRatio) / 2; + + qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" + << lastCutoffRatio << "and is now" << _loudnessCutoffRatio; + hasRatioChanged = true; + } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _loudnessCutoffRatio != 0) { + // we've recovered and can back off the required loudness + _loudnessCutoffRatio -= _loudnessCutoffRatio / 2; + + if (_loudnessCutoffRatio < CUTOFF_EPSILON) { + _loudnessCutoffRatio = 0; + } + + qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" + << lastCutoffRatio << "and is now" << _loudnessCutoffRatio; + hasRatioChanged = true; + } + + if (hasRatioChanged) { + // set out min required loudness from the new ratio + _minRequiredLoudness = _loudnessCutoffRatio * (_maxSourceLoudnessInFrame - _minSourceLoudnessInFrame); + qDebug() << "Minimum loudness required to be mixed is now" << _minRequiredLoudness; + } foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() @@ -384,7 +440,7 @@ void AudioMixer::run() { break; } - int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); + usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); if (usecToSleep > 0) { usleep(usecToSleep); diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 5a68b0023f..4ba8cdebd3 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -37,7 +37,14 @@ private: void prepareMixForListeningNode(Node* node); // client samples capacity is larger than what will be sent to optimize mixing - int16_t _clientSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + SAMPLE_PHASE_DELAY_AT_90]; + // we are MMX adding 4 samples at a time so we need client samples to have an extra 4 + int16_t _clientSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)]; + + float _trailingSleepRatio; + float _minSourceLoudnessInFrame; + float _maxSourceLoudnessInFrame; + float _loudnessCutoffRatio; + float _minRequiredLoudness; }; #endif /* defined(__hifi__AudioMixer__) */ diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index b2da0a0aaa..41fd72e54e 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -6,6 +6,8 @@ // Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // +#include + #include #include @@ -14,8 +16,7 @@ #include "AudioMixerClientData.h" AudioMixerClientData::AudioMixerClientData() : - _ringBuffers(), - _nextOutputLoudness(0) + _ringBuffers() { } @@ -82,16 +83,29 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { return 0; } -void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples) { +void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples, + float& currentMinLoudness, + float& currentMaxLoudness) { for (unsigned int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->shouldBeAddedToMix(jitterBufferLengthSamples)) { // this is a ring buffer that is ready to go // set its flag so we know to push its buffer when all is said and done _ringBuffers[i]->setWillBeAddedToMix(true); + // calculate the average loudness for the next NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL // that would be mixed in - _nextOutputLoudness = _ringBuffers[i]->averageLoudnessForBoundarySamples(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); + _ringBuffers[i]->updateAverageLoudnessForBoundarySamples(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); + + float ringBufferLoudness = _ringBuffers[i]->getAverageLoudness(); + + if (ringBufferLoudness != 0 && ringBufferLoudness < currentMinLoudness) { + currentMinLoudness = ringBufferLoudness; + } + + if (ringBufferLoudness > currentMaxLoudness) { + currentMaxLoudness = ringBufferLoudness; + } } } } diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index bb10098e23..d41563bbca 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -24,14 +24,11 @@ public: const std::vector getRingBuffers() const { return _ringBuffers; } AvatarAudioRingBuffer* getAvatarAudioRingBuffer() const; - float getNextOutputLoudness() const { return _nextOutputLoudness; } - int parseData(const QByteArray& packet); - void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples); + void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples, float& currentMinLoudness, float& currentMaxLoudness); void pushBuffersAfterFrameSend(); private: std::vector _ringBuffers; - float _nextOutputLoudness; }; #endif /* defined(__hifi__AudioMixerClientData__) */ diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index a1922f980d..2ceb9e1040 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -36,18 +36,34 @@ OctreeQueryNode::OctreeQueryNode() : _lodChanged(false), _lodInitialized(false), _sequenceNumber(0), - _lastRootTimestamp(0) + _lastRootTimestamp(0), + _myPacketType(PacketTypeUnknown), + _isShuttingDown(false) { } OctreeQueryNode::~OctreeQueryNode() { + _isShuttingDown = true; + const bool extraDebugging = false; + if (extraDebugging) { + qDebug() << "OctreeQueryNode::~OctreeQueryNode()"; + } if (_octreeSendThread) { + if (extraDebugging) { + qDebug() << "OctreeQueryNode::~OctreeQueryNode()... calling _octreeSendThread->terminate()"; + } _octreeSendThread->terminate(); + if (extraDebugging) { + qDebug() << "OctreeQueryNode::~OctreeQueryNode()... calling delete _octreeSendThread"; + } delete _octreeSendThread; } delete[] _octreePacket; delete[] _lastOctreePacket; + if (extraDebugging) { + qDebug() << "OctreeQueryNode::~OctreeQueryNode()... DONE..."; + } } @@ -59,9 +75,13 @@ void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* octreeServer, con } bool OctreeQueryNode::packetIsDuplicate() const { + // if shutting down, return immediately + if (_isShuttingDown) { + return false; + } // since our packets now include header information, like sequence number, and createTime, we can't just do a memcmp // of the entire packet, we need to compare only the packet content... - int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(getMyPacketType()); + int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(_myPacketType); if (_lastOctreePacketLength == getPacketLength()) { if (memcmp(_lastOctreePacket + (numBytesPacketHeader + OCTREE_PACKET_EXTRA_HEADERS_SIZE), @@ -74,6 +94,11 @@ bool OctreeQueryNode::packetIsDuplicate() const { } bool OctreeQueryNode::shouldSuppressDuplicatePacket() { + // if shutting down, return immediately + if (_isShuttingDown) { + return true; + } + bool shouldSuppress = false; // assume we won't suppress // only consider duplicate packets @@ -107,7 +132,17 @@ bool OctreeQueryNode::shouldSuppressDuplicatePacket() { return shouldSuppress; } +void OctreeQueryNode::init() { + _myPacketType = getMyPacketType(); + resetOctreePacket(true); // don't bump sequence +} + void OctreeQueryNode::resetOctreePacket(bool lastWasSurpressed) { + // if shutting down, return immediately + if (_isShuttingDown) { + return; + } + // Whenever we call this, we will keep a copy of the last packet, so we can determine if the last packet has // changed since we last reset it. Since we know that no two packets can ever be identical without being the same // scene information, (e.g. the root node packet of a static scene), we can use this as a strategy for reducing @@ -128,7 +163,7 @@ void OctreeQueryNode::resetOctreePacket(bool lastWasSurpressed) { } _octreePacketAvailableBytes = MAX_PACKET_SIZE; - int numBytesPacketHeader = populatePacketHeader(reinterpret_cast(_octreePacket), getMyPacketType()); + int numBytesPacketHeader = populatePacketHeader(reinterpret_cast(_octreePacket), _myPacketType); _octreePacketAt = _octreePacket + numBytesPacketHeader; _octreePacketAvailableBytes -= numBytesPacketHeader; @@ -158,6 +193,11 @@ void OctreeQueryNode::resetOctreePacket(bool lastWasSurpressed) { } void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int bytes) { + // if shutting down, return immediately + if (_isShuttingDown) { + return; + } + // compressed packets include lead bytes which contain compressed size, this allows packing of // multiple compressed portions together if (_currentPacketIsCompressed) { @@ -174,6 +214,11 @@ void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int by } bool OctreeQueryNode::updateCurrentViewFrustum() { + // if shutting down, return immediately + if (_isShuttingDown) { + return false; + } + bool currentViewFrustumChanged = false; ViewFrustum newestViewFrustum; // get position and orientation details from the camera @@ -233,6 +278,11 @@ void OctreeQueryNode::setViewSent(bool viewSent) { } void OctreeQueryNode::updateLastKnownViewFrustum() { + // if shutting down, return immediately + if (_isShuttingDown) { + return; + } + bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum); if (frustumChanges) { @@ -247,6 +297,11 @@ void OctreeQueryNode::updateLastKnownViewFrustum() { bool OctreeQueryNode::moveShouldDump() const { + // if shutting down, return immediately + if (_isShuttingDown) { + return false; + } + glm::vec3 oldPosition = _lastKnownViewFrustum.getPosition(); glm::vec3 newPosition = _currentViewFrustum.getPosition(); @@ -259,6 +314,11 @@ bool OctreeQueryNode::moveShouldDump() const { } void OctreeQueryNode::dumpOutOfView() { + // if shutting down, return immediately + if (_isShuttingDown) { + return; + } + int stillInView = 0; int outOfView = 0; OctreeElementBag tempBag; diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index 20fa2c5858..eab8cb5d0a 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -28,6 +28,7 @@ public: OctreeQueryNode(); virtual ~OctreeQueryNode(); + void init(); // called after creation to set up some virtual items virtual PacketType getMyPacketType() const = 0; void resetOctreePacket(bool lastWasSurpressed = false); // resets octree packet to after "V" header @@ -91,6 +92,7 @@ public: unsigned int getlastOctreePacketLength() const { return _lastOctreePacketLength; } int getDuplicatePacketCount() const { return _duplicatePacketCount; } + bool isShuttingDown() const { return _isShuttingDown; } private: OctreeQueryNode(const OctreeQueryNode &); @@ -127,6 +129,9 @@ private: OCTREE_PACKET_SEQUENCE _sequenceNumber; quint64 _lastRootTimestamp; + + PacketType _myPacketType; + bool _isShuttingDown; }; #endif /* defined(__hifi__OctreeQueryNode__) */ diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index caa729e340..9c04c4a1ad 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -65,7 +65,7 @@ bool OctreeSendThread::process() { nodeData = (OctreeQueryNode*) node->getLinkedData(); // Sometimes the node data has not yet been linked, in which case we can't really do anything - if (nodeData) { + if (nodeData && !nodeData->isShuttingDown()) { bool viewFrustumChanged = nodeData->updateCurrentViewFrustum(); packetDistributor(node, nodeData, viewFrustumChanged); } @@ -99,7 +99,14 @@ quint64 OctreeSendThread::_totalBytes = 0; quint64 OctreeSendThread::_totalWastedBytes = 0; quint64 OctreeSendThread::_totalPackets = 0; -int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { +int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, + OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { + + // if we're shutting down, then exit early + if (nodeData->isShuttingDown()) { + return 0; + } + bool debug = _myServer->wantsDebugSending(); quint64 now = usecTimestampNow(); @@ -136,7 +143,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQuer // If we've got a stats message ready to send, then see if we can piggyback them together - if (nodeData->stats.isReadyToSend()) { + if (nodeData->stats.isReadyToSend() && !nodeData->isShuttingDown()) { // Send the stats message to the client unsigned char* statsMessage = nodeData->stats.getStatsMessage(); int statsMessageLength = nodeData->stats.getStatsMessageLength(); @@ -203,7 +210,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQuer nodeData->stats.markAsSent(); } else { // If there's actually a packet waiting, then send it. - if (nodeData->isPacketWaiting()) { + if (nodeData->isPacketWaiting() && !nodeData->isShuttingDown()) { // just send the voxel packet NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), SharedNodePointer(node)); @@ -234,6 +241,12 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQuer /// Version of voxel distributor that sends the deepest LOD level at once int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { + + // if shutting down, exit early + if (nodeData->isShuttingDown()) { + return 0; + } + int truePacketsSent = 0; int trueBytesSent = 0; int packetsSentThisInterval = 0; @@ -336,7 +349,7 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue int extraPackingAttempts = 0; bool completedScene = false; - while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval) { + while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) { float lockWaitElapsedUsec = OctreeServer::SKIP_TIME; float encodeElapsedUsec = OctreeServer::SKIP_TIME; float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME; @@ -503,7 +516,7 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue // Here's where we can/should allow the server to send other data... // send the environment packet // TODO: should we turn this into a while loop to better handle sending multiple special packets - if (_myServer->hasSpecialPacketToSend(node)) { + if (_myServer->hasSpecialPacketToSend(node) && !nodeData->isShuttingDown()) { trueBytesSent += _myServer->sendSpecialPacket(node); truePacketsSent++; packetsSentThisInterval++; diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 7e6ffe52da..496f9af1a0 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -168,7 +168,7 @@ void OctreeServer::trackPacketSendingTime(float time) { void OctreeServer::attachQueryNodeToNode(Node* newNode) { if (!newNode->getLinkedData()) { OctreeQueryNode* newQueryNodeData = _instance->createOctreeQueryNode(); - newQueryNodeData->resetOctreePacket(true); // don't bump sequence + newQueryNodeData->init(); newNode->setLinkedData(newQueryNodeData); } } @@ -784,16 +784,26 @@ void OctreeServer::readPendingDatagrams() { if (packetType == getMyQueryMessageType()) { bool debug = false; if (debug) { - qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow(); + if (matchingNode) { + qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow() << "node:" << *matchingNode; + } else { + qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow() << "node: ??????"; + } } // If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we // need to make sure we have it in our nodeList. if (matchingNode) { + if (debug) { + qDebug() << "calling updateNodeWithDataFromPacket()... node:" << *matchingNode; + } nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket); OctreeQueryNode* nodeData = (OctreeQueryNode*) matchingNode->getLinkedData(); if (nodeData && !nodeData->isOctreeSendThreadInitalized()) { + if (debug) { + qDebug() << "calling initializeOctreeSendThread()... node:" << *matchingNode; + } nodeData->initializeOctreeSendThread(this, matchingNode->getUUID()); } } @@ -999,6 +1009,8 @@ void OctreeServer::nodeKilled(SharedNodePointer node) { node->setLinkedData(NULL); // set this first in case another thread comes through and tryes to acces this qDebug() << qPrintable(_safeServerName) << "server deleting Linked Data for node:" << *node; nodeData->deleteLater(); + } else { + qDebug() << qPrintable(_safeServerName) << "server node missing linked data node:" << *node; } } diff --git a/examples/editVoxels.js b/examples/editVoxels.js index 31d483d798..43bc485274 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -20,8 +20,6 @@ var windowDimensions = Controller.getViewportDimensions(); var NEW_VOXEL_SIZE = 1.0; var NEW_VOXEL_DISTANCE_FROM_CAMERA = 3.0; -var ORBIT_RATE_ALTITUDE = 200.0; -var ORBIT_RATE_AZIMUTH = 90.0; var PIXELS_PER_EXTRUDE_VOXEL = 16; var WHEEL_PIXELS_PER_SCALE_CHANGE = 100; var MAX_VOXEL_SCALE = 1.0; @@ -37,19 +35,7 @@ var previewLineWidth = 1.5; var oldMode = Camera.getMode(); var isAdding = false; -var isExtruding = false; -var isOrbiting = false; -var isOrbitingFromTouch = false; -var isPanning = false; -var isPanningFromTouch = false; -var touchPointsToOrbit = 2; // you can change these, but be mindful that on some track pads 2 touch points = right click+drag -var touchPointsToPan = 3; -var orbitAzimuth = 0.0; -var orbitAltitude = 0.0; -var orbitCenter = { x: 0, y: 0, z: 0 }; -var orbitPosition = { x: 0, y: 0, z: 0 }; -var torsoToEyeVector = { x: 0, y: 0, z: 0 }; -var orbitRadius = 0.0; +var isExtruding = false; var extrudeDirection = { x: 0, y: 0, z: 0 }; var extrudeScale = 0.0; var lastVoxelPosition = { x: 0, y: 0, z: 0 }; @@ -444,24 +430,11 @@ function getNewVoxelPosition() { return newPosition; } -function fixEulerAngles(eulers) { - var rVal = { x: 0, y: 0, z: eulers.z }; - if (eulers.x >= 90.0) { - rVal.x = 180.0 - eulers.x; - rVal.y = eulers.y - 180.0; - } else if (eulers.x <= -90.0) { - rVal.x = 180.0 - eulers.x; - rVal.y = eulers.y - 180.0; - } - return rVal; -} - var trackLastMouseX = 0; var trackLastMouseY = 0; var trackAsDelete = false; var trackAsRecolor = false; var trackAsEyedropper = false; -var trackAsOrbitOrPan = false; var voxelToolSelected = true; var recolorToolSelected = false; @@ -761,10 +734,6 @@ function trackKeyPressEvent(event) { trackAsEyedropper = true; moveTools(); } - if (event.text == "ALT") { - trackAsOrbitOrPan = true; - moveTools(); - } showPreviewGuides(); } @@ -802,10 +771,6 @@ function trackKeyReleaseEvent(event) { trackAsEyedropper = false; moveTools(); } - if (event.text == "ALT") { - trackAsOrbitOrPan = false; - moveTools(); - } // on F1 toggle the preview mode between cubes and lines if (event.text == "F1") { @@ -816,74 +781,6 @@ function trackKeyReleaseEvent(event) { } } -function startOrbitMode(event) { - mouseX = event.x; - mouseY = event.y; - var pickRay = Camera.computePickRay(event.x, event.y); - var intersection = Voxels.findRayIntersection(pickRay); - - // start orbit camera! - var cameraPosition = Camera.getPosition(); - torsoToEyeVector = Vec3.subtract(cameraPosition, MyAvatar.position); - torsoToEyeVector.x = 0.0; - torsoToEyeVector.z = 0.0; - oldMode = Camera.getMode(); - Camera.setMode("independent"); - Camera.keepLookingAt(intersection.intersection); - // get position for initial azimuth, elevation - orbitCenter = intersection.intersection; - var orbitVector = Vec3.subtract(cameraPosition, orbitCenter); - orbitRadius = Vec3.length(orbitVector); - orbitAzimuth = Math.atan2(orbitVector.z, orbitVector.x); - orbitAltitude = Math.asin(orbitVector.y / Vec3.length(orbitVector)); - - //print("startOrbitMode..."); -} - -function handleOrbitingMove(event) { - var cameraOrientation = Camera.getOrientation(); - var origEulers = Quat.safeEulerAngles(cameraOrientation); - var newEulers = fixEulerAngles(Quat.safeEulerAngles(cameraOrientation)); - var dx = event.x - mouseX; - var dy = event.y - mouseY; - orbitAzimuth += dx / ORBIT_RATE_AZIMUTH; - orbitAltitude += dy / ORBIT_RATE_ALTITUDE; - var orbitVector = { x:(Math.cos(orbitAltitude) * Math.cos(orbitAzimuth)) * orbitRadius, - y:Math.sin(orbitAltitude) * orbitRadius, - z:(Math.cos(orbitAltitude) * Math.sin(orbitAzimuth)) * orbitRadius }; - orbitPosition = Vec3.sum(orbitCenter, orbitVector); - Camera.setPosition(orbitPosition); - - mouseX = event.x; - mouseY = event.y; - //print("handleOrbitingMove..."); -} - -function endOrbitMode(event) { - var cameraOrientation = Camera.getOrientation(); - MyAvatar.position = Vec3.subtract(Camera.getPosition(), torsoToEyeVector); - MyAvatar.headOrientation = cameraOrientation; - Camera.stopLooking(); - Camera.setMode(oldMode); - Camera.setOrientation(cameraOrientation); - //print("endOrbitMode..."); -} - -function startPanMode(event, intersection) { - // start pan camera! - print("handle PAN mode!!!"); -} - -function handlePanMove(event) { - print("PANNING mode!!! "); - //print("isPanning="+isPanning + " inPanningFromTouch="+isPanningFromTouch + " trackAsOrbitOrPan="+trackAsOrbitOrPan); -} - -function endPanMode(event) { - print("ending PAN mode!!!"); -} - - function mousePressEvent(event) { // if our tools are off, then don't do anything @@ -891,31 +788,6 @@ function mousePressEvent(event) { return; } - // Normally, if we're panning or orbiting from touch, ignore these... because our touch takes precedence. - // but In the case of a button="RIGHT" click, we may get some touch messages first, and we actually want to - // cancel any touch mode, and then let the right-click through - if (isOrbitingFromTouch || isPanningFromTouch) { - - // if the user is holding the ALT key AND they are clicking the RIGHT button (or on multi-touch doing a two - // finger touch, then we want to let the new panning behavior take over. - // if it's any other case we still want to bail - if (event.button == "RIGHT" && trackAsOrbitOrPan) { - // cancel our current multitouch operation... - if (isOrbitingFromTouch) { - endOrbitMode(event); - isOrbitingFromTouch = false; - } - if (isPanningFromTouch) { - //print("mousePressEvent... calling endPanMode()"); - endPanMode(event); - isPanningFromTouch = false; - } - // let things fall through - } else { - return; - } - } - // no clicking on overlays while in panning mode if (!trackAsOrbitOrPan) { var clickedOnSomething = false; @@ -1008,17 +880,7 @@ function mousePressEvent(event) { calcThumbFromScale(intersection.voxel.s); } - // Note: touch and mouse events can cross paths, so we want to ignore any mouse events that would - // start a pan or orbit if we're already doing a pan or orbit via touch... - if ((event.isAlt || trackAsOrbitOrPan) && !(isOrbitingFromTouch || isPanningFromTouch)) { - if (event.isLeftButton && !event.isRightButton) { - startOrbitMode(event); - isOrbiting = true; - } else if (event.isRightButton && !event.isLeftButton) { - startPanMode(event); - isPanning = true; - } - } else if (trackAsDelete || event.isRightButton && !trackAsEyedropper) { + if (trackAsDelete || event.isRightButton && !trackAsEyedropper) { // Delete voxel voxelDetails = calculateVoxelFromIntersection(intersection,"delete"); Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s); @@ -1209,23 +1071,7 @@ function mouseMoveEvent(event) { return; } - // if we're panning or orbiting from touch, ignore these... because our touch takes precedence. - if (isOrbitingFromTouch || isPanningFromTouch) { - return; - } - // double check that we didn't accidentally miss a pan or orbit click request - if (trackAsOrbitOrPan && !isPanning && !isOrbiting) { - if (event.isLeftButton && !event.isRightButton) { - startOrbitMode(event); - isOrbiting = true; - } - if (!event.isLeftButton && event.isRightButton) { - startPanMode(event); - isPanning = true; - } - } - if (!trackAsOrbitOrPan && isMovingSlider) { thumbX = (event.x - thumbClickOffsetX) - sliderX; if (thumbX < minThumbX) { @@ -1236,10 +1082,6 @@ function mouseMoveEvent(event) { } calcScaleFromThumb(thumbX); - } else if (isOrbiting) { - handleOrbitingMove(event); - } else if (isPanning) { - handlePanMove(event); } else if (!trackAsOrbitOrPan && isAdding) { // Watch the drag direction to tell which way to 'extrude' this voxel if (!isExtruding) { @@ -1293,16 +1135,6 @@ function mouseReleaseEvent(event) { if (isMovingSlider) { isMovingSlider = false; } - - if (isOrbiting) { - endOrbitMode(event); - isOrbiting = false; - } - if (isPanning) { - print("mouseReleaseEvent... calling endPanMode()"); - endPanMode(event); - isPanning = false; - } isAdding = false; isExtruding = false; } @@ -1396,96 +1228,18 @@ function touchBeginEvent(event) { if (!editToolsOn) { return; } - - // if we're already in the middle of orbiting or panning, then ignore these multi-touch events... - if (isOrbiting || isPanning) { - return; - } - - if (event.isAlt || trackAsOrbitOrPan) { - if (event.touchPoints == touchPointsToOrbit) { - // we need to double check that we didn't start an orbit, because the touch events will sometimes - // come in as 2 then 3 touches... - if (isPanningFromTouch) { - print("touchBeginEvent... calling endPanMode()"); - endPanMode(event); - isPanningFromTouch = false; - } - startOrbitMode(event); - isOrbitingFromTouch = true; - } else if (event.touchPoints == touchPointsToPan) { - // we need to double check that we didn't start an orbit, because the touch events will sometimes - // come in as 2 then 3 touches... - if (isOrbitingFromTouch) { - endOrbitMode(event); - isOrbitingFromTouch = false; - } - startPanMode(event); - isPanningFromTouch = true; - } - } } function touchUpdateEvent(event) { if (!editToolsOn) { return; } - - // if we're already in the middle of orbiting or panning, then ignore these multi-touch events... - if (isOrbiting || isPanning) { - return; - } - - if (isOrbitingFromTouch) { - // we need to double check that we didn't start an orbit, because the touch events will sometimes - // come in as 2 then 3 touches... - if (event.touchPoints == touchPointsToPan) { - //print("we now have touchPointsToPan touches... switch to pan..."); - endOrbitMode(event); - isOrbitingFromTouch = false; - startPanMode(event); - isPanningFromTouch = true; - } else { - handleOrbitingMove(event); - } - } - if (isPanningFromTouch) { - //print("touchUpdateEvent... isPanningFromTouch... event.touchPoints=" + event.touchPoints); - // we need to double check that we didn't start an orbit, because the touch events will sometimes - // come in as 2 then 3 touches... - if (event.touchPoints == touchPointsToOrbit) { - //print("we now have touchPointsToOrbit touches... switch to orbit..."); - //print("touchUpdateEvent... calling endPanMode()"); - endPanMode(event); - isPanningFromTouch = false; - startOrbitMode(event); - isOrbitingFromTouch = true; - handleOrbitingMove(event); - } else { - handlePanMove(event); - } - } } function touchEndEvent(event) { if (!editToolsOn) { return; } - - // if we're already in the middle of orbiting or panning, then ignore these multi-touch events... - if (isOrbiting || isPanning) { - return; - } - - if (isOrbitingFromTouch) { - endOrbitMode(event); - isOrbitingFromTouch = false; - } - if (isPanningFromTouch) { - print("touchEndEvent... calling endPanMode()"); - endPanMode(event); - isPanningFromTouch = false; - } } var lastFingerAddVoxel = { x: -1, y: -1, z: -1}; // off of the build-able area diff --git a/examples/inspect.js b/examples/inspect.js new file mode 100644 index 0000000000..9292450784 --- /dev/null +++ b/examples/inspect.js @@ -0,0 +1,240 @@ +// +// inspect.js +// hifi +// +// Created by Clément Brisset on March 20, 2014 +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// Allows you to inspect non moving objects (Voxels or Avatars) using Atl, Control (Command on Mac) and Shift +// +// radial mode = hold ALT +// orbit mode = hold ALT + CONTROL +// pan mode = hold ALT + CONTROL + SHIFT +// Once you are in a mode left click on the object to inspect and hold the click +// Dragging the mouse will move your camera according to the mode you are in. +// + +var AZIMUTH_RATE = 90.0; +var ALTITUDE_RATE = 200.0; +var RADIUS_RATE = 20.0; +var PAN_RATE = 50.0; + +var alt = false; +var shift = false; +var control = false; + +var isActive = false; + +var noMode = 0; +var orbitMode = 1; +var radialMode = 2; +var panningMode = 3; + +var mode = noMode; + +var mouseLastX = 0; +var mouseLastY = 0; + + +var center = { x: 0, y: 0, z: 0 }; +var position = { x: 0, y: 0, z: 0 }; +var vector = { x: 0, y: 0, z: 0 }; +var radius = 0.0; +var azimuth = 0.0; +var altitude = 0.0; + + +function handleRadialMode(dx, dy) { + azimuth += dx / AZIMUTH_RATE; + radius += radius * dy / RADIUS_RATE; + if (radius < 1) { + radius = 1; + } + + vector = { x: (Math.cos(altitude) * Math.cos(azimuth)) * radius, + y: Math.sin(altitude) * radius, + z: (Math.cos(altitude) * Math.sin(azimuth)) * radius }; + position = Vec3.sum(center, vector); + Camera.setPosition(position); +} + +function handleOrbitMode(dx, dy) { + azimuth += dx / AZIMUTH_RATE; + altitude += dy / ALTITUDE_RATE; + + vector = { x:(Math.cos(altitude) * Math.cos(azimuth)) * radius, + y:Math.sin(altitude) * radius, + z:(Math.cos(altitude) * Math.sin(azimuth)) * radius }; + position = Vec3.sum(center, vector); + Camera.setPosition(position); +} + + +function handlePanMode(dx, dy) { + var up = Quat.getUp(Camera.getOrientation()); + var right = Quat.getRight(Camera.getOrientation()); + var distance = Vec3.length(vector); + + var dv = Vec3.sum(Vec3.multiply(up, - distance * dy / PAN_RATE), Vec3.multiply(right, distance * dx / PAN_RATE)); + + center = Vec3.sum(center, dv); + position = Vec3.sum(position, dv); + + Camera.setPosition(position); + Camera.keepLookingAt(center); +} + +function saveCameraState() { + oldMode = Camera.getMode(); + var oldPosition = Camera.getPosition(); + Camera.setMode("independent"); + Camera.setPosition(oldPosition); +} + +function restoreCameraState() { + Camera.stopLooking(); + Camera.setMode(oldMode); +} + +function handleModes() { + var newMode = noMode; + if (alt) { + if (control) { + if (shift) { + newMode = panningMode; + } else { + newMode = orbitMode; + } + } else { + newMode = radialMode; + } + } + + // if leaving noMode + if (mode == noMode && newMode != noMode) { + saveCameraState(); + } + // if entering noMode + if (newMode == noMode && mode != noMode) { + restoreCameraState(); + } + + mode = newMode; +} + +function keyPressEvent(event) { + var changed = false; + + if (event.text == "ALT") { + alt = true; + changed = true; + } + if (event.text == "CONTROL") { + control = true; + changed = true; + } + if (event.text == "SHIFT") { + shift = true; + changed = true; + } + + if (changed) { + handleModes(); + } +} + +function keyReleaseEvent(event) { + var changed = false; + + if (event.text == "ALT") { + alt = false; + changed = true; + } + if (event.text == "CONTROL") { + control = false; + changed = true; + } + if (event.text == "SHIFT") { + shift = false; + changed = true; + } + + if (changed) { + handleModes(); + } +} + +function mousePressEvent(event) { + if (alt) { + isActive = true; + mouseLastX = event.x; + mouseLastY = event.y; + + // Compute trajectories related values + var pickRay = Camera.computePickRay(mouseLastX, mouseLastY); + var intersection = Voxels.findRayIntersection(pickRay); + + position = Camera.getPosition(); + + avatarTarget = MyAvatar.getTargetAvatarPosition(); + voxelTarget = intersection.intersection; + if (Vec3.length(Vec3.subtract(avatarTarget, position)) < Vec3.length(Vec3.subtract(voxelTarget, position))) { + if (avatarTarget.x != 0 || avatarTarget.y != 0 || avatarTarget.z != 0) { + center = avatarTarget; + } else { + center = voxelTarget; + } + } else { + if (voxelTarget.x != 0 || voxelTarget.y != 0 || voxelTarget.z != 0) { + center = voxelTarget; + } else { + center = avatarTarget; + } + } + + vector = Vec3.subtract(position, center); + radius = Vec3.length(vector); + azimuth = Math.atan2(vector.z, vector.x); + altitude = Math.asin(vector.y / Vec3.length(vector)); + + Camera.keepLookingAt(center); + } +} + +function mouseReleaseEvent(event) { + if (isActive) { + isActive = false; + } +} + +function mouseMoveEvent(event) { + if (isActive && mode != noMode) { + if (mode == radialMode) { + handleRadialMode(event.x - mouseLastX, event.y - mouseLastY); + } + if (mode == orbitMode) { + handleOrbitMode(event.x - mouseLastX, event.y - mouseLastY); + } + if (mode == panningMode) { + handlePanMode(event.x - mouseLastX, event.y - mouseLastY); + } + + mouseLastX = event.x; + mouseLastY = event.y; + } +} + +function scriptEnding() { + if (mode != noMode) { + restoreCameraState(); + } +} + +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); + +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); +Controller.mouseMoveEvent.connect(mouseMoveEvent); + +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/interface/interface_en.ts b/interface/interface_en.ts index 3c61d5f17a..05998e6436 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -4,22 +4,22 @@ Application - + Export Voxels - + Sparse Voxel Octree Files (*.svo) - + Open Script - + JavaScript Files (*.js) @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file @@ -132,122 +132,116 @@ PreferencesDialog - - + + Avatar - - + + <html><head/><body><p>Avatar display name <span style=" color:#909090;">(optional)</span></p></body></html> - - + + Not showing a name - - + + Head - - + + Body - - + + Advanced Tuning - - + + It's not recomended that you play with these settings unless you've looked into exactly what they do. - - + + <p>Avatar</p> - - + + Lean scale (applies to Faceshift users) - - + + Vertical field of view - - + + Avatar scale (default is 1.0) - - + + Pupil dillation - - + + Audio Jitter Buffer Samples (0 for automatic) - - + + Faceshift eye detection - - + + <html><head/><body><p>Voxels</p></body></html> - - + + Maximum voxels - - + + Max voxels sent each second - - - PushButton - - - - - + + Cancel - - + + Save all changes diff --git a/interface/resources/resources.qrc b/interface/resources/resources.qrc index 372fa8b1d4..300728392c 100644 --- a/interface/resources/resources.qrc +++ b/interface/resources/resources.qrc @@ -1,5 +1,6 @@ - - images/close.svg - + + images/close.svg + styles/search.svg + diff --git a/interface/resources/styles/global.qss b/interface/resources/styles/global.qss index d302555463..2554f3b2c9 100644 --- a/interface/resources/styles/global.qss +++ b/interface/resources/styles/global.qss @@ -13,8 +13,9 @@ QLineEdit { border-style: solid; border-width: 1px; border-color: #ccc; - padding: 7px; - opacity: 1; + padding: 8px; + font-size: 16px; + color: rgb(51, 51, 51); } QLabel p { @@ -27,14 +28,15 @@ QPushButton { border-radius: 9px; font-family: Arial; font-size: 18px; - font-weight: 100; color: #ffffff; - padding: 10px 15px; + padding: 10px 0px; } QSpinBox, QDoubleSpinBox { padding: 5px; border-width: 1; + font-size: 16px; + color: rgb(51, 51, 51); } QDoubleSpinBox::up-arrow, @@ -98,3 +100,14 @@ QSlider::handle:horizontal { background-repeat: no-repeat; background-position: center center; } + +QPushButton#closeButton { + border-color: #ccc; + border-style: solid; + border-width: 1px; + border-radius: 0; + background-color: #fff; + background-image: url(styles/close.svg); + background-repeat: no-repeat; + background-position: center center; +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 50dbf1b51a..030e3bc6fd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -692,6 +692,8 @@ bool Application::event(QEvent* event) { void Application::keyPressEvent(QKeyEvent* event) { + _keysPressed.insert(event->key()); + _controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -914,6 +916,8 @@ void Application::keyPressEvent(QKeyEvent* event) { void Application::keyReleaseEvent(QKeyEvent* event) { + _keysPressed.remove(event->key()); + _controllerScriptingInterface.emitKeyReleaseEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -921,60 +925,66 @@ void Application::keyReleaseEvent(QKeyEvent* event) { return; } + switch (event->key()) { + case Qt::Key_E: + _myAvatar->setDriveKeys(UP, 0.f); + break; - if (activeWindow() == _window) { - switch (event->key()) { - case Qt::Key_E: - _myAvatar->setDriveKeys(UP, 0.f); - break; + case Qt::Key_C: + _myAvatar->setDriveKeys(DOWN, 0.f); + break; - case Qt::Key_C: - _myAvatar->setDriveKeys(DOWN, 0.f); - break; + case Qt::Key_W: + _myAvatar->setDriveKeys(FWD, 0.f); + break; - case Qt::Key_W: - _myAvatar->setDriveKeys(FWD, 0.f); - break; + case Qt::Key_S: + _myAvatar->setDriveKeys(BACK, 0.f); + break; - case Qt::Key_S: - _myAvatar->setDriveKeys(BACK, 0.f); - break; + case Qt::Key_A: + _myAvatar->setDriveKeys(ROT_LEFT, 0.f); + break; - case Qt::Key_A: - _myAvatar->setDriveKeys(ROT_LEFT, 0.f); - break; + case Qt::Key_D: + _myAvatar->setDriveKeys(ROT_RIGHT, 0.f); + break; - case Qt::Key_D: - _myAvatar->setDriveKeys(ROT_RIGHT, 0.f); - break; + case Qt::Key_Up: + _myAvatar->setDriveKeys(FWD, 0.f); + _myAvatar->setDriveKeys(UP, 0.f); + break; - case Qt::Key_Up: - _myAvatar->setDriveKeys(FWD, 0.f); - _myAvatar->setDriveKeys(UP, 0.f); - break; + case Qt::Key_Down: + _myAvatar->setDriveKeys(BACK, 0.f); + _myAvatar->setDriveKeys(DOWN, 0.f); + break; - case Qt::Key_Down: - _myAvatar->setDriveKeys(BACK, 0.f); - _myAvatar->setDriveKeys(DOWN, 0.f); - break; + case Qt::Key_Left: + _myAvatar->setDriveKeys(LEFT, 0.f); + _myAvatar->setDriveKeys(ROT_LEFT, 0.f); + break; - case Qt::Key_Left: - _myAvatar->setDriveKeys(LEFT, 0.f); - _myAvatar->setDriveKeys(ROT_LEFT, 0.f); - break; + case Qt::Key_Right: + _myAvatar->setDriveKeys(RIGHT, 0.f); + _myAvatar->setDriveKeys(ROT_RIGHT, 0.f); + break; - case Qt::Key_Right: - _myAvatar->setDriveKeys(RIGHT, 0.f); - _myAvatar->setDriveKeys(ROT_RIGHT, 0.f); - break; - - default: - event->ignore(); - break; - } + default: + event->ignore(); + break; } } +void Application::focusOutEvent(QFocusEvent* event) { + // synthesize events for keys currently pressed, since we may not get their release events + foreach (int key, _keysPressed) { + QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier); + keyReleaseEvent(&event); + } + _keysPressed.clear(); +} + void Application::mouseMoveEvent(QMouseEvent* event) { _controllerScriptingInterface.emitMouseMoveEvent(event); // send events to any registered scripts @@ -1680,7 +1690,7 @@ void Application::updateMyAvatarLookAtPosition() { } else { // look in direction of the mouse ray, but use distance from intersection, if any float distance = TREE_SCALE; - if (_myAvatar->getLookAtTargetAvatar()) { + if (_myAvatar->getLookAtTargetAvatar() && _myAvatar != _myAvatar->getLookAtTargetAvatar()) { distance = glm::distance(_mouseRayOrigin, static_cast(_myAvatar->getLookAtTargetAvatar())->getHead()->calculateAverageEyePosition()); } @@ -2140,7 +2150,8 @@ void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) { } glm::vec3 Application::getSunDirection() { - return glm::normalize(_environment.getClosestData(_myCamera.getPosition()).getSunLocation() - _myCamera.getPosition()); + return glm::normalize(_environment.getClosestData(_myCamera.getPosition()).getSunLocation(_myCamera.getPosition()) - + _myCamera.getPosition()); } void Application::updateShadowMap() { @@ -2302,7 +2313,8 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { float alpha = 1.0f; if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { const EnvironmentData& closestData = _environment.getClosestData(whichCamera.getPosition()); - float height = glm::distance(whichCamera.getPosition(), closestData.getAtmosphereCenter()); + float height = glm::distance(whichCamera.getPosition(), + closestData.getAtmosphereCenter(whichCamera.getPosition())); if (height < closestData.getAtmosphereInnerRadius()) { alpha = 0.0f; diff --git a/interface/src/Application.h b/interface/src/Application.h index cbfbf4166d..28060113a9 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -122,6 +123,8 @@ public: void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); + void focusOutEvent(QFocusEvent* event); + void mouseMoveEvent(QMouseEvent* event); void mousePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); @@ -432,6 +435,8 @@ private: bool _mousePressed; // true if mouse has been pressed (clear when finished) + QSet _keysPressed; + GeometryCache _geometryCache; TextureCache _textureCache; diff --git a/interface/src/Environment.cpp b/interface/src/Environment.cpp index 1f9e23bee1..096b8770fb 100644 --- a/interface/src/Environment.cpp +++ b/interface/src/Environment.cpp @@ -92,7 +92,7 @@ glm::vec3 Environment::getGravity (const glm::vec3& position) { foreach (const ServerData& serverData, _data) { foreach (const EnvironmentData& environmentData, serverData) { - glm::vec3 vector = environmentData.getAtmosphereCenter() - position; + glm::vec3 vector = environmentData.getAtmosphereCenter(position) - position; float surfaceRadius = environmentData.getAtmosphereInnerRadius(); if (glm::length(vector) <= surfaceRadius) { // At or inside a planet, gravity is as set for the planet @@ -116,7 +116,7 @@ const EnvironmentData Environment::getClosestData(const glm::vec3& position) { float closestDistance = FLT_MAX; foreach (const ServerData& serverData, _data) { foreach (const EnvironmentData& environmentData, serverData) { - float distance = glm::distance(position, environmentData.getAtmosphereCenter()) - + float distance = glm::distance(position, environmentData.getAtmosphereCenter(position)) - environmentData.getAtmosphereOuterRadius(); if (distance < closestDistance) { closest = environmentData; @@ -132,6 +132,8 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3 // collide with the "floor" bool found = findCapsulePlanePenetration(start, end, radius, glm::vec4(0.0f, 1.0f, 0.0f, 0.0f), penetration); + glm::vec3 middle = (start + end) * 0.5f; + // get the lock for the duration of the call QMutexLocker locker(&_mutex); @@ -141,7 +143,7 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3 continue; // don't bother colliding with gravity-less environments } glm::vec3 environmentPenetration; - if (findCapsuleSpherePenetration(start, end, radius, environmentData.getAtmosphereCenter(), + if (findCapsuleSpherePenetration(start, end, radius, environmentData.getAtmosphereCenter(middle), environmentData.getAtmosphereInnerRadius(), environmentPenetration)) { penetration = addPenetrations(penetration, environmentPenetration); found = true; @@ -203,10 +205,12 @@ ProgramObject* Environment::createSkyProgram(const char* from, int* locations) { } void Environment::renderAtmosphere(Camera& camera, const EnvironmentData& data) { + glm::vec3 center = data.getAtmosphereCenter(camera.getPosition()); + glPushMatrix(); - glTranslatef(data.getAtmosphereCenter().x, data.getAtmosphereCenter().y, data.getAtmosphereCenter().z); - - glm::vec3 relativeCameraPos = camera.getPosition() - data.getAtmosphereCenter(); + glTranslatef(center.x, center.y, center.z); + + glm::vec3 relativeCameraPos = camera.getPosition() - center; float height = glm::length(relativeCameraPos); // use the appropriate shader depending on whether we're inside or outside diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index a91452c06d..513dcfe40c 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -55,6 +55,10 @@ void GLCanvas::keyReleaseEvent(QKeyEvent* event) { Application::getInstance()->keyReleaseEvent(event); } +void GLCanvas::focusOutEvent(QFocusEvent* event) { + Application::getInstance()->focusOutEvent(event); +} + void GLCanvas::mouseMoveEvent(QMouseEvent* event) { Application::getInstance()->mouseMoveEvent(event); } diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index ad396a48ce..f7f7fb7c20 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -31,6 +31,8 @@ protected: virtual void keyPressEvent(QKeyEvent* event); virtual void keyReleaseEvent(QKeyEvent* event); + virtual void focusOutEvent(QFocusEvent* event); + virtual void mouseMoveEvent(QMouseEvent* event); virtual void mousePressEvent(QMouseEvent* event); virtual void mouseReleaseEvent(QMouseEvent* event); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 54b6a5dac2..2b1003f2e4 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -24,11 +24,12 @@ #include #include #include -#include +#include #include #include #include +#include #include "Application.h" #include "Menu.h" @@ -36,6 +37,7 @@ #include "Util.h" #include "InfoView.h" #include "ui/MetavoxelEditor.h" +#include "ModelBrowser.h" Menu* Menu::_instance = NULL; @@ -138,9 +140,8 @@ Menu::Menu() : this, SLOT(goTo())); - addDisabledActionAndSeparator(fileMenu, "Upload/Browse"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::UploaderAvatarHead, 0, Application::getInstance(), SLOT(uploadFST())); - addActionToQMenuAndActionHash(fileMenu, MenuOption::UploaderAvatarSkeleton, 0, Application::getInstance(), SLOT(uploadFST())); + addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model"); + addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadFST, 0, Application::getInstance(), SLOT(uploadFST())); addDisabledActionAndSeparator(fileMenu, "Settings"); addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings())); @@ -693,7 +694,7 @@ void Menu::loginForCurrentDomain() { } void Menu::editPreferences() { - if (! _preferencesDialog) { + if (!_preferencesDialog) { _preferencesDialog = new PreferencesDialog(Application::getInstance()->getGLWidget()); _preferencesDialog->show(); } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 15a4ee1811..96d5267b9c 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -300,8 +300,7 @@ namespace MenuOption { const QString StopAllScripts = "Stop All Scripts"; const QString TestPing = "Test Ping"; const QString TransmitterDrive = "Transmitter Drive"; - const QString UploaderAvatarHead = "Upload Avatar Head"; - const QString UploaderAvatarSkeleton = "Upload Avatar Skeleton"; + const QString UploadFST = "Upload FST file"; const QString Visage = "Visage"; const QString Quit = "Quit"; const QString Voxels = "Voxels"; diff --git a/interface/src/ModelBrowser.cpp b/interface/src/ModelBrowser.cpp new file mode 100644 index 0000000000..e03081273f --- /dev/null +++ b/interface/src/ModelBrowser.cpp @@ -0,0 +1,150 @@ +// +// ModelBrowser.cpp +// hifi +// +// Created by Clement on 3/17/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ModelBrowser.h" + +static const QString PREFIX_PARAMETER_NAME = "prefix"; +static const QString MARKER_PARAMETER_NAME = "marker"; +static const QString IS_TRUNCATED_NAME = "IsTruncated"; +static const QString CONTAINER_NAME = "Contents"; +static const QString KEY_NAME = "Key"; + +ModelBrowser::ModelBrowser(ModelType modelType, QWidget* parent) : QWidget(parent), _type(modelType) { + QUrl url(S3_URL); + QUrlQuery query; + + if (_type == Head) { + query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); + } else if (_type == Skeleton) { + query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); + } + url.setQuery(query); + + _downloader = new FileDownloader(url); + connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); +} + +ModelBrowser::~ModelBrowser() { + delete _downloader; +} + +void ModelBrowser::downloadFinished() { + parseXML(_downloader->getData()); +} + +void ModelBrowser::browse() { + QDialog dialog(this); + dialog.setWindowTitle("Browse models"); + + QGridLayout* layout = new QGridLayout(&dialog); + dialog.setLayout(layout); + + QLineEdit* searchBar = new QLineEdit(&dialog); + layout->addWidget(searchBar, 0, 0); + + ListView* listView = new ListView(&dialog); + listView->setEditTriggers(QAbstractItemView::NoEditTriggers); + layout->addWidget(listView, 1, 0); + listView->connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(keyboardSearch(const QString&))); + dialog.connect(listView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept())); + + QStringListModel* model = new QStringListModel(_models.keys(), listView); + model->sort(0); + listView->setModel(model); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + layout->addWidget(buttons, 2, 0); + dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); + dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); + + if (dialog.exec() == QDialog::Rejected) { + return; + } + + QString selectedKey = model->data(listView->currentIndex(), Qt::DisplayRole).toString(); + + emit selected(_models[selectedKey]); +} + +bool ModelBrowser::parseXML(QByteArray xmlFile) { + QXmlStreamReader xml(xmlFile); + QRegExp rx(".*fst"); + bool truncated = false; + QString lastKey; + + // Read xml until the end or an error is detected + while(!xml.atEnd() && !xml.hasError()) { + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) { + while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) { + // Let's check if there is more + xml.readNext(); + if (xml.text().toString() == "True") { + truncated = true; + } + } + } + + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) { + while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) { + // If a file is find, process it + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) { + xml.readNext(); + lastKey = xml.text().toString(); + if (rx.exactMatch(xml.text().toString())) { + // Add the found file to the list + _models.insert(QFileInfo(xml.text().toString()).baseName(), + S3_URL + "/" + xml.text().toString()); + } + } + xml.readNext(); + } + } + xml.readNext(); + } + + // Error handling + if(xml.hasError()) { + _models.clear(); + QMessageBox::critical(this, + "ModelBrowser::ModelBrowser()", + xml.errorString(), + QMessageBox::Ok); + return false; + } + + // If we didn't all the files, download the next ones + if (truncated) { + QUrl url(S3_URL); + QUrlQuery query; + + if (_type == Head) { + query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); + } else if (_type == Skeleton) { + query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); + } + query.addQueryItem(MARKER_PARAMETER_NAME, lastKey); + url.setQuery(query); + + delete _downloader; + _downloader = new FileDownloader(url); + connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); + } + + return true; +} \ No newline at end of file diff --git a/interface/src/ModelBrowser.h b/interface/src/ModelBrowser.h new file mode 100644 index 0000000000..4628642e89 --- /dev/null +++ b/interface/src/ModelBrowser.h @@ -0,0 +1,62 @@ +// +// ModelBrowser.h +// hifi +// +// Created by Clement on 3/17/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__ModelBrowser__ +#define __hifi__ModelBrowser__ + +#include + +#include +#include + +static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; +static const QString HEAD_MODELS_LOCATION = "models/heads/"; +static const QString SKELETON_MODELS_LOCATION = "models/skeletons/"; + +enum ModelType { + Head, + Skeleton +}; + +class ModelBrowser : public QWidget { + Q_OBJECT + +public: + ModelBrowser(ModelType modelType, QWidget* parent = NULL); + ~ModelBrowser(); + +signals: + void selected(QString filename); + +public slots: + void browse(); + +private slots: + void downloadFinished(); + +private: + ModelType _type; + FileDownloader* _downloader; + QHash _models; + + bool parseXML(QByteArray xmlFile); +}; + + + +class ListView : public QListView { + Q_OBJECT +public: + ListView(QWidget* parent) : QListView(parent) {} + public slots: + void keyboardSearch(const QString& text) { + QAbstractItemView::keyboardSearch(text); + } +}; + +#endif /* defined(__hifi__ModelBrowser__) */ diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fb0d704c6a..13e2d3321a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -582,16 +582,15 @@ void MyAvatar::updateLookAtTargetAvatar() { foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) { Avatar* avatar = static_cast(avatarPointer.data()); - if (avatar == static_cast(this)) { - continue; - } float distance; if (avatar->findRayIntersection(mouseOrigin, mouseDirection, distance)) { _lookAtTargetAvatar = avatarPointer; + _targetAvatarPosition = avatarPointer->getPosition(); return; } } _lookAtTargetAvatar.clear(); + _targetAvatarPosition = glm::vec3(0, 0, 0); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3544fb1a61..d958103fa6 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -76,6 +76,7 @@ public: void orbit(const glm::vec3& position, int deltaX, int deltaY); + Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; } AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); } void updateLookAtTargetAvatar(); void clearLookAtTargetAvatar(); @@ -116,6 +117,7 @@ private: glm::vec3 _moveTarget; int _moveTargetStepCounter; QWeakPointer _lookAtTargetAvatar; + glm::vec3 _targetAvatarPosition; bool _shouldRender; bool _billboardValid; diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 2b43c89998..b3820abf25 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -313,7 +313,7 @@ void ImageReader::run() { int imageArea = image.width() * image.height(); if (opaquePixels == imageArea) { qDebug() << "Image with alpha channel is completely opaque:" << url; - image.convertToFormat(QImage::Format_RGB888); + image = image.convertToFormat(QImage::Format_RGB888); } QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, translucentPixels >= imageArea / 2)); diff --git a/interface/src/ui/FramelessDialog.cpp b/interface/src/ui/FramelessDialog.cpp index 9239deed90..1f67e9f4d1 100644 --- a/interface/src/ui/FramelessDialog.cpp +++ b/interface/src/ui/FramelessDialog.cpp @@ -6,6 +6,7 @@ // // +#include #include #include #include @@ -13,13 +14,29 @@ #include "Application.h" #include "FramelessDialog.h" -FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags) : QDialog(parent, flags | Qt::FramelessWindowHint) { - setWindowOpacity(0.9); +const int RESIZE_HANDLE_WIDTH = 7; + +FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags) : + QDialog(parent, flags | Qt::FramelessWindowHint), + _isResizing(false) { + + setWindowOpacity(0.95); setAttribute(Qt::WA_DeleteOnClose); - isResizing = false; + + installEventFilter(this); } -void FramelessDialog::setStyleSheet(const QString& fileName) { +void FramelessDialog::showEvent(QShowEvent* event) { + QDesktopWidget desktop; + + // move to upper left + move(desktop.availableGeometry().x(), desktop.availableGeometry().y()); + + // keep full height + resize(size().width(), desktop.availableGeometry().height()); +} + +void FramelessDialog::setStyleSheetFile(const QString& fileName) { QFile globalStyleSheet(Application::resourcesPath() + "styles/global.qss"); QFile styleSheet(Application::resourcesPath() + fileName); if (styleSheet.open(QIODevice::ReadOnly) && globalStyleSheet.open(QIODevice::ReadOnly) ) { @@ -27,27 +44,24 @@ void FramelessDialog::setStyleSheet(const QString& fileName) { QDialog::setStyleSheet(globalStyleSheet.readAll() + styleSheet.readAll()); } } + void FramelessDialog::mousePressEvent(QMouseEvent* mouseEvent) { - if (abs(mouseEvent->pos().x() - size().width()) < 2 && mouseEvent->button() == Qt::LeftButton) { - isResizing = true; + + if (abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH && mouseEvent->button() == Qt::LeftButton) { + _isResizing = true; QApplication::setOverrideCursor(Qt::SizeHorCursor); } - // propagate the event - QDialog::mousePressEvent(mouseEvent); } void FramelessDialog::mouseReleaseEvent(QMouseEvent* mouseEvent) { QApplication::restoreOverrideCursor(); - isResizing = false; - // propagate the event - QDialog::mouseReleaseEvent(mouseEvent); + _isResizing = false; } void FramelessDialog::mouseMoveEvent(QMouseEvent* mouseEvent) { - if (isResizing) { + if (_isResizing) { resize(mouseEvent->pos().x(), size().height()); } - QDialog::mouseMoveEvent(mouseEvent); } FramelessDialog::~FramelessDialog() { diff --git a/interface/src/ui/FramelessDialog.h b/interface/src/ui/FramelessDialog.h index 4e18aff161..2bb8d6b85c 100644 --- a/interface/src/ui/FramelessDialog.h +++ b/interface/src/ui/FramelessDialog.h @@ -21,15 +21,16 @@ class FramelessDialog : public QDialog { public: FramelessDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0); virtual ~FramelessDialog(); - void setStyleSheet(const QString& fileName); + void setStyleSheetFile(const QString& fileName); protected: - void mouseMoveEvent(QMouseEvent* mouseEvent); - void mousePressEvent(QMouseEvent* mouseEvent); - void mouseReleaseEvent(QMouseEvent* mouseEvent); + virtual void mouseMoveEvent(QMouseEvent* mouseEvent); + virtual void mousePressEvent(QMouseEvent* mouseEvent); + virtual void mouseReleaseEvent(QMouseEvent* mouseEvent); + virtual void showEvent(QShowEvent* event); private: - bool isResizing; + bool _isResizing; }; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 318db54f2c..56dbf8470c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -6,14 +6,27 @@ // // -#include "PreferencesDialog.h" #include "Application.h" #include "Menu.h" +#include "ModelBrowser.h" +#include "PreferencesDialog.h" + +const int SCROLL_PANEL_BOTTOM_MARGIN = 30; +const int OK_BUTTON_RIGHT_MARGIN = 30; +const int BUTTONS_TOP_MARGIN = 24; PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : FramelessDialog(parent, flags) { + ui.setupUi(this); - setStyleSheet("styles/preferences.qss"); + setStyleSheetFile("styles/preferences.qss"); loadPreferences(); + connect(ui.closeButton, &QPushButton::clicked, this, &QDialog::close); + connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &PreferencesDialog::openHeadModelBrowser); + connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &PreferencesDialog::openBodyModelBrowser); +} + +PreferencesDialog::~PreferencesDialog() { + deleteLater(); } void PreferencesDialog::accept() { @@ -21,6 +34,39 @@ void PreferencesDialog::accept() { close(); } +void PreferencesDialog::openHeadModelBrowser() { + ModelBrowser modelBrowser(Head); + modelBrowser.browse(); + connect(&modelBrowser, &ModelBrowser::selected, ui.faceURLEdit, &QLineEdit::setText); +} + +void PreferencesDialog::openBodyModelBrowser() { + ModelBrowser modelBrowser(Skeleton); + modelBrowser.browse(); + connect(&modelBrowser, &ModelBrowser::selected, ui.skeletonURLEdit, &QLineEdit::setText); +} + +void PreferencesDialog::resizeEvent(QResizeEvent *resizeEvent) { + + // keep buttons panel at the bottom + ui.buttonsPanel->setGeometry(0, size().height() - ui.buttonsPanel->height(), size().width(), ui.buttonsPanel->height()); + + // set width and height of srcollarea to match bottom panel and width + ui.scrollArea->setGeometry(ui.scrollArea->geometry().x(), ui.scrollArea->geometry().y(), + size().width(), + size().height() - ui.buttonsPanel->height() - + SCROLL_PANEL_BOTTOM_MARGIN - ui.scrollArea->geometry().y()); + + // move Save button to left position + ui.defaultButton->move(size().width() - OK_BUTTON_RIGHT_MARGIN - ui.defaultButton->size().width(), BUTTONS_TOP_MARGIN); + + // move Save button to left position + ui.cancelButton->move(ui.defaultButton->pos().x() - ui.cancelButton->size().width(), BUTTONS_TOP_MARGIN); + + // move close button + ui.closeButton->move(size().width() - OK_BUTTON_RIGHT_MARGIN - ui.closeButton->size().width(), ui.closeButton->pos().y()); +} + void PreferencesDialog::loadPreferences() { MyAvatar* myAvatar = Application::getInstance()->getAvatar(); diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h index d5cb41c7df..f1f37c66cd 100644 --- a/interface/src/ui/PreferencesDialog.h +++ b/interface/src/ui/PreferencesDialog.h @@ -19,16 +19,22 @@ class PreferencesDialog : public FramelessDialog { public: PreferencesDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0); + ~PreferencesDialog(); + +protected: + void resizeEvent(QResizeEvent* resizeEvent); private: - Ui_PreferencesDialog ui; void loadPreferences(); void savePreferences(); - + void openHeadModelBrowser(); + void openBodyModelBrowser(); + + Ui_PreferencesDialog ui; QString _faceURLString; QString _skeletonURLString; QString _displayNameString; - + private slots: void accept(); diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index 0099838177..0f33e2a0b0 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -3,14 +3,14 @@ PreferencesDialog - Qt::ApplicationModal + Qt::NonModal 0 0 - 640 - 752 + 610 + 499 @@ -25,15 +25,27 @@ 0 - + 0 - 720 - 616 - 80 + 400 + 611 + 97 + + + 0 + 0 + + + + + 0 + 97 + + Arial @@ -42,1136 +54,1245 @@ background-color: #0e7077 - + - 0 - 20 - 591 - 41 + 310 + 24 + 91 + 50 + + + 0 + 0 + + + + + 0 + 50 + + Arial + 18 + 50 + false - - - 0 - - - - - - Arial - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - - Arial - - - - Cancel - - - - - - - - Arial - - - - background-color: #fff; + + + + + Cancel + + + + + + 400 + 24 + 188 + 50 + + + + + 0 + 0 + + + + + 188 + 49 + + + + + Arial + 18 + + + + background-color: #fff; color: #0e7077 - - - Save all changes - - - true - - - - + + + Save all changes + + + true + 0 - 0 - 621 - 1000 + 30 + 615 + 351 - - - 0 - 0 - + + QFrame::NoFrame + + + QFrame::Plain + + + 0 true - + 0 0 - 619 - 998 + 615 + 833 - - - - 0 - 50 - 621 - 783 - + + + 0 - - - Arial - + + 30 - - - 0 - - - 30 - - - 0 - - - 30 - - - - - - 0 - 0 - - - - - Arial - 24 - - - - color: #0e7077 - - - Avatar - - - 25 - - - - - - - - - - - 0 - 0 - - - - - 0 - 30 - - - - - Arial - 16 - - - - color: #0e7077 - - - <html><head/><body><p>Avatar display name <span style=" color:#909090;">(optional)</span></p></body></html> - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - displayNameEdit - - - - - - - - 0 - 0 - - - - - 280 - 0 - - - - - Arial - - - - Qt::LeftToRight - - - - - - Not showing a name - - - - - - - - 0 - 0 - - - - - 0 - 30 - - - - - Arial - 16 - - - - color: #0e7077 - - - Head - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - 0 - - - faceURLEdit - - - - - - - - 0 - 0 - - - - - Arial - - - - - - - - - 0 - 0 - - - - - 0 - 30 - - - - - Arial - 16 - - - - color: #0e7077 - - - Body - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - skeletonURLEdit - - - - - - - - 0 - 0 - - - - - Arial - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - Arial - 24 - - - - color: #0e7077 - - - Advanced Tuning - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 25 - - - - - - - - 0 - 0 - - - - - Arial - - - - color: rgb(51, 51, 51) - - - It's not recomended that you play with these settings unless you've looked into exactly what they do. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 0 - 40 - - - - - Arial - 20 - 50 - false - - - - color: #0e7077 - - - <p>Avatar</p> - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - - - - - 0 - - - 10 - - - 10 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 25 - - - - - Arial - - - - color: rgb(51, 51, 51) - - - Lean scale (applies to Faceshift users) - - - leanScaleSpin - - - - - - - - Arial - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 10 - - - - - - - - - 0 - 0 - - - - - 95 - 36 - - - - - 70 - 16777215 - - - - - Arial - - - - - - - - - - 0 - - - 10 - - - 0 - - - 10 - - - - - - Arial - - - - color: rgb(51, 51, 51) - - - Vertical field of view - - - fieldOfViewSpin - - - - - - - - Arial - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 95 - 36 - - - - - 70 - 16777215 - - - - - Arial - - - - 1 - - - 180 - - - - - - - - - 0 - - - 10 - - - 0 - - - 10 - - - - - - Arial - - - - color: rgb(51, 51, 51) - - - Avatar scale (default is 1.0) - - - avatarScaleSpin - - - - - - - - Arial - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 95 - 36 - - - - - 70 - 16777215 - - - - - Arial - - - - - - - - - - 0 - - - 10 - - - 0 - - - 10 - - - - - - Arial - - - - color: rgb(51, 51, 51) - - - Pupil dillation - - - - - - - - Arial - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 125 - 0 - - - - - Arial - - - - Qt::Horizontal - - - - - - - - - 0 - - - 10 - - - 0 - - - 10 - - - - - - Arial - - - - color: rgb(51, 51, 51) - - - Audio Jitter Buffer Samples (0 for automatic) - - - - - - - - Arial - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 95 - 36 - - - - - 70 - 16777215 - - - - - Arial - - - - -10000 - - - 10000 - - - 1 - - - - - - - - - 0 - - - 10 - - - 0 - - - 10 - - - - - - Arial - - - - color: rgb(51, 51, 51) - - - Faceshift eye detection - - - avatarScaleSpin - - - - - - - - Arial - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 125 - 0 - - - - - Arial - - - - Qt::Horizontal - - - - - - - - - - 0 - 0 - - - - - 0 - 40 - - - - - Arial - 20 - 50 - false - - - - color: #0e7077 - - - <html><head/><body><p>Voxels</p></body></html> - - - - - - - 0 - - - 10 - - - 0 - - - 10 - - - - - - Arial - - - - color: rgb(51, 51, 51) - - - Maximum voxels - - - leanScaleSpin - - - - - - - - Arial - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 125 - 36 - - - - - Arial - - - - 5000000 - - - 50000 - - - - - - - - - 0 - - - 10 - - - 0 - - - 10 - - - - - - Arial - - - - Max voxels sent each second - - - leanScaleSpin - - - - - - - - Arial - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 95 - 36 - - - - - 70 - 16777215 - - - - - Arial - - - - 60 - - - 6000 - - - 10 - - - - - - - - - - - 540 - 40 - 114 - 32 - + + 0 - - PushButton + + 30 - + + 30 + + + + + + 0 + 0 + + + + + Arial + 24 + + + + color: #0e7077 + + + Avatar + + + 25 + + + + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + Arial + 16 + + + + color: #0e7077 + + + <html><head/><body><p>Avatar display name <span style=" color:#909090;">(optional)</span></p></body></html> + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + displayNameEdit + + + + + + + + 0 + 0 + + + + + 280 + 20 + + + + + Arial + + + + Qt::LeftToRight + + + + + + Not showing a name + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + Arial + 16 + + + + color: #0e7077 + + + Head + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + 0 + + + faceURLEdit + + + + + + + + + + 0 + 0 + + + + + Arial + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + background-color: #fff; +border-radius: 0; +padding: 0; + + + + + + + :/styles/search.svg + + + + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + Arial + 16 + + + + color: #0e7077 + + + Body + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + skeletonURLEdit + + + + + + + + + + 0 + 0 + + + + + Arial + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + background-color: #fff; +border-radius: 0; +padding: 0; + + + + + + + :/styles/search.svg + + + + + + + + + + Qt::Vertical + + + + 0 + 35 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + Arial + 24 + + + + color: #0e7077 + + + Advanced Tuning + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 25 + + + + + + + + 0 + 0 + + + + + Arial + 16 + + + + color: rgb(51, 51, 51) + + + It's not recomended that you play with these settings unless you've looked into exactly what they do. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + Arial + 20 + 50 + false + + + + color: #0e7077 + + + <p>Avatar</p> + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Vertical field of view + + + fieldOfViewSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + 1 + + + 180 + + + + + + + + + 0 + + + 10 + + + 10 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 25 + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Lean scale (applies to Faceshift users) + + + leanScaleSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 10 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Avatar scale (default is 1.0) + + + avatarScaleSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Pupil dillation + + + pupilDilationSlider + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 125 + 0 + + + + + Arial + + + + Qt::Horizontal + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Audio Jitter Buffer Samples (0 for automatic) + + + audioJitterSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + -10000 + + + 10000 + + + 1 + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Faceshift eye detection + + + faceshiftEyeDeflectionSider + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 125 + 0 + + + + + Arial + + + + Qt::Horizontal + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + Arial + 20 + 50 + false + + + + color: #0e7077 + + + <html><head/><body><p>Voxels</p></body></html> + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Maximum voxels + + + maxVoxelsSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 125 + 36 + + + + + Arial + + + + 5000000 + + + 50000 + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + Max voxels sent each second + + + maxVoxelsPPSSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + 60 + + + 6000 + + + 10 + + + + + + + + + + 540 + 24 + 31 + 31 + + + + + PreferAntialias + + + + + + @@ -1179,6 +1300,41 @@ color: #0e7077 1 - - + + + + + + cancelButton + clicked() + PreferencesDialog + close() + + + 495 + 749 + + + 528 + 0 + + + + + defaultButton + clicked() + PreferencesDialog + accept() + + + 504 + 749 + + + 20 + 20 + + + + diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 0e92ab4eb5..cf35b6a20f 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -19,7 +19,8 @@ AudioRingBuffer::AudioRingBuffer(int numFrameSamples) : NodeData(), _sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES), _isStarved(true), - _hasStarted(false) + _hasStarted(false), + _averageLoudness(0) { if (numFrameSamples) { _buffer = new int16_t[_sampleCapacity]; @@ -55,18 +56,22 @@ int AudioRingBuffer::parseData(const QByteArray& packet) { return writeData(packet.data() + numBytesPacketHeader, packet.size() - numBytesPacketHeader); } -float AudioRingBuffer::averageLoudnessForBoundarySamples(int numSamples) { +void AudioRingBuffer::updateAverageLoudnessForBoundarySamples(int numSamples) { // ForBoundarySamples means that we expect the number of samples not to roll of the end of the ring buffer - float averageLoudness = 0; + float nextLoudness = 0; for (int i = 0; i < numSamples; ++i) { - averageLoudness += fabsf(_nextOutput[i]); + nextLoudness += fabsf(_nextOutput[i]); } - averageLoudness /= numSamples; - averageLoudness /= MAX_SAMPLE_VALUE; + nextLoudness /= numSamples; + nextLoudness /= MAX_SAMPLE_VALUE; + + const int TRAILING_AVERAGE_FRAMES = 100; + const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; + const float PREVIOUS_FRAMES_RATIO = 1 - CURRENT_FRAME_RATIO; - return averageLoudness; + _averageLoudness = (_averageLoudness * PREVIOUS_FRAMES_RATIO) + (CURRENT_FRAME_RATIO * nextLoudness); } qint64 AudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples) { diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 0beb45ca18..efc85ac94b 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -50,7 +50,8 @@ public: const int16_t* getNextOutput() { return _nextOutput; } const int16_t* getBuffer() { return _buffer; } - float averageLoudnessForBoundarySamples(int numSamples); + void updateAverageLoudnessForBoundarySamples(int numSamples); + float getAverageLoudness() const { return _averageLoudness; } qint64 readSamples(int16_t* destination, qint64 maxSamples); qint64 writeSamples(const int16_t* source, qint64 maxSamples); @@ -85,6 +86,8 @@ protected: int16_t* _buffer; bool _isStarved; bool _hasStarted; + + float _averageLoudness; }; #endif /* defined(__interface__AudioRingBuffer__) */ diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 66a27647d6..ccbe5d3f23 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -50,6 +50,7 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) { int16_t numSilentSamples; memcpy(&numSilentSamples, packet.data() + readBytes, sizeof(int16_t)); + readBytes += sizeof(int16_t); addSilentFrame(numSilentSamples); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index e5e0e4b3d7..4e57e311eb 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -88,6 +88,17 @@ QByteArray AvatarData::toByteArray() { // Body scale destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale); + // Head rotation (NOTE: This needs to become a quaternion to save two bytes) + destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getTweakedYaw()); + destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getTweakedPitch()); + destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getTweakedRoll()); + + // Head lean X,Z (head lateral and fwd/back motion relative to torso) + memcpy(destinationBuffer, &_headData->_leanSideways, sizeof(_headData->_leanSideways)); + destinationBuffer += sizeof(_headData->_leanSideways); + memcpy(destinationBuffer, &_headData->_leanForward, sizeof(_headData->_leanForward)); + destinationBuffer += sizeof(_headData->_leanForward); + // Lookat Position memcpy(destinationBuffer, &_headData->_lookAtPosition, sizeof(_headData->_lookAtPosition)); destinationBuffer += sizeof(_headData->_lookAtPosition); @@ -191,6 +202,21 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { // Body scale sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale); + // Head rotation (NOTE: This needs to become a quaternion to save two bytes) + float headYaw, headPitch, headRoll; + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll); + _headData->setYaw(headYaw); + _headData->setPitch(headPitch); + _headData->setRoll(headRoll); + + // Head position relative to pelvis + memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways)); + sourceBuffer += sizeof(float); + memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward)); + sourceBuffer += sizeof(_headData->_leanForward); + // Lookat Position memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition)); sourceBuffer += sizeof(_headData->_lookAtPosition); diff --git a/libraries/shared/src/FileDownloader.cpp b/libraries/shared/src/FileDownloader.cpp new file mode 100644 index 0000000000..2b65bbd8df --- /dev/null +++ b/libraries/shared/src/FileDownloader.cpp @@ -0,0 +1,65 @@ +// +// FileDownloader.cpp +// hifi +// +// Created by Clement Brisset on 3/14/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// + +#include +#include +#include +#include + +#include "FileDownloader.h" + +FileDownloader::FileDownloader(const QUrl dataURL, QObject* parent) : + QObject(parent), + _done(false) +{ + connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*))); + + QNetworkRequest request(dataURL); + _networkAccessManager.get(request); +} + +void FileDownloader::processReply(QNetworkReply *reply) { + if (reply->error() == QNetworkReply::NoError) { + _downloadedData = reply->readAll(); + } + + reply->deleteLater(); + _done = true; + emit done(reply->error()); +} + +void FileDownloader::waitForFile(int timeout) { + QTimer timer; + QEventLoop loop; + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + connect(this, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit())); + + if (!_done) { + if (timeout > 0) { + timer.start(timeout); + } + loop.exec(); + } +} + +QByteArray FileDownloader::download(const QUrl dataURL, int timeout) { + QTimer timer; + QEventLoop loop; + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit)); + + FileDownloader downloader(dataURL); + connect(&downloader, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit())); + + if (timeout > 0) { + timer.start(timeout); + } + loop.exec(); + + return downloader.getData(); +} \ No newline at end of file diff --git a/libraries/shared/src/FileDownloader.h b/libraries/shared/src/FileDownloader.h new file mode 100644 index 0000000000..593b39b013 --- /dev/null +++ b/libraries/shared/src/FileDownloader.h @@ -0,0 +1,44 @@ +// +// FileDownloader.h +// hifi +// +// Created by Clement Brisset on 3/14/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// + +#ifndef __hifi__FileDownloader__ +#define __hifi__FileDownloader__ + +#include +#include +#include + +class FileDownloader : public QObject { + Q_OBJECT + +public: + FileDownloader(const QUrl dataURL, QObject* parent = NULL); + + void waitForFile(int timeout = 0); + + QByteArray getData() const { return _downloadedData; } + bool done() { return _done; } + + static QByteArray download(const QUrl dataURL, int timeout = 0); + +signals: + void done(QNetworkReply::NetworkError error); + +private slots: + void processReply(QNetworkReply* reply); + +private: + QNetworkAccessManager _networkAccessManager; + QByteArray _downloadedData; + + bool _done; +}; + + +#endif /* defined(__hifi__FileDownloader__) */ diff --git a/libraries/shared/src/FstReader.cpp b/libraries/shared/src/FstReader.cpp index d82ddf68a3..a803583598 100644 --- a/libraries/shared/src/FstReader.cpp +++ b/libraries/shared/src/FstReader.cpp @@ -24,6 +24,7 @@ static const QString NAME_FIELD = "name"; static const QString FILENAME_FIELD = "filename"; static const QString TEXDIR_FIELD = "texdir"; static const QString LOD_FIELD = "lod"; +static const QString HEAD_SPECIFIC_FIELD = "bs"; static const QString MODEL_URL = "/api/v1/models"; @@ -32,6 +33,7 @@ static const int MAX_SIZE = 10 * 1024 * 1024; // 10 MB FstReader::FstReader() : _lodCount(-1), _texturesCount(-1), + _isHead(false), _readyToSend(false), _dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType)) { @@ -81,13 +83,15 @@ bool FstReader::zip() { } // according to what is read, we modify the command - if (line.first() == NAME_FIELD) { + if (line[1] == HEAD_SPECIFIC_FIELD) { + _isHead = true; + } else if (line[1] == NAME_FIELD) { QHttpPart textPart; textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" " name=\"model_name\""); textPart.setBody(line[1].toUtf8()); _dataMultiPart->append(textPart); - } else if (line.first() == FILENAME_FIELD) { + } else if (line[1] == FILENAME_FIELD) { QFileInfo fbx(QFileInfo(fst).path() + "/" + line[1]); if (!fbx.exists() || !fbx.isFile()) { // Check existence qDebug() << "[ERROR] FBX file " << fbx.absoluteFilePath() << " doesn't exist."; @@ -101,7 +105,7 @@ bool FstReader::zip() { if (!addPart(_zipDir.path() + "/" + line[1], "fbx")) { return false; } - } else if (line.first() == TEXDIR_FIELD) { // Check existence + } else if (line[1] == TEXDIR_FIELD) { // Check existence QFileInfo texdir(QFileInfo(fst).path() + "/" + line[1]); if (!texdir.exists() || !texdir.isDir()) { qDebug() << "[ERROR] Texture directory " << texdir.absolutePath() << " doesn't exist."; @@ -110,7 +114,7 @@ bool FstReader::zip() { if (!addTextures(texdir)) { // Recursive compress and copy return false; } - } else if (line.first() == LOD_FIELD) { + } else if (line[1] == LOD_FIELD) { QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]); if (!lod.exists() || !lod.isFile()) { // Check existence qDebug() << "[ERROR] FBX file " << lod.absoluteFilePath() << " doesn't exist."; @@ -127,6 +131,17 @@ bool FstReader::zip() { } } + + QHttpPart textPart; + textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" + " name=\"model_category\""); + if (_isHead) { + textPart.setBody("head"); + } else { + textPart.setBody("skeleton"); + } + _dataMultiPart->append(textPart); + _readyToSend = true; return true; } diff --git a/libraries/shared/src/FstReader.h b/libraries/shared/src/FstReader.h index aab42bd967..1d9da71641 100644 --- a/libraries/shared/src/FstReader.h +++ b/libraries/shared/src/FstReader.h @@ -27,6 +27,7 @@ private: int _lodCount; int _texturesCount; int _totalSize; + bool _isHead; bool _readyToSend; QHttpMultiPart* _dataMultiPart; diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index 307453d8bf..c7518708ce 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -45,7 +45,9 @@ int packArithmeticallyCodedValue(int value, char* destination) { PacketVersion versionForPacketType(PacketType type) { switch (type) { case PacketTypeAvatarData: - return 2; + return 3; + case PacketTypeEnvironmentData: + return 1; case PacketTypeParticleData: return 1; case PacketTypeDomainList: diff --git a/libraries/voxels/src/EnvironmentData.cpp b/libraries/voxels/src/EnvironmentData.cpp index 5cee88e127..1c9af55abd 100644 --- a/libraries/voxels/src/EnvironmentData.cpp +++ b/libraries/voxels/src/EnvironmentData.cpp @@ -14,6 +14,7 @@ // GameEngine.cpp EnvironmentData::EnvironmentData(int id) : _id(id), + _flat(true), _gravity(0.0f), _atmosphereCenter(0, -1000, 0), _atmosphereInnerRadius(1000), @@ -25,12 +26,23 @@ EnvironmentData::EnvironmentData(int id) : _sunBrightness(20.0f) { } +glm::vec3 EnvironmentData::getAtmosphereCenter(const glm::vec3& cameraPosition) const { + return _atmosphereCenter + (_flat ? glm::vec3(cameraPosition.x, 0.0f, cameraPosition.z) : glm::vec3()); +} + +glm::vec3 EnvironmentData::getSunLocation(const glm::vec3& cameraPosition) const { + return _sunLocation + (_flat ? glm::vec3(cameraPosition.x, 0.0f, cameraPosition.z) : glm::vec3()); +} + int EnvironmentData::getBroadcastData(unsigned char* destinationBuffer) const { unsigned char* bufferStart = destinationBuffer; memcpy(destinationBuffer, &_id, sizeof(_id)); destinationBuffer += sizeof(_id); + memcpy(destinationBuffer, &_flat, sizeof(_flat)); + destinationBuffer += sizeof(_flat); + memcpy(destinationBuffer, &_gravity, sizeof(_gravity)); destinationBuffer += sizeof(_gravity); @@ -67,6 +79,9 @@ int EnvironmentData::parseData(const unsigned char* sourceBuffer, int numBytes) memcpy(&_id, sourceBuffer, sizeof(_id)); sourceBuffer += sizeof(_id); + memcpy(&_flat, sourceBuffer, sizeof(_flat)); + sourceBuffer += sizeof(_flat); + memcpy(&_gravity, sourceBuffer, sizeof(_gravity)); sourceBuffer += sizeof(_gravity); diff --git a/libraries/voxels/src/EnvironmentData.h b/libraries/voxels/src/EnvironmentData.h index 90cc0763fe..627a661e1c 100644 --- a/libraries/voxels/src/EnvironmentData.h +++ b/libraries/voxels/src/EnvironmentData.h @@ -19,6 +19,9 @@ public: void setID(int id) { _id = id; } int getID() const { return _id; } + void setFlat(bool flat) { _flat = flat; } + bool isFlat() const { return _flat; } + void setGravity(float gravity) { _gravity = gravity; } float getGravity() const { return _gravity; } @@ -42,6 +45,9 @@ public: const glm::vec3& getSunLocation() const { return _sunLocation; } float getSunBrightness() const { return _sunBrightness; } + glm::vec3 getAtmosphereCenter(const glm::vec3& cameraPosition) const; + glm::vec3 getSunLocation(const glm::vec3& cameraPosition) const; + int getBroadcastData(unsigned char* destinationBuffer) const; int parseData(const unsigned char* sourceBuffer, int numBytes); @@ -49,6 +55,8 @@ private: int _id; + bool _flat; + float _gravity; glm::vec3 _atmosphereCenter;