mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 19:59:28 +02:00
merging
This commit is contained in:
commit
2975f73fc9
37 changed files with 307 additions and 487 deletions
|
@ -316,6 +316,10 @@ void AudioMixer::sendStatsPacket() {
|
||||||
addTiming(_mixTiming, "mix");
|
addTiming(_mixTiming, "mix");
|
||||||
addTiming(_eventsTiming, "events");
|
addTiming(_eventsTiming, "events");
|
||||||
|
|
||||||
|
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||||
|
timingStats["ns_per_throttle"] = (_stats.totalMixes > 0) ? (float)(_stats.throttleTime / _stats.totalMixes) : 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
// call it "avg_..." to keep it higher in the display, sorted alphabetically
|
// call it "avg_..." to keep it higher in the display, sorted alphabetically
|
||||||
statsObject["avg_timing_stats"] = timingStats;
|
statsObject["avg_timing_stats"] = timingStats;
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,12 @@ void sendMutePacket(const SharedNodePointer& node, AudioMixerClientData&);
|
||||||
void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData& data);
|
void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData& data);
|
||||||
|
|
||||||
// mix helpers
|
// mix helpers
|
||||||
bool shouldIgnoreNode(const SharedNodePointer& listener, const SharedNodePointer& node);
|
inline bool shouldIgnoreNode(const SharedNodePointer& listener, const SharedNodePointer& node);
|
||||||
float gainForSource(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||||
|
const glm::vec3& relativePosition);
|
||||||
|
inline float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||||
const glm::vec3& relativePosition, bool isEcho);
|
const glm::vec3& relativePosition, bool isEcho);
|
||||||
float azimuthForSource(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||||
const glm::vec3& relativePosition);
|
const glm::vec3& relativePosition);
|
||||||
|
|
||||||
void AudioMixerSlave::configure(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) {
|
void AudioMixerSlave::configure(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) {
|
||||||
|
@ -126,9 +128,10 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
||||||
AudioMixerClientData&, const QUuid&, const AvatarAudioStream&, const PositionalAudioStream&);
|
AudioMixerClientData&, const QUuid&, const AvatarAudioStream&, const PositionalAudioStream&);
|
||||||
auto allStreams = [&](const SharedNodePointer& node, MixFunctor mixFunctor) {
|
auto allStreams = [&](const SharedNodePointer& node, MixFunctor mixFunctor) {
|
||||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||||
|
auto nodeID = node->getUUID();
|
||||||
for (auto& streamPair : nodeData->getAudioStreams()) {
|
for (auto& streamPair : nodeData->getAudioStreams()) {
|
||||||
auto nodeStream = streamPair.second;
|
auto nodeStream = streamPair.second;
|
||||||
(this->*mixFunctor)(*listenerData, node->getUUID(), *listenerAudioStream, *nodeStream);
|
(this->*mixFunctor)(*listenerData, nodeID, *listenerAudioStream, *nodeStream);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -147,14 +150,28 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
||||||
if (!isThrottling) {
|
if (!isThrottling) {
|
||||||
allStreams(node, &AudioMixerSlave::mixStream);
|
allStreams(node, &AudioMixerSlave::mixStream);
|
||||||
} else {
|
} else {
|
||||||
|
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||||
|
auto throttleStart = p_high_resolution_clock::now();
|
||||||
|
#endif
|
||||||
|
|
||||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||||
|
auto nodeID = node->getUUID();
|
||||||
|
|
||||||
// compute the node's max relative volume
|
// compute the node's max relative volume
|
||||||
float nodeVolume;
|
float nodeVolume;
|
||||||
for (auto& streamPair : nodeData->getAudioStreams()) {
|
for (auto& streamPair : nodeData->getAudioStreams()) {
|
||||||
auto nodeStream = streamPair.second;
|
auto nodeStream = streamPair.second;
|
||||||
float distance = glm::length(nodeStream->getPosition() - listenerAudioStream->getPosition());
|
|
||||||
nodeVolume = std::max(nodeStream->getLastPopOutputTrailingLoudness() / distance, nodeVolume);
|
// approximate the gain
|
||||||
|
glm::vec3 relativePosition = nodeStream->getPosition() - listenerAudioStream->getPosition();
|
||||||
|
float gain = approximateGain(*listenerAudioStream, *nodeStream, relativePosition);
|
||||||
|
|
||||||
|
// modify by hrtf gain adjustment
|
||||||
|
auto& hrtf = listenerData->hrtfForStream(nodeID, nodeStream->getStreamIdentifier());
|
||||||
|
gain *= hrtf.getGainAdjustment();
|
||||||
|
|
||||||
|
auto streamVolume = nodeStream->getLastPopOutputTrailingLoudness() * gain;
|
||||||
|
nodeVolume = std::max(streamVolume, nodeVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
// max-heapify the nodes by relative volume
|
// max-heapify the nodes by relative volume
|
||||||
|
@ -162,6 +179,13 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
||||||
if (!throttledNodes.empty()) {
|
if (!throttledNodes.empty()) {
|
||||||
std::push_heap(throttledNodes.begin(), throttledNodes.end());
|
std::push_heap(throttledNodes.begin(), throttledNodes.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||||
|
auto throttleEnd = p_high_resolution_clock::now();
|
||||||
|
uint64_t throttleTime =
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(throttleEnd - throttleStart).count();
|
||||||
|
stats.throttleTime += throttleTime;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -227,9 +251,9 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
|
||||||
glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition();
|
glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition();
|
||||||
|
|
||||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||||
float gain = gainForSource(listeningNodeStream, streamToAdd, relativePosition, isEcho);
|
float gain = computeGain(listeningNodeStream, streamToAdd, relativePosition, isEcho);
|
||||||
float azimuth = isEcho ? 0.0f : azimuthForSource(listeningNodeStream, listeningNodeStream, relativePosition);
|
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
|
||||||
static const int HRTF_DATASET_INDEX = 1;
|
const int HRTF_DATASET_INDEX = 1;
|
||||||
|
|
||||||
if (!streamToAdd.lastPopSucceeded()) {
|
if (!streamToAdd.lastPopSucceeded()) {
|
||||||
bool forceSilentBlock = true;
|
bool forceSilentBlock = true;
|
||||||
|
@ -330,7 +354,7 @@ std::unique_ptr<NLPacket> createAudioPacket(PacketType type, int size, quint16 s
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMixPacket(const SharedNodePointer& node, AudioMixerClientData& data, QByteArray& buffer) {
|
void sendMixPacket(const SharedNodePointer& node, AudioMixerClientData& data, QByteArray& buffer) {
|
||||||
static const int MIX_PACKET_SIZE =
|
const int MIX_PACKET_SIZE =
|
||||||
sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE + AudioConstants::NETWORK_FRAME_BYTES_STEREO;
|
sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE + AudioConstants::NETWORK_FRAME_BYTES_STEREO;
|
||||||
quint16 sequence = data.getOutgoingSequenceNumber();
|
quint16 sequence = data.getOutgoingSequenceNumber();
|
||||||
QString codec = data.getCodecName();
|
QString codec = data.getCodecName();
|
||||||
|
@ -345,7 +369,7 @@ void sendMixPacket(const SharedNodePointer& node, AudioMixerClientData& data, QB
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendSilentPacket(const SharedNodePointer& node, AudioMixerClientData& data) {
|
void sendSilentPacket(const SharedNodePointer& node, AudioMixerClientData& data) {
|
||||||
static const int SILENT_PACKET_SIZE =
|
const int SILENT_PACKET_SIZE =
|
||||||
sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE + sizeof(quint16);
|
sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE + sizeof(quint16);
|
||||||
quint16 sequence = data.getOutgoingSequenceNumber();
|
quint16 sequence = data.getOutgoingSequenceNumber();
|
||||||
QString codec = data.getCodecName();
|
QString codec = data.getCodecName();
|
||||||
|
@ -475,40 +499,54 @@ bool shouldIgnoreNode(const SharedNodePointer& listener, const SharedNodePointer
|
||||||
return ignore;
|
return ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
float gainForSource(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
static const float ATTENUATION_START_DISTANCE = 1.0f;
|
||||||
const glm::vec3& relativePosition, bool isEcho) {
|
|
||||||
|
float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||||
|
const glm::vec3& relativePosition) {
|
||||||
float gain = 1.0f;
|
float gain = 1.0f;
|
||||||
|
|
||||||
float distanceBetween = glm::length(relativePosition);
|
// injector: apply attenuation
|
||||||
|
|
||||||
if (distanceBetween < EPSILON) {
|
|
||||||
distanceBetween = EPSILON;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamToAdd.getType() == PositionalAudioStream::Injector) {
|
if (streamToAdd.getType() == PositionalAudioStream::Injector) {
|
||||||
gain *= reinterpret_cast<const InjectedAudioStream*>(&streamToAdd)->getAttenuationRatio();
|
gain *= reinterpret_cast<const InjectedAudioStream*>(&streamToAdd)->getAttenuationRatio();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEcho && (streamToAdd.getType() == PositionalAudioStream::Microphone)) {
|
// avatar: skip attenuation - it is too costly to approximate
|
||||||
// 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;
|
|
||||||
|
|
||||||
|
// distance attenuation: approximate, ignore zone-specific attenuations
|
||||||
|
// this is a good approximation for streams further than ATTENUATION_START_DISTANCE
|
||||||
|
// those streams closer will be amplified; amplifying close streams is acceptable
|
||||||
|
// when throttling, as close streams are expected to be heard by a user
|
||||||
|
float distance = glm::length(relativePosition);
|
||||||
|
return gain / distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||||
|
const glm::vec3& relativePosition, bool isEcho) {
|
||||||
|
float gain = 1.0f;
|
||||||
|
|
||||||
|
// injector: apply attenuation
|
||||||
|
if (streamToAdd.getType() == PositionalAudioStream::Injector) {
|
||||||
|
gain *= reinterpret_cast<const InjectedAudioStream*>(&streamToAdd)->getAttenuationRatio();
|
||||||
|
|
||||||
|
// avatar: apply fixed off-axis attenuation to make them quieter as they turn away
|
||||||
|
} else if (!isEcho && (streamToAdd.getType() == PositionalAudioStream::Microphone)) {
|
||||||
|
glm::vec3 rotatedListenerPosition = glm::inverse(streamToAdd.getOrientation()) * relativePosition;
|
||||||
float angleOfDelivery = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f),
|
float angleOfDelivery = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f),
|
||||||
glm::normalize(rotatedListenerPosition));
|
glm::normalize(rotatedListenerPosition));
|
||||||
|
|
||||||
const float MAX_OFF_AXIS_ATTENUATION = 0.2f;
|
const float MAX_OFF_AXIS_ATTENUATION = 0.2f;
|
||||||
const float OFF_AXIS_ATTENUATION_FORMULA_STEP = (1 - MAX_OFF_AXIS_ATTENUATION) / 2.0f;
|
const float OFF_AXIS_ATTENUATION_STEP = (1 - MAX_OFF_AXIS_ATTENUATION) / 2.0f;
|
||||||
|
|
||||||
float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION +
|
float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION +
|
||||||
(OFF_AXIS_ATTENUATION_FORMULA_STEP * (angleOfDelivery / PI_OVER_TWO));
|
(angleOfDelivery * (OFF_AXIS_ATTENUATION_STEP / PI_OVER_TWO));
|
||||||
|
|
||||||
// multiply the current attenuation coefficient by the calculated off axis coefficient
|
|
||||||
gain *= offAxisCoefficient;
|
gain *= offAxisCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float attenuationPerDoublingInDistance = AudioMixer::getAttenuationPerDoublingInDistance();
|
|
||||||
auto& zoneSettings = AudioMixer::getZoneSettings();
|
|
||||||
auto& audioZones = AudioMixer::getAudioZones();
|
auto& audioZones = AudioMixer::getAudioZones();
|
||||||
|
auto& zoneSettings = AudioMixer::getZoneSettings();
|
||||||
|
|
||||||
|
// find distance attenuation coefficient
|
||||||
|
float attenuationPerDoublingInDistance = AudioMixer::getAttenuationPerDoublingInDistance();
|
||||||
for (int i = 0; i < zoneSettings.length(); ++i) {
|
for (int i = 0; i < zoneSettings.length(); ++i) {
|
||||||
if (audioZones[zoneSettings[i].source].contains(streamToAdd.getPosition()) &&
|
if (audioZones[zoneSettings[i].source].contains(streamToAdd.getPosition()) &&
|
||||||
audioZones[zoneSettings[i].listener].contains(listeningNodeStream.getPosition())) {
|
audioZones[zoneSettings[i].listener].contains(listeningNodeStream.getPosition())) {
|
||||||
|
@ -517,16 +555,17 @@ float gainForSource(const AvatarAudioStream& listeningNodeStream, const Position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
|
// distance attenuation
|
||||||
if (distanceBetween >= ATTENUATION_BEGINS_AT_DISTANCE) {
|
float distance = glm::length(relativePosition);
|
||||||
|
assert(ATTENUATION_START_DISTANCE > EPSILON);
|
||||||
|
if (distance >= ATTENUATION_START_DISTANCE) {
|
||||||
|
|
||||||
// translate the zone setting to gain per log2(distance)
|
// translate the zone setting to gain per log2(distance)
|
||||||
float g = 1.0f - attenuationPerDoublingInDistance;
|
float g = 1.0f - attenuationPerDoublingInDistance;
|
||||||
g = (g < EPSILON) ? EPSILON : g;
|
g = glm::clamp(g, EPSILON, 1.0f);
|
||||||
g = (g > 1.0f) ? 1.0f : g;
|
|
||||||
|
|
||||||
// calculate the distance coefficient using the distance to this node
|
// calculate the distance coefficient using the distance to this node
|
||||||
float distanceCoefficient = fastExp2f(fastLog2f(g) * fastLog2f(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE));
|
float distanceCoefficient = fastExp2f(fastLog2f(g) * fastLog2f(distance/ATTENUATION_START_DISTANCE));
|
||||||
|
|
||||||
// multiply the current attenuation coefficient by the distance coefficient
|
// multiply the current attenuation coefficient by the distance coefficient
|
||||||
gain *= distanceCoefficient;
|
gain *= distanceCoefficient;
|
||||||
|
@ -535,7 +574,7 @@ float gainForSource(const AvatarAudioStream& listeningNodeStream, const Position
|
||||||
return gain;
|
return gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
float azimuthForSource(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||||
const glm::vec3& relativePosition) {
|
const glm::vec3& relativePosition) {
|
||||||
glm::quat inverseOrientation = glm::inverse(listeningNodeStream.getOrientation());
|
glm::quat inverseOrientation = glm::inverse(listeningNodeStream.getOrientation());
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,9 @@ void AudioMixerStats::reset() {
|
||||||
hrtfThrottleRenders = 0;
|
hrtfThrottleRenders = 0;
|
||||||
manualStereoMixes = 0;
|
manualStereoMixes = 0;
|
||||||
manualEchoMixes = 0;
|
manualEchoMixes = 0;
|
||||||
|
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||||
|
throttleTime = 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixerStats::accumulate(const AudioMixerStats& otherStats) {
|
void AudioMixerStats::accumulate(const AudioMixerStats& otherStats) {
|
||||||
|
@ -31,4 +34,7 @@ void AudioMixerStats::accumulate(const AudioMixerStats& otherStats) {
|
||||||
hrtfThrottleRenders += otherStats.hrtfThrottleRenders;
|
hrtfThrottleRenders += otherStats.hrtfThrottleRenders;
|
||||||
manualStereoMixes += otherStats.manualStereoMixes;
|
manualStereoMixes += otherStats.manualStereoMixes;
|
||||||
manualEchoMixes += otherStats.manualEchoMixes;
|
manualEchoMixes += otherStats.manualEchoMixes;
|
||||||
|
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||||
|
throttleTime += otherStats.throttleTime;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
#ifndef hifi_AudioMixerStats_h
|
#ifndef hifi_AudioMixerStats_h
|
||||||
#define hifi_AudioMixerStats_h
|
#define hifi_AudioMixerStats_h
|
||||||
|
|
||||||
|
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||||
|
#include <cstdint>
|
||||||
|
#endif
|
||||||
|
|
||||||
struct AudioMixerStats {
|
struct AudioMixerStats {
|
||||||
int sumStreams { 0 };
|
int sumStreams { 0 };
|
||||||
int sumListeners { 0 };
|
int sumListeners { 0 };
|
||||||
|
@ -25,6 +29,10 @@ struct AudioMixerStats {
|
||||||
int manualStereoMixes { 0 };
|
int manualStereoMixes { 0 };
|
||||||
int manualEchoMixes { 0 };
|
int manualEchoMixes { 0 };
|
||||||
|
|
||||||
|
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||||
|
uint64_t throttleTime { 0 };
|
||||||
|
#endif
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void accumulate(const AudioMixerStats& otherStats);
|
void accumulate(const AudioMixerStats& otherStats);
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,7 +34,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
||||||
DependencyManager::set<ScriptCache>();
|
DependencyManager::set<ScriptCache>();
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase },
|
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics },
|
||||||
this, "handleEntityPacket");
|
this, "handleEntityPacket");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,218 +141,6 @@
|
||||||
"can_set": true
|
"can_set": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Operating Hours",
|
|
||||||
"help": "\"Open\" domains can be searched using their operating hours. Hours are entered in the local timezone, selected below.",
|
|
||||||
|
|
||||||
"name": "weekday_hours",
|
|
||||||
"caption": "Weekday Hours (Monday-Friday)",
|
|
||||||
"type": "table",
|
|
||||||
"can_add_new_rows": false,
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "open",
|
|
||||||
"label": "Opening Time",
|
|
||||||
"type": "time",
|
|
||||||
"default": "00:00",
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "close",
|
|
||||||
"label": "Closing Time",
|
|
||||||
"type": "time",
|
|
||||||
"default": "23:59",
|
|
||||||
"editable": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "weekend_hours",
|
|
||||||
"label": "Weekend Hours (Saturday/Sunday)",
|
|
||||||
"type": "table",
|
|
||||||
"can_add_new_rows": false,
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "open",
|
|
||||||
"label": "Opening Time",
|
|
||||||
"type": "time",
|
|
||||||
"default": "00:00",
|
|
||||||
"editable": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "close",
|
|
||||||
"label": "Closing Time",
|
|
||||||
"type": "time",
|
|
||||||
"default": "23:59",
|
|
||||||
"editable": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Time Zone",
|
|
||||||
"name": "utc_offset",
|
|
||||||
"caption": "Time Zone",
|
|
||||||
"help": "This server's time zone. Used to define your server's operating hours.",
|
|
||||||
"type": "select",
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"value": "-12",
|
|
||||||
"label": "UTC-12:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-11",
|
|
||||||
"label": "UTC-11:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-10",
|
|
||||||
"label": "UTC-10:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-9.5",
|
|
||||||
"label": "UTC-09:30"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-9",
|
|
||||||
"label": "UTC-09:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-8",
|
|
||||||
"label": "UTC-08:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-7",
|
|
||||||
"label": "UTC-07:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-6",
|
|
||||||
"label": "UTC-06:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-5",
|
|
||||||
"label": "UTC-05:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-4",
|
|
||||||
"label": "UTC-04:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-3.5",
|
|
||||||
"label": "UTC-03:30"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-3",
|
|
||||||
"label": "UTC-03:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-2",
|
|
||||||
"label": "UTC-02:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "-1",
|
|
||||||
"label": "UTC-01:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "",
|
|
||||||
"label": "UTC±00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "1",
|
|
||||||
"label": "UTC+01:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "2",
|
|
||||||
"label": "UTC+02:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "3",
|
|
||||||
"label": "UTC+03:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "3.5",
|
|
||||||
"label": "UTC+03:30"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "4",
|
|
||||||
"label": "UTC+04:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "4.5",
|
|
||||||
"label": "UTC+04:30"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "5",
|
|
||||||
"label": "UTC+05:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "5.5",
|
|
||||||
"label": "UTC+05:30"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "5.75",
|
|
||||||
"label": "UTC+05:45"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "6",
|
|
||||||
"label": "UTC+06:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "6.5",
|
|
||||||
"label": "UTC+06:30"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "7",
|
|
||||||
"label": "UTC+07:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "8",
|
|
||||||
"label": "UTC+08:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "8.5",
|
|
||||||
"label": "UTC+08:30"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "8.75",
|
|
||||||
"label": "UTC+08:45"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "9",
|
|
||||||
"label": "UTC+09:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "9.5",
|
|
||||||
"label": "UTC+09:30"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "10",
|
|
||||||
"label": "UTC+10:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "10.5",
|
|
||||||
"label": "UTC+10:30"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "11",
|
|
||||||
"label": "UTC+11:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "12",
|
|
||||||
"label": "UTC+12:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "12.75",
|
|
||||||
"label": "UTC+12:45"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "13",
|
|
||||||
"label": "UTC+13:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "14",
|
|
||||||
"label": "UTC+14:00"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,12 +35,6 @@ const QString DomainMetadata::Descriptors::RESTRICTION = "restriction"; // parse
|
||||||
const QString DomainMetadata::Descriptors::MATURITY = "maturity";
|
const QString DomainMetadata::Descriptors::MATURITY = "maturity";
|
||||||
const QString DomainMetadata::Descriptors::HOSTS = "hosts";
|
const QString DomainMetadata::Descriptors::HOSTS = "hosts";
|
||||||
const QString DomainMetadata::Descriptors::TAGS = "tags";
|
const QString DomainMetadata::Descriptors::TAGS = "tags";
|
||||||
const QString DomainMetadata::Descriptors::HOURS = "hours";
|
|
||||||
const QString DomainMetadata::Descriptors::Hours::WEEKDAY = "weekday";
|
|
||||||
const QString DomainMetadata::Descriptors::Hours::WEEKEND = "weekend";
|
|
||||||
const QString DomainMetadata::Descriptors::Hours::UTC_OFFSET = "utc_offset";
|
|
||||||
const QString DomainMetadata::Descriptors::Hours::OPEN = "open";
|
|
||||||
const QString DomainMetadata::Descriptors::Hours::CLOSE = "close";
|
|
||||||
// descriptors metadata will appear as (JSON):
|
// descriptors metadata will appear as (JSON):
|
||||||
// { "description": String, // capped description
|
// { "description": String, // capped description
|
||||||
// "capacity": Number,
|
// "capacity": Number,
|
||||||
|
@ -48,11 +42,6 @@ const QString DomainMetadata::Descriptors::Hours::CLOSE = "close";
|
||||||
// "maturity": String, // enum corresponding to ESRB ratings
|
// "maturity": String, // enum corresponding to ESRB ratings
|
||||||
// "hosts": [ String ], // capped list of usernames
|
// "hosts": [ String ], // capped list of usernames
|
||||||
// "tags": [ String ], // capped list of tags
|
// "tags": [ String ], // capped list of tags
|
||||||
// "hours": {
|
|
||||||
// "utc_offset": Number,
|
|
||||||
// "weekday": [ [ Time, Time ] ],
|
|
||||||
// "weekend": [ [ Time, Time ] ],
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// metadata will appear as (JSON):
|
// metadata will appear as (JSON):
|
||||||
|
@ -60,52 +49,10 @@ const QString DomainMetadata::Descriptors::Hours::CLOSE = "close";
|
||||||
//
|
//
|
||||||
// it is meant to be sent to and consumed by an external API
|
// it is meant to be sent to and consumed by an external API
|
||||||
|
|
||||||
// merge delta into target
|
|
||||||
// target should be of the form [ OpenTime, CloseTime ],
|
|
||||||
// delta should be of the form [ { open: Time, close: Time } ]
|
|
||||||
void parseHours(QVariant delta, QVariant& target) {
|
|
||||||
using Hours = DomainMetadata::Descriptors::Hours;
|
|
||||||
static const QVariantList DEFAULT_HOURS{
|
|
||||||
{ QVariantList{ "00:00", "23:59" } }
|
|
||||||
};
|
|
||||||
target.setValue(DEFAULT_HOURS);
|
|
||||||
|
|
||||||
if (!delta.canConvert<QVariantList>()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& deltaList = *static_cast<QVariantList*>(delta.data());
|
|
||||||
if (deltaList.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& deltaHours = *static_cast<QVariantMap*>(deltaList.first().data());
|
|
||||||
auto open = deltaHours.find(Hours::OPEN);
|
|
||||||
auto close = deltaHours.find(Hours::CLOSE);
|
|
||||||
if (open == deltaHours.end() || close == deltaHours.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge delta into new hours
|
|
||||||
static const int OPEN_INDEX = 0;
|
|
||||||
static const int CLOSE_INDEX = 1;
|
|
||||||
auto& hours = *static_cast<QVariantList*>(static_cast<QVariantList*>(target.data())->first().data());
|
|
||||||
hours[OPEN_INDEX] = open.value();
|
|
||||||
hours[CLOSE_INDEX] = close.value();
|
|
||||||
|
|
||||||
assert(hours[OPEN_INDEX].canConvert<QString>());
|
|
||||||
assert(hours[CLOSE_INDEX].canConvert<QString>());
|
|
||||||
}
|
|
||||||
|
|
||||||
DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) {
|
DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) {
|
||||||
// set up the structure necessary for casting during parsing (see parseHours, esp.)
|
// set up the structure necessary for casting during parsing
|
||||||
_metadata[USERS] = QVariantMap {};
|
_metadata[USERS] = QVariantMap {};
|
||||||
_metadata[DESCRIPTORS] = QVariantMap { {
|
_metadata[DESCRIPTORS] = QVariantMap {};
|
||||||
Descriptors::HOURS, QVariantMap {
|
|
||||||
{ Descriptors::Hours::WEEKDAY, QVariant{} },
|
|
||||||
{ Descriptors::Hours::WEEKEND, QVariant{} }
|
|
||||||
}
|
|
||||||
} };
|
|
||||||
|
|
||||||
assert(dynamic_cast<DomainServer*>(domainServer));
|
assert(dynamic_cast<DomainServer*>(domainServer));
|
||||||
DomainServer* server = static_cast<DomainServer*>(domainServer);
|
DomainServer* server = static_cast<DomainServer*>(domainServer);
|
||||||
|
@ -154,16 +101,6 @@ void DomainMetadata::descriptorsChanged() {
|
||||||
unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0;
|
unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0;
|
||||||
state[Descriptors::CAPACITY] = capacity;
|
state[Descriptors::CAPACITY] = capacity;
|
||||||
|
|
||||||
// parse operating hours
|
|
||||||
static const QString WEEKDAY_HOURS = "weekday_hours";
|
|
||||||
static const QString WEEKEND_HOURS = "weekend_hours";
|
|
||||||
static const QString UTC_OFFSET = "utc_offset";
|
|
||||||
assert(state[Descriptors::HOURS].canConvert<QVariantMap>());
|
|
||||||
auto& hours = *static_cast<QVariantMap*>(state[Descriptors::HOURS].data());
|
|
||||||
hours[Descriptors::Hours::UTC_OFFSET] = descriptors.take(UTC_OFFSET);
|
|
||||||
parseHours(descriptors[WEEKDAY_HOURS], hours[Descriptors::Hours::WEEKDAY]);
|
|
||||||
parseHours(descriptors[WEEKEND_HOURS], hours[Descriptors::Hours::WEEKEND]);
|
|
||||||
|
|
||||||
#if DEV_BUILD || PR_BUILD
|
#if DEV_BUILD || PR_BUILD
|
||||||
qDebug() << "Domain metadata descriptors set:" << QJsonObject::fromVariantMap(_metadata[DESCRIPTORS].toMap());
|
qDebug() << "Domain metadata descriptors set:" << QJsonObject::fromVariantMap(_metadata[DESCRIPTORS].toMap());
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -39,15 +39,6 @@ public:
|
||||||
static const QString MATURITY;
|
static const QString MATURITY;
|
||||||
static const QString HOSTS;
|
static const QString HOSTS;
|
||||||
static const QString TAGS;
|
static const QString TAGS;
|
||||||
static const QString HOURS;
|
|
||||||
class Hours {
|
|
||||||
public:
|
|
||||||
static const QString WEEKDAY;
|
|
||||||
static const QString WEEKEND;
|
|
||||||
static const QString UTC_OFFSET;
|
|
||||||
static const QString OPEN;
|
|
||||||
static const QString CLOSE;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DomainMetadata(QObject* domainServer);
|
DomainMetadata(QObject* domainServer);
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
#include <QtCore/QStandardPaths>
|
#include <QtCore/QStandardPaths>
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
#include <QtCore/QUrlQuery>
|
#include <QtCore/QUrlQuery>
|
||||||
#include <QTimeZone>
|
|
||||||
|
|
||||||
#include <AccountManager.h>
|
#include <AccountManager.h>
|
||||||
#include <Assignment.h>
|
#include <Assignment.h>
|
||||||
|
@ -270,11 +269,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
_agentPermissions.clear();
|
_agentPermissions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < 1.5) {
|
|
||||||
// This was prior to operating hours, so add default hours
|
|
||||||
validateDescriptorsMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 1.6) {
|
if (oldVersion < 1.6) {
|
||||||
unpackPermissions();
|
unpackPermissions();
|
||||||
|
|
||||||
|
@ -305,46 +299,10 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
|
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
|
||||||
validateDescriptorsMap();
|
|
||||||
|
|
||||||
static const QString DESCRIPTORS{ "descriptors" };
|
static const QString DESCRIPTORS{ "descriptors" };
|
||||||
return *static_cast<QVariantMap*>(getSettingsMap()[DESCRIPTORS].data());
|
return *static_cast<QVariantMap*>(getSettingsMap()[DESCRIPTORS].data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServerSettingsManager::validateDescriptorsMap() {
|
|
||||||
static const QString WEEKDAY_HOURS{ "descriptors.weekday_hours" };
|
|
||||||
static const QString WEEKEND_HOURS{ "descriptors.weekend_hours" };
|
|
||||||
static const QString UTC_OFFSET{ "descriptors.utc_offset" };
|
|
||||||
|
|
||||||
QVariant* weekdayHours = _configMap.valueForKeyPath(WEEKDAY_HOURS, true);
|
|
||||||
QVariant* weekendHours = _configMap.valueForKeyPath(WEEKEND_HOURS, true);
|
|
||||||
QVariant* utcOffset = _configMap.valueForKeyPath(UTC_OFFSET, true);
|
|
||||||
|
|
||||||
static const QString OPEN{ "open" };
|
|
||||||
static const QString CLOSE{ "close" };
|
|
||||||
static const QString DEFAULT_OPEN{ "00:00" };
|
|
||||||
static const QString DEFAULT_CLOSE{ "23:59" };
|
|
||||||
bool wasMalformed = false;
|
|
||||||
if (weekdayHours->isNull()) {
|
|
||||||
*weekdayHours = QVariantList{ QVariantMap{ { OPEN, QVariant(DEFAULT_OPEN) }, { CLOSE, QVariant(DEFAULT_CLOSE) } } };
|
|
||||||
wasMalformed = true;
|
|
||||||
}
|
|
||||||
if (weekendHours->isNull()) {
|
|
||||||
*weekendHours = QVariantList{ QVariantMap{ { OPEN, QVariant(DEFAULT_OPEN) }, { CLOSE, QVariant(DEFAULT_CLOSE) } } };
|
|
||||||
wasMalformed = true;
|
|
||||||
}
|
|
||||||
if (utcOffset->isNull()) {
|
|
||||||
*utcOffset = QVariant(QTimeZone::systemTimeZone().offsetFromUtc(QDateTime::currentDateTime()) / (float)SECS_PER_HOUR);
|
|
||||||
wasMalformed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wasMalformed) {
|
|
||||||
// write the new settings to file
|
|
||||||
persistToFile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& permissionsRows,
|
void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& permissionsRows,
|
||||||
QString groupName, NodePermissionsPointer perms) {
|
QString groupName, NodePermissionsPointer perms) {
|
||||||
// this is called when someone has used the domain-settings webpage to add a group. They type the group's name
|
// this is called when someone has used the domain-settings webpage to add a group. They type the group's name
|
||||||
|
|
|
@ -138,8 +138,6 @@ private:
|
||||||
|
|
||||||
friend class DomainServer;
|
friend class DomainServer;
|
||||||
|
|
||||||
void validateDescriptorsMap();
|
|
||||||
|
|
||||||
// these cause calls to metaverse's group api
|
// these cause calls to metaverse's group api
|
||||||
void apiGetGroupID(const QString& groupName);
|
void apiGetGroupID(const QString& groupName);
|
||||||
void apiGetGroupRanks(const QUuid& groupID);
|
void apiGetGroupRanks(const QUuid& groupID);
|
||||||
|
|
|
@ -7,7 +7,7 @@ PreferencesDialog {
|
||||||
id: root
|
id: root
|
||||||
objectName: "AvatarPreferencesDialog"
|
objectName: "AvatarPreferencesDialog"
|
||||||
title: "Avatar Settings"
|
title: "Avatar Settings"
|
||||||
showCategories: [ "Avatar Basics", "Snapshots", "Avatar Tuning", "Avatar Camera" ]
|
showCategories: [ "Avatar Basics", "Avatar Tuning", "Avatar Camera" ]
|
||||||
property var settings: Settings {
|
property var settings: Settings {
|
||||||
category: root.objectName
|
category: root.objectName
|
||||||
property alias x: root.x
|
property alias x: root.x
|
||||||
|
|
|
@ -104,7 +104,7 @@ void setupPreferences() {
|
||||||
{
|
{
|
||||||
auto getter = []()->bool { return SnapshotAnimated::alsoTakeAnimatedSnapshot.get(); };
|
auto getter = []()->bool { return SnapshotAnimated::alsoTakeAnimatedSnapshot.get(); };
|
||||||
auto setter = [](bool value) { SnapshotAnimated::alsoTakeAnimatedSnapshot.set(value); };
|
auto setter = [](bool value) { SnapshotAnimated::alsoTakeAnimatedSnapshot.set(value); };
|
||||||
preferences->addPreference(new CheckPreference(SNAPSHOTS, "Take Animated GIF Snapshot with HUD Button", getter, setter));
|
preferences->addPreference(new CheckPreference(SNAPSHOTS, "Take Animated GIF Snapshot", getter, setter));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto getter = []()->float { return SnapshotAnimated::snapshotAnimatedDuration.get(); };
|
auto getter = []()->float { return SnapshotAnimated::snapshotAnimatedDuration.get(); };
|
||||||
|
|
|
@ -48,6 +48,7 @@ public:
|
||||||
// HRTF local gain adjustment in amplitude (1.0 == unity)
|
// HRTF local gain adjustment in amplitude (1.0 == unity)
|
||||||
//
|
//
|
||||||
void setGainAdjustment(float gain) { _gainAdjust = HRTF_GAIN * gain; };
|
void setGainAdjustment(float gain) { _gainAdjust = HRTF_GAIN * gain; };
|
||||||
|
float getGainAdjustment() { return _gainAdjust; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AudioHRTF(const AudioHRTF&) = delete;
|
AudioHRTF(const AudioHRTF&) = delete;
|
||||||
|
|
|
@ -131,12 +131,16 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
|
||||||
|
|
||||||
// handle this packet based on its arrival status.
|
// handle this packet based on its arrival status.
|
||||||
switch (arrivalInfo._status) {
|
switch (arrivalInfo._status) {
|
||||||
|
case SequenceNumberStats::Unreasonable: {
|
||||||
|
lostAudioData(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case SequenceNumberStats::Early: {
|
case SequenceNumberStats::Early: {
|
||||||
// Packet is early; write droppable silent samples for each of the skipped packets.
|
// Packet is early; write droppable silent samples for each of the skipped packets.
|
||||||
// NOTE: we assume that each dropped packet contains the same number of samples
|
// NOTE: we assume that each dropped packet contains the same number of samples
|
||||||
// as the packet we just received.
|
// as the packet we just received.
|
||||||
int packetsDropped = arrivalInfo._seqDiffFromExpected;
|
int packetsDropped = arrivalInfo._seqDiffFromExpected;
|
||||||
writeFramesForDroppedPackets(packetsDropped * networkFrames);
|
lostAudioData(packetsDropped);
|
||||||
|
|
||||||
// fall through to OnTime case
|
// fall through to OnTime case
|
||||||
}
|
}
|
||||||
|
@ -208,6 +212,21 @@ int InboundAudioStream::parseStreamProperties(PacketType type, const QByteArray&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int InboundAudioStream::lostAudioData(int numPackets) {
|
||||||
|
QByteArray decodedBuffer;
|
||||||
|
|
||||||
|
while (numPackets--) {
|
||||||
|
if (_decoder) {
|
||||||
|
_decoder->lostFrame(decodedBuffer);
|
||||||
|
} else {
|
||||||
|
decodedBuffer.resize(AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||||
|
memset(decodedBuffer.data(), 0, decodedBuffer.size());
|
||||||
|
}
|
||||||
|
_ringBuffer.writeData(decodedBuffer.data(), decodedBuffer.size());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
|
int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
|
||||||
QByteArray decodedBuffer;
|
QByteArray decodedBuffer;
|
||||||
if (_decoder) {
|
if (_decoder) {
|
||||||
|
@ -220,9 +239,6 @@ int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packet
|
||||||
}
|
}
|
||||||
|
|
||||||
int InboundAudioStream::writeDroppableSilentFrames(int silentFrames) {
|
int InboundAudioStream::writeDroppableSilentFrames(int silentFrames) {
|
||||||
if (_decoder) {
|
|
||||||
_decoder->trackLostFrames(silentFrames);
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate how many silent frames we should drop.
|
// calculate how many silent frames we should drop.
|
||||||
int silentSamples = silentFrames * _numChannels;
|
int silentSamples = silentFrames * _numChannels;
|
||||||
|
@ -416,29 +432,6 @@ void InboundAudioStream::packetReceivedUpdateTimingStats() {
|
||||||
_lastPacketReceivedTime = now;
|
_lastPacketReceivedTime = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
int InboundAudioStream::writeFramesForDroppedPackets(int networkFrames) {
|
|
||||||
return writeLastFrameRepeatedWithFade(networkFrames);
|
|
||||||
}
|
|
||||||
|
|
||||||
int InboundAudioStream::writeLastFrameRepeatedWithFade(int frames) {
|
|
||||||
AudioRingBuffer::ConstIterator frameToRepeat = _ringBuffer.lastFrameWritten();
|
|
||||||
int frameSize = _ringBuffer.getNumFrameSamples();
|
|
||||||
int samplesToWrite = frames * _numChannels;
|
|
||||||
int indexOfRepeat = 0;
|
|
||||||
do {
|
|
||||||
int samplesToWriteThisIteration = std::min(samplesToWrite, frameSize);
|
|
||||||
float fade = calculateRepeatedFrameFadeFactor(indexOfRepeat);
|
|
||||||
if (fade == 1.0f) {
|
|
||||||
samplesToWrite -= _ringBuffer.writeSamples(frameToRepeat, samplesToWriteThisIteration);
|
|
||||||
} else {
|
|
||||||
samplesToWrite -= _ringBuffer.writeSamplesWithFade(frameToRepeat, samplesToWriteThisIteration, fade);
|
|
||||||
}
|
|
||||||
indexOfRepeat++;
|
|
||||||
} while (samplesToWrite > 0);
|
|
||||||
|
|
||||||
return frames;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioStreamStats InboundAudioStream::getAudioStreamStats() const {
|
AudioStreamStats InboundAudioStream::getAudioStreamStats() const {
|
||||||
AudioStreamStats streamStats;
|
AudioStreamStats streamStats;
|
||||||
|
|
||||||
|
|
|
@ -115,8 +115,6 @@ public slots:
|
||||||
private:
|
private:
|
||||||
void packetReceivedUpdateTimingStats();
|
void packetReceivedUpdateTimingStats();
|
||||||
|
|
||||||
int writeFramesForDroppedPackets(int networkFrames);
|
|
||||||
|
|
||||||
void popSamplesNoCheck(int samples);
|
void popSamplesNoCheck(int samples);
|
||||||
void framesAvailableChanged();
|
void framesAvailableChanged();
|
||||||
|
|
||||||
|
@ -134,12 +132,11 @@ protected:
|
||||||
/// default implementation assumes packet contains raw audio samples after stream properties
|
/// default implementation assumes packet contains raw audio samples after stream properties
|
||||||
virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties);
|
virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties);
|
||||||
|
|
||||||
|
/// produces audio data for lost network packets.
|
||||||
|
virtual int lostAudioData(int numPackets);
|
||||||
|
|
||||||
/// writes silent frames to the buffer that may be dropped to reduce latency caused by the buffer
|
/// writes silent frames to the buffer that may be dropped to reduce latency caused by the buffer
|
||||||
virtual int writeDroppableSilentFrames(int silentFrames);
|
virtual int writeDroppableSilentFrames(int silentFrames);
|
||||||
|
|
||||||
/// writes the last written frame repeatedly, gradually fading to silence.
|
|
||||||
/// used for writing samples for dropped packets.
|
|
||||||
virtual int writeLastFrameRepeatedWithFade(int frames);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
|
|
@ -31,11 +31,26 @@ int MixedProcessedAudioStream::writeDroppableSilentFrames(int silentFrames) {
|
||||||
return deviceSilentFramesWritten;
|
return deviceSilentFramesWritten;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MixedProcessedAudioStream::writeLastFrameRepeatedWithFade(int frames) {
|
int MixedProcessedAudioStream::lostAudioData(int numPackets) {
|
||||||
int deviceFrames = networkToDeviceFrames(frames);
|
QByteArray decodedBuffer;
|
||||||
int deviceFramesWritten = InboundAudioStream::writeLastFrameRepeatedWithFade(deviceFrames);
|
QByteArray outputBuffer;
|
||||||
emit addedLastFrameRepeatedWithFade(deviceToNetworkFrames(deviceFramesWritten));
|
|
||||||
return deviceFramesWritten;
|
while (numPackets--) {
|
||||||
|
if (_decoder) {
|
||||||
|
_decoder->lostFrame(decodedBuffer);
|
||||||
|
} else {
|
||||||
|
decodedBuffer.resize(AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||||
|
memset(decodedBuffer.data(), 0, decodedBuffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
emit addedStereoSamples(decodedBuffer);
|
||||||
|
|
||||||
|
emit processSamples(decodedBuffer, outputBuffer);
|
||||||
|
|
||||||
|
_ringBuffer.writeData(outputBuffer.data(), outputBuffer.size());
|
||||||
|
qCDebug(audiostream, "Wrote %d samples to buffer (%d available)", outputBuffer.size() / (int)sizeof(int16_t), getSamplesAvailable());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
|
int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
|
||||||
|
|
|
@ -34,8 +34,8 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int writeDroppableSilentFrames(int silentFrames) override;
|
int writeDroppableSilentFrames(int silentFrames) override;
|
||||||
int writeLastFrameRepeatedWithFade(int frames) override;
|
|
||||||
int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) override;
|
int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) override;
|
||||||
|
int lostAudioData(int numPackets) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int networkToDeviceFrames(int networkFrames);
|
int networkToDeviceFrames(int networkFrames);
|
||||||
|
|
|
@ -29,7 +29,7 @@ void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer<Received
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) {
|
void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) {
|
||||||
if (type == PacketType::EntityAdd || type == PacketType::EntityEdit) {
|
if (type == PacketType::EntityAdd || type == PacketType::EntityEdit || type == PacketType::EntityPhysics) {
|
||||||
EntityItem::adjustEditPacketForClockSkew(buffer, clockSkew);
|
EntityItem::adjustEditPacketForClockSkew(buffer, clockSkew);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,18 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
||||||
|
|
||||||
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
|
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
|
||||||
|
|
||||||
if (EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut)) {
|
bool success;
|
||||||
|
if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) {
|
||||||
|
EntityItemProperties propertiesCopy = properties;
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||||
|
propertiesCopy.setParentID(myNodeID);
|
||||||
|
success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut);
|
||||||
|
} else {
|
||||||
|
success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
#ifdef WANT_DEBUG
|
#ifdef WANT_DEBUG
|
||||||
qCDebug(entities) << "calling queueOctreeEditMessage()...";
|
qCDebug(entities) << "calling queueOctreeEditMessage()...";
|
||||||
qCDebug(entities) << " id:" << entityItemID;
|
qCDebug(entities) << " id:" << entityItemID;
|
||||||
|
|
|
@ -828,7 +828,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
{ // parentID and parentJointIndex are also protected by simulation ownership
|
{ // parentID and parentJointIndex are also protected by simulation ownership
|
||||||
bool oldOverwrite = overwriteLocalData;
|
bool oldOverwrite = overwriteLocalData;
|
||||||
overwriteLocalData = overwriteLocalData && !weOwnSimulation;
|
overwriteLocalData = overwriteLocalData && !weOwnSimulation;
|
||||||
READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, setParentID);
|
READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, updateParentID);
|
||||||
READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex);
|
READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex);
|
||||||
overwriteLocalData = oldOverwrite;
|
overwriteLocalData = oldOverwrite;
|
||||||
}
|
}
|
||||||
|
@ -1823,28 +1823,6 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t userMask = getCollisionMask();
|
uint8_t userMask = getCollisionMask();
|
||||||
if (userMask & USER_COLLISION_GROUP_MY_AVATAR) {
|
|
||||||
// if this entity is a descendant of MyAvatar, don't collide with MyAvatar. This avoids the
|
|
||||||
// "bootstrapping" problem where you can shoot yourself across the room by grabbing something
|
|
||||||
// and holding it against your own avatar.
|
|
||||||
QUuid ancestorID = findAncestorOfType(NestableType::Avatar);
|
|
||||||
if (!ancestorID.isNull() && ancestorID == Physics::getSessionUUID()) {
|
|
||||||
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (userMask & USER_COLLISION_GROUP_MY_AVATAR) {
|
|
||||||
// also, don't bootstrap our own avatar with a hold action
|
|
||||||
QList<EntityActionPointer> holdActions = getActionsOfType(ACTION_TYPE_HOLD);
|
|
||||||
QList<EntityActionPointer>::const_iterator i = holdActions.begin();
|
|
||||||
while (i != holdActions.end()) {
|
|
||||||
EntityActionPointer action = *i;
|
|
||||||
if (action->isMine()) {
|
|
||||||
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((bool)(userMask & USER_COLLISION_GROUP_MY_AVATAR) !=
|
if ((bool)(userMask & USER_COLLISION_GROUP_MY_AVATAR) !=
|
||||||
(bool)(userMask & USER_COLLISION_GROUP_OTHER_AVATAR)) {
|
(bool)(userMask & USER_COLLISION_GROUP_OTHER_AVATAR)) {
|
||||||
|
@ -1854,6 +1832,33 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
|
||||||
userMask ^= USER_COLLISION_MASK_AVATARS | ~userMask;
|
userMask ^= USER_COLLISION_MASK_AVATARS | ~userMask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userMask & USER_COLLISION_GROUP_MY_AVATAR) {
|
||||||
|
bool iAmHoldingThis = false;
|
||||||
|
// if this entity is a descendant of MyAvatar, don't collide with MyAvatar. This avoids the
|
||||||
|
// "bootstrapping" problem where you can shoot yourself across the room by grabbing something
|
||||||
|
// and holding it against your own avatar.
|
||||||
|
QUuid ancestorID = findAncestorOfType(NestableType::Avatar);
|
||||||
|
if (!ancestorID.isNull() &&
|
||||||
|
(ancestorID == Physics::getSessionUUID() || ancestorID == AVATAR_SELF_ID)) {
|
||||||
|
iAmHoldingThis = true;
|
||||||
|
}
|
||||||
|
// also, don't bootstrap our own avatar with a hold action
|
||||||
|
QList<EntityActionPointer> holdActions = getActionsOfType(ACTION_TYPE_HOLD);
|
||||||
|
QList<EntityActionPointer>::const_iterator i = holdActions.begin();
|
||||||
|
while (i != holdActions.end()) {
|
||||||
|
EntityActionPointer action = *i;
|
||||||
|
if (action->isMine()) {
|
||||||
|
iAmHoldingThis = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iAmHoldingThis) {
|
||||||
|
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask);
|
mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,7 @@ bool EntityTree::handlesEditPacketType(PacketType packetType) const {
|
||||||
case PacketType::EntityAdd:
|
case PacketType::EntityAdd:
|
||||||
case PacketType::EntityEdit:
|
case PacketType::EntityEdit:
|
||||||
case PacketType::EntityErase:
|
case PacketType::EntityErase:
|
||||||
|
case PacketType::EntityPhysics:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
@ -931,10 +932,15 @@ void EntityTree::initEntityEditFilterEngine(QScriptEngine* engine, std::function
|
||||||
qCDebug(entities) << "Filter function specified but not found. Will reject all edits.";
|
qCDebug(entities) << "Filter function specified but not found. Will reject all edits.";
|
||||||
_entityEditFilterEngine = nullptr; // So that we don't try to call it. See filterProperties.
|
_entityEditFilterEngine = nullptr; // So that we don't try to call it. See filterProperties.
|
||||||
}
|
}
|
||||||
|
auto entitiesObject = _entityEditFilterEngine->newObject();
|
||||||
|
entitiesObject.setProperty("ADD_FILTER_TYPE", FilterType::Add);
|
||||||
|
entitiesObject.setProperty("EDIT_FILTER_TYPE", FilterType::Edit);
|
||||||
|
entitiesObject.setProperty("PHYSICS_FILTER_TYPE", FilterType::Physics);
|
||||||
|
global.setProperty("Entities", entitiesObject);
|
||||||
_hasEntityEditFilter = true;
|
_hasEntityEditFilter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd) {
|
bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) {
|
||||||
if (!_entityEditFilterEngine) {
|
if (!_entityEditFilterEngine) {
|
||||||
propertiesOut = propertiesIn;
|
propertiesOut = propertiesIn;
|
||||||
wasChanged = false; // not changed
|
wasChanged = false; // not changed
|
||||||
|
@ -953,7 +959,7 @@ bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItem
|
||||||
auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter.
|
auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter.
|
||||||
QScriptValueList args;
|
QScriptValueList args;
|
||||||
args << inputValues;
|
args << inputValues;
|
||||||
args << isAdd;
|
args << filterType;
|
||||||
|
|
||||||
QScriptValue result = _entityEditFilterFunction.call(_nullObjectForFilter, args);
|
QScriptValue result = _entityEditFilterFunction.call(_nullObjectForFilter, args);
|
||||||
if (_entityEditFilterHadUncaughtExceptions()) {
|
if (_entityEditFilterHadUncaughtExceptions()) {
|
||||||
|
@ -1001,6 +1007,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
|
|
||||||
case PacketType::EntityAdd:
|
case PacketType::EntityAdd:
|
||||||
isAdd = true; // fall through to next case
|
isAdd = true; // fall through to next case
|
||||||
|
case PacketType::EntityPhysics:
|
||||||
case PacketType::EntityEdit: {
|
case PacketType::EntityEdit: {
|
||||||
quint64 startDecode = 0, endDecode = 0;
|
quint64 startDecode = 0, endDecode = 0;
|
||||||
quint64 startLookup = 0, endLookup = 0;
|
quint64 startLookup = 0, endLookup = 0;
|
||||||
|
@ -1010,6 +1017,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
quint64 startLogging = 0, endLogging = 0;
|
quint64 startLogging = 0, endLogging = 0;
|
||||||
|
|
||||||
bool suppressDisallowedScript = false;
|
bool suppressDisallowedScript = false;
|
||||||
|
bool isPhysics = message.getType() == PacketType::EntityPhysics;
|
||||||
|
|
||||||
_totalEditMessages++;
|
_totalEditMessages++;
|
||||||
|
|
||||||
|
@ -1021,6 +1029,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
entityItemID, properties);
|
entityItemID, properties);
|
||||||
endDecode = usecTimestampNow();
|
endDecode = usecTimestampNow();
|
||||||
|
|
||||||
|
|
||||||
if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) {
|
if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) {
|
||||||
bool passedWhiteList = false;
|
bool passedWhiteList = false;
|
||||||
|
|
||||||
|
@ -1053,8 +1062,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((isAdd ||
|
if ((isAdd || properties.lifetimeChanged()) &&
|
||||||
(message.getType() == PacketType::EntityEdit && properties.lifetimeChanged())) &&
|
|
||||||
!senderNode->getCanRez() && senderNode->getCanRezTmp()) {
|
!senderNode->getCanRez() && senderNode->getCanRezTmp()) {
|
||||||
// this node is only allowed to rez temporary entities. if need be, cap the lifetime.
|
// this node is only allowed to rez temporary entities. if need be, cap the lifetime.
|
||||||
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
|
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
|
||||||
|
@ -1070,8 +1078,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
|
|
||||||
startFilter = usecTimestampNow();
|
startFilter = usecTimestampNow();
|
||||||
bool wasChanged = false;
|
bool wasChanged = false;
|
||||||
// Having (un)lock rights bypasses the filter.
|
// Having (un)lock rights bypasses the filter, unless it's a physics result.
|
||||||
bool allowed = senderNode->isAllowedEditor() || filterProperties(properties, properties, wasChanged, isAdd);
|
FilterType filterType = isPhysics ? FilterType::Physics : (isAdd ? FilterType::Add : FilterType::Edit);
|
||||||
|
bool allowed = (!isPhysics && senderNode->isAllowedEditor()) || filterProperties(properties, properties, wasChanged, filterType);
|
||||||
if (!allowed) {
|
if (!allowed) {
|
||||||
auto timestamp = properties.getLastEdited();
|
auto timestamp = properties.getLastEdited();
|
||||||
properties = EntityItemProperties();
|
properties = EntityItemProperties();
|
||||||
|
@ -1088,7 +1097,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
startLookup = usecTimestampNow();
|
startLookup = usecTimestampNow();
|
||||||
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
|
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
|
||||||
endLookup = usecTimestampNow();
|
endLookup = usecTimestampNow();
|
||||||
if (existingEntity && message.getType() == PacketType::EntityEdit) {
|
if (existingEntity && !isAdd) {
|
||||||
|
|
||||||
if (suppressDisallowedScript) {
|
if (suppressDisallowedScript) {
|
||||||
bumpTimestamp(properties);
|
bumpTimestamp(properties);
|
||||||
|
|
|
@ -60,6 +60,11 @@ public:
|
||||||
class EntityTree : public Octree, public SpatialParentTree {
|
class EntityTree : public Octree, public SpatialParentTree {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
enum FilterType {
|
||||||
|
Add,
|
||||||
|
Edit,
|
||||||
|
Physics
|
||||||
|
};
|
||||||
EntityTree(bool shouldReaverage = false);
|
EntityTree(bool shouldReaverage = false);
|
||||||
virtual ~EntityTree();
|
virtual ~EntityTree();
|
||||||
|
|
||||||
|
@ -357,7 +362,7 @@ protected:
|
||||||
|
|
||||||
float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME };
|
float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME };
|
||||||
|
|
||||||
bool filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd);
|
bool filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType);
|
||||||
bool _hasEntityEditFilter{ false };
|
bool _hasEntityEditFilter{ false };
|
||||||
QScriptEngine* _entityEditFilterEngine{};
|
QScriptEngine* _entityEditFilterEngine{};
|
||||||
QScriptValue _entityEditFilterFunction{};
|
QScriptValue _entityEditFilterFunction{};
|
||||||
|
|
|
@ -1468,25 +1468,26 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
||||||
|
|
||||||
// HACK: until we get proper LOD management we're going to cap model textures
|
// HACK: until we get proper LOD management we're going to cap model textures
|
||||||
// according to how many unique textures the model uses:
|
// according to how many unique textures the model uses:
|
||||||
// 1 - 7 textures --> 2048
|
// 1 - 8 textures --> 2048
|
||||||
// 8 - 31 textures --> 1024
|
// 8 - 32 textures --> 1024
|
||||||
// 32 - 127 textures --> 512
|
// 33 - 128 textures --> 512
|
||||||
// etc...
|
// etc...
|
||||||
QSet<QString> uniqueTextures;
|
QSet<QString> uniqueTextures;
|
||||||
for (auto& material : _fbxMaterials) {
|
for (auto& material : _fbxMaterials) {
|
||||||
material.getTextureNames(uniqueTextures);
|
material.getTextureNames(uniqueTextures);
|
||||||
}
|
}
|
||||||
int numTextures = uniqueTextures.size();
|
int numTextures = uniqueTextures.size();
|
||||||
const int MAX_NUM_TEXTURES_AT_MAX_RESOLUTION = 7;
|
const int MAX_NUM_TEXTURES_AT_MAX_RESOLUTION = 8;
|
||||||
|
int maxWidth = sqrt(MAX_NUM_PIXELS_FOR_FBX_TEXTURE);
|
||||||
if (numTextures > MAX_NUM_TEXTURES_AT_MAX_RESOLUTION) {
|
if (numTextures > MAX_NUM_TEXTURES_AT_MAX_RESOLUTION) {
|
||||||
int maxWidth = sqrt(MAX_NUM_PIXELS_FOR_FBX_TEXTURE + 1);
|
int numTextureThreshold = MAX_NUM_TEXTURES_AT_MAX_RESOLUTION;
|
||||||
int t = numTextures;
|
const int MIN_MIP_TEXTURE_WIDTH = 64;
|
||||||
t /= MAX_NUM_TEXTURES_AT_MAX_RESOLUTION;
|
do {
|
||||||
while (t > 0) {
|
|
||||||
maxWidth /= 2;
|
maxWidth /= 2;
|
||||||
t /= 4;
|
numTextureThreshold *= 4;
|
||||||
}
|
} while (numTextureThreshold < numTextures && maxWidth > MIN_MIP_TEXTURE_WIDTH);
|
||||||
qCDebug(modelformat) << "max square texture width =" << maxWidth << " for model" << url;
|
|
||||||
|
qCDebug(modelformat) << "Capped square texture width =" << maxWidth << "for model" << url << "with" << numTextures << "textures";
|
||||||
for (auto& material : _fbxMaterials) {
|
for (auto& material : _fbxMaterials) {
|
||||||
material.setMaxNumPixelsPerTexture(maxWidth * maxWidth);
|
material.setMaxNumPixelsPerTexture(maxWidth * maxWidth);
|
||||||
}
|
}
|
||||||
|
|
|
@ -827,18 +827,26 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ignoreEnabled) {
|
if (ignoreEnabled) {
|
||||||
QReadLocker ignoredSetLocker{ &_ignoredSetLock }; // read lock for insert
|
{
|
||||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
QReadLocker ignoredSetLocker{ &_ignoredSetLock }; // read lock for insert
|
||||||
// add this nodeID to our set of ignored IDs
|
// add this nodeID to our set of ignored IDs
|
||||||
_ignoredNodeIDs.insert(nodeID);
|
_ignoredNodeIDs.insert(nodeID);
|
||||||
// add this nodeID to our set of personal muted IDs
|
}
|
||||||
_personalMutedNodeIDs.insert(nodeID);
|
{
|
||||||
|
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
||||||
|
// add this nodeID to our set of personal muted IDs
|
||||||
|
_personalMutedNodeIDs.insert(nodeID);
|
||||||
|
}
|
||||||
emit ignoredNode(nodeID, true);
|
emit ignoredNode(nodeID, true);
|
||||||
} else {
|
} else {
|
||||||
QWriteLocker ignoredSetLocker{ &_ignoredSetLock }; // write lock for unsafe_erase
|
{
|
||||||
QWriteLocker personalMutedSetLocker{ &_personalMutedSetLock }; // write lock for unsafe_erase
|
QWriteLocker ignoredSetLocker{ &_ignoredSetLock }; // write lock for unsafe_erase
|
||||||
_ignoredNodeIDs.unsafe_erase(nodeID);
|
_ignoredNodeIDs.unsafe_erase(nodeID);
|
||||||
_personalMutedNodeIDs.unsafe_erase(nodeID);
|
}
|
||||||
|
{
|
||||||
|
QWriteLocker personalMutedSetLocker{ &_personalMutedSetLock }; // write lock for unsafe_erase
|
||||||
|
_personalMutedNodeIDs.unsafe_erase(nodeID);
|
||||||
|
}
|
||||||
emit ignoredNode(nodeID, false);
|
emit ignoredNode(nodeID, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -850,10 +858,14 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
|
||||||
void NodeList::removeFromIgnoreMuteSets(const QUuid& nodeID) {
|
void NodeList::removeFromIgnoreMuteSets(const QUuid& nodeID) {
|
||||||
// don't remove yourself, or nobody
|
// don't remove yourself, or nobody
|
||||||
if (!nodeID.isNull() && _sessionUUID != nodeID) {
|
if (!nodeID.isNull() && _sessionUUID != nodeID) {
|
||||||
QWriteLocker ignoredSetLocker{ &_ignoredSetLock };
|
{
|
||||||
QWriteLocker personalMutedSetLocker{ &_personalMutedSetLock };
|
QWriteLocker ignoredSetLocker{ &_ignoredSetLock };
|
||||||
_ignoredNodeIDs.unsafe_erase(nodeID);
|
_ignoredNodeIDs.unsafe_erase(nodeID);
|
||||||
_personalMutedNodeIDs.unsafe_erase(nodeID);
|
}
|
||||||
|
{
|
||||||
|
QWriteLocker personalMutedSetLocker{ &_personalMutedSetLock };
|
||||||
|
_personalMutedNodeIDs.unsafe_erase(nodeID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,14 @@ void UserActivityLoggerScriptingInterface::enabledEdit() {
|
||||||
logAction("enabled_edit");
|
logAction("enabled_edit");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UserActivityLoggerScriptingInterface::openedTablet() {
|
||||||
|
logAction("opened_tablet");
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserActivityLoggerScriptingInterface::closedTablet() {
|
||||||
|
logAction("closed_tablet");
|
||||||
|
}
|
||||||
|
|
||||||
void UserActivityLoggerScriptingInterface::openedMarketplace() {
|
void UserActivityLoggerScriptingInterface::openedMarketplace() {
|
||||||
logAction("opened_marketplace");
|
logAction("opened_marketplace");
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ class UserActivityLoggerScriptingInterface : public QObject, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Q_INVOKABLE void enabledEdit();
|
Q_INVOKABLE void enabledEdit();
|
||||||
|
Q_INVOKABLE void openedTablet();
|
||||||
|
Q_INVOKABLE void closedTablet();
|
||||||
Q_INVOKABLE void openedMarketplace();
|
Q_INVOKABLE void openedMarketplace();
|
||||||
Q_INVOKABLE void toggledAway(bool isAway);
|
Q_INVOKABLE void toggledAway(bool isAway);
|
||||||
Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete,
|
Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete,
|
||||||
|
|
|
@ -48,7 +48,8 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::EntityAdd:
|
case PacketType::EntityAdd:
|
||||||
case PacketType::EntityEdit:
|
case PacketType::EntityEdit:
|
||||||
case PacketType::EntityData:
|
case PacketType::EntityData:
|
||||||
return VERSION_ENTITIES_SERVER_SCRIPTS;
|
case PacketType::EntityPhysics:
|
||||||
|
return VERSION_ENTITIES_PHYSICS_PACKET;
|
||||||
case PacketType::EntityQuery:
|
case PacketType::EntityQuery:
|
||||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::JsonFilter);
|
return static_cast<PacketVersion>(EntityQueryPacketVersion::JsonFilter);
|
||||||
case PacketType::AvatarIdentity:
|
case PacketType::AvatarIdentity:
|
||||||
|
|
|
@ -110,7 +110,8 @@ public:
|
||||||
EntityScriptGetStatus,
|
EntityScriptGetStatus,
|
||||||
EntityScriptGetStatusReply,
|
EntityScriptGetStatusReply,
|
||||||
ReloadEntityServerScript,
|
ReloadEntityServerScript,
|
||||||
LAST_PACKET_TYPE = ReloadEntityServerScript
|
EntityPhysics,
|
||||||
|
LAST_PACKET_TYPE = EntityPhysics
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -201,6 +202,7 @@ const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63;
|
||||||
const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64;
|
const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64;
|
||||||
const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65;
|
const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65;
|
||||||
const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66;
|
const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66;
|
||||||
|
const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67;
|
||||||
|
|
||||||
enum class EntityQueryPacketVersion: PacketVersion {
|
enum class EntityQueryPacketVersion: PacketVersion {
|
||||||
JsonFilter = 18
|
JsonFilter = 18
|
||||||
|
|
|
@ -199,15 +199,12 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(entityTreeIsLocked());
|
assert(entityTreeIsLocked());
|
||||||
if (_motionType == MOTION_TYPE_KINEMATIC) {
|
if (_motionType == MOTION_TYPE_KINEMATIC && !_entity->hasAncestorOfType(NestableType::Avatar)) {
|
||||||
BT_PROFILE("kinematicIntegration");
|
BT_PROFILE("kinematicIntegration");
|
||||||
// This is physical kinematic motion which steps strictly by the subframe count
|
// This is physical kinematic motion which steps strictly by the subframe count
|
||||||
// of the physics simulation and uses full gravity for acceleration.
|
// of the physics simulation and uses full gravity for acceleration.
|
||||||
if (_entity->hasAncestorOfType(NestableType::Avatar)) {
|
_entity->setAcceleration(_entity->getGravity());
|
||||||
_entity->setAcceleration(glm::vec3(0.0f));
|
|
||||||
} else {
|
|
||||||
_entity->setAcceleration(_entity->getGravity());
|
|
||||||
}
|
|
||||||
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
|
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
|
||||||
float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||||
_entity->stepKinematicMotion(dt);
|
_entity->stepKinematicMotion(dt);
|
||||||
|
@ -614,7 +611,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
||||||
properties.setClientOnly(_entity->getClientOnly());
|
properties.setClientOnly(_entity->getClientOnly());
|
||||||
properties.setOwningAvatarID(_entity->getOwningAvatarID());
|
properties.setOwningAvatarID(_entity->getOwningAvatarID());
|
||||||
|
|
||||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, id, properties);
|
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties);
|
||||||
_entity->setLastBroadcast(now);
|
_entity->setLastBroadcast(now);
|
||||||
|
|
||||||
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
|
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
|
||||||
|
@ -630,7 +627,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
||||||
newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly());
|
newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly());
|
||||||
newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID());
|
newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID());
|
||||||
|
|
||||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree,
|
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree,
|
||||||
descendant->getID(), newQueryCubeProperties);
|
descendant->getID(), newQueryCubeProperties);
|
||||||
entityDescendant->setLastBroadcast(now);
|
entityDescendant->setLastBroadcast(now);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,7 @@ public:
|
||||||
virtual ~Decoder() { }
|
virtual ~Decoder() { }
|
||||||
virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) = 0;
|
virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) = 0;
|
||||||
|
|
||||||
// numFrames - number of samples (mono) or sample-pairs (stereo)
|
virtual void lostFrame(QByteArray& decodedBuffer) = 0;
|
||||||
virtual void trackLostFrames(int numFrames) = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CodecPlugin : public Plugin {
|
class CodecPlugin : public Plugin {
|
||||||
|
|
|
@ -360,7 +360,7 @@ glm::vec3 AABox::getClosestPointOnFace(const glm::vec3& point, BoxFace face) con
|
||||||
|
|
||||||
case MIN_Z_FACE:
|
case MIN_Z_FACE:
|
||||||
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z),
|
return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z),
|
||||||
glm::vec3(_corner.x + _scale.z, _corner.y + _scale.y, _corner.z));
|
glm::vec3(_corner.x + _scale.x, _corner.y + _scale.y, _corner.z));
|
||||||
|
|
||||||
default: //quiet windows warnings
|
default: //quiet windows warnings
|
||||||
case MAX_Z_FACE:
|
case MAX_Z_FACE:
|
||||||
|
|
|
@ -1034,6 +1034,13 @@ AACube SpatiallyNestable::getQueryAACube() const {
|
||||||
|
|
||||||
bool SpatiallyNestable::hasAncestorOfType(NestableType nestableType) const {
|
bool SpatiallyNestable::hasAncestorOfType(NestableType nestableType) const {
|
||||||
bool success;
|
bool success;
|
||||||
|
if (nestableType == NestableType::Avatar) {
|
||||||
|
QUuid parentID = getParentID();
|
||||||
|
if (parentID == AVATAR_SELF_ID) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SpatiallyNestablePointer parent = getParentPointer(success);
|
SpatiallyNestablePointer parent = getParentPointer(success);
|
||||||
if (!success || !parent) {
|
if (!success || !parent) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1048,6 +1055,14 @@ bool SpatiallyNestable::hasAncestorOfType(NestableType nestableType) const {
|
||||||
|
|
||||||
const QUuid SpatiallyNestable::findAncestorOfType(NestableType nestableType) const {
|
const QUuid SpatiallyNestable::findAncestorOfType(NestableType nestableType) const {
|
||||||
bool success;
|
bool success;
|
||||||
|
|
||||||
|
if (nestableType == NestableType::Avatar) {
|
||||||
|
QUuid parentID = getParentID();
|
||||||
|
if (parentID == AVATAR_SELF_ID) {
|
||||||
|
return AVATAR_SELF_ID; // TODO -- can we put nodeID here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SpatiallyNestablePointer parent = getParentPointer(success);
|
SpatiallyNestablePointer parent = getParentPointer(success);
|
||||||
if (!success || !parent) {
|
if (!success || !parent) {
|
||||||
return QUuid();
|
return QUuid();
|
||||||
|
|
|
@ -65,12 +65,10 @@ public:
|
||||||
AudioDecoder::process((const int16_t*)encodedBuffer.constData(), (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, true);
|
AudioDecoder::process((const int16_t*)encodedBuffer.constData(), (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void trackLostFrames(int numFrames) override {
|
virtual void lostFrame(QByteArray& decodedBuffer) override {
|
||||||
QByteArray encodedBuffer;
|
|
||||||
QByteArray decodedBuffer;
|
|
||||||
decodedBuffer.resize(_decodedSize);
|
decodedBuffer.resize(_decodedSize);
|
||||||
// NOTE: we don't actually use the results of this decode, we just do it to keep the state of the codec clean
|
// this performs packet loss interpolation
|
||||||
AudioDecoder::process((const int16_t*)encodedBuffer.constData(), (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, false);
|
AudioDecoder::process(nullptr, (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, false);
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
int _decodedSize;
|
int _decodedSize;
|
||||||
|
|
|
@ -38,11 +38,14 @@ public:
|
||||||
virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override {
|
virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override {
|
||||||
encodedBuffer = decodedBuffer;
|
encodedBuffer = decodedBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override {
|
virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override {
|
||||||
decodedBuffer = encodedBuffer;
|
decodedBuffer = encodedBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void trackLostFrames(int numFrames) override { }
|
virtual void lostFrame(QByteArray& decodedBuffer) override {
|
||||||
|
memset(decodedBuffer.data(), 0, decodedBuffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const char* NAME;
|
static const char* NAME;
|
||||||
|
@ -77,7 +80,9 @@ public:
|
||||||
decodedBuffer = qUncompress(encodedBuffer);
|
decodedBuffer = qUncompress(encodedBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void trackLostFrames(int numFrames) override { }
|
virtual void lostFrame(QByteArray& decodedBuffer) override {
|
||||||
|
memset(decodedBuffer.data(), 0, decodedBuffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const char* NAME;
|
static const char* NAME;
|
||||||
|
|
|
@ -2882,7 +2882,6 @@ function MyController(hand) {
|
||||||
this.touchingEnterTimer += dt;
|
this.touchingEnterTimer += dt;
|
||||||
|
|
||||||
if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && this.triggerSmoothedSqueezed()) {
|
if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && this.triggerSmoothedSqueezed()) {
|
||||||
this.setState(STATE_OFF, "trigger squeezed");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// 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
|
||||||
//
|
//
|
||||||
|
|
||||||
/* global Script, HMD, WebTablet, UIWebTablet */
|
/* global Script, HMD, WebTablet, UIWebTablet, UserActivityLogger, Settings, Entities, Messages, Tablet, Overlays, MyAvatar */
|
||||||
|
|
||||||
(function() { // BEGIN LOCAL_SCOPE
|
(function() { // BEGIN LOCAL_SCOPE
|
||||||
var tabletShown = false;
|
var tabletShown = false;
|
||||||
|
@ -65,8 +65,10 @@
|
||||||
hideTabletUI();
|
hideTabletUI();
|
||||||
HMD.closeTablet();
|
HMD.closeTablet();
|
||||||
} else if (HMD.showTablet && !tabletShown) {
|
} else if (HMD.showTablet && !tabletShown) {
|
||||||
|
UserActivityLogger.openedTablet();
|
||||||
showTabletUI();
|
showTabletUI();
|
||||||
} else if (!HMD.showTablet && tabletShown) {
|
} else if (!HMD.showTablet && tabletShown) {
|
||||||
|
UserActivityLogger.closedTablet();
|
||||||
hideTabletUI();
|
hideTabletUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +88,6 @@
|
||||||
var accumulatedLevel = 0.0;
|
var accumulatedLevel = 0.0;
|
||||||
// Note: Might have to tweak the following two based on the rate we're getting the data
|
// Note: Might have to tweak the following two based on the rate we're getting the data
|
||||||
var AVERAGING_RATIO = 0.05;
|
var AVERAGING_RATIO = 0.05;
|
||||||
var MIC_LEVEL_UPDATE_INTERVAL_MS = 100;
|
|
||||||
|
|
||||||
// Calculate microphone level with the same scaling equation (log scale, exponentially averaged) in AvatarInputs and pal.js
|
// Calculate microphone level with the same scaling equation (log scale, exponentially averaged) in AvatarInputs and pal.js
|
||||||
function getMicLevel() {
|
function getMicLevel() {
|
||||||
|
|
|
@ -169,3 +169,17 @@ void AABoxTests::testScale() {
|
||||||
box3 += glm::vec3(-1.0f, -1.0f, -1.0f);
|
box3 += glm::vec3(-1.0f, -1.0f, -1.0f);
|
||||||
QCOMPARE(box3.contains(glm::vec3(0.5f, 0.5f, 0.5f)), true);
|
QCOMPARE(box3.contains(glm::vec3(0.5f, 0.5f, 0.5f)), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AABoxTests::testFindSpherePenetration() {
|
||||||
|
vec3 searchPosition(-0.0141186f, 0.0640736f, -0.116081f);
|
||||||
|
float searchRadius = 0.5f;
|
||||||
|
|
||||||
|
vec3 boxMin(-0.800014f, -0.450025f, -0.00503815f);
|
||||||
|
vec3 boxDim(1.60003f, 0.900049f, 0.0100763f);
|
||||||
|
AABox testBox(boxMin, boxDim);
|
||||||
|
|
||||||
|
vec3 penetration;
|
||||||
|
bool hit = testBox.findSpherePenetration(searchPosition, searchRadius, penetration);
|
||||||
|
QCOMPARE(hit, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ private slots:
|
||||||
void testContainsPoint();
|
void testContainsPoint();
|
||||||
void testTouchesSphere();
|
void testTouchesSphere();
|
||||||
void testScale();
|
void testScale();
|
||||||
|
void testFindSpherePenetration();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AABoxTests_h
|
#endif // hifi_AABoxTests_h
|
||||||
|
|
Loading…
Reference in a new issue