mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 15:29:32 +02:00
Merge pull request #8285 from samcake/skin
Merging upstream master and fixing comments from review
This commit is contained in:
commit
569fdde7f1
31 changed files with 507 additions and 218 deletions
|
@ -525,7 +525,6 @@ void AudioMixer::handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> mess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
||||||
|
|
||||||
// FIXME - why would we not have client data at this point??
|
// FIXME - why would we not have client data at this point??
|
||||||
|
@ -539,14 +538,7 @@ void AudioMixer::handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> mess
|
||||||
clientData->setupCodec(selectedCodec, selectedCodecName);
|
clientData->setupCodec(selectedCodec, selectedCodecName);
|
||||||
|
|
||||||
qDebug() << "selectedCodecName:" << selectedCodecName;
|
qDebug() << "selectedCodecName:" << selectedCodecName;
|
||||||
|
clientData->sendSelectAudioFormat(sendingNode, selectedCodecName);
|
||||||
auto replyPacket = NLPacket::create(PacketType::SelectedAudioFormat);
|
|
||||||
|
|
||||||
// write them to our packet
|
|
||||||
replyPacket->writeString(selectedCodecName);
|
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
nodeList->sendPacket(std::move(replyPacket), *sendingNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
|
void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
|
||||||
|
@ -769,13 +761,18 @@ void AudioMixer::broadcastMixes() {
|
||||||
std::unique_ptr<NLPacket> mixPacket;
|
std::unique_ptr<NLPacket> mixPacket;
|
||||||
|
|
||||||
if (mixHasAudio) {
|
if (mixHasAudio) {
|
||||||
int mixPacketBytes = sizeof(quint16) + AudioConstants::NETWORK_FRAME_BYTES_STEREO;
|
int mixPacketBytes = sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE
|
||||||
|
+ AudioConstants::NETWORK_FRAME_BYTES_STEREO;
|
||||||
mixPacket = NLPacket::create(PacketType::MixedAudio, mixPacketBytes);
|
mixPacket = NLPacket::create(PacketType::MixedAudio, mixPacketBytes);
|
||||||
|
|
||||||
// pack sequence number
|
// pack sequence number
|
||||||
quint16 sequence = nodeData->getOutgoingSequenceNumber();
|
quint16 sequence = nodeData->getOutgoingSequenceNumber();
|
||||||
mixPacket->writePrimitive(sequence);
|
mixPacket->writePrimitive(sequence);
|
||||||
|
|
||||||
|
// write the codec
|
||||||
|
QString codecInPacket = nodeData->getCodecName();
|
||||||
|
mixPacket->writeString(codecInPacket);
|
||||||
|
|
||||||
QByteArray decodedBuffer(reinterpret_cast<char*>(_clampedSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
QByteArray decodedBuffer(reinterpret_cast<char*>(_clampedSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||||
QByteArray encodedBuffer;
|
QByteArray encodedBuffer;
|
||||||
nodeData->encode(decodedBuffer, encodedBuffer);
|
nodeData->encode(decodedBuffer, encodedBuffer);
|
||||||
|
@ -783,13 +780,17 @@ void AudioMixer::broadcastMixes() {
|
||||||
// pack mixed audio samples
|
// pack mixed audio samples
|
||||||
mixPacket->write(encodedBuffer.constData(), encodedBuffer.size());
|
mixPacket->write(encodedBuffer.constData(), encodedBuffer.size());
|
||||||
} else {
|
} else {
|
||||||
int silentPacketBytes = sizeof(quint16) + sizeof(quint16);
|
int silentPacketBytes = sizeof(quint16) + sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE;
|
||||||
mixPacket = NLPacket::create(PacketType::SilentAudioFrame, silentPacketBytes);
|
mixPacket = NLPacket::create(PacketType::SilentAudioFrame, silentPacketBytes);
|
||||||
|
|
||||||
// pack sequence number
|
// pack sequence number
|
||||||
quint16 sequence = nodeData->getOutgoingSequenceNumber();
|
quint16 sequence = nodeData->getOutgoingSequenceNumber();
|
||||||
mixPacket->writePrimitive(sequence);
|
mixPacket->writePrimitive(sequence);
|
||||||
|
|
||||||
|
// write the codec
|
||||||
|
QString codecInPacket = nodeData->getCodecName();
|
||||||
|
mixPacket->writeString(codecInPacket);
|
||||||
|
|
||||||
// pack number of silent audio samples
|
// pack number of silent audio samples
|
||||||
quint16 numSilentSamples = AudioConstants::NETWORK_FRAME_SAMPLES_STEREO;
|
quint16 numSilentSamples = AudioConstants::NETWORK_FRAME_SAMPLES_STEREO;
|
||||||
mixPacket->writePrimitive(numSilentSamples);
|
mixPacket->writePrimitive(numSilentSamples);
|
||||||
|
|
|
@ -113,6 +113,8 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
||||||
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
|
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
|
||||||
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||||
|
|
||||||
|
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::sendSelectAudioFormat);
|
||||||
|
|
||||||
auto emplaced = _audioStreams.emplace(
|
auto emplaced = _audioStreams.emplace(
|
||||||
QUuid(),
|
QUuid(),
|
||||||
std::unique_ptr<PositionalAudioStream> { avatarAudioStream }
|
std::unique_ptr<PositionalAudioStream> { avatarAudioStream }
|
||||||
|
@ -128,7 +130,6 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
||||||
isMicStream = true;
|
isMicStream = true;
|
||||||
} else if (packetType == PacketType::InjectAudio) {
|
} else if (packetType == PacketType::InjectAudio) {
|
||||||
// this is injected audio
|
// this is injected audio
|
||||||
|
|
||||||
// grab the stream identifier for this injected audio
|
// grab the stream identifier for this injected audio
|
||||||
message.seek(sizeof(quint16));
|
message.seek(sizeof(quint16));
|
||||||
QUuid streamIdentifier = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
QUuid streamIdentifier = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
|
@ -344,6 +345,14 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioMixerClientData::sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName) {
|
||||||
|
auto replyPacket = NLPacket::create(PacketType::SelectedAudioFormat);
|
||||||
|
replyPacket->writeString(selectedCodecName);
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
nodeList->sendPacket(std::move(replyPacket), *node);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void AudioMixerClientData::setupCodec(CodecPluginPointer codec, const QString& codecName) {
|
void AudioMixerClientData::setupCodec(CodecPluginPointer codec, const QString& codecName) {
|
||||||
cleanupCodec(); // cleanup any previously allocated coders first
|
cleanupCodec(); // cleanup any previously allocated coders first
|
||||||
_codec = codec;
|
_codec = codec;
|
||||||
|
|
|
@ -78,9 +78,14 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString getCodecName() { return _selectedCodecName; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void injectorStreamFinished(const QUuid& streamIdentifier);
|
void injectorStreamFinished(const QUuid& streamIdentifier);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QReadWriteLock _streamsLock;
|
QReadWriteLock _streamsLock;
|
||||||
AudioStreamMap _audioStreams; // microphone stream from avatar is stored under key of null UUID
|
AudioStreamMap _audioStreams; // microphone stream from avatar is stored under key of null UUID
|
||||||
|
|
|
@ -1083,9 +1083,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
// Setup the domain object to send to the data server
|
// Setup the domain object to send to the data server
|
||||||
QJsonObject domainObject;
|
QJsonObject domainObject;
|
||||||
|
|
||||||
// add the version
|
// add the versions
|
||||||
static const QString VERSION_KEY = "version";
|
static const QString VERSION_KEY = "version";
|
||||||
domainObject[VERSION_KEY] = BuildInfo::VERSION;
|
domainObject[VERSION_KEY] = BuildInfo::VERSION;
|
||||||
|
static const QString PROTOCOL_KEY = "protocol";
|
||||||
|
domainObject[PROTOCOL_KEY] = protocolVersionsSignatureBase64();
|
||||||
|
|
||||||
// add networking
|
// add networking
|
||||||
if (!networkAddress.isEmpty()) {
|
if (!networkAddress.isEmpty()) {
|
||||||
|
@ -1119,7 +1121,12 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
||||||
|
|
||||||
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
QString path = DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID()));
|
||||||
|
#if DEV_BUILD || PR_BUILD
|
||||||
|
qDebug() << "Domain metadata sent to" << path;
|
||||||
|
qDebug() << "Domain metadata update:" << domainUpdateJSON;
|
||||||
|
#endif
|
||||||
|
DependencyManager::get<AccountManager>()->sendRequest(path,
|
||||||
AccountManagerAuth::Optional,
|
AccountManagerAuth::Optional,
|
||||||
QNetworkAccessManager::PutOperation,
|
QNetworkAccessManager::PutOperation,
|
||||||
JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"),
|
JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"),
|
||||||
|
|
|
@ -58,6 +58,7 @@ set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
|
||||||
# qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS})
|
# qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS})
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
|
||||||
# configure CMake to use a custom Info.plist
|
# configure CMake to use a custom Info.plist
|
||||||
set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in)
|
set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in)
|
||||||
|
|
||||||
|
@ -229,6 +230,13 @@ if (APPLE)
|
||||||
|
|
||||||
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources")
|
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources")
|
||||||
|
|
||||||
|
# copy script files beside the executable
|
||||||
|
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||||
|
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||||
|
"${CMAKE_SOURCE_DIR}/scripts"
|
||||||
|
$<TARGET_FILE_DIR:${TARGET_NAME}>/../Resources/scripts
|
||||||
|
)
|
||||||
|
|
||||||
# call the fixup_interface macro to add required bundling commands for installation
|
# call the fixup_interface macro to add required bundling commands for installation
|
||||||
fixup_interface()
|
fixup_interface()
|
||||||
|
|
||||||
|
@ -263,6 +271,7 @@ else (APPLE)
|
||||||
endif (APPLE)
|
endif (APPLE)
|
||||||
|
|
||||||
if (SCRIPTS_INSTALL_DIR)
|
if (SCRIPTS_INSTALL_DIR)
|
||||||
|
|
||||||
# setup install of scripts beside interface executable
|
# setup install of scripts beside interface executable
|
||||||
install(
|
install(
|
||||||
DIRECTORY "${CMAKE_SOURCE_DIR}/scripts/"
|
DIRECTORY "${CMAKE_SOURCE_DIR}/scripts/"
|
||||||
|
|
BIN
interface/resources/images/default-domain.gif
Normal file
BIN
interface/resources/images/default-domain.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
|
@ -13,6 +13,7 @@ import QtQuick 2.4
|
||||||
import "controls"
|
import "controls"
|
||||||
import "styles"
|
import "styles"
|
||||||
import "windows"
|
import "windows"
|
||||||
|
import "hifi"
|
||||||
|
|
||||||
Window {
|
Window {
|
||||||
id: root
|
id: root
|
||||||
|
@ -44,11 +45,50 @@ Window {
|
||||||
anchors.centerIn = parent;
|
anchors.centerIn = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goCard(card) {
|
||||||
|
addressLine.text = card.userStory.name;
|
||||||
|
toggleOrGo(true);
|
||||||
|
}
|
||||||
|
property var allDomains: [];
|
||||||
|
property var suggestionChoices: [];
|
||||||
|
property var domainsBaseUrl: null;
|
||||||
|
property int cardWidth: 200;
|
||||||
|
property int cardHeight: 152;
|
||||||
|
|
||||||
AddressBarDialog {
|
AddressBarDialog {
|
||||||
id: addressBarDialog
|
id: addressBarDialog
|
||||||
implicitWidth: backgroundImage.width
|
implicitWidth: backgroundImage.width
|
||||||
implicitHeight: backgroundImage.height
|
implicitHeight: backgroundImage.height
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: backgroundImage.width;
|
||||||
|
anchors {
|
||||||
|
bottom: backgroundImage.top;
|
||||||
|
bottomMargin: 2 * hifi.layout.spacing;
|
||||||
|
right: backgroundImage.right;
|
||||||
|
rightMargin: -104; // FIXME
|
||||||
|
}
|
||||||
|
spacing: hifi.layout.spacing;
|
||||||
|
Card {
|
||||||
|
id: s0;
|
||||||
|
width: cardWidth;
|
||||||
|
height: cardHeight;
|
||||||
|
goFunction: goCard
|
||||||
|
}
|
||||||
|
Card {
|
||||||
|
id: s1;
|
||||||
|
width: cardWidth;
|
||||||
|
height: cardHeight;
|
||||||
|
goFunction: goCard
|
||||||
|
}
|
||||||
|
Card {
|
||||||
|
id: s2;
|
||||||
|
width: cardWidth;
|
||||||
|
height: cardHeight;
|
||||||
|
goFunction: goCard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: backgroundImage
|
id: backgroundImage
|
||||||
source: "../images/address-bar.svg"
|
source: "../images/address-bar.svg"
|
||||||
|
@ -130,22 +170,178 @@ Window {
|
||||||
}
|
}
|
||||||
font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.75
|
font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.75
|
||||||
helperText: "Go to: place, @user, /path, network address"
|
helperText: "Go to: place, @user, /path, network address"
|
||||||
|
onTextChanged: filterChoicesByText()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects.
|
||||||
|
// TODO: make available to other .qml.
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
// QT bug: apparently doesn't handle onload. Workaround using readyState.
|
||||||
|
request.onreadystatechange = function () {
|
||||||
|
var READY_STATE_DONE = 4;
|
||||||
|
var HTTP_OK = 200;
|
||||||
|
if (request.readyState >= READY_STATE_DONE) {
|
||||||
|
var error = (request.status !== HTTP_OK) && request.status.toString() + ':' + request.statusText,
|
||||||
|
response = !error && request.responseText,
|
||||||
|
contentType = !error && request.getResponseHeader('content-type');
|
||||||
|
if (!error && contentType.indexOf('application/json') === 0) {
|
||||||
|
try {
|
||||||
|
response = JSON.parse(response);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb(error, response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.open("GET", url, true);
|
||||||
|
request.send();
|
||||||
|
}
|
||||||
|
// call iterator(element, icb) once for each element of array, and then cb(error) when icb(error) has been called by each iterator.
|
||||||
|
// short-circuits if error. Note that iterator MUST be an asynchronous function. (Use setTimeout if necessary.)
|
||||||
|
function asyncEach(array, iterator, cb) {
|
||||||
|
var count = array.length;
|
||||||
|
function icb(error) {
|
||||||
|
if (!--count || error) {
|
||||||
|
count = -1; // don't cb multiple times (e.g., if error)
|
||||||
|
cb(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!count) {
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
array.forEach(function (element) {
|
||||||
|
iterator(element, icb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function identity(x) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPictureToDomain(domainInfo, cb) { // asynchronously add thumbnail and lobby to domainInfo, if available, and cb(error)
|
||||||
|
// This requests data for all the names at once, and just uses the first one to come back.
|
||||||
|
// We might change this to check one at a time, which would be less requests and more latency.
|
||||||
|
asyncEach([domainInfo.name].concat(domainInfo.names || null).filter(identity), function (name, icb) {
|
||||||
|
var url = "https://metaverse.highfidelity.com/api/v1/places/" + name;
|
||||||
|
getRequest(url, function (error, json) {
|
||||||
|
var previews = !error && json.data.place.previews;
|
||||||
|
if (previews) {
|
||||||
|
if (!domainInfo.thumbnail) { // just grab the first one
|
||||||
|
domainInfo.thumbnail = previews.thumbnail;
|
||||||
|
}
|
||||||
|
if (!domainInfo.lobby) {
|
||||||
|
domainInfo.lobby = previews.lobby;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
icb(error);
|
||||||
|
});
|
||||||
|
}, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDomains(options, cb) { // cb(error, arrayOfData)
|
||||||
|
if (!options.page) {
|
||||||
|
options.page = 1;
|
||||||
|
}
|
||||||
|
if (!domainsBaseUrl) {
|
||||||
|
var domainsOptions = [
|
||||||
|
'open', // published hours handle now
|
||||||
|
'active', // has at least one person connected. FIXME: really want any place that is verified accessible.
|
||||||
|
// FIXME: really want places I'm allowed in, not just open ones.
|
||||||
|
'restriction=open', // Not by whitelist, etc. FIXME: If logged in, add hifi to the restriction options, in order to include places that require login.
|
||||||
|
// FIXME add maturity
|
||||||
|
'protocol=' + encodeURIComponent(AddressManager.protocolVersion()),
|
||||||
|
'sort_by=users',
|
||||||
|
'sort_order=desc',
|
||||||
|
];
|
||||||
|
domainsBaseUrl = "https://metaverse.highfidelity.com/api/v1/domains/all?" + domainsOptions.join('&');
|
||||||
|
}
|
||||||
|
var url = domainsBaseUrl + "&page=" + options.page + "&users=" + options.minUsers + "-" + options.maxUsers;
|
||||||
|
getRequest(url, function (error, json) {
|
||||||
|
if (!error && (json.status !== 'success')) {
|
||||||
|
error = new Error("Bad response: " + JSON.stringify(json));
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
error.message += ' for ' + url;
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
var domains = json.data.domains;
|
||||||
|
if (json.current_page < json.total_pages) {
|
||||||
|
options.page++;
|
||||||
|
return getDomains(options, function (error, others) {
|
||||||
|
cb(error, domains.concat(others));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cb(null, domains);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterChoicesByText() {
|
||||||
|
function fill1(target, data) {
|
||||||
|
if (!data) {
|
||||||
|
target.visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('suggestion:', JSON.stringify(data));
|
||||||
|
target.userStory = data;
|
||||||
|
target.image.source = data.lobby || target.defaultPicture;
|
||||||
|
target.placeText = data.name;
|
||||||
|
target.usersText = data.online_users + ((data.online_users === 1) ? ' user' : ' users');
|
||||||
|
target.visible = true;
|
||||||
|
}
|
||||||
|
var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity);
|
||||||
|
var filtered = !words.length ? suggestionChoices : allDomains.filter(function (domain) {
|
||||||
|
var text = domain.names.concat(domain.tags).join(' ');
|
||||||
|
if (domain.description) {
|
||||||
|
text += domain.description;
|
||||||
|
}
|
||||||
|
text = text.toUpperCase();
|
||||||
|
return words.every(function (word) {
|
||||||
|
return text.indexOf(word) >= 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
fill1(s0, filtered[0]);
|
||||||
|
fill1(s1, filtered[1]);
|
||||||
|
fill1(s2, filtered[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillDestinations() {
|
||||||
|
allDomains = suggestionChoices = [];
|
||||||
|
getDomains({minUsers: 0, maxUsers: 20}, function (error, domains) {
|
||||||
|
if (error) {
|
||||||
|
console.log('domain query failed:', error);
|
||||||
|
return filterChoicesByText();
|
||||||
|
}
|
||||||
|
var here = AddressManager.hostname; // don't show where we are now.
|
||||||
|
allDomains = domains.filter(function (domain) { return domain.name !== here; });
|
||||||
|
// Whittle down suggestions to those that have at least one user, and try to get pictures.
|
||||||
|
suggestionChoices = allDomains.filter(function (domain) { return domain.online_users; });
|
||||||
|
asyncEach(domains, addPictureToDomain, function (error) {
|
||||||
|
if (error) {
|
||||||
|
console.log('place picture query failed:', error);
|
||||||
|
}
|
||||||
|
// Whittle down more by requiring a picture.
|
||||||
|
suggestionChoices = suggestionChoices.filter(function (domain) { return domain.lobby; });
|
||||||
|
filterChoicesByText();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
addressLine.forceActiveFocus()
|
addressLine.forceActiveFocus()
|
||||||
|
fillDestinations();
|
||||||
} else {
|
} else {
|
||||||
addressLine.text = ""
|
addressLine.text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleOrGo() {
|
function toggleOrGo(fromSuggestions) {
|
||||||
if (addressLine.text !== "") {
|
if (addressLine.text !== "") {
|
||||||
addressBarDialog.loadAddress(addressLine.text)
|
addressBarDialog.loadAddress(addressLine.text, fromSuggestions)
|
||||||
}
|
}
|
||||||
root.shown = false;
|
root.shown = false;
|
||||||
}
|
}
|
||||||
|
|
96
interface/resources/qml/hifi/Card.qml
Normal file
96
interface/resources/qml/hifi/Card.qml
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
//
|
||||||
|
// Card.qml
|
||||||
|
// qml/hifi
|
||||||
|
//
|
||||||
|
// Displays a clickable card representing a user story or destination.
|
||||||
|
//
|
||||||
|
// Created by Howard Stearns on 7/13/2016
|
||||||
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import Hifi 1.0
|
||||||
|
import QtQuick 2.5
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import "../styles-uit"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
property var goFunction: null;
|
||||||
|
property var userStory: null;
|
||||||
|
property alias image: lobby;
|
||||||
|
property alias placeText: place.text;
|
||||||
|
property alias usersText: users.text;
|
||||||
|
property int textPadding: 20;
|
||||||
|
property int textSize: 24;
|
||||||
|
property string defaultPicture: "../../images/default-domain.gif";
|
||||||
|
HifiConstants { id: hifi }
|
||||||
|
Image {
|
||||||
|
id: lobby;
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
source: defaultPicture;
|
||||||
|
fillMode: Image.PreserveAspectCrop;
|
||||||
|
// source gets filled in later
|
||||||
|
anchors.verticalCenter: parent.verticalCenter;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status == Image.Error) {
|
||||||
|
console.log("source: " + source + ": failed to load " + JSON.stringify(userStory));
|
||||||
|
source = defaultPicture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property int dropHorizontalOffset: 0;
|
||||||
|
property int dropVerticalOffset: 1;
|
||||||
|
property int dropRadius: 2;
|
||||||
|
property int dropSamples: 9;
|
||||||
|
property int dropSpread: 0;
|
||||||
|
DropShadow {
|
||||||
|
source: place;
|
||||||
|
anchors.fill: place;
|
||||||
|
horizontalOffset: dropHorizontalOffset;
|
||||||
|
verticalOffset: dropVerticalOffset;
|
||||||
|
radius: dropRadius;
|
||||||
|
samples: dropSamples;
|
||||||
|
color: hifi.colors.black;
|
||||||
|
spread: dropSpread;
|
||||||
|
}
|
||||||
|
DropShadow {
|
||||||
|
source: users;
|
||||||
|
anchors.fill: users;
|
||||||
|
horizontalOffset: dropHorizontalOffset;
|
||||||
|
verticalOffset: dropVerticalOffset;
|
||||||
|
radius: dropRadius;
|
||||||
|
samples: dropSamples;
|
||||||
|
color: hifi.colors.black;
|
||||||
|
spread: dropSpread;
|
||||||
|
}
|
||||||
|
RalewaySemiBold {
|
||||||
|
id: place;
|
||||||
|
color: hifi.colors.white;
|
||||||
|
size: textSize;
|
||||||
|
anchors {
|
||||||
|
top: parent.top;
|
||||||
|
left: parent.left;
|
||||||
|
margins: textPadding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RalewayRegular {
|
||||||
|
id: users;
|
||||||
|
size: textSize;
|
||||||
|
color: hifi.colors.white;
|
||||||
|
anchors {
|
||||||
|
bottom: parent.bottom;
|
||||||
|
right: parent.right;
|
||||||
|
margins: textPadding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent;
|
||||||
|
acceptedButtons: Qt.LeftButton;
|
||||||
|
onClicked: goFunction(parent);
|
||||||
|
hoverEnabled: true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2747,7 +2747,7 @@ void Application::touchUpdateEvent(QTouchEvent* event) {
|
||||||
if (_keyboardMouseDevice->isActive()) {
|
if (_keyboardMouseDevice->isActive()) {
|
||||||
_keyboardMouseDevice->touchUpdateEvent(event);
|
_keyboardMouseDevice->touchUpdateEvent(event);
|
||||||
}
|
}
|
||||||
if (_touchscreenDevice->isActive()) {
|
if (_touchscreenDevice && _touchscreenDevice->isActive()) {
|
||||||
_touchscreenDevice->touchUpdateEvent(event);
|
_touchscreenDevice->touchUpdateEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2768,7 +2768,7 @@ void Application::touchBeginEvent(QTouchEvent* event) {
|
||||||
if (_keyboardMouseDevice->isActive()) {
|
if (_keyboardMouseDevice->isActive()) {
|
||||||
_keyboardMouseDevice->touchBeginEvent(event);
|
_keyboardMouseDevice->touchBeginEvent(event);
|
||||||
}
|
}
|
||||||
if (_touchscreenDevice->isActive()) {
|
if (_touchscreenDevice && _touchscreenDevice->isActive()) {
|
||||||
_touchscreenDevice->touchBeginEvent(event);
|
_touchscreenDevice->touchBeginEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2788,7 +2788,7 @@ void Application::touchEndEvent(QTouchEvent* event) {
|
||||||
if (_keyboardMouseDevice->isActive()) {
|
if (_keyboardMouseDevice->isActive()) {
|
||||||
_keyboardMouseDevice->touchEndEvent(event);
|
_keyboardMouseDevice->touchEndEvent(event);
|
||||||
}
|
}
|
||||||
if (_touchscreenDevice->isActive()) {
|
if (_touchscreenDevice && _touchscreenDevice->isActive()) {
|
||||||
_touchscreenDevice->touchEndEvent(event);
|
_touchscreenDevice->touchEndEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2796,7 +2796,7 @@ void Application::touchEndEvent(QTouchEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::touchGestureEvent(QGestureEvent* event) {
|
void Application::touchGestureEvent(QGestureEvent* event) {
|
||||||
if (_touchscreenDevice->isActive()) {
|
if (_touchscreenDevice && _touchscreenDevice->isActive()) {
|
||||||
_touchscreenDevice->touchGestureEvent(event);
|
_touchscreenDevice->touchGestureEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,10 +40,10 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare
|
||||||
_forwardEnabled = !(DependencyManager::get<AddressManager>()->getForwardStack().isEmpty());
|
_forwardEnabled = !(DependencyManager::get<AddressManager>()->getForwardStack().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressBarDialog::loadAddress(const QString& address) {
|
void AddressBarDialog::loadAddress(const QString& address, bool fromSuggestions) {
|
||||||
qDebug() << "Called LoadAddress with address " << address;
|
qDebug() << "Called LoadAddress with address " << address;
|
||||||
if (!address.isEmpty()) {
|
if (!address.isEmpty()) {
|
||||||
DependencyManager::get<AddressManager>()->handleLookupString(address);
|
DependencyManager::get<AddressManager>()->handleLookupString(address, fromSuggestions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ protected:
|
||||||
void displayAddressOfflineMessage();
|
void displayAddressOfflineMessage();
|
||||||
void displayAddressNotFoundMessage();
|
void displayAddressNotFoundMessage();
|
||||||
|
|
||||||
Q_INVOKABLE void loadAddress(const QString& address);
|
Q_INVOKABLE void loadAddress(const QString& address, bool fromSuggestions = false);
|
||||||
Q_INVOKABLE void loadHome();
|
Q_INVOKABLE void loadHome();
|
||||||
Q_INVOKABLE void loadBack();
|
Q_INVOKABLE void loadBack();
|
||||||
Q_INVOKABLE void loadForward();
|
Q_INVOKABLE void loadForward();
|
||||||
|
|
|
@ -834,7 +834,7 @@ void AudioClient::handleAudioInput() {
|
||||||
encodedBuffer = decocedBuffer;
|
encodedBuffer = decocedBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, packetType);
|
emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, packetType, _selectedCodecName);
|
||||||
_stats.sentPacket();
|
_stats.sentPacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -852,7 +852,7 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME check a flag to see if we should echo audio?
|
// FIXME check a flag to see if we should echo audio?
|
||||||
emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, PacketType::MicrophoneAudioWithEcho);
|
emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, PacketType::MicrophoneAudioWithEcho, _selectedCodecName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioClient::mixLocalAudioInjectors(int16_t* inputBuffer) {
|
void AudioClient::mixLocalAudioInjectors(int16_t* inputBuffer) {
|
||||||
|
@ -1015,7 +1015,6 @@ bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) {
|
||||||
// no reason to lock access to the vector of injectors.
|
// no reason to lock access to the vector of injectors.
|
||||||
if (!_activeLocalAudioInjectors.contains(injector)) {
|
if (!_activeLocalAudioInjectors.contains(injector)) {
|
||||||
qDebug() << "adding new injector";
|
qDebug() << "adding new injector";
|
||||||
|
|
||||||
_activeLocalAudioInjectors.append(injector);
|
_activeLocalAudioInjectors.append(injector);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "injector exists in active list already";
|
qDebug() << "injector exists in active list already";
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
|
|
||||||
#include "AudioConstants.h"
|
#include "AudioConstants.h"
|
||||||
|
|
||||||
void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform, PacketType packetType) {
|
void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber,
|
||||||
|
const Transform& transform, PacketType packetType, QString codecName) {
|
||||||
static std::mutex _mutex;
|
static std::mutex _mutex;
|
||||||
using Locker = std::unique_lock<std::mutex>;
|
using Locker = std::unique_lock<std::mutex>;
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
@ -27,10 +28,17 @@ void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes
|
||||||
if (audioMixer && audioMixer->getActiveSocket()) {
|
if (audioMixer && audioMixer->getActiveSocket()) {
|
||||||
Locker lock(_mutex);
|
Locker lock(_mutex);
|
||||||
auto audioPacket = NLPacket::create(packetType);
|
auto audioPacket = NLPacket::create(packetType);
|
||||||
|
|
||||||
|
// FIXME - this is not a good way to determine stereoness with codecs....
|
||||||
quint8 isStereo = bytes == AudioConstants::NETWORK_FRAME_BYTES_STEREO ? 1 : 0;
|
quint8 isStereo = bytes == AudioConstants::NETWORK_FRAME_BYTES_STEREO ? 1 : 0;
|
||||||
|
|
||||||
// write sequence number
|
// write sequence number
|
||||||
audioPacket->writePrimitive(sequenceNumber++);
|
auto sequence = sequenceNumber++;
|
||||||
|
audioPacket->writePrimitive(sequence);
|
||||||
|
|
||||||
|
// write the codec
|
||||||
|
audioPacket->writeString(codecName);
|
||||||
|
|
||||||
if (packetType == PacketType::SilentAudioFrame) {
|
if (packetType == PacketType::SilentAudioFrame) {
|
||||||
// pack num silent samples
|
// pack num silent samples
|
||||||
quint16 numSilentSamples = isStereo ?
|
quint16 numSilentSamples = isStereo ?
|
||||||
|
@ -49,8 +57,8 @@ void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes
|
||||||
|
|
||||||
if (audioPacket->getType() != PacketType::SilentAudioFrame) {
|
if (audioPacket->getType() != PacketType::SilentAudioFrame) {
|
||||||
// audio samples have already been packed (written to networkAudioSamples)
|
// audio samples have already been packed (written to networkAudioSamples)
|
||||||
audioPacket->setPayloadSize(audioPacket->getPayloadSize() + bytes);
|
int leadingBytes = audioPacket->getPayloadSize();
|
||||||
static const int leadingBytes = sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8);
|
audioPacket->setPayloadSize(leadingBytes + bytes);
|
||||||
memcpy(audioPacket->getPayload() + leadingBytes, audioData, bytes);
|
memcpy(audioPacket->getPayload() + leadingBytes, audioData, bytes);
|
||||||
}
|
}
|
||||||
nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket);
|
nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket);
|
||||||
|
|
|
@ -28,7 +28,8 @@ class AbstractAudioInterface : public QObject {
|
||||||
public:
|
public:
|
||||||
AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {};
|
AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {};
|
||||||
|
|
||||||
static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform, PacketType packetType);
|
static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform,
|
||||||
|
PacketType packetType, QString codecName = QString(""));
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual bool outputLocalInjector(bool isStereo, AudioInjector* injector) = 0;
|
virtual bool outputLocalInjector(bool isStereo, AudioInjector* injector) = 0;
|
||||||
|
|
|
@ -26,6 +26,8 @@ namespace AudioConstants {
|
||||||
|
|
||||||
inline const char* getAudioFrameName() { return "com.highfidelity.recording.Audio"; }
|
inline const char* getAudioFrameName() { return "com.highfidelity.recording.Audio"; }
|
||||||
|
|
||||||
|
const int MAX_CODEC_NAME_LENGTH = 30;
|
||||||
|
const int MAX_CODEC_NAME_LENGTH_ON_WIRE = MAX_CODEC_NAME_LENGTH + sizeof(uint32_t);
|
||||||
const int NETWORK_FRAME_BYTES_STEREO = 1024;
|
const int NETWORK_FRAME_BYTES_STEREO = 1024;
|
||||||
const int NETWORK_FRAME_SAMPLES_STEREO = NETWORK_FRAME_BYTES_STEREO / sizeof(AudioSample);
|
const int NETWORK_FRAME_SAMPLES_STEREO = NETWORK_FRAME_BYTES_STEREO / sizeof(AudioSample);
|
||||||
const int NETWORK_FRAME_BYTES_PER_CHANNEL = 512;
|
const int NETWORK_FRAME_BYTES_PER_CHANNEL = 512;
|
||||||
|
|
|
@ -214,6 +214,14 @@ const uchar MAX_INJECTOR_VOLUME = 0xFF;
|
||||||
static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1;
|
static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1;
|
||||||
static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;
|
static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;
|
||||||
|
|
||||||
|
qint64 writeStringToStream(const QString& string, QDataStream& stream) {
|
||||||
|
QByteArray data = string.toUtf8();
|
||||||
|
uint32_t length = data.length();
|
||||||
|
stream << static_cast<quint32>(length);
|
||||||
|
stream << data;
|
||||||
|
return length + sizeof(uint32_t);
|
||||||
|
}
|
||||||
|
|
||||||
int64_t AudioInjector::injectNextFrame() {
|
int64_t AudioInjector::injectNextFrame() {
|
||||||
if (stateHas(AudioInjectorState::NetworkInjectionFinished)) {
|
if (stateHas(AudioInjectorState::NetworkInjectionFinished)) {
|
||||||
qDebug() << "AudioInjector::injectNextFrame called but AudioInjector has finished and was not restarted. Returning.";
|
qDebug() << "AudioInjector::injectNextFrame called but AudioInjector has finished and was not restarted. Returning.";
|
||||||
|
@ -260,6 +268,10 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
// pack some placeholder sequence number for now
|
// pack some placeholder sequence number for now
|
||||||
audioPacketStream << (quint16) 0;
|
audioPacketStream << (quint16) 0;
|
||||||
|
|
||||||
|
// current injectors don't use codecs, so pack in the unknown codec name
|
||||||
|
QString noCodecForInjectors("");
|
||||||
|
writeStringToStream(noCodecForInjectors, audioPacketStream);
|
||||||
|
|
||||||
// pack stream identifier (a generated UUID)
|
// pack stream identifier (a generated UUID)
|
||||||
audioPacketStream << QUuid::createUuid();
|
audioPacketStream << QUuid::createUuid();
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
#include <NLPacket.h>
|
#include <NLPacket.h>
|
||||||
#include <Node.h>
|
#include <Node.h>
|
||||||
|
#include <NodeList.h>
|
||||||
|
|
||||||
#include "InboundAudioStream.h"
|
#include "InboundAudioStream.h"
|
||||||
|
|
||||||
|
@ -58,6 +59,7 @@ void InboundAudioStream::reset() {
|
||||||
_isStarved = true;
|
_isStarved = true;
|
||||||
_hasStarted = false;
|
_hasStarted = false;
|
||||||
resetStats();
|
resetStats();
|
||||||
|
cleanupCodec();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InboundAudioStream::resetStats() {
|
void InboundAudioStream::resetStats() {
|
||||||
|
@ -99,12 +101,12 @@ void InboundAudioStream::perSecondCallbackForUpdatingStats() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int InboundAudioStream::parseData(ReceivedMessage& message) {
|
int InboundAudioStream::parseData(ReceivedMessage& message) {
|
||||||
|
|
||||||
// parse sequence number and track it
|
// parse sequence number and track it
|
||||||
quint16 sequence;
|
quint16 sequence;
|
||||||
message.readPrimitive(&sequence);
|
message.readPrimitive(&sequence);
|
||||||
SequenceNumberStats::ArrivalInfo arrivalInfo = _incomingSequenceNumberStats.sequenceNumberReceived(sequence,
|
SequenceNumberStats::ArrivalInfo arrivalInfo = _incomingSequenceNumberStats.sequenceNumberReceived(sequence,
|
||||||
message.getSourceID());
|
message.getSourceID());
|
||||||
|
QString codecInPacket = message.readString();
|
||||||
|
|
||||||
packetReceivedUpdateTimingStats();
|
packetReceivedUpdateTimingStats();
|
||||||
|
|
||||||
|
@ -129,9 +131,22 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
|
||||||
case SequenceNumberStats::OnTime: {
|
case SequenceNumberStats::OnTime: {
|
||||||
// Packet is on time; parse its data to the ringbuffer
|
// Packet is on time; parse its data to the ringbuffer
|
||||||
if (message.getType() == PacketType::SilentAudioFrame) {
|
if (message.getType() == PacketType::SilentAudioFrame) {
|
||||||
|
// FIXME - Some codecs need to know about these silent frames... and can produce better output
|
||||||
writeDroppableSilentSamples(networkSamples);
|
writeDroppableSilentSamples(networkSamples);
|
||||||
} else {
|
} else {
|
||||||
parseAudioData(message.getType(), message.readWithoutCopy(message.getBytesLeftToRead()));
|
// note: PCM and no codec are identical
|
||||||
|
bool selectedPCM = _selectedCodecName == "pcm" || _selectedCodecName == "";
|
||||||
|
bool packetPCM = codecInPacket == "pcm" || codecInPacket == "";
|
||||||
|
if (codecInPacket == _selectedCodecName || (packetPCM && selectedPCM)) {
|
||||||
|
auto afterProperties = message.readWithoutCopy(message.getBytesLeftToRead());
|
||||||
|
parseAudioData(message.getType(), afterProperties);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Codec mismatch: expected" << _selectedCodecName << "got" << codecInPacket << "writing silence";
|
||||||
|
writeDroppableSilentSamples(networkSamples);
|
||||||
|
// inform others of the mismatch
|
||||||
|
auto sendingNode = DependencyManager::get<NodeList>()->nodeWithUUID(message.getSourceID());
|
||||||
|
emit mismatchedAudioCodec(sendingNode, _selectedCodecName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#ifndef hifi_InboundAudioStream_h
|
#ifndef hifi_InboundAudioStream_h
|
||||||
#define hifi_InboundAudioStream_h
|
#define hifi_InboundAudioStream_h
|
||||||
|
|
||||||
|
#include <Node.h>
|
||||||
#include <NodeData.h>
|
#include <NodeData.h>
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
#include <udt/PacketHeaders.h>
|
#include <udt/PacketHeaders.h>
|
||||||
|
@ -180,6 +181,9 @@ public:
|
||||||
void setupCodec(CodecPluginPointer codec, const QString& codecName, int numChannels);
|
void setupCodec(CodecPluginPointer codec, const QString& codecName, int numChannels);
|
||||||
void cleanupCodec();
|
void cleanupCodec();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void mismatchedAudioCodec(SharedNodePointer sendingNode, const QString& desiredCodec);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// This function should be called every second for all the stats to function properly. If dynamic jitter buffers
|
/// This function should be called every second for all the stats to function properly. If dynamic jitter buffers
|
||||||
/// is enabled, those stats are used to calculate _desiredJitterBufferFrames.
|
/// is enabled, those stats are used to calculate _desiredJitterBufferFrames.
|
||||||
|
|
|
@ -33,6 +33,7 @@ const uchar MAX_INJECTOR_VOLUME = 255;
|
||||||
int InjectedAudioStream::parseStreamProperties(PacketType type,
|
int InjectedAudioStream::parseStreamProperties(PacketType type,
|
||||||
const QByteArray& packetAfterSeqNum,
|
const QByteArray& packetAfterSeqNum,
|
||||||
int& numAudioSamples) {
|
int& numAudioSamples) {
|
||||||
|
|
||||||
// setup a data stream to read from this packet
|
// setup a data stream to read from this packet
|
||||||
QDataStream packetStream(packetAfterSeqNum);
|
QDataStream packetStream(packetAfterSeqNum);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "NodeList.h"
|
#include "NodeList.h"
|
||||||
#include "NetworkLogging.h"
|
#include "NetworkLogging.h"
|
||||||
#include "UserActivityLogger.h"
|
#include "UserActivityLogger.h"
|
||||||
|
#include "udt/PacketHeaders.h"
|
||||||
|
|
||||||
|
|
||||||
const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";
|
const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";
|
||||||
|
@ -37,6 +38,10 @@ AddressManager::AddressManager() :
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString AddressManager::protocolVersion() {
|
||||||
|
return protocolVersionsSignatureBase64();
|
||||||
|
}
|
||||||
|
|
||||||
bool AddressManager::isConnected() {
|
bool AddressManager::isConnected() {
|
||||||
return DependencyManager::get<NodeList>()->getDomainHandler().isConnected();
|
return DependencyManager::get<NodeList>()->getDomainHandler().isConnected();
|
||||||
}
|
}
|
||||||
|
@ -221,7 +226,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressManager::handleLookupString(const QString& lookupString) {
|
void AddressManager::handleLookupString(const QString& lookupString, bool fromSuggestions) {
|
||||||
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.trimmed();
|
QString sanitizedString = lookupString.trimmed();
|
||||||
|
@ -236,7 +241,7 @@ void AddressManager::handleLookupString(const QString& lookupString) {
|
||||||
lookupURL = QUrl(lookupString);
|
lookupURL = QUrl(lookupString);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUrl(lookupURL);
|
handleUrl(lookupURL, fromSuggestions ? Suggestions : UserInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ class AddressManager : public QObject, public Dependency {
|
||||||
Q_PROPERTY(QString hostname READ getHost)
|
Q_PROPERTY(QString hostname READ getHost)
|
||||||
Q_PROPERTY(QString pathname READ currentPath)
|
Q_PROPERTY(QString pathname READ currentPath)
|
||||||
public:
|
public:
|
||||||
|
Q_INVOKABLE QString protocolVersion();
|
||||||
using PositionGetter = std::function<glm::vec3()>;
|
using PositionGetter = std::function<glm::vec3()>;
|
||||||
using OrientationGetter = std::function<glm::quat()>;
|
using OrientationGetter = std::function<glm::quat()>;
|
||||||
|
|
||||||
|
@ -49,7 +50,8 @@ public:
|
||||||
StartupFromSettings,
|
StartupFromSettings,
|
||||||
DomainPathResponse,
|
DomainPathResponse,
|
||||||
Internal,
|
Internal,
|
||||||
AttemptedRefresh
|
AttemptedRefresh,
|
||||||
|
Suggestions
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isConnected();
|
bool isConnected();
|
||||||
|
@ -77,7 +79,7 @@ public:
|
||||||
std::function<void()> localSandboxNotRunningDoThat);
|
std::function<void()> localSandboxNotRunningDoThat);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void handleLookupString(const QString& lookupString);
|
void handleLookupString(const QString& lookupString, bool fromSuggestions = false);
|
||||||
|
|
||||||
// we currently expect this to be called from NodeList once handleLookupString has been called with a path
|
// we currently expect this to be called from NodeList once handleLookupString has been called with a path
|
||||||
bool goToViewpointForPath(const QString& viewpointString, const QString& pathString)
|
bool goToViewpointForPath(const QString& viewpointString, const QString& pathString)
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
#include "NetworkLogging.h"
|
#include "NetworkLogging.h"
|
||||||
#include "udt/Packet.h"
|
#include "udt/Packet.h"
|
||||||
|
|
||||||
const char SOLO_NODE_TYPES[2] = {
|
const std::set<NodeType_t> SOLO_NODE_TYPES = {
|
||||||
NodeType::AvatarMixer,
|
NodeType::AvatarMixer,
|
||||||
NodeType::AudioMixer
|
NodeType::AudioMixer
|
||||||
};
|
};
|
||||||
|
@ -551,7 +551,33 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
||||||
|
|
||||||
SharedNodePointer newNodePointer(newNode, &QObject::deleteLater);
|
SharedNodePointer newNodePointer(newNode, &QObject::deleteLater);
|
||||||
|
|
||||||
|
// if this is a solo node type, we assume that the DS has replaced its assignment and we should kill the previous node
|
||||||
|
if (SOLO_NODE_TYPES.count(newNode->getType())) {
|
||||||
|
// while we still have the read lock, see if there is a previous solo node we'll need to remove
|
||||||
|
auto previousSoloIt = std::find_if(_nodeHash.cbegin(), _nodeHash.cend(), [newNode](const UUIDNodePair& nodePair){
|
||||||
|
return nodePair.second->getType() == newNode->getType();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (previousSoloIt != _nodeHash.cend()) {
|
||||||
|
// we have a previous solo node, switch to a write lock so we can remove it
|
||||||
|
readLocker.unlock();
|
||||||
|
|
||||||
|
QWriteLocker writeLocker(&_nodeMutex);
|
||||||
|
|
||||||
|
auto oldSoloNode = previousSoloIt->second;
|
||||||
|
|
||||||
|
_nodeHash.unsafe_erase(previousSoloIt);
|
||||||
|
handleNodeKill(oldSoloNode);
|
||||||
|
|
||||||
|
// convert the current lock back to a read lock for insertion of new node
|
||||||
|
writeLocker.unlock();
|
||||||
|
readLocker.relock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the new node and release our read lock
|
||||||
_nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer));
|
_nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer));
|
||||||
|
readLocker.unlock();
|
||||||
|
|
||||||
qCDebug(networking) << "Added" << *newNode;
|
qCDebug(networking) << "Added" << *newNode;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
@ -46,7 +47,7 @@
|
||||||
|
|
||||||
const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
||||||
|
|
||||||
extern const char SOLO_NODE_TYPES[2];
|
extern const std::set<NodeType_t> SOLO_NODE_TYPES;
|
||||||
|
|
||||||
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
|
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
|
||||||
|
|
||||||
|
|
|
@ -178,6 +178,9 @@ void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QSt
|
||||||
case AddressManager::StartupFromSettings:
|
case AddressManager::StartupFromSettings:
|
||||||
trigger = "StartupFromSettings";
|
trigger = "StartupFromSettings";
|
||||||
break;
|
break;
|
||||||
|
case AddressManager::Suggestions:
|
||||||
|
trigger = "Suggesions";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,8 +154,7 @@ qint64 BasePacket::writeString(const QString& string) {
|
||||||
QByteArray data = string.toUtf8();
|
QByteArray data = string.toUtf8();
|
||||||
uint32_t length = data.length();
|
uint32_t length = data.length();
|
||||||
writePrimitive(length);
|
writePrimitive(length);
|
||||||
writeData(data.constData(), data.length());
|
write(data.constData(), data.length());
|
||||||
seek(pos() + length);
|
|
||||||
return length + sizeof(uint32_t);
|
return length + sizeof(uint32_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +175,6 @@ bool BasePacket::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 BasePacket::writeData(const char* data, qint64 maxSize) {
|
qint64 BasePacket::writeData(const char* data, qint64 maxSize) {
|
||||||
|
|
||||||
Q_ASSERT_X(maxSize <= bytesAvailableForWrite(), "BasePacket::writeData", "not enough space for write");
|
Q_ASSERT_X(maxSize <= bytesAvailableForWrite(), "BasePacket::writeData", "not enough space for write");
|
||||||
|
|
||||||
// make sure we have the space required to write this block
|
// make sure we have the space required to write this block
|
||||||
|
|
|
@ -72,6 +72,13 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::DomainServerAddedNode:
|
case PacketType::DomainServerAddedNode:
|
||||||
return static_cast<PacketVersion>(DomainServerAddedNodeVersion::PermissionsGrid);
|
return static_cast<PacketVersion>(DomainServerAddedNodeVersion::PermissionsGrid);
|
||||||
|
|
||||||
|
case PacketType::MixedAudio:
|
||||||
|
case PacketType::SilentAudioFrame:
|
||||||
|
case PacketType::InjectAudio:
|
||||||
|
case PacketType::MicrophoneAudioNoEcho:
|
||||||
|
case PacketType::MicrophoneAudioWithEcho:
|
||||||
|
return static_cast<PacketVersion>(AudioVersion::CodecNameInAudioPackets);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 17;
|
return 17;
|
||||||
}
|
}
|
||||||
|
@ -99,8 +106,9 @@ void sendWrongProtocolVersionsSignature(bool sendWrongVersion) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QByteArray protocolVersionsSignature() {
|
static QByteArray protocolVersionSignature;
|
||||||
static QByteArray protocolVersionSignature;
|
static QString protocolVersionSignatureBase64;
|
||||||
|
static void ensureProtocolVersionsSignature() {
|
||||||
static std::once_flag once;
|
static std::once_flag once;
|
||||||
std::call_once(once, [&] {
|
std::call_once(once, [&] {
|
||||||
QByteArray buffer;
|
QByteArray buffer;
|
||||||
|
@ -114,8 +122,11 @@ QByteArray protocolVersionsSignature() {
|
||||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||||
hash.addData(buffer);
|
hash.addData(buffer);
|
||||||
protocolVersionSignature = hash.result();
|
protocolVersionSignature = hash.result();
|
||||||
|
protocolVersionSignatureBase64 = protocolVersionSignature.toBase64();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
QByteArray protocolVersionsSignature() {
|
||||||
|
ensureProtocolVersionsSignature();
|
||||||
#if (PR_BUILD || DEV_BUILD)
|
#if (PR_BUILD || DEV_BUILD)
|
||||||
if (sendWrongProtocolVersion) {
|
if (sendWrongProtocolVersion) {
|
||||||
return QByteArray("INCORRECTVERSION"); // only for debugging version checking
|
return QByteArray("INCORRECTVERSION"); // only for debugging version checking
|
||||||
|
@ -124,3 +135,7 @@ QByteArray protocolVersionsSignature() {
|
||||||
|
|
||||||
return protocolVersionSignature;
|
return protocolVersionSignature;
|
||||||
}
|
}
|
||||||
|
QString protocolVersionsSignatureBase64() {
|
||||||
|
ensureProtocolVersionsSignature();
|
||||||
|
return protocolVersionSignatureBase64;
|
||||||
|
}
|
||||||
|
|
|
@ -113,6 +113,7 @@ extern const QSet<PacketType> NON_SOURCED_PACKETS;
|
||||||
|
|
||||||
PacketVersion versionForPacketType(PacketType packetType);
|
PacketVersion versionForPacketType(PacketType packetType);
|
||||||
QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols
|
QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols
|
||||||
|
QString protocolVersionsSignatureBase64();
|
||||||
|
|
||||||
#if (PR_BUILD || DEV_BUILD)
|
#if (PR_BUILD || DEV_BUILD)
|
||||||
void sendWrongProtocolVersionsSignature(bool sendWrongVersion); /// for debugging version negotiation
|
void sendWrongProtocolVersionsSignature(bool sendWrongVersion); /// for debugging version negotiation
|
||||||
|
@ -213,4 +214,9 @@ enum class DomainListVersion : PacketVersion {
|
||||||
PermissionsGrid
|
PermissionsGrid
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class AudioVersion : PacketVersion {
|
||||||
|
HasCompressedAudio = 17,
|
||||||
|
CodecNameInAudioPackets
|
||||||
|
};
|
||||||
|
|
||||||
#endif // hifi_PacketHeaders_h
|
#endif // hifi_PacketHeaders_h
|
||||||
|
|
|
@ -36,12 +36,12 @@ protected:
|
||||||
// It s changing every frame
|
// It s changing every frame
|
||||||
class FrameTransform {
|
class FrameTransform {
|
||||||
public:
|
public:
|
||||||
// Pixel info is { viemport width height and stereo on off}
|
// Pixel info is { viewport width height}
|
||||||
glm::vec4 pixelInfo;
|
glm::vec4 pixelInfo;
|
||||||
glm::vec4 invpixelInfo;
|
glm::vec4 invpixelInfo;
|
||||||
// Depth info is { n.f, f - n, -f}
|
// Depth info is { n.f, f - n, -f}
|
||||||
glm::vec4 depthInfo;
|
glm::vec4 depthInfo;
|
||||||
// Stereo info
|
// Stereo info is { isStereoFrame, halfWidth }
|
||||||
glm::vec4 stereoInfo{ 0.0 };
|
glm::vec4 stereoInfo{ 0.0 };
|
||||||
// Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space
|
// Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space
|
||||||
glm::mat4 projection[2];
|
glm::mat4 projection[2];
|
||||||
|
|
|
@ -1587,13 +1587,16 @@ function MyController(hand) {
|
||||||
this.clearEquipHaptics();
|
this.clearEquipHaptics();
|
||||||
|
|
||||||
// controller pose is in avatar frame
|
// controller pose is in avatar frame
|
||||||
var avatarControllerPose =
|
var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||||
Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand);
|
var avatarControllerPose = Controller.getPoseValue(device);
|
||||||
|
|
||||||
// transform it into world frame
|
// transform it into world frame
|
||||||
var controllerPositionVSAvatar = Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation);
|
var worldControllerPosition = Vec3.sum(MyAvatar.position,
|
||||||
var controllerPosition = Vec3.sum(MyAvatar.position, controllerPositionVSAvatar);
|
Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation));
|
||||||
var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation);
|
|
||||||
|
// also transform the position into room space
|
||||||
|
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||||
|
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
|
||||||
|
|
||||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
|
@ -1604,7 +1607,7 @@ function MyController(hand) {
|
||||||
this.currentObjectTime = now;
|
this.currentObjectTime = now;
|
||||||
this.currentCameraOrientation = Camera.orientation;
|
this.currentCameraOrientation = Camera.orientation;
|
||||||
|
|
||||||
this.grabRadius = Vec3.distance(this.currentObjectPosition, controllerPosition);
|
this.grabRadius = Vec3.distance(this.currentObjectPosition, worldControllerPosition);
|
||||||
this.grabRadialVelocity = 0.0;
|
this.grabRadialVelocity = 0.0;
|
||||||
|
|
||||||
// compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object
|
// compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object
|
||||||
|
@ -1639,8 +1642,7 @@ function MyController(hand) {
|
||||||
|
|
||||||
this.turnOffVisualizations();
|
this.turnOffVisualizations();
|
||||||
|
|
||||||
this.previousControllerPositionVSAvatar = controllerPositionVSAvatar;
|
this.previousRoomControllerPosition = roomControllerPosition;
|
||||||
this.previousControllerRotation = controllerRotation;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.distanceHolding = function (deltaTime, timestamp) {
|
this.distanceHolding = function (deltaTime, timestamp) {
|
||||||
|
@ -1653,13 +1655,17 @@ function MyController(hand) {
|
||||||
this.heartBeat(this.grabbedEntity);
|
this.heartBeat(this.grabbedEntity);
|
||||||
|
|
||||||
// controller pose is in avatar frame
|
// controller pose is in avatar frame
|
||||||
var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ?
|
var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||||
Controller.Standard.RightHand : Controller.Standard.LeftHand);
|
var avatarControllerPose = Controller.getPoseValue(device);
|
||||||
|
|
||||||
// transform it into world frame
|
// transform it into world frame
|
||||||
var controllerPositionVSAvatar = Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation);
|
var worldControllerPosition = Vec3.sum(MyAvatar.position,
|
||||||
var controllerPosition = Vec3.sum(MyAvatar.position, controllerPositionVSAvatar);
|
Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation));
|
||||||
var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation);
|
var worldControllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation);
|
||||||
|
|
||||||
|
// also transform the position into room space
|
||||||
|
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||||
|
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
|
||||||
|
|
||||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
||||||
|
|
||||||
|
@ -1668,26 +1674,16 @@ function MyController(hand) {
|
||||||
this.currentObjectTime = now;
|
this.currentObjectTime = now;
|
||||||
|
|
||||||
// the action was set up when this.distanceHolding was called. update the targets.
|
// the action was set up when this.distanceHolding was called. update the targets.
|
||||||
var radius = Vec3.distance(this.currentObjectPosition, controllerPosition) *
|
var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) *
|
||||||
this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR;
|
this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR;
|
||||||
if (radius < 1.0) {
|
if (radius < 1.0) {
|
||||||
radius = 1.0;
|
radius = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// scale delta controller hand movement by radius.
|
var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition);
|
||||||
var handMoved = Vec3.multiply(Vec3.subtract(controllerPositionVSAvatar, this.previousControllerPositionVSAvatar),
|
var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta);
|
||||||
radius);
|
var handMoved = Vec3.multiply(worldHandDelta, radius);
|
||||||
|
|
||||||
/// double delta controller rotation
|
|
||||||
// var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did
|
|
||||||
// var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation,
|
|
||||||
// controllerRotation,
|
|
||||||
// DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR),
|
|
||||||
// Quat.inverse(this.previousControllerRotation));
|
|
||||||
|
|
||||||
// update the currentObject position and rotation.
|
|
||||||
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved);
|
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved);
|
||||||
// this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
|
||||||
|
|
||||||
this.callEntityMethodOnGrabbed("continueDistantGrab");
|
this.callEntityMethodOnGrabbed("continueDistantGrab");
|
||||||
|
|
||||||
|
@ -1698,10 +1694,9 @@ function MyController(hand) {
|
||||||
var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData);
|
var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData);
|
||||||
|
|
||||||
// Update radialVelocity
|
// Update radialVelocity
|
||||||
var lastVelocity = Vec3.subtract(controllerPositionVSAvatar, this.previousControllerPositionVSAvatar);
|
var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime);
|
||||||
lastVelocity = Vec3.multiply(lastVelocity, 1.0 / deltaObjectTime);
|
var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition));
|
||||||
var newRadialVelocity = Vec3.dot(lastVelocity,
|
var newRadialVelocity = Vec3.dot(lastVelocity, delta);
|
||||||
Vec3.normalize(Vec3.subtract(grabbedProperties.position, controllerPosition)));
|
|
||||||
|
|
||||||
var VELOCITY_AVERAGING_TIME = 0.016;
|
var VELOCITY_AVERAGING_TIME = 0.016;
|
||||||
this.grabRadialVelocity = (deltaObjectTime / VELOCITY_AVERAGING_TIME) * newRadialVelocity +
|
this.grabRadialVelocity = (deltaObjectTime / VELOCITY_AVERAGING_TIME) * newRadialVelocity +
|
||||||
|
@ -1713,9 +1708,8 @@ function MyController(hand) {
|
||||||
this.grabRadius * RADIAL_GRAB_AMPLIFIER);
|
this.grabRadius * RADIAL_GRAB_AMPLIFIER);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(controllerRotation));
|
var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));
|
||||||
newTargetPosition = Vec3.sum(newTargetPosition, controllerPosition);
|
newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition);
|
||||||
|
|
||||||
|
|
||||||
var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position);
|
var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position);
|
||||||
if (handControllerData.disableMoveWithHead !== true) {
|
if (handControllerData.disableMoveWithHead !== true) {
|
||||||
|
@ -1771,8 +1765,7 @@ function MyController(hand) {
|
||||||
print("continueDistanceHolding -- updateAction failed");
|
print("continueDistanceHolding -- updateAction failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.previousControllerPositionVSAvatar = controllerPositionVSAvatar;
|
this.previousRoomControllerPosition = roomControllerPosition;
|
||||||
this.previousControllerRotation = controllerRotation;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setupHoldAction = function () {
|
this.setupHoldAction = function () {
|
||||||
|
|
|
@ -1,131 +0,0 @@
|
||||||
//
|
|
||||||
// handControllerMouse.js
|
|
||||||
// examples/controllers
|
|
||||||
//
|
|
||||||
// Created by Brad Hefta-Gaub on 2015/12/15
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
var DEBUGGING = false;
|
|
||||||
var angularVelocityTrailingAverage = 0.0; // Global trailing average used to decide whether to move reticle at all
|
|
||||||
var lastX = 0;
|
|
||||||
var lastY = 0;
|
|
||||||
|
|
||||||
Math.clamp=function(a,b,c) {
|
|
||||||
return Math.max(b,Math.min(c,a));
|
|
||||||
}
|
|
||||||
|
|
||||||
function length(posA, posB) {
|
|
||||||
var dx = posA.x - posB.x;
|
|
||||||
var dy = posA.y - posB.y;
|
|
||||||
var length = Math.sqrt((dx*dx) + (dy*dy))
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveReticleAbsolute(x, y) {
|
|
||||||
var globalPos = Reticle.getPosition();
|
|
||||||
globalPos.x = x;
|
|
||||||
globalPos.y = y;
|
|
||||||
Reticle.setPosition(globalPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation";
|
|
||||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
|
||||||
if (Controller.Hardware.Hydra !== undefined) {
|
|
||||||
mapping.from(Controller.Hardware.Hydra.L3).peek().to(Controller.Actions.ReticleClick);
|
|
||||||
mapping.from(Controller.Hardware.Hydra.R4).peek().to(Controller.Actions.ReticleClick);
|
|
||||||
}
|
|
||||||
if (Controller.Hardware.Vive !== undefined) {
|
|
||||||
mapping.from(Controller.Hardware.Vive.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
|
|
||||||
mapping.from(Controller.Hardware.Vive.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping.enable();
|
|
||||||
|
|
||||||
function debugPrint(message) {
|
|
||||||
if (DEBUGGING) {
|
|
||||||
print(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var leftRightBias = 0.0;
|
|
||||||
var filteredRotatedLeft = Vec3.UNIT_NEG_Y;
|
|
||||||
var filteredRotatedRight = Vec3.UNIT_NEG_Y;
|
|
||||||
var lastAlpha = 0;
|
|
||||||
|
|
||||||
Script.update.connect(function(deltaTime) {
|
|
||||||
|
|
||||||
// avatar frame
|
|
||||||
var poseRight = Controller.getPoseValue(Controller.Standard.RightHand);
|
|
||||||
var poseLeft = Controller.getPoseValue(Controller.Standard.LeftHand);
|
|
||||||
|
|
||||||
// NOTE: hack for now
|
|
||||||
var screenSize = Reticle.maximumPosition;
|
|
||||||
var screenSizeX = screenSize.x;
|
|
||||||
var screenSizeY = screenSize.y;
|
|
||||||
|
|
||||||
// transform hand facing vectors from avatar frame into sensor frame.
|
|
||||||
var worldToSensorMatrix = Mat4.inverse(MyAvatar.sensorToWorldMatrix);
|
|
||||||
var rotatedRight = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y)));
|
|
||||||
var rotatedLeft = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y)));
|
|
||||||
|
|
||||||
lastRotatedRight = rotatedRight;
|
|
||||||
|
|
||||||
// Decide which hand should be controlling the pointer
|
|
||||||
// by comparing which one is moving more, and by
|
|
||||||
// tending to stay with the one moving more.
|
|
||||||
if (deltaTime > 0.001) {
|
|
||||||
// leftRightBias is a running average of the difference in angular hand speed.
|
|
||||||
// a positive leftRightBias indicates the right hand is spinning faster then the left hand.
|
|
||||||
// a negative leftRightBias indicates the left hand is spnning faster.
|
|
||||||
var BIAS_ADJUST_PERIOD = 1.0;
|
|
||||||
var tau = Math.clamp(deltaTime / BIAS_ADJUST_PERIOD, 0, 1);
|
|
||||||
newLeftRightBias = Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity);
|
|
||||||
leftRightBias = (1 - tau) * leftRightBias + tau * newLeftRightBias;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add a bit of hysteresis to prevent control flopping back and forth
|
|
||||||
// between hands when they are both mostly stationary.
|
|
||||||
var alpha;
|
|
||||||
var HYSTERESIS_OFFSET = 0.25;
|
|
||||||
if (lastAlpha > 0.5) {
|
|
||||||
// prefer right hand over left
|
|
||||||
alpha = leftRightBias > -HYSTERESIS_OFFSET ? 1 : 0;
|
|
||||||
} else {
|
|
||||||
alpha = leftRightBias > HYSTERESIS_OFFSET ? 1 : 0;
|
|
||||||
}
|
|
||||||
lastAlpha = alpha;
|
|
||||||
|
|
||||||
// Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers
|
|
||||||
var VELOCITY_FILTER_GAIN = 0.5;
|
|
||||||
filteredRotatedLeft = Vec3.mix(filteredRotatedLeft, rotatedLeft, Math.clamp(Vec3.length(poseLeft.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
|
|
||||||
filteredRotatedRight = Vec3.mix(filteredRotatedRight, rotatedRight, Math.clamp(Vec3.length(poseRight.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
|
|
||||||
var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, alpha);
|
|
||||||
|
|
||||||
var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again...
|
|
||||||
var absoluteYaw = -rotated.x; // from -1 left to 1 right
|
|
||||||
|
|
||||||
var x = Math.clamp(screenSizeX * (absoluteYaw + 0.5), 0, screenSizeX);
|
|
||||||
var y = Math.clamp(screenSizeX * absolutePitch, 0, screenSizeY);
|
|
||||||
|
|
||||||
// don't move the reticle with the hand controllers unless the controllers are actually being moved
|
|
||||||
// take a time average of angular velocity, and don't move mouse at all if it's below threshold
|
|
||||||
|
|
||||||
var AVERAGING_INTERVAL = 0.95;
|
|
||||||
var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.03;
|
|
||||||
var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - alpha) + Vec3.length(poseRight.angularVelocity) * alpha;
|
|
||||||
angularVelocityTrailingAverage = angularVelocityTrailingAverage * AVERAGING_INTERVAL + angularVelocityMagnitude * (1.0 - AVERAGING_INTERVAL);
|
|
||||||
|
|
||||||
if ((angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) {
|
|
||||||
moveReticleAbsolute(x, y);
|
|
||||||
lastX = x;
|
|
||||||
lastY = y;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Script.scriptEnding.connect(function(){
|
|
||||||
mapping.disable();
|
|
||||||
});
|
|
|
@ -88,10 +88,13 @@ function Teleporter() {
|
||||||
|
|
||||||
this.createTargetOverlay = function() {
|
this.createTargetOverlay = function() {
|
||||||
|
|
||||||
|
if (_this.targetOverlay !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var targetOverlayProps = {
|
var targetOverlayProps = {
|
||||||
url: TARGET_MODEL_URL,
|
url: TARGET_MODEL_URL,
|
||||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||||
visible: true,
|
visible: true
|
||||||
};
|
};
|
||||||
|
|
||||||
_this.targetOverlay = Overlays.addOverlay("model", targetOverlayProps);
|
_this.targetOverlay = Overlays.addOverlay("model", targetOverlayProps);
|
||||||
|
@ -191,6 +194,9 @@ function Teleporter() {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.deleteTargetOverlay = function() {
|
this.deleteTargetOverlay = function() {
|
||||||
|
if (this.targetOverlay === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Overlays.deleteOverlay(this.targetOverlay);
|
Overlays.deleteOverlay(this.targetOverlay);
|
||||||
this.intersection = null;
|
this.intersection = null;
|
||||||
this.targetOverlay = null;
|
this.targetOverlay = null;
|
||||||
|
|
Loading…
Reference in a new issue