mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 06:58:56 +02:00
merge upstream/master into andrew/ragdoll
This commit is contained in:
commit
8359e672bb
57 changed files with 1359 additions and 536 deletions
|
@ -57,6 +57,8 @@
|
||||||
#include "AvatarAudioStream.h"
|
#include "AvatarAudioStream.h"
|
||||||
#include "InjectedAudioStream.h"
|
#include "InjectedAudioStream.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include "AudioMixer.h"
|
#include "AudioMixer.h"
|
||||||
|
|
||||||
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
|
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
|
||||||
|
@ -92,7 +94,9 @@ AudioMixer::AudioMixer(const QByteArray& packet) :
|
||||||
_timeSpentPerHashMatchCallStats(0, READ_DATAGRAMS_STATS_WINDOW_SECONDS),
|
_timeSpentPerHashMatchCallStats(0, READ_DATAGRAMS_STATS_WINDOW_SECONDS),
|
||||||
_readPendingCallsPerSecondStats(1, READ_DATAGRAMS_STATS_WINDOW_SECONDS)
|
_readPendingCallsPerSecondStats(1, READ_DATAGRAMS_STATS_WINDOW_SECONDS)
|
||||||
{
|
{
|
||||||
|
// constant defined in AudioMixer.h. However, we don't want to include this here
|
||||||
|
// we will soon find a better common home for these audio-related constants
|
||||||
|
_penumbraFilter.initialize(SAMPLE_RATE, (NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioMixer::~AudioMixer() {
|
AudioMixer::~AudioMixer() {
|
||||||
|
@ -102,7 +106,7 @@ AudioMixer::~AudioMixer() {
|
||||||
|
|
||||||
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
|
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
|
||||||
const float ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE = 0.18f;
|
const float ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE = 0.18f;
|
||||||
const float ATTENUATION_EPSILON_DISTANCE = 0.1f;
|
const float RADIUS_OF_HEAD = 0.076f;
|
||||||
|
|
||||||
int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd,
|
int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd,
|
||||||
AvatarAudioStream* listeningNodeStream) {
|
AvatarAudioStream* listeningNodeStream) {
|
||||||
|
@ -112,6 +116,8 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream*
|
||||||
// Basically, we'll repeat that last frame until it has a frame to mix. Depending on how many times
|
// Basically, we'll repeat that last frame until it has a frame to mix. Depending on how many times
|
||||||
// we've repeated that frame in a row, we'll gradually fade that repeated frame into silence.
|
// we've repeated that frame in a row, we'll gradually fade that repeated frame into silence.
|
||||||
// This improves the perceived quality of the audio slightly.
|
// This improves the perceived quality of the audio slightly.
|
||||||
|
|
||||||
|
bool showDebug = false; // (randFloat() < 0.05f);
|
||||||
|
|
||||||
float repeatedFrameFadeFactor = 1.0f;
|
float repeatedFrameFadeFactor = 1.0f;
|
||||||
|
|
||||||
|
@ -140,188 +146,221 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream*
|
||||||
int numSamplesDelay = 0;
|
int numSamplesDelay = 0;
|
||||||
float weakChannelAmplitudeRatio = 1.0f;
|
float weakChannelAmplitudeRatio = 1.0f;
|
||||||
|
|
||||||
bool shouldAttenuate = (streamToAdd != listeningNodeStream);
|
bool shouldDistanceAttenuate = true;
|
||||||
|
|
||||||
if (shouldAttenuate) {
|
// Is the source that I am mixing my own?
|
||||||
|
bool sourceIsSelf = (streamToAdd == listeningNodeStream);
|
||||||
// if the two stream pointers do not match then these are different streams
|
|
||||||
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream->getPosition();
|
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream->getPosition();
|
||||||
|
|
||||||
float distanceBetween = glm::length(relativePosition);
|
float distanceBetween = glm::length(relativePosition);
|
||||||
|
|
||||||
if (distanceBetween < EPSILON) {
|
if (distanceBetween < EPSILON) {
|
||||||
distanceBetween = EPSILON;
|
distanceBetween = EPSILON;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamToAdd->getLastPopOutputTrailingLoudness() / distanceBetween <= _minAudibilityThreshold) {
|
if (streamToAdd->getLastPopOutputTrailingLoudness() / distanceBetween <= _minAudibilityThreshold) {
|
||||||
// according to mixer performance we have decided this does not get to be mixed in
|
// according to mixer performance we have decided this does not get to be mixed in
|
||||||
// bail out
|
// bail out
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
++_sumMixes;
|
++_sumMixes;
|
||||||
|
|
||||||
if (streamToAdd->getListenerUnattenuatedZone()) {
|
if (streamToAdd->getListenerUnattenuatedZone()) {
|
||||||
shouldAttenuate = !streamToAdd->getListenerUnattenuatedZone()->contains(listeningNodeStream->getPosition());
|
shouldDistanceAttenuate = !streamToAdd->getListenerUnattenuatedZone()->contains(listeningNodeStream->getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamToAdd->getType() == PositionalAudioStream::Injector) {
|
if (streamToAdd->getType() == PositionalAudioStream::Injector) {
|
||||||
attenuationCoefficient *= reinterpret_cast<InjectedAudioStream*>(streamToAdd)->getAttenuationRatio();
|
attenuationCoefficient *= reinterpret_cast<InjectedAudioStream*>(streamToAdd)->getAttenuationRatio();
|
||||||
}
|
if (showDebug) {
|
||||||
|
qDebug() << "AttenuationRatio: " << reinterpret_cast<InjectedAudioStream*>(streamToAdd)->getAttenuationRatio();
|
||||||
shouldAttenuate = shouldAttenuate && distanceBetween > ATTENUATION_EPSILON_DISTANCE;
|
|
||||||
|
|
||||||
if (shouldAttenuate) {
|
|
||||||
glm::quat inverseOrientation = glm::inverse(listeningNodeStream->getOrientation());
|
|
||||||
|
|
||||||
float distanceSquareToSource = glm::dot(relativePosition, relativePosition);
|
|
||||||
float radius = 0.0f;
|
|
||||||
|
|
||||||
if (streamToAdd->getType() == PositionalAudioStream::Injector) {
|
|
||||||
radius = reinterpret_cast<InjectedAudioStream*>(streamToAdd)->getRadius();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (radius == 0 || (distanceSquareToSource > radius * radius)) {
|
|
||||||
// this is either not a spherical source, or the listener is outside the sphere
|
|
||||||
|
|
||||||
if (radius > 0) {
|
|
||||||
// this is a spherical source - the distance used for the coefficient
|
|
||||||
// needs to be the closest point on the boundary to the source
|
|
||||||
|
|
||||||
// ovveride the distance to the node with the distance to the point on the
|
|
||||||
// boundary of the sphere
|
|
||||||
distanceSquareToSource -= (radius * radius);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// calculate the angle delivery for off-axis attenuation
|
|
||||||
glm::vec3 rotatedListenerPosition = glm::inverse(streamToAdd->getOrientation()) * relativePosition;
|
|
||||||
|
|
||||||
float angleOfDelivery = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f),
|
|
||||||
glm::normalize(rotatedListenerPosition));
|
|
||||||
|
|
||||||
const float MAX_OFF_AXIS_ATTENUATION = 0.2f;
|
|
||||||
const float OFF_AXIS_ATTENUATION_FORMULA_STEP = (1 - MAX_OFF_AXIS_ATTENUATION) / 2.0f;
|
|
||||||
|
|
||||||
float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION +
|
|
||||||
(OFF_AXIS_ATTENUATION_FORMULA_STEP * (angleOfDelivery / PI_OVER_TWO));
|
|
||||||
|
|
||||||
// multiply the current attenuation coefficient by the calculated off axis coefficient
|
|
||||||
attenuationCoefficient *= offAxisCoefficient;
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;
|
|
||||||
|
|
||||||
if (distanceBetween >= ATTENUATION_BEGINS_AT_DISTANCE) {
|
|
||||||
// calculate the distance coefficient using the distance to this node
|
|
||||||
float distanceCoefficient = 1 - (logf(distanceBetween / ATTENUATION_BEGINS_AT_DISTANCE) / logf(2.0f)
|
|
||||||
* ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE);
|
|
||||||
|
|
||||||
if (distanceCoefficient < 0) {
|
|
||||||
distanceCoefficient = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiply the current attenuation coefficient by the distance coefficient
|
|
||||||
attenuationCoefficient *= distanceCoefficient;
|
|
||||||
}
|
|
||||||
|
|
||||||
// project the rotated source position vector onto the XZ plane
|
|
||||||
rotatedSourcePosition.y = 0.0f;
|
|
||||||
|
|
||||||
// produce an oriented angle about the y-axis
|
|
||||||
bearingRelativeAngleToSource = glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f),
|
|
||||||
glm::normalize(rotatedSourcePosition),
|
|
||||||
glm::vec3(0.0f, 1.0f, 0.0f));
|
|
||||||
|
|
||||||
const float PHASE_AMPLITUDE_RATIO_AT_90 = 0.5;
|
|
||||||
|
|
||||||
// figure out the number of samples of delay and the ratio of the amplitude
|
|
||||||
// in the weak channel for audio spatialization
|
|
||||||
float sinRatio = fabsf(sinf(bearingRelativeAngleToSource));
|
|
||||||
numSamplesDelay = SAMPLE_PHASE_DELAY_AT_90 * sinRatio;
|
|
||||||
weakChannelAmplitudeRatio = 1 - (PHASE_AMPLITUDE_RATIO_AT_90 * sinRatio);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioRingBuffer::ConstIterator streamPopOutput = streamToAdd->getLastPopOutput();
|
if (showDebug) {
|
||||||
|
qDebug() << "distance: " << distanceBetween;
|
||||||
|
}
|
||||||
|
|
||||||
if (!streamToAdd->isStereo() && shouldAttenuate) {
|
glm::quat inverseOrientation = glm::inverse(listeningNodeStream->getOrientation());
|
||||||
// this is a mono stream, which means it gets full attenuation and spatialization
|
|
||||||
|
if (!sourceIsSelf && (streamToAdd->getType() == PositionalAudioStream::Microphone)) {
|
||||||
|
// source is another avatar, apply fixed off-axis attenuation to make them quieter as they turn away from listener
|
||||||
|
glm::vec3 rotatedListenerPosition = glm::inverse(streamToAdd->getOrientation()) * relativePosition;
|
||||||
|
|
||||||
// if the bearing relative angle to source is > 0 then the delayed channel is the right one
|
float angleOfDelivery = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f),
|
||||||
int delayedChannelOffset = (bearingRelativeAngleToSource > 0.0f) ? 1 : 0;
|
glm::normalize(rotatedListenerPosition));
|
||||||
int goodChannelOffset = delayedChannelOffset == 0 ? 1 : 0;
|
|
||||||
|
|
||||||
int16_t correctStreamSample[2], delayStreamSample[2];
|
const float MAX_OFF_AXIS_ATTENUATION = 0.2f;
|
||||||
int delayedChannelIndex = 0;
|
const float OFF_AXIS_ATTENUATION_FORMULA_STEP = (1 - MAX_OFF_AXIS_ATTENUATION) / 2.0f;
|
||||||
|
|
||||||
const int SINGLE_STEREO_OFFSET = 2;
|
float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION +
|
||||||
float attenuationAndFade = attenuationCoefficient * repeatedFrameFadeFactor;
|
(OFF_AXIS_ATTENUATION_FORMULA_STEP * (angleOfDelivery / PI_OVER_TWO));
|
||||||
|
|
||||||
for (int s = 0; s < NETWORK_BUFFER_LENGTH_SAMPLES_STEREO; s += 4) {
|
if (showDebug) {
|
||||||
|
qDebug() << "angleOfDelivery" << angleOfDelivery << "offAxisCoefficient: " << offAxisCoefficient;
|
||||||
|
|
||||||
// setup the int16_t variables for the two sample sets
|
}
|
||||||
correctStreamSample[0] = streamPopOutput[s / 2] * attenuationAndFade;
|
// multiply the current attenuation coefficient by the calculated off axis coefficient
|
||||||
correctStreamSample[1] = streamPopOutput[(s / 2) + 1] * attenuationAndFade;
|
|
||||||
|
attenuationCoefficient *= offAxisCoefficient;
|
||||||
delayedChannelIndex = s + (numSamplesDelay * 2) + delayedChannelOffset;
|
}
|
||||||
|
|
||||||
delayStreamSample[0] = correctStreamSample[0] * weakChannelAmplitudeRatio;
|
if (shouldDistanceAttenuate && (distanceBetween >= ATTENUATION_BEGINS_AT_DISTANCE)) {
|
||||||
delayStreamSample[1] = correctStreamSample[1] * weakChannelAmplitudeRatio;
|
// calculate the distance coefficient using the distance to this node
|
||||||
|
float distanceCoefficient = 1 - (logf(distanceBetween / ATTENUATION_BEGINS_AT_DISTANCE) / logf(2.0f)
|
||||||
_clientSamples[s + goodChannelOffset] += correctStreamSample[0];
|
* ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE);
|
||||||
_clientSamples[s + goodChannelOffset + SINGLE_STEREO_OFFSET] += correctStreamSample[1];
|
|
||||||
_clientSamples[delayedChannelIndex] += delayStreamSample[0];
|
if (distanceCoefficient < 0) {
|
||||||
_clientSamples[delayedChannelIndex + SINGLE_STEREO_OFFSET] += delayStreamSample[1];
|
distanceCoefficient = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numSamplesDelay > 0) {
|
// multiply the current attenuation coefficient by the distance coefficient
|
||||||
// if there was a sample delay for this stream, we need to pull samples prior to the popped output
|
attenuationCoefficient *= distanceCoefficient;
|
||||||
// to stick at the beginning
|
if (showDebug) {
|
||||||
float attenuationAndWeakChannelRatioAndFade = attenuationCoefficient * weakChannelAmplitudeRatio * repeatedFrameFadeFactor;
|
qDebug() << "distanceCoefficient: " << distanceCoefficient;
|
||||||
AudioRingBuffer::ConstIterator delayStreamPopOutput = streamPopOutput - numSamplesDelay;
|
|
||||||
|
|
||||||
// TODO: delayStreamPopOutput may be inside the last frame written if the ringbuffer is completely full
|
|
||||||
// maybe make AudioRingBuffer have 1 extra frame in its buffer
|
|
||||||
|
|
||||||
for (int i = 0; i < numSamplesDelay; i++) {
|
|
||||||
int parentIndex = i * 2;
|
|
||||||
_clientSamples[parentIndex + delayedChannelOffset] += *delayStreamPopOutput * attenuationAndWeakChannelRatioAndFade;
|
|
||||||
++delayStreamPopOutput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int stereoDivider = streamToAdd->isStereo() ? 1 : 2;
|
|
||||||
|
|
||||||
if (!shouldAttenuate) {
|
|
||||||
attenuationCoefficient = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float attenuationAndFade = attenuationCoefficient * repeatedFrameFadeFactor;
|
|
||||||
|
|
||||||
for (int s = 0; s < NETWORK_BUFFER_LENGTH_SAMPLES_STEREO; s++) {
|
|
||||||
_clientSamples[s] = glm::clamp(_clientSamples[s] + (int)(streamPopOutput[s / stereoDivider] * attenuationAndFade),
|
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_enableFilter && shouldAttenuate) {
|
if (!sourceIsSelf) {
|
||||||
|
// Compute sample delay for the two ears to create phase panning
|
||||||
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream->getPosition();
|
|
||||||
glm::quat inverseOrientation = glm::inverse(listeningNodeStream->getOrientation());
|
|
||||||
glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;
|
glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;
|
||||||
|
|
||||||
// project the rotated source position vector onto the XZ plane
|
// project the rotated source position vector onto the XZ plane
|
||||||
rotatedSourcePosition.y = 0.0f;
|
rotatedSourcePosition.y = 0.0f;
|
||||||
|
|
||||||
// produce an oriented angle about the y-axis
|
// produce an oriented angle about the y-axis
|
||||||
float bearingAngleToSource = glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f),
|
bearingRelativeAngleToSource = glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f),
|
||||||
glm::normalize(rotatedSourcePosition),
|
glm::normalize(rotatedSourcePosition),
|
||||||
glm::vec3(0.0f, -1.0f, 0.0f));
|
glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
|
|
||||||
|
const float PHASE_AMPLITUDE_RATIO_AT_90 = 0.5;
|
||||||
|
|
||||||
|
// figure out the number of samples of delay and the ratio of the amplitude
|
||||||
|
// in the weak channel for audio spatialization
|
||||||
|
float sinRatio = fabsf(sinf(bearingRelativeAngleToSource));
|
||||||
|
numSamplesDelay = SAMPLE_PHASE_DELAY_AT_90 * sinRatio;
|
||||||
|
weakChannelAmplitudeRatio = 1 - (PHASE_AMPLITUDE_RATIO_AT_90 * sinRatio);
|
||||||
|
|
||||||
|
if (distanceBetween < RADIUS_OF_HEAD) {
|
||||||
|
// Diminish phase panning if source would be inside head
|
||||||
|
numSamplesDelay *= distanceBetween / RADIUS_OF_HEAD;
|
||||||
|
weakChannelAmplitudeRatio += (PHASE_AMPLITUDE_RATIO_AT_90 * sinRatio) * distanceBetween / RADIUS_OF_HEAD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDebug) {
|
||||||
|
qDebug() << "attenuation: " << attenuationCoefficient;
|
||||||
|
qDebug() << "bearingRelativeAngleToSource: " << bearingRelativeAngleToSource << " numSamplesDelay: " << numSamplesDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioRingBuffer::ConstIterator streamPopOutput = streamToAdd->getLastPopOutput();
|
||||||
|
|
||||||
|
if (!streamToAdd->isStereo()) {
|
||||||
|
// this is a mono stream, which means it gets full attenuation and spatialization
|
||||||
|
|
||||||
|
// we need to do several things in this process:
|
||||||
|
// 1) convert from mono to stereo by copying each input sample into the left and right output samples
|
||||||
|
// 2)
|
||||||
|
// 2) apply an attenuation AND fade to all samples (left and right)
|
||||||
|
// 3) based on the bearing relative angle to the source we will weaken and delay either the left or
|
||||||
|
// right channel of the input into the output
|
||||||
|
// 4) because one of these channels is delayed, we will need to use historical samples from
|
||||||
|
// the input stream for that delayed channel
|
||||||
|
|
||||||
|
// Mono input to stereo output (item 1 above)
|
||||||
|
int OUTPUT_SAMPLES_PER_INPUT_SAMPLE = 2;
|
||||||
|
int inputSampleCount = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / OUTPUT_SAMPLES_PER_INPUT_SAMPLE;
|
||||||
|
int maxOutputIndex = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO;
|
||||||
|
|
||||||
|
// attenuation and fade applied to all samples (item 2 above)
|
||||||
|
float attenuationAndFade = attenuationCoefficient * repeatedFrameFadeFactor;
|
||||||
|
|
||||||
|
// determine which side is weak and delayed (item 3 above)
|
||||||
|
bool rightSideWeakAndDelayed = (bearingRelativeAngleToSource > 0.0f);
|
||||||
|
|
||||||
|
// since we're converting from mono to stereo, we'll use these two indices to step through
|
||||||
|
// the output samples. we'll increment each index independently in the loop
|
||||||
|
int leftDestinationIndex = 0;
|
||||||
|
int rightDestinationIndex = 1;
|
||||||
|
|
||||||
|
// One of our two channels will be delayed (determined below). We'll use this index to step
|
||||||
|
// through filling in our output with the historical samples for the delayed channel. (item 4 above)
|
||||||
|
int delayedChannelHistoricalAudioOutputIndex;
|
||||||
|
|
||||||
|
// All samples will be attenuated by at least this much
|
||||||
|
float leftSideAttenuation = attenuationAndFade;
|
||||||
|
float rightSideAttenuation = attenuationAndFade;
|
||||||
|
|
||||||
|
// The weak/delayed channel will be attenuated by this additional amount
|
||||||
|
float attenuationAndWeakChannelRatioAndFade = attenuationAndFade * weakChannelAmplitudeRatio;
|
||||||
|
|
||||||
|
// Now, based on the determination of which side is weak and delayed, set up our true starting point
|
||||||
|
// for our indexes, as well as the appropriate attenuation for each channel
|
||||||
|
if (rightSideWeakAndDelayed) {
|
||||||
|
delayedChannelHistoricalAudioOutputIndex = rightDestinationIndex;
|
||||||
|
rightSideAttenuation = attenuationAndWeakChannelRatioAndFade;
|
||||||
|
rightDestinationIndex += (numSamplesDelay * OUTPUT_SAMPLES_PER_INPUT_SAMPLE);
|
||||||
|
} else {
|
||||||
|
delayedChannelHistoricalAudioOutputIndex = leftDestinationIndex;
|
||||||
|
leftSideAttenuation = attenuationAndWeakChannelRatioAndFade;
|
||||||
|
leftDestinationIndex += (numSamplesDelay * OUTPUT_SAMPLES_PER_INPUT_SAMPLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there was a sample delay for this stream, we need to pull samples prior to the official start of the input
|
||||||
|
// and stick those samples at the beginning of the output. We only need to loop through this for the weak/delayed
|
||||||
|
// side, since the normal side is fully handled below. (item 4 above)
|
||||||
|
if (numSamplesDelay > 0) {
|
||||||
|
|
||||||
|
// TODO: delayStreamSourceSamples may be inside the last frame written if the ringbuffer is completely full
|
||||||
|
// maybe make AudioRingBuffer have 1 extra frame in its buffer
|
||||||
|
AudioRingBuffer::ConstIterator delayStreamSourceSamples = streamPopOutput - numSamplesDelay;
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamplesDelay; i++) {
|
||||||
|
int16_t originalHistoricalSample = *delayStreamSourceSamples;
|
||||||
|
|
||||||
|
_preMixSamples[delayedChannelHistoricalAudioOutputIndex] += originalHistoricalSample
|
||||||
|
* attenuationAndWeakChannelRatioAndFade;
|
||||||
|
++delayStreamSourceSamples; // move our input pointer
|
||||||
|
delayedChannelHistoricalAudioOutputIndex += OUTPUT_SAMPLES_PER_INPUT_SAMPLE; // move our output sample
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here's where we copy the MONO input to the STEREO output, and account for delay and weak side attenuation
|
||||||
|
for (int inputSample = 0; inputSample < inputSampleCount; inputSample++) {
|
||||||
|
int16_t originalSample = streamPopOutput[inputSample];
|
||||||
|
int16_t leftSideSample = originalSample * leftSideAttenuation;
|
||||||
|
int16_t rightSideSample = originalSample * rightSideAttenuation;
|
||||||
|
|
||||||
|
// since we might be delayed, don't write beyond our maxOutputIndex
|
||||||
|
if (leftDestinationIndex <= maxOutputIndex) {
|
||||||
|
_preMixSamples[leftDestinationIndex] += leftSideSample;
|
||||||
|
}
|
||||||
|
if (rightDestinationIndex <= maxOutputIndex) {
|
||||||
|
_preMixSamples[rightDestinationIndex] += rightSideSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
leftDestinationIndex += OUTPUT_SAMPLES_PER_INPUT_SAMPLE;
|
||||||
|
rightDestinationIndex += OUTPUT_SAMPLES_PER_INPUT_SAMPLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
int stereoDivider = streamToAdd->isStereo() ? 1 : 2;
|
||||||
|
|
||||||
|
float attenuationAndFade = attenuationCoefficient * repeatedFrameFadeFactor;
|
||||||
|
|
||||||
|
for (int s = 0; s < NETWORK_BUFFER_LENGTH_SAMPLES_STEREO; s++) {
|
||||||
|
_preMixSamples[s] = glm::clamp(_preMixSamples[s] + (int)(streamPopOutput[s / stereoDivider] * attenuationAndFade),
|
||||||
|
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sourceIsSelf && _enableFilter) {
|
||||||
|
|
||||||
const float TWO_OVER_PI = 2.0f / PI;
|
const float TWO_OVER_PI = 2.0f / PI;
|
||||||
|
|
||||||
const float ZERO_DB = 1.0f;
|
const float ZERO_DB = 1.0f;
|
||||||
// const float NEGATIVE_ONE_DB = 0.891f;
|
//const float NEGATIVE_ONE_DB = 0.891f;
|
||||||
const float NEGATIVE_THREE_DB = 0.708f;
|
const float NEGATIVE_THREE_DB = 0.708f;
|
||||||
const float NEGATIVE_SIX_DB = 0.501f;
|
const float NEGATIVE_SIX_DB = 0.501f;
|
||||||
|
|
||||||
|
@ -337,45 +376,53 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream*
|
||||||
float penumbraFilterGainL;
|
float penumbraFilterGainL;
|
||||||
float penumbraFilterGainR;
|
float penumbraFilterGainR;
|
||||||
|
|
||||||
// variable gain calculation broken down by quadrent
|
// variable gain calculation broken down by quadrant
|
||||||
if (bearingAngleToSource < -PI_OVER_TWO && bearingAngleToSource > -PI) {
|
if (-bearingRelativeAngleToSource < -PI_OVER_TWO && -bearingRelativeAngleToSource > -PI) {
|
||||||
penumbraFilterGainL = TWO_OVER_PI *
|
penumbraFilterGainL = TWO_OVER_PI *
|
||||||
(FILTER_GAIN_AT_0 - FILTER_GAIN_AT_180) * (bearingAngleToSource + PI_OVER_TWO) + FILTER_GAIN_AT_0;
|
(FILTER_GAIN_AT_0 - FILTER_GAIN_AT_180) * (-bearingRelativeAngleToSource + PI_OVER_TWO) + FILTER_GAIN_AT_0;
|
||||||
penumbraFilterGainR = TWO_OVER_PI *
|
penumbraFilterGainR = TWO_OVER_PI *
|
||||||
(FILTER_GAIN_AT_90 - FILTER_GAIN_AT_180) * (bearingAngleToSource + PI_OVER_TWO) + FILTER_GAIN_AT_90;
|
(FILTER_GAIN_AT_90 - FILTER_GAIN_AT_180) * (-bearingRelativeAngleToSource + PI_OVER_TWO) + FILTER_GAIN_AT_90;
|
||||||
} else if (bearingAngleToSource <= PI && bearingAngleToSource > PI_OVER_TWO) {
|
} else if (-bearingRelativeAngleToSource <= PI && -bearingRelativeAngleToSource > PI_OVER_TWO) {
|
||||||
penumbraFilterGainL = TWO_OVER_PI *
|
penumbraFilterGainL = TWO_OVER_PI *
|
||||||
(FILTER_GAIN_AT_180 - FILTER_GAIN_AT_90) * (bearingAngleToSource - PI) + FILTER_GAIN_AT_180;
|
(FILTER_GAIN_AT_180 - FILTER_GAIN_AT_90) * (-bearingRelativeAngleToSource - PI) + FILTER_GAIN_AT_180;
|
||||||
penumbraFilterGainR = TWO_OVER_PI *
|
penumbraFilterGainR = TWO_OVER_PI *
|
||||||
(FILTER_GAIN_AT_180 - FILTER_GAIN_AT_0) * (bearingAngleToSource - PI) + FILTER_GAIN_AT_180;
|
(FILTER_GAIN_AT_180 - FILTER_GAIN_AT_0) * (-bearingRelativeAngleToSource - PI) + FILTER_GAIN_AT_180;
|
||||||
} else if (bearingAngleToSource <= PI_OVER_TWO && bearingAngleToSource > 0) {
|
} else if (-bearingRelativeAngleToSource <= PI_OVER_TWO && -bearingRelativeAngleToSource > 0) {
|
||||||
penumbraFilterGainL = TWO_OVER_PI *
|
penumbraFilterGainL = TWO_OVER_PI *
|
||||||
(FILTER_GAIN_AT_90 - FILTER_GAIN_AT_0) * (bearingAngleToSource - PI_OVER_TWO) + FILTER_GAIN_AT_90;
|
(FILTER_GAIN_AT_90 - FILTER_GAIN_AT_0) * (-bearingRelativeAngleToSource - PI_OVER_TWO) + FILTER_GAIN_AT_90;
|
||||||
penumbraFilterGainR = FILTER_GAIN_AT_0;
|
penumbraFilterGainR = FILTER_GAIN_AT_0;
|
||||||
} else {
|
} else {
|
||||||
penumbraFilterGainL = FILTER_GAIN_AT_0;
|
penumbraFilterGainL = FILTER_GAIN_AT_0;
|
||||||
penumbraFilterGainR = TWO_OVER_PI *
|
penumbraFilterGainR = TWO_OVER_PI *
|
||||||
(FILTER_GAIN_AT_0 - FILTER_GAIN_AT_90) * (bearingAngleToSource) + FILTER_GAIN_AT_0;
|
(FILTER_GAIN_AT_0 - FILTER_GAIN_AT_90) * (-bearingRelativeAngleToSource) + FILTER_GAIN_AT_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distanceBetween < RADIUS_OF_HEAD) {
|
||||||
|
// Diminish effect if source would be inside head
|
||||||
|
penumbraFilterGainL += (1.f - penumbraFilterGainL) * (1.f - distanceBetween / RADIUS_OF_HEAD);
|
||||||
|
penumbraFilterGainR += (1.f - penumbraFilterGainR) * (1.f - distanceBetween / RADIUS_OF_HEAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
qDebug() << "avatar="
|
qDebug() << "gainL="
|
||||||
<< listeningNodeStream
|
|
||||||
<< "gainL="
|
|
||||||
<< penumbraFilterGainL
|
<< penumbraFilterGainL
|
||||||
<< "gainR="
|
<< "gainR="
|
||||||
<< penumbraFilterGainR
|
<< penumbraFilterGainR
|
||||||
<< "angle="
|
<< "angle="
|
||||||
<< bearingAngleToSource;
|
<< -bearingRelativeAngleToSource;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// set the gain on both filter channels
|
// set the gain on both filter channels
|
||||||
AudioFilterHSF1s& penumbraFilter = streamToAdd->getFilter();
|
_penumbraFilter.reset();
|
||||||
|
_penumbraFilter.setParameters(0, 0, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainL, penumbraFilterSlope);
|
||||||
penumbraFilter.setParameters(0, 0, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainL, penumbraFilterSlope);
|
_penumbraFilter.setParameters(0, 1, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainR, penumbraFilterSlope);
|
||||||
penumbraFilter.setParameters(0, 1, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainR, penumbraFilterSlope);
|
_penumbraFilter.render(_preMixSamples, _preMixSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2);
|
||||||
|
}
|
||||||
penumbraFilter.render(_clientSamples, _clientSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2);
|
|
||||||
|
// Actually mix the _preMixSamples into the _mixSamples here.
|
||||||
|
for (int s = 0; s < NETWORK_BUFFER_LENGTH_SAMPLES_STEREO; s++) {
|
||||||
|
_mixSamples[s] = glm::clamp(_mixSamples[s] + _preMixSamples[s], MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -385,7 +432,8 @@ int AudioMixer::prepareMixForListeningNode(Node* node) {
|
||||||
AvatarAudioStream* nodeAudioStream = ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioStream();
|
AvatarAudioStream* nodeAudioStream = ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioStream();
|
||||||
|
|
||||||
// zero out the client mix for this node
|
// zero out the client mix for this node
|
||||||
memset(_clientSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
|
memset(_preMixSamples, 0, sizeof(_preMixSamples));
|
||||||
|
memset(_mixSamples, 0, sizeof(_mixSamples));
|
||||||
|
|
||||||
// loop through all other nodes that have sufficient audio to mix
|
// loop through all other nodes that have sufficient audio to mix
|
||||||
int streamsMixed = 0;
|
int streamsMixed = 0;
|
||||||
|
@ -778,7 +826,7 @@ void AudioMixer::run() {
|
||||||
dataAt += sizeof(quint16);
|
dataAt += sizeof(quint16);
|
||||||
|
|
||||||
// pack mixed audio samples
|
// pack mixed audio samples
|
||||||
memcpy(dataAt, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
|
memcpy(dataAt, _mixSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
|
||||||
dataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO;
|
dataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO;
|
||||||
} else {
|
} else {
|
||||||
// pack header
|
// pack header
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
#define hifi_AudioMixer_h
|
#define hifi_AudioMixer_h
|
||||||
|
|
||||||
#include <AABox.h>
|
#include <AABox.h>
|
||||||
|
#include <AudioFormat.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||||
|
#include <AudioBuffer.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||||
|
#include <AudioFilter.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||||
|
#include <AudioFilterBank.h> // For AudioFilterHSF1s and _penumbraFilter
|
||||||
#include <AudioRingBuffer.h>
|
#include <AudioRingBuffer.h>
|
||||||
#include <ThreadedAssignment.h>
|
#include <ThreadedAssignment.h>
|
||||||
|
|
||||||
|
@ -23,7 +27,6 @@ const int SAMPLE_PHASE_DELAY_AT_90 = 20;
|
||||||
|
|
||||||
const int READ_DATAGRAMS_STATS_WINDOW_SECONDS = 30;
|
const int READ_DATAGRAMS_STATS_WINDOW_SECONDS = 30;
|
||||||
|
|
||||||
|
|
||||||
/// Handles assignments of type AudioMixer - mixing streams of audio and re-distributing to various clients.
|
/// Handles assignments of type AudioMixer - mixing streams of audio and re-distributing to various clients.
|
||||||
class AudioMixer : public ThreadedAssignment {
|
class AudioMixer : public ThreadedAssignment {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -48,11 +51,17 @@ private:
|
||||||
|
|
||||||
/// prepares and sends a mix to one Node
|
/// prepares and sends a mix to one Node
|
||||||
int prepareMixForListeningNode(Node* node);
|
int prepareMixForListeningNode(Node* node);
|
||||||
|
|
||||||
|
// used on a per stream basis to run the filter on before mixing, large enough to handle the historical
|
||||||
|
// data from a phase delay as well as an entire network buffer
|
||||||
|
int16_t _preMixSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
|
||||||
|
|
||||||
// client samples capacity is larger than what will be sent to optimize mixing
|
// client samples capacity is larger than what will be sent to optimize mixing
|
||||||
// we are MMX adding 4 samples at a time so we need client samples to have an extra 4
|
// 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)];
|
int16_t _mixSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
|
||||||
|
|
||||||
|
AudioFilterHSF1s _penumbraFilter;
|
||||||
|
|
||||||
void perSecondActions();
|
void perSecondActions();
|
||||||
|
|
||||||
QString getReadPendingDatagramsCallsPerSecondsStatsString() const;
|
QString getReadPendingDatagramsCallsPerSecondsStatsString() const;
|
||||||
|
|
|
@ -19,28 +19,38 @@ AvatarAudioStream::AvatarAudioStream(bool isStereo, const InboundAudioStream::Se
|
||||||
}
|
}
|
||||||
|
|
||||||
int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) {
|
int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) {
|
||||||
|
|
||||||
_shouldLoopbackForNode = (type == PacketTypeMicrophoneAudioWithEcho);
|
|
||||||
|
|
||||||
int readBytes = 0;
|
int readBytes = 0;
|
||||||
|
|
||||||
// read the channel flag
|
if (type == PacketTypeSilentAudioFrame) {
|
||||||
quint8 channelFlag = packetAfterSeqNum.at(readBytes);
|
const char* dataAt = packetAfterSeqNum.constData();
|
||||||
bool isStereo = channelFlag == 1;
|
quint16 numSilentSamples = *(reinterpret_cast<const quint16*>(dataAt));
|
||||||
readBytes += sizeof(quint8);
|
readBytes += sizeof(quint16);
|
||||||
|
numAudioSamples = (int)numSilentSamples;
|
||||||
|
|
||||||
// if isStereo value has changed, restart the ring buffer with new frame size
|
// read the positional data
|
||||||
if (isStereo != _isStereo) {
|
readBytes += parsePositionalData(packetAfterSeqNum.mid(readBytes));
|
||||||
_ringBuffer.resizeForFrameSize(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
|
||||||
_isStereo = isStereo;
|
} else {
|
||||||
|
_shouldLoopbackForNode = (type == PacketTypeMicrophoneAudioWithEcho);
|
||||||
|
|
||||||
|
// read the channel flag
|
||||||
|
quint8 channelFlag = packetAfterSeqNum.at(readBytes);
|
||||||
|
bool isStereo = channelFlag == 1;
|
||||||
|
readBytes += sizeof(quint8);
|
||||||
|
|
||||||
|
// if isStereo value has changed, restart the ring buffer with new frame size
|
||||||
|
if (isStereo != _isStereo) {
|
||||||
|
_ringBuffer.resizeForFrameSize(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||||
|
_isStereo = isStereo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the positional data
|
||||||
|
readBytes += parsePositionalData(packetAfterSeqNum.mid(readBytes));
|
||||||
|
|
||||||
|
// calculate how many samples are in this packet
|
||||||
|
int numAudioBytes = packetAfterSeqNum.size() - readBytes;
|
||||||
|
numAudioSamples = numAudioBytes / sizeof(int16_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the positional data
|
|
||||||
readBytes += parsePositionalData(packetAfterSeqNum.mid(readBytes));
|
|
||||||
|
|
||||||
// calculate how many samples are in this packet
|
|
||||||
int numAudioBytes = packetAfterSeqNum.size() - readBytes;
|
|
||||||
numAudioSamples = numAudioBytes / sizeof(int16_t);
|
|
||||||
|
|
||||||
return readBytes;
|
return readBytes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<label for="<%- setting_id %>" class="col-sm-2 control-label"><%- setting.label %></label>
|
<label for="<%- setting_id %>" class="col-sm-2 control-label"><%- setting.label %></label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<% if (setting.type === "checkbox") { %>
|
<% if (setting.type === "checkbox") { %>
|
||||||
<% var checked_box = (values[group_key] || {})[setting_key] || setting.default %>
|
<% var checked_box = _.has(values, group_key) ? values[group_key][setting_key] : setting.default %>
|
||||||
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
|
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<% if (setting.input_addon) { %>
|
<% if (setting.input_addon) { %>
|
||||||
|
|
|
@ -72,6 +72,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
setupNodeListAndAssignments();
|
setupNodeListAndAssignments();
|
||||||
|
|
||||||
loadExistingSessionsFromSettings();
|
loadExistingSessionsFromSettings();
|
||||||
|
|
||||||
|
// check if we have the flag that enables dynamic IP
|
||||||
|
setupDynamicIPAddressUpdating();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +165,8 @@ bool DomainServer::optionallySetupOAuth() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString DOMAIN_CONFIG_ID_KEY = "id";
|
||||||
|
|
||||||
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||||
|
|
||||||
const QString CUSTOM_PORT_OPTION = "port";
|
const QString CUSTOM_PORT_OPTION = "port";
|
||||||
|
@ -190,8 +195,6 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||||
|
|
||||||
LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort);
|
LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort);
|
||||||
|
|
||||||
const QString DOMAIN_CONFIG_ID_KEY = "id";
|
|
||||||
|
|
||||||
// set our LimitedNodeList UUID to match the UUID from our config
|
// set our LimitedNodeList UUID to match the UUID from our config
|
||||||
// nodes will currently use this to add resources to data-web that relate to our domain
|
// nodes will currently use this to add resources to data-web that relate to our domain
|
||||||
nodeList->setSessionUUID(_argumentVariantMap.value(DOMAIN_CONFIG_ID_KEY).toString());
|
nodeList->setSessionUUID(_argumentVariantMap.value(DOMAIN_CONFIG_ID_KEY).toString());
|
||||||
|
@ -209,27 +212,29 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||||
addStaticAssignmentsToQueue();
|
addStaticAssignmentsToQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainServer::optionallySetupAssignmentPayment() {
|
const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME";
|
||||||
// check if we have a username and password set via env
|
const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD";
|
||||||
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
|
|
||||||
const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME";
|
|
||||||
const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD";
|
|
||||||
|
|
||||||
if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
|
|
||||||
_argumentVariantMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool()) {
|
|
||||||
if (!_oauthProviderURL.isEmpty()) {
|
|
||||||
|
|
||||||
|
bool DomainServer::hasOAuthProviderAndAuthInformation() {
|
||||||
|
|
||||||
|
if (!_oauthProviderURL.isEmpty()) {
|
||||||
|
|
||||||
|
static bool hasAttemptedAuthWithOAuthProvider = false;
|
||||||
|
|
||||||
|
if (!hasAttemptedAuthWithOAuthProvider) {
|
||||||
AccountManager& accountManager = AccountManager::getInstance();
|
AccountManager& accountManager = AccountManager::getInstance();
|
||||||
accountManager.setAuthURL(_oauthProviderURL);
|
accountManager.setAuthURL(_oauthProviderURL);
|
||||||
|
|
||||||
if (!accountManager.hasValidAccessToken()) {
|
if (!accountManager.hasValidAccessToken()) {
|
||||||
// we don't have a valid access token so we need to get one
|
// we don't have a valid access token so we need to get one
|
||||||
|
// check if we have a username and password set via env
|
||||||
QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY);
|
QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY);
|
||||||
QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY);
|
QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY);
|
||||||
|
|
||||||
if (!username.isEmpty() && !password.isEmpty()) {
|
if (!username.isEmpty() && !password.isEmpty()) {
|
||||||
|
|
||||||
accountManager.requestAccessToken(username, password);
|
accountManager.requestAccessToken(username, password);
|
||||||
|
|
||||||
// connect to loginFailed signal from AccountManager so we can quit if that is the case
|
// connect to loginFailed signal from AccountManager so we can quit if that is the case
|
||||||
connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed);
|
connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed);
|
||||||
} else {
|
} else {
|
||||||
|
@ -238,34 +243,92 @@ bool DomainServer::optionallySetupAssignmentPayment() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
|
hasAttemptedAuthWithOAuthProvider = true;
|
||||||
|
|
||||||
// assume that the fact we are authing against HF data server means we will pay for assignments
|
|
||||||
// setup a timer to send transactions to pay assigned nodes every 30 seconds
|
|
||||||
QTimer* creditSetupTimer = new QTimer(this);
|
|
||||||
connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits);
|
|
||||||
|
|
||||||
const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000;
|
|
||||||
creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS);
|
|
||||||
|
|
||||||
QTimer* nodePaymentTimer = new QTimer(this);
|
|
||||||
connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer);
|
|
||||||
|
|
||||||
const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000;
|
|
||||||
nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
qDebug() << "Missing OAuth provider URL, but assigned node payment was enabled. domain-server will now quit.";
|
|
||||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qDebug() << "Missing OAuth provider URL, but a domain-server feature was required that requires authentication." <<
|
||||||
|
"domain-server will now quit.";
|
||||||
|
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DomainServer::optionallySetupAssignmentPayment() {
|
||||||
|
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
|
||||||
|
|
||||||
|
if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
|
||||||
|
_argumentVariantMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
|
||||||
|
hasOAuthProviderAndAuthInformation()) {
|
||||||
|
|
||||||
|
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
|
||||||
|
|
||||||
|
// assume that the fact we are authing against HF data server means we will pay for assignments
|
||||||
|
// setup a timer to send transactions to pay assigned nodes every 30 seconds
|
||||||
|
QTimer* creditSetupTimer = new QTimer(this);
|
||||||
|
connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits);
|
||||||
|
|
||||||
|
const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000;
|
||||||
|
creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS);
|
||||||
|
|
||||||
|
QTimer* nodePaymentTimer = new QTimer(this);
|
||||||
|
connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer);
|
||||||
|
|
||||||
|
const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000;
|
||||||
|
nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::setupDynamicIPAddressUpdating() {
|
||||||
|
const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip";
|
||||||
|
|
||||||
|
if (_argumentVariantMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
|
||||||
|
_argumentVariantMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
|
||||||
|
hasOAuthProviderAndAuthInformation()) {
|
||||||
|
|
||||||
|
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
||||||
|
const QUuid& domainID = nodeList->getSessionUUID();
|
||||||
|
|
||||||
|
if (!domainID.isNull()) {
|
||||||
|
qDebug() << "domain-server IP address will be updated for domain with ID"
|
||||||
|
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||||
|
|
||||||
|
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
|
||||||
|
|
||||||
|
// setup our timer to check our IP via stun every 30 seconds
|
||||||
|
QTimer* dynamicIPTimer = new QTimer(this);
|
||||||
|
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentIPAddressViaSTUN);
|
||||||
|
dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
|
||||||
|
|
||||||
|
// send public socket changes to the data server so nodes can find us at our new IP
|
||||||
|
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewPublicSocketToDataServer);
|
||||||
|
|
||||||
|
if (!AccountManager::getInstance().hasValidAccessToken()) {
|
||||||
|
// we don't have an access token to talk to data-web yet, so
|
||||||
|
// check our IP address as soon as we get an AccountManager access token
|
||||||
|
connect(&AccountManager::getInstance(), &AccountManager::loginComplete,
|
||||||
|
this, &DomainServer::requestCurrentIPAddressViaSTUN);
|
||||||
|
} else {
|
||||||
|
// access token good to go, attempt to update our IP now
|
||||||
|
requestCurrentIPAddressViaSTUN();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID."
|
||||||
|
<< "Please add an id to your config.json or pass it with the command line argument --id.";
|
||||||
|
qDebug() << "Failed dynamic IP address update setup. domain-server will now quit.";
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::loginFailed() {
|
void DomainServer::loginFailed() {
|
||||||
qDebug() << "Login to data server has failed. domain-server will now quit";
|
qDebug() << "Login to data server has failed. domain-server will now quit";
|
||||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||||
|
@ -836,37 +899,68 @@ void DomainServer::transactionJSONCallback(const QJsonObject& data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::requestCurrentIPAddressViaSTUN() {
|
||||||
|
LimitedNodeList::getInstance()->sendSTUNRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServer::sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr) {
|
||||||
|
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||||
|
const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID();
|
||||||
|
|
||||||
|
// setup the domain object to send to the data server
|
||||||
|
const QString DOMAIN_JSON_OBJECT = "{\"domain\":{\"network_address\":\"%1\"}}";
|
||||||
|
|
||||||
|
QString domainUpdateJSON = DOMAIN_JSON_OBJECT.arg(newPublicSockAddr.getAddress().toString());
|
||||||
|
|
||||||
|
AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
||||||
|
QNetworkAccessManager::PutOperation,
|
||||||
|
JSONCallbackParameters(),
|
||||||
|
domainUpdateJSON.toUtf8());
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
|
void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
|
||||||
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
||||||
|
|
||||||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||||
PacketType requestType = packetTypeForPacket(receivedPacket);
|
PacketType requestType = packetTypeForPacket(receivedPacket);
|
||||||
|
switch (requestType) {
|
||||||
if (requestType == PacketTypeDomainConnectRequest) {
|
case PacketTypeDomainConnectRequest:
|
||||||
handleConnectRequest(receivedPacket, senderSockAddr);
|
handleConnectRequest(receivedPacket, senderSockAddr);
|
||||||
} else if (requestType == PacketTypeDomainListRequest) {
|
break;
|
||||||
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
|
case PacketTypeDomainListRequest: {
|
||||||
|
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
|
||||||
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
|
|
||||||
NodeType_t throwawayNodeType;
|
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
|
||||||
HifiSockAddr nodePublicAddress, nodeLocalAddress;
|
NodeType_t throwawayNodeType;
|
||||||
|
HifiSockAddr nodePublicAddress, nodeLocalAddress;
|
||||||
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
|
|
||||||
receivedPacket, senderSockAddr);
|
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
|
||||||
|
receivedPacket, senderSockAddr);
|
||||||
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
|
|
||||||
|
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
|
||||||
// update last receive to now
|
|
||||||
quint64 timeNow = usecTimestampNow();
|
// update last receive to now
|
||||||
checkInNode->setLastHeardMicrostamp(timeNow);
|
quint64 timeNow = usecTimestampNow();
|
||||||
|
checkInNode->setLastHeardMicrostamp(timeNow);
|
||||||
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes));
|
|
||||||
|
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (requestType == PacketTypeNodeJsonStats) {
|
case PacketTypeNodeJsonStats: {
|
||||||
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
|
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||||
if (matchingNode) {
|
if (matchingNode) {
|
||||||
reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket);
|
reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case PacketTypeStunResponse:
|
||||||
|
nodeList->processSTUNResponse(receivedPacket);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,11 +58,16 @@ private slots:
|
||||||
void readAvailableDatagrams();
|
void readAvailableDatagrams();
|
||||||
void setupPendingAssignmentCredits();
|
void setupPendingAssignmentCredits();
|
||||||
void sendPendingTransactionsToServer();
|
void sendPendingTransactionsToServer();
|
||||||
|
|
||||||
|
void requestCurrentIPAddressViaSTUN();
|
||||||
|
void sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr);
|
||||||
private:
|
private:
|
||||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||||
bool optionallySetupOAuth();
|
bool optionallySetupOAuth();
|
||||||
bool optionallyReadX509KeyAndCertificate();
|
bool optionallyReadX509KeyAndCertificate();
|
||||||
|
bool hasOAuthProviderAndAuthInformation();
|
||||||
bool optionallySetupAssignmentPayment();
|
bool optionallySetupAssignmentPayment();
|
||||||
|
void setupDynamicIPAddressUpdating();
|
||||||
|
|
||||||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||||
|
|
||||||
|
|
395
examples/leapHands.js
Normal file
395
examples/leapHands.js
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
//
|
||||||
|
// leapHands.js
|
||||||
|
// examples
|
||||||
|
//
|
||||||
|
// Created by David Rowe on 8 Sep 2014.
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// This is an example script that uses the Leap Motion to make the avatar's hands replicate the user's hand actions.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
var leapHands = (function () {
|
||||||
|
|
||||||
|
var hands,
|
||||||
|
wrists,
|
||||||
|
NUM_HANDS = 2, // 0 = left; 1 = right
|
||||||
|
fingers,
|
||||||
|
NUM_FINGERS = 5, // 0 = thumb; ...; 4 = pinky
|
||||||
|
THUMB = 0,
|
||||||
|
NUM_FINGER_JOINTS = 3, // 0 = metacarpal(hand)-proximal(finger) joint; ...; 2 = intermediate-distal(tip) joint
|
||||||
|
MAX_HAND_INACTIVE_COUNT = 20,
|
||||||
|
calibrationStatus,
|
||||||
|
UNCALIBRATED = 0,
|
||||||
|
CALIBRATING = 1,
|
||||||
|
CALIBRATED = 2,
|
||||||
|
CALIBRATION_TIME = 1000, // milliseconds
|
||||||
|
PI = 3.141593,
|
||||||
|
isWindows;
|
||||||
|
|
||||||
|
function printSkeletonJointNames() {
|
||||||
|
var jointNames,
|
||||||
|
i;
|
||||||
|
|
||||||
|
print(MyAvatar.skeletonModelURL);
|
||||||
|
|
||||||
|
print("Skeleton joint names ...");
|
||||||
|
jointNames = MyAvatar.getJointNames();
|
||||||
|
for (i = 0; i < jointNames.length; i += 1) {
|
||||||
|
print(i + ": " + jointNames[i]);
|
||||||
|
}
|
||||||
|
print("... skeleton joint names");
|
||||||
|
|
||||||
|
/*
|
||||||
|
http://public.highfidelity.io/models/skeletons/ron_standing.fst
|
||||||
|
Skeleton joint names ...
|
||||||
|
0: Hips
|
||||||
|
1: RightUpLeg
|
||||||
|
2: RightLeg
|
||||||
|
3: RightFoot
|
||||||
|
4: RightToeBase
|
||||||
|
5: RightToe_End
|
||||||
|
6: LeftUpLeg
|
||||||
|
7: LeftLeg
|
||||||
|
8: LeftFoot
|
||||||
|
9: LeftToeBase
|
||||||
|
10: LeftToe_End
|
||||||
|
11: Spine
|
||||||
|
12: Spine1
|
||||||
|
13: Spine2
|
||||||
|
14: RightShoulder
|
||||||
|
15: RightArm
|
||||||
|
16: RightForeArm
|
||||||
|
17: RightHand
|
||||||
|
18: RightHandPinky1
|
||||||
|
19: RightHandPinky2
|
||||||
|
20: RightHandPinky3
|
||||||
|
21: RightHandPinky4
|
||||||
|
22: RightHandRing1
|
||||||
|
23: RightHandRing2
|
||||||
|
24: RightHandRing3
|
||||||
|
25: RightHandRing4
|
||||||
|
26: RightHandMiddle1
|
||||||
|
27: RightHandMiddle2
|
||||||
|
28: RightHandMiddle3
|
||||||
|
29: RightHandMiddle4
|
||||||
|
30: RightHandIndex1
|
||||||
|
31: RightHandIndex2
|
||||||
|
32: RightHandIndex3
|
||||||
|
33: RightHandIndex4
|
||||||
|
34: RightHandThumb1
|
||||||
|
35: RightHandThumb2
|
||||||
|
36: RightHandThumb3
|
||||||
|
37: RightHandThumb4
|
||||||
|
38: LeftShoulder
|
||||||
|
39: LeftArm
|
||||||
|
40: LeftForeArm
|
||||||
|
41: LeftHand
|
||||||
|
42: LeftHandPinky1
|
||||||
|
43: LeftHandPinky2
|
||||||
|
44: LeftHandPinky3
|
||||||
|
45: LeftHandPinky4
|
||||||
|
46: LeftHandRing1
|
||||||
|
47: LeftHandRing2
|
||||||
|
48: LeftHandRing3
|
||||||
|
49: LeftHandRing4
|
||||||
|
50: LeftHandMiddle1
|
||||||
|
51: LeftHandMiddle2
|
||||||
|
52: LeftHandMiddle3
|
||||||
|
53: LeftHandMiddle4
|
||||||
|
54: LeftHandIndex1
|
||||||
|
55: LeftHandIndex2
|
||||||
|
56: LeftHandIndex3
|
||||||
|
57: LeftHandIndex4
|
||||||
|
58: LeftHandThumb1
|
||||||
|
59: LeftHandThumb2
|
||||||
|
60: LeftHandThumb3
|
||||||
|
61: LeftHandThumb4
|
||||||
|
62: Neck
|
||||||
|
63: Head
|
||||||
|
64: HeadTop_End
|
||||||
|
65: body
|
||||||
|
... skeleton joint names
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishCalibration() {
|
||||||
|
var avatarPosition,
|
||||||
|
avatarOrientation,
|
||||||
|
avatarHandPosition,
|
||||||
|
leapHandHeight,
|
||||||
|
h;
|
||||||
|
|
||||||
|
if (hands[0].controller.isActive() && hands[1].controller.isActive()) {
|
||||||
|
leapHandHeight = (hands[0].controller.getAbsTranslation().y + hands[1].controller.getAbsTranslation().y) / 2.0;
|
||||||
|
|
||||||
|
// TODO: Temporary detection of Windows to work around Leap Controller problem.
|
||||||
|
isWindows = (hands[1].controller.getAbsRotation().z > (0.25 * PI));
|
||||||
|
} else {
|
||||||
|
calibrationStatus = UNCALIBRATED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarPosition = MyAvatar.position;
|
||||||
|
avatarOrientation = MyAvatar.orientation;
|
||||||
|
|
||||||
|
for (h = 0; h < NUM_HANDS; h += 1) {
|
||||||
|
avatarHandPosition = MyAvatar.getJointPosition(hands[h].jointName);
|
||||||
|
hands[h].zeroPosition = {
|
||||||
|
x: avatarHandPosition.x - avatarPosition.x,
|
||||||
|
y: avatarHandPosition.y - avatarPosition.y,
|
||||||
|
z: avatarPosition.z - avatarHandPosition.z
|
||||||
|
};
|
||||||
|
hands[h].zeroPosition = Vec3.multiplyQbyV(MyAvatar.orientation, hands[h].zeroPosition);
|
||||||
|
hands[h].zeroPosition.y = hands[h].zeroPosition.y - leapHandHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyAvatar.clearJointData("LeftHand");
|
||||||
|
MyAvatar.clearJointData("LeftForeArm");
|
||||||
|
MyAvatar.clearJointData("LeftArm");
|
||||||
|
MyAvatar.clearJointData("RightHand");
|
||||||
|
MyAvatar.clearJointData("RightForeArm");
|
||||||
|
MyAvatar.clearJointData("RightArm");
|
||||||
|
|
||||||
|
calibrationStatus = CALIBRATED;
|
||||||
|
print("Leap Motion: Calibrated");
|
||||||
|
}
|
||||||
|
|
||||||
|
function calibrate() {
|
||||||
|
|
||||||
|
calibrationStatus = CALIBRATING;
|
||||||
|
|
||||||
|
// Set avatar arms vertical, forearms horizontal, as "zero" position for calibration
|
||||||
|
MyAvatar.setJointData("LeftArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, -90.0));
|
||||||
|
MyAvatar.setJointData("LeftForeArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 180.0));
|
||||||
|
MyAvatar.setJointData("LeftHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0));
|
||||||
|
MyAvatar.setJointData("RightArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 90.0));
|
||||||
|
MyAvatar.setJointData("RightForeArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 180.0));
|
||||||
|
MyAvatar.setJointData("RightHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
// Wait for arms to assume their positions before calculating
|
||||||
|
Script.setTimeout(finishCalibration, CALIBRATION_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkCalibration() {
|
||||||
|
|
||||||
|
if (calibrationStatus === CALIBRATED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calibrationStatus !== CALIBRATING) {
|
||||||
|
calibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUp() {
|
||||||
|
|
||||||
|
calibrationStatus = UNCALIBRATED;
|
||||||
|
|
||||||
|
// TODO: Leap Motion controller joint naming doesn't match up with skeleton joint naming; numbers are out by 1.
|
||||||
|
|
||||||
|
hands = [
|
||||||
|
{
|
||||||
|
jointName: "LeftHand",
|
||||||
|
controller: Controller.createInputController("Spatial", "joint_L_hand"),
|
||||||
|
inactiveCount: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jointName: "RightHand",
|
||||||
|
controller: Controller.createInputController("Spatial", "joint_R_hand"),
|
||||||
|
inactiveCount: 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
wrists = [
|
||||||
|
{ controller: Controller.createInputController("Spatial", "joint_L_wrist") },
|
||||||
|
{ controller: Controller.createInputController("Spatial", "joint_R_wrist") }
|
||||||
|
];
|
||||||
|
|
||||||
|
fingers = [{}, {}];
|
||||||
|
fingers[0] = [
|
||||||
|
[
|
||||||
|
{ jointName: "LeftHandThumb1", controller: Controller.createInputController("Spatial", "joint_L_thumb2") },
|
||||||
|
{ jointName: "LeftHandThumb2", controller: Controller.createInputController("Spatial", "joint_L_thumb3") },
|
||||||
|
{ jointName: "LeftHandThumb3", controller: Controller.createInputController("Spatial", "joint_L_thumb4") }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ jointName: "LeftHandIndex1", controller: Controller.createInputController("Spatial", "joint_L_index2") },
|
||||||
|
{ jointName: "LeftHandIndex2", controller: Controller.createInputController("Spatial", "joint_L_index3") },
|
||||||
|
{ jointName: "LeftHandIndex3", controller: Controller.createInputController("Spatial", "joint_L_index4") }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ jointName: "LeftHandMiddle1", controller: Controller.createInputController("Spatial", "joint_L_middle2") },
|
||||||
|
{ jointName: "LeftHandMiddle2", controller: Controller.createInputController("Spatial", "joint_L_middle3") },
|
||||||
|
{ jointName: "LeftHandMiddle3", controller: Controller.createInputController("Spatial", "joint_L_middle4") }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ jointName: "LeftHandRing1", controller: Controller.createInputController("Spatial", "joint_L_ring2") },
|
||||||
|
{ jointName: "LeftHandRing2", controller: Controller.createInputController("Spatial", "joint_L_ring3") },
|
||||||
|
{ jointName: "LeftHandRing3", controller: Controller.createInputController("Spatial", "joint_L_ring4") }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ jointName: "LeftHandPinky1", controller: Controller.createInputController("Spatial", "joint_L_pinky2") },
|
||||||
|
{ jointName: "LeftHandPinky2", controller: Controller.createInputController("Spatial", "joint_L_pinky3") },
|
||||||
|
{ jointName: "LeftHandPinky3", controller: Controller.createInputController("Spatial", "joint_L_pinky4") }
|
||||||
|
]
|
||||||
|
];
|
||||||
|
fingers[1] = [
|
||||||
|
[
|
||||||
|
{ jointName: "RightHandThumb1", controller: Controller.createInputController("Spatial", "joint_R_thumb2") },
|
||||||
|
{ jointName: "RightHandThumb2", controller: Controller.createInputController("Spatial", "joint_R_thumb3") },
|
||||||
|
{ jointName: "RightHandThumb3", controller: Controller.createInputController("Spatial", "joint_R_thumb4") }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ jointName: "RightHandIndex1", controller: Controller.createInputController("Spatial", "joint_R_index2") },
|
||||||
|
{ jointName: "RightHandIndex2", controller: Controller.createInputController("Spatial", "joint_R_index3") },
|
||||||
|
{ jointName: "RightHandIndex3", controller: Controller.createInputController("Spatial", "joint_R_index4") }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ jointName: "RightHandMiddle1", controller: Controller.createInputController("Spatial", "joint_R_middle2") },
|
||||||
|
{ jointName: "RightHandMiddle2", controller: Controller.createInputController("Spatial", "joint_R_middle3") },
|
||||||
|
{ jointName: "RightHandMiddle3", controller: Controller.createInputController("Spatial", "joint_R_middle4") }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ jointName: "RightHandRing1", controller: Controller.createInputController("Spatial", "joint_R_ring2") },
|
||||||
|
{ jointName: "RightHandRing2", controller: Controller.createInputController("Spatial", "joint_R_ring3") },
|
||||||
|
{ jointName: "RightHandRing3", controller: Controller.createInputController("Spatial", "joint_R_ring4") }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ jointName: "RightHandPinky1", controller: Controller.createInputController("Spatial", "joint_R_pinky2") },
|
||||||
|
{ jointName: "RightHandPinky2", controller: Controller.createInputController("Spatial", "joint_R_pinky3") },
|
||||||
|
{ jointName: "RightHandPinky3", controller: Controller.createInputController("Spatial", "joint_R_pinky4") }
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveHands() {
|
||||||
|
var h,
|
||||||
|
i,
|
||||||
|
j,
|
||||||
|
side,
|
||||||
|
handOffset,
|
||||||
|
handRoll,
|
||||||
|
handPitch,
|
||||||
|
handYaw,
|
||||||
|
handRotation,
|
||||||
|
wristAbsRotation,
|
||||||
|
locRotation;
|
||||||
|
|
||||||
|
for (h = 0; h < NUM_HANDS; h += 1) {
|
||||||
|
side = h === 0 ? -1.0 : 1.0;
|
||||||
|
|
||||||
|
if (hands[h].controller.isActive()) {
|
||||||
|
|
||||||
|
// Calibrate when and if a controller is first active.
|
||||||
|
if (!checkCalibration()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hand position ...
|
||||||
|
handOffset = hands[h].controller.getAbsTranslation();
|
||||||
|
handOffset = {
|
||||||
|
x: -handOffset.x,
|
||||||
|
y: hands[h].zeroPosition.y + handOffset.y,
|
||||||
|
z: hands[h].zeroPosition.z - handOffset.z
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: 2.0* scale factor should not be necessary; Leap Motion controller code needs investigating.
|
||||||
|
handRoll = 2.0 * -hands[h].controller.getAbsRotation().z;
|
||||||
|
wristAbsRotation = wrists[h].controller.getAbsRotation();
|
||||||
|
handPitch = 2.0 * -wristAbsRotation.x;
|
||||||
|
handYaw = 2.0 * wristAbsRotation.y;
|
||||||
|
|
||||||
|
// TODO: Leap Motion controller's right-hand roll calculation only works if physical hand is upside down.
|
||||||
|
// Approximate fix is to add a fudge factor.
|
||||||
|
if (h === 1 && isWindows) {
|
||||||
|
handRoll = handRoll + 0.6 * PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hand position and orientation ...
|
||||||
|
if (h === 0) {
|
||||||
|
handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 1, z: 0 }),
|
||||||
|
Quat.fromVec3Radians({ x: handRoll, y: handYaw, z: -handPitch }));
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 1, z: 0 }),
|
||||||
|
Quat.fromVec3Radians({ x: -handRoll, y: handYaw, z: handPitch }));
|
||||||
|
}
|
||||||
|
MyAvatar.setJointModelPositionAndOrientation(hands[h].jointName, handOffset, handRotation, true);
|
||||||
|
|
||||||
|
// Finger joints ...
|
||||||
|
// TODO: 2.0 * scale factors should not be necessary; Leap Motion controller code needs investigating.
|
||||||
|
for (i = 0; i < NUM_FINGERS; i += 1) {
|
||||||
|
for (j = 0; j < NUM_FINGER_JOINTS; j += 1) {
|
||||||
|
if (fingers[h][i][j].controller !== null) {
|
||||||
|
locRotation = fingers[h][i][j].controller.getLocRotation();
|
||||||
|
if (i === THUMB) {
|
||||||
|
MyAvatar.setJointData(fingers[h][i][j].jointName,
|
||||||
|
Quat.fromPitchYawRollRadians(2.0 * side * locRotation.y, 2.0 * -locRotation.z,
|
||||||
|
2.0 * side * -locRotation.x));
|
||||||
|
} else {
|
||||||
|
MyAvatar.setJointData(fingers[h][i][j].jointName,
|
||||||
|
Quat.fromPitchYawRollRadians(2.0 * -locRotation.x, 0.0, 2.0 * -locRotation.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hands[h].inactiveCount = 0;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
hands[h].inactiveCount += 1;
|
||||||
|
|
||||||
|
if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) {
|
||||||
|
if (h === 0) {
|
||||||
|
MyAvatar.clearJointData("LeftHand");
|
||||||
|
MyAvatar.clearJointData("LeftForeArm");
|
||||||
|
MyAvatar.clearJointData("LeftArm");
|
||||||
|
} else {
|
||||||
|
MyAvatar.clearJointData("RightHand");
|
||||||
|
MyAvatar.clearJointData("RightForeArm");
|
||||||
|
MyAvatar.clearJointData("RightArm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tearDown() {
|
||||||
|
var h,
|
||||||
|
i,
|
||||||
|
j;
|
||||||
|
|
||||||
|
for (h = 0; h < NUM_HANDS; h += 1) {
|
||||||
|
Controller.releaseInputController(hands[h].controller);
|
||||||
|
Controller.releaseInputController(wrists[h].controller);
|
||||||
|
for (i = 0; i < NUM_FINGERS; i += 1) {
|
||||||
|
for (j = 0; j < NUM_FINGER_JOINTS; j += 1) {
|
||||||
|
if (fingers[h][i][j].controller !== null) {
|
||||||
|
Controller.releaseInputController(fingers[h][i][j].controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
printSkeletonJointNames: printSkeletonJointNames,
|
||||||
|
setUp : setUp,
|
||||||
|
moveHands : moveHands,
|
||||||
|
tearDown : tearDown
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
|
||||||
|
|
||||||
|
//leapHands.printSkeletonJointNames();
|
||||||
|
|
||||||
|
leapHands.setUp();
|
||||||
|
Script.update.connect(leapHands.moveHands);
|
||||||
|
Script.scriptEnding.connect(leapHands.tearDown);
|
|
@ -5,39 +5,51 @@
|
||||||
// Created by David Rowe on 5/29/14.
|
// Created by David Rowe on 5/29/14.
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
//
|
//
|
||||||
// This example script plays a sound in a continuous loop.
|
// This example script plays a sound in a continuous loop, and creates a red sphere in front of you at the origin of the sound.
|
||||||
//
|
//
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
|
// A few sample files you may want to try:
|
||||||
|
|
||||||
var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+A.raw");
|
var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+A.raw");
|
||||||
|
//var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/220Sine.wav");
|
||||||
|
//var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Cocktail+Party+Snippets/Bandcamp.wav");
|
||||||
|
|
||||||
var soundPlaying = false;
|
var soundPlaying = false;
|
||||||
|
var options = new AudioInjectionOptions();
|
||||||
|
options.position = Vec3.sum(Camera.getPosition(), Quat.getFront(MyAvatar.orientation));
|
||||||
|
options.volume = 0.5;
|
||||||
|
options.loop = true;
|
||||||
|
var playing = false;
|
||||||
|
var ball = false;
|
||||||
|
|
||||||
function keyPressEvent(event) {
|
function maybePlaySound(deltaTime) {
|
||||||
if (event.text === "1") {
|
if (sound.downloaded) {
|
||||||
if (!Audio.isInjectorPlaying(soundPlaying)) {
|
var properties = {
|
||||||
var options = new AudioInjectionOptions();
|
type: "Sphere",
|
||||||
options.position = MyAvatar.position;
|
position: options.position,
|
||||||
options.volume = 0.5;
|
dimensions: { x: 0.2, y: 0.2, z: 0.2 },
|
||||||
options.loop = true;
|
color: { red: 200, green: 0, blue: 0 }
|
||||||
soundPlaying = Audio.playSound(sound, options);
|
};
|
||||||
print("Started sound loop");
|
ball = Entities.addEntity(properties);
|
||||||
} else {
|
soundPlaying = Audio.playSound(sound, options);
|
||||||
Audio.stopInjector(soundPlaying);
|
print("Started sound looping.");
|
||||||
print("Stopped sound loop");
|
Script.update.disconnect(maybePlaySound);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scriptEnding() {
|
function scriptEnding() {
|
||||||
if (Audio.isInjectorPlaying(soundPlaying)) {
|
if (Audio.isInjectorPlaying(soundPlaying)) {
|
||||||
Audio.stopInjector(soundPlaying);
|
Audio.stopInjector(soundPlaying);
|
||||||
print("Stopped sound loop");
|
Entities.deleteEntity(ball);
|
||||||
|
print("Stopped sound.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect a call back that happens every frame
|
// Connect a call back that happens every frame
|
||||||
Script.scriptEnding.connect(scriptEnding);
|
Script.scriptEnding.connect(scriptEnding);
|
||||||
Controller.keyPressEvent.connect(keyPressEvent);
|
Script.update.connect(maybePlaySound);
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,6 @@ void main(void) {
|
||||||
normalizedNormal));
|
normalizedNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,6 @@ void main(void) {
|
||||||
normalizedNormal));
|
normalizedNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,6 @@ void main(void) {
|
||||||
normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal));
|
normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@ void main(void) {
|
||||||
normalize(vec4(interpolatedPosition.xyz, 0.0))), viewNormal));
|
normalize(vec4(interpolatedPosition.xyz, 0.0))), viewNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb *
|
||||||
|
texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ void main(void) {
|
||||||
normalizedNormal));
|
normalizedNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb *
|
||||||
|
texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,6 @@ void main(void) {
|
||||||
normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal));
|
normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ void main(void) {
|
||||||
normalize(vec4(interpolatedPosition.xyz, 0.0))), viewNormal));
|
normalize(vec4(interpolatedPosition.xyz, 0.0))), viewNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb *
|
||||||
|
texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,6 @@ void main(void) {
|
||||||
normalizedNormal));
|
normalizedNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,6 @@ void main(void) {
|
||||||
normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal));
|
normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ void main(void) {
|
||||||
normalize(vec4(interpolatedPosition.xyz, 0.0))), viewNormal));
|
normalize(vec4(interpolatedPosition.xyz, 0.0))), viewNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb *
|
||||||
|
texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ void main(void) {
|
||||||
normalizedNormal));
|
normalizedNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb *
|
||||||
|
texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ void main(void) {
|
||||||
normalizedNormal));
|
normalizedNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
gl_FragColor = vec4(base.rgb, gl_FrontMaterial.diffuse.a) * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb *
|
||||||
|
texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,6 +288,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||||
// set the account manager's root URL and trigger a login request if we don't have the access token
|
// set the account manager's root URL and trigger a login request if we don't have the access token
|
||||||
accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
|
accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
|
||||||
UserActivityLogger::getInstance().launch(applicationVersion());
|
UserActivityLogger::getInstance().launch(applicationVersion());
|
||||||
|
|
||||||
|
// grab the location manager instance early so it lives in our thread
|
||||||
|
LocationManager::getInstance();
|
||||||
|
|
||||||
// once the event loop has started, check and signal for an access token
|
// once the event loop has started, check and signal for an access token
|
||||||
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
|
||||||
|
@ -927,6 +930,13 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Qt::Key_N:
|
||||||
|
if (isMeta) {
|
||||||
|
Menu::getInstance()->triggerOption(MenuOption::NameLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case Qt::Key_Up:
|
case Qt::Key_Up:
|
||||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||||
|
|
|
@ -753,6 +753,15 @@ void Audio::handleAudioInput() {
|
||||||
quint16 numSilentSamples = numNetworkSamples;
|
quint16 numSilentSamples = numNetworkSamples;
|
||||||
memcpy(currentPacketPtr, &numSilentSamples, sizeof(quint16));
|
memcpy(currentPacketPtr, &numSilentSamples, sizeof(quint16));
|
||||||
currentPacketPtr += sizeof(quint16);
|
currentPacketPtr += sizeof(quint16);
|
||||||
|
|
||||||
|
// memcpy the three float positions
|
||||||
|
memcpy(currentPacketPtr, &headPosition, sizeof(headPosition));
|
||||||
|
currentPacketPtr += (sizeof(headPosition));
|
||||||
|
|
||||||
|
// memcpy our orientation
|
||||||
|
memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation));
|
||||||
|
currentPacketPtr += sizeof(headOrientation);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// set the mono/stereo byte
|
// set the mono/stereo byte
|
||||||
*currentPacketPtr++ = isStereo;
|
*currentPacketPtr++ = isStereo;
|
||||||
|
|
|
@ -332,9 +332,9 @@ Menu::Menu() :
|
||||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersVoxelNodes,
|
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersVoxelNodes,
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_1, false,
|
Qt::CTRL | Qt::SHIFT | Qt::Key_1, false,
|
||||||
&nodeBounds, SLOT(setShowVoxelNodes(bool)));
|
&nodeBounds, SLOT(setShowVoxelNodes(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersModelNodes,
|
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersEntityNodes,
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_2, false,
|
Qt::CTRL | Qt::SHIFT | Qt::Key_2, false,
|
||||||
&nodeBounds, SLOT(setShowModelNodes(bool)));
|
&nodeBounds, SLOT(setShowEntityNodes(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersParticleNodes,
|
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersParticleNodes,
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_3, false,
|
Qt::CTRL | Qt::SHIFT | Qt::Key_3, false,
|
||||||
&nodeBounds, SLOT(setShowParticleNodes(bool)));
|
&nodeBounds, SLOT(setShowParticleNodes(bool)));
|
||||||
|
|
|
@ -441,7 +441,7 @@ namespace MenuOption {
|
||||||
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
|
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
|
||||||
const QString SettingsExport = "Export Settings";
|
const QString SettingsExport = "Export Settings";
|
||||||
const QString SettingsImport = "Import Settings";
|
const QString SettingsImport = "Import Settings";
|
||||||
const QString ShowBordersModelNodes = "Show Model Nodes";
|
const QString ShowBordersEntityNodes = "Show Entity Nodes";
|
||||||
const QString ShowBordersParticleNodes = "Show Particle Nodes";
|
const QString ShowBordersParticleNodes = "Show Particle Nodes";
|
||||||
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
|
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
|
||||||
const QString ShowIKConstraints = "Show IK Constraints";
|
const QString ShowIKConstraints = "Show IK Constraints";
|
||||||
|
|
|
@ -42,17 +42,15 @@ ScriptsModel::ScriptsModel(QObject* parent) :
|
||||||
_localDirectory(),
|
_localDirectory(),
|
||||||
_fsWatcher(),
|
_fsWatcher(),
|
||||||
_localFiles(),
|
_localFiles(),
|
||||||
_remoteFiles() {
|
_remoteFiles()
|
||||||
|
{
|
||||||
QString scriptPath = Menu::getInstance()->getScriptsLocation();
|
|
||||||
|
|
||||||
_localDirectory.setPath(scriptPath);
|
|
||||||
_localDirectory.setFilter(QDir::Files | QDir::Readable);
|
_localDirectory.setFilter(QDir::Files | QDir::Readable);
|
||||||
_localDirectory.setNameFilters(QStringList("*.js"));
|
_localDirectory.setNameFilters(QStringList("*.js"));
|
||||||
|
|
||||||
_fsWatcher.addPath(_localDirectory.absolutePath());
|
updateScriptsLocation(Menu::getInstance()->getScriptsLocation());
|
||||||
|
|
||||||
connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles);
|
connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles);
|
||||||
|
|
||||||
connect(Menu::getInstance(), &Menu::scriptLocationChanged, this, &ScriptsModel::updateScriptsLocation);
|
connect(Menu::getInstance(), &Menu::scriptLocationChanged, this, &ScriptsModel::updateScriptsLocation);
|
||||||
|
|
||||||
reloadLocalFiles();
|
reloadLocalFiles();
|
||||||
|
@ -88,8 +86,13 @@ int ScriptsModel::rowCount(const QModelIndex& parent) const {
|
||||||
|
|
||||||
void ScriptsModel::updateScriptsLocation(const QString& newPath) {
|
void ScriptsModel::updateScriptsLocation(const QString& newPath) {
|
||||||
_fsWatcher.removePath(_localDirectory.absolutePath());
|
_fsWatcher.removePath(_localDirectory.absolutePath());
|
||||||
|
|
||||||
_localDirectory.setPath(newPath);
|
_localDirectory.setPath(newPath);
|
||||||
_fsWatcher.addPath(_localDirectory.absolutePath());
|
|
||||||
|
if (!_localDirectory.absolutePath().isEmpty()) {
|
||||||
|
_fsWatcher.addPath(_localDirectory.absolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
reloadLocalFiles();
|
reloadLocalFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -830,6 +830,28 @@ glm::quat Avatar::getJointCombinedRotation(const QString& name) const {
|
||||||
return rotation;
|
return rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f;
|
||||||
|
|
||||||
|
void Avatar::setJointModelPositionAndOrientation(int index, glm::vec3 position, const glm::quat& rotation) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(const_cast<Avatar*>(this), "setJointModelPositionAndOrientation",
|
||||||
|
Qt::BlockingQueuedConnection, Q_ARG(const int, index), Q_ARG(const glm::vec3, position),
|
||||||
|
Q_ARG(const glm::quat&, rotation));
|
||||||
|
} else {
|
||||||
|
_skeletonModel.inverseKinematics(index, position, rotation, SCRIPT_PRIORITY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Avatar::setJointModelPositionAndOrientation(const QString& name, glm::vec3 position, const glm::quat& rotation) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(const_cast<Avatar*>(this), "setJointModelPositionAndOrientation",
|
||||||
|
Qt::BlockingQueuedConnection, Q_ARG(const QString&, name), Q_ARG(const glm::vec3, position),
|
||||||
|
Q_ARG(const glm::quat&, rotation));
|
||||||
|
} else {
|
||||||
|
_skeletonModel.inverseKinematics(getJointIndex(name), position, rotation, SCRIPT_PRIORITY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
|
void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
|
||||||
//Scale a world space vector as if it was relative to the position
|
//Scale a world space vector as if it was relative to the position
|
||||||
positionToScale = _position + _scale * (positionToScale - _position);
|
positionToScale = _position + _scale * (positionToScale - _position);
|
||||||
|
|
|
@ -151,6 +151,10 @@ public:
|
||||||
Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const;
|
Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const;
|
||||||
Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const;
|
Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void setJointModelPositionAndOrientation(int index, const glm::vec3 position, const glm::quat& rotation);
|
||||||
|
Q_INVOKABLE void setJointModelPositionAndOrientation(const QString& name, const glm::vec3 position,
|
||||||
|
const glm::quat& rotation);
|
||||||
|
|
||||||
Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; }
|
Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; }
|
||||||
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
|
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
|
||||||
Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; }
|
Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; }
|
||||||
|
|
|
@ -57,15 +57,15 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX
|
||||||
* joint.rotation, DEFAULT_PRIORITY);
|
* joint.rotation, DEFAULT_PRIORITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||||
// likewise with the eye joints
|
// likewise with the eye joints
|
||||||
// NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual.
|
// NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual.
|
||||||
glm::mat4 inverse = glm::inverse(glm::mat4_cast(_rotation) * parentState.getTransform() *
|
glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() *
|
||||||
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
|
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
|
||||||
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
|
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
|
||||||
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f));
|
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f));
|
||||||
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() +
|
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() +
|
||||||
_owningHead->getSaccade() - _translation, 1.0f));
|
_owningHead->getSaccade() - model->getTranslation(), 1.0f));
|
||||||
glm::quat between = rotationBetween(front, lookAt);
|
glm::quat between = rotationBetween(front, lookAt);
|
||||||
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
|
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
|
||||||
state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
|
state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
|
||||||
|
@ -82,7 +82,7 @@ void FaceModel::updateJointState(int index) {
|
||||||
maybeUpdateNeckRotation(parentState, joint, state);
|
maybeUpdateNeckRotation(parentState, joint, state);
|
||||||
|
|
||||||
} else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) {
|
} else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) {
|
||||||
maybeUpdateEyeRotation(parentState, joint, state);
|
maybeUpdateEyeRotation(this, parentState, joint, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
virtual void simulate(float deltaTime, bool fullUpdate = true);
|
virtual void simulate(float deltaTime, bool fullUpdate = true);
|
||||||
|
|
||||||
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||||
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
virtual void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||||
virtual void updateJointState(int index);
|
virtual void updateJointState(int index);
|
||||||
|
|
||||||
/// Retrieve the positions of up to two eye meshes.
|
/// Retrieve the positions of up to two eye meshes.
|
||||||
|
|
|
@ -184,8 +184,8 @@ void Head::relaxLean(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Head::render(float alpha, Model::RenderMode mode) {
|
void Head::render(float alpha, Model::RenderMode mode) {
|
||||||
if (_faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)) &&
|
_faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows));
|
||||||
_renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) {
|
if (_renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) {
|
||||||
renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition);
|
renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1002,6 +1002,7 @@ void MyAvatar::clearJointData(int index) {
|
||||||
if (QThread::currentThread() == thread()) {
|
if (QThread::currentThread() == thread()) {
|
||||||
// HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority
|
// HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority
|
||||||
_skeletonModel.setJointState(index, false, glm::quat(), 0.0f);
|
_skeletonModel.setJointState(index, false, glm::quat(), 0.0f);
|
||||||
|
_skeletonModel.clearJointAnimationPriority(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1833,25 +1834,34 @@ void MyAvatar::resetSize() {
|
||||||
qDebug("Reseted scale to %f", _targetScale);
|
qDebug("Reseted scale to %f", _targetScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::vec3& newOrientation) {
|
void MyAvatar::goToLocation(const glm::vec3& newPosition,
|
||||||
glm::quat quatOrientation = getOrientation();
|
bool hasOrientation, const glm::quat& newOrientation,
|
||||||
|
bool shouldFaceLocation) {
|
||||||
|
|
||||||
qDebug().nospace() << "MyAvatar goToLocation - moving to " << newPosition.x << ", "
|
qDebug().nospace() << "MyAvatar goToLocation - moving to " << newPosition.x << ", "
|
||||||
<< newPosition.y << ", " << newPosition.z;
|
<< newPosition.y << ", " << newPosition.z;
|
||||||
|
|
||||||
|
glm::vec3 shiftedPosition = newPosition;
|
||||||
|
|
||||||
if (hasOrientation) {
|
if (hasOrientation) {
|
||||||
qDebug().nospace() << "MyAvatar goToLocation - new orientation is "
|
qDebug().nospace() << "MyAvatar goToLocation - new orientation is "
|
||||||
<< newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z;
|
<< newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w;
|
||||||
|
|
||||||
// orient the user to face the target
|
// orient the user to face the target
|
||||||
glm::quat quatOrientation = glm::quat(glm::radians(newOrientation))
|
glm::quat quatOrientation = newOrientation;
|
||||||
* glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
|
||||||
|
if (shouldFaceLocation) {
|
||||||
|
|
||||||
|
quatOrientation = newOrientation * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
|
|
||||||
|
// move the user a couple units away
|
||||||
|
const float DISTANCE_TO_USER = 2.0f;
|
||||||
|
shiftedPosition = newPosition - quatOrientation * IDENTITY_FRONT * DISTANCE_TO_USER;
|
||||||
|
}
|
||||||
|
|
||||||
setOrientation(quatOrientation);
|
setOrientation(quatOrientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// move the user a couple units away
|
|
||||||
const float DISTANCE_TO_USER = 2.0f;
|
|
||||||
glm::vec3 shiftedPosition = newPosition - quatOrientation * IDENTITY_FRONT * DISTANCE_TO_USER;
|
|
||||||
slamPosition(shiftedPosition);
|
slamPosition(shiftedPosition);
|
||||||
emit transformChanged();
|
emit transformChanged();
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,7 +163,9 @@ public slots:
|
||||||
void decreaseSize();
|
void decreaseSize();
|
||||||
void resetSize();
|
void resetSize();
|
||||||
|
|
||||||
void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::vec3& newOrientation = glm::vec3());
|
void goToLocation(const glm::vec3& newPosition,
|
||||||
|
bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(),
|
||||||
|
bool shouldFaceLocation = false);
|
||||||
|
|
||||||
// Set/Get update the thrust that will move the avatar around
|
// Set/Get update the thrust that will move the avatar around
|
||||||
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
|
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
|
||||||
|
|
|
@ -281,7 +281,7 @@ void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||||
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(parentState, joint, state);
|
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(this, parentState, joint, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonModel::renderJointConstraints(int jointIndex) {
|
void SkeletonModel::renderJointConstraints(int jointIndex) {
|
||||||
|
|
|
@ -9,10 +9,14 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <qhttpmultipart.h>
|
||||||
#include <qjsonobject.h>
|
#include <qjsonobject.h>
|
||||||
|
|
||||||
#include <AccountManager.h>
|
#include <AccountManager.h>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "ui/Snapshot.h"
|
||||||
|
|
||||||
#include "LocationManager.h"
|
#include "LocationManager.h"
|
||||||
|
|
||||||
const QString POST_LOCATION_CREATE = "/api/v1/locations/";
|
const QString POST_LOCATION_CREATE = "/api/v1/locations/";
|
||||||
|
@ -24,13 +28,17 @@ LocationManager& LocationManager::getInstance() {
|
||||||
|
|
||||||
const QString UNKNOWN_ERROR_MESSAGE = "Unknown error creating named location. Please try again!";
|
const QString UNKNOWN_ERROR_MESSAGE = "Unknown error creating named location. Please try again!";
|
||||||
|
|
||||||
void LocationManager::namedLocationDataReceived(const QJsonObject& data) {
|
const QString LOCATION_OBJECT_KEY = "location";
|
||||||
if (data.isEmpty()) {
|
const QString LOCATION_ID_KEY = "id";
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.contains("status") && data["status"].toString() == "success") {
|
void LocationManager::namedLocationDataReceived(const QJsonObject& rootObject) {
|
||||||
|
|
||||||
|
if (rootObject.contains("status") && rootObject["status"].toString() == "success") {
|
||||||
emit creationCompleted(QString());
|
emit creationCompleted(QString());
|
||||||
|
|
||||||
|
// successfuly created a location - grab the ID from the response and create a snapshot to upload
|
||||||
|
QString locationIDString = rootObject[LOCATION_OBJECT_KEY].toObject()[LOCATION_ID_KEY].toString();
|
||||||
|
updateSnapshotForExistingLocation(locationIDString);
|
||||||
} else {
|
} else {
|
||||||
emit creationCompleted(UNKNOWN_ERROR_MESSAGE);
|
emit creationCompleted(UNKNOWN_ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
|
@ -87,3 +95,57 @@ void LocationManager::errorDataReceived(QNetworkReply& errorReply) {
|
||||||
creationCompleted(UNKNOWN_ERROR_MESSAGE);
|
creationCompleted(UNKNOWN_ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LocationManager::locationImageUpdateSuccess(const QJsonObject& rootObject) {
|
||||||
|
qDebug() << "Successfuly updated a location image.";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationManager::updateSnapshotForExistingLocation(const QString& locationID) {
|
||||||
|
// first create a snapshot and save it
|
||||||
|
Application* application = Application::getInstance();
|
||||||
|
|
||||||
|
QTemporaryFile* tempImageFile = Snapshot::saveTempSnapshot(application->getGLWidget(), application->getAvatar());
|
||||||
|
|
||||||
|
if (tempImageFile && tempImageFile->open()) {
|
||||||
|
AccountManager& accountManager = AccountManager::getInstance();
|
||||||
|
|
||||||
|
// setup a multipart that is in the AccountManager thread - we need this so it can be cleaned up after the QNetworkReply
|
||||||
|
QHttpMultiPart* imageFileMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||||
|
imageFileMultiPart->moveToThread(accountManager.thread());
|
||||||
|
|
||||||
|
// parent the temp file to the QHttpMultipart after moving it to account manager thread
|
||||||
|
tempImageFile->moveToThread(accountManager.thread());
|
||||||
|
tempImageFile->setParent(imageFileMultiPart);
|
||||||
|
|
||||||
|
qDebug() << "Uploading a snapshot from" << QFileInfo(*tempImageFile).absoluteFilePath()
|
||||||
|
<< "as location image for" << locationID;
|
||||||
|
|
||||||
|
const QString LOCATION_IMAGE_NAME = "location[image]";
|
||||||
|
|
||||||
|
QHttpPart imagePart;
|
||||||
|
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||||
|
QVariant("form-data; name=\"" + LOCATION_IMAGE_NAME + "\";"
|
||||||
|
" filename=\"" + QFileInfo(tempImageFile->fileName()).fileName().toUtf8() + "\""));
|
||||||
|
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||||
|
imagePart.setBodyDevice(tempImageFile);
|
||||||
|
|
||||||
|
imageFileMultiPart->append(imagePart);
|
||||||
|
|
||||||
|
const QString LOCATION_IMAGE_PUT_PATH = "api/v1/locations/%1/image";
|
||||||
|
|
||||||
|
JSONCallbackParameters imageCallbackParams;
|
||||||
|
imageCallbackParams.jsonCallbackReceiver = this;
|
||||||
|
imageCallbackParams.jsonCallbackMethod = "locationImageUpdateSuccess";
|
||||||
|
|
||||||
|
// make an authenticated request via account manager to upload the image
|
||||||
|
// don't do anything with error or success for now
|
||||||
|
AccountManager::getInstance().authenticatedRequest(LOCATION_IMAGE_PUT_PATH.arg(locationID),
|
||||||
|
QNetworkAccessManager::PutOperation,
|
||||||
|
JSONCallbackParameters(), QByteArray(), imageFileMultiPart);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Couldn't open snapshot file to upload as location image. No location image will be stored.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,12 @@ signals:
|
||||||
void creationCompleted(const QString& errorMessage);
|
void creationCompleted(const QString& errorMessage);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void namedLocationDataReceived(const QJsonObject& data);
|
void namedLocationDataReceived(const QJsonObject& jsonObject);
|
||||||
void errorDataReceived(QNetworkReply& errorReply);
|
void errorDataReceived(QNetworkReply& errorReply);
|
||||||
|
void locationImageUpdateSuccess(const QJsonObject& jsonObject);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateSnapshotForExistingLocation(const QString& locationID);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -684,10 +684,10 @@ bool NetworkMeshPart::isTranslucent() const {
|
||||||
return diffuseTexture && diffuseTexture->isTranslucent();
|
return diffuseTexture && diffuseTexture->isTranslucent();
|
||||||
}
|
}
|
||||||
|
|
||||||
int NetworkMesh::getTranslucentPartCount() const {
|
int NetworkMesh::getTranslucentPartCount(const FBXMesh& fbxMesh) const {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
foreach (const NetworkMeshPart& part, parts) {
|
for (int i = 0; i < parts.size(); i++) {
|
||||||
if (part.isTranslucent()) {
|
if (parts.at(i).isTranslucent() || fbxMesh.parts.at(i).opacity != 1.0f) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ public:
|
||||||
|
|
||||||
QVector<NetworkMeshPart> parts;
|
QVector<NetworkMeshPart> parts;
|
||||||
|
|
||||||
int getTranslucentPartCount() const;
|
int getTranslucentPartCount(const FBXMesh& fbxMesh) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_GeometryCache_h
|
#endif // hifi_GeometryCache_h
|
||||||
|
|
|
@ -608,13 +608,13 @@ bool Model::render(float alpha, RenderMode mode, bool receiveShadows) {
|
||||||
glAlphaFunc(GL_GREATER, 0.5f * alpha);
|
glAlphaFunc(GL_GREATER, 0.5f * alpha);
|
||||||
|
|
||||||
receiveShadows &= Menu::getInstance()->getShadowsEnabled();
|
receiveShadows &= Menu::getInstance()->getShadowsEnabled();
|
||||||
renderMeshes(alpha, mode, false, receiveShadows);
|
renderMeshes(mode, false, receiveShadows);
|
||||||
|
|
||||||
glDisable(GL_ALPHA_TEST);
|
glDisable(GL_ALPHA_TEST);
|
||||||
|
|
||||||
// render translucent meshes afterwards
|
// render translucent meshes afterwards
|
||||||
|
|
||||||
renderMeshes(alpha, mode, true, receiveShadows);
|
renderMeshes(mode, true, receiveShadows);
|
||||||
|
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
@ -894,6 +894,10 @@ void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::setScaleToFit(bool scaleToFit, float largestDimension) {
|
void Model::setScaleToFit(bool scaleToFit, float largestDimension) {
|
||||||
|
if (!isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) {
|
if (_scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) {
|
||||||
_scaleToFit = scaleToFit;
|
_scaleToFit = scaleToFit;
|
||||||
|
|
||||||
|
@ -1356,7 +1360,7 @@ void Model::deleteGeometry() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool receiveShadows) {
|
void Model::renderMeshes(RenderMode mode, bool translucent, bool receiveShadows) {
|
||||||
updateVisibleJointStates();
|
updateVisibleJointStates();
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
||||||
|
@ -1365,13 +1369,13 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re
|
||||||
for (int i = 0; i < networkMeshes.size(); i++) {
|
for (int i = 0; i < networkMeshes.size(); i++) {
|
||||||
// exit early if the translucency doesn't match what we're drawing
|
// exit early if the translucency doesn't match what we're drawing
|
||||||
const NetworkMesh& networkMesh = networkMeshes.at(i);
|
const NetworkMesh& networkMesh = networkMeshes.at(i);
|
||||||
if (translucent ? (networkMesh.getTranslucentPartCount() == 0) :
|
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||||
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
|
if (translucent ? (networkMesh.getTranslucentPartCount(mesh) == 0) :
|
||||||
|
(networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
|
const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
|
||||||
|
|
||||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
|
||||||
int vertexCount = mesh.vertices.size();
|
int vertexCount = mesh.vertices.size();
|
||||||
if (vertexCount == 0) {
|
if (vertexCount == 0) {
|
||||||
// sanity check
|
// sanity check
|
||||||
|
@ -1528,7 +1532,7 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re
|
||||||
if (!mesh.colors.isEmpty()) {
|
if (!mesh.colors.isEmpty()) {
|
||||||
glEnableClientState(GL_COLOR_ARRAY);
|
glEnableClientState(GL_COLOR_ARRAY);
|
||||||
} else {
|
} else {
|
||||||
glColor4f(1.0f, 1.0f, 1.0f, alpha);
|
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
}
|
}
|
||||||
if (!mesh.texCoords.isEmpty()) {
|
if (!mesh.texCoords.isEmpty()) {
|
||||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||||
|
@ -1538,7 +1542,7 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re
|
||||||
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
||||||
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
||||||
const FBXMeshPart& part = mesh.parts.at(j);
|
const FBXMeshPart& part = mesh.parts.at(j);
|
||||||
if (networkPart.isTranslucent() != translucent) {
|
if ((networkPart.isTranslucent() || part.opacity != 1.0f) != translucent) {
|
||||||
offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int);
|
offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1547,8 +1551,8 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
|
glm::vec4 diffuse = glm::vec4(part.diffuseColor, part.opacity);
|
||||||
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
|
glm::vec4 specular = glm::vec4(part.specularColor, part.opacity);
|
||||||
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
|
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
|
||||||
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
|
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
|
||||||
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
|
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
|
||||||
|
|
|
@ -183,6 +183,8 @@ public:
|
||||||
QVector<JointState>& getJointStates() { return _jointStates; }
|
QVector<JointState>& getJointStates() { return _jointStates; }
|
||||||
const QVector<JointState>& getJointStates() const { return _jointStates; }
|
const QVector<JointState>& getJointStates() const { return _jointStates; }
|
||||||
|
|
||||||
|
void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QSharedPointer<NetworkGeometry> _geometry;
|
QSharedPointer<NetworkGeometry> _geometry;
|
||||||
|
|
||||||
|
@ -238,8 +240,6 @@ protected:
|
||||||
bool useRotation = false, int lastFreeIndex = -1, bool allIntermediatesFree = false,
|
bool useRotation = false, int lastFreeIndex = -1, bool allIntermediatesFree = false,
|
||||||
const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f), float priority = 1.0f);
|
const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f), float priority = 1.0f);
|
||||||
|
|
||||||
void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority);
|
|
||||||
|
|
||||||
/// Restores the indexed joint to its default position.
|
/// Restores the indexed joint to its default position.
|
||||||
/// \param fraction the fraction of the default position to apply (i.e., 0.25f to slerp one fourth of the way to
|
/// \param fraction the fraction of the default position to apply (i.e., 0.25f to slerp one fourth of the way to
|
||||||
/// the original position
|
/// the original position
|
||||||
|
@ -256,7 +256,7 @@ private:
|
||||||
|
|
||||||
void applyNextGeometry();
|
void applyNextGeometry();
|
||||||
void deleteGeometry();
|
void deleteGeometry();
|
||||||
void renderMeshes(float alpha, RenderMode mode, bool translucent, bool receiveShadows);
|
void renderMeshes(RenderMode mode, bool translucent, bool receiveShadows);
|
||||||
QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
||||||
void initJointTransforms();
|
void initJointTransforms();
|
||||||
|
|
||||||
|
|
|
@ -312,7 +312,8 @@ void ControllerScriptingInterface::updateInputControllers() {
|
||||||
InputController::InputController(int deviceTrackerId, int subTrackerId, QObject* parent) :
|
InputController::InputController(int deviceTrackerId, int subTrackerId, QObject* parent) :
|
||||||
AbstractInputController(),
|
AbstractInputController(),
|
||||||
_deviceTrackerId(deviceTrackerId),
|
_deviceTrackerId(deviceTrackerId),
|
||||||
_subTrackerId(subTrackerId)
|
_subTrackerId(subTrackerId),
|
||||||
|
_isActive(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,24 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
||||||
|
QFile* snapshotFile = savedFileForSnapshot(widget, avatar, false);
|
||||||
|
|
||||||
|
// we don't need the snapshot file, so close it, grab its filename and delete it
|
||||||
|
snapshotFile->close();
|
||||||
|
|
||||||
|
QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath();
|
||||||
|
|
||||||
|
delete snapshotFile;
|
||||||
|
|
||||||
|
return snapshotPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTemporaryFile* Snapshot::saveTempSnapshot(QGLWidget* widget, Avatar* avatar) {
|
||||||
|
// return whatever we get back from saved file for snapshot
|
||||||
|
return static_cast<QTemporaryFile*>(savedFileForSnapshot(widget, avatar, true));;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile* Snapshot::savedFileForSnapshot(QGLWidget* widget, Avatar* avatar, bool isTemporary) {
|
||||||
QImage shot = widget->grabFrameBuffer();
|
QImage shot = widget->grabFrameBuffer();
|
||||||
|
|
||||||
glm::vec3 location = avatar->getPosition();
|
glm::vec3 location = avatar->getPosition();
|
||||||
|
@ -91,16 +109,40 @@ QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
||||||
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
|
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
|
||||||
|
|
||||||
QDateTime now = QDateTime::currentDateTime();
|
QDateTime now = QDateTime::currentDateTime();
|
||||||
QString fileName = Menu::getInstance()->getSnapshotsLocation();
|
|
||||||
|
|
||||||
if (!fileName.endsWith(QDir::separator())) {
|
|
||||||
fileName.append(QDir::separator());
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName.append(QString(FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT), formattedLocation)));
|
|
||||||
shot.save(fileName, 0, 100);
|
|
||||||
|
|
||||||
return fileName;
|
QString filename = FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT), formattedLocation);
|
||||||
|
|
||||||
|
const int IMAGE_QUALITY = 100;
|
||||||
|
|
||||||
|
if (!isTemporary) {
|
||||||
|
QString snapshotFullPath = Menu::getInstance()->getSnapshotsLocation();
|
||||||
|
|
||||||
|
if (!snapshotFullPath.endsWith(QDir::separator())) {
|
||||||
|
snapshotFullPath.append(QDir::separator());
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotFullPath.append(filename);
|
||||||
|
|
||||||
|
QFile* imageFile = new QFile(snapshotFullPath);
|
||||||
|
imageFile->open(QIODevice::WriteOnly);
|
||||||
|
|
||||||
|
shot.save(imageFile, 0, IMAGE_QUALITY);
|
||||||
|
imageFile->close();
|
||||||
|
|
||||||
|
return imageFile;
|
||||||
|
} else {
|
||||||
|
QTemporaryFile* imageTempFile = new QTemporaryFile(QDir::tempPath() + "/XXXXXX-" + filename);
|
||||||
|
|
||||||
|
if (!imageTempFile->open()) {
|
||||||
|
qDebug() << "Unable to open QTemporaryFile for temp snapshot. Will not save.";
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
shot.save(imageTempFile, 0, IMAGE_QUALITY);
|
||||||
|
imageTempFile->close();
|
||||||
|
|
||||||
|
return imageTempFile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,11 @@ class Snapshot {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static QString saveSnapshot(QGLWidget* widget, Avatar* avatar);
|
static QString saveSnapshot(QGLWidget* widget, Avatar* avatar);
|
||||||
|
static QTemporaryFile* saveTempSnapshot(QGLWidget* widget, Avatar* avatar);
|
||||||
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
|
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QFile* savedFileForSnapshot(QGLWidget* widget, Avatar* avatar, bool isTemporary);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Snapshot_h
|
#endif // hifi_Snapshot_h
|
||||||
|
|
|
@ -109,14 +109,8 @@ int InboundAudioStream::parseData(const QByteArray& packet) {
|
||||||
|
|
||||||
int networkSamples;
|
int networkSamples;
|
||||||
|
|
||||||
if (packetType == PacketTypeSilentAudioFrame) {
|
// parse the info after the seq number and before the audio data (the stream properties)
|
||||||
quint16 numSilentSamples = *(reinterpret_cast<const quint16*>(dataAt));
|
readBytes += parseStreamProperties(packetType, packet.mid(readBytes), networkSamples);
|
||||||
readBytes += sizeof(quint16);
|
|
||||||
networkSamples = (int)numSilentSamples;
|
|
||||||
} else {
|
|
||||||
// parse the info after the seq number and before the audio data (the stream properties)
|
|
||||||
readBytes += parseStreamProperties(packetType, packet.mid(readBytes), networkSamples);
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle this packet based on its arrival status.
|
// handle this packet based on its arrival status.
|
||||||
switch (arrivalInfo._status) {
|
switch (arrivalInfo._status) {
|
||||||
|
|
|
@ -33,10 +33,6 @@ PositionalAudioStream::PositionalAudioStream(PositionalAudioStream::Type type, b
|
||||||
_lastPopOutputLoudness(0.0f),
|
_lastPopOutputLoudness(0.0f),
|
||||||
_listenerUnattenuatedZone(NULL)
|
_listenerUnattenuatedZone(NULL)
|
||||||
{
|
{
|
||||||
// constant defined in AudioMixer.h. However, we don't want to include this here
|
|
||||||
// we will soon find a better common home for these audio-related constants
|
|
||||||
const int SAMPLE_PHASE_DELAY_AT_90 = 20;
|
|
||||||
_filter.initialize(SAMPLE_RATE, (NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)) / 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PositionalAudioStream::resetStats() {
|
void PositionalAudioStream::resetStats() {
|
||||||
|
|
|
@ -16,10 +16,6 @@
|
||||||
#include <AABox.h>
|
#include <AABox.h>
|
||||||
|
|
||||||
#include "InboundAudioStream.h"
|
#include "InboundAudioStream.h"
|
||||||
#include "AudioFormat.h"
|
|
||||||
#include "AudioBuffer.h"
|
|
||||||
#include "AudioFilter.h"
|
|
||||||
#include "AudioFilterBank.h"
|
|
||||||
|
|
||||||
const int AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY = 100;
|
const int AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY = 100;
|
||||||
|
|
||||||
|
@ -50,8 +46,6 @@ public:
|
||||||
|
|
||||||
void setListenerUnattenuatedZone(AABox* listenerUnattenuatedZone) { _listenerUnattenuatedZone = listenerUnattenuatedZone; }
|
void setListenerUnattenuatedZone(AABox* listenerUnattenuatedZone) { _listenerUnattenuatedZone = listenerUnattenuatedZone; }
|
||||||
|
|
||||||
AudioFilterHSF1s& getFilter() { return _filter; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// disallow copying of PositionalAudioStream objects
|
// disallow copying of PositionalAudioStream objects
|
||||||
PositionalAudioStream(const PositionalAudioStream&);
|
PositionalAudioStream(const PositionalAudioStream&);
|
||||||
|
@ -70,8 +64,6 @@ protected:
|
||||||
float _lastPopOutputTrailingLoudness;
|
float _lastPopOutputTrailingLoudness;
|
||||||
float _lastPopOutputLoudness;
|
float _lastPopOutputLoudness;
|
||||||
AABox* _listenerUnattenuatedZone;
|
AABox* _listenerUnattenuatedZone;
|
||||||
|
|
||||||
AudioFilterHSF1s _filter;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_PositionalAudioStream_h
|
#endif // hifi_PositionalAudioStream_h
|
||||||
|
|
|
@ -657,6 +657,7 @@ public:
|
||||||
glm::vec3 diffuse;
|
glm::vec3 diffuse;
|
||||||
glm::vec3 specular;
|
glm::vec3 specular;
|
||||||
float shininess;
|
float shininess;
|
||||||
|
float opacity;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Cluster {
|
class Cluster {
|
||||||
|
@ -1280,7 +1281,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
textureContent.insert(filename, content);
|
textureContent.insert(filename, content);
|
||||||
}
|
}
|
||||||
} else if (object.name == "Material") {
|
} else if (object.name == "Material") {
|
||||||
Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f };
|
Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f, 1.0f };
|
||||||
foreach (const FBXNode& subobject, object.children) {
|
foreach (const FBXNode& subobject, object.children) {
|
||||||
bool properties = false;
|
bool properties = false;
|
||||||
QByteArray propertyName;
|
QByteArray propertyName;
|
||||||
|
@ -1306,6 +1307,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
|
|
||||||
} else if (property.properties.at(0) == "Shininess") {
|
} else if (property.properties.at(0) == "Shininess") {
|
||||||
material.shininess = property.properties.at(index).value<double>();
|
material.shininess = property.properties.at(index).value<double>();
|
||||||
|
|
||||||
|
} else if (property.properties.at(0) == "Opacity") {
|
||||||
|
material.opacity = property.properties.at(index).value<double>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1602,6 +1606,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
part.diffuseColor = material.diffuse;
|
part.diffuseColor = material.diffuse;
|
||||||
part.specularColor = material.specular;
|
part.specularColor = material.specular;
|
||||||
part.shininess = material.shininess;
|
part.shininess = material.shininess;
|
||||||
|
part.opacity = material.opacity;
|
||||||
if (!diffuseTexture.filename.isNull()) {
|
if (!diffuseTexture.filename.isNull()) {
|
||||||
part.diffuseTexture = diffuseTexture;
|
part.diffuseTexture = diffuseTexture;
|
||||||
}
|
}
|
||||||
|
@ -1750,12 +1755,24 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
|
|
||||||
// look for an unused slot in the weights vector
|
// look for an unused slot in the weights vector
|
||||||
glm::vec4& weights = extracted.mesh.clusterWeights[it.value()];
|
glm::vec4& weights = extracted.mesh.clusterWeights[it.value()];
|
||||||
for (int k = 0; k < 4; k++) {
|
int lowestIndex = -1;
|
||||||
|
float lowestWeight = FLT_MAX;
|
||||||
|
int k = 0;
|
||||||
|
for (; k < 4; k++) {
|
||||||
if (weights[k] == 0.0f) {
|
if (weights[k] == 0.0f) {
|
||||||
extracted.mesh.clusterIndices[it.value()][k] = i;
|
extracted.mesh.clusterIndices[it.value()][k] = i;
|
||||||
weights[k] = weight;
|
weights[k] = weight;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (weights[k] < lowestWeight) {
|
||||||
|
lowestIndex = k;
|
||||||
|
lowestWeight = weights[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (k == 4) {
|
||||||
|
// no space for an additional weight; we must replace the lowest
|
||||||
|
weights[lowestIndex] = weight;
|
||||||
|
extracted.mesh.clusterIndices[it.value()][lowestIndex] = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1764,6 +1781,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
maxJointIndex = jointIndex;
|
maxJointIndex = jointIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// normalize the weights if they don't add up to one
|
||||||
|
for (int i = 0; i < extracted.mesh.clusterWeights.size(); i++) {
|
||||||
|
glm::vec4& weights = extracted.mesh.clusterWeights[i];
|
||||||
|
float total = weights.x + weights.y + weights.z + weights.w;
|
||||||
|
if (total != 1.0f && total != 0.0f) {
|
||||||
|
weights /= total;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int jointIndex = maxJointIndex;
|
int jointIndex = maxJointIndex;
|
||||||
FBXJoint& joint = geometry.joints[jointIndex];
|
FBXJoint& joint = geometry.joints[jointIndex];
|
||||||
|
@ -2042,6 +2067,7 @@ FBXGeometry readSVO(const QByteArray& model) {
|
||||||
FBXMeshPart part;
|
FBXMeshPart part;
|
||||||
part.diffuseColor = glm::vec3(1.0f, 1.0f, 1.0f);
|
part.diffuseColor = glm::vec3(1.0f, 1.0f, 1.0f);
|
||||||
part.shininess = 96.0f;
|
part.shininess = 96.0f;
|
||||||
|
part.opacity = 1.0f;
|
||||||
mesh.parts.append(part);
|
mesh.parts.append(part);
|
||||||
|
|
||||||
VoxelTree tree;
|
VoxelTree tree;
|
||||||
|
|
|
@ -109,6 +109,7 @@ public:
|
||||||
glm::vec3 diffuseColor;
|
glm::vec3 diffuseColor;
|
||||||
glm::vec3 specularColor;
|
glm::vec3 specularColor;
|
||||||
float shininess;
|
float shininess;
|
||||||
|
float opacity;
|
||||||
|
|
||||||
FBXTexture diffuseTexture;
|
FBXTexture diffuseTexture;
|
||||||
FBXTexture normalTexture;
|
FBXTexture normalTexture;
|
||||||
|
|
|
@ -28,7 +28,7 @@ QString AddressManager::pathForPositionAndOrientation(const glm::vec3& position,
|
||||||
QString pathString = "/" + createByteArray(position);
|
QString pathString = "/" + createByteArray(position);
|
||||||
|
|
||||||
if (hasOrientation) {
|
if (hasOrientation) {
|
||||||
QString orientationString = createByteArray(glm::degrees(safeEulerAngles(orientation)));
|
QString orientationString = createByteArray(orientation);
|
||||||
pathString += "/" + orientationString;
|
pathString += "/" + orientationString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,25 +61,25 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) {
|
||||||
// 3. location string (posX,posY,posZ/eulerX,eulerY,eulerZ)
|
// 3. location string (posX,posY,posZ/eulerX,eulerY,eulerZ)
|
||||||
// 4. domain network address (IP or dns resolvable hostname)
|
// 4. domain network address (IP or dns resolvable hostname)
|
||||||
|
|
||||||
if (lookupUrl.isRelative()) {
|
// use our regex'ed helpers to figure out what we're supposed to do with this
|
||||||
// if this is a relative path then handle it as a relative viewpoint
|
if (!handleUsername(lookupUrl.authority())) {
|
||||||
handleRelativeViewpoint(lookupUrl.path());
|
// we're assuming this is either a network address or global place name
|
||||||
} else {
|
// check if it is a network address first
|
||||||
// use our regex'ed helpers to figure out what we're supposed to do with this
|
if (!handleNetworkAddress(lookupUrl.host())) {
|
||||||
if (!handleUsername(lookupUrl.authority())) {
|
// wasn't an address - lookup the place name
|
||||||
// we're assuming this is either a network address or global place name
|
attemptPlaceNameLookup(lookupUrl.host());
|
||||||
// check if it is a network address first
|
|
||||||
if (!handleNetworkAddress(lookupUrl.host())) {
|
|
||||||
// wasn't an address - lookup the place name
|
|
||||||
attemptPlaceNameLookup(lookupUrl.host());
|
|
||||||
}
|
|
||||||
|
|
||||||
// we may have a path that defines a relative viewpoint - if so we should jump to that now
|
|
||||||
handleRelativeViewpoint(lookupUrl.path());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we may have a path that defines a relative viewpoint - if so we should jump to that now
|
||||||
|
handleRelativeViewpoint(lookupUrl.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
} else if (lookupUrl.toString().startsWith('/')) {
|
||||||
|
qDebug() << "Going to relative path" << lookupUrl.path();
|
||||||
|
|
||||||
|
// if this is a relative path then handle it as a relative viewpoint
|
||||||
|
handleRelativeViewpoint(lookupUrl.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -89,10 +89,18 @@ void AddressManager::handleLookupString(const QString& lookupString) {
|
||||||
if (!lookupString.isEmpty()) {
|
if (!lookupString.isEmpty()) {
|
||||||
// make this a valid hifi URL and handle it off to handleUrl
|
// make this a valid hifi URL and handle it off to handleUrl
|
||||||
QString sanitizedString = lookupString;
|
QString sanitizedString = lookupString;
|
||||||
const QRegExp HIFI_SCHEME_REGEX = QRegExp(HIFI_URL_SCHEME + ":\\/{1,2}", Qt::CaseInsensitive);
|
QUrl lookupURL;
|
||||||
sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX);
|
|
||||||
|
|
||||||
handleUrl(QUrl(HIFI_URL_SCHEME + "://" + sanitizedString));
|
if (!lookupString.startsWith('/')) {
|
||||||
|
const QRegExp HIFI_SCHEME_REGEX = QRegExp(HIFI_URL_SCHEME + ":\\/{1,2}", Qt::CaseInsensitive);
|
||||||
|
sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX);
|
||||||
|
|
||||||
|
lookupURL = QUrl(HIFI_URL_SCHEME + "://" + sanitizedString);
|
||||||
|
} else {
|
||||||
|
lookupURL = QUrl(lookupString);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUrl(lookupURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +132,11 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
|
||||||
returnedPath = domainObject[LOCATION_KEY].toObject()[LOCATION_PATH_KEY].toString();
|
returnedPath = domainObject[LOCATION_KEY].toObject()[LOCATION_PATH_KEY].toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool shouldFaceViewpoint = dataObject.contains(ADDRESS_API_ONLINE_KEY);
|
||||||
|
|
||||||
if (!returnedPath.isEmpty()) {
|
if (!returnedPath.isEmpty()) {
|
||||||
// try to parse this returned path as a viewpoint, that's the only thing it could be for now
|
// try to parse this returned path as a viewpoint, that's the only thing it could be for now
|
||||||
if (!handleRelativeViewpoint(returnedPath)) {
|
if (!handleRelativeViewpoint(returnedPath, shouldFaceViewpoint)) {
|
||||||
qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath;
|
qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,38 +193,47 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AddressManager::handleRelativeViewpoint(const QString& lookupString) {
|
bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool shouldFace) {
|
||||||
const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)";
|
const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)";
|
||||||
const QString TRIPLE_FLOAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + "\\s*,\\s*" +
|
const QString SPACED_COMMA_REGEX_STRING = "\\s*,\\s*";
|
||||||
FLOAT_REGEX_STRING + "\\s*,\\s*" + FLOAT_REGEX_STRING + "\\s*(?:$|\\/)";
|
const QString POSITION_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING +
|
||||||
|
FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + "\\s*(?:$|\\/)";
|
||||||
|
const QString QUAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING +
|
||||||
|
FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING +
|
||||||
|
FLOAT_REGEX_STRING + "\\s*$";
|
||||||
|
|
||||||
QRegExp tripleFloatRegex(TRIPLE_FLOAT_REGEX_STRING);
|
QRegExp positionRegex(POSITION_REGEX_STRING);
|
||||||
|
|
||||||
if (tripleFloatRegex.indexIn(lookupString) != -1) {
|
if (positionRegex.indexIn(lookupString) != -1) {
|
||||||
// we have at least a position, so emit our signal to say we need to change position
|
// we have at least a position, so emit our signal to say we need to change position
|
||||||
glm::vec3 newPosition(tripleFloatRegex.cap(1).toFloat(),
|
glm::vec3 newPosition(positionRegex.cap(1).toFloat(),
|
||||||
tripleFloatRegex.cap(2).toFloat(),
|
positionRegex.cap(2).toFloat(),
|
||||||
tripleFloatRegex.cap(3).toFloat());
|
positionRegex.cap(3).toFloat());
|
||||||
|
|
||||||
if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) {
|
if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) {
|
||||||
glm::vec3 newOrientation;
|
glm::quat newOrientation;
|
||||||
|
|
||||||
|
QRegExp orientationRegex(QUAT_REGEX_STRING);
|
||||||
|
|
||||||
// we may also have an orientation
|
// we may also have an orientation
|
||||||
if (lookupString[tripleFloatRegex.matchedLength() - 1] == QChar('/')
|
if (lookupString[positionRegex.matchedLength() - 1] == QChar('/')
|
||||||
&& tripleFloatRegex.indexIn(lookupString, tripleFloatRegex.matchedLength() - 1) != -1) {
|
&& orientationRegex.indexIn(lookupString, positionRegex.matchedLength() - 1) != -1) {
|
||||||
|
|
||||||
glm::vec3 newOrientation(tripleFloatRegex.cap(1).toFloat(),
|
glm::quat newOrientation = glm::normalize(glm::quat(orientationRegex.cap(4).toFloat(),
|
||||||
tripleFloatRegex.cap(2).toFloat(),
|
orientationRegex.cap(1).toFloat(),
|
||||||
tripleFloatRegex.cap(3).toFloat());
|
orientationRegex.cap(2).toFloat(),
|
||||||
|
orientationRegex.cap(3).toFloat()));
|
||||||
|
|
||||||
if (!isNaN(newOrientation.x) && !isNaN(newOrientation.y) && !isNaN(newOrientation.z)) {
|
if (!isNaN(newOrientation.x) && !isNaN(newOrientation.y) && !isNaN(newOrientation.z)
|
||||||
emit locationChangeRequired(newPosition, true, newOrientation);
|
&& !isNaN(newOrientation.w)) {
|
||||||
|
emit locationChangeRequired(newPosition, true, newOrientation, shouldFace);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Orientation parsed from lookup string is invalid. Will not use for location change.";
|
qDebug() << "Orientation parsed from lookup string is invalid. Will not use for location change.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit locationChangeRequired(newPosition, false, newOrientation);
|
emit locationChangeRequired(newPosition, false, newOrientation, shouldFace);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Could not jump to position from lookup string because it has an invalid value.";
|
qDebug() << "Could not jump to position from lookup string because it has an invalid value.";
|
||||||
|
|
|
@ -40,14 +40,16 @@ signals:
|
||||||
void lookupResultIsOffline();
|
void lookupResultIsOffline();
|
||||||
void lookupResultIsNotFound();
|
void lookupResultIsNotFound();
|
||||||
void possibleDomainChangeRequired(const QString& newHostname);
|
void possibleDomainChangeRequired(const QString& newHostname);
|
||||||
void locationChangeRequired(const glm::vec3& newPosition, bool hasOrientationChange, const glm::vec3& newOrientation);
|
void locationChangeRequired(const glm::vec3& newPosition,
|
||||||
|
bool hasOrientationChange, const glm::quat& newOrientation,
|
||||||
|
bool shouldFaceLocation);
|
||||||
private:
|
private:
|
||||||
const JSONCallbackParameters& apiCallbackParameters();
|
const JSONCallbackParameters& apiCallbackParameters();
|
||||||
|
|
||||||
bool handleUrl(const QUrl& lookupUrl);
|
bool handleUrl(const QUrl& lookupUrl);
|
||||||
|
|
||||||
bool handleNetworkAddress(const QString& lookupString);
|
bool handleNetworkAddress(const QString& lookupString);
|
||||||
bool handleRelativeViewpoint(const QString& pathSubsection);
|
bool handleRelativeViewpoint(const QString& pathSubsection, bool shouldFace = false);
|
||||||
bool handleUsername(const QString& lookupString);
|
bool handleUsername(const QString& lookupString);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
|
||||||
_nodeHashMutex(QMutex::Recursive),
|
_nodeHashMutex(QMutex::Recursive),
|
||||||
_nodeSocket(this),
|
_nodeSocket(this),
|
||||||
_dtlsSocket(NULL),
|
_dtlsSocket(NULL),
|
||||||
|
_publicSockAddr(),
|
||||||
_numCollectedPackets(0),
|
_numCollectedPackets(0),
|
||||||
_numCollectedBytes(0),
|
_numCollectedBytes(0),
|
||||||
_packetStatTimer()
|
_packetStatTimer()
|
||||||
|
@ -502,3 +503,115 @@ void LimitedNodeList::removeSilentNodes() {
|
||||||
|
|
||||||
_nodeHashMutex.unlock();
|
_nodeHashMutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442;
|
||||||
|
const int NUM_BYTES_STUN_HEADER = 20;
|
||||||
|
|
||||||
|
void LimitedNodeList::sendSTUNRequest() {
|
||||||
|
|
||||||
|
unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER];
|
||||||
|
|
||||||
|
int packetIndex = 0;
|
||||||
|
|
||||||
|
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
|
||||||
|
|
||||||
|
// leading zeros + message type
|
||||||
|
const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001);
|
||||||
|
memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE));
|
||||||
|
packetIndex += sizeof(REQUEST_MESSAGE_TYPE);
|
||||||
|
|
||||||
|
// message length (no additional attributes are included)
|
||||||
|
uint16_t messageLength = 0;
|
||||||
|
memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength));
|
||||||
|
packetIndex += sizeof(messageLength);
|
||||||
|
|
||||||
|
memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER));
|
||||||
|
packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
|
||||||
|
|
||||||
|
// transaction ID (random 12-byte unsigned integer)
|
||||||
|
const uint NUM_TRANSACTION_ID_BYTES = 12;
|
||||||
|
QUuid randomUUID = QUuid::createUuid();
|
||||||
|
memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES);
|
||||||
|
|
||||||
|
// lookup the IP for the STUN server
|
||||||
|
static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT);
|
||||||
|
|
||||||
|
_nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket),
|
||||||
|
stunSockAddr.getAddress(), stunSockAddr.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) {
|
||||||
|
// check the cookie to make sure this is actually a STUN response
|
||||||
|
// and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS
|
||||||
|
const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4;
|
||||||
|
const uint16_t XOR_MAPPED_ADDRESS_TYPE = htons(0x0020);
|
||||||
|
|
||||||
|
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
|
||||||
|
|
||||||
|
int attributeStartIndex = NUM_BYTES_STUN_HEADER;
|
||||||
|
|
||||||
|
if (memcmp(packet.data() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH,
|
||||||
|
&RFC_5389_MAGIC_COOKIE_NETWORK_ORDER,
|
||||||
|
sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) {
|
||||||
|
|
||||||
|
// enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE
|
||||||
|
while (attributeStartIndex < packet.size()) {
|
||||||
|
if (memcmp(packet.data() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) {
|
||||||
|
const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4;
|
||||||
|
const int NUM_BYTES_FAMILY_ALIGN = 1;
|
||||||
|
const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN;
|
||||||
|
|
||||||
|
uint8_t addressFamily = 0;
|
||||||
|
memcpy(&addressFamily, packet.data() + byteIndex, sizeof(addressFamily));
|
||||||
|
|
||||||
|
byteIndex += sizeof(addressFamily);
|
||||||
|
|
||||||
|
if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) {
|
||||||
|
// grab the X-Port
|
||||||
|
uint16_t xorMappedPort = 0;
|
||||||
|
memcpy(&xorMappedPort, packet.data() + byteIndex, sizeof(xorMappedPort));
|
||||||
|
|
||||||
|
uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16);
|
||||||
|
|
||||||
|
byteIndex += sizeof(xorMappedPort);
|
||||||
|
|
||||||
|
// grab the X-Address
|
||||||
|
uint32_t xorMappedAddress = 0;
|
||||||
|
memcpy(&xorMappedAddress, packet.data() + byteIndex, sizeof(xorMappedAddress));
|
||||||
|
|
||||||
|
uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
|
||||||
|
|
||||||
|
QHostAddress newPublicAddress = QHostAddress(stunAddress);
|
||||||
|
|
||||||
|
if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) {
|
||||||
|
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
|
||||||
|
|
||||||
|
qDebug("New public socket received from STUN server is %s:%hu",
|
||||||
|
_publicSockAddr.getAddress().toString().toLocal8Bit().constData(),
|
||||||
|
_publicSockAddr.getPort());
|
||||||
|
|
||||||
|
emit publicSockAddrChanged(_publicSockAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// push forward attributeStartIndex by the length of this attribute
|
||||||
|
const int NUM_BYTES_ATTRIBUTE_TYPE = 2;
|
||||||
|
|
||||||
|
uint16_t attributeLength = 0;
|
||||||
|
memcpy(&attributeLength, packet.data() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE,
|
||||||
|
sizeof(attributeLength));
|
||||||
|
attributeLength = ntohs(attributeLength);
|
||||||
|
|
||||||
|
attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,9 @@ extern const QUrl DEFAULT_NODE_AUTH_URL;
|
||||||
|
|
||||||
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
|
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
|
||||||
|
|
||||||
|
const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io";
|
||||||
|
const unsigned short STUN_SERVER_PORT = 3478;
|
||||||
|
|
||||||
class HifiSockAddr;
|
class HifiSockAddr;
|
||||||
|
|
||||||
typedef QSet<NodeType_t> NodeSet;
|
typedef QSet<NodeType_t> NodeSet;
|
||||||
|
@ -99,6 +102,9 @@ public:
|
||||||
|
|
||||||
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
|
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
|
||||||
void resetPacketStats();
|
void resetPacketStats();
|
||||||
|
|
||||||
|
virtual void sendSTUNRequest();
|
||||||
|
virtual bool processSTUNResponse(const QByteArray& packet);
|
||||||
public slots:
|
public slots:
|
||||||
void reset();
|
void reset();
|
||||||
void eraseAllNodes();
|
void eraseAllNodes();
|
||||||
|
@ -110,6 +116,7 @@ signals:
|
||||||
void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID);
|
void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID);
|
||||||
void nodeAdded(SharedNodePointer);
|
void nodeAdded(SharedNodePointer);
|
||||||
void nodeKilled(SharedNodePointer);
|
void nodeKilled(SharedNodePointer);
|
||||||
|
void publicSockAddrChanged(const HifiSockAddr& publicSockAddr);
|
||||||
protected:
|
protected:
|
||||||
static LimitedNodeList* _sharedInstance;
|
static LimitedNodeList* _sharedInstance;
|
||||||
|
|
||||||
|
@ -130,6 +137,7 @@ protected:
|
||||||
QMutex _nodeHashMutex;
|
QMutex _nodeHashMutex;
|
||||||
QUdpSocket _nodeSocket;
|
QUdpSocket _nodeSocket;
|
||||||
QUdpSocket* _dtlsSocket;
|
QUdpSocket* _dtlsSocket;
|
||||||
|
HifiSockAddr _publicSockAddr;
|
||||||
int _numCollectedPackets;
|
int _numCollectedPackets;
|
||||||
int _numCollectedBytes;
|
int _numCollectedBytes;
|
||||||
QElapsedTimer _packetStatTimer;
|
QElapsedTimer _packetStatTimer;
|
||||||
|
|
|
@ -57,7 +57,6 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
|
||||||
_domainHandler(this),
|
_domainHandler(this),
|
||||||
_numNoReplyDomainCheckIns(0),
|
_numNoReplyDomainCheckIns(0),
|
||||||
_assignmentServerSocket(),
|
_assignmentServerSocket(),
|
||||||
_publicSockAddr(),
|
|
||||||
_hasCompletedInitialSTUNFailure(false),
|
_hasCompletedInitialSTUNFailure(false),
|
||||||
_stunRequestsSinceSuccess(0)
|
_stunRequestsSinceSuccess(0)
|
||||||
{
|
{
|
||||||
|
@ -195,47 +194,16 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes)
|
||||||
_nodeTypesOfInterest.unite(setOfNodeTypes);
|
_nodeTypesOfInterest.unite(setOfNodeTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442;
|
|
||||||
const int NUM_BYTES_STUN_HEADER = 20;
|
|
||||||
const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5;
|
const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5;
|
||||||
|
|
||||||
void NodeList::sendSTUNRequest() {
|
void NodeList::sendSTUNRequest() {
|
||||||
const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io";
|
|
||||||
const unsigned short STUN_SERVER_PORT = 3478;
|
|
||||||
|
|
||||||
unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER];
|
|
||||||
|
|
||||||
int packetIndex = 0;
|
|
||||||
|
|
||||||
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
|
|
||||||
|
|
||||||
// leading zeros + message type
|
|
||||||
const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001);
|
|
||||||
memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE));
|
|
||||||
packetIndex += sizeof(REQUEST_MESSAGE_TYPE);
|
|
||||||
|
|
||||||
// message length (no additional attributes are included)
|
|
||||||
uint16_t messageLength = 0;
|
|
||||||
memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength));
|
|
||||||
packetIndex += sizeof(messageLength);
|
|
||||||
|
|
||||||
memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER));
|
|
||||||
packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
|
|
||||||
|
|
||||||
// transaction ID (random 12-byte unsigned integer)
|
|
||||||
const uint NUM_TRANSACTION_ID_BYTES = 12;
|
|
||||||
QUuid randomUUID = QUuid::createUuid();
|
|
||||||
memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES);
|
|
||||||
|
|
||||||
// lookup the IP for the STUN server
|
|
||||||
static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT);
|
|
||||||
|
|
||||||
if (!_hasCompletedInitialSTUNFailure) {
|
if (!_hasCompletedInitialSTUNFailure) {
|
||||||
qDebug("Sending intial stun request to %s", stunSockAddr.getAddress().toString().toLocal8Bit().constData());
|
qDebug() << "Sending intial stun request to" << STUN_SERVER_HOSTNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
_nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket),
|
LimitedNodeList::sendSTUNRequest();
|
||||||
stunSockAddr.getAddress(), stunSockAddr.getPort());
|
|
||||||
|
|
||||||
_stunRequestsSinceSuccess++;
|
_stunRequestsSinceSuccess++;
|
||||||
|
|
||||||
|
@ -255,79 +223,16 @@ void NodeList::sendSTUNRequest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::processSTUNResponse(const QByteArray& packet) {
|
bool NodeList::processSTUNResponse(const QByteArray& packet) {
|
||||||
// check the cookie to make sure this is actually a STUN response
|
if (LimitedNodeList::processSTUNResponse(packet)) {
|
||||||
// and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS
|
// reset the number of failed STUN requests since last success
|
||||||
const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4;
|
_stunRequestsSinceSuccess = 0;
|
||||||
const uint16_t XOR_MAPPED_ADDRESS_TYPE = htons(0x0020);
|
|
||||||
|
_hasCompletedInitialSTUNFailure = true;
|
||||||
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
|
|
||||||
|
return true;
|
||||||
int attributeStartIndex = NUM_BYTES_STUN_HEADER;
|
} else {
|
||||||
|
return false;
|
||||||
if (memcmp(packet.data() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH,
|
|
||||||
&RFC_5389_MAGIC_COOKIE_NETWORK_ORDER,
|
|
||||||
sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) {
|
|
||||||
|
|
||||||
// enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE
|
|
||||||
while (attributeStartIndex < packet.size()) {
|
|
||||||
if (memcmp(packet.data() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) {
|
|
||||||
const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4;
|
|
||||||
const int NUM_BYTES_FAMILY_ALIGN = 1;
|
|
||||||
const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8;
|
|
||||||
|
|
||||||
// reset the number of failed STUN requests since last success
|
|
||||||
_stunRequestsSinceSuccess = 0;
|
|
||||||
|
|
||||||
int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN;
|
|
||||||
|
|
||||||
uint8_t addressFamily = 0;
|
|
||||||
memcpy(&addressFamily, packet.data() + byteIndex, sizeof(addressFamily));
|
|
||||||
|
|
||||||
byteIndex += sizeof(addressFamily);
|
|
||||||
|
|
||||||
if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) {
|
|
||||||
// grab the X-Port
|
|
||||||
uint16_t xorMappedPort = 0;
|
|
||||||
memcpy(&xorMappedPort, packet.data() + byteIndex, sizeof(xorMappedPort));
|
|
||||||
|
|
||||||
uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16);
|
|
||||||
|
|
||||||
byteIndex += sizeof(xorMappedPort);
|
|
||||||
|
|
||||||
// grab the X-Address
|
|
||||||
uint32_t xorMappedAddress = 0;
|
|
||||||
memcpy(&xorMappedAddress, packet.data() + byteIndex, sizeof(xorMappedAddress));
|
|
||||||
|
|
||||||
uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
|
|
||||||
|
|
||||||
QHostAddress newPublicAddress = QHostAddress(stunAddress);
|
|
||||||
|
|
||||||
if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) {
|
|
||||||
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
|
|
||||||
|
|
||||||
qDebug("New public socket received from STUN server is %s:%hu",
|
|
||||||
_publicSockAddr.getAddress().toString().toLocal8Bit().constData(),
|
|
||||||
_publicSockAddr.getPort());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_hasCompletedInitialSTUNFailure = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// push forward attributeStartIndex by the length of this attribute
|
|
||||||
const int NUM_BYTES_ATTRIBUTE_TYPE = 2;
|
|
||||||
|
|
||||||
uint16_t attributeLength = 0;
|
|
||||||
memcpy(&attributeLength, packet.data() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE,
|
|
||||||
sizeof(attributeLength));
|
|
||||||
attributeLength = ntohs(attributeLength);
|
|
||||||
|
|
||||||
attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,8 +89,9 @@ private:
|
||||||
NodeList(char ownerType, unsigned short socketListenPort, unsigned short dtlsListenPort);
|
NodeList(char ownerType, unsigned short socketListenPort, unsigned short dtlsListenPort);
|
||||||
NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton
|
NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton
|
||||||
void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton
|
void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton
|
||||||
|
|
||||||
void sendSTUNRequest();
|
void sendSTUNRequest();
|
||||||
void processSTUNResponse(const QByteArray& packet);
|
bool processSTUNResponse(const QByteArray& packet);
|
||||||
|
|
||||||
void processDomainServerAuthRequest(const QByteArray& packet);
|
void processDomainServerAuthRequest(const QByteArray& packet);
|
||||||
void requestAuthForDomainServer();
|
void requestAuthForDomainServer();
|
||||||
|
@ -102,7 +103,6 @@ private:
|
||||||
DomainHandler _domainHandler;
|
DomainHandler _domainHandler;
|
||||||
int _numNoReplyDomainCheckIns;
|
int _numNoReplyDomainCheckIns;
|
||||||
HifiSockAddr _assignmentServerSocket;
|
HifiSockAddr _assignmentServerSocket;
|
||||||
HifiSockAddr _publicSockAddr;
|
|
||||||
bool _hasCompletedInitialSTUNFailure;
|
bool _hasCompletedInitialSTUNFailure;
|
||||||
unsigned int _stunRequestsSinceSuccess;
|
unsigned int _stunRequestsSinceSuccess;
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,7 +51,7 @@ PacketVersion versionForPacketType(PacketType type) {
|
||||||
case PacketTypeMicrophoneAudioWithEcho:
|
case PacketTypeMicrophoneAudioWithEcho:
|
||||||
return 2;
|
return 2;
|
||||||
case PacketTypeSilentAudioFrame:
|
case PacketTypeSilentAudioFrame:
|
||||||
return 3;
|
return 4;
|
||||||
case PacketTypeMixedAudio:
|
case PacketTypeMixedAudio:
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeAvatarData:
|
case PacketTypeAvatarData:
|
||||||
|
|
|
@ -505,6 +505,11 @@ void ScriptEngine::run() {
|
||||||
// write the number of silent samples so the audio-mixer can uphold timing
|
// write the number of silent samples so the audio-mixer can uphold timing
|
||||||
packetStream.writeRawData(reinterpret_cast<const char*>(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t));
|
packetStream.writeRawData(reinterpret_cast<const char*>(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t));
|
||||||
|
|
||||||
|
// use the orientation and position of this avatar for the source of this audio
|
||||||
|
packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));
|
||||||
|
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
||||||
|
packetStream.writeRawData(reinterpret_cast<const char*>(&headOrientation), sizeof(glm::quat));
|
||||||
|
|
||||||
} else if (nextSoundOutput) {
|
} else if (nextSoundOutput) {
|
||||||
// assume scripted avatar audio is mono and set channel flag to zero
|
// assume scripted avatar audio is mono and set channel flag to zero
|
||||||
packetStream << (quint8)0;
|
packetStream << (quint8)0;
|
||||||
|
@ -609,6 +614,7 @@ void ScriptEngine::timerFired() {
|
||||||
|
|
||||||
if (!callingTimer->isActive()) {
|
if (!callingTimer->isActive()) {
|
||||||
// this timer is done, we can kill it
|
// this timer is done, we can kill it
|
||||||
|
_timerFunctionMap.remove(callingTimer);
|
||||||
delete callingTimer;
|
delete callingTimer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,6 +284,11 @@ QByteArray createByteArray(const glm::vec3& vector) {
|
||||||
return QByteArray::number(vector.x) + ',' + QByteArray::number(vector.y) + ',' + QByteArray::number(vector.z);
|
return QByteArray::number(vector.x) + ',' + QByteArray::number(vector.y) + ',' + QByteArray::number(vector.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray createByteArray(const glm::quat& quat) {
|
||||||
|
return QByteArray::number(quat.x) + ',' + QByteArray::number(quat.y) + "," + QByteArray::number(quat.z) + ","
|
||||||
|
+ QByteArray::number(quat.w);
|
||||||
|
}
|
||||||
|
|
||||||
bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientionB, float similarEnough) {
|
bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientionB, float similarEnough) {
|
||||||
// Compute the angular distance between the two orientations
|
// Compute the angular distance between the two orientations
|
||||||
float angleOrientation = orientionA == orientionB ? 0.0f : glm::degrees(glm::angle(orientionA * glm::inverse(orientionB)));
|
float angleOrientation = orientionA == orientionB ? 0.0f : glm::degrees(glm::angle(orientionA * glm::inverse(orientionB)));
|
||||||
|
|
|
@ -77,6 +77,7 @@ float extractUniformScale(const glm::mat4& matrix);
|
||||||
float extractUniformScale(const glm::vec3& scale);
|
float extractUniformScale(const glm::vec3& scale);
|
||||||
|
|
||||||
QByteArray createByteArray(const glm::vec3& vector);
|
QByteArray createByteArray(const glm::vec3& vector);
|
||||||
|
QByteArray createByteArray(const glm::quat& quat);
|
||||||
|
|
||||||
/// \return bool are two orientations similar to each other
|
/// \return bool are two orientations similar to each other
|
||||||
const float ORIENTATION_SIMILAR_ENOUGH = 5.0f; // 10 degrees in any direction
|
const float ORIENTATION_SIMILAR_ENOUGH = 5.0f; // 10 degrees in any direction
|
||||||
|
|
Loading…
Reference in a new issue