Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels

This commit is contained in:
Andrzej Kapolka 2014-03-21 11:12:10 -07:00
commit 924e5517f4
25 changed files with 428 additions and 303 deletions

View file

@ -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,12 +355,19 @@ 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);
}
}
@ -384,7 +396,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);

View file

@ -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__) */

View file

@ -6,6 +6,8 @@
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QDebug>
#include <PacketHeaders.h>
#include <UUID.h>
@ -14,8 +16,7 @@
#include "AudioMixerClientData.h"
AudioMixerClientData::AudioMixerClientData() :
_ringBuffers(),
_nextOutputLoudness(0)
_ringBuffers()
{
}
@ -82,16 +83,20 @@ 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);
}
}
}

View file

@ -24,14 +24,11 @@ public:
const std::vector<PositionalAudioRingBuffer*> 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<PositionalAudioRingBuffer*> _ringBuffers;
float _nextOutputLoudness;
};
#endif /* defined(__hifi__AudioMixerClientData__) */

View file

@ -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<char*>(_octreePacket), getMyPacketType());
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(_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;

View file

@ -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__) */

View file

@ -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++;

View file

@ -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;
}
}

View file

@ -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;
@ -35,21 +33,9 @@ var zFightingSizeAdjust = 0.002; // used to adjust preview voxels to prevent z f
var previewLineWidth = 1.5;
var oldMode = Camera.getMode();
var trackAsOrbitOrPan = false;
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,36 +788,10 @@ 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;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
// If the user clicked on the thumb, handle the slider logic
if (clickedOverlay == thumb) {
@ -1008,17 +879,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 +1070,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 +1081,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 +1134,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 +1227,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

240
examples/inspect.js Normal file
View file

@ -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);

View file

@ -40,7 +40,7 @@ detect_strip_roi_width 2
detect_strip_roi_height 4
smoothing_factors
150 5 -2 100 -1 50 50 0
5 25 -2 100 -1 50 25 0
#translation rotation action_units eyebrows mouth gaze eye_closure other
process_eyes 1

View file

@ -1690,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<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->calculateAverageEyePosition());
}

View file

@ -138,9 +138,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()));
@ -715,7 +714,6 @@ void Menu::editPreferences() {
QPushButton headBrowseButton(BROWSE_BUTTON_TEXT);
connect(&headBrowseButton, SIGNAL(clicked()), &headBrowser, SLOT(browse()));
connect(&headBrowser, SIGNAL(selected(QString)), &headURLEdit, SLOT(setText(QString)));
headURLEdit.setReadOnly(true);
headURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH);
headURLEdit.setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString());
headModelLayout.addWidget(&headURLEdit);
@ -728,7 +726,6 @@ void Menu::editPreferences() {
QPushButton SkeletonBrowseButton(BROWSE_BUTTON_TEXT);
connect(&SkeletonBrowseButton, SIGNAL(clicked()), &skeletonBrowser, SLOT(browse()));
connect(&skeletonBrowser, SIGNAL(selected(QString)), &skeletonURLEdit, SLOT(setText(QString)));
skeletonURLEdit.setReadOnly(true);
skeletonURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH);
skeletonURLEdit.setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString());
skeletonModelLayout.addWidget(&skeletonURLEdit);

View file

@ -295,8 +295,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";

View file

@ -31,7 +31,7 @@ public:
~ModelBrowser();
signals:
void selected(QString);
void selected(QString filename);
public slots:
void browse();

View file

@ -582,16 +582,15 @@ void MyAvatar::updateLookAtTargetAvatar() {
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (avatar == static_cast<Avatar*>(this)) {
continue;
}
float distance;
if (avatar->findRayIntersection(mouseOrigin, mouseDirection, distance)) {
_lookAtTargetAvatar = avatarPointer;
_targetAvatarPosition = avatarPointer->getPosition();
return;
}
}
_lookAtTargetAvatar.clear();
_targetAvatarPosition = glm::vec3(0, 0, 0);
}
}

View file

@ -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<AvatarData> _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;
bool _shouldRender;
bool _billboardValid;

View file

@ -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));

View file

@ -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) {

View file

@ -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__) */

View file

@ -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);
@ -79,13 +80,11 @@ int PositionalAudioRingBuffer::parsePositionalData(const QByteArray& positionalB
bool PositionalAudioRingBuffer::shouldBeAddedToMix(int numJitterBufferSamples) {
if (!isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL + numJitterBufferSamples)) {
if (_shouldOutputStarveDebug) {
qDebug() << "Starved and do not have minimum samples to start. Buffer held back.";
_shouldOutputStarveDebug = false;
}
return false;
} else if (samplesAvailable() < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) {
qDebug() << "Do not have number of samples needed for interval. Buffer starved.";
_isStarved = true;
// reset our _shouldOutputStarveDebug to true so the next is printed

View file

@ -28,7 +28,7 @@ public:
static QByteArray download(const QUrl dataURL, int timeout = 0);
signals:
void done(QNetworkReply::NetworkError);
void done(QNetworkReply::NetworkError error);
private slots:
void processReply(QNetworkReply* reply);

View file

@ -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;
}

View file

@ -27,6 +27,7 @@ private:
int _lodCount;
int _texturesCount;
int _totalSize;
bool _isHead;
bool _readyToSend;
QHttpMultiPart* _dataMultiPart;

View file

@ -56,6 +56,7 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy
void ThreadedAssignment::checkInWithDomainServerOrExit() {
if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
qDebug() << "NRDC:" << NodeList::getInstance()->getNumNoReplyDomainCheckIns();
setFinished(true);
} else {
NodeList::getInstance()->sendDomainServerCheckIn();